Skip to content

Commit 162c91e

Browse files
fix(site): fix resource selection when workspace has no prev resources (#11594)
1 parent cb77f04 commit 162c91e

File tree

3 files changed

+66
-47
lines changed

3 files changed

+66
-47
lines changed

site/src/pages/WorkspacePage/Workspace.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { SidebarIconButton } from "components/FullPageLayout/Sidebar";
2626
import HubOutlined from "@mui/icons-material/HubOutlined";
2727
import { ResourcesSidebar } from "./ResourcesSidebar";
2828
import { ResourceCard } from "components/Resources/ResourceCard";
29-
import { useResourcesNav } from "./useResourcesNav";
29+
import { resourceOptionValue, useResourcesNav } from "./useResourcesNav";
3030
import { MemoizedInlineMarkdown } from "components/Markdown/Markdown";
3131

3232
export type WorkspaceError =
@@ -163,6 +163,9 @@ export const Workspace: FC<WorkspaceProps> = ({
163163
(a, b) => countAgents(b) - countAgents(a),
164164
);
165165
const resourcesNav = useResourcesNav(resources);
166+
const selectedResource = resources.find(
167+
(r) => resourceOptionValue(r) === resourcesNav.value,
168+
);
166169

167170
return (
168171
<div
@@ -377,9 +380,9 @@ export const Workspace: FC<WorkspaceProps> = ({
377380

378381
{buildLogs}
379382

380-
{resourcesNav.selected && (
383+
{selectedResource && (
381384
<ResourceCard
382-
resource={resourcesNav.selected}
385+
resource={selectedResource}
383386
agentRow={(agent) => (
384387
<AgentRow
385388
key={agent.id}

site/src/pages/WorkspacePage/useResourcesNav.test.tsx

+47-29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { renderHook } from "@testing-library/react";
2-
import { resourceOptionId, useResourcesNav } from "./useResourcesNav";
2+
import { resourceOptionValue, useResourcesNav } from "./useResourcesNav";
33
import { WorkspaceResource } from "api/typesGenerated";
44
import { MockWorkspaceResource } from "testHelpers/entities";
55
import { RouterProvider, createMemoryRouter } from "react-router-dom";
@@ -20,27 +20,9 @@ describe("useResourcesNav", () => {
2020
/>
2121
),
2222
});
23-
expect(result.current.selected?.id).toBe(MockWorkspaceResource.id);
24-
});
25-
26-
it("selects the first resource if it has agents and selected resource is not find", async () => {
27-
const resources: WorkspaceResource[] = [
28-
MockWorkspaceResource,
29-
{
30-
...MockWorkspaceResource,
31-
agents: [],
32-
},
33-
];
34-
const { result } = renderHook(() => useResourcesNav(resources), {
35-
wrapper: ({ children }) => (
36-
<RouterProvider
37-
router={createMemoryRouter([{ path: "/", element: children }], {
38-
initialEntries: ["/?resources=not_found_resource_id"],
39-
})}
40-
/>
41-
),
42-
});
43-
expect(result.current.selected?.id).toBe(MockWorkspaceResource.id);
23+
expect(result.current.value).toBe(
24+
resourceOptionValue(MockWorkspaceResource),
25+
);
4426
});
4527

4628
it("selects the resource passed in the URL", () => {
@@ -66,12 +48,14 @@ describe("useResourcesNav", () => {
6648
wrapper: ({ children }) => (
6749
<RouterProvider
6850
router={createMemoryRouter([{ path: "/", element: children }], {
69-
initialEntries: [`/?resources=${resourceOptionId(resources[1])}`],
51+
initialEntries: [
52+
`/?resources=${resourceOptionValue(resources[1])}`,
53+
],
7054
})}
7155
/>
7256
),
7357
});
74-
expect(result.current.selected?.id).toBe(resources[1].id);
58+
expect(result.current.value).toBe(resourceOptionValue(resources[1]));
7559
});
7660

7761
it("selects a resource when resources are updated", () => {
@@ -104,7 +88,7 @@ describe("useResourcesNav", () => {
10488
initialProps: { resources: startedResources },
10589
},
10690
);
107-
expect(result.current.selected?.id).toBe(startedResources[0].id);
91+
expect(result.current.value).toBe(resourceOptionValue(startedResources[0]));
10892

10993
// When a workspace is stopped, there are no resources with agents, so we
11094
// need to retain the currently selected resource. This ensures consistency
@@ -121,12 +105,46 @@ describe("useResourcesNav", () => {
121105
},
122106
];
123107
rerender({ resources: stoppedResources });
124-
expect(result.current.selectedValue).toBe(
125-
resourceOptionId(startedResources[0]),
126-
);
108+
expect(result.current.value).toBe(resourceOptionValue(startedResources[0]));
127109

128110
// When a workspace is started again a resource is selected
129111
rerender({ resources: startedResources });
130-
expect(result.current.selected?.id).toBe(startedResources[0].id);
112+
expect(result.current.value).toBe(resourceOptionValue(startedResources[0]));
113+
});
114+
115+
// This happens when a new workspace is created and there are no resources
116+
it("selects a resource when resources are not defined previously", () => {
117+
const startingResources: WorkspaceResource[] = [];
118+
const { result, rerender } = renderHook(
119+
({ resources }) => useResourcesNav(resources),
120+
{
121+
wrapper: ({ children }) => (
122+
<RouterProvider
123+
router={createMemoryRouter([{ path: "/", element: children }])}
124+
/>
125+
),
126+
initialProps: { resources: startingResources },
127+
},
128+
);
129+
const startedResources: WorkspaceResource[] = [
130+
{
131+
...MockWorkspaceResource,
132+
type: "docker_container",
133+
name: "coder_python",
134+
},
135+
{
136+
...MockWorkspaceResource,
137+
type: "docker_container",
138+
name: "coder_java",
139+
},
140+
{
141+
...MockWorkspaceResource,
142+
type: "docker_image",
143+
name: "coder_image_python",
144+
agents: [],
145+
},
146+
];
147+
rerender({ resources: startedResources });
148+
expect(result.current.value).toBe(resourceOptionValue(startedResources[0]));
131149
});
132150
});

site/src/pages/WorkspacePage/useResourcesNav.ts

+13-15
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useTab } from "hooks";
33
import { useEffectEvent } from "hooks/hookPolyfills";
44
import { useCallback, useEffect } from "react";
55

6-
export const resourceOptionId = (resource: WorkspaceResource) => {
6+
export const resourceOptionValue = (resource: WorkspaceResource) => {
77
return `${resource.type}_${resource.name}`;
88
};
99

@@ -18,38 +18,36 @@ export const useResourcesNav = (resources: WorkspaceResource[]) => {
1818

1919
const isSelected = useCallback(
2020
(resource: WorkspaceResource) => {
21-
return resourceOptionId(resource) === resourcesNav.value;
21+
return resourceOptionValue(resource) === resourcesNav.value;
2222
},
2323
[resourcesNav.value],
2424
);
2525

26-
const selectedResource = resources.find(isSelected);
27-
const onSelectedResourceChange = useEffectEvent(
28-
(previousResource?: WorkspaceResource) => {
26+
const onResourceChanges = useEffectEvent(
27+
(resources?: WorkspaceResource[]) => {
28+
const hasSelectedResource = resourcesNav.value !== "";
29+
const hasResources = resources && resources.length > 0;
2930
const hasResourcesWithAgents =
30-
resources.length > 0 &&
31-
resources[0].agents &&
32-
resources[0].agents.length > 0;
33-
if (!previousResource && hasResourcesWithAgents) {
34-
resourcesNav.set(resourceOptionId(resources[0]));
31+
hasResources && resources[0].agents && resources[0].agents.length > 0;
32+
if (!hasSelectedResource && hasResourcesWithAgents) {
33+
resourcesNav.set(resourceOptionValue(resources[0]));
3534
}
3635
},
3736
);
3837
useEffect(() => {
39-
onSelectedResourceChange(selectedResource);
40-
}, [onSelectedResourceChange, selectedResource]);
38+
onResourceChanges(resources);
39+
}, [onResourceChanges, resources]);
4140

4241
const select = useCallback(
4342
(resource: WorkspaceResource) => {
44-
resourcesNav.set(resourceOptionId(resource));
43+
resourcesNav.set(resourceOptionValue(resource));
4544
},
4645
[resourcesNav],
4746
);
4847

4948
return {
5049
isSelected,
5150
select,
52-
selected: selectedResource,
53-
selectedValue: resourcesNav.value,
51+
value: resourcesNav.value,
5452
};
5553
};

0 commit comments

Comments
 (0)