Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: add notification for suspended/activated account#14367

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
mtojek merged 20 commits intomainfrom17-suspended-reactivated
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
20 commits
Select commitHold shift + click to select a range
000a917
migrations
mtojekAug 20, 2024
62cf15f
notify
mtojekAug 20, 2024
323680c
fix
mtojekAug 20, 2024
bbfb296
TestNotifyUserSuspended
mtojekAug 21, 2024
e1b6c28
TestNotifyUserReactivate
mtojekAug 21, 2024
9ef46de
Merge branch 'main' into 17-suspended-reactivated
mtojekAug 21, 2024
c7783ac
post merge
mtojekAug 21, 2024
55e4e13
fix escape
mtojekAug 21, 2024
38bca28
TestNotificationTemplatesCanRender
mtojekAug 21, 2024
37add7d
links and events
mtojekAug 22, 2024
baf1a95
notifyEnq
mtojekAug 22, 2024
a674476
findUserAdmins
mtojekAug 22, 2024
96611b1
notifyUserStatusChanged
mtojekAug 22, 2024
fabba3c
go build
mtojekAug 22, 2024
e72bf2a
your and admin
mtojekAug 22, 2024
80b227a
tests
mtojekAug 22, 2024
d27bc78
refactor
mtojekAug 22, 2024
a9d8fb6
Merge branch 'main' into 17-suspended-reactivated
mtojekAug 22, 2024
b789a53
247
mtojekAug 22, 2024
9931bbf
Danny's review
mtojekAug 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
DELETE FROM notification_templates WHERE id = 'b02ddd82-4733-4d02-a2d7-c36f3598997d';
DELETE FROM notification_templates WHERE id = '6a2f0609-9b69-4d36-a989-9f5925b6cbff';
DELETE FROM notification_templates WHERE id = '9f5af851-8408-4e73-a7a1-c6502ba46689';
DELETE FROM notification_templates WHERE id = '1a6a6bea-ee0a-43e2-9e7c-eabdb53730e4';
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
INSERT INTO notification_templates (id, name, title_template, body_template, "group", actions)
VALUES ('b02ddd82-4733-4d02-a2d7-c36f3598997d', 'User account suspended', E'User account "{{.Labels.suspended_account_name}}" suspended',
E'Hi {{.UserName}},\nUser account **{{.Labels.suspended_account_name}}** has been suspended.',
'User Events', '[
{
"label": "View suspended accounts",
"url": "{{ base_url }}/deployment/users?filter=status%3Asuspended"
}
]'::jsonb);
INSERT INTO notification_templates (id, name, title_template, body_template, "group", actions)
VALUES ('6a2f0609-9b69-4d36-a989-9f5925b6cbff', 'Your account has been suspended', E'Your account "{{.Labels.suspended_account_name}}" has been suspended',
E'Hi {{.UserName}},\nYour account **{{.Labels.suspended_account_name}}** has been suspended.',
'User Events', '[]'::jsonb);
INSERT INTO notification_templates (id, name, title_template, body_template, "group", actions)
VALUES ('9f5af851-8408-4e73-a7a1-c6502ba46689', 'User account activated', E'User account "{{.Labels.activated_account_name}}" activated',
E'Hi {{.UserName}},\nUser account **{{.Labels.activated_account_name}}** has been activated.',
'User Events', '[
{
"label": "View accounts",
"url": "{{ base_url }}/deployment/users?filter=status%3Aactive"
}
]'::jsonb);
INSERT INTO notification_templates (id, name, title_template, body_template, "group", actions)
VALUES ('1a6a6bea-ee0a-43e2-9e7c-eabdb53730e4', 'Your account has been activated', E'Your account "{{.Labels.activated_account_name}}" has been activated',
E'Hi {{.UserName}},\nYour account **{{.Labels.activated_account_name}}** has been activated.',
'User Events', '[
{
"label": "Open Coder",
"url": "{{ base_url }}"
}
]'::jsonb);
5 changes: 5 additions & 0 deletionscoderd/notifications/events.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,11 @@ var (
var (
TemplateUserAccountCreated = uuid.MustParse("4e19c0ac-94e1-4532-9515-d1801aa283b2")
TemplateUserAccountDeleted = uuid.MustParse("f44d9314-ad03-4bc8-95d0-5cad491da6b6")

TemplateUserAccountSuspended = uuid.MustParse("b02ddd82-4733-4d02-a2d7-c36f3598997d")
TemplateUserAccountActivated = uuid.MustParse("9f5af851-8408-4e73-a7a1-c6502ba46689")
TemplateYourAccountSuspended = uuid.MustParse("6a2f0609-9b69-4d36-a989-9f5925b6cbff")
TemplateYourAccountActivated = uuid.MustParse("1a6a6bea-ee0a-43e2-9e7c-eabdb53730e4")
)

// Template-related events.
Expand Down
40 changes: 40 additions & 0 deletionscoderd/notifications/notifications_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -756,6 +756,46 @@ func TestNotificationTemplatesCanRender(t *testing.T) {
},
},
},
{
name: "TemplateUserAccountSuspended",
id: notifications.TemplateUserAccountSuspended,
payload: types.MessagePayload{
UserName: "bobby",
Labels: map[string]string{
"suspended_account_name": "bobby",
},
},
},
{
name: "TemplateUserAccountActivated",
id: notifications.TemplateUserAccountActivated,
payload: types.MessagePayload{
UserName: "bobby",
Labels: map[string]string{
"activated_account_name": "bobby",
},
},
},
{
name: "TemplateYourAccountSuspended",
id: notifications.TemplateYourAccountSuspended,
payload: types.MessagePayload{
UserName: "bobby",
Labels: map[string]string{
"suspended_account_name": "bobby",
},
},
},
{
name: "TemplateYourAccountActivated",
id: notifications.TemplateYourAccountActivated,
payload: types.MessagePayload{
UserName: "bobby",
Labels: map[string]string{
"activated_account_name": "bobby",
},
},
},
{
name: "TemplateTemplateDeleted",
id: notifications.TemplateTemplateDeleted,
Expand Down
54 changes: 51 additions & 3 deletionscoderd/users.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -845,7 +845,7 @@ func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseW
}
}

suspendedUser, err := api.Database.UpdateUserStatus(ctx, database.UpdateUserStatusParams{
targetUser, err := api.Database.UpdateUserStatus(ctx, database.UpdateUserStatusParams{
ID: user.ID,
Status: status,
UpdatedAt: dbtime.Now(),
Expand All@@ -857,7 +857,12 @@ func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseW
})
return
}
aReq.New = suspendedUser
aReq.New = targetUser

err = api.notifyUserStatusChanged(ctx, 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))
}

organizations, err := userOrganizationIDs(ctx, api, user)
if err != nil {
Expand All@@ -867,9 +872,52 @@ 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
var adminTemplateID, personalTemplateID uuid.UUID
switch status {
case database.UserStatusSuspended:
key = "suspended_account_name"
adminTemplateID = notifications.TemplateUserAccountSuspended
personalTemplateID = notifications.TemplateYourAccountSuspended
case database.UserStatusActive:
key = "activated_account_name"
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)))
return xerrors.Errorf("unable to notify admins as the user's status is unsupported")
}

userAdmins, err := findUserAdmins(ctx, api.Database)
if err != nil {
api.Logger.Error(ctx, "unable to find user admins", slog.Error(err))
}

httpapi.Write(ctx, rw, http.StatusOK, db2sdk.User(suspendedUser, organizations))
// 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,
); err != nil {
api.Logger.Warn(ctx, "unable to notify about changed user's status", slog.F("affected_user", user.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,
); 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))
}
return nil
}

// @Summary Update user appearance settings
Expand Down
104 changes: 104 additions & 0 deletionscoderd/users_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -374,6 +374,110 @@ func TestDeleteUser(t *testing.T) {
})
}

func TestNotifyUserStatusChanged(t *testing.T) {
t.Parallel()

type expectedNotification struct {
TemplateID uuid.UUID
UserID uuid.UUID
}

verifyNotificationDispatched := func(notifyEnq *testutil.FakeNotificationsEnqueuer, expectedNotifications []expectedNotification, member codersdk.User, label string) {
require.Equal(t, len(expectedNotifications), len(notifyEnq.Sent))

// Validate that each expected notification is present in notifyEnq.Sent
for _, expected := range expectedNotifications {
found := false
for _, sent := range notifyEnq.Sent {
if sent.TemplateID == expected.TemplateID &&
sent.UserID == expected.UserID &&
slices.Contains(sent.Targets, member.ID) &&
sent.Labels[label] == member.Username {
found = true
break
}
}
require.True(t, found, "Expected notification not found: %+v", expected)
}
}

t.Run("Account suspended", func(t *testing.T) {
t.Parallel()

notifyEnq := &testutil.FakeNotificationsEnqueuer{}
adminClient := coderdtest.New(t, &coderdtest.Options{
NotificationsEnqueuer: notifyEnq,
})
firstUser := coderdtest.CreateFirstUser(t, adminClient)

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()

_, userAdmin := coderdtest.CreateAnotherUser(t, adminClient, firstUser.OrganizationID, rbac.RoleUserAdmin())

member, err := adminClient.CreateUser(ctx, codersdk.CreateUserRequest{
OrganizationID: firstUser.OrganizationID,
Email: "another@user.org",
Username: "someone-else",
Password: "SomeSecurePassword!",
})
require.NoError(t, err)

notifyEnq.Clear()

// when
_, err = adminClient.UpdateUserStatus(context.Background(), member.Username, codersdk.UserStatusSuspended)
require.NoError(t, err)

// then
verifyNotificationDispatched(notifyEnq, []expectedNotification{
{TemplateID: notifications.TemplateUserAccountSuspended, UserID: firstUser.UserID},
{TemplateID: notifications.TemplateUserAccountSuspended, UserID: userAdmin.ID},
{TemplateID: notifications.TemplateYourAccountSuspended, UserID: member.ID},
}, member, "suspended_account_name")
})

t.Run("Account reactivated", func(t *testing.T) {
t.Parallel()

// given
notifyEnq := &testutil.FakeNotificationsEnqueuer{}
adminClient := coderdtest.New(t, &coderdtest.Options{
NotificationsEnqueuer: notifyEnq,
})
firstUser := coderdtest.CreateFirstUser(t, adminClient)

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()

_, userAdmin := coderdtest.CreateAnotherUser(t, adminClient, firstUser.OrganizationID, rbac.RoleUserAdmin())

member, err := adminClient.CreateUser(ctx, codersdk.CreateUserRequest{
OrganizationID: firstUser.OrganizationID,
Email: "another@user.org",
Username: "someone-else",
Password: "SomeSecurePassword!",
})
require.NoError(t, err)

_, err = adminClient.UpdateUserStatus(context.Background(), member.Username, codersdk.UserStatusSuspended)
require.NoError(t, err)

notifyEnq.Clear()

// when
_, err = adminClient.UpdateUserStatus(context.Background(), member.Username, codersdk.UserStatusActive)
require.NoError(t, err)

// then
verifyNotificationDispatched(notifyEnq, []expectedNotification{
{TemplateID: notifications.TemplateUserAccountActivated, UserID: firstUser.UserID},
{TemplateID: notifications.TemplateUserAccountActivated, UserID: userAdmin.ID},
{TemplateID: notifications.TemplateYourAccountActivated, UserID: member.ID},
}, member, "activated_account_name")
})
}

func TestNotifyDeletedUser(t *testing.T) {
t.Parallel()

Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp