Skip to content

Commit ce134bc

Browse files
authored
fix: handle invalid provisioning timings in ui (#18058)
Relates to #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.
1 parent 565fad5 commit ce134bc

File tree

3 files changed

+99
-4
lines changed

3 files changed

+99
-4
lines changed

site/src/modules/workspaces/WorkspaceTiming/Chart/utils.ts

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import dayjs from "dayjs";
2+
13
export type TimeRange = {
24
startedAt: Date;
35
endedAt: Date;
@@ -11,12 +13,26 @@ export const mergeTimeRanges = (ranges: TimeRange[]): TimeRange => {
1113
const sortedDurations = ranges
1214
.slice()
1315
.sort((a, b) => a.startedAt.getTime() - b.startedAt.getTime());
14-
const start = sortedDurations[0].startedAt;
1516

1617
const sortedEndDurations = [...ranges].sort(
1718
(a, b) => a.endedAt.getTime() - b.endedAt.getTime(),
1819
);
1920
const end = sortedEndDurations[sortedEndDurations.length - 1].endedAt;
21+
22+
// Ref: #15432: if there start time is the 'zero' value, default
23+
// to the end time. This will result in an 'instant'.
24+
let start: Date = end;
25+
for (const r of sortedDurations) {
26+
if (
27+
Number.isNaN(r.startedAt.getTime()) ||
28+
r.startedAt.getUTCFullYear() <= 1
29+
) {
30+
continue; // Skip invalid start dates.
31+
}
32+
start = r.startedAt;
33+
break;
34+
}
35+
2036
return { startedAt: start, endedAt: end };
2137
};
2238

@@ -33,7 +49,12 @@ const second = 1_000;
3349
const minute = 60 * second;
3450
const hour = 60 * minute;
3551
const day = 24 * hour;
52+
const week = 7 * day;
53+
const year = 365 * day; // Unlikely, and leap years won't matter here.
54+
3655
const scales = [
56+
year,
57+
week,
3758
day,
3859
hour,
3960
5 * minute,
@@ -44,6 +65,8 @@ const scales = [
4465
100,
4566
];
4667

68+
const zeroTime: Date = dayjs("0001-01-01T00:00:00Z").toDate();
69+
4770
const pickScale = (totalTime: number): number => {
4871
for (const s of scales) {
4972
if (totalTime > s) {
@@ -64,6 +87,7 @@ export const formatTime = (time: number): string => {
6487
const absTime = Math.abs(time);
6588
let unit = "";
6689
let value = 0;
90+
let frac = 2;
6791

6892
if (absTime < second) {
6993
value = time;
@@ -74,15 +98,26 @@ export const formatTime = (time: number): string => {
7498
} else if (absTime < hour) {
7599
value = time / minute;
76100
unit = "m";
101+
frac = 1;
77102
} else if (absTime < day) {
78103
value = time / hour;
79104
unit = "h";
80-
} else {
105+
frac = 0;
106+
} else if (absTime < week) {
81107
value = time / day;
82108
unit = "d";
109+
frac = 0;
110+
} else if (absTime < year) {
111+
value = time / week;
112+
unit = "w";
113+
frac = 0;
114+
} else {
115+
value = time / year;
116+
unit = "y";
117+
frac = 0;
83118
}
84119
return `${value.toLocaleString(undefined, {
85-
maximumFractionDigits: 2,
120+
maximumFractionDigits: frac,
86121
})}${unit}`;
87122
};
88123

site/src/modules/workspaces/WorkspaceTiming/StagesChart.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ export const StagesChart: FC<StagesChartProps> = ({
141141

142142
const value = calcDuration(t.range);
143143
const offset = calcOffset(t.range, totalRange);
144+
const validDuration = value > 0 && !Number.isNaN(value);
144145

145146
return (
146147
<XAxisRow
@@ -172,7 +173,17 @@ export const StagesChart: FC<StagesChartProps> = ({
172173
) : (
173174
<Bar scale={scale} value={value} offset={offset} />
174175
)}
175-
{formatTime(calcDuration(t.range))}
176+
{validDuration ? (
177+
<span>{formatTime(value)}</span>
178+
) : (
179+
<span
180+
css={(theme) => ({
181+
color: theme.palette.error.main,
182+
})}
183+
>
184+
Invalid
185+
</span>
186+
)}
176187
</XAxisRow>
177188
);
178189
})}

site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.stories.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,52 @@ export const MissedAction: Story = {
228228
await canvas.findByText("missed action");
229229
},
230230
};
231+
232+
// Ref: #15432
233+
export const InvalidTimeRange: Story = {
234+
args: {
235+
provisionerTimings: [
236+
{
237+
...WorkspaceTimingsResponse.provisioner_timings[0],
238+
stage: "init",
239+
started_at: "2025-01-01T00:00:00Z",
240+
ended_at: "2025-01-01T00:01:00Z",
241+
},
242+
{
243+
...WorkspaceTimingsResponse.provisioner_timings[0],
244+
stage: "plan",
245+
started_at: "2025-01-01T00:01:00Z",
246+
ended_at: "0001-01-01T00:00:00Z",
247+
},
248+
{
249+
...WorkspaceTimingsResponse.provisioner_timings[0],
250+
stage: "graph",
251+
started_at: "0001-01-01T00:00:00Z",
252+
ended_at: "2025-01-01T00:03:00Z",
253+
},
254+
{
255+
...WorkspaceTimingsResponse.provisioner_timings[0],
256+
stage: "apply",
257+
started_at: "2025-01-01T00:03:00Z",
258+
ended_at: "2025-01-01T00:04:00Z",
259+
},
260+
],
261+
agentConnectionTimings: [
262+
{
263+
started_at: "2025-01-01T00:05:00Z",
264+
ended_at: "2025-01-01T00:06:00Z",
265+
stage: "connect",
266+
workspace_agent_id: "67e37a9d-ccac-497e-8f48-4093bcc4f3e7",
267+
workspace_agent_name: "main",
268+
},
269+
],
270+
agentScriptTimings: [
271+
{
272+
...WorkspaceTimingsResponse.agent_script_timings[0],
273+
display_name: "Startup Script 1",
274+
started_at: "0001-01-01T00:00:00Z",
275+
ended_at: "2025-01-01T00:10:00Z",
276+
},
277+
],
278+
},
279+
};

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy