Skip to content

Commit bfbf974

Browse files
committed
Fix data passing
1 parent 3270cd5 commit bfbf974

File tree

10 files changed

+57
-170
lines changed

10 files changed

+57
-170
lines changed

cli/server.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -993,9 +993,10 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
993993
if experiments.Enabled(codersdk.ExperimentNotifications) {
994994
cfg := options.DeploymentValues.Notifications
995995
metrics := notifications.NewMetrics(options.PrometheusRegistry)
996+
helpers := templateHelpers(options)
996997

997998
// The enqueuer is responsible for enqueueing notifications to the given store.
998-
enqueuer, err := notifications.NewStoreEnqueuer(cfg, options.Database, templateHelpers(options), logger.Named("notifications.enqueuer"))
999+
enqueuer, err := notifications.NewStoreEnqueuer(cfg, options.Database, helpers, logger.Named("notifications.enqueuer"))
9991000
if err != nil {
10001001
return xerrors.Errorf("failed to instantiate notification store enqueuer: %w", err)
10011002
}
@@ -1004,7 +1005,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
10041005
// The notification manager is responsible for:
10051006
// - creating notifiers and managing their lifecycles (notifiers are responsible for dequeueing/sending notifications)
10061007
// - keeping the store updated with status updates
1007-
notificationsManager, err = notifications.NewManager(cfg, options.Database, metrics, logger.Named("notifications.manager"))
1008+
notificationsManager, err = notifications.NewManager(cfg, options.Database, helpers, metrics, logger.Named("notifications.manager"))
10081009
if err != nil {
10091010
return xerrors.Errorf("failed to instantiate notification manager: %w", err)
10101011
}
@@ -1291,7 +1292,8 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
12911292
// We can later use this to inject whitelabel fields when app name / logo URL are overridden.
12921293
func templateHelpers(options *coderd.Options) map[string]any {
12931294
return map[string]any{
1294-
"base_url": func() string { return options.AccessURL.String() },
1295+
"base_url": func() string { return options.AccessURL.String() },
1296+
"current_year": func() string { return strconv.Itoa(time.Now().Year()) },
12951297
}
12961298
}
12971299

coderd/notifications/dispatch/smtp.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"slices"
1717
"strings"
1818
"sync"
19+
"text/template"
1920
"time"
2021

2122
"github.com/emersion/go-sasl"
@@ -53,10 +54,12 @@ type SMTPHandler struct {
5354
log slog.Logger
5455

5556
loginWarnOnce sync.Once
57+
58+
helpers template.FuncMap
5659
}
5760

58-
func NewSMTPHandler(cfg codersdk.NotificationsEmailConfig, log slog.Logger) *SMTPHandler {
59-
return &SMTPHandler{cfg: cfg, log: log}
61+
func NewSMTPHandler(cfg codersdk.NotificationsEmailConfig, helpers template.FuncMap, log slog.Logger) *SMTPHandler {
62+
return &SMTPHandler{cfg: cfg, helpers: helpers, log: log}
6063
}
6164

6265
func (s *SMTPHandler) Dispatcher(payload types.MessagePayload, titleTmpl, bodyTmpl string) (DeliveryFunc, error) {
@@ -75,12 +78,12 @@ func (s *SMTPHandler) Dispatcher(payload types.MessagePayload, titleTmpl, bodyTm
7578
// Then, reuse these strings in the HTML & plain body templates.
7679
payload.Labels["_subject"] = subject
7780
payload.Labels["_body"] = htmlBody
78-
htmlBody, err = render.GoTemplate(htmlTemplate, payload, nil)
81+
htmlBody, err = render.GoTemplate(htmlTemplate, payload, s.helpers)
7982
if err != nil {
8083
return nil, xerrors.Errorf("render full html template: %w", err)
8184
}
8285
payload.Labels["_body"] = plainBody
83-
plainBody, err = render.GoTemplate(plainTemplate, payload, nil)
86+
plainBody, err = render.GoTemplate(plainTemplate, payload, s.helpers)
8487
if err != nil {
8588
return nil, xerrors.Errorf("render full plaintext template: %w", err)
8689
}

coderd/notifications/dispatch/smtp/html.gotmpl

Lines changed: 18 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -4,147 +4,28 @@
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<title>{{ .Labels._subject }}</title>
7-
<style type="text/css">
8-
:root {
9-
--text-primary-color: #020617;
10-
--text-secondary-color: #475569;
11-
--border-color: #e2e8f0;
12-
--bg-body-color: #f8fafc;
13-
--bg-card-color: #fff;
14-
--bg-btn-color: #020617;
15-
--text-btn-color: #f8fafc;
16-
--text-link-color: #2563eb;
17-
}
18-
19-
@media (prefers-color-scheme: dark) {
20-
:root {
21-
--text-primary-color: #f8fafc;
22-
--text-secondary-color: #94a3b8;
23-
--border-color: #475569;
24-
--bg-body-color: #020617;
25-
--bg-card-color: #1e293b;
26-
--bg-btn-color: #f8fafc;
27-
--text-btn-color: #0f172a;
28-
--text-link-color: #60a5fa;
29-
}
30-
}
31-
32-
body {
33-
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI",
34-
"Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
35-
"Helvetica Neue", sans-serif;
36-
margin: 0;
37-
padding: 0;
38-
color: var(--text-primary-color);
39-
background: var(--bg-body-color);
40-
}
41-
42-
.card {
43-
max-width: 600px;
44-
margin: 20px auto;
45-
border: 1px solid var(--border-color);
46-
background-color: var(--bg-card-color);
47-
padding: 60px;
48-
border-radius: 8px;
49-
line-height: 1.5;
50-
font-size: 14px;
51-
}
52-
53-
.logo {
54-
width: 60px;
55-
display: block;
56-
margin: auto;
57-
}
58-
59-
.title {
60-
text-align: center;
61-
font-weight: 400;
62-
font-size: 24px;
63-
margin: 8px 0 32px;
64-
line-height: 1.5;
65-
}
66-
67-
.actions {
68-
display: flex;
69-
align-items: center;
70-
justify-content: center;
71-
gap: 8px;
72-
margin-top: 32px;
73-
}
74-
75-
.btn {
76-
display: inline-block;
77-
padding: 13px 24px;
78-
background-color: var(--bg-btn-color);
79-
color: var(--text-btn-color);
80-
text-decoration: none;
81-
border-radius: 8px;
82-
font-weight: 500;
83-
}
84-
85-
.footer {
86-
border-top: 1px solid var(--border-color);
87-
color: var(--text-secondary-color);
88-
font-size: 12px;
89-
margin-top: 64px;
90-
padding-top: 24px;
91-
line-height: 1.6;
92-
}
93-
94-
.link {
95-
color: var(--text-link-color);
96-
text-decoration: none;
97-
}
98-
</style>
997
</head>
100-
<body>
101-
<div class="card">
102-
<svg
103-
class="logo"
104-
viewBox="0 0 509 358"
105-
fill="currentColor"
106-
xmlns="http://www.w3.org/2000/svg"
107-
>
108-
<path
109-
d="M496.593 155.299C486.451 155.299 479.693 149.445 479.693 137.427V68.4055C479.693 24.3424 461.254 0 413.623 0H391.497V46.5278H398.258C417.003 46.5278 425.915 56.6964 425.915 74.876V135.887C425.915 162.386 433.904 173.171 451.421 178.717C433.904 183.956 425.915 195.048 425.915 221.547C425.915 236.646 425.915 251.744 425.915 266.844C425.915 279.476 425.915 291.802 422.534 304.435C419.154 316.144 413.623 327.237 405.94 336.788C401.638 342.336 396.721 346.957 391.191 351.272V357.434H413.315C460.947 357.434 479.385 333.091 479.385 289.028V220.007C479.385 207.681 485.837 202.135 496.286 202.135H508.886V155.607H496.593V155.299Z"
110-
/>
111-
<path
112-
d="M346.022 70.2636H277.801C276.264 70.2636 275.036 69.0309 275.036 67.4903V62.2525C275.036 60.7114 276.264 59.4792 277.801 59.4792H346.329C347.865 59.4792 349.094 60.7114 349.094 62.2525V67.4903C349.094 69.0309 347.557 70.2636 346.022 70.2636Z"
113-
/>
114-
<path
115-
d="M357.694 136.818H307.912C306.375 136.818 305.145 135.584 305.145 134.044V128.806C305.145 127.266 306.375 126.033 307.912 126.033H357.694C359.231 126.033 360.459 127.266 360.459 128.806V134.044C360.459 135.277 359.231 136.818 357.694 136.818Z"
116-
/>
117-
<path
118-
d="M377.365 103.54H277.801C276.264 103.54 275.036 102.308 275.036 100.767V95.5288C275.036 93.9882 276.264 92.7559 277.801 92.7559H377.058C378.595 92.7559 379.824 93.9882 379.824 95.5288V100.767C379.824 102 378.902 103.54 377.365 103.54Z"
119-
/>
120-
<path
121-
d="M198.821 85.3529C205.581 85.3529 212.342 85.9693 218.795 87.5099V74.876C218.795 57.0043 228.014 46.5278 246.452 46.5278H253.213V0H231.087C183.455 0 165.018 24.3424 165.018 68.4055V91.2071C175.773 87.5099 187.144 85.3529 198.821 85.3529Z"
122-
/>
123-
<path
124-
d="M398.259 252.97C393.342 213.837 363.226 181.175 324.507 173.78C313.752 171.623 302.996 171.314 292.548 173.163C292.241 173.163 292.241 172.855 291.934 172.855C275.032 137.42 238.771 114.002 199.437 114.002C160.102 114.002 124.149 136.804 106.94 172.239C106.632 172.239 106.632 172.548 106.325 172.548C95.2627 171.314 84.1998 171.93 73.1369 174.704C35.032 183.947 6.14596 215.994 0.921893 254.818C0.307298 258.824 0 262.829 0 266.528C0 278.236 7.98976 289.021 19.667 290.562C34.11 292.719 46.7093 281.626 46.402 267.452C46.402 265.295 46.402 262.829 46.7093 260.673C49.1678 240.952 64.2253 224.314 83.8921 219.691C90.0385 218.15 96.1843 217.843 102.023 218.767C120.768 221.232 139.206 211.68 147.196 195.041C153.035 182.716 162.254 171.93 174.546 166.076C188.066 159.605 203.432 158.681 217.568 163.612C232.317 168.849 243.38 179.942 250.141 193.808C257.208 207.367 260.588 216.918 275.647 218.767C281.792 219.691 299.001 219.383 305.455 219.075C318.054 219.075 330.653 223.389 339.565 232.325C345.403 238.487 349.705 246.191 351.549 254.818C354.315 268.684 350.935 282.55 342.637 293.027C336.798 300.422 328.809 305.968 319.897 308.433C315.595 309.666 311.293 309.974 306.991 309.974C304.533 309.974 301.152 309.974 297.158 309.974C284.866 309.974 258.745 309.974 239.077 309.974C225.557 309.974 214.801 299.19 214.801 285.631V195.348C214.801 191.651 211.729 188.57 208.041 188.57H198.515C179.769 188.878 164.712 209.832 164.712 232.016C164.712 254.202 164.712 313.056 164.712 313.056C164.712 337.09 184.071 356.502 208.041 356.502C208.041 356.502 314.674 356.193 316.21 356.193C340.793 353.729 363.534 341.095 378.898 321.683C394.264 302.887 401.331 278.236 398.259 252.97Z"
125-
/>
126-
</svg>
127-
128-
<h1 class="title">{{ .Labels._subject }}</h1>
129-
{{ .Labels._body }}
130-
131-
<div class="actions">
8+
<body style="margin: 0; padding: 0; font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; color: #020617; background: #f8fafc;">
9+
<div style="max-width: 600px; margin: 20px auto; padding: 60px; border: 1px solid #e2e8f0; border-radius: 8px; background-color: #fff; text-align: left; font-size: 14px; line-height: 1.5;">
10+
<div style="text-align: center;">
11+
<img src="https://coder.com/coder-logo-300x300.png" alt="Coder Logo" style="width: 60px;" />
12+
</div>
13+
<h1 style="text-align: center; font-size: 24px; font-weight: 400; margin: 8px 0 32px; line-height: 1.5;">
14+
{{ .Labels._subject }}
15+
</h1>
16+
<div style="line-height: 1.5;">
17+
{{ .Labels._body }}
18+
</div>
19+
<div style="text-align: center; margin-top: 32px;">
13220
{{ range $action := .Actions }}
133-
<a href="{{ $action.URL }}" class="btn">{{ $action.Label }}</a>
21+
<a href="{{ $action.URL }}" style="display: inline-block; padding: 13px 24px; background-color: #020617; color: #f8fafc; text-decoration: none; border-radius: 8px; margin: 0 4px;">
22+
{{ $action.Label }}
23+
</a>
13424
{{ end }}
13525
</div>
136-
137-
<div class="footer">
138-
<!-- TODO: dynamic copyright -->
139-
<p>
140-
&copy; {{ current_year }} Coder. All rights reserved -
141-
<a href="{{ base_url }}" class="link">{{ base_url }}</a>
142-
</p>
143-
<p>
144-
<a href="{{ base_url }}/settings/notifications" class="link"
145-
>Click here to manage your notification settings</a
146-
>
147-
</p>
26+
<div style="border-top: 1px solid #e2e8f0; color: #475569; font-size: 12px; margin-top: 64px; padding-top: 24px; line-height: 1.6;">
27+
<p>&copy;&nbsp;{{ current_year }}&nbsp;Coder. All rights reserved&nbsp;-&nbsp;<a href="{{ base_url }}" style="color: #2563eb; text-decoration: none;">{{ base_url }}</a></p>
28+
<p><a href="{{ base_url }}/settings/notifications" style="color: #2563eb; text-decoration: none;">Click here to manage your notification settings</a></p>
14829
</div>
14930
</div>
15031
</body>

coderd/notifications/dispatch/smtp_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net"
1010
"sync"
1111
"testing"
12+
"text/template"
1213

1314
"github.com/emersion/go-sasl"
1415
"github.com/emersion/go-smtp"
@@ -417,7 +418,7 @@ func TestSMTP(t *testing.T) {
417418
require.NoError(t, hp.Set(listen.Addr().String()))
418419
tc.cfg.Smarthost = hp
419420

420-
handler := dispatch.NewSMTPHandler(tc.cfg, logger.Named("smtp"))
421+
handler := dispatch.NewSMTPHandler(tc.cfg, template.FuncMap{}, logger.Named("smtp"))
421422

422423
// Start mock SMTP server in the background.
423424
var wg sync.WaitGroup

coderd/notifications/manager.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package notifications
33
import (
44
"context"
55
"sync"
6+
"text/template"
67
"time"
78

89
"github.com/google/uuid"
@@ -59,7 +60,7 @@ type Manager struct {
5960
//
6061
// helpers is a map of template helpers which are used to customize notification messages to use global settings like
6162
// access URL etc.
62-
func NewManager(cfg codersdk.NotificationsConfig, store Store, metrics *Metrics, log slog.Logger) (*Manager, error) {
63+
func NewManager(cfg codersdk.NotificationsConfig, store Store, helpers template.FuncMap, metrics *Metrics, log slog.Logger) (*Manager, error) {
6364
// TODO(dannyk): add the ability to use multiple notification methods.
6465
var method database.NotificationMethod
6566
if err := method.Scan(cfg.Method.String()); err != nil {
@@ -93,14 +94,14 @@ func NewManager(cfg codersdk.NotificationsConfig, store Store, metrics *Metrics,
9394
stop: make(chan any),
9495
done: make(chan any),
9596

96-
handlers: defaultHandlers(cfg, log),
97+
handlers: defaultHandlers(cfg, helpers, log),
9798
}, nil
9899
}
99100

100101
// defaultHandlers builds a set of known handlers; panics if any error occurs as these handlers should be valid at compile time.
101-
func defaultHandlers(cfg codersdk.NotificationsConfig, log slog.Logger) map[database.NotificationMethod]Handler {
102+
func defaultHandlers(cfg codersdk.NotificationsConfig, helpers template.FuncMap, log slog.Logger) map[database.NotificationMethod]Handler {
102103
return map[database.NotificationMethod]Handler{
103-
database.NotificationMethodSmtp: dispatch.NewSMTPHandler(cfg.SMTP, log.Named("dispatcher.smtp")),
104+
database.NotificationMethodSmtp: dispatch.NewSMTPHandler(cfg.SMTP, helpers, log.Named("dispatcher.smtp")),
104105
database.NotificationMethodWebhook: dispatch.NewWebhookHandler(cfg.Webhook, log.Named("dispatcher.webhook")),
105106
}
106107
}

coderd/notifications/manager_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func TestBufferedUpdates(t *testing.T) {
3434
cfg.StoreSyncInterval = serpent.Duration(time.Hour) // Ensure we don't sync the store automatically.
3535

3636
// GIVEN: a manager which will pass or fail notifications based on their "nice" labels
37-
mgr, err := notifications.NewManager(cfg, interceptor, createMetrics(), logger.Named("notifications-manager"))
37+
mgr, err := notifications.NewManager(cfg, interceptor, defaultHelpers(), createMetrics(), logger.Named("notifications-manager"))
3838
require.NoError(t, err)
3939
mgr.WithHandlers(map[database.NotificationMethod]notifications.Handler{
4040
database.NotificationMethodSmtp: santa,
@@ -150,7 +150,7 @@ func TestStopBeforeRun(t *testing.T) {
150150
ctx, logger, db := setupInMemory(t)
151151

152152
// GIVEN: a standard manager
153-
mgr, err := notifications.NewManager(defaultNotificationsConfig(database.NotificationMethodSmtp), db, createMetrics(), logger.Named("notifications-manager"))
153+
mgr, err := notifications.NewManager(defaultNotificationsConfig(database.NotificationMethodSmtp), db, defaultHelpers(), createMetrics(), logger.Named("notifications-manager"))
154154
require.NoError(t, err)
155155

156156
// THEN: validate that the manager can be stopped safely without Run() having been called yet

coderd/notifications/metrics_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func TestMetrics(t *testing.T) {
5151
cfg.RetryInterval = serpent.Duration(time.Millisecond * 50)
5252
cfg.StoreSyncInterval = serpent.Duration(time.Millisecond * 100) // Twice as long as fetch interval to ensure we catch pending updates.
5353

54-
mgr, err := notifications.NewManager(cfg, store, metrics, logger.Named("manager"))
54+
mgr, err := notifications.NewManager(cfg, store, defaultHelpers(), metrics, logger.Named("manager"))
5555
require.NoError(t, err)
5656
t.Cleanup(func() {
5757
assert.NoError(t, mgr.Stop(ctx))
@@ -218,7 +218,7 @@ func TestPendingUpdatesMetric(t *testing.T) {
218218

219219
syncer := &syncInterceptor{Store: store}
220220
interceptor := newUpdateSignallingInterceptor(syncer)
221-
mgr, err := notifications.NewManager(cfg, interceptor, metrics, logger.Named("manager"))
221+
mgr, err := notifications.NewManager(cfg, interceptor, defaultHelpers(), metrics, logger.Named("manager"))
222222
require.NoError(t, err)
223223
t.Cleanup(func() {
224224
assert.NoError(t, mgr.Stop(ctx))
@@ -292,7 +292,7 @@ func TestInflightDispatchesMetric(t *testing.T) {
292292
cfg.RetryInterval = serpent.Duration(time.Hour) // Delay retries so they don't interfere.
293293
cfg.StoreSyncInterval = serpent.Duration(time.Millisecond * 100)
294294

295-
mgr, err := notifications.NewManager(cfg, store, metrics, logger.Named("manager"))
295+
mgr, err := notifications.NewManager(cfg, store, defaultHelpers(), metrics, logger.Named("manager"))
296296
require.NoError(t, err)
297297
t.Cleanup(func() {
298298
assert.NoError(t, mgr.Stop(ctx))
@@ -371,7 +371,7 @@ func TestCustomMethodMetricCollection(t *testing.T) {
371371

372372
// WHEN: two notifications (each with different templates) are enqueued.
373373
cfg := defaultNotificationsConfig(defaultMethod)
374-
mgr, err := notifications.NewManager(cfg, store, metrics, logger.Named("manager"))
374+
mgr, err := notifications.NewManager(cfg, store, defaultHelpers(), metrics, logger.Named("manager"))
375375
require.NoError(t, err)
376376
t.Cleanup(func() {
377377
assert.NoError(t, mgr.Stop(ctx))

0 commit comments

Comments
 (0)