Skip to content
This repository has been archived by the owner on Nov 16, 2023. It is now read-only.

Commit

Permalink
KA-559 Send webhook data to publisher
Browse files Browse the repository at this point in the history
  • Loading branch information
Matus Backor authored and Matus Backor committed Jul 31, 2019
1 parent 98a19d2 commit 289e19b
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 154 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ obj
node_modules
dist

.idea/*

local.settings.json
4 changes: 4 additions & 0 deletions enums/Operation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum Operation {
Upsert = 'upsert',
ChangeWorkflowStep = 'change_workflow_step',
}
18 changes: 18 additions & 0 deletions kcd-webhook-service/Configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { HttpRequest } from '@azure/functions/Interfaces';
import { Operation } from '../enums/Operation';

export class Configuration {
public static eventGridKey: string;
public static eventGridHost: string;

public static set(request: HttpRequest): void {
const isWorkflowStepChange = request.body.message.operation === Operation.ChangeWorkflowStep;

this.eventGridKey = (isWorkflowStepChange
? process.env['EventGrid.WorkflowChanged.Key']
: process.env['EventGrid.DocsChanged.Key']) || '';
this.eventGridHost = (isWorkflowStepChange
? process.env['EventGrid.WorkflowChanged.Endpoint']
: process.env['EventGrid.DocsChanged.Endpoint']) || '';
}
}
105 changes: 63 additions & 42 deletions kcd-webhook-service/eventComposer.test.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,75 @@
import { eventComposer } from './eventComposer';

const webhookBody = {
data: {
xxx: 'xxx',
yyy: 'yyy'
},
message: {
operation: 'someOperation',
type: ''
}
const body = {
data: {
xxx: 'xxx',
yyy: 'yyy',
},
message: {
operation: 'someOperation',
type: '',
},
};
const eventType = 'kentico-cloud';

describe('eventComposer', () => {
test('composes event with data from webhook', async () => {
const isTest = undefined;
const event = eventComposer(webhookBody, isTest);
test('composes event with data from webhook', async () => {
const isTest = undefined;
const request = {
body,
query: {
source: eventType,
test: isTest,
},
} as any;
const event = eventComposer(request);

expect(event.id).toBeTruthy();
expect(event.subject).toBe(webhookBody.message.operation);
expect(event.eventType).toBe(eventType);
expect(event.dataVersion).toBe('1.0');
expect(event.data.webhook).toBe(webhookBody.data);
expect(event.data.test).toBe('disabled');
expect(event.eventTime).toBeTruthy();
});
expect(event.id).toBeTruthy();
expect(event.subject).toBe(body.message.operation);
expect(event.eventType).toBe(eventType);
expect(event.dataVersion).toBe('1.0');
expect(event.data.webhook).toBe(body.data);
expect(event.data.test).toBe('disabled');
expect(event.eventTime).toBeTruthy();
});

test('composes event with testing configuration', async () => {
const isTest = 'enabled';
const event = eventComposer(webhookBody, isTest);
test('composes event with testing configuration', async () => {
const isTest = 'enabled';
const request = {
body,
query: {
source: eventType,
test: isTest,
},
} as any;
const event = eventComposer(request);

expect(event.id).toBeTruthy();
expect(event.subject).toBe(webhookBody.message.operation);
expect(event.eventType).toBe(eventType);
expect(event.dataVersion).toBe('1.0');
expect(event.data.webhook).toBe(webhookBody.data);
expect(event.data.test).toBe('enabled');
expect(event.eventTime).toBeTruthy();
});
expect(event.id).toBeTruthy();
expect(event.subject).toBe(body.message.operation);
expect(event.eventType).toBe(eventType);
expect(event.dataVersion).toBe('1.0');
expect(event.data.webhook).toBe(body.data);
expect(event.data.test).toBe('enabled');
expect(event.eventTime).toBeTruthy();
});

test('composes event with incorrect testing configuration', async () => {
const isTest = 'something';
const event = eventComposer(webhookBody, isTest);
test('composes event with incorrect testing configuration', async () => {
const isTest = 'something';
const request = {
body,
query: {
source: eventType,
test: isTest,
},
} as any;
const event = eventComposer(request);

expect(event.id).toBeTruthy();
expect(event.subject).toBe(webhookBody.message.operation);
expect(event.eventType).toBe(eventType);
expect(event.dataVersion).toBe('1.0');
expect(event.data.webhook).toBe(webhookBody.data);
expect(event.data.test).toBe('disabled');
expect(event.eventTime).toBeTruthy();
});
expect(event.id).toBeTruthy();
expect(event.subject).toBe(body.message.operation);
expect(event.eventType).toBe(eventType);
expect(event.data.webhook).toBe(body.data);
expect(event.dataVersion).toBe('1.0');
expect(event.data.test).toBe('disabled');
expect(event.eventTime).toBeTruthy();
});
});
19 changes: 6 additions & 13 deletions kcd-webhook-service/eventComposer.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
import { HttpRequest } from '@azure/functions/Interfaces';
import { EventGridModels } from 'azure-eventgrid';
import getUuid from 'uuid';
import { RequestBody } from '../@types/global';

export const eventComposer = (
webhookBody: RequestBody,
test?: string
): EventGridModels.EventGridEvent => {
const isTest = test === 'enabled' ? 'enabled' : 'disabled';

return {
export const eventComposer = ({body, query: {test}}: HttpRequest): EventGridModels.EventGridEvent => ({
data: {
test: isTest,
webhook: webhookBody.data
test: test === 'enabled' ? test : 'disabled',
webhook: body.data,
},
dataVersion: '1.0',
eventTime: new Date(),
eventType: 'kentico-cloud',
id: getUuid(),
subject: webhookBody.message.operation
};
};
subject: body.message.operation,
});
31 changes: 31 additions & 0 deletions kcd-webhook-service/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import EventGridClient from 'azure-eventgrid';
import { TopicCredentials } from 'ms-rest-azure';
import {
HttpRequest,
RequestBody,
} from '../@types/global';
import { Operation } from '../enums/Operation';
import { Configuration } from './Configuration';
import { eventComposer } from './eventComposer';
import { publishEventsCreator } from './publishEventsCreator';

export const isRequestBodyValid = (body: any): body is RequestBody =>
body
&& body.message
&& body.message.type
&& body.message.operation
&& body.data;

export const shouldIgnoreRequest = ({ message: { type, operation } }: RequestBody): boolean =>
type === 'content_item' && operation !== Operation.Upsert;

export const publishEventWithWebhookData = async (request: HttpRequest) => {
const topicCredentials = new TopicCredentials(Configuration.eventGridKey);
const eventGridClient = new EventGridClient(topicCredentials);
const publishEvents = publishEventsCreator({eventGridClient, host: Configuration.eventGridHost});

const event = eventComposer(request);
await publishEvents([event]);

return event;
};
48 changes: 24 additions & 24 deletions kcd-webhook-service/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
import azureFunction from './index';

describe('Azure function fails', () => {
let context = {};
let context = {};

beforeEach(() => {
context = {
done: jest.fn(),
res: null
};
});
beforeEach(() => {
context = {
done: jest.fn(),
res: null,
};
});

test('returns 200 but does nothing on kentico-cloud and content_item', async () => {
const request = {
body: {
data: 'anything',
message: {
operation: 'anything',
type: 'content_item'
}
},
query: {
source: 'kentico-cloud'
}
};
test('returns 200 but does nothing on kentico-cloud and content_item', async () => {
const request = {
body: {
data: 'anything',
message: {
operation: 'anything',
type: 'content_item',
},
},
query: {
source: 'kentico-cloud',
},
};

const response = await azureFunction(context as any, request, null);
const response = await azureFunction(context as any, request, null);

expect(response.status).toBe(200);
expect(response.body).toBe('Nothing published');
});
expect(response.status).toBe(200);
expect(response.body).toBe('Nothing published');
});
});
92 changes: 39 additions & 53 deletions kcd-webhook-service/index.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,43 @@
import { AzureFunction, Context } from '@azure/functions';
import EventGridClient from 'azure-eventgrid';
import { TopicCredentials } from 'ms-rest-azure';
import { HttpRequest, HttpResponse, RequestBody } from '../@types/global';
import { eventComposer } from './eventComposer';
import { publishEventsCreator } from './publishEventsCreator';

const isRequestBodyValid = (body: any): body is RequestBody =>
body
&& body.message
&& body.message.type
&& body.message.operation
&& body.data;

const shouldIgnoreRequest = ({ message: { type, operation } }: RequestBody): boolean =>
type === 'content_item' && operation !== 'upsert';

const parseWebhook: AzureFunction = async (
_context: Context,
request: HttpRequest
): Promise<HttpResponse> => {
try {
if (!isRequestBodyValid(request.body)) {
throw new Error('Received invalid message body');
}

if (shouldIgnoreRequest(request.body)) {
return {
body: 'Nothing published',
status: 200
};
import {
AzureFunction,
Context,
} from '@azure/functions';
import {
HttpRequest,
HttpResponse,
} from '../@types/global';
import { Configuration } from './Configuration';
import {
isRequestBodyValid,
publishEventWithWebhookData,
shouldIgnoreRequest,
} from './helpers';

const parseWebhook: AzureFunction = async (context: Context, request: HttpRequest): Promise<HttpResponse> => {
try {
if (!isRequestBodyValid(request.body)) {
throw new Error('Received invalid message body');
}

if (shouldIgnoreRequest(request.body)) {
return {
body: 'Nothing published',
status: 200,
};
}

Configuration.set(request);

const event = await publishEventWithWebhookData(request);

return {
body: event,
status: 200,
};
} catch (error) {
/** This try-catch is required for correct logging of exceptions in Azure */
throw `Message: ${error.message} \nStack Trace: ${error.stack}`;
}

const eventGridKey = process.env['EventGrid.DocsChanged.Key'];
const host = process.env['EventGrid.DocsChanged.Endpoint'];
if (!eventGridKey || !host) {
throw new Error('Undefined env property provided');
}

const topicCredentials = new TopicCredentials(eventGridKey);
const eventGridClient = new EventGridClient(topicCredentials);
const publishEvents = publishEventsCreator({ eventGridClient, host });

const event = eventComposer(request.body, request.query.test);
await publishEvents([event]);

return {
body: event,
status: 200
};
} catch (error) {
/** This try-catch is required for correct logging of exceptions in Azure */
throw `Message: ${error.message} \nStack Trace: ${error.stack}`;
}
};

export default parseWebhook;
Loading

0 comments on commit 289e19b

Please sign in to comment.