Skip to content

Commit ebc4965

Browse files
committed
initialization
1 parent 4764e5c commit ebc4965

File tree

4 files changed

+211
-5
lines changed

4 files changed

+211
-5
lines changed

site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.test.tsx

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as TypesGen from "../../api/typesGenerated"
22
import { WorkspaceScheduleFormValues } from "../../components/WorkspaceStats/WorkspaceScheduleForm"
3-
import { formValuesToAutoStartRequest, formValuesToTTLRequest } from "./WorkspaceSchedulePage"
3+
import * as Mocks from "../../testHelpers/entities"
4+
import { formValuesToAutoStartRequest, formValuesToTTLRequest, workspaceToInitialValues } from "./WorkspaceSchedulePage"
45

56
const validValues: WorkspaceScheduleFormValues = {
67
sunday: false,
@@ -149,4 +150,73 @@ describe("WorkspaceSchedulePage", () => {
149150
expect(formValuesToTTLRequest(values)).toEqual(request)
150151
})
151152
})
153+
154+
describe("workspaceToInitialValues", () => {
155+
it.each<[TypesGen.Workspace, WorkspaceScheduleFormValues]>([
156+
// Empty case
157+
[
158+
{
159+
...Mocks.MockWorkspace,
160+
autostart_schedule: "",
161+
ttl: undefined,
162+
},
163+
{
164+
sunday: false,
165+
monday: false,
166+
tuesday: false,
167+
wednesday: false,
168+
thursday: false,
169+
friday: false,
170+
saturday: false,
171+
startTime: "",
172+
timezone: "",
173+
ttl: 0,
174+
},
175+
],
176+
177+
// Basic case: 9:30 1-5 UTC running for 2 hours
178+
[
179+
{
180+
...Mocks.MockWorkspace,
181+
autostart_schedule: "30 9 * * 1-5",
182+
ttl: 7_200_000_000_000,
183+
},
184+
{
185+
sunday: false,
186+
monday: true,
187+
tuesday: true,
188+
wednesday: true,
189+
thursday: true,
190+
friday: true,
191+
saturday: false,
192+
startTime: "09:30",
193+
timezone: "",
194+
ttl: 2,
195+
},
196+
],
197+
198+
// Complex case: 4:20 1 3-4 6 Canada/Eastern for 8 hours
199+
[
200+
{
201+
...Mocks.MockWorkspace,
202+
autostart_schedule: "CRON_TZ=Canada/Eastern 20 16 * * 1,3-4,6",
203+
ttl: 28_800_000_000_000,
204+
},
205+
{
206+
sunday: false,
207+
monday: true,
208+
tuesday: false,
209+
wednesday: true,
210+
thursday: true,
211+
friday: false,
212+
saturday: true,
213+
startTime: "16:20",
214+
timezone: "Canada/Eastern",
215+
ttl: 8,
216+
},
217+
],
218+
])(`workspaceToInitialValues(%p) returns %p`, (workspace, formValues) => {
219+
expect(workspaceToInitialValues(workspace)).toEqual(formValues)
220+
})
221+
})
152222
})

site/src/pages/WorkspaceSchedulePage/WorkspaceSchedulePage.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
WorkspaceScheduleFormValues,
1010
} from "../../components/WorkspaceStats/WorkspaceScheduleForm"
1111
import { firstOrItem } from "../../util/array"
12+
import { dowToWeeklyFlag, extractTimezone, stripTimezone } from "../../util/schedule"
1213
import { workspaceSchedule } from "../../xServices/workspaceSchedule/workspaceScheduleXService"
1314

1415
export const formValuesToAutoStartRequest = (
@@ -81,6 +82,51 @@ export const formValuesToTTLRequest = (values: WorkspaceScheduleFormValues): Typ
8182
}
8283
}
8384

85+
export const workspaceToInitialValues = (workspace: TypesGen.Workspace): WorkspaceScheduleFormValues => {
86+
const schedule = workspace.autostart_schedule
87+
88+
if (!schedule) {
89+
return {
90+
sunday: false,
91+
monday: false,
92+
tuesday: false,
93+
wednesday: false,
94+
thursday: false,
95+
friday: false,
96+
saturday: false,
97+
startTime: "",
98+
timezone: "",
99+
ttl: 0,
100+
}
101+
}
102+
103+
const timezone = extractTimezone(schedule, "")
104+
const cronString = stripTimezone(schedule)
105+
106+
// parts has the following format: "mm HH * * dow"
107+
const parts = cronString.split(" ")
108+
109+
// -> we skip month and day-of-month
110+
const mm = parts[0]
111+
const HH = parts[1]
112+
const dow = parts[4]
113+
114+
const weeklyFlags = dowToWeeklyFlag(dow)
115+
116+
return {
117+
sunday: weeklyFlags[0],
118+
monday: weeklyFlags[1],
119+
tuesday: weeklyFlags[2],
120+
wednesday: weeklyFlags[3],
121+
thursday: weeklyFlags[4],
122+
friday: weeklyFlags[5],
123+
saturday: weeklyFlags[6],
124+
startTime: `${HH.padStart(2, "0")}:${mm.padStart(2, "0")}`,
125+
timezone,
126+
ttl: workspace.ttl ? workspace.ttl / (1_000_000 * 1000 * 60 * 60) : 0,
127+
}
128+
}
129+
84130
export const WorkspaceSchedulePage: React.FC = () => {
85131
const navigate = useNavigate()
86132
const { workspace: workspaceQueryParam } = useParams()

site/src/util/schedule.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { extractTimezone, stripTimezone } from "./schedule"
1+
import { dowToWeeklyFlag, extractTimezone, stripTimezone, WeeklyFlag } from "./schedule"
22

33
describe("util/schedule", () => {
44
describe("stripTimezone", () => {
@@ -20,4 +20,25 @@ describe("util/schedule", () => {
2020
expect(extractTimezone(input)).toBe(expected)
2121
})
2222
})
23+
24+
describe("dowToWeeklyFlag", () => {
25+
it.each<[string, WeeklyFlag]>([
26+
// All days
27+
["*", [true, true, true, true, true, true, true]],
28+
["1-7", [true, true, true, true, true, true, true]],
29+
30+
// Single number modulo 7
31+
["3", [false, false, false, true, false, false, false]],
32+
["0", [true, false, false, false, false, false, false]],
33+
["7", [true, false, false, false, false, false, false]],
34+
["8", [false, true, false, false, false, false, false]],
35+
36+
// Comma-separated Numbers, Ranges and Mixes
37+
["1,3,5", [false, true, false, true, false, true, false]],
38+
["1-2,4-5", [false, true, true, false, true, true, false]],
39+
["1,3-4,6", [false, true, false, true, true, false, true]],
40+
])(`dowToWeeklyFlag(%p) returns %p`, (dow, weeklyFlag) => {
41+
expect(dowToWeeklyFlag(dow)).toEqual(weeklyFlag)
42+
})
43+
})
2344
})

site/src/util/schedule.ts

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,83 @@ export const stripTimezone = (raw: string): string => {
1919

2020
/**
2121
* extractTimezone returns a leading timezone from a schedule string if one is
22-
* specified; otherwise DEFAULT_TIMEZONE
22+
* specified; otherwise the specified defaultTZ
2323
*/
24-
export const extractTimezone = (raw: string): string => {
24+
export const extractTimezone = (raw: string, defaultTZ = DEFAULT_TIMEZONE): string => {
2525
const matches = raw.match(/CRON_TZ=\S*\s/g)
2626

2727
if (matches && matches.length) {
2828
return matches[0].replace(/CRON_TZ=/, "").trim()
2929
} else {
30-
return DEFAULT_TIMEZONE
30+
return defaultTZ
3131
}
3232
}
33+
34+
/**
35+
* WeeklyFlag is an array represnting which days of the week are set or flagged
36+
*
37+
* @remarks
38+
*
39+
* A WeeklyFlag has an array size of 7 and should never have its size modified.
40+
* The 0th index is Sunday
41+
* The 6th index is Saturday
42+
*/
43+
export type WeeklyFlag = [boolean, boolean, boolean, boolean, boolean, boolean, boolean]
44+
45+
/**
46+
* dowToWeeklyFlag converts a dow cron string to a WeeklyFlag array.
47+
*
48+
* @example
49+
*
50+
* dowToWeeklyFlag("1") // [false, true, false, false, false, false, false]
51+
* dowToWeeklyFlag("1-5") // [false, true, true, true, true, true, false]
52+
* dowToWeeklyFlag("1,3-4,6") // [false, true, false, true, true, false, true]
53+
*/
54+
export const dowToWeeklyFlag = (dow: string): WeeklyFlag => {
55+
if (dow === "*") {
56+
return [true, true, true, true, true, true, true]
57+
}
58+
59+
const results: WeeklyFlag = [false, false, false, false, false, false, false]
60+
61+
const commaSeparatedRangeOrNum = dow.split(",")
62+
63+
for (const rangeOrNum of commaSeparatedRangeOrNum) {
64+
const flags = processRangeOrNum(rangeOrNum)
65+
66+
flags.forEach((value, idx) => {
67+
if (value) {
68+
results[idx] = true
69+
}
70+
})
71+
}
72+
73+
return results
74+
}
75+
76+
/**
77+
* processRangeOrNum is a helper for dowToWeeklyFlag. It processes a range or
78+
* number (modulo 7) into a Weeklyflag boolean array.
79+
*
80+
* @example
81+
*
82+
* processRangeOrNum("1") // [false, true, false, false, false, false, false]
83+
* processRangeOrNum("1-5") // [false, true, true, true, true, true, false]
84+
*/
85+
const processRangeOrNum = (rangeOrNum: string): WeeklyFlag => {
86+
const result: WeeklyFlag = [false, false, false, false, false, false, false]
87+
88+
const isRange = /^[0-9]-[0-9]$/.test(rangeOrNum)
89+
90+
if (isRange) {
91+
const [first, last] = rangeOrNum.split("-")
92+
93+
for (let i = Number(first); i <= Number(last); i++) {
94+
result[i % 7] = true
95+
}
96+
} else {
97+
result[Number(rangeOrNum) % 7] = true
98+
}
99+
100+
return result
101+
}

0 commit comments

Comments
 (0)