mirror of
https://github.com/coder/coder.git
synced 2026-09-21 20:51:01 +08:00
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:
@@ -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")}.`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user