fix(coderd): show correct deletion time in dormancy notification (#26488)

The dormancy notification's "will be automatically deleted in X"
sentence rendered the dormancy threshold instead of the auto-delete
duration. A 30-day threshold rendered as "4 weeks" even when auto-delete
was 90 days; a 60-day threshold rendered as "1 month" with a 7-day
auto-delete. Render the countdown from the auto-delete setting, and skip
the deletion sentence entirely when auto-delete is disabled so the
notification no longer promises a deletion that will never happen.
This commit is contained in:
Zach
2026-06-18 20:17:37 +00:00
committed by GitHub
parent 992b1ffed1
commit 76fa1d4577
10 changed files with 344 additions and 31 deletions
+69
View File
@@ -4884,6 +4884,75 @@ func TestWorkspaceNotifications(t *testing.T) {
require.Contains(t, sent[0].Targets, workspace.ID)
require.Contains(t, sent[0].Targets, workspace.OrganizationID)
require.Contains(t, sent[0].Targets, workspace.OwnerID)
// Auto-delete is not configured on this template, so the body must
// omit the deletion timeline.
require.NotContains(t, sent[0].Labels, "timeTilDelete")
})
t.Run("InitiatorNotOwnerWithAutoDelete", func(t *testing.T) {
t.Parallel()
// Given
var (
notifyEnq = &notificationstest.FakeEnqueuer{}
// 35 days sits solidly inside humanize.Time's "1 month"
// bucket (between 30 and 60 days), so the rendered label is
// deterministic regardless of microsecond-level timing
// differences.
timeTilDormantAutoDelete = 35 * 24 * time.Hour
client = coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
NotificationsEnqueuer: notifyEnq,
// AGPL templateScheduleStore drops TimeTilDormantAutoDelete
// when Set runs. The mock propagates it into the template
// row so the UPDATE in UpdateWorkspaceDormantDeletingAt
// can compute deleting_at.
TemplateScheduleStore: schedule.MockTemplateScheduleStore{
SetFn: func(ctx context.Context, db database.Store, template database.Template, options schedule.TemplateScheduleOptions) (database.Template, error) {
template.TimeTilDormantAutoDelete = int64(options.TimeTilDormantAutoDelete)
return schedule.NewAGPLTemplateScheduleStore().Set(ctx, db, template, options)
},
GetFn: func(_ context.Context, _ database.Store, _ uuid.UUID) (schedule.TemplateScheduleOptions, error) {
return schedule.TemplateScheduleOptions{
UserAutostartEnabled: false,
UserAutostopEnabled: true,
DefaultTTL: 0,
AutostopRequirement: schedule.TemplateAutostopRequirement{},
TimeTilDormantAutoDelete: timeTilDormantAutoDelete,
}, nil
},
},
})
user = coderdtest.CreateFirstUser(t, client)
memberClient, _ = coderdtest.CreateAnotherUser(t, client, user.OrganizationID, rbac.RoleOwner())
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
ctr.TimeTilDormantAutoDeleteMillis = ptr.Ref[int64](timeTilDormantAutoDelete.Milliseconds())
})
workspace = coderdtest.CreateWorkspace(t, client, template.ID)
_ = coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
)
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
t.Cleanup(cancel)
// When
err := memberClient.UpdateWorkspaceDormancy(ctx, workspace.ID, codersdk.UpdateWorkspaceDormancy{
Dormant: true,
})
// Then
require.NoError(t, err, "mark workspace as dormant")
sent := notifyEnq.Sent(notificationstest.WithTemplateID(notifications.TemplateWorkspaceDormant))
require.Len(t, sent, 1)
require.Contains(t, sent[0].Labels, "timeTilDelete")
require.Contains(t, sent[0].Labels["timeTilDelete"], "1 month",
"timeTilDelete must humanize the workspace's deleting_at, got %q",
sent[0].Labels["timeTilDelete"])
require.NotContains(t, sent[0].Labels["timeTilDelete"], "ago",
"timeTilDelete must be a future timestamp, got %q",
sent[0].Labels["timeTilDelete"])
})
t.Run("InitiatorIsOwner", func(t *testing.T) {