From 8e5188828f2086c73de71aab2ed58c35ef9c6fe7 Mon Sep 17 00:00:00 2001 From: johnstcn Date: Wed, 29 Jun 2022 12:44:43 +0000 Subject: [PATCH 01/12] site: add buttons to workspace schedule to edit deadline --- site/src/components/Workspace/Workspace.tsx | 11 ++++++- .../WorkspaceSchedule/WorkspaceSchedule.tsx | 31 +++++++++++++++++++ .../src/pages/WorkspacePage/WorkspacePage.tsx | 8 +++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/site/src/components/Workspace/Workspace.tsx b/site/src/components/Workspace/Workspace.tsx index ace4c3ba928c6..608a8b096c3b0 100644 --- a/site/src/components/Workspace/Workspace.tsx +++ b/site/src/components/Workspace/Workspace.tsx @@ -19,6 +19,10 @@ export interface WorkspaceProps { isLoading?: boolean onExtend: () => void } + scheduleProps: { + onDeadlinePlus: () => void + onDeadlineMinus: () => void + } handleStart: () => void handleStop: () => void handleDelete: () => void @@ -36,6 +40,7 @@ export interface WorkspaceProps { */ export const Workspace: FC = ({ bannerProps, + scheduleProps, handleStart, handleStop, handleDelete, @@ -99,7 +104,11 @@ export const Workspace: FC = ({ - + diff --git a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx index e0873db921878..c41d23e8ba41f 100644 --- a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx +++ b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx @@ -1,3 +1,4 @@ +import Button from "@material-ui/core/Button" import Link from "@material-ui/core/Link" import { makeStyles } from "@material-ui/core/styles" import Typography from "@material-ui/core/Typography" @@ -66,6 +67,8 @@ export const Language = { } }, editScheduleLink: "Edit schedule", + editDeadlineMinus: "[-1 hour]", + editDeadlinePlus: "[+1 hour]", scheduleHeader: (workspace: Workspace): string => { const tz = workspace.autostart_schedule ? extractTimezone(workspace.autostart_schedule) @@ -76,6 +79,16 @@ export const Language = { export interface WorkspaceScheduleProps { workspace: Workspace + onDeadlinePlus: () => void + onDeadlineMinus: () => void +} + +export const shouldDisplayPlusMins = (workspace: Workspace): boolean => { + if (!isWorkspaceOn(workspace)) { + return false + } + const deadline = dayjs(workspace.latest_build.deadline).utc() + return deadline.year() > 1 } export const WorkspaceSchedule: FC = ({ workspace }) => { @@ -100,6 +113,16 @@ export const WorkspaceSchedule: FC = ({ workspace }) => {Language.autoStopDisplay(workspace)} +
+ + + + +
({ scheduleAction: { cursor: "pointer", }, + editDeadline: { + fontSize: 12, + textTransform: "uppercase", + display: "inline-block", + fontWeight: 600, + color: theme.palette.text.secondary, + margin: theme.spacing(0), + }, })) diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index 6dfd881bf50e9..6056c39832228 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -59,6 +59,14 @@ export const WorkspacePage: React.FC = () => { bannerSend({ type: "EXTEND_DEADLINE_DEFAULT", workspaceId: workspace.id }) }, }} + scheduleProps={{ + onDeadlineMinus: () => { + console.log("not implemented") + }, + onDeadlinePlus: () => { + console.log("not implemented") + }, + }} workspace={workspace} handleStart={() => workspaceSend("START")} handleStop={() => workspaceSend("STOP")} From e7b080edb861899998f99fc0256b3b9a593a85cb Mon Sep 17 00:00:00 2001 From: johnstcn Date: Wed, 29 Jun 2022 15:55:38 +0000 Subject: [PATCH 02/12] the buttons now do things --- site/src/api/api.ts | 5 +-- .../WorkspaceSchedule/WorkspaceSchedule.tsx | 10 ++++-- .../src/pages/WorkspacePage/WorkspacePage.tsx | 34 +++++++++++++++++-- .../workspaceScheduleBannerXService.ts | 18 +++++----- 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 5938666d1c997..bf510fddf21c5 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -2,6 +2,7 @@ import axios, { AxiosRequestHeaders } from "axios" import * as Types from "./types" import { WorkspaceBuildTransition } from "./types" import * as TypesGen from "./typesGenerated" +import dayjs from "dayjs" const CONTENT_TYPE_JSON: AxiosRequestHeaders = { "Content-Type": "application/json", @@ -339,7 +340,7 @@ export const getWorkspaceBuildLogs = async ( export const putWorkspaceExtension = async ( workspaceId: string, - extendWorkspaceRequest: TypesGen.PutExtendWorkspaceRequest, + newDeadline: dayjs.Dayjs, ): Promise => { - await axios.put(`/api/v2/workspaces/${workspaceId}/extend`, extendWorkspaceRequest) + await axios.put(`/api/v2/workspaces/${workspaceId}/extend`, { deadline: newDeadline }) } diff --git a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx index c41d23e8ba41f..e29102a60ab29 100644 --- a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx +++ b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx @@ -91,7 +91,11 @@ export const shouldDisplayPlusMins = (workspace: Workspace): boolean => { return deadline.year() > 1 } -export const WorkspaceSchedule: FC = ({ workspace }) => { +export const WorkspaceSchedule: FC = ({ + workspace, + onDeadlineMinus, + onDeadlinePlus, +}) => { const styles = useStyles() return ( @@ -115,10 +119,10 @@ export const WorkspaceSchedule: FC = ({ workspace }) =>
- - diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index 6056c39832228..abe0185498714 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -1,4 +1,6 @@ import { useMachine, useSelector } from "@xstate/react" +import dayjs from "dayjs" +import minMax from "dayjs/plugin/minMax" import React, { useContext, useEffect } from "react" import { Helmet } from "react-helmet" import { useParams } from "react-router-dom" @@ -13,6 +15,8 @@ import { XServiceContext } from "../../xServices/StateContext" import { workspaceMachine } from "../../xServices/workspace/workspaceXService" import { workspaceScheduleBannerMachine } from "../../xServices/workspaceSchedule/workspaceScheduleBannerXService" +dayjs.extend(minMax) + export const WorkspacePage: React.FC = () => { const { username: usernameQueryParam, workspace: workspaceQueryParam } = useParams() const username = firstOrItem(usernameQueryParam, null) @@ -56,15 +60,33 @@ export const WorkspacePage: React.FC = () => { bannerProps={{ isLoading: bannerState.hasTag("loading"), onExtend: () => { - bannerSend({ type: "EXTEND_DEADLINE_DEFAULT", workspaceId: workspace.id }) + bannerSend({ + type: "UPDATE_DEADLINE", + workspaceId: workspace.id, + newDeadline: dayjs().utc().add(4, "hours"), + }) }, }} scheduleProps={{ onDeadlineMinus: () => { - console.log("not implemented") + bannerSend({ + type: "UPDATE_DEADLINE", + workspaceId: workspace.id, + newDeadline: boundedDeadline( + dayjs(workspace.latest_build.deadline).utc().add(-1, "hours"), + dayjs(), + ), + }) }, onDeadlinePlus: () => { - console.log("not implemented") + bannerSend({ + type: "UPDATE_DEADLINE", + workspaceId: workspace.id, + newDeadline: boundedDeadline( + dayjs(workspace.latest_build.deadline).utc().add(1, "hours"), + dayjs(), + ), + }) }, }} workspace={workspace} @@ -89,3 +111,9 @@ export const WorkspacePage: React.FC = () => { ) } } + +export const boundedDeadline = (newDeadline: dayjs.Dayjs, now: dayjs.Dayjs): dayjs.Dayjs => { + const minDeadline = now.add(30, "minutes") + const maxDeadline = now.add(24, "hours") + return dayjs.min(dayjs.max(minDeadline, newDeadline), maxDeadline) +} diff --git a/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts b/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts index 418177519bf0b..e1c3f83e852a9 100644 --- a/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts +++ b/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts @@ -1,18 +1,18 @@ /** * @fileoverview workspaceScheduleBanner is an xstate machine backing a form, - * presented as an Alert/banner, for reactively extending a workspace schedule. + * presented as an Alert/banner, for reactively updating a workspace schedule. */ import { createMachine } from "xstate" import * as API from "../../api/api" +import dayjs from "dayjs" import { displayError, displaySuccess } from "../../components/GlobalSnackbar/utils" -import { defaultWorkspaceExtension } from "../../util/workspace" export const Language = { errorExtension: "Failed to extend workspace deadline.", successExtension: "Successfully extended workspace deadline.", } -export type WorkspaceScheduleBannerEvent = { type: "EXTEND_DEADLINE_DEFAULT"; workspaceId: string } +export type WorkspaceScheduleBannerEvent = { type: "UPDATE_DEADLINE"; workspaceId: string, newDeadline: dayjs.Dayjs } export const workspaceScheduleBannerMachine = createMachine( { @@ -25,13 +25,13 @@ export const workspaceScheduleBannerMachine = createMachine( states: { idle: { on: { - EXTEND_DEADLINE_DEFAULT: "extendingDeadline", + UPDATE_DEADLINE: "updatingDeadline", }, }, - extendingDeadline: { + updatingDeadline: { invoke: { - src: "extendDeadlineDefault", - id: "extendDeadlineDefault", + src: "updateDeadline", + id: "updateDeadline", onDone: { target: "idle", actions: "displaySuccessMessage", @@ -56,8 +56,8 @@ export const workspaceScheduleBannerMachine = createMachine( }, services: { - extendDeadlineDefault: async (_, event) => { - await API.putWorkspaceExtension(event.workspaceId, defaultWorkspaceExtension()) + updateDeadline: async (_, event) => { + await API.putWorkspaceExtension(event.workspaceId, event.newDeadline ) }, }, }, From 8046a8bfdf3dd4e0d718c761d0fa9cef8683c895 Mon Sep 17 00:00:00 2001 From: johnstcn Date: Wed, 29 Jun 2022 16:09:27 +0000 Subject: [PATCH 03/12] only show buttons when workspace is running --- .../WorkspaceSchedule/WorkspaceSchedule.tsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx index e29102a60ab29..89bfe8229817e 100644 --- a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx +++ b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx @@ -97,6 +97,18 @@ export const WorkspaceSchedule: FC = ({ onDeadlinePlus, }) => { const styles = useStyles() + const editDeadlineButtons = shouldDisplayPlusMins(workspace) ? ( +
+ + + + +
+ ) : null return (
@@ -117,16 +129,7 @@ export const WorkspaceSchedule: FC = ({ {Language.autoStopDisplay(workspace)}
-
- - - - -
+ {editDeadlineButtons}
Date: Wed, 29 Jun 2022 16:10:40 +0000 Subject: [PATCH 04/12] fmt --- site/src/api/api.ts | 2 +- .../workspaceScheduleBannerXService.ts | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index bf510fddf21c5..8517dab9e8d26 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -1,8 +1,8 @@ import axios, { AxiosRequestHeaders } from "axios" +import dayjs from "dayjs" import * as Types from "./types" import { WorkspaceBuildTransition } from "./types" import * as TypesGen from "./typesGenerated" -import dayjs from "dayjs" const CONTENT_TYPE_JSON: AxiosRequestHeaders = { "Content-Type": "application/json", diff --git a/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts b/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts index e1c3f83e852a9..e0eed8aff3048 100644 --- a/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts +++ b/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts @@ -2,9 +2,9 @@ * @fileoverview workspaceScheduleBanner is an xstate machine backing a form, * presented as an Alert/banner, for reactively updating a workspace schedule. */ +import dayjs from "dayjs" import { createMachine } from "xstate" import * as API from "../../api/api" -import dayjs from "dayjs" import { displayError, displaySuccess } from "../../components/GlobalSnackbar/utils" export const Language = { @@ -12,7 +12,11 @@ export const Language = { successExtension: "Successfully extended workspace deadline.", } -export type WorkspaceScheduleBannerEvent = { type: "UPDATE_DEADLINE"; workspaceId: string, newDeadline: dayjs.Dayjs } +export type WorkspaceScheduleBannerEvent = { + type: "UPDATE_DEADLINE" + workspaceId: string + newDeadline: dayjs.Dayjs +} export const workspaceScheduleBannerMachine = createMachine( { @@ -57,7 +61,7 @@ export const workspaceScheduleBannerMachine = createMachine( services: { updateDeadline: async (_, event) => { - await API.putWorkspaceExtension(event.workspaceId, event.newDeadline ) + await API.putWorkspaceExtension(event.workspaceId, event.newDeadline) }, }, }, From 72febb50d6c72c927924b03a3425b3b873ea0d14 Mon Sep 17 00:00:00 2001 From: johnstcn Date: Thu, 30 Jun 2022 10:45:13 +0000 Subject: [PATCH 05/12] Adjust style to match #2091 --- .../WorkspaceSchedule/WorkspaceSchedule.tsx | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx index 89bfe8229817e..1426ae9942d26 100644 --- a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx +++ b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx @@ -1,7 +1,10 @@ -import Button from "@material-ui/core/Button" +import IconButton from "@material-ui/core/IconButton" import Link from "@material-ui/core/Link" import { makeStyles } from "@material-ui/core/styles" +import Tooltip from "@material-ui/core/Tooltip" import Typography from "@material-ui/core/Typography" +import AddBoxIcon from "@material-ui/icons/AddBox" +import IndeterminateCheckBoxIcon from "@material-ui/icons/IndeterminateCheckBox" import ScheduleIcon from "@material-ui/icons/Schedule" import cronstrue from "cronstrue" import dayjs from "dayjs" @@ -67,8 +70,8 @@ export const Language = { } }, editScheduleLink: "Edit schedule", - editDeadlineMinus: "[-1 hour]", - editDeadlinePlus: "[+1 hour]", + editDeadlineMinus: "Subtract one hour", + editDeadlinePlus: "Add one hour", scheduleHeader: (workspace: Workspace): string => { const tz = workspace.autostart_schedule ? extractTimezone(workspace.autostart_schedule) @@ -98,16 +101,18 @@ export const WorkspaceSchedule: FC = ({ }) => { const styles = useStyles() const editDeadlineButtons = shouldDisplayPlusMins(workspace) ? ( -
- - - - -
+ + + + + + + + + + + + ) : null return ( @@ -125,11 +130,13 @@ export const WorkspaceSchedule: FC = ({
{Language.autoStopLabel} - - {Language.autoStopDisplay(workspace)} - + + + {Language.autoStopDisplay(workspace)} + + {editDeadlineButtons} +
- {editDeadlineButtons}
({ color: theme.palette.text.secondary, }, scheduleValue: { - fontSize: 16, - marginTop: theme.spacing(0.25), + fontSize: 14, + marginTop: theme.spacing(0.75), display: "inline-block", color: theme.palette.text.secondary, }, @@ -177,11 +184,6 @@ const useStyles = makeStyles((theme) => ({ cursor: "pointer", }, editDeadline: { - fontSize: 12, - textTransform: "uppercase", - display: "inline-block", - fontWeight: 600, color: theme.palette.text.secondary, - margin: theme.spacing(0), }, })) From 7bae0facc6c1cf37ad02176da641c7114b57881c Mon Sep 17 00:00:00 2001 From: johnstcn Date: Thu, 30 Jun 2022 11:29:58 +0000 Subject: [PATCH 06/12] disable edit deadline buttons if outside bounds --- .../WorkspaceSchedule/WorkspaceSchedule.tsx | 26 +++++++++++++++++-- .../src/pages/WorkspacePage/WorkspacePage.tsx | 2 +- .../workspaceScheduleBannerXService.ts | 4 +-- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx index 1426ae9942d26..56672d39805e1 100644 --- a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx +++ b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx @@ -81,6 +81,7 @@ export const Language = { } export interface WorkspaceScheduleProps { + now?: dayjs.Dayjs workspace: Workspace onDeadlinePlus: () => void onDeadlineMinus: () => void @@ -94,7 +95,18 @@ export const shouldDisplayPlusMins = (workspace: Workspace): boolean => { return deadline.year() > 1 } +export const deadlineMinusDisabled = (workspace: Workspace, now: dayjs.Dayjs): boolean => { + const delta = dayjs(workspace.latest_build.deadline).diff(now) + return delta <= 30 * 60 * 1000 +} + +export const deadlinePlusDisabled = (workspace: Workspace, now: dayjs.Dayjs): boolean => { + const delta = dayjs(workspace.latest_build.deadline).diff(now) + return delta > 23 * 59 * 59 * 1000 +} + export const WorkspaceSchedule: FC = ({ + now, workspace, onDeadlineMinus, onDeadlinePlus, @@ -102,12 +114,22 @@ export const WorkspaceSchedule: FC = ({ const styles = useStyles() const editDeadlineButtons = shouldDisplayPlusMins(workspace) ? ( - + - + diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index abe0185498714..7dfbac41e803c 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -63,7 +63,7 @@ export const WorkspacePage: React.FC = () => { bannerSend({ type: "UPDATE_DEADLINE", workspaceId: workspace.id, - newDeadline: dayjs().utc().add(4, "hours"), + newDeadline: dayjs(workspace.latest_build.deadline).utc().add(4, "hours"), }) }, }} diff --git a/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts b/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts index e0eed8aff3048..cee9230fcd953 100644 --- a/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts +++ b/site/src/xServices/workspaceSchedule/workspaceScheduleBannerXService.ts @@ -8,8 +8,8 @@ import * as API from "../../api/api" import { displayError, displaySuccess } from "../../components/GlobalSnackbar/utils" export const Language = { - errorExtension: "Failed to extend workspace deadline.", - successExtension: "Successfully extended workspace deadline.", + errorExtension: "Failed to update workspace shutdown time.", + successExtension: "Updated workspace shutdown time.", } export type WorkspaceScheduleBannerEvent = { From e14765ec2382f942e8c8d71c8484393a4ca97272 Mon Sep 17 00:00:00 2001 From: johnstcn Date: Thu, 30 Jun 2022 11:32:14 +0000 Subject: [PATCH 07/12] use fancy operator --- site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx index 56672d39805e1..0bc30f93adb71 100644 --- a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx +++ b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx @@ -116,7 +116,7 @@ export const WorkspaceSchedule: FC = ({ @@ -126,7 +126,7 @@ export const WorkspaceSchedule: FC = ({ From 435afe8905c1554304fdfb4421b7fab655703097 Mon Sep 17 00:00:00 2001 From: johnstcn Date: Thu, 30 Jun 2022 11:49:09 +0000 Subject: [PATCH 08/12] add story --- .../WorkspaceSchedule.stories.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.stories.tsx b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.stories.tsx index fa941cf88f516..835c5c34d2c82 100644 --- a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.stories.tsx +++ b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.stories.tsx @@ -11,6 +11,7 @@ dayjs.extend(utc) // SEE: https:github.com/storybookjs/storybook/issues/12208#issuecomment-697044557 const ONE = 1 const SEVEN = 7 +const THIRTY = 30 export default { title: "components/WorkspaceSchedule", @@ -47,6 +48,19 @@ NoTTL.args = { }, } +export const ShutdownRealSoon = Template.bind({}) +ShutdownRealSoon.args = { + workspace: { + ...Mocks.MockWorkspace, + latest_build: { + ...Mocks.MockWorkspaceBuild, + deadline: dayjs().add(THIRTY, "minute").utc().format(), + transition: "start", + }, + ttl_ms: 2 * 60 * 60 * 1000, // 2 hours + }, +} + export const ShutdownSoon = Template.bind({}) ShutdownSoon.args = { workspace: { From 5c1fef77897a618c62600ee8de6d4b902ce5ab6b Mon Sep 17 00:00:00 2001 From: johnstcn Date: Thu, 30 Jun 2022 12:54:20 +0000 Subject: [PATCH 09/12] fix storybook --- site/src/components/Workspace/Workspace.stories.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/site/src/components/Workspace/Workspace.stories.tsx b/site/src/components/Workspace/Workspace.stories.tsx index 0ed600e104f84..c94da97054640 100644 --- a/site/src/components/Workspace/Workspace.stories.tsx +++ b/site/src/components/Workspace/Workspace.stories.tsx @@ -17,6 +17,14 @@ Started.args = { isLoading: false, onExtend: action("extend"), }, + scheduleProps: { + onDeadlineMinus: () => { + // do nothing, this is just for storybook + }, + onDeadlinePlus: () => { + // do nothing, this is just for storybook + }, + }, workspace: Mocks.MockWorkspace, handleStart: action("start"), handleStop: action("stop"), From d65f8373eda00d0e8f3aa6583eae1841e3dba4a1 Mon Sep 17 00:00:00 2001 From: johnstcn Date: Thu, 30 Jun 2022 13:56:55 +0000 Subject: [PATCH 10/12] add unit tests for workspace deadline buttons --- .../WorkspaceSchedule.test.tsx | 92 +++++++++++++++++++ .../WorkspaceSchedule/WorkspaceSchedule.tsx | 8 +- 2 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 site/src/components/WorkspaceSchedule/WorkspaceSchedule.test.tsx diff --git a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.test.tsx b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.test.tsx new file mode 100644 index 0000000000000..0b3917ccfc343 --- /dev/null +++ b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.test.tsx @@ -0,0 +1,92 @@ +import dayjs from "dayjs" +import utc from "dayjs/plugin/utc" +import * as TypesGen from "../../api/typesGenerated" +import * as Mocks from "../../testHelpers/entities" +import { + deadlineMinusDisabled, + deadlinePlusDisabled, + shouldDisplayPlusMinus, +} from "./WorkspaceSchedule" + +dayjs.extend(utc) +const now = dayjs() + +describe("WorkspaceSchedule", () => { + describe("shouldDisplayPlusMinus", () => { + it("should not display if the workspace is not running", () => { + // Given: a stopped workspace + const workspace: TypesGen.Workspace = Mocks.MockStoppedWorkspace + + // Then: shouldDisplayPlusMinus should be false + expect(shouldDisplayPlusMinus(workspace)).toBeFalsy() + }) + + it("should display if the workspace is running", () => { + // Given: a stopped workspace + const workspace: TypesGen.Workspace = Mocks.MockWorkspace + + // Then: shouldDisplayPlusMinus should be false + expect(shouldDisplayPlusMinus(workspace)).toBeTruthy() + }) + }) + + describe("deadlineMinusDisabled", () => { + it("should be false if the deadline is more than 30 minutes in the future", () => { + // Given: a workspace with a deadline set to 31 minutes in the future + const workspace: TypesGen.Workspace = { + ...Mocks.MockWorkspace, + latest_build: { + ...Mocks.MockWorkspaceBuild, + deadline: now.add(31, "minutes").utc().format(), + }, + } + + // Then: deadlineMinusDisabled should be falsy + expect(deadlineMinusDisabled(workspace, now)).toBeFalsy() + }) + + it("should be true if the deadline is 30 minutes or less in the future", () => { + // Given: a workspace with a deadline set to 30 minutes in the future + const workspace: TypesGen.Workspace = { + ...Mocks.MockWorkspace, + latest_build: { + ...Mocks.MockWorkspaceBuild, + deadline: now.add(30, "minutes").utc().format(), + }, + } + + // Then: deadlineMinusDisabled should be truthy + expect(deadlineMinusDisabled(workspace, now)).toBeTruthy() + }) + }) + + describe("deadlinePlusDisabled", () => { + it("should be false if the deadline is less than 24 hours in the future", () => { + // Given: a workspace with a deadline set to 23 hours in the future + const workspace: TypesGen.Workspace = { + ...Mocks.MockWorkspace, + latest_build: { + ...Mocks.MockWorkspaceBuild, + deadline: now.add(23, "hours").utc().format(), + }, + } + + // Then: deadlinePlusDisabled should be falsy + expect(deadlinePlusDisabled(workspace, now)).toBeFalsy() + }) + + it("should be true if the deadline is 24 hours or more in the future", () => { + // Given: a workspace with a deadline set to 25 hours in the future + const workspace: TypesGen.Workspace = { + ...Mocks.MockWorkspace, + latest_build: { + ...Mocks.MockWorkspaceBuild, + deadline: now.add(25, "hours").utc().format(), + }, + } + + // Then: deadlinePlusDisabled should be truthy + expect(deadlinePlusDisabled(workspace, now)).toBeTruthy() + }) + }) +}) diff --git a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx index 0bc30f93adb71..9d1b0ae751cd8 100644 --- a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx +++ b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx @@ -87,7 +87,7 @@ export interface WorkspaceScheduleProps { onDeadlineMinus: () => void } -export const shouldDisplayPlusMins = (workspace: Workspace): boolean => { +export const shouldDisplayPlusMinus = (workspace: Workspace): boolean => { if (!isWorkspaceOn(workspace)) { return false } @@ -97,12 +97,12 @@ export const shouldDisplayPlusMins = (workspace: Workspace): boolean => { export const deadlineMinusDisabled = (workspace: Workspace, now: dayjs.Dayjs): boolean => { const delta = dayjs(workspace.latest_build.deadline).diff(now) - return delta <= 30 * 60 * 1000 + return Math.abs(delta) <= 30 * 60 * 1000 // 30 minutes } export const deadlinePlusDisabled = (workspace: Workspace, now: dayjs.Dayjs): boolean => { const delta = dayjs(workspace.latest_build.deadline).diff(now) - return delta > 23 * 59 * 59 * 1000 + return Math.abs(delta) >= 24 * 60 * 60 * 1000 // 24 hours } export const WorkspaceSchedule: FC = ({ @@ -112,7 +112,7 @@ export const WorkspaceSchedule: FC = ({ onDeadlinePlus, }) => { const styles = useStyles() - const editDeadlineButtons = shouldDisplayPlusMins(workspace) ? ( + const editDeadlineButtons = shouldDisplayPlusMinus(workspace) ? ( Date: Thu, 30 Jun 2022 14:23:39 +0000 Subject: [PATCH 11/12] fixup! add unit tests for workspace deadline buttons --- site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx index 9d1b0ae751cd8..66b8768d261a8 100644 --- a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx +++ b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx @@ -97,12 +97,12 @@ export const shouldDisplayPlusMinus = (workspace: Workspace): boolean => { export const deadlineMinusDisabled = (workspace: Workspace, now: dayjs.Dayjs): boolean => { const delta = dayjs(workspace.latest_build.deadline).diff(now) - return Math.abs(delta) <= 30 * 60 * 1000 // 30 minutes + return delta <= 30 * 60 * 1000 // 30 minutes } export const deadlinePlusDisabled = (workspace: Workspace, now: dayjs.Dayjs): boolean => { const delta = dayjs(workspace.latest_build.deadline).diff(now) - return Math.abs(delta) >= 24 * 60 * 60 * 1000 // 24 hours + return delta >= 24 * 60 * 60 * 1000 // 24 hours } export const WorkspaceSchedule: FC = ({ From 9e069d8bccb1c366700e14e9edad2032a9a6b4b4 Mon Sep 17 00:00:00 2001 From: johnstcn Date: Thu, 30 Jun 2022 14:27:51 +0000 Subject: [PATCH 12/12] add tests for past deadline --- .../WorkspaceSchedule.test.tsx | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.test.tsx b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.test.tsx index 0b3917ccfc343..b08dfe5e37aeb 100644 --- a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.test.tsx +++ b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.test.tsx @@ -58,6 +58,20 @@ describe("WorkspaceSchedule", () => { // Then: deadlineMinusDisabled should be truthy expect(deadlineMinusDisabled(workspace, now)).toBeTruthy() }) + + it("should be true if the deadline is in the past", () => { + // Given: a workspace with a deadline set to 1 minute in the past + const workspace: TypesGen.Workspace = { + ...Mocks.MockWorkspace, + latest_build: { + ...Mocks.MockWorkspaceBuild, + deadline: now.add(-1, "minutes").utc().format(), + }, + } + + // Then: deadlineMinusDisabled should be truthy + expect(deadlineMinusDisabled(workspace, now)).toBeTruthy() + }) }) describe("deadlinePlusDisabled", () => { @@ -88,5 +102,19 @@ describe("WorkspaceSchedule", () => { // Then: deadlinePlusDisabled should be truthy expect(deadlinePlusDisabled(workspace, now)).toBeTruthy() }) + + it("should be false if the deadline is in the past", () => { + // Given: a workspace with a deadline set to 1 minute in the past + const workspace: TypesGen.Workspace = { + ...Mocks.MockWorkspace, + latest_build: { + ...Mocks.MockWorkspaceBuild, + deadline: now.add(-1, "minute").utc().format(), + }, + } + + // Then: deadlinePlusDisabled should be falsy + expect(deadlinePlusDisabled(workspace, now)).toBeFalsy() + }) }) })