feat: expose Markdown fields in webhook payload (#14931)

Fixes: https://github.com/coder/coder/issues/14930
This commit is contained in:
Marcin Tojek
2024-10-02 15:38:22 +02:00
committed by GitHub
parent 2f043d7ab9
commit 0aa84b18a1
5 changed files with 41 additions and 32 deletions

View File

@ -32,38 +32,42 @@ type WebhookPayload struct {
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, titleMarkdown, bodyMarkdown 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(titleMarkdown)
if err != nil {
return nil, xerrors.Errorf("render title: %w", err)
}
body, err := markdown.PlaintextFromMarkdown(bodyTmpl)
bodyPlaintext, err := markdown.PlaintextFromMarkdown(bodyMarkdown)
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, titleMarkdown, bodyPlaintext, bodyMarkdown, 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",
Version: "1.1",
MsgID: msgID,
Title: title,
Body: body,
Title: titlePlaintext,
TitleMarkdown: titleMarkdown,
Body: bodyPlaintext,
BodyMarkdown: bodyMarkdown,
Payload: msgPayload,
}
m, err := json.Marshal(payload)

View File

@ -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)

View File

@ -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)

View File

@ -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 },
},
],
};

View File

@ -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
},
{