208 lines
7.1 KiB
JavaScript
208 lines
7.1 KiB
JavaScript
import { Card, Table, Tag } from "antd";
|
|
import axios from "axios";
|
|
import { useEffect, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import dayjs from "../../../utils/day";
|
|
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
|
|
import DashboardRefreshRequired from "../refresh-required.component";
|
|
|
|
const fortyFiveDaysAgo = () => dayjs().subtract(45, "day").toLocaleString();
|
|
|
|
export default function JobLifecycleDashboardComponent({ data, bodyshop, ...cardProps }) {
|
|
const { t } = useTranslation();
|
|
const [loading, setLoading] = useState(false);
|
|
const [lifecycleData, setLifecycleData] = useState(null);
|
|
|
|
useEffect(() => {
|
|
async function getLifecycleData() {
|
|
if (data && data.job_lifecycle) {
|
|
setLoading(true);
|
|
const response = await axios.post("/job/lifecycle", {
|
|
jobids: data.job_lifecycle.map((x) => x.id),
|
|
statuses: bodyshop.md_ro_statuses
|
|
});
|
|
setLifecycleData(response.data.durations);
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
getLifecycleData().catch((e) => {
|
|
console.error(`Error in getLifecycleData: ${e}`);
|
|
});
|
|
}, [data, bodyshop]);
|
|
|
|
const columns = [
|
|
{
|
|
title: t("job_lifecycle.columns.status"),
|
|
dataIndex: "status",
|
|
bgColor: "red",
|
|
key: "status",
|
|
render: (text, record) => {
|
|
return <Tag color={record.color}>{record.status}</Tag>;
|
|
}
|
|
},
|
|
{
|
|
title: t("job_lifecycle.columns.human_readable"),
|
|
dataIndex: "humanReadable",
|
|
key: "humanReadable"
|
|
},
|
|
{
|
|
title: t("job_lifecycle.columns.average_human_readable"),
|
|
dataIndex: "averageHumanReadable",
|
|
key: "averageHumanReadable"
|
|
},
|
|
{
|
|
title: t("job_lifecycle.columns.status_count"),
|
|
key: "statusCount",
|
|
render: (text, record) => {
|
|
return lifecycleData.statusCounts[record.status];
|
|
}
|
|
},
|
|
{
|
|
title: t("job_lifecycle.columns.percentage"),
|
|
dataIndex: "percentage",
|
|
key: "percentage",
|
|
render: (text, record) => {
|
|
return record.percentage.toFixed(2) + "%";
|
|
}
|
|
}
|
|
];
|
|
|
|
if (!data) return null;
|
|
if (!data.job_lifecycle || !lifecycleData) return <DashboardRefreshRequired {...cardProps} />;
|
|
|
|
const extra = `${t("job_lifecycle.content.calculated_based_on")} ${lifecycleData.jobs} ${t("job_lifecycle.content.jobs_in_since")} ${fortyFiveDaysAgo()}`;
|
|
|
|
return (
|
|
<Card title={t("job_lifecycle.titles.dashboard")} {...cardProps}>
|
|
<LoadingSkeleton loading={loading}>
|
|
<div style={{ overflow: "scroll", height: "100%" }}>
|
|
<div
|
|
id="bar-container"
|
|
style={{
|
|
display: "flex",
|
|
width: "100%",
|
|
height: "100px",
|
|
textAlign: "center",
|
|
borderRadius: "5px",
|
|
borderWidth: "5px",
|
|
borderStyle: "solid",
|
|
borderColor: "var(--bar-border-color)",
|
|
margin: 0,
|
|
padding: 0
|
|
}}
|
|
>
|
|
{lifecycleData.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 var(--bar-border-color)",
|
|
borderBottom: "1px solid var(--bar-border-color)",
|
|
borderLeft: isFirst ? "1px solid var(--bar-border-color)" : undefined,
|
|
borderRight: isLast ? "1px solid var(--bar-border-color)" : 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: "var(--tag-wrapper-bg)",
|
|
borderRadius: "5px",
|
|
paddingRight: "2px",
|
|
paddingLeft: "2px",
|
|
fontSize: "0.8rem"
|
|
}}
|
|
>
|
|
{key.status}
|
|
</div>
|
|
</>
|
|
) : null}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
<Card
|
|
extra={extra}
|
|
type="inner"
|
|
title={t("job_lifecycle.content.legend_title")}
|
|
style={{ marginTop: "10px" }}
|
|
>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
flexWrap: "wrap",
|
|
gap: 8
|
|
}}
|
|
>
|
|
{lifecycleData.summations.map((key) => (
|
|
<Tag
|
|
key={key.status}
|
|
color={key.color}
|
|
style={{
|
|
// IMPORTANT: let the tag grow with its content
|
|
width: "auto",
|
|
padding: 0,
|
|
margin: 0,
|
|
display: "inline-flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
boxSizing: "border-box"
|
|
}}
|
|
>
|
|
<div
|
|
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
|
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
|
style={{
|
|
backgroundColor: "var(--tag-wrapper-bg)",
|
|
color: "var(--tag-wrapper-text)",
|
|
padding: "4px 8px",
|
|
textAlign: "center",
|
|
whiteSpace: "nowrap" // keep it on one line while letting the pill expand
|
|
}}
|
|
>
|
|
{key.status} [{lifecycleData.statusCounts[key.status]}] ({key.roundedPercentage})
|
|
</div>
|
|
</Tag>
|
|
))}
|
|
</div>
|
|
</Card>
|
|
<Card style={{ marginTop: "5px" }} type="inner" title={t("job_lifecycle.titles.top_durations")}>
|
|
<Table
|
|
size="small"
|
|
pagination={false}
|
|
columns={columns}
|
|
rowKey={(record) => record.status}
|
|
dataSource={lifecycleData.summations.sort((a, b) => b.value - a.value).slice(0, 3)}
|
|
/>
|
|
</Card>
|
|
</div>
|
|
</LoadingSkeleton>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
export const JobLifecycleDashboardGQL = `
|
|
job_lifecycle: jobs(where: {
|
|
actual_in: {
|
|
_gte: "${dayjs().subtract(45, "day").toISOString()}"
|
|
}
|
|
}) {
|
|
id
|
|
actual_in
|
|
} `;
|