diff --git a/client/src/components/job-lifecycle/job-lifecycle.component.jsx b/client/src/components/job-lifecycle/job-lifecycle.component.jsx new file mode 100644 index 000000000..9f5ac94fd --- /dev/null +++ b/client/src/components/job-lifecycle/job-lifecycle.component.jsx @@ -0,0 +1,246 @@ +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"; + +require('./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} + */ + 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 ( + + {!loading ? ( + lifecycleData && lifecycleData.lifecycle && lifecycleData.durations ? ( + + + + {t('job_lifecycle.content.title_durations')} + + + )} + style={{width: '100%'}} + > +
+ {lifecycleData.durations.summations.map((key, index, array) => { + const isFirst = index === 0; + const isLast = index === array.length - 1; + return ( +
+ + {key.percentage > 15 ? + <> +
{key.roundedPercentage}
+
+ {key.status} +
+ + : null} +
+ ); + })} +
+ +
+ {lifecycleData.durations.summations.map((key) => ( + +
+ {key.status} ({key.roundedPercentage}) +
+
+ ))} +
+
+ {(lifecycleData?.durations?.humanReadableTotal) || + (lifecycleData.lifecycle[0] && lifecycleData.lifecycle[0].value && lifecycleData?.durations?.totalCurrentStatusDuration?.humanReadable) ? + +
    + {lifecycleData.durations && lifecycleData.durations.humanReadableTotal && +
  • + {t('job_lifecycle.content.previous_status_accumulated_time')}: {lifecycleData.durations.humanReadableTotal} +
  • + } + {lifecycleData.lifecycle[0] && lifecycleData.lifecycle[0].value && lifecycleData?.durations?.totalCurrentStatusDuration?.humanReadable && +
  • + {t('job_lifecycle.content.current_status_accumulated_time')} ({lifecycleData.lifecycle[0].value}): {lifecycleData.durations.totalCurrentStatusDuration.humanReadable} +
  • + } +
+
+ : null} +
+ + + + {t('job_lifecycle.content.title_transitions')} + + + )}> + + + + ) : ( + + {t('job_lifecycle.content.data_unavailable')} + + ) + ) : ( + + {t('job_lifecycle.content.loading')} + + )} + + ); +} + +export default JobLifecycleComponent; \ No newline at end of file diff --git a/client/src/components/job-lifecycle/job-lifecycle.styles.scss b/client/src/components/job-lifecycle/job-lifecycle.styles.scss new file mode 100644 index 000000000..e69de29bb diff --git a/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx b/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx index ec1bd976b..9d95f6e80 100644 --- a/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx +++ b/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx @@ -52,7 +52,7 @@ export default function JobAdminDeleteIntake({ job }) { return ( <> - +