From 19e734146bd2b3b3fbb6111452358027192f524a Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Mon, 13 Nov 2023 14:37:11 +0000 Subject: [PATCH 1/5] added workspace delete dialog --- docs/workspaces.md | 7 +- site/src/api/api.ts | 2 + site/src/components/Dialogs/Dialog.tsx | 18 +- site/src/components/MoreMenu/MoreMenu.tsx | 2 +- .../WorkspaceDeleteDialog.tsx | 183 ++++++++++++++++++ .../WorkspacePage/WorkspaceReadyPage.tsx | 15 +- .../xServices/workspace/workspaceXService.ts | 13 +- 7 files changed, 216 insertions(+), 24 deletions(-) create mode 100644 site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx diff --git a/docs/workspaces.md b/docs/workspaces.md index ad5001ee82cfe..499a989a11d6e 100644 --- a/docs/workspaces.md +++ b/docs/workspaces.md @@ -115,7 +115,12 @@ though the exact behavior depends on the template. For more information, see > You can use `coder show ` to see which resources are > persistent and which are ephemeral. -When a workspace is deleted, all of the workspace's resources are deleted. +Typically, when a workspace is deleted, all of the workspace's resources are +deleted along with it. Rarely, one may wish to delete a workspace without +deleting its resources, e.g. a workspace in a broken state. Users with the +Template Admin role have the option to do so both in the UI, and also in the CLI +by running the `delete` command with the `--orphan` flag. This option should be +considered cautiously as orphaning may lead to unaccounted cloud resources. ## Repairing workspaces diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 7e50291a9b995..dbf5edd3733ec 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -546,10 +546,12 @@ export const stopWorkspace = ( export const deleteWorkspace = ( workspaceId: string, logLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"], + orphan?: TypesGen.CreateWorkspaceBuildRequest["orphan"], ) => postWorkspaceBuild(workspaceId, { transition: "delete", log_level: logLevel, + orphan, }); export const cancelWorkspaceBuild = async ( diff --git a/site/src/components/Dialogs/Dialog.tsx b/site/src/components/Dialogs/Dialog.tsx index 535f151a83dc1..c64a48c454ae1 100644 --- a/site/src/components/Dialogs/Dialog.tsx +++ b/site/src/components/Dialogs/Dialog.tsx @@ -59,7 +59,7 @@ export const DialogActionButtons: React.FC = ({ disabled={disabled} type="submit" css={[ - type === "delete" && styles.errorButton, + type === "delete" && styles.warningButton, type === "success" && styles.successButton, ]} > @@ -71,26 +71,26 @@ export const DialogActionButtons: React.FC = ({ }; const styles = { - errorButton: (theme) => ({ + warningButton: (theme) => ({ "&.MuiButton-contained": { - backgroundColor: colors.red[10], - borderColor: colors.red[9], + backgroundColor: colors.orange[12], + borderColor: colors.orange[9], "&:not(.MuiLoadingButton-loading)": { color: theme.palette.text.primary, }, "&:hover:not(:disabled)": { - backgroundColor: colors.red[9], - borderColor: colors.red[9], + backgroundColor: colors.orange[9], + borderColor: colors.orange[9], }, "&.Mui-disabled": { - backgroundColor: colors.red[15], - borderColor: colors.red[15], + backgroundColor: colors.orange[14], + borderColor: colors.orange[15], "&:not(.MuiLoadingButton-loading)": { - color: colors.red[9], + color: colors.orange[12], }, }, }, diff --git a/site/src/components/MoreMenu/MoreMenu.tsx b/site/src/components/MoreMenu/MoreMenu.tsx index b1af6c2caadda..d86cfc82439ad 100644 --- a/site/src/components/MoreMenu/MoreMenu.tsx +++ b/site/src/components/MoreMenu/MoreMenu.tsx @@ -91,7 +91,7 @@ export const MoreMenuItem = ( {...menuItemProps} css={(theme) => ({ fontSize: 14, - color: danger ? theme.palette.error.light : undefined, + color: danger ? theme.palette.warning.light : undefined, "& .MuiSvgIcon-root": { width: 16, height: 16, diff --git a/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx b/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx new file mode 100644 index 0000000000000..002ea0da421b9 --- /dev/null +++ b/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx @@ -0,0 +1,183 @@ +import { Workspace, CreateWorkspaceBuildRequest } from "api/typesGenerated"; +import { usePermissions } from "hooks/usePermissions"; +import { useId, useState, FormEvent } from "react"; +import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; +import { type Interpolation, type Theme } from "@emotion/react"; +import { colors } from "theme/colors"; +import dayjs from "dayjs"; +import TextField from "@mui/material/TextField"; +import { docs } from "utils/docs"; +import Link from "@mui/material/Link"; +import Checkbox from "@mui/material/Checkbox"; + +const styles = { + workspaceInfo: (theme) => ({ + display: "flex", + justifyContent: "space-between", + backgroundColor: colors.gray[14], + border: `1px solid ${theme.palette.divider}`, + borderRadius: 8, + padding: 12, + marginBottom: 20, + lineHeight: "18px", + + "& .name": { + fontSize: 18, + fontWeight: 800, + color: theme.palette.text.primary, + }, + + "& .label": { + fontSize: 11, + color: theme.palette.text.secondary, + }, + + "& .info": { + fontSize: 14, + fontWeight: 500, + color: theme.palette.text.primary, + }, + }), + orphanContainer: () => ({ + marginTop: 24, + display: "flex", + backgroundColor: colors.orange[15], + justifyContent: "space-between", + border: `1px solid ${colors.orange[11]}`, + borderRadius: 8, + padding: 12, + lineHeight: "18px", + + "& .option": { + color: colors.orange[11], + "&.Mui-checked": { + color: colors.orange[11], + }, + }, + + "& .info": { + fontSize: "14px", + color: colors.orange[10], + fontWeight: 500, + }, + }), +} satisfies Record>; + +interface WorkspaceDeleteDialogProps { + workspace: Workspace; + isOpen: boolean; + onCancel: () => void; + onConfirm: (arg: CreateWorkspaceBuildRequest["orphan"]) => void; +} +export const WorkspaceDeleteDialog = (props: WorkspaceDeleteDialogProps) => { + const { workspace, isOpen, onCancel, onConfirm } = props; + + const permissions = usePermissions(); + const hookId = useId(); + const [userConfirmationText, setUserConfirmationText] = useState(""); + const [orphanWorkspace, setOrphanWorkspace] = + useState(false); + const [isFocused, setIsFocused] = useState(false); + + const deletionConfirmed = workspace.name === userConfirmationText; + const onSubmit = (event: FormEvent) => { + event.preventDefault(); + if (deletionConfirmed) { + onConfirm(orphanWorkspace); + } + }; + + const hasError = !deletionConfirmed && userConfirmationText.length > 0; + const displayErrorMessage = hasError && !isFocused; + const inputColor = hasError ? "error" : "primary"; + + return ( + onConfirm(orphanWorkspace)} + onClose={onCancel} + disabled={!deletionConfirmed} + description={ + <> +
+
+

{workspace.name}

+

workspace

+
+
+

{dayjs(workspace.created_at).fromNow()}

+

created

+
+
+ +

Deleting this workspace is irreversible!

+

+ Type “{workspace.name}“ below to + confirm: +

+ +
+ setUserConfirmationText(event.target.value)} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + label="Workspace name" + color={inputColor} + error={displayErrorMessage} + helperText={ + displayErrorMessage && + `${userConfirmationText} does not match the name of this workspace` + } + InputProps={{ color: inputColor }} + inputProps={{ + "data-testid": "delete-dialog-name-confirmation", + }} + /> + {permissions.updateTemplates && ( +
+
+ { + setOrphanWorkspace(!orphanWorkspace); + }} + className="option" + name="orphan_resources" + checked={orphanWorkspace} + /> +
+
+

Orphan resources

+ + Skip resource cleanup. Resources such as volumes and virtual + machines will not be destroyed.  + + Learn more... + + +
+
+ )} + + + } + /> + ); +}; diff --git a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx index 02b4a0dfb267a..5265e0f970413 100644 --- a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx @@ -1,6 +1,5 @@ import { useActor } from "@xstate/react"; import { useDashboard } from "components/Dashboard/DashboardProvider"; -import dayjs from "dayjs"; import { useFeatureVisibility } from "hooks/useFeatureVisibility"; import { FC, useEffect, useState } from "react"; import { Helmet } from "react-helmet-async"; @@ -12,7 +11,6 @@ import { getMinDeadline, } from "utils/schedule"; import { StateFrom } from "xstate"; -import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog"; import { Workspace, WorkspaceErrors } from "./Workspace"; import { pageTitle } from "utils/page"; import { getFaviconByStatus, hasJobError } from "utils/workspace"; @@ -34,6 +32,7 @@ import { templateVersion, templateVersions } from "api/queries/templates"; import { Alert } from "components/Alert/Alert"; import { Stack } from "components/Stack/Stack"; import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs"; +import { WorkspaceDeleteDialog } from "./WorkspaceDeleteDialog/WorkspaceDeleteDialog"; interface WorkspaceReadyPageProps { workspaceState: StateFrom; @@ -210,16 +209,12 @@ export const WorkspaceReadyPage = ({ ) } /> - workspaceSend({ type: "CANCEL_DELETE" })} - onConfirm={() => { - workspaceSend({ type: "DELETE" }); + onConfirm={(orphan) => { + workspaceSend({ type: "DELETE", orphan }); }} /> async (send) => { + deleteWorkspace: (context, data) => async (send) => { if (context.workspace) { const deleteWorkspacePromise = await API.deleteWorkspace( context.workspace.id, context.createBuildLogLevel, + data.orphan, ); send({ type: "REFRESH_TIMELINE" }); return deleteWorkspacePromise; From 16271f1235b00f3e4cea1009e1b54731bbae88f0 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Mon, 13 Nov 2023 15:39:26 +0000 Subject: [PATCH 2/5] added stories and tests --- .../WorkspaceDeleteDialog.stories.tsx | 30 +++++++++++++ .../WorkspaceDeleteDialog.tsx | 7 ++-- .../WorkspaceDeleteDialog/index.ts | 1 + .../WorkspacePage/WorkspacePage.test.tsx | 42 ++++++++++++++++++- .../WorkspacePage/WorkspaceReadyPage.tsx | 3 +- 5 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.stories.tsx create mode 100644 site/src/pages/WorkspacePage/WorkspaceDeleteDialog/index.ts diff --git a/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.stories.tsx b/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.stories.tsx new file mode 100644 index 0000000000000..4359e82812453 --- /dev/null +++ b/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.stories.tsx @@ -0,0 +1,30 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { WorkspaceDeleteDialog } from "./WorkspaceDeleteDialog"; +import { MockWorkspace } from "testHelpers/entities"; + +const meta: Meta = { + title: "pages/WorkspacePage/WorkspaceDeleteDialog", + component: WorkspaceDeleteDialog, +}; + +export default meta; +type Story = StoryObj; + +const args = { + workspace: MockWorkspace, + canUpdateTemplate: false, + isOpen: true, + onCancel: () => {}, + onConfirm: () => {}, +}; + +export const NotTemplateAdmin: Story = { + args, +}; + +export const TemplateAdmin: Story = { + args: { + ...args, + canUpdateTemplate: true, + }, +}; diff --git a/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx b/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx index 002ea0da421b9..8241f6ffb7aa2 100644 --- a/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx @@ -1,5 +1,4 @@ import { Workspace, CreateWorkspaceBuildRequest } from "api/typesGenerated"; -import { usePermissions } from "hooks/usePermissions"; import { useId, useState, FormEvent } from "react"; import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; import { type Interpolation, type Theme } from "@emotion/react"; @@ -65,14 +64,14 @@ const styles = { interface WorkspaceDeleteDialogProps { workspace: Workspace; + canUpdateTemplate: boolean; isOpen: boolean; onCancel: () => void; onConfirm: (arg: CreateWorkspaceBuildRequest["orphan"]) => void; } export const WorkspaceDeleteDialog = (props: WorkspaceDeleteDialogProps) => { - const { workspace, isOpen, onCancel, onConfirm } = props; + const { workspace, canUpdateTemplate, isOpen, onCancel, onConfirm } = props; - const permissions = usePermissions(); const hookId = useId(); const [userConfirmationText, setUserConfirmationText] = useState(""); const [orphanWorkspace, setOrphanWorkspace] = @@ -144,7 +143,7 @@ export const WorkspaceDeleteDialog = (props: WorkspaceDeleteDialogProps) => { "data-testid": "delete-dialog-name-confirmation", }} /> - {permissions.updateTemplates && ( + {canUpdateTemplate && (
{ // Get dialog and confirm const dialog = await screen.findByTestId("dialog"); - const labelText = "Name of the workspace to delete"; + const labelText = "Workspace name"; const textField = within(dialog).getByLabelText(labelText); await user.type(textField, MockWorkspace.name); const confirmButton = within(dialog).getByRole("button", { @@ -101,6 +102,45 @@ describe("WorkspacePage", () => { expect(deleteWorkspaceMock).toBeCalled(); }); + it("orphans the workspace on delete if option is selected", async () => { + const user = userEvent.setup({ delay: 0 }); + const deleteWorkspaceMock = jest + .spyOn(api, "deleteWorkspace") + .mockResolvedValueOnce(MockWorkspaceBuildDelete); + await renderWorkspacePage(); + + // open the workspace action popover so we have access to all available ctas + const trigger = screen.getByTestId("workspace-options-button"); + await user.click(trigger); + + // Click on delete + const button = await screen.findByTestId("delete-button"); + await user.click(button); + + // Get dialog and enter confirmation text + const dialog = await screen.findByTestId("dialog"); + const labelText = "Workspace name"; + const textField = within(dialog).getByLabelText(labelText); + await user.type(textField, MockWorkspace.name); + + // check orphan option + const orphanCheckbox = screen.getByRole("checkbox"); + await user.click(orphanCheckbox); + + // confirm + const confirmButton = within(dialog).getByRole("button", { + name: "Delete", + hidden: false, + }); + await user.click(confirmButton); + // arguments are workspace.name, log level (undefined), and orphan + expect(deleteWorkspaceMock).toBeCalledWith( + MockWorkspace.id, + undefined, + true, + ); + }); + it("requests a start job when the user presses Start", async () => { server.use( rest.get( diff --git a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx index 5265e0f970413..fd1e2aafea45b 100644 --- a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx @@ -32,7 +32,7 @@ import { templateVersion, templateVersions } from "api/queries/templates"; import { Alert } from "components/Alert/Alert"; import { Stack } from "components/Stack/Stack"; import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs"; -import { WorkspaceDeleteDialog } from "./WorkspaceDeleteDialog/WorkspaceDeleteDialog"; +import { WorkspaceDeleteDialog } from "./WorkspaceDeleteDialog"; interface WorkspaceReadyPageProps { workspaceState: StateFrom; @@ -211,6 +211,7 @@ export const WorkspaceReadyPage = ({ /> workspaceSend({ type: "CANCEL_DELETE" })} onConfirm={(orphan) => { From e94d7ae2e1ddeafce7915417bb7d2d2dbbf812eb Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Mon, 13 Nov 2023 18:52:44 +0000 Subject: [PATCH 3/5] PR review --- site/src/api/api.ts | 6 ++---- .../WorkspaceDeleteDialog/WorkspaceDeleteDialog.stories.tsx | 3 ++- .../WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx | 6 +++--- site/src/xServices/workspace/workspaceXService.ts | 6 ++++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index bb13ceca47973..2379d00cf114d 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -545,13 +545,11 @@ export const stopWorkspace = ( export const deleteWorkspace = ( workspaceId: string, - logLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"], - orphan?: TypesGen.CreateWorkspaceBuildRequest["orphan"], + options?: Pick, ) => postWorkspaceBuild(workspaceId, { transition: "delete", - log_level: logLevel, - orphan, + ...options, }); export const cancelWorkspaceBuild = async ( diff --git a/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.stories.tsx b/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.stories.tsx index 4359e82812453..742d416991da7 100644 --- a/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.stories.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.stories.tsx @@ -1,3 +1,4 @@ +import { type ComponentProps } from "react"; import { Meta, StoryObj } from "@storybook/react"; import { WorkspaceDeleteDialog } from "./WorkspaceDeleteDialog"; import { MockWorkspace } from "testHelpers/entities"; @@ -10,7 +11,7 @@ const meta: Meta = { export default meta; type Story = StoryObj; -const args = { +const args: ComponentProps = { workspace: MockWorkspace, canUpdateTemplate: false, isOpen: true, diff --git a/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx b/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx index 8241f6ffb7aa2..39aaa1c76ee1e 100644 --- a/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx @@ -102,11 +102,11 @@ export const WorkspaceDeleteDialog = (props: WorkspaceDeleteDialogProps) => { description={ <>
-
+

{workspace.name}

workspace

-
+

{dayjs(workspace.created_at).fromNow()}

created

@@ -150,7 +150,7 @@ export const WorkspaceDeleteDialog = (props: WorkspaceDeleteDialogProps) => { id="orphan_resources" size="small" color="warning" - onChange={async () => { + onChange={() => { setOrphanWorkspace(!orphanWorkspace); }} className="option" diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index 622f5e370c10c..00fb5f83e852a 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -599,8 +599,10 @@ export const workspaceMachine = createMachine( if (context.workspace) { const deleteWorkspacePromise = await API.deleteWorkspace( context.workspace.id, - context.createBuildLogLevel, - data.orphan, + { + log_level: context.createBuildLogLevel, + orphan: data.orphan, + }, ); send({ type: "REFRESH_TIMELINE" }); return deleteWorkspacePromise; From 03c5082b2fa5bbf14e6b02667a154567597b6922 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Mon, 13 Nov 2023 20:24:47 +0000 Subject: [PATCH 4/5] fix flake --- .../WorkspaceDeleteDialog.tsx | 4 +-- .../WorkspacePage/WorkspacePage.test.tsx | 29 +++++++++++++++---- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx b/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx index 39aaa1c76ee1e..55bbe44554ec2 100644 --- a/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx @@ -18,7 +18,7 @@ const styles = { borderRadius: 8, padding: 12, marginBottom: 20, - lineHeight: "18px", + lineHeight: "1.3em", "& .name": { fontSize: 18, @@ -71,7 +71,6 @@ interface WorkspaceDeleteDialogProps { } export const WorkspaceDeleteDialog = (props: WorkspaceDeleteDialogProps) => { const { workspace, canUpdateTemplate, isOpen, onCancel, onConfirm } = props; - const hookId = useId(); const [userConfirmationText, setUserConfirmationText] = useState(""); const [orphanWorkspace, setOrphanWorkspace] = @@ -156,6 +155,7 @@ export const WorkspaceDeleteDialog = (props: WorkspaceDeleteDialogProps) => { className="option" name="orphan_resources" checked={orphanWorkspace} + data-testid="orphan-checkbox" />
diff --git a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx index 9d1da4f813a61..fa9096b088f66 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx @@ -104,6 +104,21 @@ describe("WorkspacePage", () => { it("orphans the workspace on delete if option is selected", async () => { const user = userEvent.setup({ delay: 0 }); + + // set permissions + server.use( + rest.post("/api/v2/authcheck", async (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + updateTemplates: true, + updateWorkspace: true, + updateTemplate: true, + }), + ); + }), + ); + const deleteWorkspaceMock = jest .spyOn(api, "deleteWorkspace") .mockResolvedValueOnce(MockWorkspaceBuildDelete); @@ -124,7 +139,10 @@ describe("WorkspacePage", () => { await user.type(textField, MockWorkspace.name); // check orphan option - const orphanCheckbox = screen.getByRole("checkbox"); + const orphanCheckbox = within( + screen.getByTestId("orphan-checkbox"), + ).getByRole("checkbox"); + await user.click(orphanCheckbox); // confirm @@ -134,11 +152,10 @@ describe("WorkspacePage", () => { }); await user.click(confirmButton); // arguments are workspace.name, log level (undefined), and orphan - expect(deleteWorkspaceMock).toBeCalledWith( - MockWorkspace.id, - undefined, - true, - ); + expect(deleteWorkspaceMock).toBeCalledWith(MockWorkspace.id, { + log_level: undefined, + orphan: true, + }); }); it("requests a start job when the user presses Start", async () => { From 176125af609c3ee12e1ab5edd12b199d6ee5af67 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Tue, 14 Nov 2023 16:23:15 +0000 Subject: [PATCH 5/5] fixed stories --- .../WorkspaceDeleteDialog.stories.tsx | 1 + .../WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx | 13 ++++++++++--- site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx | 2 ++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.stories.tsx b/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.stories.tsx index 742d416991da7..3e6c4a555ef2e 100644 --- a/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.stories.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.stories.tsx @@ -17,6 +17,7 @@ const args: ComponentProps = { isOpen: true, onCancel: () => {}, onConfirm: () => {}, + workspaceBuildDateStr: "2 days ago", }; export const NotTemplateAdmin: Story = { diff --git a/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx b/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx index 55bbe44554ec2..d2e6369b78e4e 100644 --- a/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceDeleteDialog/WorkspaceDeleteDialog.tsx @@ -3,7 +3,6 @@ import { useId, useState, FormEvent } from "react"; import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; import { type Interpolation, type Theme } from "@emotion/react"; import { colors } from "theme/colors"; -import dayjs from "dayjs"; import TextField from "@mui/material/TextField"; import { docs } from "utils/docs"; import Link from "@mui/material/Link"; @@ -68,9 +67,17 @@ interface WorkspaceDeleteDialogProps { isOpen: boolean; onCancel: () => void; onConfirm: (arg: CreateWorkspaceBuildRequest["orphan"]) => void; + workspaceBuildDateStr: string; } export const WorkspaceDeleteDialog = (props: WorkspaceDeleteDialogProps) => { - const { workspace, canUpdateTemplate, isOpen, onCancel, onConfirm } = props; + const { + workspace, + canUpdateTemplate, + isOpen, + onCancel, + onConfirm, + workspaceBuildDateStr, + } = props; const hookId = useId(); const [userConfirmationText, setUserConfirmationText] = useState(""); const [orphanWorkspace, setOrphanWorkspace] = @@ -106,7 +113,7 @@ export const WorkspaceDeleteDialog = (props: WorkspaceDeleteDialogProps) => {

workspace

-

{dayjs(workspace.created_at).fromNow()}

+

{workspaceBuildDateStr}

created

diff --git a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx index 780082e2f222c..11185f5a9012f 100644 --- a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx @@ -35,6 +35,7 @@ import { decreaseDeadline, increaseDeadline } from "api/queries/workspaces"; import { getErrorMessage } from "api/errors"; import { displaySuccess, displayError } from "components/GlobalSnackbar/utils"; import { WorkspaceDeleteDialog } from "./WorkspaceDeleteDialog"; +import dayjs from "dayjs"; interface WorkspaceReadyPageProps { workspaceState: StateFrom; @@ -224,6 +225,7 @@ export const WorkspaceReadyPage = ({ onConfirm={(orphan) => { workspaceSend({ type: "DELETE", orphan }); }} + workspaceBuildDateStr={dayjs(workspace.created_at).fromNow()} />