fix(site): fix resource selection when workspace has no prev resources (#11594)

This commit is contained in:
Bruno Quaresma
2024-01-12 15:45:06 -03:00
committed by GitHub
parent cb77f04104
commit 162c91ec2a
3 changed files with 66 additions and 47 deletions
+6 -3
View File
@@ -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<WorkspaceProps> = ({
(a, b) => countAgents(b) - countAgents(a),
);
const resourcesNav = useResourcesNav(resources);
const selectedResource = resources.find(
(r) => resourceOptionValue(r) === resourcesNav.value,
);
return (
<div
@@ -377,9 +380,9 @@ export const Workspace: FC<WorkspaceProps> = ({
{buildLogs}
{resourcesNav.selected && (
{selectedResource && (
<ResourceCard
resource={resourcesNav.selected}
resource={selectedResource}
agentRow={(agent) => (
<AgentRow
key={agent.id}
@@ -1,5 +1,5 @@
import { renderHook } from "@testing-library/react";
import { resourceOptionId, useResourcesNav } from "./useResourcesNav";
import { resourceOptionValue, useResourcesNav } from "./useResourcesNav";
import { WorkspaceResource } from "api/typesGenerated";
import { MockWorkspaceResource } from "testHelpers/entities";
import { RouterProvider, createMemoryRouter } from "react-router-dom";
@@ -20,27 +20,9 @@ describe("useResourcesNav", () => {
/>
),
});
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 }) => (
<RouterProvider
router={createMemoryRouter([{ path: "/", element: children }], {
initialEntries: ["/?resources=not_found_resource_id"],
})}
/>
),
});
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 }) => (
<RouterProvider
router={createMemoryRouter([{ path: "/", element: children }], {
initialEntries: [`/?resources=${resourceOptionId(resources[1])}`],
initialEntries: [
`/?resources=${resourceOptionValue(resources[1])}`,
],
})}
/>
),
});
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 }) => (
<RouterProvider
router={createMemoryRouter([{ path: "/", element: 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]));
});
});
+13 -15
View File
@@ -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,
};
};