mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Feat: webhook markdown (#7658)
Add ability to format format event as Markdown in generic webhooks, similar to Datadog integration. Closes https://github.com/Unleash/unleash/issues/7646 Co-authored-by: Nuno Góis <github@nunogois.com>
This commit is contained in:
		
							parent
							
								
									245c3e119d
								
							
						
					
					
						commit
						369518cd7d
					
				@ -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 ? (
 | 
			
		||||
                    <Markdown>{definition.description}</Markdown>
 | 
			
		||||
                ) : undefined
 | 
			
		||||
            }
 | 
			
		||||
        />
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -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",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								src/lib/addons/__snapshots__/webhook.test.ts.snap
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/lib/addons/__snapshots__/webhook.test.ts.snap
									
									
									
									
									
										Normal file
									
								
							@ -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)*"
 | 
			
		||||
}"
 | 
			
		||||
`;
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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 = {
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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"
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user