- Progress

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-01-24 10:07:07 -05:00
parent d0a2bb7da0
commit d740446ccb
3 changed files with 92 additions and 106 deletions

View File

@@ -1,139 +1,110 @@
import {createStructuredSelector} from "reselect";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {connect} from "react-redux";
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";
import React, {useEffect, useMemo, useState} from 'react';
import axios from 'axios';
import {Card, Space, Table, Timeline} from 'antd';
import {Bar, BarChart, CartesianGrid, LabelList, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis} from 'recharts';
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function JobLifecycleComponent({job, ...rest}) {
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884d8'];
export function JobLifecycleComponent({bodyshop, job, ...rest}) {
const [loading, setLoading] = useState(true);
const [lifecycleData, setLifecycleData] = useState(null);
useEffect(() => {
async function getLifecycleData() {
const getLifecycleData = async () => {
if (job && job.id) {
setLoading(true);
const response = await axios.post("/job/lifecycle", {
jobids: job.id,
});
const data = response.data.transition[job.id];
setLifecycleData(data);
setLoading(false);
try {
setLoading(true);
const response = await axios.post("/job/lifecycle", {jobids: job.id});
const data = response.data.transition[job.id];
setLifecycleData(data);
} catch (err) {
console.error(`Error getting Job Lifecycle Data: ${err.message}`);
} finally {
setLoading(false);
}
}
}
};
getLifecycleData().catch((err) => {
console.log(`Something went wrong getting Job Lifecycle Data: ${err.message}`);
setLoading(false);
});
getLifecycleData();
}, [job]);
// // TODO - Delete this useEffect, it is for testing
// useEffect(() => {
// console.dir(lifecycleData)
// }, [lifecycleData]);
const columnKeys = [
'start',
'end',
'value',
'prev_value',
'next_value',
'duration',
'type',
'created_at',
'updated_at',
'start_readable',
'end_readable',
'start', 'end', 'value', 'prev_value', 'next_value', 'duration', 'type', 'created_at', 'updated_at', 'start_readable', 'end_readable','duration'
];
const columns = columnKeys.map(key => ({
title: key.charAt(0).toUpperCase() + key.slice(1), // Capitalize the first letter for the title
title: key.charAt(0).toUpperCase() + key.slice(1),
dataIndex: key,
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]);
const durationsData = useMemo(() => {
if (!lifecycleData) {
return [];
}
/**
* 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]);
const transformedData = Object.entries(lifecycleData.durations).map(([name, {value, humanReadable}]) => {
return {
name,
amt: value,
pv: humanReadable,
uv: value,
}
})
return [transformedData];
}, [lifecycleData]);
useEffect(() => {
console.dir(lifecycleData, {depth: null})
console.dir(durationsData, {depth: null})
}, [lifecycleData,durationsData]);
return (
<Card loading={loading} title='Job Lifecycle Component'>
{!loading ? (
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'}>
<Timeline.Item key={index}
color={item.value === 'Open' ? 'green' : item.value === 'Scheduled' ? 'yellow' : 'red'}>
{item.value} - {item.start_readable}
</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"
<ResponsiveContainer width="100%" height="100%">
<BarChart
width={500}
height={300}
data={durationsData}
margin={{
top: 20,
right: 30,
left: 20,
bottom: 5,
}}
>
{renderCells()}
</Pie>
<Tooltip/>
<Legend/>
</PieChart>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="pv" stackId="a" fill="#8884d8" />
<Bar dataKey="uv" stackId="a" fill="#82ca9d" />
</BarChart>
</ResponsiveContainer>
</Card>
</Space>
<Card type='inner' title='Table Format'>
<Table columns={columns} dataSource={lifecycleData.lifecycle}/>
</Card>
</Space>
) : (
<Card type='inner' style={{textAlign: 'center'}}>
@@ -149,4 +120,4 @@ export function JobLifecycleComponent({bodyshop, job, ...rest}) {
);
}
export default connect(mapStateToProps, mapDispatchToProps)(JobLifecycleComponent);
export default JobLifecycleComponent;

View File

@@ -294,7 +294,7 @@ export function JobsDetailPage({
tab={<span><BarsOutlined />Lifecycle</span>}
key="lifecycle"
>
<JobLifecycleComponent job={job}/>
<JobLifecycleComponent job={job} refetch={refetch} form={form}/>
</Tabs.TabPane>
<Tabs.TabPane
forceRender

View File

@@ -6,26 +6,37 @@ const calculateStatusDuration = (transitions) => {
let statusDuration = {};
transitions.forEach((transition, index) => {
let duration = transition.duration;
let duration = transition.duration_minutes;
// If there is no prev_value, it is the first transition
if (!transition.prev_value) {
statusDuration[transition.value] = duration;
statusDuration[transition.value] = {
value: duration,
humanReadable: transition.duration_readable
};
}
// 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;
statusDuration[transition.value].value += duration;
statusDuration[transition.value].humanReadable = transition.duration_readable;
} else {
statusDuration[transition.value] = duration;
statusDuration[transition.value] = {
value: duration,
humanReadable: transition.duration_readable
};
}
}
// For all other transitions
else {
if (statusDuration[transition.value]) {
statusDuration[transition.value] += duration;
statusDuration[transition.value].value += duration;
statusDuration[transition.value].humanReadable = transition.duration_readable;
} else {
statusDuration[transition.value] = duration;
statusDuration[transition.value] = {
value: duration,
humanReadable: transition.duration_readable
};
}
}
});
@@ -53,7 +64,6 @@ const jobLifecycle = async (req, res) => {
}
const transitionsByJobId = _.groupBy(resp.transitions, 'jobid');
const groupedTransitions = {};
@@ -66,6 +76,11 @@ const jobLifecycle = async (req, res) => {
if (transition.end) {
transition.end_readable = moment(transition.end).fromNow();
}
if(transition.duration){
transition.duration_seconds = Math.round(transition.duration / 1000);
transition.duration_minutes = Math.round(transition.duration_seconds / 60);
transition.duration_readable = moment.duration(transition.duration).humanize();
}
return transition;
});
@@ -75,7 +90,7 @@ const jobLifecycle = async (req, res) => {
};
}
console.dir(groupedTransitions, {depth: null});
console.dir(groupedTransitions, {depth: null})
return res.status(200).json({
jobIDs,