Skip to content

Commit 2ab1c4d

Browse files
committed
feat: support custom notifications
1 parent 4cd0ada commit 2ab1c4d

23 files changed

+639
-13
lines changed

cli/notifications.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,21 @@ func (r *RootCmd) notifications() *serpent.Command {
1616
Short: "Manage Coder notifications",
1717
Long: "Administrators can use these commands to change notification settings.\n" + FormatExamples(
1818
Example{
19-
Description: "Pause Coder notifications. Administrators can temporarily stop notifiers from dispatching messages in case of the target outage (for example: unavailable SMTP server or Webhook not responding).",
19+
Description: "Pause Coder notifications. Administrators can temporarily stop notifiers from dispatching messages in case of the target outage (for example: unavailable SMTP server or Webhook not responding)",
2020
Command: "coder notifications pause",
2121
},
2222
Example{
2323
Description: "Resume Coder notifications",
2424
Command: "coder notifications resume",
2525
},
2626
Example{
27-
Description: "Send a test notification. Administrators can use this to verify the notification target settings.",
27+
Description: "Send a test notification. Administrators can use this to verify the notification target settings",
2828
Command: "coder notifications test",
2929
},
30+
Example{
31+
Description: "Send a custom notification to the requesting user. The recipient is always the requesting user as targeting other users or groups isn’t supported yet",
32+
Command: "coder notifications custom \"Custom Title\" \"Custom Message\"",
33+
},
3034
),
3135
Aliases: []string{"notification"},
3236
Handler: func(inv *serpent.Invocation) error {
@@ -36,6 +40,7 @@ func (r *RootCmd) notifications() *serpent.Command {
3640
r.pauseNotifications(),
3741
r.resumeNotifications(),
3842
r.testNotifications(),
43+
r.customNotifications(),
3944
},
4045
}
4146
return cmd
@@ -109,3 +114,28 @@ func (r *RootCmd) testNotifications() *serpent.Command {
109114
}
110115
return cmd
111116
}
117+
118+
func (r *RootCmd) customNotifications() *serpent.Command {
119+
client := new(codersdk.Client)
120+
cmd := &serpent.Command{
121+
Use: "custom <title> <message>",
122+
Short: "Send a custom notification",
123+
Middleware: serpent.Chain(
124+
serpent.RequireNArgs(2),
125+
r.InitClient(client),
126+
),
127+
Handler: func(inv *serpent.Invocation) error {
128+
err := client.PostCustomNotification(inv.Context(), codersdk.CustomNotification{
129+
Title: inv.Args[0],
130+
Message: inv.Args[1],
131+
})
132+
if err != nil {
133+
return xerrors.Errorf("unable to post custom notification: %w", err)
134+
}
135+
136+
_, _ = fmt.Fprintln(inv.Stderr, "A custom notification has been sent.")
137+
return nil
138+
},
139+
}
140+
return cmd
141+
}

cli/notifications_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,66 @@ func TestNotificationsTest(t *testing.T) {
166166
require.Len(t, sent, 0)
167167
})
168168
}
169+
170+
func TestCustomNotifications(t *testing.T) {
171+
t.Parallel()
172+
173+
t.Run("BadRequest", func(t *testing.T) {
174+
t.Parallel()
175+
176+
notifyEnq := &notificationstest.FakeEnqueuer{}
177+
178+
// Given: A member user
179+
ownerClient := coderdtest.New(t, &coderdtest.Options{
180+
DeploymentValues: coderdtest.DeploymentValues(t),
181+
NotificationsEnqueuer: notifyEnq,
182+
})
183+
ownerUser := coderdtest.CreateFirstUser(t, ownerClient)
184+
memberClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, ownerUser.OrganizationID)
185+
186+
// When: The member user attempts to send a custom notification with empty title and message
187+
inv, root := clitest.New(t, "notifications", "custom", "", "")
188+
clitest.SetupConfig(t, memberClient, root)
189+
190+
// Then: an error is expected with no notifications sent
191+
err := inv.Run()
192+
var sdkError *codersdk.Error
193+
require.Error(t, err)
194+
require.ErrorAsf(t, err, &sdkError, "error should be of type *codersdk.Error")
195+
assert.Equal(t, http.StatusBadRequest, sdkError.StatusCode())
196+
require.Equal(t, "Invalid request body", sdkError.Message)
197+
198+
sent := notifyEnq.Sent(notificationstest.WithTemplateID(notifications.TemplateTestNotification))
199+
require.Len(t, sent, 0)
200+
})
201+
202+
t.Run("Success", func(t *testing.T) {
203+
t.Parallel()
204+
205+
notifyEnq := &notificationstest.FakeEnqueuer{}
206+
207+
// Given: A member user
208+
ownerClient := coderdtest.New(t, &coderdtest.Options{
209+
DeploymentValues: coderdtest.DeploymentValues(t),
210+
NotificationsEnqueuer: notifyEnq,
211+
})
212+
ownerUser := coderdtest.CreateFirstUser(t, ownerClient)
213+
memberClient, memberUser := coderdtest.CreateAnotherUser(t, ownerClient, ownerUser.OrganizationID)
214+
215+
// When: The member user attempts to send a custom notification
216+
inv, root := clitest.New(t, "notifications", "custom", "Custom Title", "Custom Message")
217+
clitest.SetupConfig(t, memberClient, root)
218+
219+
// Then: we expect a custom notification to be sent to the member user
220+
err := inv.Run()
221+
require.NoError(t, err)
222+
223+
sent := notifyEnq.Sent(notificationstest.WithTemplateID(notifications.TemplateCustomNotification))
224+
require.Len(t, sent, 1)
225+
require.Equal(t, memberUser.ID, sent[0].UserID)
226+
require.Len(t, sent[0].Labels, 2)
227+
require.Equal(t, "Custom Title", sent[0].Labels["custom_title"])
228+
require.Equal(t, "Custom Message", sent[0].Labels["custom_message"])
229+
require.Equal(t, memberUser.ID.String(), sent[0].CreatedBy)
230+
})
231+
}

cli/testdata/coder_notifications_--help.golden

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ USAGE:
1212
from
1313
dispatching messages in case of the target outage (for example: unavailable
1414
SMTP
15-
server or Webhook not responding).:
15+
server or Webhook not responding):
1616

1717
$ coder notifications pause
1818

@@ -22,11 +22,18 @@ USAGE:
2222

2323
- Send a test notification. Administrators can use this to verify the
2424
notification
25-
target settings.:
25+
target settings:
2626

2727
$ coder notifications test
28+
29+
- Send a custom notification to the requesting user. The recipient is always
30+
the
31+
requesting user as targeting other users or groups isn’t supported yet:
32+
33+
$ coder notifications custom "Custom Title" "Custom Message"
2834

2935
SUBCOMMANDS:
36+
custom Send a custom notification
3037
pause Pause notifications
3138
resume Resume notifications
3239
test Send a test notification
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
coder v0.0.0-devel
2+
3+
USAGE:
4+
coder notifications custom <title> <message>
5+
6+
Send a custom notification
7+
8+
———
9+
Run `coder --help` for a list of global options.

coderd/apidoc/docs.go

Lines changed: 59 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 53 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,6 +1581,7 @@ func New(options *Options) *API {
15811581
})
15821582
r.Get("/dispatch-methods", api.notificationDispatchMethods)
15831583
r.Post("/test", api.postTestNotification)
1584+
r.Post("/custom", api.postCustomNotification)
15841585
})
15851586
r.Route("/tailnet", func(r chi.Router) {
15861587
r.Use(apiKeyMiddleware)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
DELETE FROM notification_templates WHERE id = '39b1e189-c857-4b0c-877a-511144c18516';
2+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
INSERT INTO notification_templates (
2+
id,
3+
name,
4+
title_template,
5+
body_template,
6+
actions,
7+
"group",
8+
method,
9+
kind,
10+
enabled_by_default
11+
) VALUES (
12+
'39b1e189-c857-4b0c-877a-511144c18516',
13+
'Custom Notification',
14+
'{{.Labels.custom_title}}',
15+
'{{.Labels.custom_message}}',
16+
'[]',
17+
'Custom Events',
18+
NULL,
19+
'system'::notification_template_kind,
20+
true
21+
);

0 commit comments

Comments
 (0)