Skip to content

Commit 009069c

Browse files
feat: allow notification templates to be disabled by default (coder#16093)
Change as part of coder#16071 It has been decided that we want to be able to have some notification templates be disabled _by default_ coder#16071 (comment). This adds a new column (`enabled_by_default`) to `notification_templates` that defaults to `TRUE`. It also modifies the `inhibit_enqueue_if_disabled` function to reject notifications for templates that have `enabled_by_default = FALSE` with the user not explicitly enabling it.
1 parent 22236f2 commit 009069c

20 files changed

+231
-62
lines changed

coderd/apidoc/docs.go

Lines changed: 3 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: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dump.sql

Lines changed: 20 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
ALTER TABLE notification_templates DROP COLUMN enabled_by_default;
2+
3+
CREATE OR REPLACE FUNCTION inhibit_enqueue_if_disabled()
4+
RETURNS TRIGGER AS
5+
$$
6+
BEGIN
7+
-- Fail the insertion if the user has disabled this notification.
8+
IF EXISTS (SELECT 1
9+
FROM notification_preferences
10+
WHERE disabled = TRUE
11+
AND user_id = NEW.user_id
12+
AND notification_template_id = NEW.notification_template_id) THEN
13+
RAISE EXCEPTION 'cannot enqueue message: user has disabled this notification';
14+
END IF;
15+
16+
RETURN NEW;
17+
END;
18+
$$ LANGUAGE plpgsql;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
ALTER TABLE notification_templates ADD COLUMN enabled_by_default boolean DEFAULT TRUE NOT NULL;
2+
3+
CREATE OR REPLACE FUNCTION inhibit_enqueue_if_disabled()
4+
RETURNS TRIGGER AS
5+
$$
6+
BEGIN
7+
-- Fail the insertion if one of the following:
8+
-- * the user has disabled this notification.
9+
-- * the notification template is disabled by default and hasn't
10+
-- been explicitly enabled by the user.
11+
IF EXISTS (
12+
SELECT 1 FROM notification_templates
13+
LEFT JOIN notification_preferences
14+
ON notification_preferences.notification_template_id = notification_templates.id
15+
AND notification_preferences.user_id = NEW.user_id
16+
WHERE notification_templates.id = NEW.notification_template_id AND (
17+
-- Case 1: The user has explicitly disabled this template
18+
notification_preferences.disabled = TRUE
19+
OR
20+
-- Case 2: The template is disabled by default AND the user hasn't enabled it
21+
(notification_templates.enabled_by_default = FALSE AND notification_preferences.notification_template_id IS NULL)
22+
)
23+
) THEN
24+
RAISE EXCEPTION 'cannot enqueue message: notification is not enabled';
25+
END IF;
26+
27+
RETURN NEW;
28+
END;
29+
$$ LANGUAGE plpgsql;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-- Enable 'workspace created' notification by default
2+
UPDATE notification_templates
3+
SET enabled_by_default = TRUE
4+
WHERE id = '281fdf73-c6d6-4cbb-8ff5-888baf8a2fff';
5+
6+
-- Enable 'workspace manually updated' notification by default
7+
UPDATE notification_templates
8+
SET enabled_by_default = TRUE
9+
WHERE id = 'd089fe7b-d5c5-4c0c-aaf5-689859f7d392';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-- Disable 'workspace created' notification by default
2+
UPDATE notification_templates
3+
SET enabled_by_default = FALSE
4+
WHERE id = '281fdf73-c6d6-4cbb-8ff5-888baf8a2fff';
5+
6+
-- Disable 'workspace manually updated' notification by default
7+
UPDATE notification_templates
8+
SET enabled_by_default = FALSE
9+
WHERE id = 'd089fe7b-d5c5-4c0c-aaf5-689859f7d392';

coderd/database/models.go

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

coderd/database/queries.sql.go

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

coderd/notifications.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -271,14 +271,15 @@ func (api *API) putUserNotificationPreferences(rw http.ResponseWriter, r *http.R
271271
func convertNotificationTemplates(in []database.NotificationTemplate) (out []codersdk.NotificationTemplate) {
272272
for _, tmpl := range in {
273273
out = append(out, codersdk.NotificationTemplate{
274-
ID: tmpl.ID,
275-
Name: tmpl.Name,
276-
TitleTemplate: tmpl.TitleTemplate,
277-
BodyTemplate: tmpl.BodyTemplate,
278-
Actions: string(tmpl.Actions),
279-
Group: tmpl.Group.String,
280-
Method: string(tmpl.Method.NotificationMethod),
281-
Kind: string(tmpl.Kind),
274+
ID: tmpl.ID,
275+
Name: tmpl.Name,
276+
TitleTemplate: tmpl.TitleTemplate,
277+
BodyTemplate: tmpl.BodyTemplate,
278+
Actions: string(tmpl.Actions),
279+
Group: tmpl.Group.String,
280+
Method: string(tmpl.Method.NotificationMethod),
281+
Kind: string(tmpl.Kind),
282+
EnabledByDefault: tmpl.EnabledByDefault,
282283
})
283284
}
284285

coderd/notifications/enqueuer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
)
2121

2222
var (
23-
ErrCannotEnqueueDisabledNotification = xerrors.New("user has disabled this notification")
23+
ErrCannotEnqueueDisabledNotification = xerrors.New("notification is not enabled")
2424
ErrDuplicate = xerrors.New("duplicate notification")
2525
)
2626

coderd/notifications/notifications_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,20 @@ func TestNotificationTemplates_Golden(t *testing.T) {
11061106
r.Name = tc.payload.UserName
11071107
},
11081108
)
1109+
1110+
// With the introduction of notifications that can be disabled
1111+
// by default, we want to make sure the user preferences have
1112+
// the notification enabled.
1113+
_, err := adminClient.UpdateUserNotificationPreferences(
1114+
context.Background(),
1115+
user.ID,
1116+
codersdk.UpdateUserNotificationPreferences{
1117+
TemplateDisabledMap: map[string]bool{
1118+
tc.id.String(): false,
1119+
},
1120+
})
1121+
require.NoError(t, err)
1122+
11091123
return &db, &api.Logger, &user
11101124
}()
11111125

@@ -1275,6 +1289,20 @@ func TestNotificationTemplates_Golden(t *testing.T) {
12751289
r.Name = tc.payload.UserName
12761290
},
12771291
)
1292+
1293+
// With the introduction of notifications that can be disabled
1294+
// by default, we want to make sure the user preferences have
1295+
// the notification enabled.
1296+
_, err := adminClient.UpdateUserNotificationPreferences(
1297+
context.Background(),
1298+
user.ID,
1299+
codersdk.UpdateUserNotificationPreferences{
1300+
TemplateDisabledMap: map[string]bool{
1301+
tc.id.String(): false,
1302+
},
1303+
})
1304+
require.NoError(t, err)
1305+
12781306
return &db, &api.Logger, &user
12791307
}()
12801308

@@ -1410,6 +1438,30 @@ func normalizeGoldenWebhook(content []byte) []byte {
14101438
return content
14111439
}
14121440

1441+
func TestDisabledByDefaultBeforeEnqueue(t *testing.T) {
1442+
t.Parallel()
1443+
1444+
if !dbtestutil.WillUsePostgres() {
1445+
t.Skip("This test requires postgres; it is testing business-logic implemented in the database")
1446+
}
1447+
1448+
// nolint:gocritic // Unit test.
1449+
ctx := dbauthz.AsNotifier(testutil.Context(t, testutil.WaitSuperLong))
1450+
store, _ := dbtestutil.NewDB(t)
1451+
logger := testutil.Logger(t)
1452+
1453+
cfg := defaultNotificationsConfig(database.NotificationMethodSmtp)
1454+
enq, err := notifications.NewStoreEnqueuer(cfg, store, defaultHelpers(), logger.Named("enqueuer"), quartz.NewReal())
1455+
require.NoError(t, err)
1456+
user := createSampleUser(t, store)
1457+
1458+
// We want to try enqueuing a notification on a template that is disabled
1459+
// by default. We expect this to fail.
1460+
templateID := notifications.TemplateWorkspaceManuallyUpdated
1461+
_, err = enq.Enqueue(ctx, user.ID, templateID, map[string]string{}, "test")
1462+
require.ErrorIs(t, err, notifications.ErrCannotEnqueueDisabledNotification, "enqueuing did not fail with expected error")
1463+
}
1464+
14131465
// TestDisabledBeforeEnqueue ensures that notifications cannot be enqueued once a user has disabled that notification template
14141466
func TestDisabledBeforeEnqueue(t *testing.T) {
14151467
t.Parallel()

codersdk/notifications.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@ type NotificationsSettings struct {
1717
}
1818

1919
type NotificationTemplate struct {
20-
ID uuid.UUID `json:"id" format:"uuid"`
21-
Name string `json:"name"`
22-
TitleTemplate string `json:"title_template"`
23-
BodyTemplate string `json:"body_template"`
24-
Actions string `json:"actions" format:""`
25-
Group string `json:"group"`
26-
Method string `json:"method"`
27-
Kind string `json:"kind"`
20+
ID uuid.UUID `json:"id" format:"uuid"`
21+
Name string `json:"name"`
22+
TitleTemplate string `json:"title_template"`
23+
BodyTemplate string `json:"body_template"`
24+
Actions string `json:"actions" format:""`
25+
Group string `json:"group"`
26+
Method string `json:"method"`
27+
Kind string `json:"kind"`
28+
EnabledByDefault bool `json:"enabled_by_default"`
2829
}
2930

3031
type NotificationMethodsResponse struct {

0 commit comments

Comments
 (0)