diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index ffadc3fa342f2..d56dc51f66622 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -152,7 +152,7 @@ export const createWorkspace = async ( const user = currentUser(page); await expectUrl(page).toHavePathName(`/@${user.username}/${name}`); - await page.waitForSelector("[data-testid='build-status'] >> text=Running", { + await page.waitForSelector("text=Workspace status: Running", { state: "visible", }); return name; @@ -364,7 +364,7 @@ export const stopWorkspace = async (page: Page, workspaceName: string) => { await page.getByTestId("workspace-stop-button").click(); - await page.waitForSelector("*[data-testid='build-status'] >> text=Stopped", { + await page.waitForSelector("text=Workspace status: Stopped", { state: "visible", }); }; @@ -389,7 +389,7 @@ export const buildWorkspaceWithParameters = async ( await page.getByTestId("confirm-button").click(); } - await page.waitForSelector("*[data-testid='build-status'] >> text=Running", { + await page.waitForSelector("text=Workspace status: Running", { state: "visible", }); }; @@ -412,11 +412,12 @@ export const startAgent = async ( export const downloadCoderVersion = async ( version: string, ): Promise => { - if (version.startsWith("v")) { - version = version.slice(1); + let versionNumber = version; + if (versionNumber.startsWith("v")) { + versionNumber = versionNumber.slice(1); } - const binaryName = `coder-e2e-${version}`; + const binaryName = `coder-e2e-${versionNumber}`; const tempDir = "/tmp/coder-e2e-cache"; // The install script adds `./bin` automatically to the path :shrug: const binaryPath = path.join(tempDir, "bin", binaryName); @@ -438,7 +439,7 @@ export const downloadCoderVersion = async ( path.join(__dirname, "../../install.sh"), [ "--version", - version, + versionNumber, "--method", "standalone", "--prefix", @@ -551,11 +552,8 @@ const emptyPlan = new TextEncoder().encode("{}"); * converts it into an uploadable tar file. */ const createTemplateVersionTar = async ( - responses?: EchoProvisionerResponses, + responses: EchoProvisionerResponses = {}, ): Promise => { - if (!responses) { - responses = {}; - } if (!responses.parse) { responses.parse = [ { @@ -1012,7 +1010,7 @@ export const updateWorkspace = async ( await fillParameters(page, richParameters, buildParameters); await page.getByRole("button", { name: /update parameters/i }).click(); - await page.waitForSelector("*[data-testid='build-status'] >> text=Running", { + await page.waitForSelector("text=Workspace status: Running", { state: "visible", }); }; @@ -1031,7 +1029,7 @@ export const updateWorkspaceParameters = async ( await fillParameters(page, richParameters, buildParameters); await page.getByRole("button", { name: /submit and restart/i }).click(); - await page.waitForSelector("*[data-testid='build-status'] >> text=Running", { + await page.waitForSelector("text=Workspace status: Running", { state: "visible", }); }; diff --git a/site/src/modules/workspaces/WorkspaceStatusBadge/WorkspaceStatusBadge.stories.tsx b/site/src/modules/workspaces/WorkspaceStatusBadge/WorkspaceStatusBadge.stories.tsx deleted file mode 100644 index 352153cda65db..0000000000000 --- a/site/src/modules/workspaces/WorkspaceStatusBadge/WorkspaceStatusBadge.stories.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { - MockBuildInfo, - MockCanceledWorkspace, - MockCancelingWorkspace, - MockDeletedWorkspace, - MockDeletingWorkspace, - MockFailedWorkspace, - MockPendingWorkspace, - MockStartingWorkspace, - MockStoppedWorkspace, - MockStoppingWorkspace, - MockWorkspace, -} from "testHelpers/entities"; -import { withDashboardProvider } from "testHelpers/storybook"; -import { WorkspaceStatusBadge } from "./WorkspaceStatusBadge"; - -const meta: Meta = { - title: "modules/workspaces/WorkspaceStatusBadge", - component: WorkspaceStatusBadge, - parameters: { - queries: [ - { - key: ["buildInfo"], - data: MockBuildInfo, - }, - ], - }, - decorators: [withDashboardProvider], -}; - -export default meta; -type Story = StoryObj; - -export const Running: Story = { - args: { - workspace: MockWorkspace, - }, -}; - -export const Starting: Story = { - args: { - workspace: MockStartingWorkspace, - }, -}; - -export const Stopped: Story = { - args: { - workspace: MockStoppedWorkspace, - }, -}; - -export const Stopping: Story = { - args: { - workspace: MockStoppingWorkspace, - }, -}; - -export const Deleting: Story = { - args: { - workspace: MockDeletingWorkspace, - }, -}; - -export const Deleted: Story = { - args: { - workspace: MockDeletedWorkspace, - }, -}; - -export const Canceling: Story = { - args: { - workspace: MockCancelingWorkspace, - }, -}; - -export const Canceled: Story = { - args: { - workspace: MockCanceledWorkspace, - }, -}; - -export const Failed: Story = { - args: { - workspace: MockFailedWorkspace, - }, -}; - -export const Pending: Story = { - args: { - workspace: MockPendingWorkspace, - }, -}; diff --git a/site/src/modules/workspaces/WorkspaceStatusBadge/WorkspaceStatusBadge.tsx b/site/src/modules/workspaces/WorkspaceStatusBadge/WorkspaceStatusBadge.tsx deleted file mode 100644 index 1bde6f9181ba6..0000000000000 --- a/site/src/modules/workspaces/WorkspaceStatusBadge/WorkspaceStatusBadge.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import Tooltip, { - type TooltipProps, - tooltipClasses, -} from "@mui/material/Tooltip"; -import type { Workspace } from "api/typesGenerated"; -import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"; -import { Pill } from "components/Pill/Pill"; -import { useClassName } from "hooks/useClassName"; -import { CircleAlertIcon } from "lucide-react"; -import type { FC, ReactNode } from "react"; -import { getDisplayWorkspaceStatus } from "utils/workspace"; - -export type WorkspaceStatusBadgeProps = { - workspace: Workspace; - children?: ReactNode; - className?: string; -}; - -export const WorkspaceStatusBadge: FC = ({ - workspace, - className, -}) => { - const { text, icon, type } = getDisplayWorkspaceStatus( - workspace.latest_build.status, - workspace.latest_build.job, - ); - - return ( - - - - - - - - {text} - - - - ); -}; - -const FailureTooltip: FC = ({ children, ...tooltipProps }) => { - const popper = useClassName( - (css, theme) => css` - & .${tooltipClasses.tooltip} { - background-color: ${theme.palette.background.paper}; - border: 1px solid ${theme.palette.divider}; - font-size: 12px; - padding: 8px 10px; - } - `, - [], - ); - - return ( - - {children} - - ); -}; diff --git a/site/src/modules/workspaces/WorkspaceStatusIndicator/WorkspaceStatusIndicator.stories.tsx b/site/src/modules/workspaces/WorkspaceStatusIndicator/WorkspaceStatusIndicator.stories.tsx new file mode 100644 index 0000000000000..75205db8ff698 --- /dev/null +++ b/site/src/modules/workspaces/WorkspaceStatusIndicator/WorkspaceStatusIndicator.stories.tsx @@ -0,0 +1,82 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import type { Workspace, WorkspaceStatus } from "api/typesGenerated"; +import { MockWorkspace } from "testHelpers/entities"; +import { WorkspaceStatusIndicator } from "./WorkspaceStatusIndicator"; + +const meta: Meta = { + title: "modules/workspaces/WorkspaceStatusIndicator", + component: WorkspaceStatusIndicator, +}; + +export default meta; +type Story = StoryObj; + +const createWorkspaceWithStatus = (status: WorkspaceStatus): Workspace => { + return { + ...MockWorkspace, + latest_build: { + ...MockWorkspace.latest_build, + status, + }, + } as Workspace; +}; + +export const Running: Story = { + args: { + workspace: createWorkspaceWithStatus("running"), + }, +}; + +export const Stopped: Story = { + args: { + workspace: createWorkspaceWithStatus("stopped"), + }, +}; + +export const Starting: Story = { + args: { + workspace: createWorkspaceWithStatus("starting"), + }, +}; + +export const Stopping: Story = { + args: { + workspace: createWorkspaceWithStatus("stopping"), + }, +}; + +export const Failed: Story = { + args: { + workspace: createWorkspaceWithStatus("failed"), + }, +}; + +export const Canceling: Story = { + args: { + workspace: createWorkspaceWithStatus("canceling"), + }, +}; + +export const Canceled: Story = { + args: { + workspace: createWorkspaceWithStatus("canceled"), + }, +}; + +export const Deleting: Story = { + args: { + workspace: createWorkspaceWithStatus("deleting"), + }, +}; + +export const Deleted: Story = { + args: { + workspace: createWorkspaceWithStatus("deleted"), + }, +}; + +export const Pending: Story = { + args: { + workspace: createWorkspaceWithStatus("pending"), + }, +}; diff --git a/site/src/modules/workspaces/WorkspaceStatusIndicator/WorkspaceStatusIndicator.tsx b/site/src/modules/workspaces/WorkspaceStatusIndicator/WorkspaceStatusIndicator.tsx new file mode 100644 index 0000000000000..bd928844cdc0f --- /dev/null +++ b/site/src/modules/workspaces/WorkspaceStatusIndicator/WorkspaceStatusIndicator.tsx @@ -0,0 +1,49 @@ +import type { Workspace } from "api/typesGenerated"; +import { + StatusIndicator, + StatusIndicatorDot, + type StatusIndicatorProps, +} from "components/StatusIndicator/StatusIndicator"; +import type { FC } from "react"; +import type React from "react"; +import { + type DisplayWorkspaceStatusType, + getDisplayWorkspaceStatus, +} from "utils/workspace"; + +const variantByStatusType: Record< + DisplayWorkspaceStatusType, + StatusIndicatorProps["variant"] +> = { + active: "pending", + inactive: "inactive", + success: "success", + error: "failed", + danger: "warning", + warning: "warning", +}; + +type WorkspaceStatusIndicatorProps = { + workspace: Workspace; + children?: React.ReactNode; +}; + +export const WorkspaceStatusIndicator: FC = ({ + workspace, + children, +}) => { + const { text, type } = getDisplayWorkspaceStatus( + workspace.latest_build.status, + workspace.latest_build.job, + ); + + return ( + + + + Workspace status: {text} + + {children} + + ); +}; diff --git a/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx b/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx index 32908156c5b5c..8f75e615895f6 100644 --- a/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx @@ -20,7 +20,7 @@ import { Popover, PopoverTrigger } from "components/deprecated/Popover/Popover"; import { TrashIcon } from "lucide-react"; import { useDashboard } from "modules/dashboard/useDashboard"; import { linkToTemplate, useLinks } from "modules/navigation"; -import { WorkspaceStatusBadge } from "modules/workspaces/WorkspaceStatusBadge/WorkspaceStatusBadge"; +import { WorkspaceStatusIndicator } from "modules/workspaces/WorkspaceStatusIndicator/WorkspaceStatusIndicator"; import type { FC } from "react"; import { useQuery } from "react-query"; import { Link as RouterLink } from "react-router-dom"; @@ -201,18 +201,13 @@ export const WorkspaceTopbar: FC = ({ {!isImmutable && ( -
+
+ = ({ onUpdateWorkspace={handleUpdate} onActivateWorkspace={handleDormantActivate} /> - + + + = { - active: "pending", - inactive: "inactive", - success: "success", - error: "failed", - danger: "warning", - warning: "warning", -}; - const WorkspaceStatusCell: FC = ({ workspace }) => { - const { text, type } = getDisplayWorkspaceStatus( - workspace.latest_build.status, - workspace.latest_build.job, - ); - return (
- - - {text} + {workspace.latest_build.status === "running" && !workspace.health.healthy && ( = ({ workspace }) => { {workspace.dormant_at && ( )} - + {lastUsedMessage(workspace.last_used_at)}