diff --git a/client/src/components/job-lifecycle/job-lifecycle.component.jsx b/client/src/components/job-lifecycle/job-lifecycle.component.jsx
index c9303177e..3d356c575 100644
--- a/client/src/components/job-lifecycle/job-lifecycle.component.jsx
+++ b/client/src/components/job-lifecycle/job-lifecycle.component.jsx
@@ -1,9 +1,10 @@
import {createStructuredSelector} from "reselect";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {connect} from "react-redux";
-import {useEffect, useState} from "react";
+import {useCallback, useEffect, useState} from "react";
import axios from "axios";
import {Card, Space, Table, Timeline} from "antd";
+import {Cell, LabelList, Legend, Pie, PieChart, Tooltip} from "recharts";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -12,6 +13,9 @@ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
+const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884d8'];
+
+
export function JobLifecycleComponent({bodyshop, job, ...rest}) {
const [loading, setLoading] = useState(true);
const [lifecycleData, setLifecycleData] = useState(null);
@@ -24,8 +28,8 @@ export function JobLifecycleComponent({bodyshop, job, ...rest}) {
const response = await axios.post("/job/lifecycle", {
jobids: job.id,
});
- console.dir(response.data.data.transitions, {depth: null});
- setLifecycleData(response.data.data.transitions);
+ const data = response.data.transition[job.id];
+ setLifecycleData(data);
setLoading(false);
}
}
@@ -36,6 +40,11 @@ export function JobLifecycleComponent({bodyshop, job, ...rest}) {
});
}, [job]);
+ // // TODO - Delete this useEffect, it is for testing
+ // useEffect(() => {
+ // console.dir(lifecycleData)
+ // }, [lifecycleData]);
+
const columnKeys = [
'start',
'end',
@@ -54,23 +63,82 @@ export function JobLifecycleComponent({bodyshop, job, ...rest}) {
key: key,
}));
+ /**
+ * Returns an array of cells for the Pie Chart
+ * @type {function(): *[]}
+ */
+ const renderCells = useCallback(() => {
+ const entires = Object
+ .entries(lifecycleData.durations)
+ .filter(([name, value]) => {
+ return value !== 0;
+ })
+
+ return entires.map(([name, value], index) => (
+ |
+
+
+ |
+ ));
+ }, [lifecycleData, job]);
+
+ /**
+ * Returns an array of objects with the name and value of the duration
+ * @type {function(): {name: *, value}[]}
+ */
+ const durationsData = useCallback(() => {
+ return Object.entries(lifecycleData.durations) .filter(([name, value]) => {
+ return value !== 0;
+ }).map(([name, value]) => ({
+ name,
+ value: value / 1000
+ }))
+ }, [lifecycleData, job]);
+
return (
{!loading ? (
-
-
-
+ lifecycleData ? (
+
+
+
+
+
+
+
+ {lifecycleData.lifecycle.map((item, index) => (
+
+ {item.value} - {new Date(item.start).toLocaleString()}
+
+ ))}
+
+
+
+
+ `${name}: ${(percent * 100).toFixed(0)}%`}
+ outerRadius={80}
+ fill="#8884d8"
+ dataKey="value"
+ >
+ {renderCells()}
+
+
+
+
+
+
+
+
+ ) : (
+
+ There is currently no lifecycle data for this job.
-
-
- {lifecycleData.map((item, index) => (
-
- {item.value} - {new Date(item.start).toLocaleString()}
-
- ))}
-
-
-
+ )
) : (
Loading Job Timelines....
diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js
index a642a4988..782bd3100 100644
--- a/server/graphql-client/queries.js
+++ b/server/graphql-client/queries.js
@@ -518,8 +518,8 @@ exports.QUERY_PAYMENTS_FOR_EXPORT = `
}
}`;
-exports.QUERY_TRANSITIONS_BY_JOBID = `query QUERY_TRANSITIONS_BY_JOBID($jobid: uuid!) {
- transitions(where: {jobid: {_eq: $jobid}}, order_by: {id: asc}) {
+exports.QUERY_TRANSITIONS_BY_JOBID = `query QUERY_TRANSITIONS_BY_JOBID($jobids: [uuid!]!) {
+ transitions(where: {jobid: {_in: $jobids}}, order_by: {created_at: asc}) {
start
end
value
@@ -529,6 +529,7 @@ exports.QUERY_TRANSITIONS_BY_JOBID = `query QUERY_TRANSITIONS_BY_JOBID($jobid: u
type
created_at
updated_at
+ jobid
}
}`;
diff --git a/server/job/job-lifecycle.js b/server/job/job-lifecycle.js
index 207a514e3..8eddd86a5 100644
--- a/server/job/job-lifecycle.js
+++ b/server/job/job-lifecycle.js
@@ -1,28 +1,73 @@
const _ = require("lodash");
const queries = require("../graphql-client/queries");
-const jobLifecycle = (req, res) => {
+
+const calculateStatusDuration = (transitions) => {
+ let statusDuration = {};
+
+ transitions.forEach((transition, index) => {
+ let duration = transition.duration;
+
+ // If there is no prev_value, it is the first transition
+ if (!transition.prev_value) {
+ statusDuration[transition.value] = duration;
+ }
+ // If there is no next_value, it is the last transition (the active one)
+ else if (!transition.next_value) {
+ if (statusDuration[transition.value]) {
+ statusDuration[transition.value] += duration;
+ } else {
+ statusDuration[transition.value] = duration;
+ }
+ }
+ // For all other transitions
+ else {
+ if (statusDuration[transition.value]) {
+ statusDuration[transition.value] += duration;
+ } else {
+ statusDuration[transition.value] = duration;
+ }
+ }
+ });
+
+ return statusDuration;
+}
+
+
+const jobLifecycle = async (req, res) => {
const {jobids} = req.body;
- return _.isArray(jobids) ?
- handleMultipleJobs(jobids, req, res) :
- handleSingleJob(jobids, req, res);
-};
+ const jobIDs = _.isArray(jobids) ? jobids : [jobids];
-const handleMultipleJobs = (jobIDs, req, res) => {
- return res.status(200).send(jobIDs);
-}
-
-const handleSingleJob = async (jobIds, req, res) => {
const client = req.userGraphQLClient;
- const resp = await client.request(queries.QUERY_TRANSITIONS_BY_JOBID, {jobid: jobIds,});
+ const resp = await client.request(queries.QUERY_TRANSITIONS_BY_JOBID, {jobids: jobIDs,});
+
+ const transitions = resp.transitions;
+
+ if (!transitions) {
+ return res.status(200).json({
+ jobIDs,
+ transitions: []
+ });
- const response = {
- jobIds,
- data: resp
}
- return res.status(200).json(response);
+ const transitionsByJobId = _.groupBy(resp.transitions, 'jobid');
+
+ const groupedTransitions = {};
+
+ for (let jobId in transitionsByJobId) {
+ groupedTransitions[jobId] = {
+ lifecycle: transitionsByJobId[jobId],
+ durations: calculateStatusDuration(transitionsByJobId[jobId])
+ };
+ }
+
+ return res.status(200).json({
+ jobIDs,
+ transition: groupedTransitions,
+ });
}
+
module.exports = jobLifecycle;
\ No newline at end of file