diff --git a/site/src/modules/workspaces/WorkspaceTiming/Chart/utils.ts b/site/src/modules/workspaces/WorkspaceTiming/Chart/utils.ts index 2790701db5265..f125b8d1387f1 100644 --- a/site/src/modules/workspaces/WorkspaceTiming/Chart/utils.ts +++ b/site/src/modules/workspaces/WorkspaceTiming/Chart/utils.ts @@ -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}`; }; diff --git a/site/src/modules/workspaces/WorkspaceTiming/StagesChart.tsx b/site/src/modules/workspaces/WorkspaceTiming/StagesChart.tsx index 32e87f9ea5b76..c9e9f8d3a71b2 100644 --- a/site/src/modules/workspaces/WorkspaceTiming/StagesChart.tsx +++ b/site/src/modules/workspaces/WorkspaceTiming/StagesChart.tsx @@ -141,6 +141,7 @@ export const StagesChart: FC = ({ const value = calcDuration(t.range); const offset = calcOffset(t.range, totalRange); + const validDuration = value > 0 && !Number.isNaN(value); return ( = ({ ) : ( )} - {formatTime(calcDuration(t.range))} + {validDuration ? ( + {formatTime(value)} + ) : ( + ({ + color: theme.palette.error.main, + })} + > + Invalid + + )} ); })} diff --git a/site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.stories.tsx b/site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.stories.tsx index c2d1193d37fc1..36f08b36c0ca0 100644 --- a/site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.stories.tsx +++ b/site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.stories.tsx @@ -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", + }, + ], + }, +}; 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