fix: handle invalid provisioning timings in ui (#18058)

Relates to https://github.com/coder/coder/issues/15432

* Adds a storybook entry for zero values in provisioner timings.
* Coerces a 'zero' start time to an 'instant'.
* Improves timing chart handling for large timeframes. Previously, this
would have caused the tab to run out of memory when encountering a
`time.Time{}`.
* Render 'instants' as 'invalid' in timing chart.
This commit is contained in:
Cian Johnston
2025-05-27 15:10:57 +01:00
committed by GitHub
parent 565fad5951
commit ce134bc63a
3 changed files with 99 additions and 4 deletions
@@ -1,3 +1,5 @@
import dayjs from "dayjs";
export type TimeRange = {
startedAt: Date;
endedAt: Date;
@@ -11,12 +13,26 @@ export const mergeTimeRanges = (ranges: TimeRange[]): TimeRange => {
const sortedDurations = ranges
.slice()
.sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
const start = sortedDurations[0].startedAt;
const sortedEndDurations = [...ranges].sort(
(a, b) => a.endedAt.getTime() - b.endedAt.getTime(),
);
const end = sortedEndDurations[sortedEndDurations.length - 1].endedAt;
// Ref: #15432: if there start time is the 'zero' value, default
// to the end time. This will result in an 'instant'.
let start: Date = end;
for (const r of sortedDurations) {
if (
Number.isNaN(r.startedAt.getTime()) ||
r.startedAt.getUTCFullYear() <= 1
) {
continue; // Skip invalid start dates.
}
start = r.startedAt;
break;
}
return { startedAt: start, endedAt: end };
};
@@ -33,7 +49,12 @@ const second = 1_000;
const minute = 60 * second;
const hour = 60 * minute;
const day = 24 * hour;
const week = 7 * day;
const year = 365 * day; // Unlikely, and leap years won't matter here.
const scales = [
year,
week,
day,
hour,
5 * minute,
@@ -44,6 +65,8 @@ const scales = [
100,
];
const zeroTime: Date = dayjs("0001-01-01T00:00:00Z").toDate();
const pickScale = (totalTime: number): number => {
for (const s of scales) {
if (totalTime > s) {
@@ -64,6 +87,7 @@ export const formatTime = (time: number): string => {
const absTime = Math.abs(time);
let unit = "";
let value = 0;
let frac = 2;
if (absTime < second) {
value = time;
@@ -74,15 +98,26 @@ export const formatTime = (time: number): string => {
} else if (absTime < hour) {
value = time / minute;
unit = "m";
frac = 1;
} else if (absTime < day) {
value = time / hour;
unit = "h";
} else {
frac = 0;
} else if (absTime < week) {
value = time / day;
unit = "d";
frac = 0;
} else if (absTime < year) {
value = time / week;
unit = "w";
frac = 0;
} else {
value = time / year;
unit = "y";
frac = 0;
}
return `${value.toLocaleString(undefined, {
maximumFractionDigits: 2,
maximumFractionDigits: frac,
})}${unit}`;
};
@@ -141,6 +141,7 @@ export const StagesChart: FC<StagesChartProps> = ({
const value = calcDuration(t.range);
const offset = calcOffset(t.range, totalRange);
const validDuration = value > 0 && !Number.isNaN(value);
return (
<XAxisRow
@@ -172,7 +173,17 @@ export const StagesChart: FC<StagesChartProps> = ({
) : (
<Bar scale={scale} value={value} offset={offset} />
)}
{formatTime(calcDuration(t.range))}
{validDuration ? (
<span>{formatTime(value)}</span>
) : (
<span
css={(theme) => ({
color: theme.palette.error.main,
})}
>
Invalid
</span>
)}
</XAxisRow>
);
})}
@@ -228,3 +228,52 @@ export const MissedAction: Story = {
await canvas.findByText("missed action");
},
};
// Ref: #15432
export const InvalidTimeRange: Story = {
args: {
provisionerTimings: [
{
...WorkspaceTimingsResponse.provisioner_timings[0],
stage: "init",
started_at: "2025-01-01T00:00:00Z",
ended_at: "2025-01-01T00:01:00Z",
},
{
...WorkspaceTimingsResponse.provisioner_timings[0],
stage: "plan",
started_at: "2025-01-01T00:01:00Z",
ended_at: "0001-01-01T00:00:00Z",
},
{
...WorkspaceTimingsResponse.provisioner_timings[0],
stage: "graph",
started_at: "0001-01-01T00:00:00Z",
ended_at: "2025-01-01T00:03:00Z",
},
{
...WorkspaceTimingsResponse.provisioner_timings[0],
stage: "apply",
started_at: "2025-01-01T00:03:00Z",
ended_at: "2025-01-01T00:04:00Z",
},
],
agentConnectionTimings: [
{
started_at: "2025-01-01T00:05:00Z",
ended_at: "2025-01-01T00:06:00Z",
stage: "connect",
workspace_agent_id: "67e37a9d-ccac-497e-8f48-4093bcc4f3e7",
workspace_agent_name: "main",
},
],
agentScriptTimings: [
{
...WorkspaceTimingsResponse.agent_script_timings[0],
display_name: "Startup Script 1",
started_at: "0001-01-01T00:00:00Z",
ended_at: "2025-01-01T00:10:00Z",
},
],
},
};