- Major Progress Commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -1,27 +1,12 @@
|
|||||||
import React, {useCallback, useEffect, useState} from 'react';
|
import React, {useCallback, useEffect, useState} from 'react';
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import {Card, Space, Table} from 'antd';
|
import {Badge, Card, Space, Table, Tag} from 'antd';
|
||||||
import {gql, useQuery} from "@apollo/client";
|
import {gql, useQuery} from "@apollo/client";
|
||||||
import {DateTimeFormatterFunction} from "../../utils/DateFormatter";
|
import {DateTimeFormatterFunction} from "../../utils/DateFormatter";
|
||||||
import {isEmpty} from "lodash";
|
import {isEmpty} from "lodash";
|
||||||
import {Bar, BarChart, CartesianGrid, Legend, Tooltip, YAxis} from "recharts";
|
|
||||||
|
|
||||||
|
require('./job-lifecycle.styles.scss');
|
||||||
const transformDataForChart = (durations) => {
|
|
||||||
const output = {};
|
|
||||||
// output.amt = durations.total;
|
|
||||||
// output.name = 'Total';
|
|
||||||
durations.summations.forEach((summation) => {
|
|
||||||
output[summation.status] = summation.value;
|
|
||||||
});
|
|
||||||
return [output];
|
|
||||||
}
|
|
||||||
const getColor = (key) => {
|
|
||||||
// Generate a random color
|
|
||||||
const randomColor = '#' + Math.floor(Math.random() * 16777215).toString(16);
|
|
||||||
return randomColor;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function JobLifecycleComponent({job, ...rest}) {
|
export function JobLifecycleComponent({job, ...rest}) {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -124,34 +109,92 @@ export function JobLifecycleComponent({job, ...rest}) {
|
|||||||
{!loading ? (
|
{!loading ? (
|
||||||
lifecycleData && lifecycleData.lifecycle && lifecycleData.durations ? (
|
lifecycleData && lifecycleData.lifecycle && lifecycleData.durations ? (
|
||||||
<Space direction='vertical' style={{width: '100%'}}>
|
<Space direction='vertical' style={{width: '100%'}}>
|
||||||
<Space direction='horizontal' style={{width: '100%'}} align='start'>
|
<Card
|
||||||
<Card type='inner' title='Durations'>
|
type='inner'
|
||||||
<BarChart
|
title={(
|
||||||
width={500}
|
<Space direction='horizontal' size='small'>
|
||||||
height={500}
|
<Badge status='processing' count={lifecycleData.durations.totalStatuses} />
|
||||||
data={transformDataForChart(lifecycleData.durations)}
|
Statuses
|
||||||
margin={{
|
</Space>
|
||||||
top: 20,
|
|
||||||
right: 30,
|
)}
|
||||||
left: 20,
|
style={{width: '100%'}}
|
||||||
bottom: 5,
|
>
|
||||||
}}
|
<div id="bar-container" style={{
|
||||||
>
|
display: 'flex',
|
||||||
<CartesianGrid strokeDasharray="3 3"/>
|
width: '100%',
|
||||||
<YAxis />
|
height: '100px',
|
||||||
<Tooltip/>
|
textAlign: 'center',
|
||||||
<Legend/>
|
borderRadius: '5px',
|
||||||
{
|
borderWidth: '5px',
|
||||||
Object.keys(transformDataForChart(lifecycleData.durations)[0]).map((key) => {
|
borderStyle: 'solid',
|
||||||
return (
|
borderColor: '#f0f2f5',
|
||||||
<Bar dataKey={key} stackId="a" fill={getColor(key)}/>
|
margin: 0,
|
||||||
)
|
padding: 0
|
||||||
})
|
}}>
|
||||||
}
|
{lifecycleData.durations.summations.map((key, index, array) => {
|
||||||
</BarChart>
|
const isFirst = index === 0;
|
||||||
|
const isLast = index === array.length - 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={key.status} style={{
|
||||||
|
overflow: 'hidden',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
borderTop: '1px solid #f0f2f5',
|
||||||
|
borderBottom: '1px solid #f0f2f5',
|
||||||
|
borderLeft: isFirst ? '1px solid #f0f2f5' : undefined,
|
||||||
|
borderRight: isLast ? '1px solid #f0f2f5' : undefined,
|
||||||
|
borderBottomLeftRadius: isFirst ? '5px' : undefined,
|
||||||
|
borderTopLeftRadius: isFirst ? '5px' : undefined,
|
||||||
|
borderBottomRightRadius: isLast ? '5px' : undefined,
|
||||||
|
borderTopRightRadius: isLast ? '5px' : undefined,
|
||||||
|
backgroundColor: key.color,
|
||||||
|
width: `${key.percentage}%`
|
||||||
|
}}
|
||||||
|
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
>
|
||||||
|
{Math.round(key.percentage)}%
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<Card type='inner' title='Legend' style={{marginTop: '10px'}}>
|
||||||
|
<div>
|
||||||
|
{lifecycleData.durations.summations.map((key) => (
|
||||||
|
<Tag color={key.color} style={{width: '13vh', padding: '4px', margin: '4px'}}>
|
||||||
|
<div
|
||||||
|
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#f0f2f5',
|
||||||
|
color: '#000',
|
||||||
|
padding: '4px',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}>
|
||||||
|
{key.status} ({key.roundedPercentage})
|
||||||
|
</div>
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Space>
|
<Card style={{marginTop: '10px'}}>
|
||||||
<Card type='inner' title='Transitions'>
|
<span style={{fontWeight: 'bold'}}>Accumulated Time:</span> {lifecycleData.durations.humanReadableTotal}
|
||||||
|
</Card>
|
||||||
|
</Card>
|
||||||
|
<Card type='inner' title={(
|
||||||
|
<>
|
||||||
|
<Space direction="horizontal" size="small">
|
||||||
|
<Badge status='processing' count={lifecycleData.lifecycle.length} />
|
||||||
|
Transitions
|
||||||
|
</Space>
|
||||||
|
</>
|
||||||
|
)}>
|
||||||
|
|
||||||
<Table columns={columns} dataSource={lifecycleData.lifecycle}/>
|
<Table columns={columns} dataSource={lifecycleData.lifecycle}/>
|
||||||
</Card>
|
</Card>
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
@@ -289,13 +289,6 @@ export function JobsDetailPage({
|
|||||||
form={form}
|
form={form}
|
||||||
/>
|
/>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane
|
|
||||||
forceRender
|
|
||||||
tab={<span><BarsOutlined />Lifecycle</span>}
|
|
||||||
key="lifecycle"
|
|
||||||
>
|
|
||||||
<JobLifecycleComponent job={job} refetch={refetch} form={form}/>
|
|
||||||
</Tabs.TabPane>
|
|
||||||
<Tabs.TabPane
|
<Tabs.TabPane
|
||||||
forceRender
|
forceRender
|
||||||
tab={
|
tab={
|
||||||
@@ -341,7 +334,15 @@ export function JobsDetailPage({
|
|||||||
>
|
>
|
||||||
<JobsDetailLaborContainer job={job} jobId={job.id} />
|
<JobsDetailLaborContainer job={job} jobId={job.id} />
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane
|
<Tabs.TabPane
|
||||||
|
forceRender
|
||||||
|
tab={<span><BarsOutlined />Lifecycle</span>}
|
||||||
|
key="lifecycle"
|
||||||
|
>
|
||||||
|
<JobLifecycleComponent job={job} refetch={refetch} form={form}/>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
|
||||||
|
<Tabs.TabPane
|
||||||
forceRender
|
forceRender
|
||||||
tab={
|
tab={
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
const moment = require('moment');
|
|
||||||
const durationToHumanReadable = require("./durationToHumanReadable");
|
const durationToHumanReadable = require("./durationToHumanReadable");
|
||||||
/**
|
const moment = require("moment");
|
||||||
* Calculate the duration of each status of a job
|
const _ = require("lodash");
|
||||||
* @param transitions
|
const crypto = require('crypto');
|
||||||
* @returns {{}}
|
|
||||||
*/
|
const getColor = (key) => {
|
||||||
|
const hash = crypto.createHash('sha256');
|
||||||
|
hash.update(key);
|
||||||
|
const hashedKey = hash.digest('hex');
|
||||||
|
const num = parseInt(hashedKey, 16);
|
||||||
|
return '#' + (num % 16777215).toString(16).padStart(6, '0');
|
||||||
|
};
|
||||||
|
|
||||||
const calculateStatusDuration = (transitions) => {
|
const calculateStatusDuration = (transitions) => {
|
||||||
let statusDuration = {};
|
let statusDuration = {};
|
||||||
let totalDuration = 0;
|
let totalDuration = 0;
|
||||||
let summations = [];
|
let summations = [];
|
||||||
|
|
||||||
transitions.forEach((transition, index) => {
|
transitions.forEach((transition, index) => {
|
||||||
let duration = transition.duration_minutes;
|
let duration = transition.duration;
|
||||||
totalDuration += duration;
|
totalDuration += duration;
|
||||||
|
|
||||||
if (!transition.prev_value) {
|
if (!transition.prev_value) {
|
||||||
@@ -42,16 +48,31 @@ const calculateStatusDuration = (transitions) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Calculate the percentage for each status
|
||||||
|
// Calculate the percentage for each status
|
||||||
|
let totalPercentage = 0;
|
||||||
|
const statusKeys = Object.keys(statusDuration);
|
||||||
|
statusKeys.forEach((status, index) => {
|
||||||
|
if (index !== statusKeys.length - 1) {
|
||||||
|
const percentage = (statusDuration[status].value / totalDuration) * 100;
|
||||||
|
totalPercentage += percentage;
|
||||||
|
statusDuration[status].percentage = percentage;
|
||||||
|
} else {
|
||||||
|
statusDuration[status].percentage = 100 - totalPercentage;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
for (let [status, {value, humanReadable}] of Object.entries(statusDuration)) {
|
for (let [status, {value, humanReadable}] of Object.entries(statusDuration)) {
|
||||||
if (status !== 'total') {
|
if (status !== 'total') {
|
||||||
summations.push({status, value, humanReadable});
|
summations.push({status, value, humanReadable, percentage: statusDuration[status].percentage, color: getColor(status), roundedPercentage: `${Math.round(statusDuration[status].percentage)}%`});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const humanReadableTotal = durationToHumanReadable(moment.duration(totalDuration));
|
const humanReadableTotal = durationToHumanReadable(moment.duration(totalDuration));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
summations,
|
summations: _.orderBy(summations, ['value'], ['asc']),
|
||||||
|
totalStatuses: summations.length,
|
||||||
total: totalDuration,
|
total: totalDuration,
|
||||||
humanReadableTotal
|
humanReadableTotal
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user