fix: workspace schedule time displays (#2249)

Summary:

Various time displays weren't quite right.

Details:

- Display date (not just time) of upcoming workspace stop in workspace
page
- Fix ttlShutdownAt for various cases + tests
  - manual to non-manual
  - unchanged/unmodified
  - isBefore --> isSameOrBefore
  - use the delta (off by _ error)
- pluralize units in dayjs.add
This commit is contained in:
G r e y
2022-06-10 17:26:20 +00:00
committed by GitHub
parent 1a9e57296c
commit 928958c94c
4 changed files with 119 additions and 36 deletions
@@ -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
@@ -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"),
@@ -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.MockStoppedWorkspace,
"America/Chicago",
1,
Language.ttlHelperText,
],
[
"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",
1,
`${Language.ttlCausesShutdownHelperText} ${Language.ttlCausesShutdownAt} 01:39 PM CDT.`,
0,
Language.ttlCausesNoShutdownHelperText,
],
[
dayjs("2022-05-17T18:10:00Z"),
Mocks.MockWorkspace,
"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.`,
],
])("ttlShutdownAt(%p, %p, %p, %p) returns %p", (now, workspace, timezone, ttlHours, expected) => {
[
"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.`,
],
])("%p", (_, now, workspace, timezone, ttlHours, expected) => {
expect(ttlShutdownAt(now, workspace, timezone, ttlHours)).toEqual(expected)
})
})
@@ -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<WorkspaceScheduleFormProps> = ({
)
}
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")}.`
}
}
}