248 lines
12 KiB
JavaScript
248 lines
12 KiB
JavaScript
import React, {useCallback, useEffect, useState} from 'react';
|
|
import day from '../../utils/day';
|
|
import axios from 'axios';
|
|
import {Badge, Card, Space, Table, Tag} from 'antd';
|
|
import {gql, useQuery} from "@apollo/client";
|
|
import {DateTimeFormatterFunction} from "../../utils/DateFormatter";
|
|
import {isEmpty} from "lodash";
|
|
import {useTranslation} from "react-i18next";
|
|
|
|
import './job-lifecycle.styles.scss';
|
|
|
|
// show text on bar if text can fit
|
|
export function JobLifecycleComponent({job, statuses, ...rest}) {
|
|
const [loading, setLoading] = useState(true);
|
|
const [lifecycleData, setLifecycleData] = useState(null);
|
|
const {t} = useTranslation(); // Used for tracking external state changes.
|
|
|
|
const {data} = useQuery(gql`
|
|
query get_job_test($id: uuid!){
|
|
jobs_by_pk(id:$id){
|
|
id
|
|
status
|
|
}
|
|
}
|
|
`, {
|
|
variables: {
|
|
id: job.id
|
|
},
|
|
fetchPolicy: 'cache-only'
|
|
});
|
|
|
|
/**
|
|
* Gets the lifecycle data for the job.
|
|
* @returns {Promise<void>}
|
|
*/
|
|
const getLifecycleData = useCallback(async () => {
|
|
if (job && job.id && statuses && statuses.statuses) {
|
|
try {
|
|
setLoading(true);
|
|
const response = await axios.post("/job/lifecycle", {
|
|
jobids: job.id,
|
|
statuses: statuses.statuses
|
|
});
|
|
const data = response.data.transition[job.id];
|
|
setLifecycleData(data);
|
|
} catch (err) {
|
|
console.error(`${t('job_lifecycle.errors.fetch')}: ${err.message}`);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
}, [job, statuses, t]);
|
|
|
|
useEffect(() => {
|
|
if (!data) return;
|
|
setTimeout(() => {
|
|
getLifecycleData().catch(err => console.error(`${t('job_lifecycle.errors.fetch')}: ${err.message}`));
|
|
}, 500);
|
|
}, [data, getLifecycleData, t]);
|
|
|
|
const columns = [
|
|
{
|
|
title: t('job_lifecycle.columns.value'),
|
|
dataIndex: 'value',
|
|
key: 'value',
|
|
},
|
|
{
|
|
title: t('job_lifecycle.columns.start'),
|
|
dataIndex: 'start',
|
|
key: 'start',
|
|
render: (text) => DateTimeFormatterFunction(text),
|
|
sorter: (a, b) => day(a.start).unix() - day(b.start).unix(),
|
|
},
|
|
{
|
|
title: t('job_lifecycle.columns.relative_start'),
|
|
dataIndex: 'start_readable',
|
|
key: 'start_readable',
|
|
},
|
|
{
|
|
title: t('job_lifecycle.columns.end'),
|
|
dataIndex: 'end',
|
|
key: 'end',
|
|
sorter: (a, b) => {
|
|
if (isEmpty(a.end) || isEmpty(b.end)) {
|
|
if (isEmpty(a.end) && isEmpty(b.end)) {
|
|
return 0;
|
|
}
|
|
return isEmpty(a.end) ? 1 : -1;
|
|
}
|
|
return day(a.end).unix() - day(b.end).unix();
|
|
},
|
|
render: (text) => isEmpty(text) ? t('job_lifecycle.content.not_available') : DateTimeFormatterFunction(text)
|
|
},
|
|
{
|
|
title: t('job_lifecycle.columns.relative_end'),
|
|
dataIndex: 'end_readable',
|
|
key: 'end_readable',
|
|
},
|
|
{
|
|
title: t('job_lifecycle.columns.duration'),
|
|
dataIndex: 'duration_readable',
|
|
key: 'duration_readable',
|
|
sorter: (a, b) => a.duration - b.duration,
|
|
},
|
|
];
|
|
|
|
return (
|
|
<Card loading={loading} title={t('job_lifecycle.content.title')}>
|
|
{!loading ? (
|
|
lifecycleData && lifecycleData.lifecycle && lifecycleData.durations ? (
|
|
<Space direction='vertical' style={{width: '100%'}}>
|
|
<Card
|
|
type='inner'
|
|
title={(
|
|
<Space direction='horizontal' size='small'>
|
|
<Badge status='processing' count={lifecycleData.durations.totalStatuses}/>
|
|
{t('job_lifecycle.content.title_durations')}
|
|
</Space>
|
|
|
|
)}
|
|
style={{width: '100%'}}
|
|
>
|
|
<div id="bar-container" style={{
|
|
display: 'flex',
|
|
width: '100%',
|
|
height: '100px',
|
|
textAlign: 'center',
|
|
borderRadius: '5px',
|
|
borderWidth: '5px',
|
|
borderStyle: 'solid',
|
|
borderColor: '#f0f2f5',
|
|
margin: 0,
|
|
padding: 0
|
|
}}>
|
|
{lifecycleData.durations.summations.map((key, index, array) => {
|
|
const isFirst = index === 0;
|
|
const isLast = index === array.length - 1;
|
|
return (
|
|
<div key={key.status} style={{
|
|
overflow: 'hidden',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
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,
|
|
|
|
backgroundColor: key.color,
|
|
width: `${key.percentage}%`
|
|
}}
|
|
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
|
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
|
>
|
|
|
|
{key.percentage > 15 ?
|
|
<>
|
|
<div>{key.roundedPercentage}</div>
|
|
<div style={{
|
|
backgroundColor: '#f0f2f5',
|
|
borderRadius: '5px',
|
|
paddingRight: '2px',
|
|
paddingLeft: '2px',
|
|
fontSize: '0.8rem',
|
|
}}>
|
|
{key.status}
|
|
</div>
|
|
</>
|
|
: null}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
<Card type='inner' title={t('job_lifecycle.content.legend_title')}
|
|
style={{marginTop: '10px'}}>
|
|
<div>
|
|
{lifecycleData.durations.summations.map((key) => (
|
|
<Tag key={key.status} 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>
|
|
{(lifecycleData?.durations?.humanReadableTotal) ||
|
|
(lifecycleData.lifecycle[0] && lifecycleData.lifecycle[0].value && lifecycleData?.durations?.totalCurrentStatusDuration?.humanReadable) ?
|
|
<Card style={{marginTop: '10px'}}>
|
|
<ul>
|
|
{lifecycleData.durations && lifecycleData.durations.humanReadableTotal &&
|
|
<li>
|
|
<span
|
|
style={{fontWeight: 'bold'}}>{t('job_lifecycle.content.previous_status_accumulated_time')}:</span> {lifecycleData.durations.humanReadableTotal}
|
|
</li>
|
|
}
|
|
{lifecycleData.lifecycle[0] && lifecycleData.lifecycle[0].value && lifecycleData?.durations?.totalCurrentStatusDuration?.humanReadable &&
|
|
<li>
|
|
<span
|
|
style={{fontWeight: 'bold'}}>{t('job_lifecycle.content.current_status_accumulated_time')} ({lifecycleData.lifecycle[0].value}):</span> {lifecycleData.durations.totalCurrentStatusDuration.humanReadable}
|
|
</li>
|
|
}
|
|
</ul>
|
|
</Card>
|
|
: null}
|
|
</Card>
|
|
<Card type='inner' title={(
|
|
<>
|
|
<Space direction="horizontal" size="small">
|
|
<Badge status='processing' count={lifecycleData.lifecycle.length}/>
|
|
{t('job_lifecycle.content.title_transitions')}
|
|
</Space>
|
|
</>
|
|
)}>
|
|
<Table style={{
|
|
overflow: 'auto',
|
|
width: '100%',
|
|
}} columns={columns} dataSource={lifecycleData.lifecycle} rowKey={"start"}/>
|
|
</Card>
|
|
</Space>
|
|
) : (
|
|
<Card type='inner' style={{textAlign: 'center'}}>
|
|
{t('job_lifecycle.content.data_unavailable')}
|
|
</Card>
|
|
)
|
|
) : (
|
|
<Card type='inner' title={t('job_lifecycle.content.title_loading')}>
|
|
{t('job_lifecycle.content.loading')}
|
|
</Card>
|
|
)}
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
export default JobLifecycleComponent;
|