From 630dd87caa8be06628580b870839e6b4e9b87652 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Fri, 26 Jul 2024 17:17:54 +0000 Subject: [PATCH 01/14] Test notification templates and update them Dormancy templates have been updated for better readability --- coderd/autobuild/lifecycle_executor.go | 39 ++++---- coderd/database/dbauthz/dbauthz.go | 7 ++ coderd/database/dbmem/dbmem.go | 15 +++ coderd/database/dbmetrics/dbmetrics.go | 7 ++ coderd/database/dbmock/dbmock.go | 15 +++ ...te_dormancy_notification_template.down.sql | 11 +++ ...date_dormancy_notification_template.up.sql | 16 ++++ coderd/database/querier.go | 1 + coderd/database/queries.sql.go | 18 ++++ coderd/database/queries/notifications.sql | 2 + coderd/dormancy/notifications.go | 75 --------------- coderd/notifications/notifications_test.go | 91 +++++++++++++++++++ .../provisionerdserver/provisionerdserver.go | 6 +- coderd/workspaces.go | 42 ++++++--- enterprise/coderd/schedule/template.go | 24 +++-- 15 files changed, 252 insertions(+), 117 deletions(-) create mode 100644 coderd/database/migrations/000232_update_dormancy_notification_template.down.sql create mode 100644 coderd/database/migrations/000232_update_dormancy_notification_template.up.sql delete mode 100644 coderd/dormancy/notifications.go diff --git a/coderd/autobuild/lifecycle_executor.go b/coderd/autobuild/lifecycle_executor.go index 082ee0feedfcf..ac3235f7345c7 100644 --- a/coderd/autobuild/lifecycle_executor.go +++ b/coderd/autobuild/lifecycle_executor.go @@ -19,7 +19,6 @@ import ( "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/database/provisionerjobs" "github.com/coder/coder/v2/coderd/database/pubsub" - "github.com/coder/coder/v2/coderd/dormancy" "github.com/coder/coder/v2/coderd/notifications" "github.com/coder/coder/v2/coderd/schedule" "github.com/coder/coder/v2/coderd/wsbuilder" @@ -145,10 +144,11 @@ func (e *Executor) runOnce(t time.Time) Stats { var ( job *database.ProvisionerJob auditLog *auditParams - dormantNotification *dormancy.WorkspaceDormantNotification + shouldNotifyDormancy bool nextBuild *database.WorkspaceBuild activeTemplateVersion database.TemplateVersion ws database.Workspace + tmpl database.Template didAutoUpdate bool ) err := e.db.InTx(func(tx database.Store) error { @@ -182,17 +182,17 @@ func (e *Executor) runOnce(t time.Time) Stats { return xerrors.Errorf("get template scheduling options: %w", err) } - template, err := tx.GetTemplateByID(e.ctx, ws.TemplateID) + tmpl, err = tx.GetTemplateByID(e.ctx, ws.TemplateID) if err != nil { return xerrors.Errorf("get template by ID: %w", err) } - activeTemplateVersion, err = tx.GetTemplateVersionByID(e.ctx, template.ActiveVersionID) + activeTemplateVersion, err = tx.GetTemplateVersionByID(e.ctx, tmpl.ActiveVersionID) if err != nil { return xerrors.Errorf("get active template version by ID: %w", err) } - accessControl := (*(e.accessControlStore.Load())).GetTemplateAccessControl(template) + accessControl := (*(e.accessControlStore.Load())).GetTemplateAccessControl(tmpl) nextTransition, reason, err := getNextTransition(user, ws, latestBuild, latestJob, templateSchedule, currentTick) if err != nil { @@ -215,7 +215,7 @@ func (e *Executor) runOnce(t time.Time) Stats { log.Debug(e.ctx, "autostarting with active version") builder = builder.ActiveVersion() - if latestBuild.TemplateVersionID != template.ActiveVersionID { + if latestBuild.TemplateVersionID != tmpl.ActiveVersionID { // control flag to know if the workspace was auto-updated, // so the lifecycle executor can notify the user didAutoUpdate = true @@ -248,12 +248,7 @@ func (e *Executor) runOnce(t time.Time) Stats { return xerrors.Errorf("update workspace dormant deleting at: %w", err) } - dormantNotification = &dormancy.WorkspaceDormantNotification{ - Workspace: ws, - Initiator: "autobuild", - Reason: "breached the template's threshold for inactivity", - CreatedBy: "lifecycleexecutor", - } + shouldNotifyDormancy = true log.Info(e.ctx, "dormant workspace", slog.F("last_used_at", ws.LastUsedAt), @@ -325,14 +320,24 @@ func (e *Executor) runOnce(t time.Time) Stats { return xerrors.Errorf("post provisioner job to pubsub: %w", err) } } - if dormantNotification != nil { - _, err = dormancy.NotifyWorkspaceDormant( + if shouldNotifyDormancy { + _, err = e.notificationsEnqueuer.Enqueue( e.ctx, - e.notificationsEnqueuer, - *dormantNotification, + ws.OwnerID, + notifications.TemplateWorkspaceDormant, + map[string]string{ + "name": ws.Name, + "reason": "prolonged inactivity, exceeding the dormancy threshold", + "timeTilFormant": time.Duration(tmpl.TimeTilDormant).String(), + }, + "api", + ws.ID, + ws.OwnerID, + ws.TemplateID, + ws.OrganizationID, ) if err != nil { - log.Warn(e.ctx, "failed to notify of workspace marked as dormant", slog.Error(err), slog.F("workspace_id", dormantNotification.Workspace.ID)) + log.Warn(e.ctx, "failed to notify of workspace marked as dormant", slog.Error(err), slog.F("workspace_id", ws.ID)) } } return nil diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index d12b9aba23863..5857e4e13a337 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1484,6 +1484,13 @@ func (q *querier) GetNotificationMessagesByStatus(ctx context.Context, arg datab return q.db.GetNotificationMessagesByStatus(ctx, arg) } +func (q *querier) GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (database.NotificationTemplate, error) { + if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil { + return database.NotificationTemplate{}, err + } + return q.db.GetNotificationTemplateByID(ctx, id) +} + func (q *querier) GetNotificationsSettings(ctx context.Context) (string, error) { // No authz checks return q.db.GetNotificationsSettings(ctx) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 8d1088616f6bc..36d93d80de4b2 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -65,6 +65,7 @@ func New() database.Store { files: make([]database.File, 0), gitSSHKey: make([]database.GitSSHKey, 0), notificationMessages: make([]database.NotificationMessage, 0), + notificationTemplates: make([]database.NotificationTemplate, 0), parameterSchemas: make([]database.ParameterSchema, 0), provisionerDaemons: make([]database.ProvisionerDaemon, 0), workspaceAgents: make([]database.WorkspaceAgent, 0), @@ -160,6 +161,7 @@ type data struct { jfrogXRayScans []database.JfrogXrayScan licenses []database.License notificationMessages []database.NotificationMessage + notificationTemplates []database.NotificationTemplate oauth2ProviderApps []database.OAuth2ProviderApp oauth2ProviderAppSecrets []database.OAuth2ProviderAppSecret oauth2ProviderAppCodes []database.OAuth2ProviderAppCode @@ -2817,6 +2819,19 @@ func (q *FakeQuerier) GetNotificationMessagesByStatus(_ context.Context, arg dat return out, nil } +func (q *FakeQuerier) GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (database.NotificationTemplate, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + for _, template := range q.notificationTemplates { + if template.ID == id { + return template, nil + } + } + + return database.NotificationTemplate{}, sql.ErrNoRows +} + func (q *FakeQuerier) GetNotificationsSettings(_ context.Context) (string, error) { q.mutex.RLock() defer q.mutex.RUnlock() diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index f987d0505653b..9ba3577ee6034 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -746,6 +746,13 @@ func (m metricsStore) GetNotificationMessagesByStatus(ctx context.Context, arg d return r0, r1 } +func (m metricsStore) GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (database.NotificationTemplate, error) { + start := time.Now() + r0, r1 := m.s.GetNotificationTemplateByID(ctx, id) + m.queryLatencies.WithLabelValues("GetNotificationTemplateByID").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) GetNotificationsSettings(ctx context.Context) (string, error) { start := time.Now() r0, r1 := m.s.GetNotificationsSettings(ctx) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 78cd95a69cde5..add0ef222ebfa 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -1480,6 +1480,21 @@ func (mr *MockStoreMockRecorder) GetNotificationMessagesByStatus(arg0, arg1 any) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotificationMessagesByStatus", reflect.TypeOf((*MockStore)(nil).GetNotificationMessagesByStatus), arg0, arg1) } +// GetNotificationTemplateByID mocks base method. +func (m *MockStore) GetNotificationTemplateByID(arg0 context.Context, arg1 uuid.UUID) (database.NotificationTemplate, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNotificationTemplateByID", arg0, arg1) + ret0, _ := ret[0].(database.NotificationTemplate) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetNotificationTemplateByID indicates an expected call of GetNotificationTemplateByID. +func (mr *MockStoreMockRecorder) GetNotificationTemplateByID(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotificationTemplateByID", reflect.TypeOf((*MockStore)(nil).GetNotificationTemplateByID), arg0, arg1) +} + // GetNotificationsSettings mocks base method. func (m *MockStore) GetNotificationsSettings(arg0 context.Context) (string, error) { m.ctrl.T.Helper() diff --git a/coderd/database/migrations/000232_update_dormancy_notification_template.down.sql b/coderd/database/migrations/000232_update_dormancy_notification_template.down.sql new file mode 100644 index 0000000000000..763b8effa5602 --- /dev/null +++ b/coderd/database/migrations/000232_update_dormancy_notification_template.down.sql @@ -0,0 +1,11 @@ +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}}\n\n' || E'Your workspace **{{.Labels.name}}** has been marked as **dormant**.\n' || E'The specified reason was "**{{.Labels.reason}} (initiated by: {{ .Labels.initiator }}){{end}}**\n\n' || E'Dormancy refers to a workspace being unused for a defined length of time, and after it exceeds {{.Labels.dormancyHours}} hours of dormancy might be deleted.\n' || E'To activate your workspace again, simply use it as normal.', +WHERE + id = '0ea69165-ec14-4314-91f1-69566ac3c5a0'; + +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}}\n\n' || E'Your workspace **{{.Labels.name}}** has been marked for **deletion** after {{.Labels.dormancyHours}} hours of dormancy.\n' || E'The specified reason was "**{{.Labels.reason}}{{end}}**\n\n' || E'Dormancy refers to a workspace being unused for a defined length of time, and after it exceeds {{.Labels.dormancyHours}} hours of dormancy it will be deleted.\n' || E'To prevent your workspace from being deleted, simply use it as normal.' +WHERE + id = '51ce2fdf-c9ca-4be1-8d70-628674f9bc42'; diff --git a/coderd/database/migrations/000232_update_dormancy_notification_template.up.sql b/coderd/database/migrations/000232_update_dormancy_notification_template.up.sql new file mode 100644 index 0000000000000..bb81cbdf0788c --- /dev/null +++ b/coderd/database/migrations/000232_update_dormancy_notification_template.up.sql @@ -0,0 +1,16 @@ +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}}\n\n' || + E'Your workspace **{{.Labels.name}}** has been marked as [**dormant**](https://coder.com/docs/templates/schedule#dormancy-threshold-enterprise) because of {{.Labels.reason}}.\n' || + E'Dormant workspaces are [automatically deleted](https://coder.com/docs/templates/schedule#dormancy-auto-deletion-enterprise) after {{.Labels.timeTilDormant}} of inactivity.\n' || + E'To prevent deletion, use your workspace with the link below.' +WHERE + id = '0ea69165-ec14-4314-91f1-69566ac3c5a0'; + +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}}\n\n' || + E'Your workspace **{{.Labels.name}}** has been marked for **deletion** after {{.Labels.timeTilDormant}} of [dormancy](https://coder.com/docs/templates/schedule#dormancy-auto-deletion-enterprise) because of {{.Labels.reason}}.\n' || + E'To prevent deletion, use your workspace with the link below.' +WHERE + id = '51ce2fdf-c9ca-4be1-8d70-628674f9bc42'; diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 9d0494813e306..b5899d700cf15 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -162,6 +162,7 @@ type sqlcQuerier interface { GetLicenses(ctx context.Context) ([]License, error) GetLogoURL(ctx context.Context) (string, error) GetNotificationMessagesByStatus(ctx context.Context, arg GetNotificationMessagesByStatusParams) ([]NotificationMessage, error) + GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (NotificationTemplate, error) GetNotificationsSettings(ctx context.Context) (string, error) GetOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderApp, error) GetOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderAppCode, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 2e3a5c9892d40..4d31b5cb88e9f 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3631,6 +3631,24 @@ func (q *sqlQuerier) GetNotificationMessagesByStatus(ctx context.Context, arg Ge return items, nil } +const getNotificationTemplateByID = `-- name: GetNotificationTemplateByID :one +SELECT id, name, title_template, body_template, actions, "group" FROM notification_templates WHERE id = $1::uuid +` + +func (q *sqlQuerier) GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (NotificationTemplate, error) { + row := q.db.QueryRowContext(ctx, getNotificationTemplateByID, id) + var i NotificationTemplate + err := row.Scan( + &i.ID, + &i.Name, + &i.TitleTemplate, + &i.BodyTemplate, + &i.Actions, + &i.Group, + ) + return i, err +} + const deleteOAuth2ProviderAppByID = `-- name: DeleteOAuth2ProviderAppByID :exec DELETE FROM oauth2_provider_apps WHERE id = $1 ` diff --git a/coderd/database/queries/notifications.sql b/coderd/database/queries/notifications.sql index 2fd372e9df029..1bd349a28b0f3 100644 --- a/coderd/database/queries/notifications.sql +++ b/coderd/database/queries/notifications.sql @@ -133,3 +133,5 @@ WHERE id IN -- name: GetNotificationMessagesByStatus :many SELECT * FROM notification_messages WHERE status = @status LIMIT sqlc.arg('limit')::int; +-- name: GetNotificationTemplateByID :one +SELECT * FROM notification_templates WHERE id = @id::uuid; diff --git a/coderd/dormancy/notifications.go b/coderd/dormancy/notifications.go deleted file mode 100644 index 162ca272db635..0000000000000 --- a/coderd/dormancy/notifications.go +++ /dev/null @@ -1,75 +0,0 @@ -// This package is located outside of the enterprise package to ensure -// accessibility in the putWorkspaceDormant function. This design choice allows -// workspaces to be taken out of dormancy even if the license has expired, -// ensuring critical functionality remains available without an active -// enterprise license. -package dormancy - -import ( - "context" - - "github.com/google/uuid" - - "github.com/coder/coder/v2/coderd/database" - "github.com/coder/coder/v2/coderd/notifications" -) - -type WorkspaceDormantNotification struct { - Workspace database.Workspace - Initiator string - Reason string - CreatedBy string -} - -func NotifyWorkspaceDormant( - ctx context.Context, - enqueuer notifications.Enqueuer, - notification WorkspaceDormantNotification, -) (id *uuid.UUID, err error) { - labels := map[string]string{ - "name": notification.Workspace.Name, - "initiator": notification.Initiator, - "reason": notification.Reason, - } - return enqueuer.Enqueue( - ctx, - notification.Workspace.OwnerID, - notifications.TemplateWorkspaceDormant, - labels, - notification.CreatedBy, - // Associate this notification with all the related entities. - notification.Workspace.ID, - notification.Workspace.OwnerID, - notification.Workspace.TemplateID, - notification.Workspace.OrganizationID, - ) -} - -type WorkspaceMarkedForDeletionNotification struct { - Workspace database.Workspace - Reason string - CreatedBy string -} - -func NotifyWorkspaceMarkedForDeletion( - ctx context.Context, - enqueuer notifications.Enqueuer, - notification WorkspaceMarkedForDeletionNotification, -) (id *uuid.UUID, err error) { - labels := map[string]string{ - "name": notification.Workspace.Name, - "reason": notification.Reason, - } - return enqueuer.Enqueue( - ctx, - notification.Workspace.OwnerID, - notifications.TemplateWorkspaceMarkedForDeletion, - labels, - notification.CreatedBy, - // Associate this notification with all the related entities. - notification.Workspace.ID, - notification.Workspace.OwnerID, - notification.Workspace.TemplateID, - notification.Workspace.OrganizationID, - ) -} diff --git a/coderd/notifications/notifications_test.go b/coderd/notifications/notifications_test.go index 7d55ac01c5b52..66becd02d54a9 100644 --- a/coderd/notifications/notifications_test.go +++ b/coderd/notifications/notifications_test.go @@ -29,6 +29,7 @@ import ( "github.com/coder/coder/v2/coderd/database/dbtestutil" "github.com/coder/coder/v2/coderd/notifications" "github.com/coder/coder/v2/coderd/notifications/dispatch" + "github.com/coder/coder/v2/coderd/notifications/render" "github.com/coder/coder/v2/coderd/notifications/types" "github.com/coder/coder/v2/coderd/util/syncmap" "github.com/coder/coder/v2/codersdk" @@ -603,6 +604,96 @@ func TestNotifierPaused(t *testing.T) { }, testutil.WaitShort, testutil.IntervalFast) } +func TestNotifcationTemplatesBody(t *testing.T) { + t.Parallel() + + if !dbtestutil.WillUsePostgres() { + t.Skip("This test requires postgres; it relies on the notification templates added by migrations in the database") + } + + tests := []struct { + name string + id uuid.UUID + payload types.MessagePayload + }{ + { + name: "TemplateWorkspaceDeleted", + id: notifications.TemplateWorkspaceDeleted, + payload: types.MessagePayload{ + UserName: "bobby", + Labels: map[string]string{ + "name": "bobby-workspace", + "reason": "autodeleted due to dormancy", + "initiator": "autobuild", + }, + }, + }, + { + name: "TemplateWorkspaceAutobuildFailed", + id: notifications.TemplateWorkspaceAutobuildFailed, + payload: types.MessagePayload{ + UserName: "bobby", + Labels: map[string]string{ + "name": "bobby-workspace", + "reason": "autostart", + }, + }, + }, + { + name: "TemplateWorkspaceDormant", + id: notifications.TemplateWorkspaceDormant, + payload: types.MessagePayload{ + UserName: "bobby", + Labels: map[string]string{ + "name": "bobby-workspace", + "reason": "breached the template's threshold for inactivity", + "initiator": "autobuild", + "dormancyHours": "24", + }, + }, + }, + { + name: "TemplateWorkspaceAutoUpdated", + id: notifications.TemplateWorkspaceAutoUpdated, + payload: types.MessagePayload{ + UserName: "bobby", + Labels: map[string]string{ + "name": "bobby-workspace", + "template_version_name": "1.0", + }, + }, + }, + { + name: "TemplateWorkspaceMarkedForDeletion", + id: notifications.TemplateWorkspaceMarkedForDeletion, + payload: types.MessagePayload{ + UserName: "bobby", + Labels: map[string]string{ + "name": "bobby-workspace", + "reason": "template updated to new dormancy policy", + "dormancyHours": "24", + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) + t.Cleanup(cancel) + db, _, _ := dbtestutil.NewDBWithSQLDB(t) + + tpl, err := db.GetNotificationTemplateByID(ctx, tc.id) + require.NoError(t, err, "failed to get notification template") + + _, err = render.GoTemplate(tpl.BodyTemplate, tc.payload, nil) + require.NoError(t, err, "failed to render notification template") + }) + } +} + type fakeHandler struct { mu sync.RWMutex succeeded, failed []string diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index b71935e9a0436..458f79ca348e6 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1101,13 +1101,11 @@ func (s *server) notifyWorkspaceBuildFailed(ctx context.Context, workspace datab return // failed workspace build initiated by a user should not notify } reason = string(build.Reason) - initiator := "autobuild" if _, err := s.NotificationsEnqueuer.Enqueue(ctx, workspace.OwnerID, notifications.TemplateWorkspaceAutobuildFailed, map[string]string{ - "name": workspace.Name, - "initiator": initiator, - "reason": reason, + "name": workspace.Name, + "reason": reason, }, "provisionerdserver", // Associate this notification with all the related entities. workspace.ID, workspace.OwnerID, workspace.TemplateID, workspace.OrganizationID, diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 1f4c4f276a5b8..2bf249eb684cb 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -23,9 +23,9 @@ import ( "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/database/provisionerjobs" - "github.com/coder/coder/v2/coderd/dormancy" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/coderd/httpmw" + "github.com/coder/coder/v2/coderd/notifications" "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/coderd/rbac/policy" "github.com/coder/coder/v2/coderd/schedule/cron" @@ -953,25 +953,43 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) { // We don't need to notify the owner if they are the one making the request. if req.Dormant && apiKey.UserID != workspace.OwnerID { - initiator, err := api.Database.GetUserByID(ctx, apiKey.UserID) - if err != nil { + initiator, initiatorErr := api.Database.GetUserByID(ctx, apiKey.UserID) + if initiatorErr != nil { api.Logger.Warn( ctx, - "failed to fetch the user that marked the workspace", + "failed to fetch the user that marked the workspace as dormant", slog.Error(err), slog.F("workspace_id", workspace.ID), slog.F("user_id", apiKey.UserID), ) - } else { - _, err = dormancy.NotifyWorkspaceDormant( + } + + tmpl, tmplErr := api.Database.GetTemplateByID(ctx, workspace.TemplateID) + if tmplErr != nil { + api.Logger.Warn( + ctx, + "failed to fetch the template of the workspace marked as dormant", + slog.Error(err), + slog.F("workspace_id", workspace.ID), + slog.F("template_id", workspace.TemplateID), + ) + } + + if initiatorErr == nil && tmplErr == nil { + _, err = api.NotificationsEnqueuer.Enqueue( ctx, - api.NotificationsEnqueuer, - dormancy.WorkspaceDormantNotification{ - Workspace: workspace, - Initiator: initiator.Username, - Reason: "requested by user", - CreatedBy: "api", + workspace.OwnerID, + notifications.TemplateWorkspaceDormant, + map[string]string{ + "name": workspace.Name, + "reason": "a " + initiator.Username + " request", + "timeTilFormant": time.Duration(tmpl.TimeTilDormant).String(), }, + "api", + workspace.ID, + workspace.OwnerID, + workspace.TemplateID, + workspace.OrganizationID, ) if err != nil { api.Logger.Warn(ctx, "failed to notify of workspace marked as dormant", slog.Error(err)) diff --git a/enterprise/coderd/schedule/template.go b/enterprise/coderd/schedule/template.go index c3cb5001e091c..5b7c9d212e787 100644 --- a/enterprise/coderd/schedule/template.go +++ b/enterprise/coderd/schedule/template.go @@ -16,7 +16,6 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbtime" - "github.com/coder/coder/v2/coderd/dormancy" "github.com/coder/coder/v2/coderd/notifications" agpl "github.com/coder/coder/v2/coderd/schedule" "github.com/coder/coder/v2/coderd/tracing" @@ -205,18 +204,25 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S return database.Template{}, err } - for _, workspace := range markedForDeletion { - _, err = dormancy.NotifyWorkspaceMarkedForDeletion( + for _, ws := range markedForDeletion { + _, err = s.enqueuer.Enqueue( ctx, - s.enqueuer, - dormancy.WorkspaceMarkedForDeletionNotification{ - Workspace: workspace, - Reason: "template updated to new dormancy policy", - CreatedBy: "scheduletemplate", + ws.OwnerID, + ws.TemplateID, + map[string]string{ + "name": ws.Name, + "reason": "an update to the template's dormancy", + "timeTilDormant": opts.TimeTilDormantAutoDelete.String(), }, + "scheduletemplate", + // Associate this notification with all the related entities. + ws.ID, + ws.OwnerID, + ws.TemplateID, + ws.OrganizationID, ) if err != nil { - s.logger.Warn(ctx, "failed to notify of workspace marked for deletion", slog.Error(err), slog.F("workspace_id", workspace.ID)) + s.logger.Warn(ctx, "failed to notify of workspace marked for deletion", slog.Error(err), slog.F("workspace_id", ws.ID)) } } From a6bcef9b3c88c316cb7f98e833e665f929a86dcb Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Fri, 26 Jul 2024 17:31:19 +0000 Subject: [PATCH 02/14] Fix lint --- coderd/database/dbmem/dbmem.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 36d93d80de4b2..b34c5d42f1be6 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -2819,7 +2819,7 @@ func (q *FakeQuerier) GetNotificationMessagesByStatus(_ context.Context, arg dat return out, nil } -func (q *FakeQuerier) GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (database.NotificationTemplate, error) { +func (q *FakeQuerier) GetNotificationTemplateByID(_ context.Context, id uuid.UUID) (database.NotificationTemplate, error) { q.mutex.RLock() defer q.mutex.RUnlock() From 97514f5f178a32396bc491de9d4ad99ac7adff8d Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Fri, 26 Jul 2024 17:34:51 +0000 Subject: [PATCH 03/14] Fix SQL --- ...32_update_dormancy_notification_template.down.sql | 4 ++-- ...0232_update_dormancy_notification_template.up.sql | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/coderd/database/migrations/000232_update_dormancy_notification_template.down.sql b/coderd/database/migrations/000232_update_dormancy_notification_template.down.sql index 763b8effa5602..15b878ef5ba7a 100644 --- a/coderd/database/migrations/000232_update_dormancy_notification_template.down.sql +++ b/coderd/database/migrations/000232_update_dormancy_notification_template.down.sql @@ -1,11 +1,11 @@ UPDATE notification_templates SET - body_template = E'Hi {{.UserName}}\n\n' || E'Your workspace **{{.Labels.name}}** has been marked as **dormant**.\n' || E'The specified reason was "**{{.Labels.reason}} (initiated by: {{ .Labels.initiator }}){{end}}**\n\n' || E'Dormancy refers to a workspace being unused for a defined length of time, and after it exceeds {{.Labels.dormancyHours}} hours of dormancy might be deleted.\n' || E'To activate your workspace again, simply use it as normal.', + body_template = E'Hi {{.UserName}}\n\n' || E'Your workspace **{{.Labels.name}}** has been marked as **dormant**.\n' || E'The specified reason was "**{{.Labels.reason}} (initiated by: {{ .Labels.initiator }})**\n\n' || E'Dormancy refers to a workspace being unused for a defined length of time, and after it exceeds {{.Labels.dormancyHours}} hours of dormancy might be deleted.\n' || E'To activate your workspace again, simply use it as normal.' WHERE id = '0ea69165-ec14-4314-91f1-69566ac3c5a0'; UPDATE notification_templates SET - body_template = E'Hi {{.UserName}}\n\n' || E'Your workspace **{{.Labels.name}}** has been marked for **deletion** after {{.Labels.dormancyHours}} hours of dormancy.\n' || E'The specified reason was "**{{.Labels.reason}}{{end}}**\n\n' || E'Dormancy refers to a workspace being unused for a defined length of time, and after it exceeds {{.Labels.dormancyHours}} hours of dormancy it will be deleted.\n' || E'To prevent your workspace from being deleted, simply use it as normal.' + body_template = E'Hi {{.UserName}}\n\n' || E'Your workspace **{{.Labels.name}}** has been marked for **deletion** after {{.Labels.dormancyHours}} hours of dormancy.\n' || E'The specified reason was "**{{.Labels.reason}}**\n\n' || E'Dormancy refers to a workspace being unused for a defined length of time, and after it exceeds {{.Labels.dormancyHours}} hours of dormancy it will be deleted.\n' || E'To prevent your workspace from being deleted, simply use it as normal.' WHERE id = '51ce2fdf-c9ca-4be1-8d70-628674f9bc42'; diff --git a/coderd/database/migrations/000232_update_dormancy_notification_template.up.sql b/coderd/database/migrations/000232_update_dormancy_notification_template.up.sql index bb81cbdf0788c..c36502841d86e 100644 --- a/coderd/database/migrations/000232_update_dormancy_notification_template.up.sql +++ b/coderd/database/migrations/000232_update_dormancy_notification_template.up.sql @@ -1,16 +1,16 @@ UPDATE notification_templates SET - body_template = E'Hi {{.UserName}}\n\n' || + body_template = E'Hi {{.UserName}}\n\n' || E'Your workspace **{{.Labels.name}}** has been marked as [**dormant**](https://coder.com/docs/templates/schedule#dormancy-threshold-enterprise) because of {{.Labels.reason}}.\n' || E'Dormant workspaces are [automatically deleted](https://coder.com/docs/templates/schedule#dormancy-auto-deletion-enterprise) after {{.Labels.timeTilDormant}} of inactivity.\n' || E'To prevent deletion, use your workspace with the link below.' WHERE - id = '0ea69165-ec14-4314-91f1-69566ac3c5a0'; + id = '0ea69165-ec14-4314-91f1-69566ac3c5a0'; UPDATE notification_templates SET - body_template = E'Hi {{.UserName}}\n\n' || - E'Your workspace **{{.Labels.name}}** has been marked for **deletion** after {{.Labels.timeTilDormant}} of [dormancy](https://coder.com/docs/templates/schedule#dormancy-auto-deletion-enterprise) because of {{.Labels.reason}}.\n' || - E'To prevent deletion, use your workspace with the link below.' + body_template = E'Hi {{.UserName}}\n\n' || + E'Your workspace **{{.Labels.name}}** has been marked for **deletion** after {{.Labels.timeTilDormant}} of [dormancy](https://coder.com/docs/templates/schedule#dormancy-auto-deletion-enterprise) because of {{.Labels.reason}}.\n' || + E'To prevent deletion, use your workspace with the link below.' WHERE - id = '51ce2fdf-c9ca-4be1-8d70-628674f9bc42'; + id = '51ce2fdf-c9ca-4be1-8d70-628674f9bc42'; From 4b75dcf79bd19c6a3b11f415b9849c3f12554d2f Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Fri, 26 Jul 2024 17:37:11 +0000 Subject: [PATCH 04/14] Fix workspace test --- coderd/workspaces_test.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index c2a8c3c9c4ec8..94e89bcd50f98 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -3457,13 +3457,13 @@ func TestNotifications(t *testing.T) { IncludeProvisionerDaemon: true, NotificationsEnqueuer: notifyEnq, }) - user = coderdtest.CreateFirstUser(t, client) - memberClient, member = 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) - workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) - _ = coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) + 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) + workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + _ = coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) ) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) @@ -3483,7 +3483,6 @@ func TestNotifications(t *testing.T) { require.Contains(t, notifyEnq.Sent[0].Targets, workspace.ID) require.Contains(t, notifyEnq.Sent[0].Targets, workspace.OrganizationID) require.Contains(t, notifyEnq.Sent[0].Targets, workspace.OwnerID) - require.Equal(t, notifyEnq.Sent[0].Labels["initiator"], member.Username) }) t.Run("InitiatorIsOwner", func(t *testing.T) { From 008a4104efb54d6ce4a9f6d0a7c89d12dbdf32f7 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Fri, 26 Jul 2024 17:43:49 +0000 Subject: [PATCH 05/14] Fix lint --- coderd/notifications/notifications_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/coderd/notifications/notifications_test.go b/coderd/notifications/notifications_test.go index 66becd02d54a9..abfbe0f16b1ae 100644 --- a/coderd/notifications/notifications_test.go +++ b/coderd/notifications/notifications_test.go @@ -678,6 +678,8 @@ func TestNotifcationTemplatesBody(t *testing.T) { } for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { t.Parallel() From 799644e333498719f3d6ca3ec19808394fab9f86 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Fri, 26 Jul 2024 17:50:04 +0000 Subject: [PATCH 06/14] Fix tests --- coderd/provisionerdserver/provisionerdserver_test.go | 1 - enterprise/coderd/schedule/template.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index 2117d8e5f3df8..79c1b00ac78ee 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -1797,7 +1797,6 @@ func TestNotifications(t *testing.T) { require.Contains(t, notifEnq.Sent[0].Targets, workspace.ID) require.Contains(t, notifEnq.Sent[0].Targets, workspace.OrganizationID) require.Contains(t, notifEnq.Sent[0].Targets, user.ID) - require.Equal(t, "autobuild", notifEnq.Sent[0].Labels["initiator"]) require.Equal(t, string(tc.buildReason), notifEnq.Sent[0].Labels["reason"]) } else { require.Len(t, notifEnq.Sent, 0) diff --git a/enterprise/coderd/schedule/template.go b/enterprise/coderd/schedule/template.go index 5b7c9d212e787..c38b8f509b5c3 100644 --- a/enterprise/coderd/schedule/template.go +++ b/enterprise/coderd/schedule/template.go @@ -208,7 +208,7 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S _, err = s.enqueuer.Enqueue( ctx, ws.OwnerID, - ws.TemplateID, + notifications.TemplateWorkspaceMarkedForDeletion, map[string]string{ "name": ws.Name, "reason": "an update to the template's dormancy", From 34f8aa465dd6dd6303dabace14e59ed4079a4da9 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Fri, 26 Jul 2024 17:56:49 +0000 Subject: [PATCH 07/14] Fix test --- coderd/autobuild/lifecycle_executor_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/coderd/autobuild/lifecycle_executor_test.go b/coderd/autobuild/lifecycle_executor_test.go index 243b2550ccf63..f2fb37c8b471c 100644 --- a/coderd/autobuild/lifecycle_executor_test.go +++ b/coderd/autobuild/lifecycle_executor_test.go @@ -1122,7 +1122,6 @@ func TestNotifications(t *testing.T) { require.Contains(t, notifyEnq.Sent[0].Targets, workspace.ID) require.Contains(t, notifyEnq.Sent[0].Targets, workspace.OrganizationID) require.Contains(t, notifyEnq.Sent[0].Targets, workspace.OwnerID) - require.Equal(t, notifyEnq.Sent[0].Labels["initiator"], "autobuild") }) } From 5da37456d59ddf57775020f209a03230902ed32d Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Fri, 26 Jul 2024 18:20:55 +0000 Subject: [PATCH 08/14] Remove sqlc query in favor of inline sql for tests --- coderd/database/dbauthz/dbauthz.go | 7 ------- coderd/database/dbmem/dbmem.go | 15 --------------- coderd/database/dbmetrics/dbmetrics.go | 7 ------- coderd/database/dbmock/dbmock.go | 15 --------------- coderd/database/querier.go | 1 - coderd/database/queries.sql.go | 18 ------------------ coderd/database/queries/notifications.sql | 3 --- coderd/notifications/notifications_test.go | 13 +++++++------ 8 files changed, 7 insertions(+), 72 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 5857e4e13a337..d12b9aba23863 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1484,13 +1484,6 @@ func (q *querier) GetNotificationMessagesByStatus(ctx context.Context, arg datab return q.db.GetNotificationMessagesByStatus(ctx, arg) } -func (q *querier) GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (database.NotificationTemplate, error) { - if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil { - return database.NotificationTemplate{}, err - } - return q.db.GetNotificationTemplateByID(ctx, id) -} - func (q *querier) GetNotificationsSettings(ctx context.Context) (string, error) { // No authz checks return q.db.GetNotificationsSettings(ctx) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index b34c5d42f1be6..8d1088616f6bc 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -65,7 +65,6 @@ func New() database.Store { files: make([]database.File, 0), gitSSHKey: make([]database.GitSSHKey, 0), notificationMessages: make([]database.NotificationMessage, 0), - notificationTemplates: make([]database.NotificationTemplate, 0), parameterSchemas: make([]database.ParameterSchema, 0), provisionerDaemons: make([]database.ProvisionerDaemon, 0), workspaceAgents: make([]database.WorkspaceAgent, 0), @@ -161,7 +160,6 @@ type data struct { jfrogXRayScans []database.JfrogXrayScan licenses []database.License notificationMessages []database.NotificationMessage - notificationTemplates []database.NotificationTemplate oauth2ProviderApps []database.OAuth2ProviderApp oauth2ProviderAppSecrets []database.OAuth2ProviderAppSecret oauth2ProviderAppCodes []database.OAuth2ProviderAppCode @@ -2819,19 +2817,6 @@ func (q *FakeQuerier) GetNotificationMessagesByStatus(_ context.Context, arg dat return out, nil } -func (q *FakeQuerier) GetNotificationTemplateByID(_ context.Context, id uuid.UUID) (database.NotificationTemplate, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - for _, template := range q.notificationTemplates { - if template.ID == id { - return template, nil - } - } - - return database.NotificationTemplate{}, sql.ErrNoRows -} - func (q *FakeQuerier) GetNotificationsSettings(_ context.Context) (string, error) { q.mutex.RLock() defer q.mutex.RUnlock() diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index 9ba3577ee6034..f987d0505653b 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -746,13 +746,6 @@ func (m metricsStore) GetNotificationMessagesByStatus(ctx context.Context, arg d return r0, r1 } -func (m metricsStore) GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (database.NotificationTemplate, error) { - start := time.Now() - r0, r1 := m.s.GetNotificationTemplateByID(ctx, id) - m.queryLatencies.WithLabelValues("GetNotificationTemplateByID").Observe(time.Since(start).Seconds()) - return r0, r1 -} - func (m metricsStore) GetNotificationsSettings(ctx context.Context) (string, error) { start := time.Now() r0, r1 := m.s.GetNotificationsSettings(ctx) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index add0ef222ebfa..78cd95a69cde5 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -1480,21 +1480,6 @@ func (mr *MockStoreMockRecorder) GetNotificationMessagesByStatus(arg0, arg1 any) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotificationMessagesByStatus", reflect.TypeOf((*MockStore)(nil).GetNotificationMessagesByStatus), arg0, arg1) } -// GetNotificationTemplateByID mocks base method. -func (m *MockStore) GetNotificationTemplateByID(arg0 context.Context, arg1 uuid.UUID) (database.NotificationTemplate, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetNotificationTemplateByID", arg0, arg1) - ret0, _ := ret[0].(database.NotificationTemplate) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetNotificationTemplateByID indicates an expected call of GetNotificationTemplateByID. -func (mr *MockStoreMockRecorder) GetNotificationTemplateByID(arg0, arg1 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotificationTemplateByID", reflect.TypeOf((*MockStore)(nil).GetNotificationTemplateByID), arg0, arg1) -} - // GetNotificationsSettings mocks base method. func (m *MockStore) GetNotificationsSettings(arg0 context.Context) (string, error) { m.ctrl.T.Helper() diff --git a/coderd/database/querier.go b/coderd/database/querier.go index b5899d700cf15..9d0494813e306 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -162,7 +162,6 @@ type sqlcQuerier interface { GetLicenses(ctx context.Context) ([]License, error) GetLogoURL(ctx context.Context) (string, error) GetNotificationMessagesByStatus(ctx context.Context, arg GetNotificationMessagesByStatusParams) ([]NotificationMessage, error) - GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (NotificationTemplate, error) GetNotificationsSettings(ctx context.Context) (string, error) GetOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderApp, error) GetOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderAppCode, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 4d31b5cb88e9f..2e3a5c9892d40 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -3631,24 +3631,6 @@ func (q *sqlQuerier) GetNotificationMessagesByStatus(ctx context.Context, arg Ge return items, nil } -const getNotificationTemplateByID = `-- name: GetNotificationTemplateByID :one -SELECT id, name, title_template, body_template, actions, "group" FROM notification_templates WHERE id = $1::uuid -` - -func (q *sqlQuerier) GetNotificationTemplateByID(ctx context.Context, id uuid.UUID) (NotificationTemplate, error) { - row := q.db.QueryRowContext(ctx, getNotificationTemplateByID, id) - var i NotificationTemplate - err := row.Scan( - &i.ID, - &i.Name, - &i.TitleTemplate, - &i.BodyTemplate, - &i.Actions, - &i.Group, - ) - return i, err -} - const deleteOAuth2ProviderAppByID = `-- name: DeleteOAuth2ProviderAppByID :exec DELETE FROM oauth2_provider_apps WHERE id = $1 ` diff --git a/coderd/database/queries/notifications.sql b/coderd/database/queries/notifications.sql index 1bd349a28b0f3..c0a2f25323957 100644 --- a/coderd/database/queries/notifications.sql +++ b/coderd/database/queries/notifications.sql @@ -132,6 +132,3 @@ WHERE id IN -- name: GetNotificationMessagesByStatus :many SELECT * FROM notification_messages WHERE status = @status LIMIT sqlc.arg('limit')::int; - --- name: GetNotificationTemplateByID :one -SELECT * FROM notification_templates WHERE id = @id::uuid; diff --git a/coderd/notifications/notifications_test.go b/coderd/notifications/notifications_test.go index abfbe0f16b1ae..03942cbc174d4 100644 --- a/coderd/notifications/notifications_test.go +++ b/coderd/notifications/notifications_test.go @@ -683,14 +683,15 @@ func TestNotifcationTemplatesBody(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) - t.Cleanup(cancel) - db, _, _ := dbtestutil.NewDBWithSQLDB(t) + _, _, sql := dbtestutil.NewDBWithSQLDB(t) - tpl, err := db.GetNotificationTemplateByID(ctx, tc.id) - require.NoError(t, err, "failed to get notification template") + var bodyTmpl string + err := sql. + QueryRow("SELECT body_template FROM notification_templates WHERE id = $1 LIMIT 1", tc.id). + Scan(&bodyTmpl) + require.NoError(t, err, "failed to query body template for template:", tc.id) - _, err = render.GoTemplate(tpl.BodyTemplate, tc.payload, nil) + _, err = render.GoTemplate(bodyTmpl, tc.payload, nil) require.NoError(t, err, "failed to render notification template") }) } From 7bdce48290daba3dd82c569be5e5cb958f29cb42 Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Mon, 29 Jul 2024 10:12:24 -0300 Subject: [PATCH 09/14] Update coderd/workspaces.go Co-authored-by: Danny Kopping --- coderd/workspaces.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 2bf249eb684cb..ceba543639cc3 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -983,7 +983,7 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) { map[string]string{ "name": workspace.Name, "reason": "a " + initiator.Username + " request", - "timeTilFormant": time.Duration(tmpl.TimeTilDormant).String(), + "timeTilDormant": time.Duration(tmpl.TimeTilDormant).String(), }, "api", workspace.ID, From fc055c8891e671bd472cfa49e563cf84d45244e0 Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Mon, 29 Jul 2024 10:12:31 -0300 Subject: [PATCH 10/14] Update coderd/autobuild/lifecycle_executor.go Co-authored-by: Danny Kopping --- coderd/autobuild/lifecycle_executor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/autobuild/lifecycle_executor.go b/coderd/autobuild/lifecycle_executor.go index ac3235f7345c7..998a2538e14a4 100644 --- a/coderd/autobuild/lifecycle_executor.go +++ b/coderd/autobuild/lifecycle_executor.go @@ -330,7 +330,7 @@ func (e *Executor) runOnce(t time.Time) Stats { "reason": "prolonged inactivity, exceeding the dormancy threshold", "timeTilFormant": time.Duration(tmpl.TimeTilDormant).String(), }, - "api", + "lifecycle_executor", ws.ID, ws.OwnerID, ws.TemplateID, From 949079298ff98dc780591836816b428df03f70e6 Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Mon, 29 Jul 2024 10:12:37 -0300 Subject: [PATCH 11/14] Update coderd/autobuild/lifecycle_executor.go Co-authored-by: Danny Kopping --- coderd/autobuild/lifecycle_executor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/autobuild/lifecycle_executor.go b/coderd/autobuild/lifecycle_executor.go index 998a2538e14a4..ee152d9ed9cfe 100644 --- a/coderd/autobuild/lifecycle_executor.go +++ b/coderd/autobuild/lifecycle_executor.go @@ -328,7 +328,7 @@ func (e *Executor) runOnce(t time.Time) Stats { map[string]string{ "name": ws.Name, "reason": "prolonged inactivity, exceeding the dormancy threshold", - "timeTilFormant": time.Duration(tmpl.TimeTilDormant).String(), + "timeTilDormant": time.Duration(tmpl.TimeTilDormant).String(), }, "lifecycle_executor", ws.ID, From cb1ca3b4efeedcb874b6cf9ec6b3792843c7d38e Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Mon, 29 Jul 2024 10:12:49 -0300 Subject: [PATCH 12/14] Update coderd/autobuild/lifecycle_executor.go Co-authored-by: Danny Kopping --- coderd/autobuild/lifecycle_executor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/autobuild/lifecycle_executor.go b/coderd/autobuild/lifecycle_executor.go index ee152d9ed9cfe..10692f91ff1c8 100644 --- a/coderd/autobuild/lifecycle_executor.go +++ b/coderd/autobuild/lifecycle_executor.go @@ -327,7 +327,7 @@ func (e *Executor) runOnce(t time.Time) Stats { notifications.TemplateWorkspaceDormant, map[string]string{ "name": ws.Name, - "reason": "prolonged inactivity, exceeding the dormancy threshold", + "reason": "inactivity exceeded the dormancy threshold", "timeTilDormant": time.Duration(tmpl.TimeTilDormant).String(), }, "lifecycle_executor", From e7557d2ce10c161e29ab252b156b975421ed7643 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Mon, 29 Jul 2024 13:36:35 +0000 Subject: [PATCH 13/14] Apply Dannys suggestion --- ..._update_dormancy_notification_template.down.sql | 11 ----------- coderd/notifications/notifications_test.go | 14 ++++++++++---- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/coderd/database/migrations/000232_update_dormancy_notification_template.down.sql b/coderd/database/migrations/000232_update_dormancy_notification_template.down.sql index 15b878ef5ba7a..e69de29bb2d1d 100644 --- a/coderd/database/migrations/000232_update_dormancy_notification_template.down.sql +++ b/coderd/database/migrations/000232_update_dormancy_notification_template.down.sql @@ -1,11 +0,0 @@ -UPDATE notification_templates -SET - body_template = E'Hi {{.UserName}}\n\n' || E'Your workspace **{{.Labels.name}}** has been marked as **dormant**.\n' || E'The specified reason was "**{{.Labels.reason}} (initiated by: {{ .Labels.initiator }})**\n\n' || E'Dormancy refers to a workspace being unused for a defined length of time, and after it exceeds {{.Labels.dormancyHours}} hours of dormancy might be deleted.\n' || E'To activate your workspace again, simply use it as normal.' -WHERE - id = '0ea69165-ec14-4314-91f1-69566ac3c5a0'; - -UPDATE notification_templates -SET - body_template = E'Hi {{.UserName}}\n\n' || E'Your workspace **{{.Labels.name}}** has been marked for **deletion** after {{.Labels.dormancyHours}} hours of dormancy.\n' || E'The specified reason was "**{{.Labels.reason}}**\n\n' || E'Dormancy refers to a workspace being unused for a defined length of time, and after it exceeds {{.Labels.dormancyHours}} hours of dormancy it will be deleted.\n' || E'To prevent your workspace from being deleted, simply use it as normal.' -WHERE - id = '51ce2fdf-c9ca-4be1-8d70-628674f9bc42'; diff --git a/coderd/notifications/notifications_test.go b/coderd/notifications/notifications_test.go index 03942cbc174d4..fe17e99d308be 100644 --- a/coderd/notifications/notifications_test.go +++ b/coderd/notifications/notifications_test.go @@ -685,14 +685,20 @@ func TestNotifcationTemplatesBody(t *testing.T) { _, _, sql := dbtestutil.NewDBWithSQLDB(t) - var bodyTmpl string + var ( + titleTmpl string + bodyTmpl string + ) err := sql. - QueryRow("SELECT body_template FROM notification_templates WHERE id = $1 LIMIT 1", tc.id). - Scan(&bodyTmpl) + QueryRow("SELECT title_template, body_template FROM notification_templates WHERE id = $1 LIMIT 1", tc.id). + Scan(&titleTmpl, &bodyTmpl) require.NoError(t, err, "failed to query body template for template:", tc.id) + _, err = render.GoTemplate(titleTmpl, tc.payload, nil) + require.NoError(t, err, "failed to render notification title template") + _, err = render.GoTemplate(bodyTmpl, tc.payload, nil) - require.NoError(t, err, "failed to render notification template") + require.NoError(t, err, "failed to render notification body template") }) } } From ab344b38e9708750461090783e3a178d83a29788 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Mon, 29 Jul 2024 14:09:50 +0000 Subject: [PATCH 14/14] Verify if body and title are not empty --- coderd/notifications/notifications_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/coderd/notifications/notifications_test.go b/coderd/notifications/notifications_test.go index fe17e99d308be..37fe4a2ce5ce3 100644 --- a/coderd/notifications/notifications_test.go +++ b/coderd/notifications/notifications_test.go @@ -694,11 +694,13 @@ func TestNotifcationTemplatesBody(t *testing.T) { Scan(&titleTmpl, &bodyTmpl) require.NoError(t, err, "failed to query body template for template:", tc.id) - _, err = render.GoTemplate(titleTmpl, tc.payload, nil) + title, err := render.GoTemplate(titleTmpl, tc.payload, nil) require.NoError(t, err, "failed to render notification title template") + require.NotEmpty(t, title, "title should not be empty") - _, err = render.GoTemplate(bodyTmpl, tc.payload, nil) + body, err := render.GoTemplate(bodyTmpl, tc.payload, nil) require.NoError(t, err, "failed to render notification body template") + require.NotEmpty(t, body, "body should not be empty") }) } }