Skip to content

Commit 82e3773

Browse files
authored
feat(coderd): add format option to inbox notifications watch endpoint (coder#17034)
This PR aims to allow clients to use different format for the title and content of inbox notifications - on the watch endpoint. This solution will help to have it working and formatted differently on VSCode, WebUI ... [Related to this issue](coder/internal#523)
1 parent 0474888 commit 82e3773

File tree

6 files changed

+128
-17
lines changed

6 files changed

+128
-17
lines changed

coderd/apidoc/docs.go

+17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/inboxnotifications.go

+26
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,17 @@ import (
1717
"github.com/coder/coder/v2/coderd/httpapi"
1818
"github.com/coder/coder/v2/coderd/httpmw"
1919
"github.com/coder/coder/v2/coderd/pubsub"
20+
markdown "github.com/coder/coder/v2/coderd/render"
2021
"github.com/coder/coder/v2/codersdk"
2122
"github.com/coder/coder/v2/codersdk/wsjson"
2223
"github.com/coder/websocket"
2324
)
2425

26+
const (
27+
notificationFormatMarkdown = "markdown"
28+
notificationFormatPlaintext = "plaintext"
29+
)
30+
2531
// convertInboxNotificationResponse works as a util function to transform a database.InboxNotification to codersdk.InboxNotification
2632
func convertInboxNotificationResponse(ctx context.Context, logger slog.Logger, notif database.InboxNotification) codersdk.InboxNotification {
2733
return codersdk.InboxNotification{
@@ -60,6 +66,7 @@ func convertInboxNotificationResponse(ctx context.Context, logger slog.Logger, n
6066
// @Param targets query string false "Comma-separated list of target IDs to filter notifications"
6167
// @Param templates query string false "Comma-separated list of template IDs to filter notifications"
6268
// @Param read_status query string false "Filter notifications by read status. Possible values: read, unread, all"
69+
// @Param format query string false "Define the output format for notifications title and body." enums(plaintext,markdown)
6370
// @Success 200 {object} codersdk.GetInboxNotificationResponse
6471
// @Router /notifications/inbox/watch [get]
6572
func (api *API) watchInboxNotifications(rw http.ResponseWriter, r *http.Request) {
@@ -73,6 +80,7 @@ func (api *API) watchInboxNotifications(rw http.ResponseWriter, r *http.Request)
7380
targets = p.UUIDs(vals, []uuid.UUID{}, "targets")
7481
templates = p.UUIDs(vals, []uuid.UUID{}, "templates")
7582
readStatus = p.String(vals, "all", "read_status")
83+
format = p.String(vals, notificationFormatMarkdown, "format")
7684
)
7785
p.ErrorExcessParams(vals)
7886
if len(p.Errors) > 0 {
@@ -176,6 +184,23 @@ func (api *API) watchInboxNotifications(rw http.ResponseWriter, r *http.Request)
176184
api.Logger.Error(ctx, "failed to count unread inbox notifications", slog.Error(err))
177185
return
178186
}
187+
188+
// By default, notifications are stored as markdown
189+
// We can change the format based on parameter if required
190+
if format == notificationFormatPlaintext {
191+
notif.Title, err = markdown.PlaintextFromMarkdown(notif.Title)
192+
if err != nil {
193+
api.Logger.Error(ctx, "failed to convert notification title to plain text", slog.Error(err))
194+
return
195+
}
196+
197+
notif.Content, err = markdown.PlaintextFromMarkdown(notif.Content)
198+
if err != nil {
199+
api.Logger.Error(ctx, "failed to convert notification content to plain text", slog.Error(err))
200+
return
201+
}
202+
}
203+
179204
if err := encoder.Encode(codersdk.GetInboxNotificationResponse{
180205
Notification: notif,
181206
UnreadCount: int(unreadCount),
@@ -196,6 +221,7 @@ func (api *API) watchInboxNotifications(rw http.ResponseWriter, r *http.Request)
196221
// @Param targets query string false "Comma-separated list of target IDs to filter notifications"
197222
// @Param templates query string false "Comma-separated list of template IDs to filter notifications"
198223
// @Param read_status query string false "Filter notifications by read status. Possible values: read, unread, all"
224+
// @Param starting_before query string false "ID of the last notification from the current page. Notifications returned will be older than the associated one" format(uuid)
199225
// @Success 200 {object} codersdk.ListInboxNotificationsResponse
200226
// @Router /notifications/inbox [get]
201227
func (api *API) listInboxNotifications(rw http.ResponseWriter, r *http.Request) {

coderd/inboxnotifications_test.go

+56
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,62 @@ func TestInboxNotification_Watch(t *testing.T) {
137137
require.Equal(t, memberClient.ID, notif.Notification.UserID)
138138
})
139139

140+
t.Run("OK - change format", func(t *testing.T) {
141+
t.Parallel()
142+
143+
ctx := testutil.Context(t, testutil.WaitLong)
144+
logger := testutil.Logger(t)
145+
146+
db, ps := dbtestutil.NewDB(t)
147+
148+
firstClient, _, _ := coderdtest.NewWithAPI(t, &coderdtest.Options{
149+
Pubsub: ps,
150+
Database: db,
151+
})
152+
firstUser := coderdtest.CreateFirstUser(t, firstClient)
153+
member, memberClient := coderdtest.CreateAnotherUser(t, firstClient, firstUser.OrganizationID, rbac.RoleTemplateAdmin())
154+
155+
u, err := member.URL.Parse("/api/v2/notifications/inbox/watch?format=plaintext")
156+
require.NoError(t, err)
157+
158+
// nolint:bodyclose
159+
wsConn, resp, err := websocket.Dial(ctx, u.String(), &websocket.DialOptions{
160+
HTTPHeader: http.Header{
161+
"Coder-Session-Token": []string{member.SessionToken()},
162+
},
163+
})
164+
if err != nil {
165+
if resp.StatusCode != http.StatusSwitchingProtocols {
166+
err = codersdk.ReadBodyAsError(resp)
167+
}
168+
require.NoError(t, err)
169+
}
170+
defer wsConn.Close(websocket.StatusNormalClosure, "done")
171+
172+
inboxHandler := dispatch.NewInboxHandler(logger, db, ps)
173+
dispatchFunc, err := inboxHandler.Dispatcher(types.MessagePayload{
174+
UserID: memberClient.ID.String(),
175+
NotificationTemplateID: notifications.TemplateWorkspaceOutOfMemory.String(),
176+
}, "# Notification Title", "This is the __content__.", nil)
177+
require.NoError(t, err)
178+
179+
_, err = dispatchFunc(ctx, uuid.New())
180+
require.NoError(t, err)
181+
182+
_, message, err := wsConn.Read(ctx)
183+
require.NoError(t, err)
184+
185+
var notif codersdk.GetInboxNotificationResponse
186+
err = json.Unmarshal(message, &notif)
187+
require.NoError(t, err)
188+
189+
require.Equal(t, 1, notif.UnreadCount)
190+
require.Equal(t, memberClient.ID, notif.Notification.UserID)
191+
192+
require.Equal(t, "Notification Title", notif.Notification.Title)
193+
require.Equal(t, "This is the content.", notif.Notification.Content)
194+
})
195+
140196
t.Run("OK - filters on templates", func(t *testing.T) {
141197
t.Parallel()
142198

coderd/notifications/dispatch/inbox.go

+1-12
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616
"github.com/coder/coder/v2/coderd/database/pubsub"
1717
"github.com/coder/coder/v2/coderd/notifications/types"
1818
coderdpubsub "github.com/coder/coder/v2/coderd/pubsub"
19-
markdown "github.com/coder/coder/v2/coderd/render"
2019
"github.com/coder/coder/v2/codersdk"
2120
)
2221

@@ -36,17 +35,7 @@ func NewInboxHandler(log slog.Logger, store InboxStore, ps pubsub.Pubsub) *Inbox
3635
}
3736

3837
func (s *InboxHandler) Dispatcher(payload types.MessagePayload, titleTmpl, bodyTmpl string, _ template.FuncMap) (DeliveryFunc, error) {
39-
subject, err := markdown.PlaintextFromMarkdown(titleTmpl)
40-
if err != nil {
41-
return nil, xerrors.Errorf("render subject: %w", err)
42-
}
43-
44-
htmlBody, err := markdown.PlaintextFromMarkdown(bodyTmpl)
45-
if err != nil {
46-
return nil, xerrors.Errorf("render html body: %w", err)
47-
}
48-
49-
return s.dispatch(payload, subject, htmlBody), nil
38+
return s.dispatch(payload, titleTmpl, bodyTmpl), nil
5039
}
5140

5241
func (s *InboxHandler) dispatch(payload types.MessagePayload, title, body string) DeliveryFunc {

docs/reference/api/notifications.md

+14-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)