From c66e80e86276c48bbf17930f06a8679ac946e32e Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Thu, 8 May 2025 13:02:27 +0100 Subject: [PATCH 1/4] fix: extract checkbox label from dynamic parameter styling prop (#17651) resolves #17474 A label will only be shown next to the checkbox If there is a value for `label` in the styling prop for the dynamic parameter Screenshot 2025-05-01 at 21 35 32 --- go.mod | 2 +- go.sum | 4 ++-- site/src/api/typesGenerated.ts | 10 +++++++-- .../DynamicParameter/DynamicParameter.tsx | 22 +++++-------------- .../CreateWorkspacePageViewExperimental.tsx | 3 +-- 5 files changed, 17 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index cffcd99d06db8..536bce2fe28b7 100644 --- a/go.mod +++ b/go.mod @@ -488,7 +488,7 @@ require ( require ( github.com/anthropics/anthropic-sdk-go v0.2.0-beta.3 - github.com/coder/preview v0.0.1 + github.com/coder/preview v0.0.2-0.20250506154333-6f500ca7b245 github.com/fsnotify/fsnotify v1.9.0 github.com/kylecarbs/aisdk-go v0.0.8 github.com/mark3labs/mcp-go v0.25.0 diff --git a/go.sum b/go.sum index 4c418e5fd2a02..a3fc878ef2653 100644 --- a/go.sum +++ b/go.sum @@ -907,8 +907,8 @@ github.com/coder/pq v1.10.5-0.20240813183442-0c420cb5a048 h1:3jzYUlGH7ZELIH4XggX github.com/coder/pq v1.10.5-0.20240813183442-0c420cb5a048/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs= github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc= -github.com/coder/preview v0.0.1 h1:2X5McKdMOZJILTIDf7qRplXKupT+91qTJBN67XUh5cA= -github.com/coder/preview v0.0.1/go.mod h1:eInDmOdSDF8cxCvapIvYkGRzmzvcvGAFL1HYqcA4g+E= +github.com/coder/preview v0.0.2-0.20250506154333-6f500ca7b245 h1:RGoANNubwwPZF8puiYAk2qbzhVgipBMNu8WIrY1VIbI= +github.com/coder/preview v0.0.2-0.20250506154333-6f500ca7b245/go.mod h1:5VnO9yw7vq19hBgBqqBksE2BH53UTmNYH1QltkYLXJI= github.com/coder/quartz v0.1.2 h1:PVhc9sJimTdKd3VbygXtS4826EOCpB1fXoRlLnCrE+s= github.com/coder/quartz v0.1.2/go.mod h1:vsiCc+AHViMKH2CQpGIpFgdHIEQsxwm8yCscqKmzbRA= github.com/coder/retry v1.5.1 h1:iWu8YnD8YqHs3XwqrqsjoBTAVqT9ml6z9ViJ2wlMiqc= diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index d195432f019c4..82332b76e060f 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1796,8 +1796,7 @@ export interface PreviewParameterData { readonly type: PreviewParameterType; // this is likely an enum in an external package "github.com/coder/terraform-provider-coder/v2/provider.ParameterFormType" readonly form_type: string; - // empty interface{} type, falling back to unknown - readonly styling: unknown; + readonly styling: PreviewParameterStyling; readonly mutable: boolean; readonly default_value: NullHCLString; readonly icon: string; @@ -1816,6 +1815,13 @@ export interface PreviewParameterOption { readonly icon: string; } +// From types/parameter.go +export interface PreviewParameterStyling { + readonly placeholder?: string; + readonly disabled?: boolean; + readonly label?: string; +} + // From types/enum.go export type PreviewParameterType = string; diff --git a/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx b/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx index 9ec69158c4e84..5f8e875dbebcf 100644 --- a/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx +++ b/site/src/modules/workspaces/DynamicParameter/DynamicParameter.tsx @@ -181,10 +181,7 @@ const ParameterField: FC = ({ > @@ -245,10 +242,7 @@ const ParameterField: FC = ({ onChange(JSON.stringify(values)); }} hidePlaceholderWhenSelected - placeholder={ - (parameter.styling as { placeholder?: string })?.placeholder || - "Select option" - } + placeholder={parameter.styling?.placeholder || "Select option"} emptyIndicator={

No results found @@ -304,9 +298,7 @@ const ParameterField: FC = ({ }} disabled={disabled} /> - + ); @@ -343,9 +335,7 @@ const ParameterField: FC = ({ target.style.height = `${target.scrollHeight}px`; }} disabled={disabled} - placeholder={ - (parameter.styling as { placeholder?: string })?.placeholder - } + placeholder={parameter.styling?.placeholder} required={parameter.required} /> ); @@ -377,9 +367,7 @@ const ParameterField: FC = ({ }} disabled={disabled} required={parameter.required} - placeholder={ - (parameter.styling as { placeholder?: string })?.placeholder - } + placeholder={parameter.styling?.placeholder} {...inputProps} /> ); diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx index 58391cdad3d9f..0ba3ee9fb77f3 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.tsx @@ -493,7 +493,6 @@ export const CreateWorkspacePageViewExperimental: FC<

{parameters.map((parameter, index) => { const parameterField = `rich_parameter_values.${index}`; - const parameterInputName = `${parameterField}.value`; const isPresetParameter = presetParameterNames.includes( parameter.name, ); @@ -501,7 +500,7 @@ export const CreateWorkspacePageViewExperimental: FC< disabledParams?.includes( parameter.name.toLowerCase().replace(/ /g, "_"), ) || - (parameter.styling as { disabled?: boolean })?.disabled || + parameter.styling?.disabled || creatingWorkspace || isPresetParameter; From 2695f4e9503319dfb5ea11f4e920d93de6c661fc Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Thu, 8 May 2025 09:32:32 -0300 Subject: [PATCH 2/4] chore: improve variable names of mocked users (#17701) Many times I got confused when using MockUser and MockUser2 so I just decided to better naming them to MockUserOwner and MockUserMember. --- .gitignore | 2 + docs/contributing/frontend.md | 2 +- .../OrganizationAutocomplete.stories.tsx | 6 +- .../UserAutocomplete.stories.tsx | 6 +- site/src/contexts/auth/RequireAuth.test.tsx | 6 +- site/src/hooks/useEmbeddedMetadata.test.ts | 6 +- .../dashboard/Navbar/MobileMenu.stories.tsx | 12 +-- .../dashboard/Navbar/NavbarView.stories.tsx | 10 +- .../dashboard/Navbar/NavbarView.test.tsx | 10 +- .../dashboard/Navbar/ProxyMenu.stories.tsx | 4 +- .../UserDropdown/UserDropdown.stories.tsx | 4 +- .../UserDropdown/UserDropdownContent.test.tsx | 6 +- .../AuditLogRow/AuditLogRow.stories.tsx | 4 +- .../pages/AuditPage/AuditPageView.stories.tsx | 4 +- .../CreateWorkspacePage.test.tsx | 14 +-- .../CreateWorkspacePageView.stories.tsx | 4 +- ...eWorkspacePageViewExperimental.stories.tsx | 4 +- .../NotificationsPage/storybookUtils.ts | 4 +- .../OrganizationMembersPage.test.tsx | 10 +- .../OrganizationMembersPageView.stories.tsx | 4 +- site/src/pages/SetupPage/SetupPage.test.tsx | 4 +- .../TerminalPage/TerminalPage.stories.tsx | 4 +- .../pages/TerminalPage/TerminalPage.test.tsx | 8 +- .../AccountPage/AccountForm.test.tsx | 14 +-- .../AccountPage/AccountUserGroups.stories.tsx | 4 +- .../AppearancePage/AppearancePage.test.tsx | 12 +-- .../NotificationsPage.stories.tsx | 6 +- .../SchedulePage/SchedulePage.test.tsx | 10 +- .../UsersPage/ResetPasswordDialog.stories.tsx | 4 +- .../src/pages/UsersPage/UsersPage.stories.tsx | 4 +- .../pages/UsersPage/UsersPageView.stories.tsx | 6 +- .../UsersTable/UsersTable.stories.tsx | 20 ++-- .../pages/WorkspacePage/Workspace.stories.tsx | 2 +- .../WorkspaceActions.stories.tsx | 6 +- .../WorkspacePage/WorkspacePage.test.tsx | 4 +- .../WorkspacePage/WorkspaceTopbar.stories.tsx | 8 +- .../WorkspaceSchedulePage.test.tsx | 8 +- .../BatchDeleteConfirmation.stories.tsx | 10 +- .../BatchUpdateConfirmation.stories.tsx | 6 +- .../WorkspacesPageView.stories.tsx | 6 +- site/src/testHelpers/entities.ts | 95 +++++++++---------- site/src/testHelpers/handlers.ts | 8 +- site/src/testHelpers/renderHelpers.tsx | 10 +- 43 files changed, 189 insertions(+), 192 deletions(-) diff --git a/.gitignore b/.gitignore index 8d29eff1048d1..24021e54ddde2 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,5 @@ result # dlv debug binaries for go tests __debug_bin* + +**/.claude/settings.local.json diff --git a/docs/contributing/frontend.md b/docs/contributing/frontend.md index 711246b0277d8..62e86c9ad4ab9 100644 --- a/docs/contributing/frontend.md +++ b/docs/contributing/frontend.md @@ -131,7 +131,7 @@ export const WithQuota: Story = { parameters: { queries: [ { - key: getWorkspaceQuotaQueryKey(MockUser.username), + key: getWorkspaceQuotaQueryKey(MockUserOwner.username), data: { credits_consumed: 2, budget: 40, diff --git a/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.stories.tsx b/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.stories.tsx index 87a7c544366a8..949b293dfce04 100644 --- a/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.stories.tsx +++ b/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.stories.tsx @@ -4,7 +4,7 @@ import { userEvent, within } from "@storybook/test"; import { MockOrganization, MockOrganization2, - MockUser, + MockUserOwner, } from "testHelpers/entities"; import { OrganizationAutocomplete } from "./OrganizationAutocomplete"; @@ -22,7 +22,7 @@ type Story = StoryObj; export const ManyOrgs: Story = { parameters: { showOrganizations: true, - user: MockUser, + user: MockUserOwner, features: ["multiple_organizations"], permissions: { viewDeploymentConfig: true }, queries: [ @@ -42,7 +42,7 @@ export const ManyOrgs: Story = { export const OneOrg: Story = { parameters: { showOrganizations: true, - user: MockUser, + user: MockUserOwner, features: ["multiple_organizations"], permissions: { viewDeploymentConfig: true }, queries: [ diff --git a/site/src/components/UserAutocomplete/UserAutocomplete.stories.tsx b/site/src/components/UserAutocomplete/UserAutocomplete.stories.tsx index eee96b248f52b..06c16e22fdebe 100644 --- a/site/src/components/UserAutocomplete/UserAutocomplete.stories.tsx +++ b/site/src/components/UserAutocomplete/UserAutocomplete.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react"; -import { MockUser } from "testHelpers/entities"; +import { MockUserOwner } from "testHelpers/entities"; import { UserAutocomplete } from "./UserAutocomplete"; const meta: Meta = { @@ -12,13 +12,13 @@ type Story = StoryObj; export const WithLabel: Story = { args: { - value: MockUser, + value: MockUserOwner, label: "User", }, }; export const NoLabel: Story = { args: { - value: MockUser, + value: MockUserOwner, }, }; diff --git a/site/src/contexts/auth/RequireAuth.test.tsx b/site/src/contexts/auth/RequireAuth.test.tsx index 291d442adbc04..b24bb06cb055c 100644 --- a/site/src/contexts/auth/RequireAuth.test.tsx +++ b/site/src/contexts/auth/RequireAuth.test.tsx @@ -3,7 +3,7 @@ import { useAuthenticated } from "hooks"; import { http, HttpResponse } from "msw"; import type { FC, PropsWithChildren } from "react"; import { QueryClientProvider } from "react-query"; -import { MockPermissions, MockUser } from "testHelpers/entities"; +import { MockPermissions, MockUserOwner } from "testHelpers/entities"; import { createTestQueryClient, renderWithAuth, @@ -82,7 +82,7 @@ describe("useAuthenticated", () => { expect(() => { renderHook(() => useAuthenticated(), { - wrapper: createAuthWrapper({ user: MockUser }), + wrapper: createAuthWrapper({ user: MockUserOwner }), }); }).toThrow("Permissions are not available."); @@ -93,7 +93,7 @@ describe("useAuthenticated", () => { expect(() => { renderHook(() => useAuthenticated(), { wrapper: createAuthWrapper({ - user: MockUser, + user: MockUserOwner, permissions: MockPermissions, }), }); diff --git a/site/src/hooks/useEmbeddedMetadata.test.ts b/site/src/hooks/useEmbeddedMetadata.test.ts index aacb635ada3bf..6f7b2741ed96b 100644 --- a/site/src/hooks/useEmbeddedMetadata.test.ts +++ b/site/src/hooks/useEmbeddedMetadata.test.ts @@ -5,8 +5,8 @@ import { MockBuildInfo, MockEntitlements, MockExperiments, - MockUser, MockUserAppearanceSettings, + MockUserOwner, } from "testHelpers/entities"; import { DEFAULT_METADATA_KEY, @@ -38,7 +38,7 @@ const mockDataForTags = { "build-info": MockBuildInfo, entitlements: MockEntitlements, experiments: MockExperiments, - user: MockUser, + user: MockUserOwner, userAppearance: MockUserAppearanceSettings, regions: MockRegions, } as const satisfies Record; @@ -97,7 +97,7 @@ const populatedMetadata: RuntimeHtmlMetadata = { }, user: { available: true, - value: MockUser, + value: MockUserOwner, }, userAppearance: { available: true, diff --git a/site/src/modules/dashboard/Navbar/MobileMenu.stories.tsx b/site/src/modules/dashboard/Navbar/MobileMenu.stories.tsx index 5392ecaaee6c9..058c8799c95e0 100644 --- a/site/src/modules/dashboard/Navbar/MobileMenu.stories.tsx +++ b/site/src/modules/dashboard/Navbar/MobileMenu.stories.tsx @@ -6,8 +6,8 @@ import { MockPrimaryWorkspaceProxy, MockProxyLatencies, MockSupportLinks, - MockUser, - MockUser2, + MockUserMember, + MockUserOwner, MockWorkspaceProxies, } from "testHelpers/entities"; import { MobileMenu } from "./MobileMenu"; @@ -36,7 +36,7 @@ const meta: Meta = { proxyLatencies: MockProxyLatencies, proxies: MockWorkspaceProxies, }, - user: MockUser, + user: MockUserOwner, supportLinks: MockSupportLinks, onSignOut: fn(), isDefaultOpen: true, @@ -63,7 +63,7 @@ export const Admin: Story = { export const Auditor: Story = { args: { - user: MockUser2, + user: MockUserMember, canViewAuditLog: true, canViewDeployment: false, canViewHealth: false, @@ -74,7 +74,7 @@ export const Auditor: Story = { export const OrgAdmin: Story = { args: { - user: MockUser2, + user: MockUserMember, canViewAuditLog: true, canViewDeployment: false, canViewHealth: false, @@ -85,7 +85,7 @@ export const OrgAdmin: Story = { export const Member: Story = { args: { - user: MockUser2, + user: MockUserMember, canViewAuditLog: false, canViewDeployment: false, canViewHealth: false, diff --git a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx index ae13c7fcc9129..6bd076a1c1c68 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { userEvent, within } from "@storybook/test"; import { chromaticWithTablet } from "testHelpers/chromatic"; -import { MockUser, MockUser2 } from "testHelpers/entities"; +import { MockUserMember, MockUserOwner } from "testHelpers/entities"; import { withDashboardProvider } from "testHelpers/storybook"; import { NavbarView } from "./NavbarView"; @@ -10,7 +10,7 @@ const meta: Meta = { parameters: { chromatic: chromaticWithTablet, layout: "fullscreen" }, component: NavbarView, args: { - user: MockUser, + user: MockUserOwner, canViewAuditLog: true, canViewDeployment: true, canViewHealth: true, @@ -33,7 +33,7 @@ export const ForAdmin: Story = { export const ForAuditor: Story = { args: { - user: MockUser2, + user: MockUserMember, canViewAuditLog: true, canViewDeployment: false, canViewHealth: false, @@ -49,7 +49,7 @@ export const ForAuditor: Story = { export const ForOrgAdmin: Story = { args: { - user: MockUser2, + user: MockUserMember, canViewAuditLog: true, canViewDeployment: false, canViewHealth: false, @@ -65,7 +65,7 @@ export const ForOrgAdmin: Story = { export const ForMember: Story = { args: { - user: MockUser2, + user: MockUserMember, canViewAuditLog: false, canViewDeployment: false, canViewHealth: false, diff --git a/site/src/modules/dashboard/Navbar/NavbarView.test.tsx b/site/src/modules/dashboard/Navbar/NavbarView.test.tsx index 4cb15ae78621b..6739f666c2b17 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.test.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.test.tsx @@ -1,7 +1,7 @@ import { screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import type { ProxyContextValue } from "contexts/ProxyContext"; -import { MockPrimaryWorkspaceProxy, MockUser } from "testHelpers/entities"; +import { MockPrimaryWorkspaceProxy, MockUserOwner } from "testHelpers/entities"; import { renderWithAuth } from "testHelpers/renderHelpers"; import { NavbarView } from "./NavbarView"; @@ -26,7 +26,7 @@ describe("NavbarView", () => { renderWithAuth( { renderWithAuth( { renderWithAuth( { renderWithAuth( = { ], parameters: { queries: [ - { key: ["me"], data: MockUser }, + { key: ["me"], data: MockUserOwner }, { key: ["authMethods"], data: MockAuthMethodsAll }, { key: ["hasFirstUser"], data: true }, { diff --git a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.stories.tsx b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.stories.tsx index ff35d79807287..24ffe6adf8ca6 100644 --- a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.stories.tsx +++ b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdown.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from "@storybook/react"; import { expect, screen, userEvent, waitFor, within } from "@storybook/test"; -import { MockBuildInfo, MockUser } from "testHelpers/entities"; +import { MockBuildInfo, MockUserOwner } from "testHelpers/entities"; import { withDashboardProvider } from "testHelpers/storybook"; import { UserDropdown } from "./UserDropdown"; @@ -8,7 +8,7 @@ const meta: Meta = { title: "modules/dashboard/UserDropdown", component: UserDropdown, args: { - user: MockUser, + user: MockUserOwner, buildInfo: MockBuildInfo, supportLinks: [ { icon: "docs", name: "Documentation", target: "" }, diff --git a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.test.tsx b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.test.tsx index d4f3858d17fef..6a9018c4eeeca 100644 --- a/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.test.tsx +++ b/site/src/modules/dashboard/Navbar/UserDropdown/UserDropdownContent.test.tsx @@ -1,6 +1,6 @@ import { screen } from "@testing-library/react"; import { Popover } from "components/deprecated/Popover/Popover"; -import { MockUser } from "testHelpers/entities"; +import { MockUserOwner } from "testHelpers/entities"; import { render, waitForLoaderToBeRemoved } from "testHelpers/renderHelpers"; import { Language, UserDropdownContent } from "./UserDropdownContent"; @@ -8,7 +8,7 @@ describe("UserDropdownContent", () => { it("has the correct link for the account item", async () => { render( - + , ); await waitForLoaderToBeRemoved(); @@ -25,7 +25,7 @@ describe("UserDropdownContent", () => { const onSignOut = jest.fn(); render( - + , ); await waitForLoaderToBeRemoved(); diff --git a/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.stories.tsx b/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.stories.tsx index 8bb45aa39378b..ab5e55f8bbd84 100644 --- a/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.stories.tsx +++ b/site/src/pages/AuditPage/AuditLogRow/AuditLogRow.stories.tsx @@ -13,7 +13,7 @@ import { MockAuditLogRequestPasswordReset, MockAuditLogWithDeletedResource, MockAuditLogWithWorkspaceBuild, - MockUser, + MockUserOwner, } from "testHelpers/entities"; import { AuditLogRow } from "./AuditLogRow"; @@ -155,7 +155,7 @@ export const NoUserAgent: Story = { description: "{user} deleted workspace {target}", resource_link: "/@jon/yeee/builds/35", is_deleted: false, - user: MockUser, + user: MockUserOwner, }, }, }; diff --git a/site/src/pages/AuditPage/AuditPageView.stories.tsx b/site/src/pages/AuditPage/AuditPageView.stories.tsx index c7830ccca4533..323ae6d78bde8 100644 --- a/site/src/pages/AuditPage/AuditPageView.stories.tsx +++ b/site/src/pages/AuditPage/AuditPageView.stories.tsx @@ -14,7 +14,7 @@ import { MockAuditLog, MockAuditLog2, MockAuditLog3, - MockUser, + MockUserOwner, } from "testHelpers/entities"; import { AuditPageView } from "./AuditPageView"; @@ -23,7 +23,7 @@ type FilterProps = ComponentProps["filterProps"]; const defaultFilterProps = getDefaultFilterProps({ query: "owner:me", values: { - username: MockUser.username, + username: MockUserOwner.username, action: undefined, resource_type: undefined, organization: undefined, diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx index 64deba2116fb1..868aa85c751bd 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx @@ -8,7 +8,7 @@ import { MockTemplateVersionParameter1, MockTemplateVersionParameter2, MockTemplateVersionParameter3, - MockUser, + MockUserOwner, MockWorkspace, MockWorkspaceQuota, MockWorkspaceRequest, @@ -36,7 +36,7 @@ describe("CreateWorkspacePage", () => { it("succeeds with default owner", async () => { jest .spyOn(API, "getUsers") - .mockResolvedValueOnce({ users: [MockUser], count: 1 }); + .mockResolvedValueOnce({ users: [MockUserOwner], count: 1 }); jest .spyOn(API, "getWorkspaceQuota") .mockResolvedValueOnce(MockWorkspaceQuota); @@ -59,7 +59,7 @@ describe("CreateWorkspacePage", () => { await waitFor(() => expect(API.createWorkspace).toBeCalledWith( - MockUser.id, + MockUserOwner.id, expect.objectContaining({ ...MockWorkspaceRichParametersRequest, }), @@ -186,7 +186,7 @@ describe("CreateWorkspacePage", () => { .mockResolvedValueOnce(MockWorkspaceQuota); jest .spyOn(API, "getUsers") - .mockResolvedValueOnce({ users: [MockUser], count: 1 }); + .mockResolvedValueOnce({ users: [MockUserOwner], count: 1 }); jest.spyOn(API, "createWorkspace").mockResolvedValueOnce(MockWorkspace); jest .spyOn(API, "getTemplateVersionExternalAuth") @@ -219,7 +219,7 @@ describe("CreateWorkspacePage", () => { await waitFor(() => expect(API.createWorkspace).toBeCalledWith( - MockUser.id, + MockUserOwner.id, expect.objectContaining({ ...MockWorkspaceRequest, }), @@ -233,7 +233,7 @@ describe("CreateWorkspacePage", () => { .mockResolvedValueOnce(MockWorkspaceQuota); jest .spyOn(API, "getUsers") - .mockResolvedValueOnce({ users: [MockUser], count: 1 }); + .mockResolvedValueOnce({ users: [MockUserOwner], count: 1 }); jest.spyOn(API, "createWorkspace").mockResolvedValueOnce(MockWorkspace); jest .spyOn(API, "getTemplateVersionExternalAuth") @@ -258,7 +258,7 @@ describe("CreateWorkspacePage", () => { await waitFor(() => expect(API.createWorkspace).toBeCalledWith( - MockUser.id, + MockUserOwner.id, expect.objectContaining({ ...MockWorkspaceRequest, }), diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx index 564209f076b08..db0a548ccc313 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx @@ -8,7 +8,7 @@ import { MockTemplateVersionParameter1, MockTemplateVersionParameter2, MockTemplateVersionParameter3, - MockUser, + MockUserOwner, mockApiError, } from "testHelpers/entities"; import { CreateWorkspacePageView } from "./CreateWorkspacePageView"; @@ -19,7 +19,7 @@ const meta: Meta = { component: CreateWorkspacePageView, args: { defaultName: "", - defaultOwner: MockUser, + defaultOwner: MockUserOwner, autofillParameters: [], template: MockTemplate, parameters: [], diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.stories.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.stories.tsx index a41e3a48c0ad9..e00b04fd6bf50 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.stories.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageViewExperimental.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { DetailedError } from "api/errors"; import { chromatic } from "testHelpers/chromatic"; -import { MockTemplate, MockUser } from "testHelpers/entities"; +import { MockTemplate, MockUserOwner } from "testHelpers/entities"; import { CreateWorkspacePageViewExperimental } from "./CreateWorkspacePageViewExperimental"; const meta: Meta = { @@ -12,7 +12,7 @@ const meta: Meta = { autofillParameters: [], diagnostics: [], defaultName: "", - defaultOwner: MockUser, + defaultOwner: MockUserOwner, externalAuth: [], externalAuthPollingState: "idle", hasAllRequiredExternalAuth: true, diff --git a/site/src/pages/DeploymentSettingsPage/NotificationsPage/storybookUtils.ts b/site/src/pages/DeploymentSettingsPage/NotificationsPage/storybookUtils.ts index 7f344d457817f..f27535d5b5397 100644 --- a/site/src/pages/DeploymentSettingsPage/NotificationsPage/storybookUtils.ts +++ b/site/src/pages/DeploymentSettingsPage/NotificationsPage/storybookUtils.ts @@ -7,7 +7,7 @@ import type { DeploymentValues, SerpentOption } from "api/typesGenerated"; import { MockNotificationMethodsResponse, MockNotificationTemplates, - MockUser, + MockUserOwner, } from "testHelpers/entities"; import { withAuthProvider, @@ -193,7 +193,7 @@ export const baseMeta = { data: MockNotificationMethodsResponse, }, ], - user: MockUser, + user: MockUserOwner, permissions: { viewDeploymentConfig: true }, deploymentOptions: mockNotificationsDeploymentOptions, deploymentValues: { diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.test.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.test.tsx index 660e66ca0ccb2..4c90a21659ee2 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.test.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPage.test.tsx @@ -7,7 +7,7 @@ import { MockOrganization, MockOrganizationAuditorRole, MockOrganizationPermissions, - MockUser, + MockUserOwner, } from "testHelpers/entities"; import { renderWithOrganizationSettingsLayout, @@ -102,11 +102,11 @@ describe("OrganizationMembersPage", () => { it("updates the roles", async () => { server.use( http.put( - `/api/v2/organizations/:organizationId/members/${MockUser.id}/roles`, + `/api/v2/organizations/:organizationId/members/${MockUserOwner.id}/roles`, async () => { return HttpResponse.json({ - ...MockUser, - roles: [...MockUser.roles, MockOrganizationAuditorRole], + ...MockUserOwner, + roles: [...MockUserOwner.roles, MockOrganizationAuditorRole], }); }, ), @@ -122,7 +122,7 @@ describe("OrganizationMembersPage", () => { it("shows an error message", async () => { server.use( http.put( - `/api/v2/organizations/:organizationId/members/${MockUser.id}/roles`, + `/api/v2/organizations/:organizationId/members/${MockUserOwner.id}/roles`, () => { return HttpResponse.json( { message: "Error on updating the user roles." }, diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.stories.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.stories.tsx index 1c2f2c6e804a3..566bebfe7f3af 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.stories.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationMembersPageView.stories.tsx @@ -4,7 +4,7 @@ import type { UsePaginatedQueryResult } from "hooks/usePaginatedQuery"; import { MockOrganizationMember, MockOrganizationMember2, - MockUser, + MockUserOwner, } from "testHelpers/entities"; import { OrganizationMembersPageView } from "./OrganizationMembersPageView"; @@ -17,7 +17,7 @@ const meta: Meta = { isAddingMember: false, isUpdatingMemberRoles: false, canViewMembers: true, - me: MockUser, + me: MockUserOwner, members: [ { ...MockOrganizationMember, groups: [] }, { ...MockOrganizationMember2, groups: [] }, diff --git a/site/src/pages/SetupPage/SetupPage.test.tsx b/site/src/pages/SetupPage/SetupPage.test.tsx index 47cf1d58746e2..0ab5d15c6f338 100644 --- a/site/src/pages/SetupPage/SetupPage.test.tsx +++ b/site/src/pages/SetupPage/SetupPage.test.tsx @@ -3,7 +3,7 @@ import userEvent from "@testing-library/user-event"; import type { Response, User } from "api/typesGenerated"; import { http, HttpResponse } from "msw"; import { createMemoryRouter } from "react-router-dom"; -import { MockBuildInfo, MockUser } from "testHelpers/entities"; +import { MockBuildInfo, MockUserOwner } from "testHelpers/entities"; import { renderWithRouter, waitForLoaderToBeRemoved, @@ -83,7 +83,7 @@ describe("Setup Page", () => { { status: 401 }, ); } - return HttpResponse.json(MockUser); + return HttpResponse.json(MockUserOwner); }), http.get( "/api/v2/users/first", diff --git a/site/src/pages/TerminalPage/TerminalPage.stories.tsx b/site/src/pages/TerminalPage/TerminalPage.stories.tsx index d58f3e328e3ff..298f890637042 100644 --- a/site/src/pages/TerminalPage/TerminalPage.stories.tsx +++ b/site/src/pages/TerminalPage/TerminalPage.stories.tsx @@ -17,8 +17,8 @@ import { MockDeploymentConfig, MockEntitlements, MockExperiments, - MockUser, MockUserAppearanceSettings, + MockUserOwner, MockWorkspace, MockWorkspaceAgent, } from "testHelpers/entities"; @@ -66,7 +66,7 @@ const meta = { ), }), queries: [ - { key: ["me"], data: MockUser }, + { key: ["me"], data: MockUserOwner }, { key: ["authMethods"], data: MockAuthMethodsAll }, { key: ["hasFirstUser"], data: true }, { key: ["buildInfo"], data: MockBuildInfo }, diff --git a/site/src/pages/TerminalPage/TerminalPage.test.tsx b/site/src/pages/TerminalPage/TerminalPage.test.tsx index 7c1e6a154fa0d..7600fa5257d43 100644 --- a/site/src/pages/TerminalPage/TerminalPage.test.tsx +++ b/site/src/pages/TerminalPage/TerminalPage.test.tsx @@ -4,7 +4,7 @@ import { API } from "api/api"; import WS from "jest-websocket-mock"; import { http, HttpResponse } from "msw"; import { - MockUser, + MockUserOwner, MockWorkspace, MockWorkspaceAgent, } from "testHelpers/entities"; @@ -13,7 +13,7 @@ import { server } from "testHelpers/server"; import TerminalPage, { Language } from "./TerminalPage"; const renderTerminal = async ( - route = `/${MockUser.username}/${MockWorkspace.name}/terminal`, + route = `/${MockUserOwner.username}/${MockWorkspace.name}/terminal`, ) => { const utils = renderWithAuth(, { route, @@ -62,11 +62,11 @@ describe("TerminalPage", () => { `ws://localhost/api/v2/workspaceagents/${MockWorkspaceAgent.id}/pty`, ); await renderTerminal( - `/${MockUser.username}/${MockWorkspace.name}/terminal`, + `/${MockUserOwner.username}/${MockWorkspace.name}/terminal`, ); await waitFor(() => { expect(API.getWorkspaceByOwnerAndName).toHaveBeenCalledWith( - MockUser.username, + MockUserOwner.username, MockWorkspace.name, { include_deleted: true }, ); diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountForm.test.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountForm.test.tsx index fca96c4e82200..6c50ba8addc5c 100644 --- a/site/src/pages/UserSettingsPage/AccountPage/AccountForm.test.tsx +++ b/site/src/pages/UserSettingsPage/AccountPage/AccountForm.test.tsx @@ -1,6 +1,6 @@ import { screen } from "@testing-library/react"; import type { UpdateUserProfileRequest } from "api/typesGenerated"; -import { MockUser2 } from "testHelpers/entities"; +import { MockUserMember } from "testHelpers/entities"; import { render } from "testHelpers/renderHelpers"; import { AccountForm } from "./AccountForm"; @@ -12,15 +12,15 @@ describe("AccountForm", () => { it("allows updating username", async () => { // Given const mockInitialValues: UpdateUserProfileRequest = { - username: MockUser2.username, - name: MockUser2.name, + username: MockUserMember.username, + name: MockUserMember.name, }; // When render( { @@ -43,15 +43,15 @@ describe("AccountForm", () => { it("does not allow updating username", async () => { // Given const mockInitialValues: UpdateUserProfileRequest = { - username: MockUser2.username, - name: MockUser2.name, + username: MockUserMember.username, + name: MockUserMember.name, }; // When render( { diff --git a/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.stories.tsx b/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.stories.tsx index 6421f73e5c79c..a85327e420e3a 100644 --- a/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.stories.tsx +++ b/site/src/pages/UserSettingsPage/AccountPage/AccountUserGroups.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { MockGroup as MockGroup1, - MockUser, + MockUserOwner, mockApiError, } from "testHelpers/entities"; import { withDashboardProvider } from "testHelpers/storybook"; @@ -11,7 +11,7 @@ const MockGroup2 = { ...MockGroup1, avatar_url: "", display_name: "Goofy Goobers", - members: [MockUser], + members: [MockUserOwner], }; const mockError = mockApiError({ diff --git a/site/src/pages/UserSettingsPage/AppearancePage/AppearancePage.test.tsx b/site/src/pages/UserSettingsPage/AppearancePage/AppearancePage.test.tsx index ade7c55f51417..e6c2462acfabc 100644 --- a/site/src/pages/UserSettingsPage/AppearancePage/AppearancePage.test.tsx +++ b/site/src/pages/UserSettingsPage/AppearancePage/AppearancePage.test.tsx @@ -1,7 +1,7 @@ import { screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { API } from "api/api"; -import { MockUser } from "testHelpers/entities"; +import { MockUserOwner } from "testHelpers/entities"; import { renderWithAuth } from "testHelpers/renderHelpers"; import AppearancePage from "./AppearancePage"; @@ -10,7 +10,7 @@ describe("appearance page", () => { renderWithAuth(); jest.spyOn(API, "updateAppearanceSettings").mockResolvedValueOnce({ - ...MockUser, + ...MockUserOwner, theme_preference: "dark", terminal_font: "fira-code", }); @@ -26,7 +26,7 @@ describe("appearance page", () => { renderWithAuth(); jest.spyOn(API, "updateAppearanceSettings").mockResolvedValueOnce({ - ...MockUser, + ...MockUserOwner, terminal_font: "ibm-plex-mono", theme_preference: "light", }); @@ -46,7 +46,7 @@ describe("appearance page", () => { renderWithAuth(); jest.spyOn(API, "updateAppearanceSettings").mockResolvedValueOnce({ - ...MockUser, + ...MockUserOwner, terminal_font: "fira-code", theme_preference: "dark", }); @@ -69,12 +69,12 @@ describe("appearance page", () => { jest .spyOn(API, "updateAppearanceSettings") .mockResolvedValueOnce({ - ...MockUser, + ...MockUserOwner, terminal_font: "fira-code", theme_preference: "dark", }) .mockResolvedValueOnce({ - ...MockUser, + ...MockUserOwner, terminal_font: "ibm-plex-mono", theme_preference: "dark", }); diff --git a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.stories.tsx b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.stories.tsx index 14492eeefe68c..e2ac02e773d2d 100644 --- a/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.stories.tsx +++ b/site/src/pages/UserSettingsPage/NotificationsPage/NotificationsPage.stories.tsx @@ -11,7 +11,7 @@ import { MockNotificationMethodsResponse, MockNotificationPreferences, MockNotificationTemplates, - MockUser, + MockUserOwner, } from "testHelpers/entities"; import { withAuthProvider, @@ -27,7 +27,7 @@ const meta = { experiments: ["notifications"], queries: [ { - key: userNotificationPreferencesKey(MockUser.id), + key: userNotificationPreferencesKey(MockUserOwner.id), data: MockNotificationPreferences, }, { @@ -39,7 +39,7 @@ const meta = { data: MockNotificationMethodsResponse, }, ], - user: MockUser, + user: MockUserOwner, permissions: { viewDeploymentConfig: true }, }, decorators: [withGlobalSnackbar, withAuthProvider, withDashboardProvider], diff --git a/site/src/pages/UserSettingsPage/SchedulePage/SchedulePage.test.tsx b/site/src/pages/UserSettingsPage/SchedulePage/SchedulePage.test.tsx index b089c36543490..874dbf3c7fb32 100644 --- a/site/src/pages/UserSettingsPage/SchedulePage/SchedulePage.test.tsx +++ b/site/src/pages/UserSettingsPage/SchedulePage/SchedulePage.test.tsx @@ -2,7 +2,7 @@ import { fireEvent, screen, waitFor, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import type { UpdateUserQuietHoursScheduleRequest } from "api/typesGenerated"; import { http, HttpResponse } from "msw"; -import { MockUser } from "testHelpers/entities"; +import { MockUserOwner } from "testHelpers/entities"; import { renderWithAuth } from "testHelpers/renderHelpers"; import { server } from "testHelpers/server"; import SchedulePage from "./SchedulePage"; @@ -55,7 +55,7 @@ const cronTests = [ describe("SchedulePage", () => { beforeEach(() => { server.use( - http.get(`/api/v2/users/${MockUser.id}/quiet-hours`, () => { + http.get(`/api/v2/users/${MockUserOwner.id}/quiet-hours`, () => { return HttpResponse.json(defaultQuietHoursResponse); }), ); @@ -67,7 +67,7 @@ describe("SchedulePage", () => { async (test) => { server.use( http.put( - `/api/v2/users/${MockUser.id}/quiet-hours`, + `/api/v2/users/${MockUserOwner.id}/quiet-hours`, async ({ request }) => { const data = (await request.json()) as UpdateUserQuietHoursScheduleRequest; @@ -98,7 +98,7 @@ describe("SchedulePage", () => { describe("when it is an unknown error", () => { it("shows a generic error message", async () => { server.use( - http.put(`/api/v2/users/${MockUser.id}/quiet-hours`, () => { + http.put(`/api/v2/users/${MockUserOwner.id}/quiet-hours`, () => { return HttpResponse.json( { message: "oh no!", @@ -120,7 +120,7 @@ describe("SchedulePage", () => { describe("when user custom schedule is disabled", () => { it("shows a warning and disables the form", async () => { server.use( - http.get(`/api/v2/users/${MockUser.id}/quiet-hours`, () => { + http.get(`/api/v2/users/${MockUserOwner.id}/quiet-hours`, () => { return HttpResponse.json({ raw_schedule: "CRON_TZ=America/Chicago 0 0 * * *", user_can_set: false, diff --git a/site/src/pages/UsersPage/ResetPasswordDialog.stories.tsx b/site/src/pages/UsersPage/ResetPasswordDialog.stories.tsx index 633c5ab4ca4d3..bd64eef6ae7e5 100644 --- a/site/src/pages/UsersPage/ResetPasswordDialog.stories.tsx +++ b/site/src/pages/UsersPage/ResetPasswordDialog.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react"; -import { MockUser } from "testHelpers/entities"; +import { MockUserOwner } from "testHelpers/entities"; import { ResetPasswordDialog } from "./ResetPasswordDialog"; const meta: Meta = { @@ -13,7 +13,7 @@ type Story = StoryObj; const Example: Story = { args: { open: true, - user: MockUser, + user: MockUserOwner, newPassword: "somerandomstringhere", onConfirm: () => {}, onClose: () => {}, diff --git a/site/src/pages/UsersPage/UsersPage.stories.tsx b/site/src/pages/UsersPage/UsersPage.stories.tsx index 88059c35e3096..fdede4ec9f163 100644 --- a/site/src/pages/UsersPage/UsersPage.stories.tsx +++ b/site/src/pages/UsersPage/UsersPage.stories.tsx @@ -9,7 +9,7 @@ import type { User } from "api/typesGenerated"; import { MockGroups } from "pages/UsersPage/storybookData/groups"; import { MockRoles } from "pages/UsersPage/storybookData/roles"; import { MockUsers } from "pages/UsersPage/storybookData/users"; -import { MockAuthMethodsAll, MockUser } from "testHelpers/entities"; +import { MockAuthMethodsAll, MockUserOwner } from "testHelpers/entities"; import { withAuthProvider, withDashboardProvider, @@ -59,7 +59,7 @@ const parameters = { }, }, ], - user: MockUser, + user: MockUserOwner, permissions: { createUser: true, updateUsers: true, diff --git a/site/src/pages/UsersPage/UsersPageView.stories.tsx b/site/src/pages/UsersPage/UsersPageView.stories.tsx index 81e557221edff..c15b8aefc1b23 100644 --- a/site/src/pages/UsersPage/UsersPageView.stories.tsx +++ b/site/src/pages/UsersPage/UsersPageView.stories.tsx @@ -9,8 +9,8 @@ import type { ComponentProps } from "react"; import { MockAssignableSiteRoles, MockAuthMethodsPasswordOnly, - MockUser, - MockUser2, + MockUserMember, + MockUserOwner, mockApiError, } from "testHelpers/entities"; import { UsersPageView } from "./UsersPageView"; @@ -32,7 +32,7 @@ const meta: Meta = { component: UsersPageView, args: { isNonInitialPage: false, - users: [MockUser, MockUser2], + users: [MockUserOwner, MockUserMember], roles: MockAssignableSiteRoles, canEditUsers: true, filterProps: defaultFilterProps, diff --git a/site/src/pages/UsersPage/UsersTable/UsersTable.stories.tsx b/site/src/pages/UsersPage/UsersTable/UsersTable.stories.tsx index 4375e69cc77ca..5ef7116025919 100644 --- a/site/src/pages/UsersPage/UsersTable/UsersTable.stories.tsx +++ b/site/src/pages/UsersPage/UsersTable/UsersTable.stories.tsx @@ -6,15 +6,15 @@ import { MockGroup, MockMemberRole, MockTemplateAdminRole, - MockUser, - MockUser2, MockUserAdminRole, + MockUserMember, + MockUserOwner, } from "testHelpers/entities"; import { UsersTable } from "./UsersTable"; const mockGroupsByUserId = new Map([ - [MockUser.id, [MockGroup]], - [MockUser2.id, [MockGroup]], + [MockUserOwner.id, [MockGroup]], + [MockUserMember.id, [MockGroup]], ]); const meta: Meta = { @@ -31,7 +31,7 @@ type Story = StoryObj; export const Example: Story = { args: { - users: [MockUser, MockUser2], + users: [MockUserOwner, MockUserMember], roles: MockAssignableSiteRoles, canEditUsers: false, groupsByUserId: mockGroupsByUserId, @@ -41,10 +41,10 @@ export const Example: Story = { export const Editable: Story = { args: { users: [ - MockUser, - MockUser2, + MockUserOwner, + MockUserMember, { - ...MockUser, + ...MockUserOwner, username: "John Doe", email: "john.doe@coder.com", roles: [ @@ -56,14 +56,14 @@ export const Editable: Story = { status: "dormant", }, { - ...MockUser, + ...MockUserOwner, username: "Roger Moore", email: "roger.moore@coder.com", roles: [], status: "suspended", }, { - ...MockUser, + ...MockUserOwner, username: "OIDC User", email: "oidc.user@coder.com", roles: [], diff --git a/site/src/pages/WorkspacePage/Workspace.stories.tsx b/site/src/pages/WorkspacePage/Workspace.stories.tsx index 7d29b02c11cb6..957a651788d2c 100644 --- a/site/src/pages/WorkspacePage/Workspace.stories.tsx +++ b/site/src/pages/WorkspacePage/Workspace.stories.tsx @@ -40,7 +40,7 @@ const meta: Meta = { data: Mocks.MockListeningPortsResponse, }, ], - user: Mocks.MockUser, + user: Mocks.MockUserOwner, }, decorators: [ withAuthProvider, diff --git a/site/src/pages/WorkspacePage/WorkspaceActions/WorkspaceActions.stories.tsx b/site/src/pages/WorkspacePage/WorkspaceActions/WorkspaceActions.stories.tsx index 48dda92b49503..a67690f929b5f 100644 --- a/site/src/pages/WorkspacePage/WorkspaceActions/WorkspaceActions.stories.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceActions/WorkspaceActions.stories.tsx @@ -17,7 +17,7 @@ const meta: Meta = { }, decorators: [withDashboardProvider, withDesktopViewport, withAuthProvider], parameters: { - user: Mocks.MockUser, + user: Mocks.MockUserOwner, }, }; @@ -175,7 +175,7 @@ export const CancelShownForUser: Story = { workspace: Mocks.MockStartingWorkspace, }, parameters: { - user: Mocks.MockUser2, + user: Mocks.MockUserMember, }, }; @@ -187,7 +187,7 @@ export const CancelHiddenForUser: Story = { }, }, parameters: { - user: Mocks.MockUser2, + user: Mocks.MockUserMember, }, }; diff --git a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx index 71654b62a5f9a..a9f78f3610983 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx @@ -22,7 +22,7 @@ import { MockTemplate, MockTemplateVersionParameter1, MockTemplateVersionParameter2, - MockUser, + MockUserOwner, MockWorkspace, MockWorkspaceBuild, MockWorkspaceBuildDelete, @@ -320,7 +320,7 @@ describe("WorkspacePage", () => { }); it("restart the workspace with one time parameters when having the confirmation dialog", async () => { - localStorage.removeItem(`${MockUser.id}_ignoredWarnings`); + localStorage.removeItem(`${MockUserOwner.id}_ignoredWarnings`); jest.spyOn(API, "getWorkspaceParameters").mockResolvedValue({ templateVersionRichParameters: [ { diff --git a/site/src/pages/WorkspacePage/WorkspaceTopbar.stories.tsx b/site/src/pages/WorkspacePage/WorkspaceTopbar.stories.tsx index 84af8a518acd8..d9b093513ed8f 100644 --- a/site/src/pages/WorkspacePage/WorkspaceTopbar.stories.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceTopbar.stories.tsx @@ -7,7 +7,7 @@ import { MockOrganization, MockTemplate, MockTemplateVersion, - MockUser, + MockUserOwner, MockWorkspace, } from "testHelpers/entities"; import { withAuthProvider, withDashboardProvider } from "testHelpers/storybook"; @@ -41,7 +41,7 @@ const meta: Meta = { chromatic: { diffThreshold: 0.6, }, - user: MockUser, + user: MockUserOwner, }, }; @@ -278,7 +278,7 @@ export const WithQuotaNoOrgs: Story = { { key: getWorkspaceQuotaQueryKey( MockOrganization.name, - MockUser.username, + MockUserOwner.username, ), data: { credits_consumed: 2, @@ -296,7 +296,7 @@ export const WithQuotaWithOrgs: Story = { { key: getWorkspaceQuotaQueryKey( MockOrganization.name, - MockUser.username, + MockUserOwner.username, ), data: { credits_consumed: 2, diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx index 72ff8e165e6a8..9ebede41abe60 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx @@ -1,7 +1,7 @@ import { screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { http, HttpResponse } from "msw"; -import { MockUser, MockWorkspace } from "testHelpers/entities"; +import { MockUserOwner, MockWorkspace } from "testHelpers/entities"; import { renderWithWorkspaceSettingsLayout } from "testHelpers/renderHelpers"; import { server } from "testHelpers/server"; import { @@ -258,7 +258,7 @@ describe("WorkspaceSchedulePage", () => { }), ); renderWithWorkspaceSettingsLayout(, { - route: `/@${MockUser.username}/${MockWorkspace.name}/schedule`, + route: `/@${MockUserOwner.username}/${MockWorkspace.name}/schedule`, path: "/:username/:workspace/schedule", }); const user = userEvent.setup(); @@ -279,7 +279,7 @@ describe("WorkspaceSchedulePage", () => { describe("autostop change dialog", () => { it("shows if autostop is changed", async () => { renderWithWorkspaceSettingsLayout(, { - route: `/@${MockUser.username}/${MockWorkspace.name}/schedule`, + route: `/@${MockUserOwner.username}/${MockWorkspace.name}/schedule`, path: "/:username/:workspace/schedule", }); const user = userEvent.setup(); @@ -303,7 +303,7 @@ describe("WorkspaceSchedulePage", () => { it("doesn't show if autostop is not changed", async () => { renderWithWorkspaceSettingsLayout(, { - route: `/@${MockUser.username}/${MockWorkspace.name}/schedule`, + route: `/@${MockUserOwner.username}/${MockWorkspace.name}/schedule`, path: "/:username/:workspace/schedule", extraRoutes: [ { path: "/:username/:workspace", element:
Workspace
}, diff --git a/site/src/pages/WorkspacesPage/BatchDeleteConfirmation.stories.tsx b/site/src/pages/WorkspacesPage/BatchDeleteConfirmation.stories.tsx index 1e38068f5bed3..3abb069f05d7b 100644 --- a/site/src/pages/WorkspacesPage/BatchDeleteConfirmation.stories.tsx +++ b/site/src/pages/WorkspacesPage/BatchDeleteConfirmation.stories.tsx @@ -1,7 +1,7 @@ import { action } from "@storybook/addon-actions"; import type { Meta, StoryObj } from "@storybook/react"; import { chromatic } from "testHelpers/chromatic"; -import { MockUser2, MockWorkspace } from "testHelpers/entities"; +import { MockUserMember, MockWorkspace } from "testHelpers/entities"; import { BatchDeleteConfirmation } from "./BatchDeleteConfirmation"; const meta: Meta = { @@ -18,15 +18,15 @@ const meta: Meta = { ...MockWorkspace, name: "Test-Workspace-2", last_used_at: "2023-08-16T15:29:10.302441433Z", - owner_id: MockUser2.id, - owner_name: MockUser2.username, + owner_id: MockUserMember.id, + owner_name: MockUserMember.username, }, { ...MockWorkspace, name: "Test-Workspace-3", last_used_at: "2023-11-16T15:29:10.302441433Z", - owner_id: MockUser2.id, - owner_name: MockUser2.username, + owner_id: MockUserMember.id, + owner_name: MockUserMember.username, }, ], }, diff --git a/site/src/pages/WorkspacesPage/BatchUpdateConfirmation.stories.tsx b/site/src/pages/WorkspacesPage/BatchUpdateConfirmation.stories.tsx index 9a41bf43ff93e..76e8637ece71a 100644 --- a/site/src/pages/WorkspacesPage/BatchUpdateConfirmation.stories.tsx +++ b/site/src/pages/WorkspacesPage/BatchUpdateConfirmation.stories.tsx @@ -8,7 +8,7 @@ import { MockOutdatedWorkspace, MockRunningOutdatedWorkspace, MockTemplateVersion, - MockUser2, + MockUserMember, MockWorkspace, } from "testHelpers/entities"; import { @@ -25,8 +25,8 @@ const workspaces = [ { ...MockRunningOutdatedWorkspace, id: "6", - owner_id: MockUser2.id, - owner_name: MockUser2.username, + owner_id: MockUserMember.id, + owner_name: MockUserMember.username, }, ]; diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx index 47faef89dea0d..f9dc50d0cbff3 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx @@ -21,7 +21,7 @@ import { MockProxyLatencies, MockStoppedWorkspace, MockTemplate, - MockUser, + MockUserOwner, MockWorkspace, MockWorkspaceAppStatus, mockApiError, @@ -105,7 +105,7 @@ const defaultFilterProps = getDefaultFilterProps({ organizations: MockMenu, }, values: { - owner: MockUser.username, + owner: MockUserOwner.username, template: undefined, status: undefined, }, @@ -144,7 +144,7 @@ const meta: Meta = { data: MockBuildInfo, }, ], - user: MockUser, + user: MockUserOwner, }, decorators: [ withAuthProvider, diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index adec32f504d6d..8562a19236da1 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -484,7 +484,7 @@ export const MockMemberPermissions = { viewAuditLog: false, }; -export const MockUser: TypesGen.User = { +export const MockUserOwner: TypesGen.User = { id: "test-user", username: "TestUser", email: "test@coder.com", @@ -499,12 +499,7 @@ export const MockUser: TypesGen.User = { name: "", }; -const MockUserAdmin: TypesGen.User = { - ...MockUser, - roles: [MockUserAdminRole], -}; - -export const MockUser2: TypesGen.User = { +export const MockUserMember: TypesGen.User = { id: "test-user-2", username: "TestUser2", email: "test2@coder.com", @@ -541,28 +536,28 @@ export const MockUserAppearanceSettings: TypesGen.UserAppearanceSettings = { export const MockOrganizationMember: TypesGen.OrganizationMemberWithUserData = { organization_id: MockOrganization.id, - user_id: MockUser.id, - username: MockUser.username, - email: MockUser.email, + user_id: MockUserOwner.id, + username: MockUserOwner.username, + email: MockUserOwner.email, created_at: "", updated_at: "", - name: MockUser.name, - avatar_url: MockUser.avatar_url, - global_roles: MockUser.roles, + name: MockUserOwner.name, + avatar_url: MockUserOwner.avatar_url, + global_roles: MockUserOwner.roles, roles: [], }; export const MockOrganizationMember2: TypesGen.OrganizationMemberWithUserData = { organization_id: MockOrganization.id, - user_id: MockUser2.id, - username: MockUser2.username, - email: MockUser2.email, + user_id: MockUserMember.id, + username: MockUserMember.username, + email: MockUserMember.email, created_at: "", updated_at: "", - name: MockUser2.name, - avatar_url: MockUser2.avatar_url, - global_roles: MockUser2.roles, + name: MockUserMember.name, + avatar_url: MockUserMember.avatar_url, + global_roles: MockUserMember.roles, roles: [], }; @@ -613,7 +608,7 @@ const MockUserAuthProvisioner: TypesGen.ProvisionerDaemon = { ...MockProvisioner, id: "test-user-auth-provisioner", key_id: MockProvisionerUserAuthKey.id, - name: `${MockUser.name}'s provisioner`, + name: `${MockUserOwner.name}'s provisioner`, tags: { scope: "user" }, }; @@ -732,7 +727,7 @@ name:Template test You can add instructions here [Some link info](https://coder.com)`, - created_by: MockUser, + created_by: MockUserOwner, archived: false, }; @@ -751,7 +746,7 @@ name:Template test 2 You can add instructions here [Some link info](https://coder.com)`, - created_by: MockUser, + created_by: MockUserOwner, archived: false, }; @@ -1246,17 +1241,17 @@ export const MockWorkspaceBuild: TypesGen.WorkspaceBuild = { build_number: 1, created_at: "2022-05-17T17:39:01.382927298Z", id: "1", - initiator_id: MockUser.id, - initiator_name: MockUser.username, + initiator_id: MockUserOwner.id, + initiator_name: MockUserOwner.username, job: MockProvisionerJob, template_version_id: MockTemplateVersion.id, template_version_name: MockTemplateVersion.name, transition: "start", updated_at: "2022-05-17T17:39:01.382927298Z", workspace_name: "test-workspace", - workspace_owner_id: MockUser.id, - workspace_owner_name: MockUser.username, - workspace_owner_avatar_url: MockUser.avatar_url, + workspace_owner_id: MockUserOwner.id, + workspace_owner_name: MockUserOwner.username, + workspace_owner_avatar_url: MockUserOwner.avatar_url, workspace_id: "759f1d46-3174-453d-aa60-980a9c1442f3", deadline: "2022-05-17T23:39:00.00Z", reason: "initiator", @@ -1274,17 +1269,17 @@ const MockWorkspaceBuildAutostart: TypesGen.WorkspaceBuild = { build_number: 1, created_at: "2022-05-17T17:39:01.382927298Z", id: "1", - initiator_id: MockUser.id, - initiator_name: MockUser.username, + initiator_id: MockUserOwner.id, + initiator_name: MockUserOwner.username, job: MockProvisionerJob, template_version_id: MockTemplateVersion.id, template_version_name: MockTemplateVersion.name, transition: "start", updated_at: "2022-05-17T17:39:01.382927298Z", workspace_name: "test-workspace", - workspace_owner_id: MockUser.id, - workspace_owner_name: MockUser.username, - workspace_owner_avatar_url: MockUser.avatar_url, + workspace_owner_id: MockUserOwner.id, + workspace_owner_name: MockUserOwner.username, + workspace_owner_avatar_url: MockUserOwner.avatar_url, workspace_id: "759f1d46-3174-453d-aa60-980a9c1442f3", deadline: "2022-05-17T23:39:00.00Z", reason: "autostart", @@ -1298,17 +1293,17 @@ const MockWorkspaceBuildAutostop: TypesGen.WorkspaceBuild = { build_number: 1, created_at: "2022-05-17T17:39:01.382927298Z", id: "1", - initiator_id: MockUser.id, - initiator_name: MockUser.username, + initiator_id: MockUserOwner.id, + initiator_name: MockUserOwner.username, job: MockProvisionerJob, template_version_id: MockTemplateVersion.id, template_version_name: MockTemplateVersion.name, transition: "start", updated_at: "2022-05-17T17:39:01.382927298Z", workspace_name: "test-workspace", - workspace_owner_id: MockUser.id, - workspace_owner_name: MockUser.username, - workspace_owner_avatar_url: MockUser.avatar_url, + workspace_owner_id: MockUserOwner.id, + workspace_owner_name: MockUserOwner.username, + workspace_owner_avatar_url: MockUserOwner.avatar_url, workspace_id: "759f1d46-3174-453d-aa60-980a9c1442f3", deadline: "2022-05-17T23:39:00.00Z", reason: "autostop", @@ -1324,17 +1319,17 @@ export const MockFailedWorkspaceBuild = ( build_number: 1, created_at: "2022-05-17T17:39:01.382927298Z", id: "1", - initiator_id: MockUser.id, - initiator_name: MockUser.username, + initiator_id: MockUserOwner.id, + initiator_name: MockUserOwner.username, job: MockFailedProvisionerJob, template_version_id: MockTemplateVersion.id, template_version_name: MockTemplateVersion.name, transition: transition, updated_at: "2022-05-17T17:39:01.382927298Z", workspace_name: "test-workspace", - workspace_owner_id: MockUser.id, - workspace_owner_name: MockUser.username, - workspace_owner_avatar_url: MockUser.avatar_url, + workspace_owner_id: MockUserOwner.id, + workspace_owner_name: MockUserOwner.username, + workspace_owner_avatar_url: MockUserOwner.avatar_url, workspace_id: "759f1d46-3174-453d-aa60-980a9c1442f3", deadline: "2022-05-17T23:39:00.00Z", reason: "initiator", @@ -1378,10 +1373,10 @@ export const MockWorkspace: TypesGen.Workspace = { template_active_version_id: MockTemplate.active_version_id, template_require_active_version: MockTemplate.require_active_version, outdated: false, - owner_id: MockUser.id, + owner_id: MockUserOwner.id, organization_id: MockOrganization.id, organization_name: "default", - owner_name: MockUser.username, + owner_name: MockUserOwner.username, owner_avatar_url: "https://avatars.githubusercontent.com/u/7122116?v=4", autostart_schedule: MockWorkspaceAutostartEnabled.schedule, ttl_ms: 2 * 60 * 60 * 1000, @@ -2508,7 +2503,7 @@ export const MockAuditLog: TypesGen.AuditLog = { status_code: 200, additional_fields: {}, description: "{user} created workspace {target}", - user: MockUser, + user: MockUserOwner, resource_link: "/@admin/bruno-dev", is_deleted: false, }; @@ -2579,7 +2574,7 @@ export const MockAuditLog3: TypesGen.AuditLog = { status_code: 200, additional_fields: {}, description: "{user} updated template {target}", - user: MockUser, + user: MockUserOwner, resource_link: "/templates/docker", is_deleted: false, }; @@ -2785,7 +2780,7 @@ export const MockGroup: TypesGen.Group = { organization_id: MockOrganization.id, organization_name: MockOrganization.name, organization_display_name: MockOrganization.display_name, - members: [MockUser, MockUser2], + members: [MockUserOwner, MockUserMember], quota_allowance: 5, source: "user", total_member_count: 2, @@ -2799,7 +2794,7 @@ export const MockGroup2: TypesGen.Group = { organization_id: MockOrganization.id, organization_name: MockOrganization.name, organization_display_name: MockOrganization.display_name, - members: [MockUser, MockUser2], + members: [MockUserOwner, MockUserMember], quota_allowance: 5, source: "user", total_member_count: 2, @@ -2826,7 +2821,7 @@ export const MockTemplateACL: TypesGen.TemplateACL = { { ...MockEveryoneGroup, role: "use" }, { ...MockGroup, role: "admin" }, ], - users: [{ ...MockUser, role: "use" }], + users: [{ ...MockUserOwner, role: "use" }], }; export const MockTemplateACLEmpty: TypesGen.TemplateACL = { @@ -4277,7 +4272,7 @@ export const MockNotification: TypesGen.InboxNotification = { url: "https://dev.coder.com/templates/coder/coder", }, ], - user_id: MockUser.id, + user_id: MockUserOwner.id, template_id: MockTemplate.id, targets: [], title: "User account created", diff --git a/site/src/testHelpers/handlers.ts b/site/src/testHelpers/handlers.ts index 51906fae2ad0d..3f163a4d3a0e8 100644 --- a/site/src/testHelpers/handlers.ts +++ b/site/src/testHelpers/handlers.ts @@ -135,12 +135,12 @@ export const handlers = [ // users http.get("/api/v2/users", () => { return HttpResponse.json({ - users: [M.MockUser, M.MockUser2, M.SuspendedMockUser], + users: [M.MockUserOwner, M.MockUserMember, M.SuspendedMockUser], count: 26, }); }), http.post("/api/v2/users", () => { - return HttpResponse.json(M.MockUser); + return HttpResponse.json(M.MockUserOwner); }), http.get("/api/v2/users/:userid/login-type", () => { return HttpResponse.json({ @@ -160,7 +160,7 @@ export const handlers = [ return new HttpResponse(null, { status: 200 }); }), http.get("/api/v2/users/me", () => { - return HttpResponse.json(M.MockUser); + return HttpResponse.json(M.MockUserOwner); }), http.get("/api/v2/users/me/appearance", () => { return HttpResponse.json(M.MockUserAppearanceSettings); @@ -198,7 +198,7 @@ export const handlers = [ return new HttpResponse(null, { status: 200 }); }), http.post("/api/v2/users/first", () => { - return HttpResponse.json(M.MockUser); + return HttpResponse.json(M.MockUserOwner); }), // workspaces diff --git a/site/src/testHelpers/renderHelpers.tsx b/site/src/testHelpers/renderHelpers.tsx index eb76b481783da..5c02eb23150ee 100644 --- a/site/src/testHelpers/renderHelpers.tsx +++ b/site/src/testHelpers/renderHelpers.tsx @@ -20,7 +20,7 @@ import { createMemoryRouter, } from "react-router-dom"; import themes, { DEFAULT_THEME } from "theme"; -import { MockUser } from "./entities"; +import { MockUserOwner } from "./entities"; export function createTestQueryClient() { // Helps create one query client for each test case, to make sure that tests @@ -118,7 +118,7 @@ export function renderWithAuth( return { ...renderResult, - user: MockUser, + user: MockUserOwner, }; } @@ -154,7 +154,7 @@ export function renderWithTemplateSettingsLayout( ); return { - user: MockUser, + user: MockUserOwner, ...renderResult, }; } @@ -191,7 +191,7 @@ export function renderWithWorkspaceSettingsLayout( ); return { - user: MockUser, + user: MockUserOwner, ...renderResult, }; } @@ -228,7 +228,7 @@ export function renderWithOrganizationSettingsLayout( ); return { - user: MockUser, + user: MockUserOwner, ...renderResult, }; } From 43414033466baa15615d6adf1bc545390bc5b224 Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Thu, 8 May 2025 09:42:39 -0300 Subject: [PATCH 3/4] chore: simplify workspaces data fetching (#17703) We've been using an abstraction that was not necessary to fetch workspaces data. I also took sometime to use the new useWorkspaceUpdate hook in the update workspace tooltip that was missing some important steps like confirmation. --- site/src/components/Badge/Badge.tsx | 2 +- site/src/hooks/usePagination.ts | 6 +- .../WorkspaceOutdatedTooltip.stories.tsx | 23 ++-- .../WorkspaceOutdatedTooltip.tsx | 130 +++++++++--------- .../useWorkspacesToBeDeleted.ts | 25 ++-- .../pages/WorkspacesPage/WorkspacesPage.tsx | 18 +-- .../WorkspacesPage/WorkspacesPageView.tsx | 3 - .../pages/WorkspacesPage/WorkspacesTable.tsx | 13 +- site/src/pages/WorkspacesPage/data.ts | 112 --------------- 9 files changed, 103 insertions(+), 229 deletions(-) delete mode 100644 site/src/pages/WorkspacesPage/data.ts diff --git a/site/src/components/Badge/Badge.tsx b/site/src/components/Badge/Badge.tsx index 8995222027ed0..e6b23b8a4dd94 100644 --- a/site/src/components/Badge/Badge.tsx +++ b/site/src/components/Badge/Badge.tsx @@ -19,7 +19,7 @@ const badgeVariants = cva( warning: "border border-solid border-border-warning bg-surface-orange text-content-warning shadow", destructive: - "border border-solid border-border-destructive bg-surface-red text-content-highlight-red shadow", + "border border-solid border-border-destructive bg-surface-red text-highlight-red shadow", }, size: { xs: "text-2xs font-regular h-5 [&_svg]:hidden rounded px-1.5", diff --git a/site/src/hooks/usePagination.ts b/site/src/hooks/usePagination.ts index 2ab409e85c9d8..72ea70868fb30 100644 --- a/site/src/hooks/usePagination.ts +++ b/site/src/hooks/usePagination.ts @@ -9,7 +9,7 @@ export const usePagination = ({ const [searchParams, setSearchParams] = searchParamsResult; const page = searchParams.get("page") ? Number(searchParams.get("page")) : 1; const limit = DEFAULT_RECORDS_PER_PAGE; - const offset = calcOffset(page, limit); + const offset = page <= 0 ? 0 : (page - 1) * limit; const goToPage = (page: number) => { searchParams.set("page", page.toString()); @@ -23,7 +23,3 @@ export const usePagination = ({ offset, }; }; - -export const calcOffset = (page: number, limit: number) => { - return page <= 0 ? 0 : (page - 1) * limit; -}; diff --git a/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.stories.tsx b/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.stories.tsx index bb0c93e74c8c6..843f8131b793f 100644 --- a/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.stories.tsx +++ b/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.stories.tsx @@ -1,7 +1,10 @@ -import { action } from "@storybook/addon-actions"; import type { Meta, StoryObj } from "@storybook/react"; import { expect, userEvent, waitFor, within } from "@storybook/test"; -import { MockTemplate, MockTemplateVersion } from "testHelpers/entities"; +import { + MockTemplate, + MockTemplateVersion, + MockWorkspace, +} from "testHelpers/entities"; import { withDashboardProvider } from "testHelpers/storybook"; import { WorkspaceOutdatedTooltip } from "./WorkspaceOutdatedTooltip"; @@ -18,9 +21,11 @@ const meta: Meta = { ], }, args: { - onUpdateVersion: action("onUpdateVersion"), - templateName: MockTemplate.display_name, - latestVersionId: MockTemplateVersion.id, + workspace: { + ...MockWorkspace, + template_name: MockTemplate.display_name, + template_active_version_id: MockTemplateVersion.id, + }, }, }; @@ -29,14 +34,12 @@ type Story = StoryObj; const Example: Story = { play: async ({ canvasElement, step }) => { - const screen = within(canvasElement); + const body = within(canvasElement.ownerDocument.body); await step("activate hover trigger", async () => { - await userEvent.hover(screen.getByRole("button")); + await userEvent.hover(body.getByRole("button")); await waitFor(() => - expect( - screen.getByText(MockTemplateVersion.message), - ).toBeInTheDocument(), + expect(body.getByText(MockTemplateVersion.message)).toBeInTheDocument(), ); }); }, diff --git a/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.tsx b/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.tsx index ada4c63d9a0bb..9615560840c59 100644 --- a/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.tsx +++ b/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.tsx @@ -3,7 +3,10 @@ import InfoIcon from "@mui/icons-material/InfoOutlined"; import RefreshIcon from "@mui/icons-material/Refresh"; import Link from "@mui/material/Link"; import Skeleton from "@mui/material/Skeleton"; +import { getErrorDetail, getErrorMessage } from "api/errors"; import { templateVersion } from "api/queries/templates"; +import type { Workspace } from "api/typesGenerated"; +import { displayError } from "components/GlobalSnackbar/utils"; import { HelpTooltip, HelpTooltipAction, @@ -17,102 +20,99 @@ import { usePopover } from "components/deprecated/Popover/Popover"; import { linkToTemplate, useLinks } from "modules/navigation"; import type { FC } from "react"; import { useQuery } from "react-query"; - -const Language = { - outdatedLabel: "Outdated", - versionTooltipText: - "This workspace version is outdated and a newer version is available.", - updateVersionLabel: "Update", -}; +import { + WorkspaceUpdateDialogs, + useWorkspaceUpdate, +} from "../WorkspaceUpdateDialogs"; interface TooltipProps { - organizationName: string; - templateName: string; - latestVersionId: string; - onUpdateVersion: () => void; - ariaLabel?: string; + workspace: Workspace; } export const WorkspaceOutdatedTooltip: FC = (props) => { return ( - + + Outdated info - ); }; -const WorkspaceOutdatedTooltipContent: FC = ({ - organizationName, - templateName, - latestVersionId, - onUpdateVersion, - ariaLabel, -}) => { +const WorkspaceOutdatedTooltipContent: FC = ({ workspace }) => { const getLink = useLinks(); const theme = useTheme(); const popover = usePopover(); const { data: activeVersion } = useQuery({ - ...templateVersion(latestVersionId), + ...templateVersion(workspace.template_active_version_id), enabled: popover.open, }); + const updateWorkspace = useWorkspaceUpdate({ + workspace, + latestVersion: activeVersion, + onError: (error) => { + displayError( + getErrorMessage(error, "Error updating workspace"), + getErrorDetail(error), + ); + }, + }); const versionLink = `${getLink( - linkToTemplate(organizationName, templateName), + linkToTemplate(workspace.organization_name, workspace.template_name), )}`; return ( - - {Language.outdatedLabel} - {Language.versionTooltipText} + <> + + Outdated + + This workspace version is outdated and a newer version is available. + -
-
-
New version
-
- {activeVersion ? ( - - {activeVersion.name} - - ) : ( - - )} +
+
+
New version
+
+ {activeVersion ? ( + + {activeVersion.name} + + ) : ( + + )} +
-
-
-
Message
-
- {activeVersion ? ( - activeVersion.message || "No message" - ) : ( - - )} +
+
Message
+
+ {activeVersion ? ( + activeVersion.message || "No message" + ) : ( + + )} +
-
- - - {Language.updateVersionLabel} - - - + + + Update + + + + + ); }; diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/useWorkspacesToBeDeleted.ts b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/useWorkspacesToBeDeleted.ts index 5a974d5d8fe31..d55f0b6c54fff 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/useWorkspacesToBeDeleted.ts +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/useWorkspacesToBeDeleted.ts @@ -1,7 +1,7 @@ +import { workspaces } from "api/queries/workspaces"; import type { Template, Workspace } from "api/typesGenerated"; import { compareAsc } from "date-fns"; -import { calcOffset } from "hooks/usePagination"; -import { useWorkspacesData } from "pages/WorkspacesPage/data"; +import { useQuery } from "react-query"; import type { TemplateScheduleFormValues } from "./formHelpers"; export const useWorkspacesToGoDormant = ( @@ -9,11 +9,11 @@ export const useWorkspacesToGoDormant = ( formValues: TemplateScheduleFormValues, fromDate: Date, ) => { - const { data } = useWorkspacesData({ - offset: calcOffset(0, 0), - limit: 0, - q: `template:${template.name}`, - }); + const { data } = useQuery( + workspaces({ + q: `template:${template.name}`, + }), + ); return data?.workspaces?.filter((workspace: Workspace) => { if (!formValues.time_til_dormant_ms) { @@ -40,11 +40,12 @@ export const useWorkspacesToBeDeleted = ( formValues: TemplateScheduleFormValues, fromDate: Date, ) => { - const { data } = useWorkspacesData({ - offset: calcOffset(0, 0), - limit: 0, - q: `template:${template.name} dormant:true`, - }); + const { data } = useQuery( + workspaces({ + q: `template:${template.name} dormant:true`, + }), + ); + return data?.workspaces?.filter((workspace: Workspace) => { if (!workspace.dormant_at || !formValues.time_til_dormant_autodelete_ms) { return false; diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx index 551c554fd5ee3..d0439be0f0462 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx @@ -1,6 +1,7 @@ import { getErrorDetail, getErrorMessage } from "api/errors"; import { workspacePermissionsByOrganization } from "api/queries/organizations"; import { templates } from "api/queries/templates"; +import { workspaces } from "api/queries/workspaces"; import type { Workspace } from "api/typesGenerated"; import { useFilter } from "components/Filter/Filter"; import { useUserFilterMenu } from "components/Filter/UserFilter"; @@ -19,7 +20,6 @@ import { BatchDeleteConfirmation } from "./BatchDeleteConfirmation"; import { BatchUpdateConfirmation } from "./BatchUpdateConfirmation"; import { WorkspacesPageView } from "./WorkspacesPageView"; import { useBatchActions } from "./batchActions"; -import { useWorkspaceUpdate, useWorkspacesData } from "./data"; import { useStatusFilterMenu, useTemplateFilterMenu } from "./filter/menus"; function useSafeSearchParams() { @@ -45,9 +45,7 @@ const WorkspacesPage: FC = () => { const pagination = usePagination({ searchParamsResult }); const { permissions, user: me } = useAuthenticated(); const { entitlements } = useDashboard(); - const templatesQuery = useQuery(templates()); - const workspacePermissionsQuery = useQuery( workspacePermissionsByOrganization( templatesQuery.data?.map((template) => template.organization_id), @@ -73,12 +71,17 @@ const WorkspacesPage: FC = () => { onFilterChange: () => pagination.goToPage(1), }); - const { data, error, queryKey, refetch } = useWorkspacesData({ + const workspacesQueryOptions = workspaces({ ...pagination, q: filterProps.filter.query, }); + const { data, error, refetch } = useQuery({ + ...workspacesQueryOptions, + refetchInterval: (_, query) => { + return query.state.error ? false : 5_000; + }, + }); - const updateWorkspace = useWorkspaceUpdate(queryKey); const [checkedWorkspaces, setCheckedWorkspaces] = useState< readonly Workspace[] >([]); @@ -123,9 +126,6 @@ const WorkspacesPage: FC = () => { limit={pagination.limit} onPageChange={pagination.goToPage} filterProps={filterProps} - onUpdateWorkspace={(workspace) => { - updateWorkspace.mutate(workspace); - }} isRunningBatchAction={batchActions.isLoading} onDeleteAll={() => setConfirmingBatchAction("delete")} onUpdateAll={() => setConfirmingBatchAction("update")} @@ -133,7 +133,7 @@ const WorkspacesPage: FC = () => { onStopAll={() => batchActions.stopAll(checkedWorkspaces)} onActionSuccess={async () => { await queryClient.invalidateQueries({ - queryKey, + queryKey: workspacesQueryOptions.queryKey, }); }} onActionError={(error) => { diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx index 6db41fef8fa6c..6633f884e1263 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx @@ -53,7 +53,6 @@ export interface WorkspacesPageViewProps { page: number; limit: number; onPageChange: (page: number) => void; - onUpdateWorkspace: (workspace: Workspace) => void; onCheckChange: (checkedWorkspaces: readonly Workspace[]) => void; isRunningBatchAction: boolean; onDeleteAll: () => void; @@ -76,7 +75,6 @@ export const WorkspacesPageView: FC = ({ count, filterProps, onPageChange, - onUpdateWorkspace, page, checkedWorkspaces, onCheckChange, @@ -223,7 +221,6 @@ export const WorkspacesPageView: FC = ({ canCreateTemplate={canCreateTemplate} workspaces={workspaces} isUsingFilter={filterProps.filter.used} - onUpdateWorkspace={onUpdateWorkspace} checkedWorkspaces={checkedWorkspaces} onCheckChange={onCheckChange} canCheckWorkspaces={canCheckWorkspaces} diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index 92dcee60dec96..b4f1c98b27261 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -97,7 +97,6 @@ export interface WorkspacesTableProps { checkedWorkspaces: readonly Workspace[]; error?: unknown; isUsingFilter: boolean; - onUpdateWorkspace: (workspace: Workspace) => void; onCheckChange: (checkedWorkspaces: readonly Workspace[]) => void; canCheckWorkspaces: boolean; templates?: Template[]; @@ -110,7 +109,6 @@ export const WorkspacesTable: FC = ({ workspaces, checkedWorkspaces, isUsingFilter, - onUpdateWorkspace, onCheckChange, canCheckWorkspaces, templates, @@ -243,16 +241,7 @@ export const WorkspacesTable: FC = ({ {workspace.name} {workspace.favorite && } {workspace.outdated && ( - { - onUpdateWorkspace(workspace); - }} - /> + )} } diff --git a/site/src/pages/WorkspacesPage/data.ts b/site/src/pages/WorkspacesPage/data.ts deleted file mode 100644 index 764ea218aa96c..0000000000000 --- a/site/src/pages/WorkspacesPage/data.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { API } from "api/api"; -import { getErrorMessage } from "api/errors"; -import { workspaces } from "api/queries/workspaces"; -import type { - Workspace, - WorkspaceBuild, - WorkspacesRequest, - WorkspacesResponse, -} from "api/typesGenerated"; -import { displayError } from "components/GlobalSnackbar/utils"; -import { useState } from "react"; -import { - type QueryKey, - useMutation, - useQuery, - useQueryClient, -} from "react-query"; - -export const useWorkspacesData = (req: WorkspacesRequest) => { - const [shouldRefetch, setShouldRefetch] = useState(true); - const workspacesQueryOptions = workspaces(req); - const result = useQuery({ - ...workspacesQueryOptions, - onSuccess: () => { - setShouldRefetch(true); - }, - onError: () => { - setShouldRefetch(false); - }, - refetchInterval: shouldRefetch ? 5_000 : undefined, - }); - - return { - ...result, - queryKey: workspacesQueryOptions.queryKey, - }; -}; - -export const useWorkspaceUpdate = (queryKey: QueryKey) => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: API.updateWorkspaceVersion, - onMutate: async (workspace) => { - await queryClient.cancelQueries({ queryKey }); - queryClient.setQueryData(queryKey, (oldResponse) => { - if (oldResponse) { - return assignPendingStatus(oldResponse, workspace); - } - }); - }, - onSuccess: (workspaceBuild) => { - queryClient.setQueryData(queryKey, (oldResponse) => { - if (oldResponse) { - return assignLatestBuild(oldResponse, workspaceBuild); - } - }); - }, - onError: (error) => { - const message = getErrorMessage( - error, - "Error updating workspace version", - ); - displayError(message); - }, - }); -}; - -const assignLatestBuild = ( - oldResponse: WorkspacesResponse, - build: WorkspaceBuild, -): WorkspacesResponse => { - return { - ...oldResponse, - workspaces: oldResponse.workspaces.map((workspace) => { - if (workspace.id === build.workspace_id) { - return { - ...workspace, - latest_build: build, - }; - } - - return workspace; - }), - }; -}; - -const assignPendingStatus = ( - oldResponse: WorkspacesResponse, - workspace: Workspace, -): WorkspacesResponse => { - return { - ...oldResponse, - workspaces: oldResponse.workspaces.map((workspaceItem) => { - if (workspaceItem.id === workspace.id) { - return { - ...workspace, - latest_build: { - ...workspace.latest_build, - status: "pending", - job: { - ...workspace.latest_build.job, - status: "pending", - }, - }, - } as Workspace; - } - - return workspaceItem; - }), - }; -}; From 857587b35d3eaff2f8a1062c9e4cfb66b786616b Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Thu, 8 May 2025 09:51:10 -0300 Subject: [PATCH 4/4] fix: do not share token with http app urls (#17720) It's a security issue to share the API token, and the protocols that we actually want to share it with are not HTTP and handled locally on the same machine. Security issue introduced by https://github.com/coder/coder/pull/17708 --- site/src/modules/resources/AppLink/AppLink.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/site/src/modules/resources/AppLink/AppLink.tsx b/site/src/modules/resources/AppLink/AppLink.tsx index 5c4209a8f72c7..0e94335ba0c43 100644 --- a/site/src/modules/resources/AppLink/AppLink.tsx +++ b/site/src/modules/resources/AppLink/AppLink.tsx @@ -106,7 +106,11 @@ export const AppLink: FC = ({ app, workspace, agent }) => { event.preventDefault(); - if (app.external) { + // HTTP links should never need the session token, since Cookies + // handle sharing it when you access the Coder Dashboard. We should + // never be forwarding the bare session token to other domains! + const isHttp = app.url?.startsWith("http"); + if (app.external && !isHttp) { // This is a magic undocumented string that is replaced // with a brand-new session token from the backend. // This only exists for external URLs, and should only