mirror of
https://github.com/coder/coder.git
synced 2025-07-18 14:17:22 +00:00
fix: handle missed actions in workspace timings (#17593)
Fix https://github.com/coder/coder/issues/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.
This commit is contained in:
@ -57,7 +57,7 @@ export const ResourcesChart: FC<ResourcesChartProps> = ({
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const legendsByAction = getLegendsByAction(theme);
|
const legendsByAction = getLegendsByAction(theme);
|
||||||
const visibleLegends = [...new Set(visibleTimings.map((t) => t.action))].map(
|
const visibleLegends = [...new Set(visibleTimings.map((t) => t.action))].map(
|
||||||
(a) => legendsByAction[a],
|
(a) => legendsByAction[a] ?? { label: a },
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -99,6 +99,7 @@ export const ResourcesChart: FC<ResourcesChartProps> = ({
|
|||||||
<XAxisSection>
|
<XAxisSection>
|
||||||
{visibleTimings.map((t) => {
|
{visibleTimings.map((t) => {
|
||||||
const duration = calcDuration(t.range);
|
const duration = calcDuration(t.range);
|
||||||
|
const legend = legendsByAction[t.action] ?? { label: t.action };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<XAxisRow
|
<XAxisRow
|
||||||
@ -117,7 +118,7 @@ export const ResourcesChart: FC<ResourcesChartProps> = ({
|
|||||||
value={duration}
|
value={duration}
|
||||||
offset={calcOffset(t.range, generalTiming)}
|
offset={calcOffset(t.range, generalTiming)}
|
||||||
scale={scale}
|
scale={scale}
|
||||||
colors={legendsByAction[t.action].colors}
|
colors={legend.colors}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{formatTime(duration)}
|
{formatTime(duration)}
|
||||||
@ -139,11 +140,20 @@ export const isCoderResource = (resource: string) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function getLegendsByAction(theme: Theme): Record<string, ChartLegend> {
|
// TODO: We should probably strongly type the action attribute on
|
||||||
|
// ProvisionerTiming to catch missing actions in the record. As a "workaround"
|
||||||
|
// for now, we are using undefined since we don't have noUncheckedIndexedAccess
|
||||||
|
// enabled.
|
||||||
|
function getLegendsByAction(
|
||||||
|
theme: Theme,
|
||||||
|
): Record<string, ChartLegend | undefined> {
|
||||||
return {
|
return {
|
||||||
"state refresh": {
|
"state refresh": {
|
||||||
label: "state refresh",
|
label: "state refresh",
|
||||||
},
|
},
|
||||||
|
provision: {
|
||||||
|
label: "provision",
|
||||||
|
},
|
||||||
create: {
|
create: {
|
||||||
label: "create",
|
label: "create",
|
||||||
colors: {
|
colors: {
|
||||||
|
@ -152,3 +152,79 @@ export const LongTimeRange = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// We want to gracefully handle the case when the action is added in the BE but
|
||||||
|
// not in the FE. This is a temporary fix until we can have strongly provisioner
|
||||||
|
// timing action types in the BE.
|
||||||
|
export const MissedAction: Story = {
|
||||||
|
args: {
|
||||||
|
agentConnectionTimings: [
|
||||||
|
{
|
||||||
|
ended_at: "2025-03-12T18:15:13.651163Z",
|
||||||
|
stage: "connect",
|
||||||
|
started_at: "2025-03-12T18:15:10.249068Z",
|
||||||
|
workspace_agent_id: "41ab4fd4-44f8-4f3a-bb69-262ae85fba0b",
|
||||||
|
workspace_agent_name: "Interface",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
agentScriptTimings: [
|
||||||
|
{
|
||||||
|
display_name: "Startup Script",
|
||||||
|
ended_at: "2025-03-12T18:16:44.771508Z",
|
||||||
|
exit_code: 0,
|
||||||
|
stage: "start",
|
||||||
|
started_at: "2025-03-12T18:15:13.847336Z",
|
||||||
|
status: "ok",
|
||||||
|
workspace_agent_id: "41ab4fd4-44f8-4f3a-bb69-262ae85fba0b",
|
||||||
|
workspace_agent_name: "Interface",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
provisionerTimings: [
|
||||||
|
{
|
||||||
|
action: "create",
|
||||||
|
ended_at: "2025-03-12T18:08:07.402358Z",
|
||||||
|
job_id: "a7c4a05d-1c36-4264-8275-8107c93c5fc8",
|
||||||
|
resource: "coder_agent.Interface",
|
||||||
|
source: "coder",
|
||||||
|
stage: "apply",
|
||||||
|
started_at: "2025-03-12T18:08:07.194957Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "create",
|
||||||
|
ended_at: "2025-03-12T18:08:08.029908Z",
|
||||||
|
job_id: "a7c4a05d-1c36-4264-8275-8107c93c5fc8",
|
||||||
|
resource: "null_resource.validate_url",
|
||||||
|
source: "null",
|
||||||
|
stage: "apply",
|
||||||
|
started_at: "2025-03-12T18:08:07.399387Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "create",
|
||||||
|
ended_at: "2025-03-12T18:08:07.440785Z",
|
||||||
|
job_id: "a7c4a05d-1c36-4264-8275-8107c93c5fc8",
|
||||||
|
resource: "module.emu_host.random_id.emulator_host_id",
|
||||||
|
source: "random",
|
||||||
|
stage: "apply",
|
||||||
|
started_at: "2025-03-12T18:08:07.403171Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "missed action",
|
||||||
|
ended_at: "2025-03-12T18:08:08.029752Z",
|
||||||
|
job_id: "a7c4a05d-1c36-4264-8275-8107c93c5fc8",
|
||||||
|
resource: "null_resource.validate_url",
|
||||||
|
source: "null",
|
||||||
|
stage: "apply",
|
||||||
|
started_at: "2025-03-12T18:08:07.410219Z",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
const applyButton = canvas.getByRole("button", {
|
||||||
|
name: "View apply details",
|
||||||
|
});
|
||||||
|
await user.click(applyButton);
|
||||||
|
await canvas.findByText("missed action");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user