mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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](https://github.com/coder/internal/issues/523)
This commit is contained in:
Generated
+17
@@ -1693,6 +1693,13 @@ const docTemplate = `{
|
||||
"description": "Filter notifications by read status. Possible values: read, unread, all",
|
||||
"name": "read_status",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -1757,6 +1764,16 @@ const docTemplate = `{
|
||||
"description": "Filter notifications by read status. Possible values: read, unread, all",
|
||||
"name": "read_status",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"plaintext",
|
||||
"markdown"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "Define the output format for notifications title and body.",
|
||||
"name": "format",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
||||
Generated
+14
@@ -1474,6 +1474,13 @@
|
||||
"description": "Filter notifications by read status. Possible values: read, unread, all",
|
||||
"name": "read_status",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -1532,6 +1539,13 @@
|
||||
"description": "Filter notifications by read status. Possible values: read, unread, all",
|
||||
"name": "read_status",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": ["plaintext", "markdown"],
|
||||
"type": "string",
|
||||
"description": "Define the output format for notifications title and body.",
|
||||
"name": "format",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
||||
@@ -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." enums(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),
|
||||
@@ -196,6 +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" format(uuid)
|
||||
// @Success 200 {object} codersdk.ListInboxNotificationsResponse
|
||||
// @Router /notifications/inbox [get]
|
||||
func (api *API) listInboxNotifications(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Generated
+14
-5
@@ -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(uuid) | false | ID of the last notification from the current page. Notifications returned will be older than the associated one |
|
||||
|
||||
### Example responses
|
||||
|
||||
@@ -146,6 +147,14 @@ curl -X GET http://coder-server:8080/api/v2/notifications/inbox/watch \
|
||||
| `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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user