Skip to content

Commit 1da27a1

Browse files
fix: handle missed actions in workspace timings (#17593)
Fix #16409 Since the provisioner timings action is not strongly typed, but it is typed as a generic string, and we are not using `noUncheckedIndexedAccess`, we can miss some of the actions returned from the API, causing type errors. To avoid that, I changed the code to be extra safe by adding `undefined` into the return type.
1 parent df47c30 commit 1da27a1

File tree

2 files changed

+89
-3
lines changed

2 files changed

+89
-3
lines changed

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

+13-3
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export const ResourcesChart: FC<ResourcesChartProps> = ({
5757
const theme = useTheme();
5858
const legendsByAction = getLegendsByAction(theme);
5959
const visibleLegends = [...new Set(visibleTimings.map((t) => t.action))].map(
60-
(a) => legendsByAction[a],
60+
(a) => legendsByAction[a] ?? { label: a },
6161
);
6262

6363
return (
@@ -99,6 +99,7 @@ export const ResourcesChart: FC<ResourcesChartProps> = ({
9999
<XAxisSection>
100100
{visibleTimings.map((t) => {
101101
const duration = calcDuration(t.range);
102+
const legend = legendsByAction[t.action] ?? { label: t.action };
102103

103104
return (
104105
<XAxisRow
@@ -117,7 +118,7 @@ export const ResourcesChart: FC<ResourcesChartProps> = ({
117118
value={duration}
118119
offset={calcOffset(t.range, generalTiming)}
119120
scale={scale}
120-
colors={legendsByAction[t.action].colors}
121+
colors={legend.colors}
121122
/>
122123
</Tooltip>
123124
{formatTime(duration)}
@@ -139,11 +140,20 @@ export const isCoderResource = (resource: string) => {
139140
);
140141
};
141142

142-
function getLegendsByAction(theme: Theme): Record<string, ChartLegend> {
143+
// TODO: We should probably strongly type the action attribute on
144+
// ProvisionerTiming to catch missing actions in the record. As a "workaround"
145+
// for now, we are using undefined since we don't have noUncheckedIndexedAccess
146+
// enabled.
147+
function getLegendsByAction(
148+
theme: Theme,
149+
): Record<string, ChartLegend | undefined> {
143150
return {
144151
"state refresh": {
145152
label: "state refresh",
146153
},
154+
provision: {
155+
label: "provision",
156+
},
147157
create: {
148158
label: "create",
149159
colors: {

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

+76
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,79 @@ export const LongTimeRange = {
152152
],
153153
},
154154
};
155+
156+
// We want to gracefully handle the case when the action is added in the BE but
157+
// not in the FE. This is a temporary fix until we can have strongly provisioner
158+
// timing action types in the BE.
159+
export const MissedAction: Story = {
160+
args: {
161+
agentConnectionTimings: [
162+
{
163+
ended_at: "2025-03-12T18:15:13.651163Z",
164+
stage: "connect",
165+
started_at: "2025-03-12T18:15:10.249068Z",
166+
workspace_agent_id: "41ab4fd4-44f8-4f3a-bb69-262ae85fba0b",
167+
workspace_agent_name: "Interface",
168+
},
169+
],
170+
agentScriptTimings: [
171+
{
172+
display_name: "Startup Script",
173+
ended_at: "2025-03-12T18:16:44.771508Z",
174+
exit_code: 0,
175+
stage: "start",
176+
started_at: "2025-03-12T18:15:13.847336Z",
177+
status: "ok",
178+
workspace_agent_id: "41ab4fd4-44f8-4f3a-bb69-262ae85fba0b",
179+
workspace_agent_name: "Interface",
180+
},
181+
],
182+
provisionerTimings: [
183+
{
184+
action: "create",
185+
ended_at: "2025-03-12T18:08:07.402358Z",
186+
job_id: "a7c4a05d-1c36-4264-8275-8107c93c5fc8",
187+
resource: "coder_agent.Interface",
188+
source: "coder",
189+
stage: "apply",
190+
started_at: "2025-03-12T18:08:07.194957Z",
191+
},
192+
{
193+
action: "create",
194+
ended_at: "2025-03-12T18:08:08.029908Z",
195+
job_id: "a7c4a05d-1c36-4264-8275-8107c93c5fc8",
196+
resource: "null_resource.validate_url",
197+
source: "null",
198+
stage: "apply",
199+
started_at: "2025-03-12T18:08:07.399387Z",
200+
},
201+
{
202+
action: "create",
203+
ended_at: "2025-03-12T18:08:07.440785Z",
204+
job_id: "a7c4a05d-1c36-4264-8275-8107c93c5fc8",
205+
resource: "module.emu_host.random_id.emulator_host_id",
206+
source: "random",
207+
stage: "apply",
208+
started_at: "2025-03-12T18:08:07.403171Z",
209+
},
210+
{
211+
action: "missed action",
212+
ended_at: "2025-03-12T18:08:08.029752Z",
213+
job_id: "a7c4a05d-1c36-4264-8275-8107c93c5fc8",
214+
resource: "null_resource.validate_url",
215+
source: "null",
216+
stage: "apply",
217+
started_at: "2025-03-12T18:08:07.410219Z",
218+
},
219+
],
220+
},
221+
play: async ({ canvasElement }) => {
222+
const user = userEvent.setup();
223+
const canvas = within(canvasElement);
224+
const applyButton = canvas.getByRole("button", {
225+
name: "View apply details",
226+
});
227+
await user.click(applyButton);
228+
await canvas.findByText("missed action");
229+
},
230+
};

0 commit comments

Comments
 (0)