From ef5759fe2fce5efb60feed8ed534827f64cf56fe Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Mar 2025 01:01:06 +0000 Subject: [PATCH 1/4] update doc and remove plaintext format --- coderd/apidoc/docs.go | 6 ++++++ coderd/apidoc/swagger.json | 6 ++++++ coderd/inboxnotifications.go | 1 + coderd/notifications/dispatch/inbox.go | 13 +------------ docs/reference/api/notifications.md | 11 ++++++----- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 868657683c9c8..eaa1d35085deb 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -1693,6 +1693,12 @@ const docTemplate = `{ "description": "Filter notifications by read status. Possible values: read, unread, all", "name": "read_status", "in": "query" + }, + { + "type": "string", + "description": "ID of the last notification from the current page. Notifications returned will be older than the associated one", + "name": "starting_before", + "in": "query" } ], "responses": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index a82fd53d6b24f..07b9c7e3be376 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -1474,6 +1474,12 @@ "description": "Filter notifications by read status. Possible values: read, unread, all", "name": "read_status", "in": "query" + }, + { + "type": "string", + "description": "ID of the last notification from the current page. Notifications returned will be older than the associated one", + "name": "starting_before", + "in": "query" } ], "responses": { diff --git a/coderd/inboxnotifications.go b/coderd/inboxnotifications.go index 23e1c8479a76b..0065653214d8c 100644 --- a/coderd/inboxnotifications.go +++ b/coderd/inboxnotifications.go @@ -196,6 +196,7 @@ func (api *API) watchInboxNotifications(rw http.ResponseWriter, r *http.Request) // @Param targets query string false "Comma-separated list of target IDs to filter notifications" // @Param templates query string false "Comma-separated list of template IDs to filter notifications" // @Param read_status query string false "Filter notifications by read status. Possible values: read, unread, all" +// @Param starting_before query string false "ID of the last notification from the current page. Notifications returned will be older than the associated one" // @Success 200 {object} codersdk.ListInboxNotificationsResponse // @Router /notifications/inbox [get] func (api *API) listInboxNotifications(rw http.ResponseWriter, r *http.Request) { diff --git a/coderd/notifications/dispatch/inbox.go b/coderd/notifications/dispatch/inbox.go index 9383e89afec3e..63e21acb56b80 100644 --- a/coderd/notifications/dispatch/inbox.go +++ b/coderd/notifications/dispatch/inbox.go @@ -16,7 +16,6 @@ import ( "github.com/coder/coder/v2/coderd/database/pubsub" "github.com/coder/coder/v2/coderd/notifications/types" coderdpubsub "github.com/coder/coder/v2/coderd/pubsub" - markdown "github.com/coder/coder/v2/coderd/render" "github.com/coder/coder/v2/codersdk" ) @@ -36,17 +35,7 @@ func NewInboxHandler(log slog.Logger, store InboxStore, ps pubsub.Pubsub) *Inbox } func (s *InboxHandler) Dispatcher(payload types.MessagePayload, titleTmpl, bodyTmpl string, _ template.FuncMap) (DeliveryFunc, error) { - subject, err := markdown.PlaintextFromMarkdown(titleTmpl) - if err != nil { - return nil, xerrors.Errorf("render subject: %w", err) - } - - htmlBody, err := markdown.PlaintextFromMarkdown(bodyTmpl) - if err != nil { - return nil, xerrors.Errorf("render html body: %w", err) - } - - return s.dispatch(payload, subject, htmlBody), nil + return s.dispatch(payload, titleTmpl, bodyTmpl), nil } func (s *InboxHandler) dispatch(payload types.MessagePayload, title, body string) DeliveryFunc { diff --git a/docs/reference/api/notifications.md b/docs/reference/api/notifications.md index 67b61bccb6302..0d83ec91aeb9d 100644 --- a/docs/reference/api/notifications.md +++ b/docs/reference/api/notifications.md @@ -61,11 +61,12 @@ curl -X GET http://coder-server:8080/api/v2/notifications/inbox \ ### Parameters -| Name | In | Type | Required | Description | -|---------------|-------|--------|----------|-------------------------------------------------------------------------| -| `targets` | query | string | false | Comma-separated list of target IDs to filter notifications | -| `templates` | query | string | false | Comma-separated list of template IDs to filter notifications | -| `read_status` | query | string | false | Filter notifications by read status. Possible values: read, unread, all | +| Name | In | Type | Required | Description | +|-------------------|-------|--------|----------|-----------------------------------------------------------------------------------------------------------------| +| `targets` | query | string | false | Comma-separated list of target IDs to filter notifications | +| `templates` | query | string | false | Comma-separated list of template IDs to filter notifications | +| `read_status` | query | string | false | Filter notifications by read status. Possible values: read, unread, all | +| `starting_before` | query | string | false | ID of the last notification from the current page. Notifications returned will be older than the associated one | ### Example responses From 63ffa9b1c6a5357b86b02fb2f19aed45881e8dcf Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Mar 2025 14:21:03 +0000 Subject: [PATCH 2/4] add format logic into watch endpoint --- coderd/inboxnotifications.go | 25 ++++++++++++++ coderd/inboxnotifications_test.go | 56 +++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/coderd/inboxnotifications.go b/coderd/inboxnotifications.go index 0065653214d8c..e8f5b0aa8bcdc 100644 --- a/coderd/inboxnotifications.go +++ b/coderd/inboxnotifications.go @@ -17,11 +17,17 @@ import ( "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/coderd/pubsub" + markdown "github.com/coder/coder/v2/coderd/render" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/wsjson" "github.com/coder/websocket" ) +const ( + notificationFormatMarkdown = "markdown" + notificationFormatPlaintext = "plaintext" +) + // convertInboxNotificationResponse works as a util function to transform a database.InboxNotification to codersdk.InboxNotification func convertInboxNotificationResponse(ctx context.Context, logger slog.Logger, notif database.InboxNotification) codersdk.InboxNotification { return codersdk.InboxNotification{ @@ -60,6 +66,7 @@ func convertInboxNotificationResponse(ctx context.Context, logger slog.Logger, n // @Param targets query string false "Comma-separated list of target IDs to filter notifications" // @Param templates query string false "Comma-separated list of template IDs to filter notifications" // @Param read_status query string false "Filter notifications by read status. Possible values: read, unread, all" +// @Param format query string false "Define the output format for notifications title and body. Possible values: plaintext, markdown" // @Success 200 {object} codersdk.GetInboxNotificationResponse // @Router /notifications/inbox/watch [get] func (api *API) watchInboxNotifications(rw http.ResponseWriter, r *http.Request) { @@ -73,6 +80,7 @@ func (api *API) watchInboxNotifications(rw http.ResponseWriter, r *http.Request) targets = p.UUIDs(vals, []uuid.UUID{}, "targets") templates = p.UUIDs(vals, []uuid.UUID{}, "templates") readStatus = p.String(vals, "all", "read_status") + format = p.String(vals, notificationFormatMarkdown, "format") ) p.ErrorExcessParams(vals) if len(p.Errors) > 0 { @@ -176,6 +184,23 @@ func (api *API) watchInboxNotifications(rw http.ResponseWriter, r *http.Request) api.Logger.Error(ctx, "failed to count unread inbox notifications", slog.Error(err)) return } + + // By default, notifications are stored as markdown + // We can change the format based on parameter if required + if format == notificationFormatPlaintext { + notif.Title, err = markdown.PlaintextFromMarkdown(notif.Title) + if err != nil { + api.Logger.Error(ctx, "failed to convert notification title to plain text", slog.Error(err)) + return + } + + notif.Content, err = markdown.PlaintextFromMarkdown(notif.Content) + if err != nil { + api.Logger.Error(ctx, "failed to convert notification content to plain text", slog.Error(err)) + return + } + } + if err := encoder.Encode(codersdk.GetInboxNotificationResponse{ Notification: notif, UnreadCount: int(unreadCount), diff --git a/coderd/inboxnotifications_test.go b/coderd/inboxnotifications_test.go index ef095ed72988c..ed0696195cb60 100644 --- a/coderd/inboxnotifications_test.go +++ b/coderd/inboxnotifications_test.go @@ -137,6 +137,62 @@ func TestInboxNotification_Watch(t *testing.T) { require.Equal(t, memberClient.ID, notif.Notification.UserID) }) + t.Run("OK - change format", func(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitLong) + logger := testutil.Logger(t) + + db, ps := dbtestutil.NewDB(t) + + firstClient, _, _ := coderdtest.NewWithAPI(t, &coderdtest.Options{ + Pubsub: ps, + Database: db, + }) + firstUser := coderdtest.CreateFirstUser(t, firstClient) + member, memberClient := coderdtest.CreateAnotherUser(t, firstClient, firstUser.OrganizationID, rbac.RoleTemplateAdmin()) + + u, err := member.URL.Parse("/api/v2/notifications/inbox/watch?format=plaintext") + require.NoError(t, err) + + // nolint:bodyclose + wsConn, resp, err := websocket.Dial(ctx, u.String(), &websocket.DialOptions{ + HTTPHeader: http.Header{ + "Coder-Session-Token": []string{member.SessionToken()}, + }, + }) + if err != nil { + if resp.StatusCode != http.StatusSwitchingProtocols { + err = codersdk.ReadBodyAsError(resp) + } + require.NoError(t, err) + } + defer wsConn.Close(websocket.StatusNormalClosure, "done") + + inboxHandler := dispatch.NewInboxHandler(logger, db, ps) + dispatchFunc, err := inboxHandler.Dispatcher(types.MessagePayload{ + UserID: memberClient.ID.String(), + NotificationTemplateID: notifications.TemplateWorkspaceOutOfMemory.String(), + }, "# Notification Title", "This is the __content__.", nil) + require.NoError(t, err) + + _, err = dispatchFunc(ctx, uuid.New()) + require.NoError(t, err) + + _, message, err := wsConn.Read(ctx) + require.NoError(t, err) + + var notif codersdk.GetInboxNotificationResponse + err = json.Unmarshal(message, ¬if) + require.NoError(t, err) + + require.Equal(t, 1, notif.UnreadCount) + require.Equal(t, memberClient.ID, notif.Notification.UserID) + + require.Equal(t, "Notification Title", notif.Notification.Title) + require.Equal(t, "This is the content.", notif.Notification.Content) + }) + t.Run("OK - filters on templates", func(t *testing.T) { t.Parallel() From 7a19ecedbf653327579c57542aef3eda0b8711a6 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Mar 2025 14:36:21 +0000 Subject: [PATCH 3/4] gen and fmt --- coderd/apidoc/docs.go | 6 ++++++ coderd/apidoc/swagger.json | 6 ++++++ docs/reference/api/notifications.md | 11 ++++++----- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index eaa1d35085deb..61ca20b2579f1 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -1763,6 +1763,12 @@ const docTemplate = `{ "description": "Filter notifications by read status. Possible values: read, unread, all", "name": "read_status", "in": "query" + }, + { + "type": "string", + "description": "Define the output format for notifications title and body. Possible values: plaintext, markdown", + "name": "format", + "in": "query" } ], "responses": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 07b9c7e3be376..a07741153a57e 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -1538,6 +1538,12 @@ "description": "Filter notifications by read status. Possible values: read, unread, all", "name": "read_status", "in": "query" + }, + { + "type": "string", + "description": "Define the output format for notifications title and body. Possible values: plaintext, markdown", + "name": "format", + "in": "query" } ], "responses": { diff --git a/docs/reference/api/notifications.md b/docs/reference/api/notifications.md index 0d83ec91aeb9d..64cb7aadc802c 100644 --- a/docs/reference/api/notifications.md +++ b/docs/reference/api/notifications.md @@ -142,11 +142,12 @@ curl -X GET http://coder-server:8080/api/v2/notifications/inbox/watch \ ### Parameters -| Name | In | Type | Required | Description | -|---------------|-------|--------|----------|-------------------------------------------------------------------------| -| `targets` | query | string | false | Comma-separated list of target IDs to filter notifications | -| `templates` | query | string | false | Comma-separated list of template IDs to filter notifications | -| `read_status` | query | string | false | Filter notifications by read status. Possible values: read, unread, all | +| Name | In | Type | Required | Description | +|---------------|-------|--------|----------|-------------------------------------------------------------------------------------------------| +| `targets` | query | string | false | Comma-separated list of target IDs to filter notifications | +| `templates` | query | string | false | Comma-separated list of template IDs to filter notifications | +| `read_status` | query | string | false | Filter notifications by read status. Possible values: read, unread, all | +| `format` | query | string | false | Define the output format for notifications title and body. Possible values: plaintext, markdown | ### Example responses From 0babb430ccd9db9c58436211b1b9b18da6a6f851 Mon Sep 17 00:00:00 2001 From: defelmnq Date: Fri, 21 Mar 2025 15:07:28 +0000 Subject: [PATCH 4/4] add better doc description --- coderd/apidoc/docs.go | 7 ++++++- coderd/apidoc/swagger.json | 4 +++- coderd/inboxnotifications.go | 4 ++-- docs/reference/api/notifications.md | 31 ++++++++++++++++++----------- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 61ca20b2579f1..fe6aacf84d5dd 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -1696,6 +1696,7 @@ const docTemplate = `{ }, { "type": "string", + "format": "uuid", "description": "ID of the last notification from the current page. Notifications returned will be older than the associated one", "name": "starting_before", "in": "query" @@ -1765,8 +1766,12 @@ const docTemplate = `{ "in": "query" }, { + "enum": [ + "plaintext", + "markdown" + ], "type": "string", - "description": "Define the output format for notifications title and body. Possible values: plaintext, markdown", + "description": "Define the output format for notifications title and body.", "name": "format", "in": "query" } diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index a07741153a57e..7a399a0e044b4 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -1477,6 +1477,7 @@ }, { "type": "string", + "format": "uuid", "description": "ID of the last notification from the current page. Notifications returned will be older than the associated one", "name": "starting_before", "in": "query" @@ -1540,8 +1541,9 @@ "in": "query" }, { + "enum": ["plaintext", "markdown"], "type": "string", - "description": "Define the output format for notifications title and body. Possible values: plaintext, markdown", + "description": "Define the output format for notifications title and body.", "name": "format", "in": "query" } diff --git a/coderd/inboxnotifications.go b/coderd/inboxnotifications.go index e8f5b0aa8bcdc..37ae8905c7d24 100644 --- a/coderd/inboxnotifications.go +++ b/coderd/inboxnotifications.go @@ -66,7 +66,7 @@ func convertInboxNotificationResponse(ctx context.Context, logger slog.Logger, n // @Param targets query string false "Comma-separated list of target IDs to filter notifications" // @Param templates query string false "Comma-separated list of template IDs to filter notifications" // @Param read_status query string false "Filter notifications by read status. Possible values: read, unread, all" -// @Param format query string false "Define the output format for notifications title and body. Possible values: plaintext, markdown" +// @Param format query string false "Define the output format for notifications title and body." enums(plaintext,markdown) // @Success 200 {object} codersdk.GetInboxNotificationResponse // @Router /notifications/inbox/watch [get] func (api *API) watchInboxNotifications(rw http.ResponseWriter, r *http.Request) { @@ -221,7 +221,7 @@ func (api *API) watchInboxNotifications(rw http.ResponseWriter, r *http.Request) // @Param targets query string false "Comma-separated list of target IDs to filter notifications" // @Param templates query string false "Comma-separated list of template IDs to filter notifications" // @Param read_status query string false "Filter notifications by read status. Possible values: read, unread, all" -// @Param starting_before query string false "ID of the last notification from the current page. Notifications returned will be older than the associated one" +// @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) // @Success 200 {object} codersdk.ListInboxNotificationsResponse // @Router /notifications/inbox [get] func (api *API) listInboxNotifications(rw http.ResponseWriter, r *http.Request) { diff --git a/docs/reference/api/notifications.md b/docs/reference/api/notifications.md index 64cb7aadc802c..09890d3b17864 100644 --- a/docs/reference/api/notifications.md +++ b/docs/reference/api/notifications.md @@ -61,12 +61,12 @@ curl -X GET http://coder-server:8080/api/v2/notifications/inbox \ ### Parameters -| Name | In | Type | Required | Description | -|-------------------|-------|--------|----------|-----------------------------------------------------------------------------------------------------------------| -| `targets` | query | string | false | Comma-separated list of target IDs to filter notifications | -| `templates` | query | string | false | Comma-separated list of template IDs to filter notifications | -| `read_status` | query | string | false | Filter notifications by read status. Possible values: read, unread, all | -| `starting_before` | query | string | false | ID of the last notification from the current page. Notifications returned will be older than the associated one | +| Name | In | Type | Required | Description | +|-------------------|-------|--------------|----------|-----------------------------------------------------------------------------------------------------------------| +| `targets` | query | string | false | Comma-separated list of target IDs to filter notifications | +| `templates` | query | string | false | Comma-separated list of template IDs to filter notifications | +| `read_status` | query | string | false | Filter notifications by read status. Possible values: read, unread, all | +| `starting_before` | query | string(uuid) | false | ID of the last notification from the current page. Notifications returned will be older than the associated one | ### Example responses @@ -142,12 +142,19 @@ curl -X GET http://coder-server:8080/api/v2/notifications/inbox/watch \ ### Parameters -| Name | In | Type | Required | Description | -|---------------|-------|--------|----------|-------------------------------------------------------------------------------------------------| -| `targets` | query | string | false | Comma-separated list of target IDs to filter notifications | -| `templates` | query | string | false | Comma-separated list of template IDs to filter notifications | -| `read_status` | query | string | false | Filter notifications by read status. Possible values: read, unread, all | -| `format` | query | string | false | Define the output format for notifications title and body. Possible values: plaintext, markdown | +| Name | In | Type | Required | Description | +|---------------|-------|--------|----------|-------------------------------------------------------------------------| +| `targets` | query | string | false | Comma-separated list of target IDs to filter notifications | +| `templates` | query | string | false | Comma-separated list of template IDs to filter notifications | +| `read_status` | query | string | false | Filter notifications by read status. Possible values: read, unread, all | +| `format` | query | string | false | Define the output format for notifications title and body. | + +#### Enumerated Values + +| Parameter | Value | +|-----------|-------------| +| `format` | `plaintext` | +| `format` | `markdown` | ### Example responses