diff --git a/frontend/src/component/integrations/IntegrationForm/IntegrationParameters/IntegrationParameter/IntegrationParameterTextField.tsx b/frontend/src/component/integrations/IntegrationForm/IntegrationParameters/IntegrationParameter/IntegrationParameterTextField.tsx index 9b226cc0ba..544092962e 100644 --- a/frontend/src/component/integrations/IntegrationForm/IntegrationParameters/IntegrationParameter/IntegrationParameterTextField.tsx +++ b/frontend/src/component/integrations/IntegrationForm/IntegrationParameters/IntegrationParameter/IntegrationParameterTextField.tsx @@ -2,6 +2,7 @@ import { TextField, Typography } from '@mui/material'; import type { AddonParameterSchema, AddonSchema } from 'openapi'; import type { ChangeEventHandler } from 'react'; import { styled } from '@mui/material'; +import { Markdown } from 'component/common/Markdown/Markdown'; const MASKED_VALUE = '*****'; @@ -62,7 +63,11 @@ export const IntegrationParameterTextField = ({ error={Boolean(error)} onChange={setParameterValue(definition.name)} variant='outlined' - helperText={definition.description} + helperText={ + definition.description ? ( + {definition.description} + ) : undefined + } /> ); }; diff --git a/package.json b/package.json index 1c10f9a597..c193d72a00 100644 --- a/package.json +++ b/package.json @@ -188,6 +188,7 @@ "@types/make-fetch-happen": "10.0.4", "@types/memoizee": "0.4.11", "@types/mime": "3.0.4", + "@types/mustache": "^4.2.5", "@types/node": "20.14.10", "@types/nodemailer": "6.4.15", "@types/owasp-password-strength-test": "1.3.2", diff --git a/src/lib/addons/__snapshots__/webhook.test.ts.snap b/src/lib/addons/__snapshots__/webhook.test.ts.snap new file mode 100644 index 0000000000..59d357fd93 --- /dev/null +++ b/src/lib/addons/__snapshots__/webhook.test.ts.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Webhook integration should allow for eventJson and eventMarkdown in bodyTemplate 1`] = ` +"{ + "json": "{\\"id\\":1,\\"createdAt\\":\\"2024-07-24T00:00:00.000Z\\",\\"createdByUserId\\":-1337,\\"type\\":\\"feature-created\\",\\"createdBy\\":\\"some@user.com\\",\\"featureName\\":\\"some-toggle\\",\\"project\\":\\"default\\",\\"data\\":{\\"name\\":\\"some-toggle\\",\\"enabled\\":false,\\"strategies\\":[{\\"name\\":\\"default\\"}]}}", + "markdown": "*some@user.com* created *[some-toggle](http://some-url.com/projects/default/features/some-toggle)* in project *[default](http://some-url.com/projects/default)*" +}" +`; diff --git a/src/lib/addons/webhook-definition.ts b/src/lib/addons/webhook-definition.ts index 408821ea6a..72703a27f8 100644 --- a/src/lib/addons/webhook-definition.ts +++ b/src/lib/addons/webhook-definition.ts @@ -91,7 +91,7 @@ const webhookDefinition: IAddonDefinition = { "json": {{{eventJson}}} }`, description: - "(Optional) You may format the body using a mustache template. If you don't specify anything, the format will similar to the events format (https://docs.getunleash.io/reference/api/legacy/unleash/admin/events). You can use {{{eventJson}}} to include entire serialized event.", + '(Optional) You may format the body using a mustache template. If you don\'t specify anything, the format will be similar to the [events format](https://docs.getunleash.io/reference/api/legacy/unleash/admin/events). You can use `{{{eventJson}}}` to include the entire serialized event, or `"{{eventMarkdown}}"` for the formatted description.', type: 'textfield', required: false, sensitive: false, diff --git a/src/lib/addons/webhook.test.ts b/src/lib/addons/webhook.test.ts index d2675634a6..fa1a28e30f 100644 --- a/src/lib/addons/webhook.test.ts +++ b/src/lib/addons/webhook.test.ts @@ -112,6 +112,41 @@ describe('Webhook integration', () => { expect(call.options.body).toBe('feature-created on toggle some-toggle'); }); + test('should allow for eventJson and eventMarkdown in bodyTemplate', async () => { + const addon = new WebhookAddon(ARGS); + const event: IEvent = { + id: 1, + createdAt: new Date('2024-07-24T00:00:00.000Z'), + createdByUserId: SYSTEM_USER_ID, + type: FEATURE_CREATED, + createdBy: 'some@user.com', + featureName: 'some-toggle', + project: 'default', + data: { + name: 'some-toggle', + enabled: false, + strategies: [{ name: 'default' }], + }, + }; + + const parameters = { + url: 'http://test.webhook.com/plain', + bodyTemplate: + '{\n "json": {{{eventJson}}},\n "markdown": "{{eventMarkdown}}"\n}', + contentType: 'text/plain', + }; + + addon.handleEvent(event, parameters, INTEGRATION_ID); + const call = fetchRetryCalls[0]; + expect(fetchRetryCalls.length).toBe(1); + expect(call.url).toBe(parameters.url); + expect(call.options.headers['Content-Type']).toBe('text/plain'); + expect(call.options.body).toMatchSnapshot(); + expect(JSON.parse(JSON.parse(call.options.body).json)).toEqual( + serializeDates(event), + ); + }); + test('Should format event with "authorization"', () => { const addon = new WebhookAddon(ARGS); const event: IEvent = { diff --git a/src/lib/addons/webhook.ts b/src/lib/addons/webhook.ts index 4086bba648..5862a16d34 100644 --- a/src/lib/addons/webhook.ts +++ b/src/lib/addons/webhook.ts @@ -4,6 +4,11 @@ import definition from './webhook-definition'; import type { IEvent } from '../types/events'; import { type IAddonConfig, serializeDates } from '../types'; import type { IntegrationEventState } from '../features/integration-events/integration-events-store'; +import { + type FeatureEventFormatter, + FeatureEventFormatterMd, + LinkStyle, +} from './feature-event-formatter-md'; interface IParameters { url: string; @@ -14,8 +19,14 @@ interface IParameters { } export default class Webhook extends Addon { + private msgFormatter: FeatureEventFormatter; + constructor(args: IAddonConfig) { super(definition, args); + this.msgFormatter = new FeatureEventFormatterMd( + args.unleashUrl, + LinkStyle.MD, + ); } async handleEvent( @@ -37,6 +48,7 @@ export default class Webhook extends Addon { event, // Stringify twice to avoid escaping in Mustache eventJson: JSON.stringify(JSON.stringify(event)), + eventMarkdown: this.msgFormatter.format(event).text, }; let body: string | undefined; diff --git a/yarn.lock b/yarn.lock index 4abdb8a544..60269729ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2085,6 +2085,13 @@ __metadata: languageName: node linkType: hard +"@types/mustache@npm:^4.2.5": + version: 4.2.5 + resolution: "@types/mustache@npm:4.2.5" + checksum: 10c0/624975c39068d47407eadb89628aaff5ef60f3b7a71eef92a254310896a4e90518a01dcf71d95779ab2c986034a6ca5403d22fea237c67ff87f2e2b3fb794ea6 + languageName: node + linkType: hard + "@types/node-fetch@npm:*": version: 2.6.2 resolution: "@types/node-fetch@npm:2.6.2" @@ -9759,6 +9766,7 @@ __metadata: "@types/make-fetch-happen": "npm:10.0.4" "@types/memoizee": "npm:0.4.11" "@types/mime": "npm:3.0.4" + "@types/mustache": "npm:^4.2.5" "@types/node": "npm:20.14.10" "@types/nodemailer": "npm:6.4.15" "@types/owasp-password-strength-test": "npm:1.3.2"