diff --git a/site/src/api/queries/workspaceBuilds.ts b/site/src/api/queries/workspaceBuilds.ts index 45f7ac3bb7fe6..a537cbed092e3 100644 --- a/site/src/api/queries/workspaceBuilds.ts +++ b/site/src/api/queries/workspaceBuilds.ts @@ -58,17 +58,9 @@ export const infiniteWorkspaceBuilds = ( }; // We use readyAgentsCount to invalidate the query when an agent connects -export const workspaceBuildTimings = ( - workspaceBuildId: string, - readyAgentsCount: number, -) => { +export const workspaceBuildTimings = (workspaceBuildId: string) => { return { - queryKey: [ - "workspaceBuilds", - workspaceBuildId, - "timings", - { readyAgentsCount }, - ], + queryKey: ["workspaceBuilds", workspaceBuildId, "timings"], queryFn: () => API.workspaceBuildTimings(workspaceBuildId), }; }; diff --git a/site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.stories.tsx b/site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.stories.tsx index 02a544ea9a718..0210353488257 100644 --- a/site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.stories.tsx +++ b/site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.stories.tsx @@ -118,3 +118,11 @@ export const DuplicatedScriptTiming: Story = { ], }, }; + +// Loading when agent script timings are empty +// Test case for https://github.com/coder/coder/issues/15273 +export const LoadingWhenAgentScriptTimingsAreEmpty: Story = { + args: { + agentScriptTimings: undefined, + }, +}; diff --git a/site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.tsx b/site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.tsx index 47873f8aaaede..63fc03ad2a3de 100644 --- a/site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.tsx +++ b/site/src/modules/workspaces/WorkspaceTiming/WorkspaceTimings.tsx @@ -58,15 +58,25 @@ export const WorkspaceTimings: FC = ({ ].sort((a, b) => { return new Date(a.started_at).getTime() - new Date(b.started_at).getTime(); }); + const [isOpen, setIsOpen] = useState(defaultIsOpen); - const isLoading = timings.length === 0; - // All stages + // If any of the timings are empty, we are still loading the data. They can be + // filled in different moments. + const isLoading = [ + provisionerTimings, + agentScriptTimings, + agentConnectionTimings, + ].some((t) => t.length === 0); + + // Each agent connection timing is a stage in the timeline to make it easier + // to users to see the timing for connection and the other scripts. const agentStageLabels = Array.from( new Set( agentConnectionTimings.map((t) => `agent (${t.workspace_agent_name})`), ), ); + const stages = [ ...provisioningStages, ...agentStageLabels.flatMap((a) => agentStages(a)), @@ -120,7 +130,8 @@ export const WorkspaceTimings: FC = ({ : mergeTimeRanges(stageTimings.map(toTimeRange)); // Prevent users from inspecting internal coder resources in - // provisioner timings. + // provisioner timings because they were not useful to the + // user and would add noise. const visibleResources = stageTimings.filter((t) => { const isProvisionerTiming = "resource" in t; return isProvisionerTiming diff --git a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx index cdb47f86f508c..62e1b5a6c6dd5 100644 --- a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx @@ -157,13 +157,27 @@ export const WorkspaceReadyPage: FC = ({ // Cancel build const cancelBuildMutation = useMutation(cancelBuild(workspace, queryClient)); - // Build Timings. Fetch build timings only when the build job is completed. - const readyAgents = workspace.latest_build.resources - .flatMap((r) => r.agents) - .filter((a) => a && a.lifecycle_state !== "starting"); + // Workspace Timings. const timingsQuery = useQuery({ - ...workspaceBuildTimings(workspace.latest_build.id, readyAgents.length), + ...workspaceBuildTimings(workspace.latest_build.id), + + // Fetch build timings only when the build job is completed. enabled: Boolean(workspace.latest_build.job.completed_at), + + // Sometimes, the timings can be fetched before the agent script timings are + // done or saved in the database so we need to conditionally refetch the + // timings. To refetch the timings, I found the best way was to compare the + // expected amount of script timings with the current amount of script + // timings returned in the response. + refetchInterval: (data) => { + const expectedScriptTimingsCount = workspace.latest_build.resources + .flatMap((r) => r.agents) + .flatMap((a) => a?.scripts ?? []).length; + const currentScriptTimingsCount = data?.agent_script_timings?.length ?? 0; + return expectedScriptTimingsCount === currentScriptTimingsCount + ? false + : 1_000; + }, }); const runLastBuild = (