Skip to content

Commit 82e3773

Browse files
authored
feat(coderd): add format option to inbox notifications watch endpoint (#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

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

coderd/inboxnotifications.go

Lines changed: 26 additions & 0 deletions
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

Lines changed: 56 additions & 0 deletions
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

Lines changed: 1 addition & 12 deletions
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

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

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