- Progress Update

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-01-23 12:35:58 -05:00
parent cfe0727447
commit f59bdf9030
3 changed files with 147 additions and 33 deletions

View File

@@ -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) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]}>
<LabelList dataKey="name" position="insideTop" />
<LabelList dataKey="value" position="insideBottom" />
</Cell>
));
}, [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 (
<Card loading={loading} title='Job Lifecycle Component'>
{!loading ? (
<Space direction='vertical' style={{width: '100%'}}>
<Card type='inner' title='Table Format'>
<Table columns={columns} dataSource={lifecycleData} />
lifecycleData ? (
<Space direction='vertical' style={{width: '100%'}}>
<Card type='inner' title='Table Format'>
<Table columns={columns} dataSource={lifecycleData.lifecycle}/>
</Card>
<Space direction='horizontal' style={{width: '100%'}} align='start'>
<Card type='inner' title='Timeline Format'>
<Timeline>
{lifecycleData.lifecycle.map((item, index) => (
<Timeline.Item key={index} color={item.value === 'Open' ? 'green' : item.value === 'Scheduled' ? 'yellow' : 'red'}>
{item.value} - {new Date(item.start).toLocaleString()}
</Timeline.Item>
))}
</Timeline>
</Card>
<Card type='inner' title='Durations'>
<PieChart width={400} height={400}>
<Pie
data={durationsData()}
cx={200}
cy={200}
labelLine={false}
label={({name, percent}) => `${name}: ${(percent * 100).toFixed(0)}%`}
outerRadius={80}
fill="#8884d8"
dataKey="value"
>
{renderCells()}
</Pie>
<Tooltip/>
<Legend/>
</PieChart>
</Card>
</Space>
</Space>
) : (
<Card type='inner' style={{textAlign: 'center'}}>
There is currently no lifecycle data for this job.
</Card>
<Card type='inner' title='Timeline Format'>
<Timeline>
{lifecycleData.map((item, index) => (
<Timeline.Item key={index} color={item.value === 'Open' ? 'green' : item.value === 'Scheduled' ? 'yellow' : 'red'}>
{item.value} - {new Date(item.start).toLocaleString()}
</Timeline.Item>
))}
</Timeline>
</Card>
</Space>
)
) : (
<Card type='inner' title='Loading'>
Loading Job Timelines....

View File

@@ -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
}
}`;

View File

@@ -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;