From 8b49100b855cc5350cd36f9cfaeb6cb765058ee1 Mon Sep 17 00:00:00 2001
From: BrunoQuaresma
Date: Tue, 19 Dec 2023 17:24:47 +0000
Subject: [PATCH 1/2] refactor(site): move workspace schedule controls to its
own component
---
.../pages/WorkspacePage/Workspace.stories.tsx | 10 -
site/src/pages/WorkspacePage/Workspace.tsx | 11 -
.../WorkspacePage/WorkspaceReadyPage.tsx | 40 +-
.../WorkspaceScheduleControls.tsx | 387 ++++++++++++++++++
.../pages/WorkspacePage/WorkspaceStats.tsx | 354 +---------------
5 files changed, 401 insertions(+), 401 deletions(-)
create mode 100644 site/src/pages/WorkspacePage/WorkspaceScheduleControls.tsx
diff --git a/site/src/pages/WorkspacePage/Workspace.stories.tsx b/site/src/pages/WorkspacePage/Workspace.stories.tsx
index f3f27e840c9f9..5a86338f6b1f4 100644
--- a/site/src/pages/WorkspacePage/Workspace.stories.tsx
+++ b/site/src/pages/WorkspacePage/Workspace.stories.tsx
@@ -66,16 +66,6 @@ type Story = StoryObj;
export const Running: Story = {
args: {
- scheduleProps: {
- onDeadlineMinus: () => {
- // do nothing, this is just for storybook
- },
- onDeadlinePlus: () => {
- // do nothing, this is just for storybook
- },
- maxDeadlineDecrease: 0,
- maxDeadlineIncrease: 24,
- },
workspace: Mocks.MockWorkspace,
handleStart: action("start"),
handleStop: action("stop"),
diff --git a/site/src/pages/WorkspacePage/Workspace.tsx b/site/src/pages/WorkspacePage/Workspace.tsx
index 08b278bec51a3..22e24c73c9fce 100644
--- a/site/src/pages/WorkspacePage/Workspace.tsx
+++ b/site/src/pages/WorkspacePage/Workspace.tsx
@@ -37,12 +37,6 @@ export type WorkspaceError =
export type WorkspaceErrors = Partial>;
export interface WorkspaceProps {
- scheduleProps: {
- onDeadlinePlus: (hours: number) => void;
- onDeadlineMinus: (hours: number) => void;
- maxDeadlineIncrease: number;
- maxDeadlineDecrease: number;
- };
handleStart: (buildParameters?: TypesGen.WorkspaceBuildParameter[]) => void;
handleStop: () => void;
handleRestart: (buildParameters?: TypesGen.WorkspaceBuildParameter[]) => void;
@@ -81,7 +75,6 @@ export interface WorkspaceProps {
* Workspace is the top-level component for viewing an individual workspace
*/
export const Workspace: FC> = ({
- scheduleProps,
handleStart,
handleStop,
handleRestart,
@@ -186,10 +179,6 @@ export const Workspace: FC> = ({
workspace={workspace}
handleUpdate={handleUpdate}
canUpdateWorkspace={canUpdateWorkspace}
- maxDeadlineDecrease={scheduleProps.maxDeadlineDecrease}
- maxDeadlineIncrease={scheduleProps.maxDeadlineIncrease}
- onDeadlineMinus={scheduleProps.onDeadlineMinus}
- onDeadlinePlus={scheduleProps.onDeadlinePlus}
/>
{canUpdateWorkspace && (
diff --git a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx
index 67f40ef0f7c24..e917deafa2b74 100644
--- a/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx
+++ b/site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx
@@ -3,12 +3,6 @@ import { useFeatureVisibility } from "hooks/useFeatureVisibility";
import { FC, useEffect, useState } from "react";
import { Helmet } from "react-helmet-async";
import { useNavigate } from "react-router-dom";
-import {
- getDeadline,
- getMaxDeadline,
- getMaxDeadlineChange,
- getMinDeadline,
-} from "utils/schedule";
import { Workspace } from "./Workspace";
import { pageTitle } from "utils/page";
import { hasJobError } from "utils/workspace";
@@ -29,16 +23,14 @@ import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs";
import {
activate,
changeVersion,
- decreaseDeadline,
deleteWorkspace,
- increaseDeadline,
updateWorkspace,
stopWorkspace,
startWorkspace,
cancelBuild,
} from "api/queries/workspaces";
import { getErrorMessage } from "api/errors";
-import { displaySuccess, displayError } from "components/GlobalSnackbar/utils";
+import { displayError } from "components/GlobalSnackbar/utils";
import { deploymentConfig, deploymentSSHConfig } from "api/queries/deployment";
import { WorkspacePermissions } from "./permissions";
import { workspaceResolveAutostart } from "api/queries/workspaceQuota";
@@ -101,27 +93,6 @@ export const WorkspaceReadyPage = ({
mutationFn: restartWorkspace,
});
- // Schedule controls
- const deadline = getDeadline(workspace);
- const onDeadlineChangeSuccess = () => {
- displaySuccess("Updated workspace shutdown time.");
- };
- const onDeadlineChangeFails = (error: unknown) => {
- displayError(
- getErrorMessage(error, "Failed to update workspace shutdown time."),
- );
- };
- const decreaseMutation = useMutation({
- ...decreaseDeadline(workspace),
- onSuccess: onDeadlineChangeSuccess,
- onError: onDeadlineChangeFails,
- });
- const increaseMutation = useMutation({
- ...increaseDeadline(workspace),
- onSuccess: onDeadlineChangeSuccess,
- onError: onDeadlineChangeFails,
- });
-
// Auto start
const canAutostartResponse = useQuery(
workspaceResolveAutostart(workspace.id),
@@ -227,15 +198,6 @@ export const WorkspaceReadyPage = ({
= ({
+ workspace,
+ canUpdateSchedule,
+}) => {
+ const deadline = getDeadline(workspace);
+ const maxDeadlineDecrease = getMaxDeadlineChange(deadline, getMinDeadline());
+ const maxDeadlineIncrease = getMaxDeadlineChange(
+ getMaxDeadline(workspace),
+ deadline,
+ );
+ const deadlinePlusEnabled = maxDeadlineIncrease >= 1;
+ const deadlineMinusEnabled = maxDeadlineDecrease >= 1;
+
+ const onDeadlineChangeSuccess = () => {
+ displaySuccess("Updated workspace shutdown time.");
+ };
+ const onDeadlineChangeFails = (error: unknown) => {
+ displayError(
+ getErrorMessage(error, "Failed to update workspace shutdown time."),
+ );
+ };
+ const decreaseMutation = useMutation({
+ ...decreaseDeadline(workspace),
+ onSuccess: onDeadlineChangeSuccess,
+ onError: onDeadlineChangeFails,
+ });
+ const increaseMutation = useMutation({
+ ...increaseDeadline(workspace),
+ onSuccess: onDeadlineChangeSuccess,
+ onError: onDeadlineChangeFails,
+ });
+
+ return (
+
+ {isWorkspaceOn(workspace) ? (
+
+ ) : (
+
+ {autostartDisplay(workspace.autostart_schedule)}
+
+ )}
+
+ {canUpdateSchedule && canEditDeadline(workspace) && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+interface AddTimeContentProps {
+ maxDeadlineIncrease: number;
+ onDeadlinePlus: (value: number) => void;
+}
+
+const AddTimeContent: FC = ({
+ maxDeadlineIncrease,
+ onDeadlinePlus,
+}) => {
+ const popover = usePopover();
+
+ return (
+ <>
+ Add hours to deadline
+
+ Delay the shutdown of this workspace for a few more hours. This is only
+ applied once.
+
+
+ >
+ );
+};
+
+interface DecreaseTimeContentProps {
+ maxDeadlineDecrease: number;
+ onDeadlineMinus: (hours: number) => void;
+}
+
+export const DecreaseTimeContent: FC = ({
+ maxDeadlineDecrease,
+ onDeadlineMinus,
+}) => {
+ const popover = usePopover();
+
+ return (
+ <>
+ Subtract hours to deadline
+
+ Anticipate the shutdown of this workspace for a few more hours. This is
+ only applied once.
+
+
+ >
+ );
+};
+
+interface AutoStopDisplayProps {
+ workspace: Workspace;
+}
+
+const AutoStopDisplay: FC = ({ workspace }) => {
+ const display = autostopDisplay(workspace);
+
+ if (display.tooltip) {
+ return (
+
+ ({
+ color: isShutdownSoon(workspace)
+ ? `${theme.palette.warning.light} !important`
+ : undefined,
+ })}
+ >
+ {display.message}
+
+
+ );
+ }
+
+ return {display.message};
+};
+
+const ScheduleSettingsLink = forwardRef(
+ (props, ref) => {
+ return (
+
+ );
+ },
+);
+
+const hasDeadline = (workspace: Workspace): boolean => {
+ return Boolean(workspace.latest_build.deadline);
+};
+
+const hasAutoStart = (workspace: Workspace): boolean => {
+ return Boolean(workspace.autostart_schedule);
+};
+
+export const canEditDeadline = (workspace: Workspace): boolean => {
+ return isWorkspaceOn(workspace) && hasDeadline(workspace);
+};
+
+export const shouldDisplayScheduleControls = (
+ workspace: Workspace,
+): boolean => {
+ const willAutoStop = isWorkspaceOn(workspace) && hasDeadline(workspace);
+ const willAutoStart = !isWorkspaceOn(workspace) && hasAutoStart(workspace);
+ return willAutoStop || willAutoStart;
+};
+
+const isShutdownSoon = (workspace: Workspace): boolean => {
+ const deadline = workspace.latest_build.deadline;
+ if (!deadline) {
+ return false;
+ }
+ const deadlineDate = new Date(deadline);
+ const now = new Date();
+ const diff = deadlineDate.getTime() - now.getTime();
+ const oneHour = 1000 * 60 * 60;
+ return diff < oneHour;
+};
+
+export const scheduleLabel = (workspace: Workspace) => {
+ return isWorkspaceOn(workspace) ? "Stops" : "Starts at";
+};
+
+const classNames = {
+ paper: css`
+ padding: 24px;
+ max-width: 288px;
+ margin-top: 8px;
+ border-radius: 4px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ `,
+
+ deadlineFormInput: css`
+ font-size: 14px;
+ padding: 0px;
+ border-radius: 4px;
+ `,
+};
+
+const styles = {
+ scheduleValue: {
+ display: "flex",
+ alignItems: "center",
+ gap: 12,
+ },
+
+ scheduleControls: {
+ display: "flex",
+ alignItems: "center",
+ gap: 4,
+ },
+
+ scheduleButton: (theme) => ({
+ border: `1px solid ${theme.palette.divider}`,
+ borderRadius: 4,
+ width: 20,
+ height: 20,
+
+ "& svg.MuiSvgIcon-root": {
+ width: 12,
+ height: 12,
+ },
+ }),
+
+ timePopoverTitle: {
+ fontWeight: 600,
+ marginBottom: 8,
+ },
+
+ timePopoverDescription: (theme) => ({
+ color: theme.palette.text.secondary,
+ }),
+
+ timePopoverForm: {
+ display: "flex",
+ alignItems: "center",
+ gap: 8,
+ padding: "8px 0",
+ marginTop: 12,
+ },
+
+ timePopoverField: {
+ margin: 0,
+ },
+
+ timePopoverButton: {
+ borderRadius: 4,
+ paddingLeft: 16,
+ paddingRight: 16,
+ flexShrink: 0,
+ },
+} satisfies Record>;
diff --git a/site/src/pages/WorkspacePage/WorkspaceStats.tsx b/site/src/pages/WorkspacePage/WorkspaceStats.tsx
index 866c26da71809..8f76c671e2a6b 100644
--- a/site/src/pages/WorkspacePage/WorkspaceStats.tsx
+++ b/site/src/pages/WorkspacePage/WorkspaceStats.tsx
@@ -1,33 +1,21 @@
-import { css } from "@emotion/css";
import { type Interpolation, type Theme } from "@emotion/react";
-import Link, { LinkProps } from "@mui/material/Link";
+import Link from "@mui/material/Link";
import { WorkspaceOutdatedTooltip } from "components/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip";
-import { forwardRef, type FC } from "react";
+import { type FC } from "react";
import { Link as RouterLink } from "react-router-dom";
-import {
- getDisplayWorkspaceTemplateName,
- isWorkspaceOn,
-} from "utils/workspace";
+import { getDisplayWorkspaceTemplateName } from "utils/workspace";
import type { Workspace } from "api/typesGenerated";
import { Stats, StatsItem } from "components/Stats/Stats";
-import { autostartDisplay, autostopDisplay } from "utils/schedule";
-import IconButton from "@mui/material/IconButton";
-import RemoveIcon from "@mui/icons-material/RemoveOutlined";
-import AddIcon from "@mui/icons-material/AddOutlined";
-import TextField from "@mui/material/TextField";
-import Button from "@mui/material/Button";
import { WorkspaceStatusText } from "components/WorkspaceStatusBadge/WorkspaceStatusBadge";
import { DormantDeletionStat } from "components/WorkspaceDeletion";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
- usePopover,
-} from "components/Popover/Popover";
import { workspaceQuota } from "api/queries/workspaceQuota";
import { useQuery } from "react-query";
-import Tooltip from "@mui/material/Tooltip";
import _ from "lodash";
+import {
+ WorkspaceScheduleControls,
+ scheduleLabel,
+ shouldDisplayScheduleControls,
+} from "./WorkspaceScheduleControls";
const Language = {
workspaceDetails: "Workspace Details",
@@ -38,26 +26,16 @@ const Language = {
export interface WorkspaceStatsProps {
workspace: Workspace;
- maxDeadlineIncrease: number;
- maxDeadlineDecrease: number;
canUpdateWorkspace: boolean;
- onDeadlinePlus: (hours: number) => void;
- onDeadlineMinus: (hours: number) => void;
handleUpdate: () => void;
}
export const WorkspaceStats: FC = ({
workspace,
- maxDeadlineDecrease,
- maxDeadlineIncrease,
canUpdateWorkspace,
handleUpdate,
- onDeadlineMinus,
- onDeadlinePlus,
}) => {
const displayTemplateName = getDisplayWorkspaceTemplateName(workspace);
- const deadlinePlusEnabled = maxDeadlineIncrease >= 1;
- const deadlineMinusEnabled = maxDeadlineDecrease >= 1;
const quotaQuery = useQuery(workspaceQuota(workspace.owner_name));
const quotaBudget = quotaQuery.data?.budget;
@@ -107,69 +85,15 @@ export const WorkspaceStats: FC = ({
}
/>
- {shouldDisplayScheduleLabel(workspace) && (
+ {shouldDisplayScheduleControls(workspace) && (
- {isWorkspaceOn(workspace) ? (
-
- ) : (
-
- {autostartDisplay(workspace.autostart_schedule)}
-
- )}
-
- {canUpdateWorkspace && canEditDeadline(workspace) && (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
+
}
/>
)}
@@ -187,206 +111,6 @@ export const WorkspaceStats: FC = ({
);
};
-interface AddTimeContentProps {
- maxDeadlineIncrease: number;
- onDeadlinePlus: (value: number) => void;
-}
-
-const AddTimeContent: FC = ({
- maxDeadlineIncrease,
- onDeadlinePlus,
-}) => {
- const popover = usePopover();
-
- return (
- <>
- Add hours to deadline
-
- Delay the shutdown of this workspace for a few more hours. This is only
- applied once.
-
-
- >
- );
-};
-
-interface DecreaseTimeContentProps {
- maxDeadlineDecrease: number;
- onDeadlineMinus: (hours: number) => void;
-}
-
-export const DecreaseTimeContent: FC = ({
- maxDeadlineDecrease,
- onDeadlineMinus,
-}) => {
- const popover = usePopover();
-
- return (
- <>
- Subtract hours to deadline
-
- Anticipate the shutdown of this workspace for a few more hours. This is
- only applied once.
-
-
- >
- );
-};
-
-interface AutoStopDisplayProps {
- workspace: Workspace;
-}
-
-const AutoStopDisplay: FC = ({ workspace }) => {
- const display = autostopDisplay(workspace);
-
- if (display.tooltip) {
- return (
-
- ({
- color: isShutdownSoon(workspace)
- ? `${theme.palette.warning.light} !important`
- : undefined,
- })}
- >
- {display.message}
-
-
- );
- }
-
- return {display.message};
-};
-
-const ScheduleSettingsLink = forwardRef(
- (props, ref) => {
- return (
-
- );
- },
-);
-
-const hasDeadline = (workspace: Workspace): boolean => {
- return Boolean(workspace.latest_build.deadline);
-};
-
-const hasAutoStart = (workspace: Workspace): boolean => {
- return Boolean(workspace.autostart_schedule);
-};
-
-export const canEditDeadline = (workspace: Workspace): boolean => {
- return isWorkspaceOn(workspace) && hasDeadline(workspace);
-};
-
-export const shouldDisplayScheduleLabel = (workspace: Workspace): boolean => {
- const willAutoStop = isWorkspaceOn(workspace) && hasDeadline(workspace);
- const willAutoStart = !isWorkspaceOn(workspace) && hasAutoStart(workspace);
- return willAutoStop || willAutoStart;
-};
-
-const scheduleLabel = (workspace: Workspace) => {
- return isWorkspaceOn(workspace) ? "Stops" : "Starts at";
-};
-
-const isShutdownSoon = (workspace: Workspace): boolean => {
- const deadline = workspace.latest_build.deadline;
- if (!deadline) {
- return false;
- }
- const deadlineDate = new Date(deadline);
- const now = new Date();
- const diff = deadlineDate.getTime() - now.getTime();
- const oneHour = 1000 * 60 * 60;
- return diff < oneHour;
-};
-
-const classNames = {
- paper: css`
- padding: 24px;
- max-width: 288px;
- margin-top: 8px;
- border-radius: 4px;
- display: flex;
- flex-direction: column;
- gap: 8px;
- `,
-
- deadlineFormInput: css`
- font-size: 14px;
- padding: 0px;
- border-radius: 4px;
- `,
-};
-
const styles = {
stats: (theme) => ({
padding: 0,
@@ -413,56 +137,4 @@ const styles = {
fontWeight: 500,
},
},
-
- scheduleValue: {
- display: "flex",
- alignItems: "center",
- gap: 12,
- },
-
- scheduleControls: {
- display: "flex",
- alignItems: "center",
- gap: 4,
- },
-
- scheduleButton: (theme) => ({
- border: `1px solid ${theme.palette.divider}`,
- borderRadius: 4,
- width: 20,
- height: 20,
-
- "& svg.MuiSvgIcon-root": {
- width: 12,
- height: 12,
- },
- }),
-
- timePopoverTitle: {
- fontWeight: 600,
- marginBottom: 8,
- },
-
- timePopoverDescription: (theme) => ({
- color: theme.palette.text.secondary,
- }),
-
- timePopoverForm: {
- display: "flex",
- alignItems: "center",
- gap: 8,
- padding: "8px 0",
- marginTop: 12,
- },
-
- timePopoverField: {
- margin: 0,
- },
-
- timePopoverButton: {
- borderRadius: 4,
- paddingLeft: 16,
- paddingRight: 16,
- flexShrink: 0,
- },
} satisfies Record>;
From f0d396bb2234f1e65e92edefc7ba8cd0d5d15a69 Mon Sep 17 00:00:00 2001
From: BrunoQuaresma
Date: Tue, 19 Dec 2023 23:40:35 +0000
Subject: [PATCH 2/2] Improve accessibility
---
.../WorkspacePage/WorkspaceScheduleControls.tsx | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/site/src/pages/WorkspacePage/WorkspaceScheduleControls.tsx b/site/src/pages/WorkspacePage/WorkspaceScheduleControls.tsx
index 11282b092bfba..1ef0fb78b0eb5 100644
--- a/site/src/pages/WorkspacePage/WorkspaceScheduleControls.tsx
+++ b/site/src/pages/WorkspacePage/WorkspaceScheduleControls.tsx
@@ -143,11 +143,11 @@ const AddTimeContent: FC = ({
return (
<>
- Add hours to deadline
-
+ Add hours to deadline
+
Delay the shutdown of this workspace for a few more hours. This is only
applied once.
-
+