Skip to content

Commit 6ebe9b0

Browse files
authored
feat: add UI for autostart workspace days (#10263)
* feat: add ui for selecting auto start days
1 parent 5a90228 commit 6ebe9b0

File tree

8 files changed

+231
-4
lines changed

8 files changed

+231
-4
lines changed

coderd/templates.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
610610
req.DefaultTTLMillis == time.Duration(template.DefaultTTL).Milliseconds() &&
611611
req.MaxTTLMillis == time.Duration(template.MaxTTL).Milliseconds() &&
612612
autostopRequirementDaysOfWeekParsed == scheduleOpts.AutostopRequirement.DaysOfWeek &&
613+
autostartRequirementDaysOfWeekParsed == scheduleOpts.AutostartRequirement.DaysOfWeek &&
613614
req.AutostopRequirement.Weeks == scheduleOpts.AutostopRequirement.Weeks &&
614615
req.FailureTTLMillis == time.Duration(template.FailureTTL).Milliseconds() &&
615616
req.TimeTilDormantMillis == time.Duration(template.TimeTilDormant).Milliseconds() &&
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { FC } from "react";
2+
import { TemplateAutostartRequirementDaysValue } from "utils/schedule";
3+
import Button from "@mui/material/Button";
4+
import { Stack } from "components/Stack/Stack";
5+
import FormHelperText from "@mui/material/FormHelperText";
6+
7+
export interface TemplateScheduleAutostartProps {
8+
allow_user_autostart?: boolean;
9+
autostart_requirement_days_of_week: TemplateAutostartRequirementDaysValue[];
10+
isSubmitting: boolean;
11+
onChange: (newDaysOfWeek: TemplateAutostartRequirementDaysValue[]) => void;
12+
}
13+
14+
export const TemplateScheduleAutostart: FC<
15+
React.PropsWithChildren<TemplateScheduleAutostartProps>
16+
> = ({
17+
autostart_requirement_days_of_week,
18+
isSubmitting,
19+
allow_user_autostart,
20+
onChange,
21+
}) => {
22+
return (
23+
<Stack
24+
direction="column"
25+
width="100%"
26+
alignItems="center"
27+
css={{
28+
marginBottom: "20px",
29+
}}
30+
>
31+
<Stack
32+
direction="row"
33+
css={{
34+
width: "100%",
35+
}}
36+
spacing={0}
37+
alignItems="baseline"
38+
justifyContent="center"
39+
>
40+
{(
41+
[
42+
{ value: "monday", key: "Mon" },
43+
{ value: "tuesday", key: "Tue" },
44+
{ value: "wednesday", key: "Wed" },
45+
{ value: "thursday", key: "Thu" },
46+
{ value: "friday", key: "Fri" },
47+
{ value: "saturday", key: "Sat" },
48+
{ value: "sunday", key: "Sun" },
49+
] as {
50+
value: TemplateAutostartRequirementDaysValue;
51+
key: string;
52+
}[]
53+
).map((day) => (
54+
<Button
55+
key={day.key}
56+
css={{
57+
borderRadius: "0px",
58+
}}
59+
// TODO: Adding a background color would also help
60+
color={
61+
autostart_requirement_days_of_week.includes(day.value)
62+
? "primary"
63+
: "secondary"
64+
}
65+
disabled={isSubmitting || !allow_user_autostart}
66+
onClick={() => {
67+
if (!autostart_requirement_days_of_week.includes(day.value)) {
68+
onChange(autostart_requirement_days_of_week.concat(day.value));
69+
} else {
70+
onChange(
71+
autostart_requirement_days_of_week.filter(
72+
(obj) => obj !== day.value,
73+
),
74+
);
75+
}
76+
}}
77+
>
78+
{day.key}
79+
</Button>
80+
))}
81+
</Stack>
82+
<FormHelperText>
83+
<AutostartRequirementDaysHelperText
84+
allowed={allow_user_autostart}
85+
days={autostart_requirement_days_of_week}
86+
/>
87+
</FormHelperText>
88+
</Stack>
89+
);
90+
};
91+
92+
export const sortedDays = [
93+
"monday",
94+
"tuesday",
95+
"wednesday",
96+
"thursday",
97+
"friday",
98+
"saturday",
99+
"sunday",
100+
] as TemplateAutostartRequirementDaysValue[];
101+
102+
const AutostartRequirementDaysHelperText: FC<{
103+
allowed?: boolean;
104+
days: TemplateAutostartRequirementDaysValue[];
105+
}> = ({ allowed, days: unsortedDays }) => {
106+
if (!allowed) {
107+
return <span>Workspaces are not allowed to auto start.</span>;
108+
}
109+
// Sort the days
110+
const days = unsortedDays.sort(
111+
(a, b) => sortedDays.indexOf(a) - sortedDays.indexOf(b),
112+
);
113+
114+
let daymsg = `Workspaces can autostart on ${days.join(", ")}.`;
115+
if (days.length === 7) {
116+
// If every day is allowed, no more explaining is needed.
117+
return <span>Workspaces are allowed to auto start on any day.</span>;
118+
}
119+
if (days.length === 0) {
120+
return (
121+
<span>
122+
Workspaces will never auto start. This is effectively the same as
123+
disabling autostart.
124+
</span>
125+
);
126+
}
127+
if (
128+
days.length === 5 &&
129+
!days.includes("saturday") &&
130+
!days.includes("sunday")
131+
) {
132+
daymsg = "Workspaces will never auto start on the weekends.";
133+
}
134+
return (
135+
<span>{daymsg} These days are relative to the user&apos;s timezone.</span>
136+
);
137+
};

site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,14 @@ import {
4242
AutostopRequirementWeeksHelperText,
4343
} from "pages/TemplateSettingsPage/TemplateSchedulePage/AutostopRequirementHelperText";
4444
import MenuItem from "@mui/material/MenuItem";
45-
import { TemplateAutostopRequirementDaysValue } from "utils/schedule";
45+
import {
46+
TemplateAutostartRequirementDaysValue,
47+
TemplateAutostopRequirementDaysValue,
48+
} from "utils/schedule";
49+
import {
50+
TemplateScheduleAutostart,
51+
sortedDays,
52+
} from "components/TemplateScheduleAutostart/TemplateScheduleAutostart";
4653

4754
const MAX_DESCRIPTION_CHAR_LIMIT = 128;
4855
const MAX_TTL_DAYS = 30;
@@ -54,6 +61,7 @@ export interface CreateTemplateData {
5461
icon: string;
5562
default_ttl_hours: number;
5663
max_ttl_hours: number;
64+
autostart_requirement_days_of_week: TemplateAutostartRequirementDaysValue[];
5765
autostop_requirement_days_of_week: TemplateAutostopRequirementDaysValue;
5866
autostop_requirement_weeks: number;
5967
allow_user_autostart: boolean;
@@ -88,6 +96,7 @@ const validationSchema = Yup.object({
8896
),
8997
autostop_requirement_days_of_week: Yup.string().required(),
9098
autostop_requirement_weeks: Yup.number().required().min(1).max(16),
99+
autostart_requirement_days_of_week: Yup.array().of(Yup.string()).required(),
91100
});
92101

93102
const defaultInitialValues: CreateTemplateData = {
@@ -110,6 +119,7 @@ const defaultInitialValues: CreateTemplateData = {
110119
// user's timezone.
111120
autostop_requirement_days_of_week: "sunday",
112121
autostop_requirement_weeks: 1,
122+
autostart_requirement_days_of_week: sortedDays,
113123
allow_user_cancel_workspace_jobs: false,
114124
allow_user_autostart: false,
115125
allow_user_autostop: false,
@@ -434,6 +444,25 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = (props) => {
434444
</strong>
435445
</Stack>
436446
</Stack>
447+
448+
{allowAdvancedScheduling && (
449+
<TemplateScheduleAutostart
450+
allow_user_autostart={form.values.allow_user_autostart}
451+
autostart_requirement_days_of_week={
452+
form.values.autostart_requirement_days_of_week
453+
}
454+
isSubmitting={isSubmitting}
455+
onChange={async (
456+
newDaysOfWeek: TemplateAutostartRequirementDaysValue[],
457+
) => {
458+
await form.setFieldValue(
459+
"autostart_requirement_days_of_week",
460+
newDaysOfWeek,
461+
);
462+
}}
463+
/>
464+
)}
465+
437466
<Stack direction="row" alignItems="center">
438467
<Checkbox
439468
id="allow-user-autostop"

site/src/pages/CreateTemplatePage/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const newTemplate = (formData: CreateTemplateData) => {
1717
max_ttl_hours,
1818
parameter_values_by_name,
1919
allow_everyone_group_access,
20+
autostart_requirement_days_of_week,
2021
autostop_requirement_days_of_week,
2122
autostop_requirement_weeks,
2223
...safeTemplateData
@@ -33,6 +34,9 @@ export const newTemplate = (formData: CreateTemplateData) => {
3334
),
3435
weeks: formData.autostop_requirement_weeks,
3536
},
37+
autostart_requirement: {
38+
days_of_week: autostart_requirement_days_of_week,
39+
},
3640
};
3741
};
3842

site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import { FC, ChangeEvent, useState, useEffect } from "react";
99
import { Template, UpdateTemplateMeta } from "api/typesGenerated";
1010
import { getFormHelpers } from "utils/formUtils";
1111
import { docs } from "utils/docs";
12-
import { calculateAutostopRequirementDaysValue } from "utils/schedule";
12+
import {
13+
TemplateAutostartRequirementDaysValue,
14+
calculateAutostopRequirementDaysValue,
15+
} from "utils/schedule";
1316
import {
1417
FormSection,
1518
HorizontalForm,
@@ -36,6 +39,7 @@ import {
3639
convertAutostopRequirementDaysValue,
3740
} from "./AutostopRequirementHelperText";
3841
import { useTheme } from "@emotion/react";
42+
import { TemplateScheduleAutostart } from "components/TemplateScheduleAutostart/TemplateScheduleAutostart";
3943

4044
const MS_HOUR_CONVERSION = 3600000;
4145
const MS_DAY_CONVERSION = 86400000;
@@ -95,6 +99,8 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
9599
? template.autostop_requirement.weeks
96100
: 1
97101
: 1,
102+
autostart_requirement_days_of_week: template.autostart_requirement
103+
.days_of_week as TemplateAutostartRequirementDaysValue[],
98104

99105
allow_user_autostart: template.allow_user_autostart,
100106
allow_user_autostop: template.allow_user_autostop,
@@ -215,6 +221,9 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
215221
),
216222
weeks: autostop_requirement_weeks,
217223
},
224+
autostart_requirement: {
225+
days_of_week: form.values.autostart_requirement_days_of_week,
226+
},
218227

219228
allow_user_autostart: form.values.allow_user_autostart,
220229
allow_user_autostop: form.values.allow_user_autostop,
@@ -430,6 +439,24 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
430439
</strong>
431440
</Stack>
432441
</Stack>
442+
{allowAdvancedScheduling && (
443+
<TemplateScheduleAutostart
444+
allow_user_autostart={form.values.allow_user_autostart}
445+
autostart_requirement_days_of_week={
446+
form.values.autostart_requirement_days_of_week
447+
}
448+
isSubmitting={isSubmitting}
449+
onChange={async (
450+
newDaysOfWeek: TemplateAutostartRequirementDaysValue[],
451+
) => {
452+
await form.setFieldValue(
453+
"autostart_requirement_days_of_week",
454+
newDaysOfWeek,
455+
);
456+
}}
457+
/>
458+
)}
459+
433460
<Stack direction="row" alignItems="center">
434461
<Checkbox
435462
id="allow-user-autostop"
@@ -623,4 +650,7 @@ const styles = {
623650
ttlFields: {
624651
width: "100%",
625652
},
653+
dayButtons: {
654+
borderRadius: "0px",
655+
},
626656
};

site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.test.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ const validFormValues: TemplateScheduleFormValues = {
2626
failure_cleanup_enabled: false,
2727
inactivity_cleanup_enabled: false,
2828
dormant_autodeletion_cleanup_enabled: false,
29+
autostart_requirement_days_of_week: [
30+
"monday",
31+
"tuesday",
32+
"wednesday",
33+
"thursday",
34+
"friday",
35+
"saturday",
36+
"sunday",
37+
],
2938
};
3039

3140
const renderTemplateSchedulePage = async () => {

site/src/pages/TemplateSettingsPage/TemplateSchedulePage/formHelpers.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
import { UpdateTemplateMeta } from "api/typesGenerated";
2-
import { TemplateAutostopRequirementDaysValue } from "utils/schedule";
2+
import {
3+
TemplateAutostartRequirementDaysValue,
4+
TemplateAutostopRequirementDaysValue,
5+
} from "utils/schedule";
36
import * as Yup from "yup";
47

58
export interface TemplateScheduleFormValues
6-
extends Omit<UpdateTemplateMeta, "autostop_requirement"> {
9+
extends Omit<
10+
UpdateTemplateMeta,
11+
"autostop_requirement" | "autostart_requirement"
12+
> {
13+
autostart_requirement_days_of_week: TemplateAutostartRequirementDaysValue[];
714
autostop_requirement_days_of_week: TemplateAutostopRequirementDaysValue;
815
autostop_requirement_weeks: number;
916
failure_cleanup_enabled: boolean;
@@ -75,5 +82,6 @@ export const getValidationSchema = (): Yup.AnyObjectSchema =>
7582
allow_user_autostop: Yup.boolean(),
7683

7784
autostop_requirement_days_of_week: Yup.string().required(),
85+
autostart_requirement_days_of_week: Yup.array().of(Yup.string()).required(),
7886
autostop_requirement_weeks: Yup.number().required().min(1).max(16),
7987
});

site/src/utils/schedule.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,15 @@ export const quietHoursDisplay = (
211211
return display;
212212
};
213213

214+
export type TemplateAutostartRequirementDaysValue =
215+
| "monday"
216+
| "tuesday"
217+
| "wednesday"
218+
| "thursday"
219+
| "friday"
220+
| "saturday"
221+
| "sunday";
222+
214223
export type TemplateAutostopRequirementDaysValue =
215224
| "off"
216225
| "daily"

0 commit comments

Comments
 (0)