diff --git a/Makefile b/Makefile index 42258d82170b5..5ddc629b25b9b 100644 --- a/Makefile +++ b/Makefile @@ -667,6 +667,7 @@ update-golden-files: \ enterprise/tailnet/testdata/.gen-golden \ tailnet/testdata/.gen-golden \ coderd/.gen-golden \ + coderd/notifications/.gen-golden \ provisioner/terraform/testdata/.gen-golden .PHONY: update-golden-files @@ -698,6 +699,10 @@ coderd/.gen-golden: $(wildcard coderd/testdata/*/*.golden) $(GO_SRC_FILES) $(wil go test ./coderd -run="Test.*Golden$$" -update touch "$@" +coderd/notifications/.gen-golden: $(wildcard coderd/notifications/testdata/*/*.golden) $(GO_SRC_FILES) $(wildcard coderd/notifications/*_test.go) + go test ./coderd/notifications -run="Test.*Golden$$" -update + touch "$@" + provisioner/terraform/testdata/.gen-golden: $(wildcard provisioner/terraform/testdata/*/*.golden) $(GO_SRC_FILES) $(wildcard provisioner/terraform/*_test.go) go test ./provisioner/terraform -run="Test.*Golden$$" -update touch "$@" diff --git a/coderd/database/migrations/000262_improve_notification_templates.down.sql b/coderd/database/migrations/000262_improve_notification_templates.down.sql new file mode 100644 index 0000000000000..62a2799e52caa --- /dev/null +++ b/coderd/database/migrations/000262_improve_notification_templates.down.sql @@ -0,0 +1,84 @@ +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\nUser account **{{.Labels.suspended_account_name}}** has been suspended.' +WHERE + id = 'b02ddd82-4733-4d02-a2d7-c36f3598997d'; + +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\nYour account **{{.Labels.suspended_account_name}}** has been suspended.' +WHERE + id = '6a2f0609-9b69-4d36-a989-9f5925b6cbff'; + +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\nUser account **{{.Labels.activated_account_name}}** has been activated.' +WHERE + id = '9f5af851-8408-4e73-a7a1-c6502ba46689'; + +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\nYour account **{{.Labels.activated_account_name}}** has been activated.' +WHERE + id = '1a6a6bea-ee0a-43e2-9e7c-eabdb53730e4'; + +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\New user account **{{.Labels.created_account_name}}** has been created.' +WHERE + id = '4e19c0ac-94e1-4532-9515-d1801aa283b2'; + +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\nUser account **{{.Labels.deleted_account_name}}** has been deleted.' +WHERE + id = 'f44d9314-ad03-4bc8-95d0-5cad491da6b6'; + +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}}\n\n' || + E'The template **{{.Labels.name}}** was deleted by **{{ .Labels.initiator }}**.' +WHERE + id = '29a09665-2a4c-403f-9648-54301670e7be'; + +UPDATE notification_templates +SET body_template = E'Hi {{.UserName}}\n' || + E'Your workspace **{{.Labels.name}}** has been updated automatically to the latest template version ({{.Labels.template_version_name}}).\n' || + E'Reason for update: **{{.Labels.template_version_message}}**' +WHERE + id = 'c34a0c09-0704-4cac-bd1c-0c0146811c2b'; + +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}}\n\nYour workspace **{{.Labels.name}}** was deleted.\nThe specified reason was "**{{.Labels.reason}}{{ if .Labels.initiator }} ({{ .Labels.initiator }}){{end}}**".' +WHERE + id = '381df2a9-c0c0-4749-420f-80a9280c66f9'; + +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}}\n\nYour workspace **{{.Labels.name}}** was deleted.\nThe specified reason was "**{{.Labels.reason}}{{ if .Labels.initiator }} ({{ .Labels.initiator }}){{end}}**".' +WHERE + id = 'f517da0b-cdc9-410f-ab89-a86107c420ed'; + +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'; + +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\nA manual build of the workspace **{{.Labels.name}}** using the template **{{.Labels.template_name}}** failed (version: **{{.Labels.template_version_name}}**).\nThe workspace build was initiated by **{{.Labels.initiator}}**.' +WHERE + id = '2faeee0f-26cb-4e96-821c-85ccb9f71513'; diff --git a/coderd/database/migrations/000262_improve_notification_templates.up.sql b/coderd/database/migrations/000262_improve_notification_templates.up.sql new file mode 100644 index 0000000000000..12dab392e2b20 --- /dev/null +++ b/coderd/database/migrations/000262_improve_notification_templates.up.sql @@ -0,0 +1,128 @@ +-- https://github.com/coder/coder/issues/14893 + +-- UserAccountSuspended +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n + E'User account **{{.Labels.suspended_account_name}}** has been suspended.\n\n' || + -- Mention the real name of the user who suspended the account: + E'The newly suspended account belongs to **{{.Labels.suspended_account_user_name}}** and was suspended by **{{.Labels.account_suspender_user_name}}**.' +WHERE + id = 'b02ddd82-4733-4d02-a2d7-c36f3598997d'; + +-- YourAccountSuspended +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n + -- Mention who suspended the account: + E'Your account **{{.Labels.suspended_account_name}}** has been suspended by **{{.Labels.account_suspender_user_name}}**.' +WHERE + id = '6a2f0609-9b69-4d36-a989-9f5925b6cbff'; + +-- UserAccountActivated +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n + E'User account **{{.Labels.activated_account_name}}** has been activated.\n\n' || + -- Mention the real name of the user who activated the account: + E'The newly activated account belongs to **{{.Labels.activated_account_user_name}}** and was activated by **{{.Labels.account_activator_user_name}}**.' +WHERE + id = '9f5af851-8408-4e73-a7a1-c6502ba46689'; + +-- YourAccountActivated +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || -- Add a \n + -- Mention who activated the account: + E'Your account **{{.Labels.activated_account_name}}** has been activated by **{{.Labels.account_activator_user_name}}**.' +WHERE + id = '1a6a6bea-ee0a-43e2-9e7c-eabdb53730e4'; + +-- UserAccountCreated +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || + E'New user account **{{.Labels.created_account_name}}** has been created.\n\n' || + -- Mention the real name of the user who created the account: + E'This new user account was created for **{{.Labels.created_account_user_name}}** by **{{.Labels.account_creator}}**.' +WHERE + id = '4e19c0ac-94e1-4532-9515-d1801aa283b2'; + +-- UserAccountDeleted +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || + E'User account **{{.Labels.deleted_account_name}}** has been deleted.\n\n' || + -- Mention the real name of the user who deleted the account: + E'The deleted account belonged to **{{.Labels.deleted_account_user_name}}** and was deleted by **{{.Labels.account_deleter_user_name}}**.' +WHERE + id = 'f44d9314-ad03-4bc8-95d0-5cad491da6b6'; + +-- TemplateDeleted +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || -- Add a comma + E'The template **{{.Labels.name}}** was deleted by **{{ .Labels.initiator }}**.\n\n' || + -- Mention template display name: + E'The template''s display name was **{{.Labels.display_name}}**.' +WHERE + id = '29a09665-2a4c-403f-9648-54301670e7be'; + +-- WorkspaceAutoUpdated +UPDATE notification_templates +SET body_template = E'Hi {{.UserName}},\n\n' || -- Add a comma and a \n + -- Add a \n: + E'Your workspace **{{.Labels.name}}** has been updated automatically to the latest template version ({{.Labels.template_version_name}}).\n\n' || + E'Reason for update: **{{.Labels.template_version_message}}**.' +WHERE + id = 'c34a0c09-0704-4cac-bd1c-0c0146811c2b'; + +-- WorkspaceAutoBuildFailed +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || -- Add a comma + -- Add a \n after: + E'Automatic build of your workspace **{{.Labels.name}}** failed.\n\n' || + E'The specified reason was "**{{.Labels.reason}}**".' +WHERE + id = '381df2a9-c0c0-4749-420f-80a9280c66f9'; + +-- WorkspaceDeleted +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || -- Add a comma + -- Add a \n after: + E'Your workspace **{{.Labels.name}}** was deleted.\n\n' || + E'The specified reason was "**{{.Labels.reason}}{{ if .Labels.initiator }} ({{ .Labels.initiator }}){{end}}**".' +WHERE + id = 'f517da0b-cdc9-410f-ab89-a86107c420ed'; + +-- WorkspaceDormant +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || -- add comma + 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'; + +-- WorkspaceMarkedForDeletion +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || -- add comma + 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'; + +-- WorkspaceManualBuildFailed +UPDATE notification_templates +SET + body_template = E'Hi {{.UserName}},\n\n' || + E'A manual build of the workspace **{{.Labels.name}}** using the template **{{.Labels.template_name}}** failed (version: **{{.Labels.template_version_name}}**).\n\n' || + -- Mention template display name: + E'The template''s display name was **{{.Labels.template_display_name}}**. ' || + E'The workspace build was initiated by **{{.Labels.initiator}}**.' +WHERE + id = '2faeee0f-26cb-4e96-821c-85ccb9f71513'; diff --git a/coderd/notifications/notifications_test.go b/coderd/notifications/notifications_test.go index 1878fcdd45007..28bd8b5190c71 100644 --- a/coderd/notifications/notifications_test.go +++ b/coderd/notifications/notifications_test.go @@ -684,7 +684,7 @@ func enumerateAllTemplates(t *testing.T) ([]string, error) { return out, nil } -func TestNotificationTemplatesCanRender(t *testing.T) { +func TestNotificationTemplates_Golden(t *testing.T) { t.Parallel() if !dbtestutil.WillUsePostgres() { @@ -764,7 +764,9 @@ func TestNotificationTemplatesCanRender(t *testing.T) { payload: types.MessagePayload{ UserName: "Bobby", Labels: map[string]string{ - "created_account_name": "bobby", + "created_account_name": "bobby", + "created_account_user_name": "William Tables", + "account_creator": "rob", }, }, }, @@ -774,7 +776,9 @@ func TestNotificationTemplatesCanRender(t *testing.T) { payload: types.MessagePayload{ UserName: "Bobby", Labels: map[string]string{ - "deleted_account_name": "bobby", + "deleted_account_name": "bobby", + "deleted_account_user_name": "william tables", + "account_deleter_user_name": "rob", }, }, }, @@ -784,7 +788,9 @@ func TestNotificationTemplatesCanRender(t *testing.T) { payload: types.MessagePayload{ UserName: "Bobby", Labels: map[string]string{ - "suspended_account_name": "bobby", + "suspended_account_name": "bobby", + "suspended_account_user_name": "william tables", + "account_suspender_user_name": "rob", }, }, }, @@ -794,7 +800,9 @@ func TestNotificationTemplatesCanRender(t *testing.T) { payload: types.MessagePayload{ UserName: "Bobby", Labels: map[string]string{ - "activated_account_name": "bobby", + "activated_account_name": "bobby", + "activated_account_user_name": "william tables", + "account_activator_user_name": "rob", }, }, }, @@ -804,7 +812,8 @@ func TestNotificationTemplatesCanRender(t *testing.T) { payload: types.MessagePayload{ UserName: "Bobby", Labels: map[string]string{ - "suspended_account_name": "bobby", + "suspended_account_name": "bobby", + "account_suspender_user_name": "rob", }, }, }, @@ -814,7 +823,8 @@ func TestNotificationTemplatesCanRender(t *testing.T) { payload: types.MessagePayload{ UserName: "Bobby", Labels: map[string]string{ - "activated_account_name": "bobby", + "activated_account_name": "bobby", + "account_activator_user_name": "rob", }, }, }, @@ -824,8 +834,9 @@ func TestNotificationTemplatesCanRender(t *testing.T) { payload: types.MessagePayload{ UserName: "Bobby", Labels: map[string]string{ - "name": "bobby-template", - "initiator": "rob", + "name": "bobby-template", + "display_name": "Bobby's Template", + "initiator": "rob", }, }, }, @@ -837,6 +848,7 @@ func TestNotificationTemplatesCanRender(t *testing.T) { Labels: map[string]string{ "name": "bobby-workspace", "template_name": "bobby-template", + "template_display_name": "William's Template", "template_version_name": "bobby-template-version", "initiator": "joe", "workspace_owner_username": "mrbobby", @@ -960,13 +972,15 @@ func TestNotificationTemplatesCanRender(t *testing.T) { return } + const hint = "run \"DB=ci make update-golden-files\" and commit the changes" + wantBody, err := os.ReadFile(bodyGoldenFile) - require.NoError(t, err, "open golden file, run \"DB=ci make update-golden-files\" and commit the changes") + require.NoError(t, err, fmt.Sprintf("missing golden notification body file. %s", hint)) wantTitle, err := os.ReadFile(titleGoldenFile) - require.NoError(t, err, "open golden file, run \"DB=ci make update-golden-files\" and commit the changes") + require.NoError(t, err, fmt.Sprintf("missing golden notification title file. %s", hint)) - require.Equal(t, string(wantBody), body, "body should be equal") - require.Equal(t, string(wantTitle), title, "title should be equal") + require.Equal(t, string(wantBody), body, fmt.Sprintf("rendered template body does not match golden file. If this is expected, %s", hint)) + require.Equal(t, string(wantTitle), title, fmt.Sprintf("rendered template title does not match golden file. If this is expected, %s", hint)) }) } } diff --git a/coderd/notifications/testdata/rendered-templates/TemplateTemplateDeleted-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateTemplateDeleted-body.md.golden index be3a61e695652..ade9c87ff791a 100644 --- a/coderd/notifications/testdata/rendered-templates/TemplateTemplateDeleted-body.md.golden +++ b/coderd/notifications/testdata/rendered-templates/TemplateTemplateDeleted-body.md.golden @@ -1,3 +1,5 @@ -Hi Bobby +Hi Bobby, -The template **bobby-template** was deleted by **rob**. \ No newline at end of file +The template **bobby-template** was deleted by **rob**. + +The template's display name was **Bobby's Template**. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountActivated-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateUserAccountActivated-body.md.golden index 2665a781492ea..5a773a51dc181 100644 --- a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountActivated-body.md.golden +++ b/coderd/notifications/testdata/rendered-templates/TemplateUserAccountActivated-body.md.golden @@ -1,2 +1,5 @@ Hi Bobby, -User account **bobby** has been activated. \ No newline at end of file + +User account **bobby** has been activated. + +The newly activated account belongs to **william tables** and was activated by **rob**. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountCreated-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateUserAccountCreated-body.md.golden index e5a5be89c11e0..88a46735f0483 100644 --- a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountCreated-body.md.golden +++ b/coderd/notifications/testdata/rendered-templates/TemplateUserAccountCreated-body.md.golden @@ -1,3 +1,5 @@ Hi Bobby, -New user account **bobby** has been created. \ No newline at end of file +New user account **bobby** has been created. + +This new user account was created for **William Tables** by **rob**. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountDeleted-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateUserAccountDeleted-body.md.golden index bd1066c25fb50..4ca8bedd0ca52 100644 --- a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountDeleted-body.md.golden +++ b/coderd/notifications/testdata/rendered-templates/TemplateUserAccountDeleted-body.md.golden @@ -1,3 +1,5 @@ Hi Bobby, -User account **bobby** has been deleted. \ No newline at end of file +User account **bobby** has been deleted. + +The deleted account belonged to **william tables** and was deleted by **rob**. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountSuspended-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateUserAccountSuspended-body.md.golden index 70a43f2960ec0..ecf276504ebc9 100644 --- a/coderd/notifications/testdata/rendered-templates/TemplateUserAccountSuspended-body.md.golden +++ b/coderd/notifications/testdata/rendered-templates/TemplateUserAccountSuspended-body.md.golden @@ -1,2 +1,5 @@ Hi Bobby, -User account **bobby** has been suspended. \ No newline at end of file + +User account **bobby** has been suspended. + +The newly suspended account belongs to **william tables** and was suspended by **rob**. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutoUpdated-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutoUpdated-body.md.golden index 79248150987c2..d9f4e27cb4c6e 100644 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutoUpdated-body.md.golden +++ b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutoUpdated-body.md.golden @@ -1,3 +1,5 @@ -Hi Bobby +Hi Bobby, + Your workspace **bobby-workspace** has been updated automatically to the latest template version (1.0). -Reason for update: **template now includes catnip** \ No newline at end of file + +Reason for update: **template now includes catnip**. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutobuildFailed-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutobuildFailed-body.md.golden index 731f71f22ae88..cddf2149d0d46 100644 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutobuildFailed-body.md.golden +++ b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceAutobuildFailed-body.md.golden @@ -1,3 +1,5 @@ -Hi Bobby +Hi Bobby, + Automatic build of your workspace **bobby-workspace** failed. + The specified reason was "**autostart**". \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDeleted-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDeleted-body.md.golden index 06aec5692465f..a5c71fb3e0170 100644 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDeleted-body.md.golden +++ b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDeleted-body.md.golden @@ -1,4 +1,5 @@ -Hi Bobby +Hi Bobby, Your workspace **bobby-workspace** was deleted. + The specified reason was "**autodeleted due to dormancy (autobuild)**". \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDormant-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDormant-body.md.golden index aa10f4864cf24..35bfe8d2c19b6 100644 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDormant-body.md.golden +++ b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceDormant-body.md.golden @@ -1,4 +1,4 @@ -Hi Bobby +Hi Bobby, Your workspace **bobby-workspace** has been marked as [**dormant**](https://coder.com/docs/templates/schedule#dormancy-threshold-enterprise) because of breached the template's threshold for inactivity. Dormant workspaces are [automatically deleted](https://coder.com/docs/templates/schedule#dormancy-auto-deletion-enterprise) after 24 hours of inactivity. diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceManualBuildFailed-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceManualBuildFailed-body.md.golden index 45f8733dd2931..e1091b2888830 100644 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceManualBuildFailed-body.md.golden +++ b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceManualBuildFailed-body.md.golden @@ -1,4 +1,5 @@ Hi Bobby, A manual build of the workspace **bobby-workspace** using the template **bobby-template** failed (version: **bobby-template-version**). -The workspace build was initiated by **joe**. \ No newline at end of file + +The template's display name was **William's Template**. The workspace build was initiated by **joe**. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceMarkedForDeletion-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceMarkedForDeletion-body.md.golden index 3d9fe99accd94..21defa7c1d500 100644 --- a/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceMarkedForDeletion-body.md.golden +++ b/coderd/notifications/testdata/rendered-templates/TemplateWorkspaceMarkedForDeletion-body.md.golden @@ -1,4 +1,4 @@ -Hi Bobby +Hi Bobby, Your workspace **bobby-workspace** has been marked for **deletion** after 24 hours of [dormancy](https://coder.com/docs/templates/schedule#dormancy-auto-deletion-enterprise) because of template updated to new dormancy policy. To prevent deletion, use your workspace with the link below. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateYourAccountActivated-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateYourAccountActivated-body.md.golden index 160fdc66e8990..a3a8710de4673 100644 --- a/coderd/notifications/testdata/rendered-templates/TemplateYourAccountActivated-body.md.golden +++ b/coderd/notifications/testdata/rendered-templates/TemplateYourAccountActivated-body.md.golden @@ -1,2 +1,3 @@ Hi Bobby, -Your account **bobby** has been activated. \ No newline at end of file + +Your account **bobby** has been activated by **rob**. \ No newline at end of file diff --git a/coderd/notifications/testdata/rendered-templates/TemplateYourAccountSuspended-body.md.golden b/coderd/notifications/testdata/rendered-templates/TemplateYourAccountSuspended-body.md.golden index ce30139213bb0..86fc40401774b 100644 --- a/coderd/notifications/testdata/rendered-templates/TemplateYourAccountSuspended-body.md.golden +++ b/coderd/notifications/testdata/rendered-templates/TemplateYourAccountSuspended-body.md.golden @@ -1,2 +1,3 @@ Hi Bobby, -Your account **bobby** has been suspended. \ No newline at end of file + +Your account **bobby** has been suspended by **rob**. \ No newline at end of file diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 2db5bbd1062b1..0fc89b239a8d4 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1128,6 +1128,7 @@ func (s *server) notifyWorkspaceManualBuildFailed(ctx context.Context, workspace map[string]string{ "name": workspace.Name, "template_name": template.Name, + "template_display_name": template.DisplayName, "template_version_name": templateVersion.Name, "initiator": build.InitiatorByUsername, "workspace_owner_username": workspaceOwner.Username, diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index d429ae1a1bcb1..32ae2efa648a0 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -1820,7 +1820,7 @@ func TestNotifications(t *testing.T) { _ = dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: user.ID, OrganizationID: pd.OrganizationID}) template := dbgen.Template(t, db, database.Template{ - Name: "template", Provisioner: database.ProvisionerTypeEcho, OrganizationID: pd.OrganizationID, + Name: "template", DisplayName: "William's Template", Provisioner: database.ProvisionerTypeEcho, OrganizationID: pd.OrganizationID, }) workspace := dbgen.Workspace(t, db, database.Workspace{ TemplateID: template.ID, OwnerID: user.ID, OrganizationID: pd.OrganizationID, @@ -1860,6 +1860,7 @@ func TestNotifications(t *testing.T) { assert.Contains(t, notifEnq.Sent[0].Targets, user.ID) assert.Equal(t, workspace.Name, notifEnq.Sent[0].Labels["name"]) assert.Equal(t, template.Name, notifEnq.Sent[0].Labels["template_name"]) + assert.Equal(t, template.DisplayName, notifEnq.Sent[0].Labels["template_display_name"]) assert.Equal(t, version.Name, notifEnq.Sent[0].Labels["template_version_name"]) assert.Equal(t, user.Username, notifEnq.Sent[0].Labels["initiator"]) assert.Equal(t, user.Username, notifEnq.Sent[0].Labels["workspace_owner_username"]) diff --git a/coderd/templates.go b/coderd/templates.go index dc32841b72aad..01c2a056bb813 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -137,8 +137,9 @@ func (api *API) notifyTemplateDeleted(ctx context.Context, template database.Tem if _, err := api.NotificationsEnqueuer.Enqueue(ctx, receiverID, notifications.TemplateTemplateDeleted, map[string]string{ - "name": template.Name, - "initiator": initiator.Username, + "name": template.Name, + "display_name": template.DisplayName, + "initiator": initiator.Username, }, "api-templates-delete", // Associate this notification with all the related entities. template.ID, template.OrganizationID, diff --git a/coderd/templates_test.go b/coderd/templates_test.go index ca8d9c4cf88f2..4273169d4ca82 100644 --- a/coderd/templates_test.go +++ b/coderd/templates_test.go @@ -1419,7 +1419,9 @@ func TestTemplateNotifications(t *testing.T) { // Setup template version = coderdtest.CreateTemplateVersion(t, client, initiator.OrganizationID, nil) _ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) - template = coderdtest.CreateTemplate(t, client, initiator.OrganizationID, version.ID) + template = coderdtest.CreateTemplate(t, client, initiator.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) { + ctr.DisplayName = "Bobby's Template" + }) ) // Setup users with different roles @@ -1455,6 +1457,7 @@ func TestTemplateNotifications(t *testing.T) { require.Contains(t, n.Targets, template.ID) require.Contains(t, n.Targets, template.OrganizationID) require.Equal(t, n.Labels["name"], template.Name) + require.Equal(t, n.Labels["display_name"], template.DisplayName) require.Equal(t, n.Labels["initiator"], coderdtest.FirstUserParams.Username) } }) diff --git a/coderd/userauth.go b/coderd/userauth.go index a1e1252797de3..0ff3dfa8f97cc 100644 --- a/coderd/userauth.go +++ b/coderd/userauth.go @@ -1481,14 +1481,15 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C Username: params.Username, OrganizationIDs: orgIDs, }, - LoginType: params.LoginType, + LoginType: params.LoginType, + accountCreatorName: "oauth", }) if err != nil { return xerrors.Errorf("create user: %w", err) } } - // Activate dormant user on sigin + // Activate dormant user on sign-in if user.Status == database.UserStatusDormant { //nolint:gocritic // System needs to update status of the user account (dormant -> active). user, err = tx.UpdateUserStatus(dbauthz.AsSystemRestricted(ctx), database.UpdateUserStatusParams{ diff --git a/coderd/users.go b/coderd/users.go index 48bc3ee15e4c5..2356bf2211a61 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -194,7 +194,8 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) { Password: createUser.Password, OrganizationIDs: []uuid.UUID{defaultOrg.ID}, }, - LoginType: database.LoginTypePassword, + LoginType: database.LoginTypePassword, + accountCreatorName: "coder", }) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ @@ -479,10 +480,22 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) { return } + apiKey := httpmw.APIKey(r) + + accountCreator, err := api.Database.GetUserByID(ctx, apiKey.UserID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Unable to determine the details of the actor creating the account.", + }) + return + } + user, err := api.CreateUser(ctx, api.Database, CreateUserRequest{ CreateUserRequestWithOrgs: req, LoginType: loginType, + accountCreatorName: accountCreator.Name, }) + if dbauthz.IsNotAuthorizedError(err) { httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ Message: "You are not authorized to create users.", @@ -576,11 +589,24 @@ func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) { return } + apiKey := httpmw.APIKey(r) + + accountDeleter, err := api.Database.GetUserByID(ctx, apiKey.UserID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Unable to determine the details of the actor deleting the account.", + }) + return + } + for _, u := range userAdmins { if _, err := api.NotificationsEnqueuer.Enqueue(ctx, u.ID, notifications.TemplateUserAccountDeleted, map[string]string{ - "deleted_account_name": user.Username, - }, "api-users-delete", + "deleted_account_name": user.Username, + "deleted_account_user_name": user.Name, + "account_deleter_user_name": accountDeleter.Name, + }, + "api-users-delete", user.ID, ); err != nil { api.Logger.Warn(ctx, "unable to notify about deleted user", slog.F("deleted_user", user.Username), slog.Error(err)) @@ -844,6 +870,14 @@ func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseW } } + actingUser, err := api.Database.GetUserByID(ctx, apiKey.UserID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Unable to determine the details of the actor creating the account.", + }) + return + } + targetUser, err := api.Database.UpdateUserStatus(ctx, database.UpdateUserStatusParams{ ID: user.ID, Status: status, @@ -858,7 +892,7 @@ func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseW } aReq.New = targetUser - err = api.notifyUserStatusChanged(ctx, user, status) + err = api.notifyUserStatusChanged(ctx, actingUser.Name, user, status) if err != nil { api.Logger.Warn(ctx, "unable to notify about changed user's status", slog.F("affected_user", user.Username), slog.Error(err)) } @@ -871,24 +905,33 @@ func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseW }) return } + httpapi.Write(ctx, rw, http.StatusOK, db2sdk.User(targetUser, organizations)) } } -func (api *API) notifyUserStatusChanged(ctx context.Context, user database.User, status database.UserStatus) error { - var key string +func (api *API) notifyUserStatusChanged(ctx context.Context, actingUserName string, targetUser database.User, status database.UserStatus) error { + var labels map[string]string var adminTemplateID, personalTemplateID uuid.UUID switch status { case database.UserStatusSuspended: - key = "suspended_account_name" + labels = map[string]string{ + "suspended_account_name": targetUser.Username, + "suspended_account_user_name": targetUser.Name, + "account_suspender_user_name": actingUserName, + } adminTemplateID = notifications.TemplateUserAccountSuspended personalTemplateID = notifications.TemplateYourAccountSuspended case database.UserStatusActive: - key = "activated_account_name" + labels = map[string]string{ + "activated_account_name": targetUser.Username, + "activated_account_user_name": targetUser.Name, + "account_activator_user_name": actingUserName, + } adminTemplateID = notifications.TemplateUserAccountActivated personalTemplateID = notifications.TemplateYourAccountActivated default: - api.Logger.Error(ctx, "user status is not supported", slog.F("username", user.Username), slog.F("user_status", string(status))) + api.Logger.Error(ctx, "user status is not supported", slog.F("username", targetUser.Username), slog.F("user_status", string(status))) return xerrors.Errorf("unable to notify admins as the user's status is unsupported") } @@ -900,21 +943,17 @@ func (api *API) notifyUserStatusChanged(ctx context.Context, user database.User, // Send notifications to user admins and affected user for _, u := range userAdmins { if _, err := api.NotificationsEnqueuer.Enqueue(ctx, u.ID, adminTemplateID, - map[string]string{ - key: user.Username, - }, "api-put-user-status", - user.ID, + labels, "api-put-user-status", + targetUser.ID, ); err != nil { - api.Logger.Warn(ctx, "unable to notify about changed user's status", slog.F("affected_user", user.Username), slog.Error(err)) + api.Logger.Warn(ctx, "unable to notify about changed user's status", slog.F("affected_user", targetUser.Username), slog.Error(err)) } } - if _, err := api.NotificationsEnqueuer.Enqueue(ctx, user.ID, personalTemplateID, - map[string]string{ - key: user.Username, - }, "api-put-user-status", - user.ID, + if _, err := api.NotificationsEnqueuer.Enqueue(ctx, targetUser.ID, personalTemplateID, + labels, "api-put-user-status", + targetUser.ID, ); err != nil { - api.Logger.Warn(ctx, "unable to notify user about status change of their account", slog.F("affected_user", user.Username), slog.Error(err)) + api.Logger.Warn(ctx, "unable to notify user about status change of their account", slog.F("affected_user", targetUser.Username), slog.Error(err)) } return nil } @@ -1280,8 +1319,9 @@ func (api *API) organizationByUserAndName(rw http.ResponseWriter, r *http.Reques type CreateUserRequest struct { codersdk.CreateUserRequestWithOrgs - LoginType database.LoginType - SkipNotifications bool + LoginType database.LoginType + SkipNotifications bool + accountCreatorName string } func (api *API) CreateUser(ctx context.Context, store database.Store, req CreateUserRequest) (database.User, error) { @@ -1365,13 +1405,16 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create for _, u := range userAdmins { if _, err := api.NotificationsEnqueuer.Enqueue(ctx, u.ID, notifications.TemplateUserAccountCreated, map[string]string{ - "created_account_name": user.Username, + "created_account_name": user.Username, + "created_account_user_name": user.Name, + "account_creator": req.accountCreatorName, }, "api-users-create", user.ID, ); err != nil { api.Logger.Warn(ctx, "unable to notify about created user", slog.F("created_user", user.Username), slog.Error(err)) } } + return user, err } diff --git a/coderd/users_test.go b/coderd/users_test.go index 1ab052d4aa470..7406df75bf1db 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -489,13 +489,16 @@ func TestNotifyDeletedUser(t *testing.T) { adminClient := coderdtest.New(t, &coderdtest.Options{ NotificationsEnqueuer: notifyEnq, }) - firstUser := coderdtest.CreateFirstUser(t, adminClient) + firstUserResponse := coderdtest.CreateFirstUser(t, adminClient) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() + firstUser, err := adminClient.User(ctx, firstUserResponse.UserID.String()) + require.NoError(t, err) + user, err := adminClient.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ - OrganizationIDs: []uuid.UUID{firstUser.OrganizationID}, + OrganizationIDs: []uuid.UUID{firstUserResponse.OrganizationID}, Email: "another@user.org", Username: "someone-else", Password: "SomeSecurePassword!", @@ -510,9 +513,11 @@ func TestNotifyDeletedUser(t *testing.T) { require.Len(t, notifyEnq.Sent, 2) // notifyEnq.Sent[0] is create account event require.Equal(t, notifications.TemplateUserAccountDeleted, notifyEnq.Sent[1].TemplateID) - require.Equal(t, firstUser.UserID, notifyEnq.Sent[1].UserID) + require.Equal(t, firstUser.ID, notifyEnq.Sent[1].UserID) require.Contains(t, notifyEnq.Sent[1].Targets, user.ID) require.Equal(t, user.Username, notifyEnq.Sent[1].Labels["deleted_account_name"]) + require.Equal(t, user.Name, notifyEnq.Sent[1].Labels["deleted_account_user_name"]) + require.Equal(t, firstUser.Name, notifyEnq.Sent[1].Labels["account_deleter_user_name"]) }) t.Run("UserAdminNotified", func(t *testing.T) { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy