diff --git a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx index 1a5d35a499f11..5d5d1db960c5c 100644 --- a/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx +++ b/site/src/components/WorkspaceSchedule/WorkspaceSchedule.tsx @@ -17,8 +17,10 @@ import { stripTimezone } from "../../util/schedule" import { isWorkspaceOn } from "../../util/workspace" import { Stack } from "../Stack/Stack" -dayjs.extend(advancedFormat) +// REMARK: some plugins depend on utc, so it's listed first. Otherwise they're +// sorted alphabetically. dayjs.extend(utc) +dayjs.extend(advancedFormat) dayjs.extend(duration) dayjs.extend(relativeTime) dayjs.extend(timezone) @@ -50,7 +52,7 @@ export const Language = { if (now.isAfter(deadline)) { return "Workspace is shutting down" } else { - return deadline.tz(dayjs.tz.guess()).format("hh:mm A") + return deadline.tz(dayjs.tz.guess()).format("MMM D, YYYY h:mm A") } } else if (!ttl || ttl < 1) { // If the workspace is not on, and the ttl is 0 or undefined, then the diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx index c9ea6eafa8b9d..5c3126fc74248 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.stories.tsx @@ -22,7 +22,7 @@ export const WorkspaceNotRunning = Template.bind({}) WorkspaceNotRunning.args = { now: dayjs("2022-05-17T17:40:00Z"), initialValues: { - ...defaultWorkspaceSchedule(5, "asdfasdf"), + ...defaultWorkspaceSchedule(5), timezone: "UTC", }, workspace: { @@ -41,7 +41,7 @@ export const WorkspaceWillNotShutDown = Template.bind({}) WorkspaceWillNotShutDown.args = { now: dayjs("2022-05-17T17:40:00Z"), initialValues: { - ...defaultWorkspaceSchedule(5, "asdfasdf"), + ...defaultWorkspaceSchedule(5), timezone: "UTC", ttl: 0, }, @@ -60,7 +60,7 @@ export const WorkspaceWillShutdown = Template.bind({}) WorkspaceWillShutdown.args = { now: dayjs("2022-05-17T17:40:00Z"), initialValues: { - ...defaultWorkspaceSchedule(5, "asdfasdf"), + ...defaultWorkspaceSchedule(5), timezone: "UTC", }, workspace: { @@ -76,9 +76,9 @@ WorkspaceWillShutdown.args = { export const WorkspaceWillShutdownSoon = Template.bind({}) WorkspaceWillShutdownSoon.args = { - now: dayjs("2022-05-17T18:10:00Z"), + now: dayjs("2022-05-17T16:39:00Z"), initialValues: { - ...defaultWorkspaceSchedule(5, "asdfasdf"), + ...defaultWorkspaceSchedule(2), timezone: "UTC", ttl: 1, }, @@ -86,8 +86,9 @@ WorkspaceWillShutdownSoon.args = { ...Mocks.MockWorkspace, latest_build: { ...Mocks.MockWorkspaceBuild, - updated_at: "2022-05-17T17:39:00Z", + deadline: "2022-05-17T18:09:00Z", }, + ttl_ms: 2 * 60 * 60 * 1000, // 2 hours = shuts off at 18:09 }, onCancel: () => action("onCancel"), onSubmit: () => action("onSubmit"), @@ -95,9 +96,9 @@ WorkspaceWillShutdownSoon.args = { export const WorkspaceWillShutdownImmediately = Template.bind({}) WorkspaceWillShutdownImmediately.args = { - now: dayjs("2022-05-17T18:40:00Z"), + now: dayjs("2022-05-17T17:09:00Z"), initialValues: { - ...defaultWorkspaceSchedule(5, "asdfasdf"), + ...defaultWorkspaceSchedule(1), timezone: "UTC", ttl: 1, }, @@ -105,8 +106,9 @@ WorkspaceWillShutdownImmediately.args = { ...Mocks.MockWorkspace, latest_build: { ...Mocks.MockWorkspaceBuild, - updated_at: "2022-05-17T17:39:00Z", + deadline: "2022-05-17T18:09:00Z", }, + ttl_ms: 2 * 60 * 60 * 1000, // 2 hours = shuts off at 18:09 }, onCancel: () => action("onCancel"), onSubmit: () => action("onSubmit"), diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts index 8fa25cc66abd3..3e37484b9c09b 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.test.ts @@ -160,31 +160,99 @@ describe("validationSchema", () => { }) describe("ttlShutdownAt", () => { - it.each<[dayjs.Dayjs, Workspace, string, number, string]>([ - [dayjs("2022-05-17T18:09:00Z"), Mocks.MockStoppedWorkspace, "America/Chicago", 1, Language.ttlHelperText], - [dayjs("2022-05-17T18:09:00Z"), Mocks.MockWorkspace, "America/Chicago", 0, Language.ttlCausesNoShutdownHelperText], + it.each<[string, dayjs.Dayjs, Workspace, string, number, string]>([ [ + "Workspace is stopped --> helper text", dayjs("2022-05-17T18:09:00Z"), - Mocks.MockWorkspace, + Mocks.MockStoppedWorkspace, "America/Chicago", 1, - `${Language.ttlCausesShutdownHelperText} ${Language.ttlCausesShutdownAt} 01:39 PM CDT.`, + Language.ttlHelperText, ], [ - dayjs("2022-05-17T18:10:00Z"), + "TTL is not modified --> helper text", + dayjs("2022-05-17T16:09:00Z"), + { + ...Mocks.MockWorkspace, + latest_build: { + ...Mocks.MockWorkspaceBuild, + deadline: "2022-05-17T18:09:00Z", + }, + ttl_ms: 2 * 60 * 60 * 1000, // 2 hours = shuts off at 18:09 + }, + "America/Chicago", + 2, + Language.ttlHelperText, + ], + [ + "TTL becomes 0 --> manual helper text", + dayjs("2022-05-17T18:09:00Z"), Mocks.MockWorkspace, "America/Chicago", + 0, + Language.ttlCausesNoShutdownHelperText, + ], + [ + "Deadline of 18:09 becomes 17:09 at 17:09 --> immediate shutdown", + dayjs("2022-05-17T17:09:00Z"), + { + ...Mocks.MockWorkspace, + latest_build: { + ...Mocks.MockWorkspaceBuild, + deadline: "2022-05-17T18:09:00Z", + }, + ttl_ms: 2 * 60 * 60 * 1000, // 2 hours = shuts off at 18:09 + }, + "America/Chicago", + 1, + `⚠️ ${Language.ttlCausesShutdownHelperText} ${Language.ttlCausesShutdownImmediately} ⚠️`, + ], + [ + "Deadline of 18:09 becomes 17:09 at 16:39 --> display shutdown soon", + dayjs("2022-05-17T16:39:00Z"), + { + ...Mocks.MockWorkspace, + latest_build: { + ...Mocks.MockWorkspaceBuild, + deadline: "2022-05-17T18:09:00Z", + }, + ttl_ms: 2 * 60 * 60 * 1000, // 2 hours = shuts off at 18:09 + }, + "America/Chicago", 1, `⚠️ ${Language.ttlCausesShutdownHelperText} ${Language.ttlCausesShutdownSoon} ⚠️`, ], [ - dayjs("2022-05-17T18:40:00Z"), - Mocks.MockWorkspace, + "Deadline of 18:09 becomes 17:09 at 16:09 --> display 12:09 CDT", + dayjs("2022-05-17T16:09:00Z"), + { + ...Mocks.MockWorkspace, + latest_build: { + ...Mocks.MockWorkspaceBuild, + deadline: "2022-05-17T18:09:00Z", + }, + ttl_ms: 2 * 60 * 60 * 1000, // 2 hours = shuts off at 18:09 + }, "America/Chicago", 1, - `⚠️ ${Language.ttlCausesShutdownHelperText} ${Language.ttlCausesShutdownImmediately} ⚠️`, + `${Language.ttlCausesShutdownHelperText} ${Language.ttlCausesShutdownAt} May 17, 2022 12:09 PM.`, + ], + [ + "Manual workspace gets new deadline of 18:09 at 17:09 --> display 1:09 CDT", + dayjs("2022-05-17T17:09:00Z"), + { + ...Mocks.MockWorkspace, + latest_build: { + ...Mocks.MockWorkspaceBuild, + deadline: "0001-01-01T00:00:00Z", + }, + ttl_ms: 0, + }, + "America/Chicago", + 1, + `${Language.ttlCausesShutdownHelperText} ${Language.ttlCausesShutdownAt} May 17, 2022 1:09 PM.`, ], - ])("ttlShutdownAt(%p, %p, %p, %p) returns %p", (now, workspace, timezone, ttlHours, expected) => { + ])("%p", (_, now, workspace, timezone, ttlHours, expected) => { expect(ttlShutdownAt(now, workspace, timezone, ttlHours)).toEqual(expected) }) }) diff --git a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx index e5d5376596c94..efd2c78199e34 100644 --- a/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx +++ b/site/src/components/WorkspaceScheduleForm/WorkspaceScheduleForm.tsx @@ -9,6 +9,7 @@ import makeStyles from "@material-ui/core/styles/makeStyles" import TextField from "@material-ui/core/TextField" import dayjs from "dayjs" import advancedFormat from "dayjs/plugin/advancedFormat" +import isSameOrBefore from "dayjs/plugin/isSameOrBefore" import timezone from "dayjs/plugin/timezone" import utc from "dayjs/plugin/utc" import { useFormik } from "formik" @@ -23,11 +24,11 @@ import { FullPageForm } from "../FullPageForm/FullPageForm" import { Stack } from "../Stack/Stack" import { zones } from "./zones" -// REMARK: timezone plugin depends on UTC -// -// SEE: https://day.js.org/docs/en/timezone/timezone -dayjs.extend(advancedFormat) +// REMARK: some plugins depend on utc, so it's listed first. Otherwise they're +// sorted alphabetically. dayjs.extend(utc) +dayjs.extend(advancedFormat) +dayjs.extend(isSameOrBefore) dayjs.extend(timezone) export const Language = { @@ -282,19 +283,29 @@ export const WorkspaceScheduleForm: FC = ({ ) } -export const ttlShutdownAt = (now: dayjs.Dayjs, workspace: Workspace, tz: string, newTTL: number): string => { - const newDeadline = dayjs(workspace.latest_build.updated_at).add(newTTL, "hour") - if (!isWorkspaceOn(workspace)) { +export const ttlShutdownAt = (now: dayjs.Dayjs, workspace: Workspace, tz: string, formTTL: number): string => { + // a manual shutdown has a deadline of '"0001-01-01T00:00:00Z"' + // SEE: #1834 + const deadline = dayjs(workspace.latest_build.deadline).utc() + const hasDeadline = deadline.year() > 1 + const ttl = workspace.ttl_ms ? workspace.ttl_ms / (1000 * 60 * 60) : 0 + const delta = formTTL - ttl + + if (delta === 0 || !isWorkspaceOn(workspace)) { return Language.ttlHelperText - } else if (newTTL === 0) { + } else if (formTTL === 0) { return Language.ttlCausesNoShutdownHelperText - } else if (newDeadline.isBefore(now)) { - return `⚠️ ${Language.ttlCausesShutdownHelperText} ${Language.ttlCausesShutdownImmediately} ⚠️` - } else if (newDeadline.isBefore(now.add(30, "minute"))) { - return `⚠️ ${Language.ttlCausesShutdownHelperText} ${Language.ttlCausesShutdownSoon} ⚠️` } else { - const newDeadlineString = newDeadline.tz(tz).format("hh:mm A z") - return `${Language.ttlCausesShutdownHelperText} ${Language.ttlCausesShutdownAt} ${newDeadlineString}.` + const newDeadline = dayjs(hasDeadline ? deadline : now).add(delta, "hours") + if (newDeadline.isSameOrBefore(now)) { + return `⚠️ ${Language.ttlCausesShutdownHelperText} ${Language.ttlCausesShutdownImmediately} ⚠️` + } else if (newDeadline.isSameOrBefore(now.add(30, "minutes"))) { + return `⚠️ ${Language.ttlCausesShutdownHelperText} ${Language.ttlCausesShutdownSoon} ⚠️` + } else { + return `${Language.ttlCausesShutdownHelperText} ${Language.ttlCausesShutdownAt} ${newDeadline + .tz(tz) + .format("MMM D, YYYY h:mm A")}.` + } } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy