From 8212659e7d978914586cef8315eab43275dc3a25 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Wed, 16 Apr 2025 14:50:22 +0000 Subject: [PATCH 1/5] refactor: redesign workspace status on workspces table --- site/src/components/Badge/Badge.tsx | 5 +- site/src/index.css | 6 +- .../WorkspaceDormantBadge.tsx | 10 +-- .../pages/WorkspacesPage/WorkspacesTable.tsx | 90 +++++++++++++------ site/src/utils/workspace.tsx | 25 ++++++ site/tailwind.config.js | 2 + 6 files changed, 103 insertions(+), 35 deletions(-) diff --git a/site/src/components/Badge/Badge.tsx b/site/src/components/Badge/Badge.tsx index 7ee7cc4f94fe0..e405966c8c235 100644 --- a/site/src/components/Badge/Badge.tsx +++ b/site/src/components/Badge/Badge.tsx @@ -17,9 +17,12 @@ export const badgeVariants = cva( default: "border-transparent bg-surface-secondary text-content-secondary shadow", warning: - "border-transparent bg-surface-orange text-content-warning shadow", + "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", }, size: { + xs: "text-2xs font-regular h-5 [&_svg]:hidden rounded px-1.5", sm: "text-2xs font-regular h-5.5 [&_svg]:size-icon-xs", md: "text-xs font-medium [&_svg]:size-icon-sm", }, diff --git a/site/src/index.css b/site/src/index.css index 6037a0d2fbfc4..7110583bc9c6a 100644 --- a/site/src/index.css +++ b/site/src/index.css @@ -28,10 +28,12 @@ --surface-grey: 240 5% 96%; --surface-orange: 34 100% 92%; --surface-sky: 201 94% 86%; + --surface-red: 0 93% 94%; --border-default: 240 6% 90%; --border-success: 142 76% 36%; --border-destructive: 0 84% 60%; - --border-hover: 240, 5%, 34%; + --border-warning: 27 96% 61%; + --border-hover: 240 5% 34%; --overlay-default: 240 5% 84% / 80%; --radius: 0.5rem; --highlight-purple: 262 83% 58%; @@ -65,9 +67,11 @@ --surface-grey: 240 6% 10%; --surface-orange: 13 81% 15%; --surface-sky: 204 80% 16%; + --surface-red: 0 75% 15%; --border-default: 240 4% 16%; --border-success: 142 76% 36%; --border-destructive: 0 91% 71%; + --border-warning: 31 97% 72%; --border-hover: 240, 5%, 34%; --overlay-default: 240 10% 4% / 80%; --highlight-purple: 252 95% 85%; diff --git a/site/src/modules/workspaces/WorkspaceDormantBadge/WorkspaceDormantBadge.tsx b/site/src/modules/workspaces/WorkspaceDormantBadge/WorkspaceDormantBadge.tsx index 9c87cd4eae01c..b9098cd307e6b 100644 --- a/site/src/modules/workspaces/WorkspaceDormantBadge/WorkspaceDormantBadge.tsx +++ b/site/src/modules/workspaces/WorkspaceDormantBadge/WorkspaceDormantBadge.tsx @@ -1,7 +1,7 @@ -import AutoDeleteIcon from "@mui/icons-material/AutoDelete"; import RecyclingIcon from "@mui/icons-material/Recycling"; import Tooltip from "@mui/material/Tooltip"; import type { Workspace } from "api/typesGenerated"; +import { Badge } from "components/Badge/Badge"; import { Pill } from "components/Pill/Pill"; import { formatDistanceToNow } from "date-fns"; import type { FC } from "react"; @@ -35,9 +35,9 @@ export const WorkspaceDormantBadge: FC = ({ } > - } type="error"> + Deletion Pending - + ) : ( = ({ } > - } type="warning"> + Dormant - + ); }; diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index 9fe72c23910e5..927621204553a 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -35,8 +35,18 @@ import { LastUsed } from "pages/WorkspacesPage/LastUsed"; import { type FC, type ReactNode, useMemo } from "react"; import { useNavigate } from "react-router-dom"; import { cn } from "utils/cn"; -import { getDisplayWorkspaceTemplateName } from "utils/workspace"; +import { + getDisplayWorkspaceStatus, + getDisplayWorkspaceTemplateName, + lastUsedMessage, + type GetWorkspaceDisplayStatusType, +} from "utils/workspace"; import { WorkspacesEmpty } from "./WorkspacesEmpty"; +import { + StatusIndicator, + StatusIndicatorDot, + type StatusIndicatorProps, +} from "components/StatusIndicator/StatusIndicator"; export interface WorkspacesTableProps { workspaces?: readonly Workspace[]; @@ -125,8 +135,7 @@ export const WorkspacesTable: FC = ({ {hasAppStatus && Activity} Template - Last used - Status + Status @@ -248,26 +257,7 @@ export const WorkspacesTable: FC = ({ /> - - - - - -
- - {workspace.latest_build.status === "running" && - !workspace.health.healthy && ( - - )} - {workspace.dormant_at && ( - - )} -
-
+
@@ -345,14 +335,11 @@ const TableLoader: FC = ({ canCheckWorkspaces }) => { - - - - + - + @@ -362,3 +349,50 @@ const TableLoader: FC = ({ canCheckWorkspaces }) => { const cantBeChecked = (workspace: Workspace) => { return ["deleting", "pending"].includes(workspace.latest_build.status); }; + +type WorkspaceStatusCellProps = { + workspace: Workspace; +}; + +const variantByStatusType: Record< + GetWorkspaceDisplayStatusType, + StatusIndicatorProps["variant"] +> = { + active: "pending", + inactive: "inactive", + success: "success", + error: "failed", + danger: "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.dormant_at && ( + + )} + + + {lastUsedMessage(workspace.last_used_at)} + +
+
+ ); +}; diff --git a/site/src/utils/workspace.tsx b/site/src/utils/workspace.tsx index 963adf58a7270..8fc35fac2ebf2 100644 --- a/site/src/utils/workspace.tsx +++ b/site/src/utils/workspace.tsx @@ -176,6 +176,7 @@ export const getDisplayWorkspaceStatus = ( case undefined: return { text: "Loading", + type: "active", icon: , } as const; case "running": @@ -241,6 +242,10 @@ export const getDisplayWorkspaceStatus = ( } }; +export type GetWorkspaceDisplayStatusType = ReturnType< + typeof getDisplayWorkspaceStatus +>["type"]; + export const hasJobError = (workspace: TypesGen.Workspace) => { return workspace.latest_build.job.error !== undefined; }; @@ -307,3 +312,23 @@ const FALLBACK_ICON = "/icon/widgets.svg"; export const getResourceIconPath = (resourceType: string): string => { return BUILT_IN_ICON_PATHS[resourceType] ?? FALLBACK_ICON; }; + +export const lastUsedMessage = (lastUsedAt: string | Date) => { + const t = dayjs(lastUsedAt); + const now = dayjs(); + let message = t.fromNow(); + + if (t.isAfter(now.subtract(1, "hour"))) { + message = "Now"; + } else if (t.isAfter(now.subtract(3, "day"))) { + message = t.fromNow(); + } else if (t.isAfter(now.subtract(1, "month"))) { + message = t.fromNow(); + } else if (t.isAfter(now.subtract(100, "year"))) { + message = t.fromNow(); + } else { + message = "Never"; + } + + return message; +}; diff --git a/site/tailwind.config.js b/site/tailwind.config.js index 971a729332aff..04d886127fe1f 100644 --- a/site/tailwind.config.js +++ b/site/tailwind.config.js @@ -49,10 +49,12 @@ module.exports = { grey: "hsl(var(--surface-grey))", orange: "hsl(var(--surface-orange))", sky: "hsl(var(--surface-sky))", + red: "hsl(var(--surface-red))", }, border: { DEFAULT: "hsl(var(--border-default))", destructive: "hsl(var(--border-destructive))", + warning: "hsl(var(--border-warning))", success: "hsl(var(--border-success))", hover: "hsl(var(--border-hover))", }, From 43685a60e5edc5208bbb3d3fbccd7ba3fe59563c Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Wed, 16 Apr 2025 14:55:46 +0000 Subject: [PATCH 2/5] FMT --- site/src/pages/WorkspacesPage/WorkspacesTable.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index 927621204553a..d1cfbce604e4d 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -13,6 +13,11 @@ import { AvatarData } from "components/Avatar/AvatarData"; import { AvatarDataSkeleton } from "components/Avatar/AvatarDataSkeleton"; import { InfoTooltip } from "components/InfoTooltip/InfoTooltip"; import { Stack } from "components/Stack/Stack"; +import { + StatusIndicator, + StatusIndicatorDot, + type StatusIndicatorProps, +} from "components/StatusIndicator/StatusIndicator"; import { Table, TableBody, @@ -36,17 +41,12 @@ import { type FC, type ReactNode, useMemo } from "react"; import { useNavigate } from "react-router-dom"; import { cn } from "utils/cn"; import { + type GetWorkspaceDisplayStatusType, getDisplayWorkspaceStatus, getDisplayWorkspaceTemplateName, lastUsedMessage, - type GetWorkspaceDisplayStatusType, } from "utils/workspace"; import { WorkspacesEmpty } from "./WorkspacesEmpty"; -import { - StatusIndicator, - StatusIndicatorDot, - type StatusIndicatorProps, -} from "components/StatusIndicator/StatusIndicator"; export interface WorkspacesTableProps { workspaces?: readonly Workspace[]; From c693ebf5a5ca8270cb4a81c09e35ac30a6a8523f Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Wed, 16 Apr 2025 16:10:03 +0000 Subject: [PATCH 3/5] Fix storybook --- .../WorkspaceDormantBadge/WorkspaceDormantBadge.tsx | 2 -- site/src/pages/WorkspacesPage/WorkspacesTable.tsx | 6 ++++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/site/src/modules/workspaces/WorkspaceDormantBadge/WorkspaceDormantBadge.tsx b/site/src/modules/workspaces/WorkspaceDormantBadge/WorkspaceDormantBadge.tsx index b9098cd307e6b..f3c9c80d085fd 100644 --- a/site/src/modules/workspaces/WorkspaceDormantBadge/WorkspaceDormantBadge.tsx +++ b/site/src/modules/workspaces/WorkspaceDormantBadge/WorkspaceDormantBadge.tsx @@ -1,8 +1,6 @@ -import RecyclingIcon from "@mui/icons-material/Recycling"; import Tooltip from "@mui/material/Tooltip"; import type { Workspace } from "api/typesGenerated"; import { Badge } from "components/Badge/Badge"; -import { Pill } from "components/Pill/Pill"; import { formatDistanceToNow } from "date-fns"; import type { FC } from "react"; diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index d1cfbce604e4d..4cb0833ad9f11 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -30,13 +30,13 @@ import { TableLoaderSkeleton, TableRowSkeleton, } from "components/TableLoader/TableLoader"; +import dayjs from "dayjs"; +import relativeTime from "dayjs/plugin/relativeTime"; import { useClickableTableRow } from "hooks/useClickableTableRow"; import { useDashboard } from "modules/dashboard/useDashboard"; import { WorkspaceAppStatus } from "modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus"; import { WorkspaceDormantBadge } from "modules/workspaces/WorkspaceDormantBadge/WorkspaceDormantBadge"; import { WorkspaceOutdatedTooltip } from "modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip"; -import { WorkspaceStatusBadge } from "modules/workspaces/WorkspaceStatusBadge/WorkspaceStatusBadge"; -import { LastUsed } from "pages/WorkspacesPage/LastUsed"; import { type FC, type ReactNode, useMemo } from "react"; import { useNavigate } from "react-router-dom"; import { cn } from "utils/cn"; @@ -48,6 +48,8 @@ import { } from "utils/workspace"; import { WorkspacesEmpty } from "./WorkspacesEmpty"; +dayjs.extend(relativeTime); + export interface WorkspacesTableProps { workspaces?: readonly Workspace[]; checkedWorkspaces: readonly Workspace[]; From f1f43d03a86c90eeeb28a0c17ab5a02c2505bfe9 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 17 Apr 2025 13:20:48 +0000 Subject: [PATCH 4/5] Apply PR comments --- .../pages/WorkspacesPage/WorkspacesTable.tsx | 7 +++--- site/src/utils/workspace.tsx | 22 ++++++++++++++----- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index 4cb0833ad9f11..a9d585fccf58c 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -41,7 +41,7 @@ import { type FC, type ReactNode, useMemo } from "react"; import { useNavigate } from "react-router-dom"; import { cn } from "utils/cn"; import { - type GetWorkspaceDisplayStatusType, + type DisplayWorkspaceStatusType, getDisplayWorkspaceStatus, getDisplayWorkspaceTemplateName, lastUsedMessage, @@ -338,7 +338,7 @@ const TableLoader: FC = ({ canCheckWorkspaces }) => {
- + @@ -357,7 +357,7 @@ type WorkspaceStatusCellProps = { }; const variantByStatusType: Record< - GetWorkspaceDisplayStatusType, + DisplayWorkspaceStatusType, StatusIndicatorProps["variant"] > = { active: "pending", @@ -365,6 +365,7 @@ const variantByStatusType: Record< success: "success", error: "failed", danger: "warning", + warning: "warning", }; const WorkspaceStatusCell: FC = ({ workspace }) => { diff --git a/site/src/utils/workspace.tsx b/site/src/utils/workspace.tsx index 8fc35fac2ebf2..32fd6ce153d0e 100644 --- a/site/src/utils/workspace.tsx +++ b/site/src/utils/workspace.tsx @@ -168,10 +168,24 @@ export const getDisplayWorkspaceTemplateName = ( : workspace.template_name; }; +export type DisplayWorkspaceStatusType = + | "success" + | "active" + | "inactive" + | "error" + | "warning" + | "danger"; + +type DisplayWorkspaceStatus = { + text: string; + type: DisplayWorkspaceStatusType; + icon: React.ReactNode; +}; + export const getDisplayWorkspaceStatus = ( workspaceStatus: TypesGen.WorkspaceStatus, provisionerJob?: TypesGen.ProvisionerJob, -) => { +): DisplayWorkspaceStatus => { switch (workspaceStatus) { case undefined: return { @@ -242,10 +256,6 @@ export const getDisplayWorkspaceStatus = ( } }; -export type GetWorkspaceDisplayStatusType = ReturnType< - typeof getDisplayWorkspaceStatus ->["type"]; - export const hasJobError = (workspace: TypesGen.Workspace) => { return workspace.latest_build.job.error !== undefined; }; @@ -313,7 +323,7 @@ export const getResourceIconPath = (resourceType: string): string => { return BUILT_IN_ICON_PATHS[resourceType] ?? FALLBACK_ICON; }; -export const lastUsedMessage = (lastUsedAt: string | Date) => { +export const lastUsedMessage = (lastUsedAt: string | Date): string => { const t = dayjs(lastUsedAt); const now = dayjs(); let message = t.fromNow(); From bbe9d8f2e48a741247a1fb606976bb7b66b86ede Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 17 Apr 2025 13:29:57 +0000 Subject: [PATCH 5/5] Fix merging conflict --- site/tailwind.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/site/tailwind.config.js b/site/tailwind.config.js index 029911d4f3030..142a4711b56f3 100644 --- a/site/tailwind.config.js +++ b/site/tailwind.config.js @@ -55,7 +55,6 @@ module.exports = { DEFAULT: "hsl(var(--border-default))", warning: "hsl(var(--border-warning))", destructive: "hsl(var(--border-destructive))", - warning: "hsl(var(--border-warning))", success: "hsl(var(--border-success))", hover: "hsl(var(--border-hover))", },