Skip to content

Commit a4bb2cf

Browse files
committed
feat(webhook): enhance webhook payload structure and add new test command
1 parent 676ca6c commit a4bb2cf

9 files changed

Lines changed: 224 additions & 65 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"test:notifier": "jest workers/notifier",
3535
"test:js": "jest workers/javascript",
3636
"test:task-manager": "jest workers/task-manager",
37+
"test:webhook": "jest workers/webhook",
3738
"test:clear": "jest --clearCache",
3839
"run-default": "yarn worker hawk-worker-default",
3940
"run-sentry": "yarn worker hawk-worker-sentry",

workers/webhook/src/deliverer.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import https from 'https';
22
import http from 'http';
33
import { createLogger, format, Logger, transports } from 'winston';
4-
import { WebhookPayload } from '../types/template';
54

65
/**
76
* Timeout for webhook delivery in milliseconds
@@ -41,7 +40,7 @@ export default class WebhookDeliverer {
4140
* @param endpoint - URL to POST to
4241
* @param payload - JSON body to send
4342
*/
44-
public async deliver(endpoint: string, payload: WebhookPayload): Promise<void> {
43+
public async deliver(endpoint: string, payload: Record<string, unknown>): Promise<void> {
4544
const body = JSON.stringify(payload);
4645
const url = new URL(endpoint);
4746
const transport = url.protocol === 'https:' ? https : http;

workers/webhook/src/provider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import NotificationsProvider from 'hawk-worker-sender/src/provider';
22
import { Notification, EventsTemplateVariables } from 'hawk-worker-sender/types/template-variables';
33
import templates from './templates';
4-
import { WebhookPayload } from '../types/template';
4+
import { WebhookTemplate } from '../types/template';
55
import WebhookDeliverer from './deliverer';
66

77
/**
@@ -26,7 +26,7 @@ export default class WebhookProvider extends NotificationsProvider {
2626
* @param notification - notification with payload and type
2727
*/
2828
public async send(to: string, notification: Notification): Promise<void> {
29-
let template: (tplData: EventsTemplateVariables) => WebhookPayload;
29+
let template: WebhookTemplate;
3030

3131
switch (notification.type) {
3232
case 'event': template = templates.EventTpl; break;
Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,30 @@
11
import type { EventsTemplateVariables, TemplateEventData } from 'hawk-worker-sender/types/template-variables';
2-
import { WebhookPayload } from '../../types/template';
32

43
/**
5-
* Builds webhook JSON payload for a single event notification
4+
* Builds webhook JSON payload for a single event notification.
5+
* Mirrors the same data structure other workers receive, serialized as JSON.
66
*
77
* @param tplData - event template data
88
*/
9-
export default function render(tplData: EventsTemplateVariables): WebhookPayload {
9+
export default function render(tplData: EventsTemplateVariables): Record<string, unknown> {
1010
const eventInfo = tplData.events[0] as TemplateEventData;
1111
const event = eventInfo.event;
1212
const eventURL = tplData.host + '/project/' + tplData.project._id + '/event/' + event._id + '/';
1313

14-
let location: string | null = null;
15-
16-
if (event.payload.backtrace && event.payload.backtrace.length > 0 && event.payload.backtrace[0].file) {
17-
location = event.payload.backtrace[0].file;
18-
}
19-
2014
return {
21-
type: 'event',
2215
project: {
2316
id: tplData.project._id.toString(),
2417
name: tplData.project.name,
2518
},
26-
events: [
27-
{
28-
id: event._id.toString(),
29-
title: event.payload.title,
30-
newCount: eventInfo.newCount,
31-
totalCount: event.totalCount,
32-
url: eventURL,
33-
location,
34-
daysRepeated: eventInfo.daysRepeated,
35-
},
36-
],
19+
event: {
20+
id: event._id?.toString() ?? null,
21+
groupHash: event.groupHash,
22+
totalCount: event.totalCount,
23+
newCount: eventInfo.newCount,
24+
daysRepeated: eventInfo.daysRepeated,
25+
url: eventURL,
26+
payload: event.payload,
27+
},
28+
period: tplData.period,
3729
};
3830
}

workers/webhook/src/templates/several-events.ts

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import type { EventsTemplateVariables } from 'hawk-worker-sender/types/template-variables';
2-
import { WebhookPayload } from '../../types/template';
32

43
/**
5-
* Builds webhook JSON payload for a several-events notification
4+
* Builds webhook JSON payload for a several-events notification.
5+
* Mirrors the same data structure other workers receive, serialized as JSON.
66
*
77
* @param tplData - event template data
88
*/
9-
export default function render(tplData: EventsTemplateVariables): WebhookPayload {
9+
export default function render(tplData: EventsTemplateVariables): Record<string, unknown> {
1010
const projectUrl = tplData.host + '/project/' + tplData.project._id;
1111

1212
return {
13-
type: 'several-events',
1413
project: {
1514
id: tplData.project._id.toString(),
1615
name: tplData.project.name,
@@ -19,20 +18,14 @@ export default function render(tplData: EventsTemplateVariables): WebhookPayload
1918
events: tplData.events.map(({ event, newCount, daysRepeated }) => {
2019
const eventURL = tplData.host + '/project/' + tplData.project._id + '/event/' + event._id + '/';
2120

22-
let location: string | null = null;
23-
24-
if (event.payload.backtrace && event.payload.backtrace.length > 0 && event.payload.backtrace[0].file) {
25-
location = event.payload.backtrace[0].file;
26-
}
27-
2821
return {
29-
id: event._id.toString(),
30-
title: event.payload.title,
31-
newCount,
22+
id: event._id?.toString() ?? null,
23+
groupHash: event.groupHash,
3224
totalCount: event.totalCount,
33-
url: eventURL,
34-
location,
25+
newCount,
3526
daysRepeated,
27+
url: eventURL,
28+
payload: event.payload,
3629
};
3730
}),
3831
period: tplData.period,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { EventNotification } from 'hawk-worker-sender/types/template-variables';
2+
import { ObjectId } from 'mongodb';
3+
4+
/**
5+
* Example of new-events notify template variables
6+
*/
7+
export default {
8+
type: 'event',
9+
payload: {
10+
events: [
11+
{
12+
event: {
13+
totalCount: 10,
14+
timestamp: Date.now(),
15+
payload: {
16+
title: 'New event',
17+
backtrace: [ {
18+
file: 'file',
19+
line: 1,
20+
sourceCode: [ {
21+
line: 1,
22+
content: 'code',
23+
} ],
24+
} ],
25+
},
26+
},
27+
daysRepeated: 1,
28+
newCount: 1,
29+
},
30+
],
31+
period: 60,
32+
host: process.env.GARAGE_URL,
33+
hostOfStatic: process.env.API_STATIC_URL,
34+
project: {
35+
_id: new ObjectId('5d206f7f9aaf7c0071d64596'),
36+
token: 'project-token',
37+
name: 'Project',
38+
workspaceId: new ObjectId('5d206f7f9aaf7c0071d64596'),
39+
uidAdded: new ObjectId('5d206f7f9aaf7c0071d64596'),
40+
notifications: [],
41+
},
42+
},
43+
} as EventNotification;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { SeveralEventsNotification } from 'hawk-worker-sender/types/template-variables';
2+
import { GroupedEventDBScheme } from '@hawk.so/types';
3+
import { ObjectId } from 'mongodb';
4+
5+
/**
6+
* Example of several-events notify template variables
7+
*/
8+
export default {
9+
type: 'several-events',
10+
payload: {
11+
events: [
12+
{
13+
event: {
14+
totalCount: 10,
15+
timestamp: Date.now(),
16+
payload: {
17+
title: 'New event',
18+
backtrace: [ {
19+
file: 'file',
20+
line: 1,
21+
sourceCode: [ {
22+
line: 1,
23+
content: 'code',
24+
} ],
25+
} ],
26+
},
27+
} as GroupedEventDBScheme,
28+
daysRepeated: 1,
29+
newCount: 1,
30+
},
31+
{
32+
event: {
33+
totalCount: 5,
34+
payload: {
35+
title: 'New event 2',
36+
timestamp: Date.now(),
37+
backtrace: [ {
38+
file: 'file',
39+
line: 1,
40+
sourceCode: [ {
41+
line: 1,
42+
content: 'code',
43+
} ],
44+
} ],
45+
},
46+
},
47+
daysRepeated: 100,
48+
newCount: 1,
49+
},
50+
],
51+
period: 60,
52+
host: process.env.GARAGE_URL,
53+
hostOfStatic: process.env.API_STATIC_URL,
54+
project: {
55+
_id: new ObjectId('5d206f7f9aaf7c0071d64596'),
56+
token: 'project-token',
57+
name: 'Project',
58+
workspaceId: new ObjectId('5d206f7f9aaf7c0071d64596'),
59+
uidAdded: new ObjectId('5d206f7f9aaf7c0071d64596'),
60+
notifications: [],
61+
},
62+
},
63+
} as SeveralEventsNotification;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import templates from '../src/templates';
2+
import EventNotifyMock from './__mocks__/event-notify';
3+
import SeveralEventsNotifyMock from './__mocks__/several-events-notify';
4+
import WebhookProvider from '../src/provider';
5+
6+
/**
7+
* The sample of a webhook endpoint
8+
*/
9+
const webhookEndpointSample = 'https://example.com/hawk-webhook';
10+
11+
/**
12+
* Mock the 'deliver' method of WebhookDeliverer
13+
*/
14+
const deliver = jest.fn();
15+
16+
/**
17+
* Webhook Deliverer mock
18+
*/
19+
jest.mock('./../src/deliverer.ts', () => {
20+
return jest.fn().mockImplementation(() => {
21+
/**
22+
* Now we can track calls to 'deliver'
23+
*/
24+
return {
25+
deliver: deliver,
26+
};
27+
});
28+
});
29+
30+
/**
31+
* Clear all records of mock calls between tests
32+
*/
33+
afterEach(() => {
34+
jest.clearAllMocks();
35+
});
36+
37+
describe('WebhookProvider', () => {
38+
/**
39+
* Check that the 'send' method works without errors
40+
*/
41+
it('The "send" method should render and deliver message', async () => {
42+
const provider = new WebhookProvider();
43+
44+
await provider.send(webhookEndpointSample, EventNotifyMock);
45+
46+
expect(deliver).toHaveBeenCalledTimes(1);
47+
expect(deliver).toHaveBeenCalledWith(webhookEndpointSample, expect.anything());
48+
});
49+
50+
/**
51+
* Logic for select the template depended on events count
52+
*/
53+
describe('Select correct template', () => {
54+
/**
55+
* If there is a single event in payload, use the 'event' template
56+
*/
57+
it('Select the event template if there is a single event in notify payload', async () => {
58+
const provider = new WebhookProvider();
59+
const EventTpl = jest.spyOn(templates, 'EventTpl');
60+
const SeveralEventsTpl = jest.spyOn(templates, 'SeveralEventsTpl');
61+
62+
await provider.send(webhookEndpointSample, EventNotifyMock);
63+
64+
expect(EventTpl).toHaveBeenCalledTimes(1);
65+
expect(SeveralEventsTpl).toHaveBeenCalledTimes(0);
66+
});
67+
68+
/**
69+
* If there are several events in payload, use the 'several-events' template
70+
*/
71+
it('Select the several-events template if there are several events in notify payload', async () => {
72+
const provider = new WebhookProvider();
73+
const EventTpl = jest.spyOn(templates, 'EventTpl');
74+
const SeveralEventsTpl = jest.spyOn(templates, 'SeveralEventsTpl');
75+
76+
await provider.send(webhookEndpointSample, SeveralEventsNotifyMock);
77+
78+
expect(EventTpl).toHaveBeenCalledTimes(0);
79+
expect(SeveralEventsTpl).toHaveBeenCalledTimes(1);
80+
});
81+
});
82+
});
Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,14 @@
1+
import type { EventsTemplateVariables } from 'hawk-worker-sender/types/template-variables';
2+
13
/**
2-
* Shape of the JSON body sent to webhook endpoints
4+
* Webhook templates should implement this interface.
5+
* Returns a JSON-serializable representation of EventsTemplateVariables.
36
*/
4-
export interface WebhookPayload {
5-
/** Notification type */
6-
type: 'event' | 'several-events';
7-
8-
/** Project info */
9-
project: {
10-
id: string;
11-
name: string;
12-
url?: string;
13-
};
14-
15-
/** List of events in this notification */
16-
events: Array<{
17-
id: string;
18-
title: string;
19-
newCount: number;
20-
totalCount: number;
21-
url: string;
22-
location: string | null;
23-
daysRepeated: number;
24-
}>;
25-
26-
/** Time period in seconds (for several-events) */
27-
period?: number;
7+
export interface WebhookTemplate {
8+
/**
9+
* Rendering method that accepts tpl args and returns a JSON-serializable object
10+
*
11+
* @param tplData - template variables
12+
*/
13+
(tplData: EventsTemplateVariables): Record<string, unknown>;
2814
}

0 commit comments

Comments
 (0)