Skip to content

Commit cd19e79

Browse files
chore: enable coder inbox by default (#17077)
Add a flag to enable Coder Inbox by default, as well as supporting disabling the feature.
1 parent 5f3a53f commit cd19e79

File tree

15 files changed

+258
-40
lines changed

15 files changed

+258
-40
lines changed

cli/server.go

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -920,34 +920,30 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
920920
notificationsManager *notifications.Manager
921921
)
922922

923-
if notificationsCfg.Enabled() {
924-
metrics := notifications.NewMetrics(options.PrometheusRegistry)
925-
helpers := templateHelpers(options)
923+
metrics := notifications.NewMetrics(options.PrometheusRegistry)
924+
helpers := templateHelpers(options)
926925

927-
// The enqueuer is responsible for enqueueing notifications to the given store.
928-
enqueuer, err := notifications.NewStoreEnqueuer(notificationsCfg, options.Database, helpers, logger.Named("notifications.enqueuer"), quartz.NewReal())
929-
if err != nil {
930-
return xerrors.Errorf("failed to instantiate notification store enqueuer: %w", err)
931-
}
932-
options.NotificationsEnqueuer = enqueuer
926+
// The enqueuer is responsible for enqueueing notifications to the given store.
927+
enqueuer, err := notifications.NewStoreEnqueuer(notificationsCfg, options.Database, helpers, logger.Named("notifications.enqueuer"), quartz.NewReal())
928+
if err != nil {
929+
return xerrors.Errorf("failed to instantiate notification store enqueuer: %w", err)
930+
}
931+
options.NotificationsEnqueuer = enqueuer
933932

934-
// The notification manager is responsible for:
935-
// - creating notifiers and managing their lifecycles (notifiers are responsible for dequeueing/sending notifications)
936-
// - keeping the store updated with status updates
937-
notificationsManager, err = notifications.NewManager(notificationsCfg, options.Database, options.Pubsub, helpers, metrics, logger.Named("notifications.manager"))
938-
if err != nil {
939-
return xerrors.Errorf("failed to instantiate notification manager: %w", err)
940-
}
933+
// The notification manager is responsible for:
934+
// - creating notifiers and managing their lifecycles (notifiers are responsible for dequeueing/sending notifications)
935+
// - keeping the store updated with status updates
936+
notificationsManager, err = notifications.NewManager(notificationsCfg, options.Database, options.Pubsub, helpers, metrics, logger.Named("notifications.manager"))
937+
if err != nil {
938+
return xerrors.Errorf("failed to instantiate notification manager: %w", err)
939+
}
941940

942-
// nolint:gocritic // We need to run the manager in a notifier context.
943-
notificationsManager.Run(dbauthz.AsNotifier(ctx))
941+
// nolint:gocritic // We need to run the manager in a notifier context.
942+
notificationsManager.Run(dbauthz.AsNotifier(ctx))
944943

945-
// Run report generator to distribute periodic reports.
946-
notificationReportGenerator := reports.NewReportGenerator(ctx, logger.Named("notifications.report_generator"), options.Database, options.NotificationsEnqueuer, quartz.NewReal())
947-
defer notificationReportGenerator.Close()
948-
} else {
949-
logger.Debug(ctx, "notifications are currently disabled as there are no configured delivery methods. See https://coder.com/docs/admin/monitoring/notifications#delivery-methods for more details")
950-
}
944+
// Run report generator to distribute periodic reports.
945+
notificationReportGenerator := reports.NewReportGenerator(ctx, logger.Named("notifications.report_generator"), options.Database, options.NotificationsEnqueuer, quartz.NewReal())
946+
defer notificationReportGenerator.Close()
951947

952948
// Since errCh only has one buffered slot, all routines
953949
// sending on it must be wrapped in a select/default to

cli/server_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ func TestServer(t *testing.T) {
298298
out := pty.ReadAll()
299299
numLines := countLines(string(out))
300300
t.Logf("numLines: %d", numLines)
301-
require.Less(t, numLines, 12, "expected less than 12 lines of output (terminal width 80), got %d", numLines)
301+
require.Less(t, numLines, 20, "expected less than 20 lines of output (terminal width 80), got %d", numLines)
302302
})
303303

304304
t.Run("OAuth2GitHubDefaultProvider", func(t *testing.T) {

cli/testdata/coder_server_--help.golden

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,10 @@ Configure TLS for your SMTP server target.
473473
Enable STARTTLS to upgrade insecure SMTP connections using TLS.
474474
DEPRECATED: Use --email-tls-starttls instead.
475475

476+
NOTIFICATIONS / INBOX OPTIONS:
477+
--notifications-inbox-enabled bool, $CODER_NOTIFICATIONS_INBOX_ENABLED (default: true)
478+
Enable Coder Inbox.
479+
476480
NOTIFICATIONS / WEBHOOK OPTIONS:
477481
--notifications-webhook-endpoint url, $CODER_NOTIFICATIONS_WEBHOOK_ENDPOINT
478482
The endpoint to which to send webhooks.

cli/testdata/server-config.yaml.golden

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,10 @@ notifications:
643643
# The endpoint to which to send webhooks.
644644
# (default: <unset>, type: url)
645645
endpoint:
646+
inbox:
647+
# Enable Coder Inbox.
648+
# (default: true, type: bool)
649+
enabled: true
646650
# The upper limit of attempts to send a notification.
647651
# (default: 5, type: int)
648652
maxSendAttempts: 5

coderd/apidoc/docs.go

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

coderd/notifications/enqueuer.go

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package notifications
33
import (
44
"context"
55
"encoding/json"
6+
"slices"
67
"strings"
78
"text/template"
89

@@ -28,7 +29,10 @@ type StoreEnqueuer struct {
2829
store Store
2930
log slog.Logger
3031

31-
defaultMethod database.NotificationMethod
32+
defaultMethod database.NotificationMethod
33+
defaultEnabled bool
34+
inboxEnabled bool
35+
3236
// helpers holds a map of template funcs which are used when rendering templates. These need to be passed in because
3337
// the template funcs will return values which are inappropriately encapsulated in this struct.
3438
helpers template.FuncMap
@@ -44,11 +48,13 @@ func NewStoreEnqueuer(cfg codersdk.NotificationsConfig, store Store, helpers tem
4448
}
4549

4650
return &StoreEnqueuer{
47-
store: store,
48-
log: log,
49-
defaultMethod: method,
50-
helpers: helpers,
51-
clock: clock,
51+
store: store,
52+
log: log,
53+
defaultMethod: method,
54+
defaultEnabled: cfg.Enabled(),
55+
inboxEnabled: cfg.Inbox.Enabled.Value(),
56+
helpers: helpers,
57+
clock: clock,
5258
}, nil
5359
}
5460

@@ -69,11 +75,6 @@ func (s *StoreEnqueuer) EnqueueWithData(ctx context.Context, userID, templateID
6975
return nil, xerrors.Errorf("new message metadata: %w", err)
7076
}
7177

72-
dispatchMethod := s.defaultMethod
73-
if metadata.CustomMethod.Valid {
74-
dispatchMethod = metadata.CustomMethod.NotificationMethod
75-
}
76-
7778
payload, err := s.buildPayload(metadata, labels, data, targets)
7879
if err != nil {
7980
s.log.Warn(ctx, "failed to build payload", slog.F("template_id", templateID), slog.F("user_id", userID), slog.Error(err))
@@ -85,11 +86,22 @@ func (s *StoreEnqueuer) EnqueueWithData(ctx context.Context, userID, templateID
8586
return nil, xerrors.Errorf("failed encoding input labels: %w", err)
8687
}
8788

88-
uuids := make([]uuid.UUID, 0, 2)
89+
methods := []database.NotificationMethod{}
90+
if metadata.CustomMethod.Valid {
91+
methods = append(methods, metadata.CustomMethod.NotificationMethod)
92+
} else if s.defaultEnabled {
93+
methods = append(methods, s.defaultMethod)
94+
}
95+
8996
// All the enqueued messages are enqueued both on the dispatch method set by the user (or default one) and the inbox.
9097
// As the inbox is not configurable per the user and is always enabled, we always enqueue the message on the inbox.
9198
// The logic is done here in order to have two completely separated processing and retries are handled separately.
92-
for _, method := range []database.NotificationMethod{dispatchMethod, database.NotificationMethodInbox} {
99+
if !slices.Contains(methods, database.NotificationMethodInbox) && s.inboxEnabled {
100+
methods = append(methods, database.NotificationMethodInbox)
101+
}
102+
103+
uuids := make([]uuid.UUID, 0, 2)
104+
for _, method := range methods {
93105
id := uuid.New()
94106
err = s.store.EnqueueNotificationMessage(ctx, database.EnqueueNotificationMessageParams{
95107
ID: id,

coderd/notifications/notifications_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1856,6 +1856,90 @@ func TestNotificationDuplicates(t *testing.T) {
18561856
require.NoError(t, err)
18571857
}
18581858

1859+
func TestNotificationTargetMatrix(t *testing.T) {
1860+
t.Parallel()
1861+
1862+
tests := []struct {
1863+
name string
1864+
defaultMethod database.NotificationMethod
1865+
defaultEnabled bool
1866+
inboxEnabled bool
1867+
expectedEnqueued int
1868+
}{
1869+
{
1870+
name: "NoDefaultAndNoInbox",
1871+
defaultMethod: database.NotificationMethodSmtp,
1872+
defaultEnabled: false,
1873+
inboxEnabled: false,
1874+
expectedEnqueued: 0,
1875+
},
1876+
{
1877+
name: "DefaultAndNoInbox",
1878+
defaultMethod: database.NotificationMethodSmtp,
1879+
defaultEnabled: true,
1880+
inboxEnabled: false,
1881+
expectedEnqueued: 1,
1882+
},
1883+
{
1884+
name: "NoDefaultAndInbox",
1885+
defaultMethod: database.NotificationMethodSmtp,
1886+
defaultEnabled: false,
1887+
inboxEnabled: true,
1888+
expectedEnqueued: 1,
1889+
},
1890+
{
1891+
name: "DefaultAndInbox",
1892+
defaultMethod: database.NotificationMethodSmtp,
1893+
defaultEnabled: true,
1894+
inboxEnabled: true,
1895+
expectedEnqueued: 2,
1896+
},
1897+
}
1898+
1899+
for _, tt := range tests {
1900+
tt := tt
1901+
1902+
t.Run(tt.name, func(t *testing.T) {
1903+
t.Parallel()
1904+
1905+
// nolint:gocritic // Unit test.
1906+
ctx := dbauthz.AsNotifier(testutil.Context(t, testutil.WaitSuperLong))
1907+
store, pubsub := dbtestutil.NewDB(t)
1908+
logger := testutil.Logger(t)
1909+
1910+
cfg := defaultNotificationsConfig(tt.defaultMethod)
1911+
cfg.Inbox.Enabled = serpent.Bool(tt.inboxEnabled)
1912+
1913+
// If the default method is not enabled, we want to ensure the config
1914+
// is wiped out.
1915+
if !tt.defaultEnabled {
1916+
cfg.SMTP = codersdk.NotificationsEmailConfig{}
1917+
cfg.Webhook = codersdk.NotificationsWebhookConfig{}
1918+
}
1919+
1920+
mgr, err := notifications.NewManager(cfg, store, pubsub, defaultHelpers(), createMetrics(), logger.Named("manager"))
1921+
require.NoError(t, err)
1922+
t.Cleanup(func() {
1923+
assert.NoError(t, mgr.Stop(ctx))
1924+
})
1925+
1926+
// Set the time to a known value.
1927+
mClock := quartz.NewMock(t)
1928+
mClock.Set(time.Date(2024, 1, 15, 9, 0, 0, 0, time.UTC))
1929+
1930+
enq, err := notifications.NewStoreEnqueuer(cfg, store, defaultHelpers(), logger.Named("enqueuer"), mClock)
1931+
require.NoError(t, err)
1932+
user := createSampleUser(t, store)
1933+
1934+
// When: A notification is enqueued, it enqueues the correct amount of notifications.
1935+
enqueued, err := enq.Enqueue(ctx, user.ID, notifications.TemplateWorkspaceDeleted,
1936+
map[string]string{"initiator": "danny"}, "test", user.ID)
1937+
require.NoError(t, err)
1938+
require.Len(t, enqueued, tt.expectedEnqueued)
1939+
})
1940+
}
1941+
}
1942+
18591943
type fakeHandler struct {
18601944
mu sync.RWMutex
18611945
succeeded, failed []string

coderd/notifications/utils_test.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package notifications_test
22

33
import (
44
"context"
5+
"net/url"
56
"sync/atomic"
67
"testing"
78
"text/template"
@@ -21,6 +22,18 @@ import (
2122
)
2223

2324
func defaultNotificationsConfig(method database.NotificationMethod) codersdk.NotificationsConfig {
25+
var (
26+
smtp codersdk.NotificationsEmailConfig
27+
webhook codersdk.NotificationsWebhookConfig
28+
)
29+
30+
switch method {
31+
case database.NotificationMethodSmtp:
32+
smtp.Smarthost = serpent.String("localhost:1337")
33+
case database.NotificationMethodWebhook:
34+
webhook.Endpoint = serpent.URL(url.URL{Host: "localhost"})
35+
}
36+
2437
return codersdk.NotificationsConfig{
2538
Method: serpent.String(method),
2639
MaxSendAttempts: 5,
@@ -31,8 +44,11 @@ func defaultNotificationsConfig(method database.NotificationMethod) codersdk.Not
3144
RetryInterval: serpent.Duration(time.Millisecond * 50),
3245
LeaseCount: 10,
3346
StoreSyncBufferSize: 50,
34-
SMTP: codersdk.NotificationsEmailConfig{},
35-
Webhook: codersdk.NotificationsWebhookConfig{},
47+
SMTP: smtp,
48+
Webhook: webhook,
49+
Inbox: codersdk.NotificationsInboxConfig{
50+
Enabled: serpent.Bool(true),
51+
},
3652
}
3753
}
3854

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy