From a947b10c5d642463d1a03061906520ebbfdc9b05 Mon Sep 17 00:00:00 2001 From: Mihir Bhalala <77438541+mihir-4116@users.noreply.github.com> Date: Wed, 4 Jan 2023 12:14:09 +0530 Subject: [PATCH] feat: onboard pagerduty destination (#1736) * feat: onboard pagerduty destination * feat: settings.json file removed * feat: desttype change to processor * fix: code review changes --- src/constants/destinationCanonicalNames.js | 3 +- src/v0/destinations/pagerduty/config.js | 34 ++ .../data/pagerdutyAlertEventConfig.json | 74 +++ .../data/pagerdutyChangeEventConfig.json | 33 + src/v0/destinations/pagerduty/transform.js | 66 ++ src/v0/destinations/pagerduty/util.js | 172 ++++++ test/__tests__/data/pagerduty.json | 569 ++++++++++++++++++ test/__tests__/data/pagerduty_router.json | 280 +++++++++ test/__tests__/pagerduty.test.js | 46 +- test/__tests__/pagerduty_source.test.js | 23 + 10 files changed, 1286 insertions(+), 14 deletions(-) create mode 100644 src/v0/destinations/pagerduty/config.js create mode 100644 src/v0/destinations/pagerduty/data/pagerdutyAlertEventConfig.json create mode 100644 src/v0/destinations/pagerduty/data/pagerdutyChangeEventConfig.json create mode 100644 src/v0/destinations/pagerduty/transform.js create mode 100644 src/v0/destinations/pagerduty/util.js create mode 100644 test/__tests__/data/pagerduty.json create mode 100644 test/__tests__/data/pagerduty_router.json create mode 100644 test/__tests__/pagerduty_source.test.js diff --git a/src/constants/destinationCanonicalNames.js b/src/constants/destinationCanonicalNames.js index 471e3a64cec..0cc5a018821 100644 --- a/src/constants/destinationCanonicalNames.js +++ b/src/constants/destinationCanonicalNames.js @@ -105,7 +105,8 @@ const DestCanonicalNames = { awin: ["awin", "Awin", "AWIN"], pipedream: ["Pipedream", "PipeDream", "pipedream", "PIPEDREAM"], ga4: ["GA4", "ga4", "Ga4", "Google Analytics 4", "googleAnalytics4"], - awin: ["awin", "Awin", "AWIN"] + awin: ["awin", "Awin", "AWIN"], + pagerduty: ["pagerduty", "PAGERDUTY", "PagerDuty", "Pagerduty", "pagerDuty"] }; module.exports = { DestHandlerMap, DestCanonicalNames }; diff --git a/src/v0/destinations/pagerduty/config.js b/src/v0/destinations/pagerduty/config.js new file mode 100644 index 00000000000..8e030f10ae0 --- /dev/null +++ b/src/v0/destinations/pagerduty/config.js @@ -0,0 +1,34 @@ +const BASE_ENDPOINT = "https://events.pagerduty.com/v2"; + +const { getMappingConfig } = require("../../util"); + +const CONFIG_CATEGORIES = { + ALERT_EVENT: { + name: "pagerdutyAlertEventConfig", + type: "track", + endpoint: `${BASE_ENDPOINT}/enqueue` + }, + CHANGE_EVENT: { + name: "pagerdutyChangeEventConfig", + type: "track", + endpoint: `${BASE_ENDPOINT}/change/enqueue` + } +}; + +const EVENT_ACTIONS = ["trigger", "acknowledge", "resolve"]; +const DEFAULT_EVENT_ACTION = "trigger"; + +const SEVERITIES = ["critical", "warning", "error", "info"]; +const DEFAULT_SEVERITY = "critical"; + +const MAPPING_CONFIG = getMappingConfig(CONFIG_CATEGORIES, __dirname); + +module.exports = { + SEVERITIES, + BASE_ENDPOINT, + EVENT_ACTIONS, + MAPPING_CONFIG, + DEFAULT_SEVERITY, + CONFIG_CATEGORIES, + DEFAULT_EVENT_ACTION +}; diff --git a/src/v0/destinations/pagerduty/data/pagerdutyAlertEventConfig.json b/src/v0/destinations/pagerduty/data/pagerdutyAlertEventConfig.json new file mode 100644 index 00000000000..f4b1a0b8e48 --- /dev/null +++ b/src/v0/destinations/pagerduty/data/pagerdutyAlertEventConfig.json @@ -0,0 +1,74 @@ +[ + { + "destKey": "payload.summary", + "sourceKeys": "event", + "required": true + }, + { + "destKey": "payload.timestamp", + "sourceKeys": "timestamp", + "sourceFromGenericMap": true, + "required": false + }, + { + "destKey": "payload.severity", + "sourceKeys": "properties.severity", + "metadata": { + "defaultValue": "critical" + }, + "required": false + }, + { + "destKey": "payload.source", + "sourceKeys": "properties.source", + "required": true + }, + { + "destKey": "event_action", + "sourceKeys": "properties.action", + "metadata": { + "defaultValue": "trigger" + }, + "required": false + }, + { + "destKey": "links", + "sourceKeys": "properties.linkURLs", + "required": false + }, + { + "destKey": "images", + "sourceKeys": "properties.imageURLs", + "required": false + }, + { + "destKey": "payload.group", + "sourceKeys": "properties.group", + "required": false + }, + { + "destKey": "payload.class", + "sourceKeys": "properties.class", + "required": false + }, + { + "destKey": "payload.component", + "sourceKeys": "properties.component", + "required": false + }, + { + "destKey": "client", + "sourceKeys": "properties.client", + "required": false + }, + { + "destKey": "client_url", + "sourceKeys": "properties.clientUrl", + "required": false + }, + { + "destKey": "payload.custom_details", + "sourceKeys": "properties.customDetails", + "required": false + } +] diff --git a/src/v0/destinations/pagerduty/data/pagerdutyChangeEventConfig.json b/src/v0/destinations/pagerduty/data/pagerdutyChangeEventConfig.json new file mode 100644 index 00000000000..e3ad6dcda28 --- /dev/null +++ b/src/v0/destinations/pagerduty/data/pagerdutyChangeEventConfig.json @@ -0,0 +1,33 @@ +[ + { + "destKey": "payload.summary", + "sourceKeys": "event", + "required": true + }, + { + "destKey": "payload.timestamp", + "sourceKeys": "timestamp", + "sourceFromGenericMap": true, + "required": false + }, + { + "destKey": "payload.source", + "sourceKeys": "properties.source", + "required": false + }, + { + "destKey": "payload.custom_details", + "sourceKeys": "properties.customDetails", + "required": false + }, + { + "destKey": "links", + "sourceKeys": "properties.linkURLs", + "required": false + }, + { + "destKey": "images", + "sourceKeys": "properties.imageURLs", + "required": false + } +] diff --git a/src/v0/destinations/pagerduty/transform.js b/src/v0/destinations/pagerduty/transform.js new file mode 100644 index 00000000000..9b533e217e8 --- /dev/null +++ b/src/v0/destinations/pagerduty/transform.js @@ -0,0 +1,66 @@ +const { EventType } = require("../../../constants"); +const { + defaultRequestConfig, + simpleProcessRouterDest, + defaultPostRequestConfig, + removeUndefinedAndNullValues +} = require("../../util"); +const { + ConfigurationError, + TransformationError, + InstrumentationError +} = require("../../util/errorTypes"); +const { trackEventPayloadBuilder } = require("./util"); + +const responseBuilder = (payload, endpoint) => { + if (payload) { + const response = defaultRequestConfig(); + response.endpoint = endpoint; + response.headers = { + "Content-Type": "application/json" + }; + response.method = defaultPostRequestConfig.requestMethod; + response.body.JSON = removeUndefinedAndNullValues(payload); + return response; + } + // fail-safety for developer error + throw new TransformationError( + "Something went wrong while constructing the payload" + ); +}; + +const trackResponseBuilder = (message, Config) => { + if (!message.event) { + throw new InstrumentationError("Event name is required"); + } + const builder = trackEventPayloadBuilder(message, Config); + const { payload, endpoint } = builder; + return responseBuilder(payload, endpoint); +}; + +const process = event => { + const { message, destination } = event; + const { Config } = destination; + + if (!Config.routingKey) { + throw new ConfigurationError("Routing Key Is Required"); + } + + if (!message.type) { + throw new InstrumentationError("Event type is required"); + } + const messageType = message.type.toLowerCase(); + + if (messageType === EventType.TRACK) { + return trackResponseBuilder(message, Config); + } + + throw new InstrumentationError(`Event type ${messageType} is not supported`); +}; + +const processRouterDest = async (inputs, reqMetadata) => { + const respList = await simpleProcessRouterDest(inputs, process, reqMetadata); + return respList; +}; + +module.exports = { process, processRouterDest }; diff --git a/src/v0/destinations/pagerduty/util.js b/src/v0/destinations/pagerduty/util.js new file mode 100644 index 00000000000..e1476545d68 --- /dev/null +++ b/src/v0/destinations/pagerduty/util.js @@ -0,0 +1,172 @@ +const get = require("get-value"); +const moment = require("moment"); +const { + SEVERITIES, + EVENT_ACTIONS, + MAPPING_CONFIG, + DEFAULT_SEVERITY, + CONFIG_CATEGORIES, + DEFAULT_EVENT_ACTION +} = require("./config"); +const { constructPayload, getIntegrationsObj } = require("../../util"); +const { InstrumentationError } = require("../../util/errorTypes"); + +/** + * Validates the timestamp + * @param {*} payload + * @returns + */ +const validateTimeStamp = payload => { + if (payload.payload?.timestamp) { + const start = moment.unix(moment(payload.payload.timestamp).format("X")); + const current = moment.unix(moment().format("X")); + // calculates past event in hours + const deltaDay = Math.ceil(moment.duration(current.diff(start)).asHours()); + if (deltaDay > 90) { + throw new InstrumentationError( + "Events must be sent within ninety days of their occurrence" + ); + } + } +}; + +/** + * Returns the valid links array + * @param {*} links + * @returns + */ +const getValidLinks = links => { + if (typeof links === "string") { + return [{ href: links }]; + } + // Removing the objects where href key is not present + return links.filter(link => link.href); +}; + +/** + * Returns the valid images array + * @param {*} images + * @returns + */ +const getValidImages = images => { + if (typeof images === "string") { + return [{ src: images }]; + } + // Removing the objects where src key is not present + return images.filter(image => image.src); +}; + +/** + * Returns the valid severity value + * @param {*} payload + * @returns + */ +const getValidSeverity = payload => { + if ( + payload.payload?.severity && + SEVERITIES.includes(payload.payload.severity) + ) { + return payload.payload.severity; + } + // If severity is not found in payload then fallback to default value which is "critical" + return DEFAULT_SEVERITY; +}; + +/** + * Returns the valid eventAction value + * @param {*} message + * @returns + */ +const getValidEventAction = message => { + const eventAction = get(message.properties, "action"); + if (eventAction && EVENT_ACTIONS.includes(eventAction)) { + return eventAction; + } + // If action is not found in payload then fallback to default value which is "trigger" + return DEFAULT_EVENT_ACTION; +}; + +/** + * Returns the alert event payload + * Ref: https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTgx-send-an-alert-event + * @param {*} message + * @param {*} Config + * @returns + */ +const prepareAlertEventPayload = (message, Config) => { + const eventAction = getValidEventAction(message); + + let dedupKey; + if (Config.dedupKeyFieldIdentifier) { + dedupKey = get(message, Config.dedupKeyFieldIdentifier); + } + + if (eventAction === "acknowledge" || eventAction === "resolve") { + // dedup_key is required if you want to acknowledge or resolve an incident + if (!dedupKey) { + throw new InstrumentationError( + `dedup_key required for ${eventAction} events` + ); + } + + return { + dedup_key: dedupKey, + event_action: eventAction + }; + } + + const payload = constructPayload( + message, + MAPPING_CONFIG[CONFIG_CATEGORIES.ALERT_EVENT.name] + ); + + // If dedup_key is not found when incident is triggered then fallback to messageId + payload.dedup_key = dedupKey || message.messageId; + payload.event_action = eventAction; + payload.payload.severity = getValidSeverity(payload); + + return payload; +}; + +/** + * Returns the track call payload + * @param {*} message + * @param {*} Config + * @returns + */ +const trackEventPayloadBuilder = (message, Config) => { + const integrationsObj = getIntegrationsObj(message, "pagerduty"); + + let payload; + let endpoint; + + if ( + integrationsObj && + integrationsObj.type && + integrationsObj.type === "changeEvent" + ) { + payload = constructPayload( + message, + MAPPING_CONFIG[CONFIG_CATEGORIES.CHANGE_EVENT.name] + ); + endpoint = CONFIG_CATEGORIES.CHANGE_EVENT.endpoint; + } else { + payload = prepareAlertEventPayload(message, Config); + endpoint = CONFIG_CATEGORIES.ALERT_EVENT.endpoint; + } + + validateTimeStamp(payload); + if (payload.links) { + payload.links = getValidLinks(payload.links); + } + if (payload.images) { + payload.images = getValidImages(payload.images); + } + const { routingKey } = Config; + payload.routing_key = routingKey; + return { payload, endpoint }; +}; + +module.exports = { + trackEventPayloadBuilder +}; diff --git a/test/__tests__/data/pagerduty.json b/test/__tests__/data/pagerduty.json new file mode 100644 index 00000000000..fdc97ea3175 --- /dev/null +++ b/test/__tests__/data/pagerduty.json @@ -0,0 +1,569 @@ +[ + { + "description": "No Message type", + "input": { + "message": { + "sentAt": "2022-10-11T13:10:54.877+05:30", + "userId": "user@45", + "rudderId": "caae04c5-959f-467b-a293-86f6c62d59e6", + "messageId": "b6ce7f31-5d76-4240-94d2-3eea020ef791", + "timestamp": "2022-10-11T13:10:52.137+05:30", + "receivedAt": "2022-10-11T13:10:52.138+05:30", + "request_ip": "[::1]", + "originalTimestamp": "2022-10-11T13:10:54.877+05:30" + }, + "destination": { + "Config": { + "routingKey": "9552b56325dc490bd0139be85f7b8fac" + } + } + }, + "output": { + "error": "Event type is required" + } + }, + { + "description": "Routing Key is not present", + "input": { + "message": { + "sentAt": "2022-10-11T13:10:54.877+05:30", + "userId": "user@45", + "context": {}, + "rudderId": "caae04c5-959f-467b-a293-86f6c62d59e6", + "messageId": "b6ce7f31-5d76-4240-94d2-3eea020ef791", + "timestamp": "2022-10-11T13:10:52.137+05:30", + "receivedAt": "2022-10-11T13:10:52.138+05:30", + "request_ip": "[::1]", + "originalTimestamp": "2022-10-11T13:10:54.877+05:30" + }, + "destination": { + "Config": {} + } + }, + "output": { + "error": "Routing Key Is Required" + } + }, + { + "description": "Unsupported Event type", + "input": { + "message": { + "type": "alias", + "sentAt": "2022-10-11T13:10:54.877+05:30", + "userId": "user@45", + "context": {}, + "rudderId": "caae04c5-959f-467b-a293-86f6c62d59e6", + "messageId": "b6ce7f31-5d76-4240-94d2-3eea020ef791", + "timestamp": "2022-10-11T13:10:52.137+05:30", + "receivedAt": "2022-10-11T13:10:52.138+05:30", + "request_ip": "[::1]", + "originalTimestamp": "2022-10-11T13:10:54.877+05:30" + }, + "destination": { + "Config": { + "routingKey": "9552b56325dc490bd0139be85f7b8fac" + } + } + }, + "output": { + "error": "Event type alias is not supported" + } + }, + { + "description": "event name is not present", + "input": { + "message": { + "channel": "web", + "type": "track", + "messageId": "9116b734-7e6b-4497-ab51-c16744d4487e", + "userId": "user@45", + "properties": {} + }, + "destination": { + "Config": { + "routingKey": "9552b56325dc490bd0139be85f7b8fac" + } + } + }, + "output": { + "error": "Event name is required" + } + }, + { + "description": "Parameter source is not present", + "input": { + "message": { + "channel": "web", + "type": "track", + "event": "Event name is required", + "messageId": "9116b734-7e6b-4497-ab51-c16744d4487e", + "userId": "user@45", + "properties": { + "dedupKey": "9116b734-7e6b-4497-ab51-c16744d4487e" + } + }, + "destination": { + "Config": { + "routingKey": "9552b56325dc490bd0139be85f7b8fac" + } + } + }, + "output": { + "error": "Missing required value from \"properties.source\"" + } + }, + { + "description": "dedup_key is not present", + "input": { + "message": { + "channel": "web", + "type": "track", + "event": "Event name is required", + "messageId": "9116b734-7e6b-4497-ab51-c16744d4487e", + "userId": "user@45", + "properties": { + "action": "resolve" + } + }, + "destination": { + "Config": { + "routingKey": "9552b56325dc490bd0139be85f7b8fac", + "dedupKeyFieldIdentifier": "properties.dedupKey" + } + } + }, + "output": { + "error": "dedup_key required for resolve events" + } + }, + { + "description": "Timestamp older then 90 days", + "input": { + "message": { + "channel": "web", + "type": "track", + "event": "apiSecret is not present", + "messageId": "9116b734-7e6b-4497-ab51-c16744d4487e", + "userId": "user@45", + "originalTimestamp": "2021-12-20T10:26:33.451Z", + "properties": { + "action": "trigger", + "dedupKey": "9116b734-7e6b-4497-ab51-c16744d4487e", + "severity": "critical", + "component": "ui", + "source": "rudder-webapp", + "group": "destination", + "class": "connection settings", + "customDetails": { + "ping time": "1500ms", + "load avg": 0.75 + }, + "imageURLs": [ + { + "src": "https://static.s4be.cochrane.org/app/uploads/2017/04/shutterstock_531145954.jpg", + "alt": "first image" + }, + { + "src": "https://chart.googleapis.com/chart?chs=600x400&chd=t:6,2,9,5,2,5,7,4,8,2,1&cht=lc&chds=a&chxt=y&chm=D,0033FF,0,0,5,1", + "alt": "second image" + }, + { + "alt": "third image" + } + ], + "linkURLs": [ + { + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error", + "text": "Js Object Error" + }, + { + "href": "https://www.techtarget.com/whatis/definition/stack-overflow#:~:text=A%20stack%20overflow%20is%20a,been%20allocated%20to%20that%20stack", + "text": "Stack Overflow Error" + }, + { + "text": "Destructure Error" + } + ] + } + }, + "destination": { + "Config": { + "routingKey": "9552b56325dc490bd0139be85f7b8fac", + "dedupKeyFieldIdentifier": "properties.dedupKey" + } + } + }, + "output": { + "error": "Events must be sent within ninety days of their occurrence" + } + }, + { + "description": "Trigger event", + "input": { + "message": { + "channel": "web", + "type": "track", + "event": "apiSecret is not present", + "messageId": "9116b734-7e6b-4497-ab51-c16744d4487e", + "userId": "user@45", + "properties": { + "action": "trigger", + "dedupKey": "9116b734-7e6b-4497-ab51-c16744d4487e", + "severity": "critical", + "component": "ui", + "source": "rudder-webapp", + "group": "destination", + "class": "connection settings", + "customDetails": { + "ping time": "1500ms", + "load avg": 0.75 + }, + "imageURLs": [ + { + "src": "https://static.s4be.cochrane.org/app/uploads/2017/04/shutterstock_531145954.jpg", + "alt": "first image" + }, + { + "src": "https://chart.googleapis.com/chart?chs=600x400&chd=t:6,2,9,5,2,5,7,4,8,2,1&cht=lc&chds=a&chxt=y&chm=D,0033FF,0,0,5,1", + "alt": "second image" + }, + { + "alt": "third image" + } + ], + "linkURLs": [ + { + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error", + "text": "Js Object Error" + }, + { + "href": "https://www.techtarget.com/whatis/definition/stack-overflow#:~:text=A%20stack%20overflow%20is%20a,been%20allocated%20to%20that%20stack", + "text": "Stack Overflow Error" + }, + { + "text": "Destructure Error" + } + ] + } + }, + "destination": { + "Config": { + "routingKey": "9552b56325dc490bd0139be85f7b8fac", + "dedupKeyFieldIdentifier": "properties.dedupKey" + } + } + }, + "output": { + "body": { + "XML": {}, + "FORM": {}, + "JSON": { + "links": [ + { + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error", + "text": "Js Object Error" + }, + { + "href": "https://www.techtarget.com/whatis/definition/stack-overflow#:~:text=A%20stack%20overflow%20is%20a,been%20allocated%20to%20that%20stack", + "text": "Stack Overflow Error" + } + ], + "images": [ + { + "alt": "first image", + "src": "https://static.s4be.cochrane.org/app/uploads/2017/04/shutterstock_531145954.jpg" + }, + { + "alt": "second image", + "src": "https://chart.googleapis.com/chart?chs=600x400&chd=t:6,2,9,5,2,5,7,4,8,2,1&cht=lc&chds=a&chxt=y&chm=D,0033FF,0,0,5,1" + } + ], + "payload": { + "class": "connection settings", + "group": "destination", + "source": "rudder-webapp", + "summary": "apiSecret is not present", + "severity": "critical", + "component": "ui", + "custom_details": { + "ping time": "1500ms", + "load avg": 0.75 + } + }, + "dedup_key": "9116b734-7e6b-4497-ab51-c16744d4487e", + "routing_key": "9552b56325dc490bd0139be85f7b8fac", + "event_action": "trigger" + }, + "JSON_ARRAY": {} + }, + "type": "REST", + "files": {}, + "method": "POST", + "params": {}, + "headers": { + "Content-Type": "application/json" + }, + "version": "1", + "endpoint": "https://events.pagerduty.com/v2/enqueue" + } + }, + { + "description": "Acknowledge event", + "input": { + "message": { + "channel": "web", + "type": "track", + "event": "apiSecret is not present", + "messageId": "9116b734-7e6b-4497-ab51-c16744d4487e", + "userId": "user@45", + "properties": { + "action": "acknowledge", + "dedupKey": "9116b734-7e6b-4497-ab51-c16744d4487e", + "severity": "critical", + "component": "ui", + "source": "rudder-webapp", + "group": "destination", + "class": "connection settings", + "customDetails": { + "ping time": "1500ms", + "load avg": 0.75 + }, + "imageURLs": [ + { + "src": "https://static.s4be.cochrane.org/app/uploads/2017/04/shutterstock_531145954.jpg", + "alt": "first image" + }, + { + "src": "https://chart.googleapis.com/chart?chs=600x400&chd=t:6,2,9,5,2,5,7,4,8,2,1&cht=lc&chds=a&chxt=y&chm=D,0033FF,0,0,5,1", + "alt": "second image" + }, + { + "alt": "third image" + } + ], + "linkURLs": [ + { + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error", + "text": "Js Object Error" + }, + { + "href": "https://www.techtarget.com/whatis/definition/stack-overflow#:~:text=A%20stack%20overflow%20is%20a,been%20allocated%20to%20that%20stack", + "text": "Stack Overflow Error" + }, + { + "text": "Destructure Error" + } + ] + } + }, + "destination": { + "Config": { + "routingKey": "9552b56325dc490bd0139be85f7b8fac", + "dedupKeyFieldIdentifier": "properties.dedupKey" + } + } + }, + "output": { + "body": { + "XML": {}, + "FORM": {}, + "JSON": { + "dedup_key": "9116b734-7e6b-4497-ab51-c16744d4487e", + "routing_key": "9552b56325dc490bd0139be85f7b8fac", + "event_action": "acknowledge" + }, + "JSON_ARRAY": {} + }, + "type": "REST", + "files": {}, + "method": "POST", + "params": {}, + "headers": { + "Content-Type": "application/json" + }, + "version": "1", + "endpoint": "https://events.pagerduty.com/v2/enqueue" + } + }, + { + "description": "Resolve event", + "input": { + "message": { + "channel": "web", + "type": "track", + "event": "apiSecret is not present", + "messageId": "9116b734-7e6b-4497-ab51-c16744d4487e", + "userId": "user@45", + "properties": { + "action": "resolve", + "dedupKey": "9116b734-7e6b-4497-ab51-c16744d4487e", + "severity": "critical", + "component": "ui", + "source": "rudder-webapp", + "group": "destination", + "class": "connection settings", + "customDetails": { + "ping time": "1500ms", + "load avg": 0.75 + }, + "imageURLs": [ + { + "src": "https://static.s4be.cochrane.org/app/uploads/2017/04/shutterstock_531145954.jpg", + "alt": "first image" + }, + { + "src": "https://chart.googleapis.com/chart?chs=600x400&chd=t:6,2,9,5,2,5,7,4,8,2,1&cht=lc&chds=a&chxt=y&chm=D,0033FF,0,0,5,1", + "alt": "second image" + }, + { + "alt": "third image" + } + ], + "linkURLs": [ + { + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error", + "text": "Js Object Error" + }, + { + "href": "https://www.techtarget.com/whatis/definition/stack-overflow#:~:text=A%20stack%20overflow%20is%20a,been%20allocated%20to%20that%20stack", + "text": "Stack Overflow Error" + }, + { + "text": "Destructure Error" + } + ] + } + }, + "destination": { + "Config": { + "routingKey": "9552b56325dc490bd0139be85f7b8fac", + "dedupKeyFieldIdentifier": "properties.dedupKey" + } + } + }, + "output": { + "body": { + "XML": {}, + "FORM": {}, + "JSON": { + "dedup_key": "9116b734-7e6b-4497-ab51-c16744d4487e", + "routing_key": "9552b56325dc490bd0139be85f7b8fac", + "event_action": "resolve" + }, + "JSON_ARRAY": {} + }, + "type": "REST", + "files": {}, + "method": "POST", + "params": {}, + "headers": { + "Content-Type": "application/json" + }, + "version": "1", + "endpoint": "https://events.pagerduty.com/v2/enqueue" + } + }, + { + "description": "Change event", + "input": { + "message": { + "channel": "web", + "type": "track", + "event": "Github CI/CD Triggered", + "messageId": "9116b734-7e6b-4497-ab51-c16744d4487e", + "userId": "user@45", + "properties": { + "source": "rudder-webapp", + "customDetails": { + "ping time": "1500ms", + "load avg": 0.75 + }, + "imageURLs": [ + { + "src": "https://static.s4be.cochrane.org/app/uploads/2017/04/shutterstock_531145954.jpg", + "alt": "first image" + }, + { + "src": "https://chart.googleapis.com/chart?chs=600x400&chd=t:6,2,9,5,2,5,7,4,8,2,1&cht=lc&chds=a&chxt=y&chm=D,0033FF,0,0,5,1", + "alt": "second image" + }, + { + "alt": "third image" + } + ], + "linkURLs": [ + { + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error", + "text": "Js Object Error" + }, + { + "href": "https://www.techtarget.com/whatis/definition/stack-overflow#:~:text=A%20stack%20overflow%20is%20a,been%20allocated%20to%20that%20stack", + "text": "Stack Overflow Error" + }, + { + "text": "Destructure Error" + } + ] + }, + "integrations": { + "pagerduty": { + "type": "changeEvent" + } + } + }, + "destination": { + "Config": { + "routingKey": "9552b56325dc490bd0139be85f7b8fac", + "dedupKeyFieldIdentifier": "properties.dedupKey" + } + } + }, + "output": { + "body": { + "XML": {}, + "FORM": {}, + "JSON": { + "links": [ + { + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error", + "text": "Js Object Error" + }, + { + "href": "https://www.techtarget.com/whatis/definition/stack-overflow#:~:text=A%20stack%20overflow%20is%20a,been%20allocated%20to%20that%20stack", + "text": "Stack Overflow Error" + } + ], + "images": [ + { + "alt": "first image", + "src": "https://static.s4be.cochrane.org/app/uploads/2017/04/shutterstock_531145954.jpg" + }, + { + "alt": "second image", + "src": "https://chart.googleapis.com/chart?chs=600x400&chd=t:6,2,9,5,2,5,7,4,8,2,1&cht=lc&chds=a&chxt=y&chm=D,0033FF,0,0,5,1" + } + ], + "payload": { + "source": "rudder-webapp", + "summary": "Github CI/CD Triggered", + "custom_details": { + "load avg": 0.75, + "ping time": "1500ms" + } + }, + "routing_key": "9552b56325dc490bd0139be85f7b8fac" + }, + "JSON_ARRAY": {} + }, + "type": "REST", + "files": {}, + "method": "POST", + "params": {}, + "headers": { + "Content-Type": "application/json" + }, + "version": "1", + "endpoint": "https://events.pagerduty.com/v2/change/enqueue" + } + } +] diff --git a/test/__tests__/data/pagerduty_router.json b/test/__tests__/data/pagerduty_router.json new file mode 100644 index 00000000000..44e57efa8b9 --- /dev/null +++ b/test/__tests__/data/pagerduty_router.json @@ -0,0 +1,280 @@ +[ + { + "description": "Router Test Case", + "input": [ + { + "message": { + "channel": "web", + "type": "track", + "event": "Github CI/CD Triggered", + "messageId": "9116b734-7e6b-4497-ab51-c16744d4487e", + "userId": "user@45", + "properties": { + "source": "rudder-webapp", + "customDetails": { + "ping time": "1500ms", + "load avg": 0.75 + }, + "imageURLs": [ + { + "src": "https://static.s4be.cochrane.org/app/uploads/2017/04/shutterstock_531145954.jpg", + "alt": "first image" + }, + { + "src": "https://chart.googleapis.com/chart?chs=600x400&chd=t:6,2,9,5,2,5,7,4,8,2,1&cht=lc&chds=a&chxt=y&chm=D,0033FF,0,0,5,1", + "alt": "second image" + }, + { + "alt": "third image" + } + ], + "linkURLs": [ + { + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error", + "text": "Js Object Error" + }, + { + "href": "https://www.techtarget.com/whatis/definition/stack-overflow#:~:text=A%20stack%20overflow%20is%20a,been%20allocated%20to%20that%20stack", + "text": "Stack Overflow Error" + }, + { + "text": "Destructure Error" + } + ] + }, + "integrations": { + "pagerduty": { + "type": "changeEvent" + } + } + }, + "metadata": { + "jobId": 1 + }, + "destination": { + "Config": { + "routingKey": "9552b56325dc490bd0139be85f7b8fac", + "dedupKeyFieldIdentifier": "properties.dedupKey" + } + } + }, + { + "message": { + "channel": "web", + "type": "track", + "event": "apiSecret is not present", + "messageId": "9116b734-7e6b-4497-ab51-c16744d4487e", + "userId": "user@45", + "properties": { + "action": "acknowledge", + "dedupKey": "9116b734-7e6b-4497-ab51-c16744d4487e", + "severity": "critical", + "component": "ui", + "source": "rudder-webapp", + "group": "destination", + "class": "connection settings", + "customDetails": { + "ping time": "1500ms", + "load avg": 0.75 + }, + "imageURLs": [ + { + "src": "https://static.s4be.cochrane.org/app/uploads/2017/04/shutterstock_531145954.jpg", + "alt": "first image" + }, + { + "src": "https://chart.googleapis.com/chart?chs=600x400&chd=t:6,2,9,5,2,5,7,4,8,2,1&cht=lc&chds=a&chxt=y&chm=D,0033FF,0,0,5,1", + "alt": "second image" + }, + { + "alt": "third image" + } + ], + "linkURLs": [ + { + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error", + "text": "Js Object Error" + }, + { + "href": "https://www.techtarget.com/whatis/definition/stack-overflow#:~:text=A%20stack%20overflow%20is%20a,been%20allocated%20to%20that%20stack", + "text": "Stack Overflow Error" + }, + { + "text": "Destructure Error" + } + ] + } + }, + "metadata": { + "jobId": 2 + }, + "destination": { + "Config": { + "routingKey": "9552b56325dc490bd0139be85f7b8fac", + "dedupKeyFieldIdentifier": "properties.dedupKey" + } + } + }, + { + "message": { + "channel": "web", + "type": "track", + "event": "apiSecret is not present", + "messageId": "9116b734-7e6b-4497-ab51-c16744d4487e", + "userId": "user@45", + "originalTimestamp": "2021-12-20T10:26:33.451Z", + "properties": { + "action": "trigger", + "dedupKey": "9116b734-7e6b-4497-ab51-c16744d4487e", + "severity": "critical", + "component": "ui", + "source": "rudder-webapp", + "group": "destination", + "class": "connection settings", + "customDetails": { + "ping time": "1500ms", + "load avg": 0.75 + }, + "imageURLs": [ + { + "src": "https://static.s4be.cochrane.org/app/uploads/2017/04/shutterstock_531145954.jpg", + "alt": "first image" + }, + { + "src": "https://chart.googleapis.com/chart?chs=600x400&chd=t:6,2,9,5,2,5,7,4,8,2,1&cht=lc&chds=a&chxt=y&chm=D,0033FF,0,0,5,1", + "alt": "second image" + }, + { + "alt": "third image" + } + ], + "linkURLs": [ + { + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error", + "text": "Js Object Error" + }, + { + "href": "https://www.techtarget.com/whatis/definition/stack-overflow#:~:text=A%20stack%20overflow%20is%20a,been%20allocated%20to%20that%20stack", + "text": "Stack Overflow Error" + }, + { + "text": "Destructure Error" + } + ] + } + }, + "metadata": { + "jobId": 3 + }, + "destination": { + "Config": { + "routingKey": "9552b56325dc490bd0139be85f7b8fac", + "dedupKeyFieldIdentifier": "properties.dedupKey" + } + } + } + ], + "output": [ + { + "batched": false, + "batchedRequest": { + "body": { + "XML": {}, + "FORM": {}, + "JSON": { + "links": [ + { + "href": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error", + "text": "Js Object Error" + }, + { + "href": "https://www.techtarget.com/whatis/definition/stack-overflow#:~:text=A%20stack%20overflow%20is%20a,been%20allocated%20to%20that%20stack", + "text": "Stack Overflow Error" + } + ], + "images": [ + { + "alt": "first image", + "src": "https://static.s4be.cochrane.org/app/uploads/2017/04/shutterstock_531145954.jpg" + }, + { + "alt": "second image", + "src": "https://chart.googleapis.com/chart?chs=600x400&chd=t:6,2,9,5,2,5,7,4,8,2,1&cht=lc&chds=a&chxt=y&chm=D,0033FF,0,0,5,1" + } + ], + "payload": { + "source": "rudder-webapp", + "summary": "Github CI/CD Triggered", + "custom_details": { + "load avg": 0.75, + "ping time": "1500ms" + } + }, + "routing_key": "9552b56325dc490bd0139be85f7b8fac" + }, + "JSON_ARRAY": {} + }, + "type": "REST", + "files": {}, + "method": "POST", + "params": {}, + "headers": { + "Content-Type": "application/json" + }, + "version": "1", + "endpoint": "https://events.pagerduty.com/v2/change/enqueue" + }, + "destination": { + "Config": { + "routingKey": "9552b56325dc490bd0139be85f7b8fac", + "dedupKeyFieldIdentifier": "properties.dedupKey" + } + }, + "metadata": [{ "jobId": 1 }], + "statusCode": 200 + }, + { + "batched": false, + "batchedRequest": { + "body": { + "XML": {}, + "FORM": {}, + "JSON": { + "dedup_key": "9116b734-7e6b-4497-ab51-c16744d4487e", + "routing_key": "9552b56325dc490bd0139be85f7b8fac", + "event_action": "acknowledge" + }, + "JSON_ARRAY": {} + }, + "type": "REST", + "files": {}, + "method": "POST", + "params": {}, + "headers": { + "Content-Type": "application/json" + }, + "version": "1", + "endpoint": "https://events.pagerduty.com/v2/enqueue" + }, + "destination": { + "Config": { + "routingKey": "9552b56325dc490bd0139be85f7b8fac", + "dedupKeyFieldIdentifier": "properties.dedupKey" + } + }, + "metadata": [{ "jobId": 2 }], + "statusCode": 200 + }, + { + "batched": false, + "error": "Events must be sent within ninety days of their occurrence", + "metadata": [{ "jobId": 3 }], + "statusCode": 400, + "statTags": { + "errorCategory": "dataValidation", + "errorType": "instrumentation" + } + } + ] + } +] diff --git a/test/__tests__/pagerduty.test.js b/test/__tests__/pagerduty.test.js index ddfb69aba63..b5bd4cf4b3f 100644 --- a/test/__tests__/pagerduty.test.js +++ b/test/__tests__/pagerduty.test.js @@ -1,23 +1,43 @@ -const integration = "pagerduty"; - const fs = require("fs"); const path = require("path"); -const transformer = require(`../../src/v0/sources/${integration}/transform`); +const integration = "pagerduty"; +const name = "PagerDuty"; +const version = "v0"; + +const transformer = require(`../../src/${version}/destinations/${integration}/transform`); const testDataFile = fs.readFileSync( - path.resolve(__dirname, `./data/${integration}_source.json`) + path.resolve(__dirname, `./data/${integration}.json`) ); - const testData = JSON.parse(testDataFile); -testData.forEach((data, index) => { - it(`${index}. ${integration} - ${data.description}`, () => { - try { - const output = transformer.process(data.input); - expect(output).toEqual(data.output); - } catch (error) { - expect(error.message).toEqual(data.output.message); - } +// Router Test files +const routerTestDataFile = fs.readFileSync( + path.resolve(__dirname, `./data/${integration}_router.json`) +); +const routerTestData = JSON.parse(routerTestDataFile); + +describe(`${name} Tests`, () => { + describe("Processor", () => { + testData.forEach((dataPoint, index) => { + it(`${index}. ${integration} - ${dataPoint.description}`, () => { + try { + const output = transformer.process(dataPoint.input); + expect(output).toEqual(dataPoint.output); + } catch (error) { + expect(error.message).toEqual(dataPoint.output.error); + } + }); + }); + }); + + describe("Router Tests", () => { + routerTestData.forEach(dataPoint => { + it("Payload", async () => { + const output = await transformer.processRouterDest(dataPoint.input); + expect(output).toEqual(dataPoint.output); + }); + }); }); }); diff --git a/test/__tests__/pagerduty_source.test.js b/test/__tests__/pagerduty_source.test.js new file mode 100644 index 00000000000..ddfb69aba63 --- /dev/null +++ b/test/__tests__/pagerduty_source.test.js @@ -0,0 +1,23 @@ +const integration = "pagerduty"; + +const fs = require("fs"); +const path = require("path"); + +const transformer = require(`../../src/v0/sources/${integration}/transform`); + +const testDataFile = fs.readFileSync( + path.resolve(__dirname, `./data/${integration}_source.json`) +); + +const testData = JSON.parse(testDataFile); + +testData.forEach((data, index) => { + it(`${index}. ${integration} - ${data.description}`, () => { + try { + const output = transformer.process(data.input); + expect(output).toEqual(data.output); + } catch (error) { + expect(error.message).toEqual(data.output.message); + } + }); +});