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

Commitf0e81ab

Browse files
feat: notify on workspace creation (#15934)
1 parentf5d3f71 commitf0e81ab

File tree

10 files changed

+291
-14
lines changed

10 files changed

+291
-14
lines changed

‎coderd/autobuild/lifecycle_executor_test.go‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
274274
}
275275

276276
iftc.expectNotification {
277-
sent:=enqueuer.Sent()
277+
sent:=enqueuer.Sent(notificationstest.WithTemplateID(notifications.TemplateWorkspaceAutoUpdated))
278278
require.Len(t,sent,1)
279279
require.Equal(t,sent[0].UserID,workspace.OwnerID)
280280
require.Contains(t,sent[0].Targets,workspace.TemplateID)
@@ -285,7 +285,8 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
285285
require.Equal(t,"autobuild",sent[0].Labels["initiator"])
286286
require.Equal(t,"autostart",sent[0].Labels["reason"])
287287
}else {
288-
require.Empty(t,enqueuer.Sent())
288+
sent:=enqueuer.Sent(notificationstest.WithTemplateID(notifications.TemplateWorkspaceAutoUpdated))
289+
require.Empty(t,sent)
289290
}
290291
})
291292
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DELETEFROM notification_templatesWHERE id='281fdf73-c6d6-4cbb-8ff5-888baf8a2fff';
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
INSERT INTO notification_templates
2+
(id, name, title_template, body_template,"group", actions)
3+
VALUES (
4+
'281fdf73-c6d6-4cbb-8ff5-888baf8a2fff',
5+
'Workspace Created',
6+
E'Workspace''{{.Labels.workspace}}'' has been created',
7+
E'Hello {{.UserName}},\n\n'||
8+
E'The workspace **{{.Labels.workspace}}** has been created from the template **{{.Labels.template}}** using version **{{.Labels.version}}**.',
9+
'Workspace Events',
10+
'[
11+
{
12+
"label": "See workspace",
13+
"url": "{{base_url}}/@{{.UserUsername}}/{{.Labels.workspace}}"
14+
}
15+
]'::jsonb
16+
);

‎coderd/notifications/events.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import "github.com/google/uuid"
77

88
// Workspace-related events.
99
var (
10+
TemplateWorkspaceCreated=uuid.MustParse("281fdf73-c6d6-4cbb-8ff5-888baf8a2fff")
1011
TemplateWorkspaceDeleted=uuid.MustParse("f517da0b-cdc9-410f-ab89-a86107c420ed")
1112
TemplateWorkspaceAutobuildFailed=uuid.MustParse("381df2a9-c0c0-4749-420f-80a9280c66f9")
1213
TemplateWorkspaceDormant=uuid.MustParse("0ea69165-ec14-4314-91f1-69566ac3c5a0")

‎coderd/notifications/notifications_test.go‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,20 @@ func TestNotificationTemplates_Golden(t *testing.T) {
10341034
},
10351035
},
10361036
},
1037+
{
1038+
name:"TemplateWorkspaceCreated",
1039+
id:notifications.TemplateWorkspaceCreated,
1040+
payload: types.MessagePayload{
1041+
UserName:"Bobby",
1042+
UserEmail:"bobby@coder.com",
1043+
UserUsername:"bobby",
1044+
Labels:map[string]string{
1045+
"workspace":"bobby-workspace",
1046+
"template":"bobby-template",
1047+
"version":"alpha",
1048+
},
1049+
},
1050+
},
10371051
}
10381052

10391053
// We must have a test case for every notification_template. This is enforced below:

‎coderd/notifications/notificationstest/fake_enqueuer.go‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,31 @@ func (f *FakeEnqueuer) Clear() {
9292
f.sent=nil
9393
}
9494

95-
func (f*FakeEnqueuer)Sent() []*FakeNotification {
95+
func (f*FakeEnqueuer)Sent(matchers...func(*FakeNotification)bool) []*FakeNotification {
9696
f.mu.Lock()
9797
deferf.mu.Unlock()
98-
returnappend([]*FakeNotification{},f.sent...)
98+
99+
sent:= []*FakeNotification{}
100+
for_,notif:=rangef.sent {
101+
// Check this notification matches all given matchers
102+
matches:=true
103+
for_,matcher:=rangematchers {
104+
if!matcher(notif) {
105+
matches=false
106+
break
107+
}
108+
}
109+
110+
ifmatches {
111+
sent=append(sent,notif)
112+
}
113+
}
114+
115+
returnsent
116+
}
117+
118+
funcWithTemplateID(id uuid.UUID)func(*FakeNotification)bool {
119+
returnfunc(n*FakeNotification)bool {
120+
returnn.TemplateID==id
121+
}
99122
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
From: system@coder.com
2+
To: bobby@coder.com
3+
Subject: Workspace 'bobby-workspace' has been created
4+
Message-Id: 02ee4935-73be-4fa1-a290-ff9999026b13@blush-whale-48
5+
Date: Fri, 11 Oct 2024 09:03:06 +0000
6+
Content-Type: multipart/alternative; boundary=bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
7+
MIME-Version: 1.0
8+
9+
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
10+
Content-Transfer-Encoding: quoted-printable
11+
Content-Type: text/plain; charset=UTF-8
12+
13+
Hello Bobby,
14+
15+
The workspace bobby-workspace has been created from the template bobby-temp=
16+
late using version alpha.
17+
18+
19+
See workspace: http://test.com/@bobby/bobby-workspace
20+
21+
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4
22+
Content-Transfer-Encoding: quoted-printable
23+
Content-Type: text/html; charset=UTF-8
24+
25+
<!doctype html>
26+
<html lang=3D"en">
27+
<head>
28+
<meta charset=3D"UTF-8" />
29+
<meta name=3D"viewport" content=3D"width=3Ddevice-width, initial-scale=
30+
=3D1.0" />
31+
<title>Workspace 'bobby-workspace' has been created</title>
32+
</head>
33+
<body style=3D"margin: 0; padding: 0; font-family: -apple-system, system-=
34+
ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarel=
35+
l', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617=
36+
; background: #f8fafc;">
37+
<div style=3D"max-width: 600px; margin: 20px auto; padding: 60px; borde=
38+
r: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-alig=
39+
n: left; font-size: 14px; line-height: 1.5;">
40+
<div style=3D"text-align: center;">
41+
<img src=3D"https://coder.com/coder-logo-horizontal.png" alt=3D"Cod=
42+
er Logo" style=3D"height: 40px;" />
43+
</div>
44+
<h1 style=3D"text-align: center; font-size: 24px; font-weight: 400; m=
45+
argin: 8px 0 32px; line-height: 1.5;">
46+
Workspace 'bobby-workspace' has been created
47+
</h1>
48+
<div style=3D"line-height: 1.5;">
49+
<p>Hello Bobby,</p>
50+
51+
<p>The workspace <strong>bobby-workspace</strong> has been created from the=
52+
template <strong>bobby-template</strong> using version <strong>alpha</stro=
53+
ng>.</p>
54+
</div>
55+
<div style=3D"text-align: center; margin-top: 32px;">
56+
=20
57+
<a href=3D"http://test.com/@bobby/bobby-workspace" style=3D"display=
58+
: inline-block; padding: 13px 24px; background-color: #020617; color: #f8fa=
59+
fc; text-decoration: none; border-radius: 8px; margin: 0 4px;">
60+
See workspace
61+
</a>
62+
=20
63+
</div>
64+
<div style=3D"border-top: 1px solid #e2e8f0; color: #475569; font-siz=
65+
e: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
66+
<p>&copy;&nbsp;2024&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a =
67+
href=3D"http://test.com" style=3D"color: #2563eb; text-decoration: none;">h=
68+
ttp://test.com</a></p>
69+
<p><a href=3D"http://test.com/settings/notifications" style=3D"colo=
70+
r: #2563eb; text-decoration: none;">Click here to manage your notification =
71+
settings</a></p>
72+
<p><a href=3D"http://test.com/settings/notifications?disabled=3D281=
73+
fdf73-c6d6-4cbb-8ff5-888baf8a2fff" style=3D"color: #2563eb; text-decoration=
74+
: none;">Stop receiving emails like this</a></p>
75+
</div>
76+
</div>
77+
</body>
78+
</html>
79+
80+
--bbe61b741255b6098bb6b3c1f41b885773df633cb18d2a3002b68e4bc9c4--
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"_version": "1.1",
3+
"msg_id": "00000000-0000-0000-0000-000000000000",
4+
"payload": {
5+
"_version": "1.1",
6+
"notification_name": "Workspace Created",
7+
"notification_template_id": "00000000-0000-0000-0000-000000000000",
8+
"user_id": "00000000-0000-0000-0000-000000000000",
9+
"user_email": "bobby@coder.com",
10+
"user_name": "Bobby",
11+
"user_username": "bobby",
12+
"actions": [
13+
{
14+
"label": "See workspace",
15+
"url": "http://test.com/@bobby/bobby-workspace"
16+
}
17+
],
18+
"labels": {
19+
"template": "bobby-template",
20+
"version": "alpha",
21+
"workspace": "bobby-workspace"
22+
},
23+
"data": null
24+
},
25+
"title": "Workspace 'bobby-workspace' has been created",
26+
"title_markdown": "Workspace 'bobby-workspace' has been created",
27+
"body": "Hello Bobby,\n\nThe workspace bobby-workspace has been created from the template bobby-template using version alpha.",
28+
"body_markdown": "Hello Bobby,\n\nThe workspace **bobby-workspace** has been created from the template **bobby-template** using version **alpha**."
29+
}

‎coderd/workspaces.go‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,8 @@ func createWorkspace(
666666
returnerr
667667
},nil)
668668

669+
api.notifyWorkspaceCreated(ctx,workspace,req.RichParameterValues)
670+
669671
varbldErr wsbuilder.BuildError
670672
ifxerrors.As(err,&bldErr) {
671673
httpapi.Write(ctx,rw,bldErr.Status, codersdk.Response{
@@ -735,6 +737,64 @@ func createWorkspace(
735737
httpapi.Write(ctx,rw,http.StatusCreated,w)
736738
}
737739

740+
func (api*API)notifyWorkspaceCreated(
741+
ctx context.Context,
742+
workspace database.Workspace,
743+
parameters []codersdk.WorkspaceBuildParameter,
744+
) {
745+
log:=api.Logger.With(slog.F("workspace_id",workspace.ID))
746+
747+
template,err:=api.Database.GetTemplateByID(ctx,workspace.TemplateID)
748+
iferr!=nil {
749+
log.Warn(ctx,"failed to fetch template for workspace creation notification",slog.F("template_id",workspace.TemplateID),slog.Error(err))
750+
return
751+
}
752+
753+
owner,err:=api.Database.GetUserByID(ctx,workspace.OwnerID)
754+
iferr!=nil {
755+
log.Warn(ctx,"failed to fetch user for workspace creation notification",slog.F("owner_id",workspace.OwnerID),slog.Error(err))
756+
return
757+
}
758+
759+
version,err:=api.Database.GetTemplateVersionByID(ctx,template.ActiveVersionID)
760+
iferr!=nil {
761+
log.Warn(ctx,"failed to fetch template version for workspace creation notification",slog.F("template_version_id",template.ActiveVersionID),slog.Error(err))
762+
return
763+
}
764+
765+
buildParameters:=make([]map[string]any,len(parameters))
766+
foridx,parameter:=rangeparameters {
767+
buildParameters[idx]=map[string]any{
768+
"name":parameter.Name,
769+
"value":parameter.Value,
770+
}
771+
}
772+
773+
if_,err:=api.NotificationsEnqueuer.EnqueueWithData(
774+
// nolint:gocritic // Need notifier actor to enqueue notifications
775+
dbauthz.AsNotifier(ctx),
776+
workspace.OwnerID,
777+
notifications.TemplateWorkspaceCreated,
778+
map[string]string{
779+
"workspace":workspace.Name,
780+
"template":template.Name,
781+
"version":version.Name,
782+
},
783+
map[string]any{
784+
"workspace":map[string]any{"id":workspace.ID,"name":workspace.Name},
785+
"template":map[string]any{"id":template.ID,"name":template.Name},
786+
"template_version":map[string]any{"id":version.ID,"name":version.Name},
787+
"owner":map[string]any{"id":owner.ID,"name":owner.Name},
788+
"parameters":buildParameters,
789+
},
790+
"api-workspaces-create",
791+
// Associate this notification with all the related entities
792+
workspace.ID,workspace.OwnerID,workspace.TemplateID,workspace.OrganizationID,
793+
);err!=nil {
794+
log.Warn(ctx,"failed to notify of workspace creation",slog.Error(err))
795+
}
796+
}
797+
738798
// @Summary Update workspace metadata by ID
739799
// @ID update-workspace-metadata-by-id
740800
// @Security CoderSessionToken

‎coderd/workspaces_test.go‎

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,59 @@ func TestPostWorkspacesByOrganization(t *testing.T) {
571571
require.Equal(t,http.StatusConflict,apiErr.StatusCode())
572572
})
573573

574+
t.Run("CreateSendsNotification",func(t*testing.T) {
575+
t.Parallel()
576+
577+
enqueuer:= notificationstest.FakeEnqueuer{}
578+
client:=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerDaemon:true,NotificationsEnqueuer:&enqueuer})
579+
user:=coderdtest.CreateFirstUser(t,client)
580+
memberClient,memberUser:=coderdtest.CreateAnotherUser(t,client,user.OrganizationID)
581+
582+
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
583+
template:=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID)
584+
coderdtest.AwaitTemplateVersionJobCompleted(t,client,version.ID)
585+
586+
workspace:=coderdtest.CreateWorkspace(t,memberClient,template.ID)
587+
coderdtest.AwaitWorkspaceBuildJobCompleted(t,memberClient,workspace.LatestBuild.ID)
588+
589+
sent:=enqueuer.Sent(notificationstest.WithTemplateID(notifications.TemplateWorkspaceCreated))
590+
require.Len(t,sent,1)
591+
require.Equal(t,memberUser.ID,sent[0].UserID)
592+
require.Contains(t,sent[0].Targets,template.ID)
593+
require.Contains(t,sent[0].Targets,workspace.ID)
594+
require.Contains(t,sent[0].Targets,workspace.OrganizationID)
595+
require.Contains(t,sent[0].Targets,workspace.OwnerID)
596+
})
597+
598+
t.Run("CreateSendsNotificationToCorrectUser",func(t*testing.T) {
599+
t.Parallel()
600+
601+
enqueuer:= notificationstest.FakeEnqueuer{}
602+
client:=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerDaemon:true,NotificationsEnqueuer:&enqueuer})
603+
user:=coderdtest.CreateFirstUser(t,client)
604+
_,memberUser:=coderdtest.CreateAnotherUser(t,client,user.OrganizationID)
605+
606+
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
607+
template:=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID)
608+
coderdtest.AwaitTemplateVersionJobCompleted(t,client,version.ID)
609+
610+
ctx:=testutil.Context(t,testutil.WaitShort)
611+
workspace,err:=client.CreateUserWorkspace(ctx,memberUser.Username, codersdk.CreateWorkspaceRequest{
612+
TemplateID:template.ID,
613+
Name:coderdtest.RandomUsername(t),
614+
})
615+
require.NoError(t,err)
616+
coderdtest.AwaitWorkspaceBuildJobCompleted(t,client,workspace.LatestBuild.ID)
617+
618+
sent:=enqueuer.Sent(notificationstest.WithTemplateID(notifications.TemplateWorkspaceCreated))
619+
require.Len(t,sent,1)
620+
require.Equal(t,memberUser.ID,sent[0].UserID)
621+
require.Contains(t,sent[0].Targets,template.ID)
622+
require.Contains(t,sent[0].Targets,workspace.ID)
623+
require.Contains(t,sent[0].Targets,workspace.OrganizationID)
624+
require.Contains(t,sent[0].Targets,workspace.OwnerID)
625+
})
626+
574627
t.Run("CreateWithAuditLogs",func(t*testing.T) {
575628
t.Parallel()
576629
auditor:=audit.NewMock()
@@ -3596,15 +3649,14 @@ func TestWorkspaceNotifications(t *testing.T) {
35963649

35973650
// Then
35983651
require.NoError(t,err,"mark workspace as dormant")
3599-
sent:=notifyEnq.Sent()
3600-
require.Len(t,sent,2)
3601-
// notifyEnq.Sent[0] is an event for created user account
3602-
require.Equal(t,sent[1].TemplateID,notifications.TemplateWorkspaceDormant)
3603-
require.Equal(t,sent[1].UserID,workspace.OwnerID)
3604-
require.Contains(t,sent[1].Targets,template.ID)
3605-
require.Contains(t,sent[1].Targets,workspace.ID)
3606-
require.Contains(t,sent[1].Targets,workspace.OrganizationID)
3607-
require.Contains(t,sent[1].Targets,workspace.OwnerID)
3652+
sent:=notifyEnq.Sent(notificationstest.WithTemplateID(notifications.TemplateWorkspaceDormant))
3653+
require.Len(t,sent,1)
3654+
require.Equal(t,sent[0].TemplateID,notifications.TemplateWorkspaceDormant)
3655+
require.Equal(t,sent[0].UserID,workspace.OwnerID)
3656+
require.Contains(t,sent[0].Targets,template.ID)
3657+
require.Contains(t,sent[0].Targets,workspace.ID)
3658+
require.Contains(t,sent[0].Targets,workspace.OrganizationID)
3659+
require.Contains(t,sent[0].Targets,workspace.OwnerID)
36083660
})
36093661

36103662
t.Run("InitiatorIsOwner",func(t*testing.T) {
@@ -3635,7 +3687,7 @@ func TestWorkspaceNotifications(t *testing.T) {
36353687

36363688
// Then
36373689
require.NoError(t,err,"mark workspace as dormant")
3638-
require.Len(t,notifyEnq.Sent(),0)
3690+
require.Len(t,notifyEnq.Sent(notificationstest.WithTemplateID(notifications.TemplateWorkspaceDormant)),0)
36393691
})
36403692

36413693
t.Run("ActivateDormantWorkspace",func(t*testing.T) {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp