mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +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 { AddonParameterSchema, AddonSchema } from 'openapi';
|
||||||
import type { ChangeEventHandler } from 'react';
|
import type { ChangeEventHandler } from 'react';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
|
import { Markdown } from 'component/common/Markdown/Markdown';
|
||||||
|
|
||||||
const MASKED_VALUE = '*****';
|
const MASKED_VALUE = '*****';
|
||||||
|
|
||||||
@ -62,7 +63,11 @@ export const IntegrationParameterTextField = ({
|
|||||||
error={Boolean(error)}
|
error={Boolean(error)}
|
||||||
onChange={setParameterValue(definition.name)}
|
onChange={setParameterValue(definition.name)}
|
||||||
variant='outlined'
|
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/make-fetch-happen": "10.0.4",
|
||||||
"@types/memoizee": "0.4.11",
|
"@types/memoizee": "0.4.11",
|
||||||
"@types/mime": "3.0.4",
|
"@types/mime": "3.0.4",
|
||||||
|
"@types/mustache": "^4.2.5",
|
||||||
"@types/node": "20.14.10",
|
"@types/node": "20.14.10",
|
||||||
"@types/nodemailer": "6.4.15",
|
"@types/nodemailer": "6.4.15",
|
||||||
"@types/owasp-password-strength-test": "1.3.2",
|
"@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}}}
|
"json": {{{eventJson}}}
|
||||||
}`,
|
}`,
|
||||||
description:
|
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',
|
type: 'textfield',
|
||||||
required: false,
|
required: false,
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
|
@ -112,6 +112,41 @@ describe('Webhook integration', () => {
|
|||||||
expect(call.options.body).toBe('feature-created on toggle some-toggle');
|
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"', () => {
|
test('Should format event with "authorization"', () => {
|
||||||
const addon = new WebhookAddon(ARGS);
|
const addon = new WebhookAddon(ARGS);
|
||||||
const event: IEvent = {
|
const event: IEvent = {
|
||||||
|
@ -4,6 +4,11 @@ import definition from './webhook-definition';
|
|||||||
import type { IEvent } from '../types/events';
|
import type { IEvent } from '../types/events';
|
||||||
import { type IAddonConfig, serializeDates } from '../types';
|
import { type IAddonConfig, serializeDates } from '../types';
|
||||||
import type { IntegrationEventState } from '../features/integration-events/integration-events-store';
|
import type { IntegrationEventState } from '../features/integration-events/integration-events-store';
|
||||||
|
import {
|
||||||
|
type FeatureEventFormatter,
|
||||||
|
FeatureEventFormatterMd,
|
||||||
|
LinkStyle,
|
||||||
|
} from './feature-event-formatter-md';
|
||||||
|
|
||||||
interface IParameters {
|
interface IParameters {
|
||||||
url: string;
|
url: string;
|
||||||
@ -14,8 +19,14 @@ interface IParameters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class Webhook extends Addon {
|
export default class Webhook extends Addon {
|
||||||
|
private msgFormatter: FeatureEventFormatter;
|
||||||
|
|
||||||
constructor(args: IAddonConfig) {
|
constructor(args: IAddonConfig) {
|
||||||
super(definition, args);
|
super(definition, args);
|
||||||
|
this.msgFormatter = new FeatureEventFormatterMd(
|
||||||
|
args.unleashUrl,
|
||||||
|
LinkStyle.MD,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleEvent(
|
async handleEvent(
|
||||||
@ -37,6 +48,7 @@ export default class Webhook extends Addon {
|
|||||||
event,
|
event,
|
||||||
// Stringify twice to avoid escaping in Mustache
|
// Stringify twice to avoid escaping in Mustache
|
||||||
eventJson: JSON.stringify(JSON.stringify(event)),
|
eventJson: JSON.stringify(JSON.stringify(event)),
|
||||||
|
eventMarkdown: this.msgFormatter.format(event).text,
|
||||||
};
|
};
|
||||||
|
|
||||||
let body: string | undefined;
|
let body: string | undefined;
|
||||||
|
@ -2085,6 +2085,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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:*":
|
"@types/node-fetch@npm:*":
|
||||||
version: 2.6.2
|
version: 2.6.2
|
||||||
resolution: "@types/node-fetch@npm: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/make-fetch-happen": "npm:10.0.4"
|
||||||
"@types/memoizee": "npm:0.4.11"
|
"@types/memoizee": "npm:0.4.11"
|
||||||
"@types/mime": "npm:3.0.4"
|
"@types/mime": "npm:3.0.4"
|
||||||
|
"@types/mustache": "npm:^4.2.5"
|
||||||
"@types/node": "npm:20.14.10"
|
"@types/node": "npm:20.14.10"
|
||||||
"@types/nodemailer": "npm:6.4.15"
|
"@types/nodemailer": "npm:6.4.15"
|
||||||
"@types/owasp-password-strength-test": "npm:1.3.2"
|
"@types/owasp-password-strength-test": "npm:1.3.2"
|
||||||
|
Loading…
Reference in New Issue
Block a user