From f06d97b3db29a3113a74e00a19c3ed96ba61bb0f Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 2 Oct 2024 14:29:09 +0200 Subject: [PATCH 1/4] feat: expose Markdown fields in webhook payload --- coderd/notifications/dispatch/webhook.go | 34 +++++++++++-------- coderd/notifications/dispatch/webhook_test.go | 17 ++++++---- docs/admin/notifications/slack.md | 12 ++++--- docs/admin/notifications/teams.md | 8 ++--- 4 files changed, 40 insertions(+), 31 deletions(-) diff --git a/coderd/notifications/dispatch/webhook.go b/coderd/notifications/dispatch/webhook.go index 4a548b40e4c2f..b19d1d71d1d23 100644 --- a/coderd/notifications/dispatch/webhook.go +++ b/coderd/notifications/dispatch/webhook.go @@ -28,43 +28,47 @@ type WebhookHandler struct { // WebhookPayload describes the JSON payload to be delivered to the configured webhook endpoint. type WebhookPayload struct { - Version string `json:"_version"` - MsgID uuid.UUID `json:"msg_id"` - Payload types.MessagePayload `json:"payload"` - Title string `json:"title"` - Body string `json:"body"` + Version string `json:"_version"` + MsgID uuid.UUID `json:"msg_id"` + Payload types.MessagePayload `json:"payload"` + Title string `json:"title"` + TitleMarkdown string `json:"title_markdown"` + Body string `json:"body"` + BodyMarkdown string `json:"body_markdown"` } func NewWebhookHandler(cfg codersdk.NotificationsWebhookConfig, log slog.Logger) *WebhookHandler { return &WebhookHandler{cfg: cfg, log: log, cl: &http.Client{}} } -func (w *WebhookHandler) Dispatcher(payload types.MessagePayload, titleTmpl, bodyTmpl string) (DeliveryFunc, error) { +func (w *WebhookHandler) Dispatcher(payload types.MessagePayload, title, body string) (DeliveryFunc, error) { if w.cfg.Endpoint.String() == "" { return nil, xerrors.New("webhook endpoint not defined") } - title, err := markdown.PlaintextFromMarkdown(titleTmpl) + titlePlaintext, err := markdown.PlaintextFromMarkdown(title) if err != nil { return nil, xerrors.Errorf("render title: %w", err) } - body, err := markdown.PlaintextFromMarkdown(bodyTmpl) + bodyPlaintext, err := markdown.PlaintextFromMarkdown(body) if err != nil { return nil, xerrors.Errorf("render body: %w", err) } - return w.dispatch(payload, title, body, w.cfg.Endpoint.String()), nil + return w.dispatch(payload, titlePlaintext, title, bodyPlaintext, body, w.cfg.Endpoint.String()), nil } -func (w *WebhookHandler) dispatch(msgPayload types.MessagePayload, title, body, endpoint string) DeliveryFunc { +func (w *WebhookHandler) dispatch(msgPayload types.MessagePayload, titlePlaintext, titleMarkdown, bodyPlaintext, bodyMarkdown, endpoint string) DeliveryFunc { return func(ctx context.Context, msgID uuid.UUID) (retryable bool, err error) { // Prepare payload. payload := WebhookPayload{ - Version: "1.0", - MsgID: msgID, - Title: title, - Body: body, - Payload: msgPayload, + Version: "1.0", + MsgID: msgID, + Title: titlePlaintext, + TitleMarkdown: titleMarkdown, + Body: bodyPlaintext, + BodyMarkdown: bodyMarkdown, + Payload: msgPayload, } m, err := json.Marshal(payload) if err != nil { diff --git a/coderd/notifications/dispatch/webhook_test.go b/coderd/notifications/dispatch/webhook_test.go index 3bfcfd8a2e621..26a78752cfd45 100644 --- a/coderd/notifications/dispatch/webhook_test.go +++ b/coderd/notifications/dispatch/webhook_test.go @@ -28,17 +28,15 @@ func TestWebhook(t *testing.T) { t.Parallel() const ( - titleTemplate = "this is the title ({{.Labels.foo}})" - bodyTemplate = "this is the body ({{.Labels.baz}})" + titlePlaintext = "this is the title" + titleMarkdown = "this *is* _the_ title" + bodyPlaintext = "this is the body" + bodyMarkdown = "~this~ is the `body`" ) msgPayload := types.MessagePayload{ Version: "1.0", NotificationName: "test", - Labels: map[string]string{ - "foo": "bar", - "baz": "quux", - }, } tests := []struct { @@ -61,6 +59,11 @@ func TestWebhook(t *testing.T) { assert.Equal(t, msgID, payload.MsgID) assert.Equal(t, msgID.String(), r.Header.Get("X-Message-Id")) + assert.Equal(t, titlePlaintext, payload.Title) + assert.Equal(t, titleMarkdown, payload.TitleMarkdown) + assert.Equal(t, bodyPlaintext, payload.Body) + assert.Equal(t, bodyMarkdown, payload.BodyMarkdown) + w.WriteHeader(http.StatusOK) _, err = w.Write([]byte(fmt.Sprintf("received %s", payload.MsgID))) assert.NoError(t, err) @@ -138,7 +141,7 @@ func TestWebhook(t *testing.T) { Endpoint: *serpent.URLOf(endpoint), } handler := dispatch.NewWebhookHandler(cfg, logger.With(slog.F("test", tc.name))) - deliveryFn, err := handler.Dispatcher(msgPayload, titleTemplate, bodyTemplate) + deliveryFn, err := handler.Dispatcher(msgPayload, titleMarkdown, bodyMarkdown) require.NoError(t, err) retryable, err := deliveryFn(ctx, msgID) diff --git a/docs/admin/notifications/slack.md b/docs/admin/notifications/slack.md index aa6a4dcdb5655..554e5c986a39c 100644 --- a/docs/admin/notifications/slack.md +++ b/docs/admin/notifications/slack.md @@ -90,9 +90,11 @@ receiver.router.post("/v1/webhook", async (req, res) => { return res.status(400).send("Error: request body is missing"); } - const { title, body } = req.body; - if (!title || !body) { - return res.status(400).send('Error: missing fields: "title", or "body"'); + const { title_markdown, body_markdown } = req.body; + if (!title_markdown || !body_markdown) { + return res + .status(400) + .send('Error: missing fields: "title_markdown", or "body_markdown"'); } const payload = req.body.payload; @@ -118,11 +120,11 @@ receiver.router.post("/v1/webhook", async (req, res) => { blocks: [ { type: "header", - text: { type: "plain_text", text: title }, + text: { type: "mrkdwn", text: title_markdown }, }, { type: "section", - text: { type: "mrkdwn", text: body }, + text: { type: "mrkdwn", text: body_markdown }, }, ], }; diff --git a/docs/admin/notifications/teams.md b/docs/admin/notifications/teams.md index 92957dd464d46..7accfbe9568a4 100644 --- a/docs/admin/notifications/teams.md +++ b/docs/admin/notifications/teams.md @@ -67,10 +67,10 @@ The process of setting up a Teams workflow consists of three key steps: } } }, - "title": { + "title_markdown": { "type": "string" }, - "body": { + "body_markdown": { "type": "string" } } @@ -108,11 +108,11 @@ The process of setting up a Teams workflow consists of three key steps: }, { "type": "TextBlock", - "text": "**@{replace(body('Parse_JSON')?['title'], '"', '\"')}**" + "text": "**@{replace(body('Parse_JSON')?['title_markdown'], '"', '\"')}**" }, { "type": "TextBlock", - "text": "@{replace(body('Parse_JSON')?['body'], '"', '\"')}", + "text": "@{replace(body('Parse_JSON')?['body_markdown'], '"', '\"')}", "wrap": true }, { From 366551f35af4a500d11966fb9c550a597515fce3 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 2 Oct 2024 15:06:27 +0200 Subject: [PATCH 2/4] bump version --- coderd/notifications/dispatch/webhook.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/notifications/dispatch/webhook.go b/coderd/notifications/dispatch/webhook.go index b19d1d71d1d23..c8335dc9fb3c2 100644 --- a/coderd/notifications/dispatch/webhook.go +++ b/coderd/notifications/dispatch/webhook.go @@ -62,7 +62,7 @@ func (w *WebhookHandler) dispatch(msgPayload types.MessagePayload, titlePlaintex return func(ctx context.Context, msgID uuid.UUID) (retryable bool, err error) { // Prepare payload. payload := WebhookPayload{ - Version: "1.0", + Version: "1.1", MsgID: msgID, Title: titlePlaintext, TitleMarkdown: titleMarkdown, From 8023fae5fc32b0b989b73cf3468ab99ee68ad755 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 2 Oct 2024 15:11:50 +0200 Subject: [PATCH 3/4] fix --- coderd/notifications/notifications_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/notifications/notifications_test.go b/coderd/notifications/notifications_test.go index ca1f4f78aad72..e52610c9c5823 100644 --- a/coderd/notifications/notifications_test.go +++ b/coderd/notifications/notifications_test.go @@ -249,7 +249,7 @@ func TestWebhookDispatch(t *testing.T) { // THEN: the webhook is received by the mock server and has the expected contents payload := testutil.RequireRecvCtx(testutil.Context(t, testutil.WaitShort), t, sent) - require.EqualValues(t, "1.0", payload.Version) + require.EqualValues(t, "1.1", payload.Version) require.Equal(t, *msgID, payload.MsgID) require.Equal(t, payload.Payload.Labels, input) require.Equal(t, payload.Payload.UserEmail, email) From d0d963d873c801f8c527aab57ab3c89742b4f37e Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 2 Oct 2024 15:19:27 +0200 Subject: [PATCH 4/4] fix --- coderd/notifications/dispatch/webhook.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/coderd/notifications/dispatch/webhook.go b/coderd/notifications/dispatch/webhook.go index c8335dc9fb3c2..fcad3a7b0eae2 100644 --- a/coderd/notifications/dispatch/webhook.go +++ b/coderd/notifications/dispatch/webhook.go @@ -41,21 +41,21 @@ func NewWebhookHandler(cfg codersdk.NotificationsWebhookConfig, log slog.Logger) return &WebhookHandler{cfg: cfg, log: log, cl: &http.Client{}} } -func (w *WebhookHandler) Dispatcher(payload types.MessagePayload, title, body string) (DeliveryFunc, error) { +func (w *WebhookHandler) Dispatcher(payload types.MessagePayload, titleMarkdown, bodyMarkdown string) (DeliveryFunc, error) { if w.cfg.Endpoint.String() == "" { return nil, xerrors.New("webhook endpoint not defined") } - titlePlaintext, err := markdown.PlaintextFromMarkdown(title) + titlePlaintext, err := markdown.PlaintextFromMarkdown(titleMarkdown) if err != nil { return nil, xerrors.Errorf("render title: %w", err) } - bodyPlaintext, err := markdown.PlaintextFromMarkdown(body) + bodyPlaintext, err := markdown.PlaintextFromMarkdown(bodyMarkdown) if err != nil { return nil, xerrors.Errorf("render body: %w", err) } - return w.dispatch(payload, titlePlaintext, title, bodyPlaintext, body, w.cfg.Endpoint.String()), nil + return w.dispatch(payload, titlePlaintext, titleMarkdown, bodyPlaintext, bodyMarkdown, w.cfg.Endpoint.String()), nil } func (w *WebhookHandler) dispatch(msgPayload types.MessagePayload, titlePlaintext, titleMarkdown, bodyPlaintext, bodyMarkdown, endpoint string) DeliveryFunc { 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