Skip to content

Commit b47e63f

Browse files
committed
Add ui to adjust default activty bump
1 parent 4e295f8 commit b47e63f

File tree

10 files changed

+126
-13
lines changed

10 files changed

+126
-13
lines changed

coderd/database/modelqueries.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate
8787
&i.AutostopRequirementWeeks,
8888
&i.AutostartBlockDaysOfWeek,
8989
&i.RequireActiveVersion,
90+
&i.DefaultTTLBump,
9091
&i.CreatedByAvatarURL,
9192
&i.CreatedByUsername,
9293
); err != nil {
@@ -247,6 +248,7 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa
247248
&i.DormantAt,
248249
&i.DeletingAt,
249250
&i.AutomaticUpdates,
251+
&i.TtlBump,
250252
&i.TemplateName,
251253
&i.TemplateVersionID,
252254
&i.TemplateVersionName,

enterprise/coderd/schedule/template.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S
108108
tpl.AutostopRequirementWeeks = 1
109109
}
110110

111-
if int64(opts.DefaultTTL) == tpl.DefaultTTL &&
111+
if int64(opts.DefaultTTLBump) == tpl.DefaultTTLBump &&
112+
int64(opts.DefaultTTL) == tpl.DefaultTTL &&
112113
int64(opts.MaxTTL) == tpl.MaxTTL &&
113114
int16(opts.AutostopRequirement.DaysOfWeek) == tpl.AutostopRequirementDaysOfWeek &&
114115
opts.AutostartRequirement.DaysOfWeek == tpl.AutostartAllowedDays() &&

enterprise/coderd/templates_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/coder/coder/v2/coderd/coderdtest"
1515
"github.com/coder/coder/v2/coderd/database"
1616
"github.com/coder/coder/v2/coderd/rbac"
17+
"github.com/coder/coder/v2/coderd/util/ptr"
1718
"github.com/coder/coder/v2/codersdk"
1819
"github.com/coder/coder/v2/cryptorand"
1920
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
@@ -89,6 +90,33 @@ func TestTemplates(t *testing.T) {
8990
require.Contains(t, apiErr.Validations[0].Detail, "time until shutdown must be less than or equal to the template's maximum TTL")
9091
})
9192

93+
t.Run("PatchTTLBump", func(t *testing.T) {
94+
t.Parallel()
95+
rootClient, user := coderdenttest.New(t, &coderdenttest.Options{
96+
Options: &coderdtest.Options{},
97+
LicenseOptions: &coderdenttest.LicenseOptions{
98+
Features: license.Features{
99+
codersdk.FeatureAdvancedTemplateScheduling: 1,
100+
},
101+
},
102+
})
103+
client, _ := coderdtest.CreateAnotherUser(t, rootClient, user.OrganizationID, rbac.RoleTemplateAdmin())
104+
105+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
106+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
107+
ctr.DefaultTTLMillis = ptr.Ref(5 * time.Hour.Milliseconds())
108+
})
109+
110+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
111+
defer cancel()
112+
exp := 10 * time.Hour.Milliseconds()
113+
found, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
114+
DefaultTTLBumpMillis: exp,
115+
})
116+
require.NoError(t, err)
117+
require.Equal(t, exp, found.DefaultTTLBumpMillis)
118+
})
119+
92120
t.Run("BlockDisablingAutoOffWithMaxTTL", func(t *testing.T) {
93121
t.Parallel()
94122
client, user := coderdenttest.New(t, &coderdenttest.Options{

site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export interface CreateTemplateData {
5959
display_name: string;
6060
description: string;
6161
icon: string;
62+
default_ttl_bump_hours: number;
6263
default_ttl_hours: number;
6364
max_ttl_hours: number;
6465
autostart_requirement_days_of_week: TemplateAutostartRequirementDaysValue[];
@@ -105,6 +106,7 @@ const defaultInitialValues: CreateTemplateData = {
105106
description: "",
106107
icon: "",
107108
default_ttl_hours: 24,
109+
default_ttl_bump_hours: 0,
108110
// max_ttl is an enterprise-only feature, and the server ignores the value if
109111
// you are not licensed. We hide the form value based on entitlements.
110112
//

site/src/pages/CreateTemplatePage/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const provisioner: ProvisionerType =
1414
export const newTemplate = (formData: CreateTemplateData) => {
1515
const {
1616
default_ttl_hours,
17+
default_ttl_bump_hours,
1718
max_ttl_hours,
1819
parameter_values_by_name,
1920
allow_everyone_group_access,
@@ -27,6 +28,7 @@ export const newTemplate = (formData: CreateTemplateData) => {
2728
...safeTemplateData,
2829
disable_everyone_group_access: !formData.allow_everyone_group_access,
2930
default_ttl_ms: formData.default_ttl_hours * 60 * 60 * 1000, // Convert hours to ms
31+
default_ttl_bump_ms: formData.default_ttl_bump_hours * 60 * 60 * 1000, // Convert hours to ms
3032
max_ttl_ms: formData.max_ttl_hours * 60 * 60 * 1000, // Convert hours to ms
3133
autostop_requirement: {
3234
days_of_week: calculateAutostopRequirementDaysValue(

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,35 @@ export const DefaultTTLHelperText = (props: { ttl?: number }) => {
2121
);
2222
};
2323

24+
export const DefaultTTLBumpHelperText = (props: {
25+
ttl?: number;
26+
ttl_bump?: number;
27+
}) => {
28+
const { ttl_bump = 0 } = props;
29+
const { ttl = 0 } = props;
30+
31+
// Error will show once field is considered touched
32+
if (ttl_bump < 0) {
33+
return null;
34+
}
35+
36+
if (ttl_bump === 0) {
37+
return (
38+
<span>
39+
Workspace lifetime will default to being extended by the default
40+
autostop duration of {ttl} {hours(ttl)} from workspace activity.
41+
</span>
42+
);
43+
}
44+
45+
return (
46+
<span>
47+
Workspace lifetime will be extended by {ttl_bump} {hours(ttl_bump)} from
48+
workspace activity.
49+
</span>
50+
);
51+
};
52+
2453
export const MaxTTLHelperText = (props: { ttl?: number }) => {
2554
const { ttl = 0 } = props;
2655

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

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
import { TemplateScheduleFormValues, getValidationSchema } from "./formHelpers";
2828
import {
2929
DefaultTTLHelperText,
30+
DefaultTTLBumpHelperText,
3031
DormancyAutoDeletionTTLHelperText,
3132
DormancyTTLHelperText,
3233
FailureTTLHelperText,
@@ -76,6 +77,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
7677
initialValues: {
7778
// on display, convert from ms => hours
7879
default_ttl_ms: template.default_ttl_ms / MS_HOUR_CONVERSION,
80+
default_ttl_bump_ms: template.default_ttl_bump_ms / MS_HOUR_CONVERSION,
7981
// the API ignores these values, but to avoid tripping up validation set
8082
// it to zero if the user can't set the field.
8183
max_ttl_ms: allowAdvancedScheduling
@@ -205,6 +207,9 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
205207
default_ttl_ms: form.values.default_ttl_ms
206208
? form.values.default_ttl_ms * MS_HOUR_CONVERSION
207209
: undefined,
210+
default_ttl_bump_ms: form.values.default_ttl_bump_ms
211+
? form.values.default_ttl_bump_ms * MS_HOUR_CONVERSION
212+
: undefined,
208213
max_ttl_ms: form.values.max_ttl_ms
209214
? form.values.max_ttl_ms * MS_HOUR_CONVERSION
210215
: undefined,
@@ -327,17 +332,34 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
327332
description="Define when workspaces created from this template are stopped."
328333
>
329334
<Stack direction="row" css={styles.ttlFields}>
330-
<TextField
331-
{...getFieldHelpers(
332-
"default_ttl_ms",
333-
<DefaultTTLHelperText ttl={form.values.default_ttl_ms} />,
334-
)}
335-
disabled={isSubmitting}
336-
fullWidth
337-
inputProps={{ min: 0, step: 1 }}
338-
label="Default autostop (hours)"
339-
type="number"
340-
/>
335+
<Stack direction="column">
336+
<TextField
337+
{...getFieldHelpers(
338+
"default_ttl_ms",
339+
<DefaultTTLHelperText ttl={form.values.default_ttl_ms} />,
340+
)}
341+
disabled={isSubmitting}
342+
fullWidth
343+
inputProps={{ min: 0, step: 1 }}
344+
label="Default autostop (hours)"
345+
type="number"
346+
/>
347+
348+
<TextField
349+
{...getFieldHelpers(
350+
"default_ttl_bump_ms",
351+
<DefaultTTLBumpHelperText
352+
ttl={form.values.default_ttl_ms}
353+
ttl_bump={form.values.default_ttl_bump_ms}
354+
/>,
355+
)}
356+
disabled={isSubmitting}
357+
fullWidth
358+
inputProps={{ min: 0, step: 1 }}
359+
label="Default activity bump (hours)"
360+
type="number"
361+
/>
362+
</Stack>
341363

342364
{!allowAutostopRequirement && (
343365
<TextField

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import TemplateSchedulePage from "./TemplateSchedulePage";
1515

1616
const validFormValues: TemplateScheduleFormValues = {
1717
default_ttl_ms: 1,
18+
default_ttl_bump_ms: 0,
1819
max_ttl_ms: 2,
1920
failure_ttl_ms: 7,
2021
time_til_dormant_ms: 180,
@@ -57,6 +58,7 @@ type FillAndSubmitConfig = {
5758

5859
const fillAndSubmitForm = async ({
5960
default_ttl_ms,
61+
default_ttl_bump_ms,
6062
max_ttl_ms,
6163
failure_ttl_ms,
6264
time_til_dormant_ms,
@@ -72,6 +74,14 @@ const fillAndSubmitForm = async ({
7274
await user.type(defaultTtlField, default_ttl_ms.toString());
7375
}
7476

77+
if (default_ttl_bump_ms) {
78+
const defaultTtlBumpField = await screen.findByLabelText(
79+
"Default activity bump (hours)",
80+
);
81+
await user.clear(defaultTtlBumpField);
82+
await user.type(defaultTtlBumpField, default_ttl_bump_ms.toString());
83+
}
84+
7585
if (max_ttl_ms) {
7686
const maxTtlField = await screen.findByLabelText("Max lifetime (hours)");
7787

@@ -240,6 +250,16 @@ describe("TemplateSchedulePage", () => {
240250
);
241251
});
242252

253+
it("allows a default ttl bump of 1 hour", () => {
254+
const values: TemplateScheduleFormValues = {
255+
...validFormValues,
256+
default_ttl_ms: 24 * 7,
257+
default_ttl_bump_ms: 1,
258+
};
259+
const validate = () => getValidationSchema().validateSync(values);
260+
expect(validate).not.toThrowError();
261+
});
262+
243263
it("allows a failure ttl of 7 days", () => {
244264
const values: TemplateScheduleFormValues = {
245265
...validFormValues,

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ export const getValidationSchema = (): Yup.AnyObjectSchema =>
2929
24 * MAX_TTL_DAYS /* 30 days in hours */,
3030
"Please enter a limit that is less than or equal to 720 hours (30 days).",
3131
),
32+
default_ttl_bump_ms: Yup.number()
33+
.integer()
34+
.min(0, "Default activity bump duration must not be less than 0.")
35+
.max(
36+
24 * MAX_TTL_DAYS /* 30 days in hours */,
37+
"Please enter a limit that is less than or equal to 720 hours (30 days).",
38+
),
3239
max_ttl_ms: Yup.number()
3340
.integer()
3441
.min(0, "Maximum time until autostop must not be less than 0.")

site/src/testHelpers/entities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ export const MockTemplate: TypesGen.Template = {
441441
},
442442
description: "This is a test description.",
443443
default_ttl_ms: 24 * 60 * 60 * 1000,
444-
default_ttl_bump_ms: 0,
444+
default_ttl_bump_ms: 2,
445445
max_ttl_ms: 2 * 24 * 60 * 60 * 1000,
446446
autostop_requirement: {
447447
days_of_week: [],

0 commit comments

Comments
 (0)