diff --git a/site/src/pages/WorkspacePage/Workspace.tsx b/site/src/pages/WorkspacePage/Workspace.tsx index 472b777a9952c..314d64ec25404 100644 --- a/site/src/pages/WorkspacePage/Workspace.tsx +++ b/site/src/pages/WorkspacePage/Workspace.tsx @@ -26,7 +26,7 @@ import { SidebarIconButton } from "components/FullPageLayout/Sidebar"; import HubOutlined from "@mui/icons-material/HubOutlined"; import { ResourcesSidebar } from "./ResourcesSidebar"; import { ResourceCard } from "components/Resources/ResourceCard"; -import { useResourcesNav } from "./useResourcesNav"; +import { resourceOptionValue, useResourcesNav } from "./useResourcesNav"; import { MemoizedInlineMarkdown } from "components/Markdown/Markdown"; export type WorkspaceError = @@ -163,6 +163,9 @@ export const Workspace: FC = ({ (a, b) => countAgents(b) - countAgents(a), ); const resourcesNav = useResourcesNav(resources); + const selectedResource = resources.find( + (r) => resourceOptionValue(r) === resourcesNav.value, + ); return (
= ({ {buildLogs} - {resourcesNav.selected && ( + {selectedResource && ( ( { /> ), }); - expect(result.current.selected?.id).toBe(MockWorkspaceResource.id); - }); - - it("selects the first resource if it has agents and selected resource is not find", async () => { - const resources: WorkspaceResource[] = [ - MockWorkspaceResource, - { - ...MockWorkspaceResource, - agents: [], - }, - ]; - const { result } = renderHook(() => useResourcesNav(resources), { - wrapper: ({ children }) => ( - - ), - }); - expect(result.current.selected?.id).toBe(MockWorkspaceResource.id); + expect(result.current.value).toBe( + resourceOptionValue(MockWorkspaceResource), + ); }); it("selects the resource passed in the URL", () => { @@ -66,12 +48,14 @@ describe("useResourcesNav", () => { wrapper: ({ children }) => ( ), }); - expect(result.current.selected?.id).toBe(resources[1].id); + expect(result.current.value).toBe(resourceOptionValue(resources[1])); }); it("selects a resource when resources are updated", () => { @@ -104,7 +88,7 @@ describe("useResourcesNav", () => { initialProps: { resources: startedResources }, }, ); - expect(result.current.selected?.id).toBe(startedResources[0].id); + expect(result.current.value).toBe(resourceOptionValue(startedResources[0])); // When a workspace is stopped, there are no resources with agents, so we // need to retain the currently selected resource. This ensures consistency @@ -121,12 +105,46 @@ describe("useResourcesNav", () => { }, ]; rerender({ resources: stoppedResources }); - expect(result.current.selectedValue).toBe( - resourceOptionId(startedResources[0]), - ); + expect(result.current.value).toBe(resourceOptionValue(startedResources[0])); // When a workspace is started again a resource is selected rerender({ resources: startedResources }); - expect(result.current.selected?.id).toBe(startedResources[0].id); + expect(result.current.value).toBe(resourceOptionValue(startedResources[0])); + }); + + // This happens when a new workspace is created and there are no resources + it("selects a resource when resources are not defined previously", () => { + const startingResources: WorkspaceResource[] = []; + const { result, rerender } = renderHook( + ({ resources }) => useResourcesNav(resources), + { + wrapper: ({ children }) => ( + + ), + initialProps: { resources: startingResources }, + }, + ); + const startedResources: WorkspaceResource[] = [ + { + ...MockWorkspaceResource, + type: "docker_container", + name: "coder_python", + }, + { + ...MockWorkspaceResource, + type: "docker_container", + name: "coder_java", + }, + { + ...MockWorkspaceResource, + type: "docker_image", + name: "coder_image_python", + agents: [], + }, + ]; + rerender({ resources: startedResources }); + expect(result.current.value).toBe(resourceOptionValue(startedResources[0])); }); }); diff --git a/site/src/pages/WorkspacePage/useResourcesNav.ts b/site/src/pages/WorkspacePage/useResourcesNav.ts index 7774d1073240c..313c5558a0d3e 100644 --- a/site/src/pages/WorkspacePage/useResourcesNav.ts +++ b/site/src/pages/WorkspacePage/useResourcesNav.ts @@ -3,7 +3,7 @@ import { useTab } from "hooks"; import { useEffectEvent } from "hooks/hookPolyfills"; import { useCallback, useEffect } from "react"; -export const resourceOptionId = (resource: WorkspaceResource) => { +export const resourceOptionValue = (resource: WorkspaceResource) => { return `${resource.type}_${resource.name}`; }; @@ -18,30 +18,29 @@ export const useResourcesNav = (resources: WorkspaceResource[]) => { const isSelected = useCallback( (resource: WorkspaceResource) => { - return resourceOptionId(resource) === resourcesNav.value; + return resourceOptionValue(resource) === resourcesNav.value; }, [resourcesNav.value], ); - const selectedResource = resources.find(isSelected); - const onSelectedResourceChange = useEffectEvent( - (previousResource?: WorkspaceResource) => { + const onResourceChanges = useEffectEvent( + (resources?: WorkspaceResource[]) => { + const hasSelectedResource = resourcesNav.value !== ""; + const hasResources = resources && resources.length > 0; const hasResourcesWithAgents = - resources.length > 0 && - resources[0].agents && - resources[0].agents.length > 0; - if (!previousResource && hasResourcesWithAgents) { - resourcesNav.set(resourceOptionId(resources[0])); + hasResources && resources[0].agents && resources[0].agents.length > 0; + if (!hasSelectedResource && hasResourcesWithAgents) { + resourcesNav.set(resourceOptionValue(resources[0])); } }, ); useEffect(() => { - onSelectedResourceChange(selectedResource); - }, [onSelectedResourceChange, selectedResource]); + onResourceChanges(resources); + }, [onResourceChanges, resources]); const select = useCallback( (resource: WorkspaceResource) => { - resourcesNav.set(resourceOptionId(resource)); + resourcesNav.set(resourceOptionValue(resource)); }, [resourcesNav], ); @@ -49,7 +48,6 @@ export const useResourcesNav = (resources: WorkspaceResource[]) => { return { isSelected, select, - selected: selectedResource, - selectedValue: resourcesNav.value, + value: resourcesNav.value, }; };