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

Commit6f1951e

Browse files
feat: add template delete notification (#14250)
1 parent86b9c97 commit6f1951e

File tree

7 files changed

+195
-1
lines changed

7 files changed

+195
-1
lines changed

‎coderd/database/migrations/000244_notifications_delete_template.down.sql

Whitespace-only changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
INSERT INTO
2+
notification_templates (
3+
id,
4+
name,
5+
title_template,
6+
body_template,
7+
"group",
8+
actions
9+
)
10+
VALUES (
11+
'29a09665-2a4c-403f-9648-54301670e7be',
12+
'Template Deleted',
13+
E'Template "{{.Labels.name}}" deleted',
14+
E'Hi {{.UserName}}\n\nThe template **{{.Labels.name}}** was deleted by **{{ .Labels.initiator }}**.',
15+
'Template Events',
16+
'[
17+
{
18+
"label": "View templates",
19+
"url": "{{ base_url }}/templates"
20+
}
21+
]'::jsonb
22+
);

‎coderd/notifications/events.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,8 @@ var (
1919
TemplateUserAccountCreated=uuid.MustParse("4e19c0ac-94e1-4532-9515-d1801aa283b2")
2020
TemplateUserAccountDeleted=uuid.MustParse("f44d9314-ad03-4bc8-95d0-5cad491da6b6")
2121
)
22+
23+
// Template-related events.
24+
var (
25+
TemplateTemplateDeleted=uuid.MustParse("29a09665-2a4c-403f-9648-54301670e7be")
26+
)

‎coderd/notifications/notifications_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,17 @@ func TestNotificationTemplatesCanRender(t *testing.T) {
740740
},
741741
},
742742
},
743+
{
744+
name:"TemplateTemplateDeleted",
745+
id:notifications.TemplateTemplateDeleted,
746+
payload: types.MessagePayload{
747+
UserName:"bobby",
748+
Labels:map[string]string{
749+
"name":"bobby-template",
750+
"initiator":"rob",
751+
},
752+
},
753+
},
743754
}
744755

745756
allTemplates,err:=enumerateAllTemplates(t)

‎coderd/templates.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package coderd
22

33
import (
4+
"context"
45
"database/sql"
56
"errors"
67
"fmt"
@@ -12,12 +13,15 @@ import (
1213
"github.com/google/uuid"
1314
"golang.org/x/xerrors"
1415

16+
"cdr.dev/slog"
17+
1518
"github.com/coder/coder/v2/coderd/audit"
1619
"github.com/coder/coder/v2/coderd/database"
1720
"github.com/coder/coder/v2/coderd/database/dbauthz"
1821
"github.com/coder/coder/v2/coderd/database/dbtime"
1922
"github.com/coder/coder/v2/coderd/httpapi"
2023
"github.com/coder/coder/v2/coderd/httpmw"
24+
"github.com/coder/coder/v2/coderd/notifications"
2125
"github.com/coder/coder/v2/coderd/rbac"
2226
"github.com/coder/coder/v2/coderd/rbac/policy"
2327
"github.com/coder/coder/v2/coderd/schedule"
@@ -56,6 +60,7 @@ func (api *API) template(rw http.ResponseWriter, r *http.Request) {
5660
// @Router /templates/{template} [delete]
5761
func (api*API)deleteTemplate(rw http.ResponseWriter,r*http.Request) {
5862
var (
63+
apiKey=httpmw.APIKey(r)
5964
ctx=r.Context()
6065
template=httpmw.TemplateParam(r)
6166
auditor=*api.Auditor.Load()
@@ -101,11 +106,47 @@ func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) {
101106
})
102107
return
103108
}
109+
110+
admins,err:=findTemplateAdmins(ctx,api.Database)
111+
iferr!=nil {
112+
httpapi.Write(ctx,rw,http.StatusInternalServerError, codersdk.Response{
113+
Message:"Internal error fetching template admins.",
114+
Detail:err.Error(),
115+
})
116+
return
117+
}
118+
for_,admin:=rangeadmins {
119+
// Don't send notification to user which initiated the event.
120+
ifadmin.ID==apiKey.UserID {
121+
continue
122+
}
123+
api.notifyTemplateDeleted(ctx,template,apiKey.UserID,admin.ID)
124+
}
125+
104126
httpapi.Write(ctx,rw,http.StatusOK, codersdk.Response{
105127
Message:"Template has been deleted!",
106128
})
107129
}
108130

131+
func (api*API)notifyTemplateDeleted(ctx context.Context,template database.Template,initiatorID uuid.UUID,receiverID uuid.UUID) {
132+
initiator,err:=api.Database.GetUserByID(ctx,initiatorID)
133+
iferr!=nil {
134+
api.Logger.Warn(ctx,"failed to fetch initiator for template deletion notification",slog.F("initiator_id",initiatorID),slog.Error(err))
135+
return
136+
}
137+
138+
if_,err:=api.NotificationsEnqueuer.Enqueue(ctx,receiverID,notifications.TemplateTemplateDeleted,
139+
map[string]string{
140+
"name":template.Name,
141+
"initiator":initiator.Username,
142+
},"api-templates-delete",
143+
// Associate this notification with all the related entities.
144+
template.ID,template.OrganizationID,
145+
);err!=nil {
146+
api.Logger.Warn(ctx,"failed to notify of template deletion",slog.F("deleted_template_id",template.ID),slog.Error(err))
147+
}
148+
}
149+
109150
// Create a new template in an organization.
110151
// Returns a single template.
111152
//
@@ -948,3 +989,22 @@ func (api *API) convertTemplate(
948989
MaxPortShareLevel:maxPortShareLevel,
949990
}
950991
}
992+
993+
// findTemplateAdmins fetches all users with template admin permission including owners.
994+
funcfindTemplateAdmins(ctx context.Context,store database.Store) ([]database.GetUsersRow,error) {
995+
// Notice: we can't scrape the user information in parallel as pq
996+
// fails with: unexpected describe rows response: 'D'
997+
owners,err:=store.GetUsers(ctx, database.GetUsersParams{
998+
RbacRole: []string{codersdk.RoleOwner},
999+
})
1000+
iferr!=nil {
1001+
returnnil,xerrors.Errorf("get owners: %w",err)
1002+
}
1003+
templateAdmins,err:=store.GetUsers(ctx, database.GetUsersParams{
1004+
RbacRole: []string{codersdk.RoleTemplateAdmin},
1005+
})
1006+
iferr!=nil {
1007+
returnnil,xerrors.Errorf("get template admins: %w",err)
1008+
}
1009+
returnappend(owners,templateAdmins...),nil
1010+
}

‎coderd/templates_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/coder/coder/v2/coderd/database"
2020
"github.com/coder/coder/v2/coderd/database/dbauthz"
2121
"github.com/coder/coder/v2/coderd/database/dbtime"
22+
"github.com/coder/coder/v2/coderd/notifications"
2223
"github.com/coder/coder/v2/coderd/rbac"
2324
"github.com/coder/coder/v2/coderd/schedule"
2425
"github.com/coder/coder/v2/coderd/util/ptr"
@@ -1326,3 +1327,98 @@ func TestTemplateMetrics(t *testing.T) {
13261327
dbtime.Now(),res.Workspaces[0].LastUsedAt,time.Minute,
13271328
)
13281329
}
1330+
1331+
funcTestTemplateNotifications(t*testing.T) {
1332+
t.Parallel()
1333+
1334+
t.Run("Delete",func(t*testing.T) {
1335+
t.Parallel()
1336+
1337+
t.Run("InitiatorIsNotNotified",func(t*testing.T) {
1338+
t.Parallel()
1339+
1340+
// Given: an initiator
1341+
var (
1342+
notifyEnq=&testutil.FakeNotificationsEnqueuer{}
1343+
client=coderdtest.New(t,&coderdtest.Options{
1344+
IncludeProvisionerDaemon:true,
1345+
NotificationsEnqueuer:notifyEnq,
1346+
})
1347+
initiator=coderdtest.CreateFirstUser(t,client)
1348+
version=coderdtest.CreateTemplateVersion(t,client,initiator.OrganizationID,nil)
1349+
_=coderdtest.AwaitTemplateVersionJobCompleted(t,client,version.ID)
1350+
template=coderdtest.CreateTemplate(t,client,initiator.OrganizationID,version.ID)
1351+
ctx=testutil.Context(t,testutil.WaitLong)
1352+
)
1353+
1354+
// When: the template is deleted by the initiator
1355+
err:=client.DeleteTemplate(ctx,template.ID)
1356+
require.NoError(t,err)
1357+
1358+
// Then: the delete notification is not sent to the initiator.
1359+
deleteNotifications:=make([]*testutil.Notification,0)
1360+
for_,n:=rangenotifyEnq.Sent {
1361+
ifn.TemplateID==notifications.TemplateTemplateDeleted {
1362+
deleteNotifications=append(deleteNotifications,n)
1363+
}
1364+
}
1365+
require.Len(t,deleteNotifications,0)
1366+
})
1367+
1368+
t.Run("OnlyOwnersAndAdminsAreNotified",func(t*testing.T) {
1369+
t.Parallel()
1370+
1371+
// Given: multiple users with different roles
1372+
var (
1373+
notifyEnq=&testutil.FakeNotificationsEnqueuer{}
1374+
client=coderdtest.New(t,&coderdtest.Options{
1375+
IncludeProvisionerDaemon:true,
1376+
NotificationsEnqueuer:notifyEnq,
1377+
})
1378+
initiator=coderdtest.CreateFirstUser(t,client)
1379+
ctx=testutil.Context(t,testutil.WaitLong)
1380+
1381+
// Setup template
1382+
version=coderdtest.CreateTemplateVersion(t,client,initiator.OrganizationID,nil)
1383+
_=coderdtest.AwaitTemplateVersionJobCompleted(t,client,version.ID)
1384+
template=coderdtest.CreateTemplate(t,client,initiator.OrganizationID,version.ID)
1385+
)
1386+
1387+
// Setup users with different roles
1388+
_,owner:=coderdtest.CreateAnotherUser(t,client,initiator.OrganizationID,rbac.RoleOwner())
1389+
_,tmplAdmin:=coderdtest.CreateAnotherUser(t,client,initiator.OrganizationID,rbac.RoleTemplateAdmin())
1390+
coderdtest.CreateAnotherUser(t,client,initiator.OrganizationID,rbac.RoleMember())
1391+
coderdtest.CreateAnotherUser(t,client,initiator.OrganizationID,rbac.RoleUserAdmin())
1392+
coderdtest.CreateAnotherUser(t,client,initiator.OrganizationID,rbac.RoleAuditor())
1393+
1394+
// When: the template is deleted by the initiator
1395+
err:=client.DeleteTemplate(ctx,template.ID)
1396+
require.NoError(t,err)
1397+
1398+
// Then: only owners and template admins should receive the
1399+
// notification.
1400+
shouldBeNotified:= []uuid.UUID{owner.ID,tmplAdmin.ID}
1401+
vardeleteTemplateNotifications []*testutil.Notification
1402+
for_,n:=rangenotifyEnq.Sent {
1403+
ifn.TemplateID==notifications.TemplateTemplateDeleted {
1404+
deleteTemplateNotifications=append(deleteTemplateNotifications,n)
1405+
}
1406+
}
1407+
notifiedUsers:=make([]uuid.UUID,0,len(deleteTemplateNotifications))
1408+
for_,n:=rangedeleteTemplateNotifications {
1409+
notifiedUsers=append(notifiedUsers,n.UserID)
1410+
}
1411+
require.ElementsMatch(t,shouldBeNotified,notifiedUsers)
1412+
1413+
// Validate the notification content
1414+
for_,n:=rangedeleteTemplateNotifications {
1415+
require.Equal(t,n.TemplateID,notifications.TemplateTemplateDeleted)
1416+
require.Contains(t,notifiedUsers,n.UserID)
1417+
require.Contains(t,n.Targets,template.ID)
1418+
require.Contains(t,n.Targets,template.OrganizationID)
1419+
require.Equal(t,n.Labels["name"],template.Name)
1420+
require.Equal(t,n.Labels["initiator"],coderdtest.FirstUserParams.Username)
1421+
}
1422+
})
1423+
})
1424+
}

‎coderd/workspaces_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3441,7 +3441,7 @@ func TestWorkspaceUsageTracking(t *testing.T) {
34413441
})
34423442
}
34433443

3444-
funcTestNotifications(t*testing.T) {
3444+
funcTestWorkspaceNotifications(t*testing.T) {
34453445
t.Parallel()
34463446

34473447
t.Run("Dormant",func(t*testing.T) {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp