feat(coderd/notifications): improve notification format consistency (#14967)

This Pull request addresses the more trivial items in
https://github.com/coder/coder/issues/14893.
These were simple formatting changes that I was able to fix despite
limited context.

Some more changes are required for which I will have to dig a bit deeper
into how the template contexts are populated. I'm happy to add those to
this PR or create a subsequent PR.
This commit is contained in:
Sas Swart
2024-10-10 00:31:12 +02:00
committed by GitHub
parent 26df33ac88
commit 9d02269191
24 changed files with 368 additions and 62 deletions

View File

@ -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';

View File

@ -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';

View File

@ -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
}
wantBody, err := os.ReadFile(bodyGoldenFile)
require.NoError(t, err, "open golden file, run \"DB=ci make update-golden-files\" and commit the changes")
wantTitle, err := os.ReadFile(titleGoldenFile)
require.NoError(t, err, "open golden file, run \"DB=ci make update-golden-files\" and commit the changes")
const hint = "run \"DB=ci make update-golden-files\" and commit the changes"
require.Equal(t, string(wantBody), body, "body should be equal")
require.Equal(t, string(wantTitle), title, "title should be equal")
wantBody, err := os.ReadFile(bodyGoldenFile)
require.NoError(t, err, fmt.Sprintf("missing golden notification body file. %s", hint))
wantTitle, err := os.ReadFile(titleGoldenFile)
require.NoError(t, err, fmt.Sprintf("missing golden notification title file. %s", hint))
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))
})
}
}

View File

@ -1,3 +1,5 @@
Hi Bobby
Hi Bobby,
The template **bobby-template** was deleted by **rob**.
The template **bobby-template** was deleted by **rob**.
The template's display name was **Bobby's Template**.

View File

@ -1,2 +1,5 @@
Hi Bobby,
User account **bobby** has been activated.
User account **bobby** has been activated.
The newly activated account belongs to **william tables** and was activated by **rob**.

View File

@ -1,3 +1,5 @@
Hi Bobby,
New user account **bobby** has been created.
New user account **bobby** has been created.
This new user account was created for **William Tables** by **rob**.

View File

@ -1,3 +1,5 @@
Hi Bobby,
User account **bobby** has been deleted.
User account **bobby** has been deleted.
The deleted account belonged to **william tables** and was deleted by **rob**.

View File

@ -1,2 +1,5 @@
Hi Bobby,
User account **bobby** has been suspended.
User account **bobby** has been suspended.
The newly suspended account belongs to **william tables** and was suspended by **rob**.

View File

@ -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**
Reason for update: **template now includes catnip**.

View File

@ -1,3 +1,5 @@
Hi Bobby
Hi Bobby,
Automatic build of your workspace **bobby-workspace** failed.
The specified reason was "**autostart**".

View File

@ -1,4 +1,5 @@
Hi Bobby
Hi Bobby,
Your workspace **bobby-workspace** was deleted.
The specified reason was "**autodeleted due to dormancy (autobuild)**".

View File

@ -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.

View File

@ -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**.
The template's display name was **William's Template**. The workspace build was initiated by **joe**.

View File

@ -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.

View File

@ -1,2 +1,3 @@
Hi Bobby,
Your account **bobby** has been activated.
Your account **bobby** has been activated by **rob**.

View File

@ -1,2 +1,3 @@
Hi Bobby,
Your account **bobby** has been suspended.
Your account **bobby** has been suspended by **rob**.

View File

@ -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,

View File

@ -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"])

View File

@ -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,

View File

@ -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)
}
})

View File

@ -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{

View File

@ -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
}

View File

@ -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) {