Skip to content

Commit

Permalink
feat: onboard pagerduty destination (#1736)
Browse files Browse the repository at this point in the history
* feat: onboard pagerduty destination

* feat: settings.json file removed

* feat: desttype change to processor

* fix: code review changes
  • Loading branch information
mihir-4116 authored Jan 4, 2023
1 parent 424bce9 commit a947b10
Show file tree
Hide file tree
Showing 10 changed files with 1,286 additions and 14 deletions.
3 changes: 2 additions & 1 deletion src/constants/destinationCanonicalNames.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
34 changes: 34 additions & 0 deletions src/v0/destinations/pagerduty/config.js
Original file line number Diff line number Diff line change
@@ -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
};
74 changes: 74 additions & 0 deletions src/v0/destinations/pagerduty/data/pagerdutyAlertEventConfig.json
Original file line number Diff line number Diff line change
@@ -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
}
]
33 changes: 33 additions & 0 deletions src/v0/destinations/pagerduty/data/pagerdutyChangeEventConfig.json
Original file line number Diff line number Diff line change
@@ -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
}
]
66 changes: 66 additions & 0 deletions src/v0/destinations/pagerduty/transform.js
Original file line number Diff line number Diff line change
@@ -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 };
172 changes: 172 additions & 0 deletions src/v0/destinations/pagerduty/util.js
Original file line number Diff line number Diff line change
@@ -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
};
Loading

0 comments on commit a947b10

Please sign in to comment.