diff --git a/packages/nodes-base/credentials/PagerDutyApi.credentials.ts b/packages/nodes-base/credentials/PagerDutyApi.credentials.ts new file mode 100644 index 0000000000..3acac2416a --- /dev/null +++ b/packages/nodes-base/credentials/PagerDutyApi.credentials.ts @@ -0,0 +1,17 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class PagerDutyApi implements ICredentialType { + name = 'pagerDutyApi'; + displayName = 'PagerDuty API'; + properties = [ + { + displayName: 'API Token', + name: 'apiToken', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts b/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts new file mode 100644 index 0000000000..1e6e20bddd --- /dev/null +++ b/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts @@ -0,0 +1,94 @@ +import { + OptionsWithUri, + } from 'request'; + +import { + IExecuteFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, + IHookFunctions, + IWebhookFunctions, +} from 'n8n-workflow'; + +import { + snakeCase, +} from 'change-case'; + +export async function pagerDutyApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const credentials = this.getCredentials('pagerDutyApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const options: OptionsWithUri = { + headers: { + Accept: 'application/vnd.pagerduty+json;version=2', + Authorization: `Token token=${credentials.apiToken}`, + }, + method, + body, + qs: query, + uri: uri || `https://api.pagerduty.com${resource}`, + json: true, + qsStringifyOptions: { + arrayFormat: 'brackets', + }, + }; + if (!Object.keys(body).length) { + delete options.form; + } + if (!Object.keys(query).length) { + delete options.qs; + } + options.headers = Object.assign({}, options.headers, headers); + try { + return await this.helpers.request!(options); + } catch (error) { + if (error.response && error.response.body && error.response.body.error && error.response.body.error.errors) { + // Try to return the error prettier + //@ts-ignore + throw new Error(`PagerDuty error response [${error.statusCode}]: ${error.response.body.error.errors.join(' | ')}`); + } + throw error; + } +} +export async function pagerDutyApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + let uri; + query.limit = 100; + query.offset = 0; + + do { + responseData = await pagerDutyApiRequest.call(this, method, endpoint, body, query, uri); + query.offset++; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData.more + ); + + return returnData; +} + +export function keysToSnakeCase(elements: IDataObject[] | IDataObject) : IDataObject[] { + if (!Array.isArray(elements)) { + elements = [elements]; + } + for (const element of elements) { + for (const key of Object.keys(element)) { + if (key !== snakeCase(key)) { + element[snakeCase(key)] = element[key]; + delete element[key]; + } + } + } + return elements; +} + diff --git a/packages/nodes-base/nodes/PagerDuty/IncidentDescription.ts b/packages/nodes-base/nodes/PagerDuty/IncidentDescription.ts new file mode 100644 index 0000000000..5971bc5e89 --- /dev/null +++ b/packages/nodes-base/nodes/PagerDuty/IncidentDescription.ts @@ -0,0 +1,642 @@ +import { + INodeProperties, + } from 'n8n-workflow'; + +export const incidentOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'incident', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create an incident', + }, + { + name: 'Get', + value: 'get', + description: 'Get an incident', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all incidents', + }, + { + name: 'Update', + value: 'update', + description: 'Update an incident', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const incidentFields = [ + +/* -------------------------------------------------------------------------- */ +/* incident:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Title', + name: 'title', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'incident', + ], + operation: [ + 'create', + ], + }, + }, + description: 'A succinct description of the nature, symptoms, cause, or effect of the incident.', + }, + { + displayName: 'Service ID', + name: 'serviceId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getServices', + }, + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'incident', + ], + operation: [ + 'create', + ], + }, + }, + description: 'The incident will be created on this service.', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'incident', + ], + operation: [ + 'create', + ], + }, + }, + description: `The email address of a valid user associated with the account making the request.`, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'incident', + ], + operation: [ + 'create', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Escalation Policy ID', + name: 'escalationPolicyId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getEscalationPolicies', + }, + default: '', + description: 'Delegate this incident to the specified escalation policy. Cannot be specified if an assignee is given.', + }, + { + displayName: 'Incident Key', + name: 'incidentKey', + type: 'string', + default: '', + description: `Sending subsequent requests referencing the same service and with the same incident_key + will result in those requests being rejected if an open incident matches that incident_key.`, + }, + { + displayName: 'Priority ID', + name: 'priorityId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getPriorities', + }, + default: '', + description: 'The incident will be created on this service.', + }, + { + displayName: 'Urgency', + name: 'urgency', + type: 'options', + options: [ + { + name: 'Hight', + value: 'high', + }, + { + name: 'Low', + value: 'low', + }, + ], + default: '', + description: 'The urgency of the incident', + }, + ], + }, + { + displayName: 'Conference Bridge', + name: 'conferenceBridgeUi', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + placeholder: 'Add Conference Bridge', + displayOptions: { + show: { + resource: [ + 'incident', + ], + operation: [ + 'create', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Conference Bridge', + name: 'conferenceBridgeValues', + values: [ + { + displayName: 'Conference Number', + name: 'conferenceNumber', + type: 'string', + default: '', + description: `Phone numbers should be formatted like +1 415-555-1212,,,,1234#, where a comma (,)
+ represents a one-second wait and pound (#) completes access code input.`, + }, + { + displayName: 'Conference URL', + name: 'conferenceUrl', + type: 'string', + default: '', + description: 'An URL for the conference bridge. This could be a link to a web conference or Slack channel.', + } + ], + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* incident:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Incident ID', + name: 'incidentId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'incident', + ], + operation: [ + 'get', + ] + }, + }, + description: 'Unique identifier for the incident.', + }, +/* -------------------------------------------------------------------------- */ +/* incident:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'incident', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'incident', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'incident', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Date Range', + name: 'dateRange', + type: 'options', + options: [ + { + name: 'All', + value: 'all', + }, + ], + default: '', + description: 'When set to all, the since and until parameters and defaults are ignored.', + }, + { + displayName: 'Incident Key', + name: 'incidentKey', + type: 'string', + default: '', + description: `Incident de-duplication key. Incidents with child alerts do not
+ have an incident key; querying by incident key will return incidents whose alerts have
+ alert_key matching the given incident key.`, + }, + { + displayName: 'Include', + name: 'include', + type: 'multiOptions', + options: [ + { + name: 'Assigness', + value: 'assigness', + }, + { + name: 'Acknowledgers', + value: 'acknowledgers', + }, + { + name: 'Conferenece Bridge', + value: 'conferenceBridge', + }, + { + name: 'Escalation Policies', + value: 'escalationPolicies', + }, + { + name: 'First Trigger Log Entries', + value: 'firstTriggerLogEntries', + }, + { + name: 'Priorities', + value: 'priorities', + }, + { + name: 'Services', + value: 'services', + }, + { + name: 'Teams', + value: 'teams', + }, + { + name: 'Users', + value: 'users', + }, + ], + default: [], + description: 'Additional details to include.', + }, + { + displayName: 'Service IDs', + name: 'serviceIds', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getServices', + }, + default: '', + description: 'Returns only the incidents associated with the passed service(s).', + }, + { + displayName: 'Since', + name: 'since', + type: 'dateTime', + default: '', + description: 'The start of the date range over which you want to search. (the limit on date ranges is 6 months)', + }, + { + displayName: 'Sort By', + name: 'sortBy', + type: 'string', + default: '', + placeholder: 'created_at:asc,resolved_at:desc', + description: `Used to specify both the field you wish to sort the results on (incident_number/created_at/resolved_at/urgency), as well as the direction (asc/desc) of the results.
+ The sort_by field and direction should be separated by a colon.
+ A maximum of two fields can be included, separated by a comma.`, + }, + { + displayName: 'Statuses', + name: 'statuses', + type: 'multiOptions', + options: [ + { + name: 'Acknowledged', + value: 'acknowledged', + }, + { + name: 'Resolved', + value: 'resolved', + }, + { + name: 'Triggered', + value: 'triggered', + }, + ], + default: '', + description: 'Returns only the incidents associated with the passed service(s).', + }, + { + displayName: 'Team IDs', + name: 'teamIds', + type: 'string', + default: '', + description: 'Team IDs. Only results related to these teams will be returned. Account must have the teams ability to use this parameter. (multiples Ids can be added separated by comma)', + }, + { + displayName: 'Timezone', + name: 'timeZone', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTimezones', + }, + default: '', + description: 'Time zone in which dates in the result will be rendered. If not set dates will return UTC', + }, + { + displayName: 'Until', + name: 'until', + type: 'dateTime', + default: '', + description: 'The end of the date range over which you want to search. (the limit on date ranges is 6 months)', + }, + { + displayName: 'Urgencies', + name: 'urgencies', + type: 'multiOptions', + options: [ + { + name: 'High', + value: 'high', + }, + { + name: 'Low', + value: 'low', + }, + ], + default: '', + description: 'urgencies of the incidents to be returned. Defaults to all urgencies. Account must have the urgencies ability to do this', + }, + { + displayName: 'User IDs', + name: 'userIds', + type: 'string', + default: '', + description: 'Returns only the incidents currently assigned to the passed user(s). This expects one or more user IDs (multiple Ids can be added separated by comma)', + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* incident:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Incident ID', + name: 'incidentId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'incident', + ], + operation: [ + 'update', + ], + }, + }, + description: 'Unique identifier for the incident.', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'incident', + ], + operation: [ + 'update', + ], + }, + }, + description: `The email address of a valid user associated with the account making the request.`, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'incident', + ], + operation: [ + 'update', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Escalation Level', + name: 'escalationLevel', + type: 'number', + default: 0, + typeOptions: { + minValue: 0, + }, + description: 'Escalate the incident to this level in the escalation policy.', + }, + { + displayName: 'Escalation Policy ID', + name: 'escalationPolicyId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getEscalationPolicies', + }, + default: '', + description: 'Delegate this incident to the specified escalation policy. Cannot be specified if an assignee is given.', + }, + { + displayName: 'Priority ID', + name: 'priorityId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getPriorities', + }, + default: '', + description: 'The incident will be created on this service.', + }, + { + displayName: 'Resolution', + name: 'resolution', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'The resolution for this incident if status is set to resolved.', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Acknowledged', + value: 'acknowledged', + }, + { + name: 'Resolved', + value: 'resolved', + }, + ], + default: '', + description: 'The new status of the incident.', + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'A succinct description of the nature, symptoms, cause, or effect of the incident.', + }, + { + displayName: 'Urgency', + name: 'urgency', + type: 'options', + options: [ + { + name: 'Hight', + value: 'high', + }, + { + name: 'Low', + value: 'low', + }, + ], + default: '', + description: 'The urgency of the incident', + }, + ], + }, + { + displayName: 'Conference Bridge', + name: 'conferenceBridgeUi', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + placeholder: 'Add Conference Bridge', + displayOptions: { + show: { + resource: [ + 'incident', + ], + operation: [ + 'update', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Conference Bridge', + name: 'conferenceBridgeValues', + values: [ + { + displayName: 'Conference Number', + name: 'conferenceNumber', + type: 'string', + default: '', + description: `Phone numbers should be formatted like +1 415-555-1212,,,,1234#, where a comma (,)
+ represents a one-second wait and pound (#) completes access code input.`, + }, + { + displayName: 'Conference URL', + name: 'conferenceUrl', + type: 'string', + default: '', + description: 'An URL for the conference bridge. This could be a link to a web conference or Slack channel.', + } + ], + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/PagerDuty/IncidentInterface.ts b/packages/nodes-base/nodes/PagerDuty/IncidentInterface.ts new file mode 100644 index 0000000000..5663d9a086 --- /dev/null +++ b/packages/nodes-base/nodes/PagerDuty/IncidentInterface.ts @@ -0,0 +1,19 @@ +import { + IDataObject, + } from "n8n-workflow"; + +export interface IIncident { + assignments?: IDataObject[]; + body?: IDataObject; + conference_bridge?: IDataObject; + escalation_level?: number; + escalation_policy?: IDataObject; + incident_key?: string; + priority?: IDataObject; + resolution?: string; + status?: string; + service?: IDataObject; + title?: string; + type?: string; + urgency?: string; +} diff --git a/packages/nodes-base/nodes/PagerDuty/IncidentNoteDescription.ts b/packages/nodes-base/nodes/PagerDuty/IncidentNoteDescription.ts new file mode 100644 index 0000000000..c76152f17f --- /dev/null +++ b/packages/nodes-base/nodes/PagerDuty/IncidentNoteDescription.ts @@ -0,0 +1,158 @@ +import { + INodeProperties, + } from 'n8n-workflow'; + +export const incidentNoteOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'incidentNote', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a incident note', + }, + { + name: 'Get All', + value: 'getAll', + description: `Get all incident's notes`, + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const incidentNoteFields = [ + +/* -------------------------------------------------------------------------- */ +/* incidentNote:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Incident ID', + name: 'incidentId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'incidentNote', + ], + operation: [ + 'create', + ], + }, + }, + description: 'Unique identifier for the incident.', + }, + { + displayName: 'Content', + name: 'content', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'incidentNote', + ], + operation: [ + 'create', + ], + }, + }, + description: 'The note content', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'incidentNote', + ], + operation: [ + 'create', + ], + }, + }, + description: `The email address of a valid user associated with the account making the request.`, + }, +/* -------------------------------------------------------------------------- */ +/* incidentNote:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Incident ID', + name: 'incidentId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'incidentNote', + ], + operation: [ + 'getAll', + ], + }, + }, + description: 'Unique identifier for the incident.', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'incidentNote', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'incidentNote', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/PagerDuty/LogEntryDescription.ts b/packages/nodes-base/nodes/PagerDuty/LogEntryDescription.ts new file mode 100644 index 0000000000..7164d31214 --- /dev/null +++ b/packages/nodes-base/nodes/PagerDuty/LogEntryDescription.ts @@ -0,0 +1,175 @@ +import { + INodeProperties, + } from 'n8n-workflow'; + +export const logEntryOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'logEntry', + ], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + description: 'Get a log entry', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all log entries', + }, + ], + default: 'get', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const logEntryFields = [ +/* -------------------------------------------------------------------------- */ +/* logEntry:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Log Entry ID', + name: 'logEntryId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'logEntry', + ], + operation: [ + 'get', + ] + }, + }, + description: 'Unique identifier for the log entry.', + }, +/* -------------------------------------------------------------------------- */ +/* logEntry:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'logEntry', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'logEntry', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'logEntry', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Include', + name: 'include', + type: 'multiOptions', + options: [ + { + name: 'Channels', + value: 'channels', + }, + { + name: 'Incidents', + value: 'incidents', + }, + { + name: 'Services', + value: 'services', + }, + { + name: 'Teams', + value: 'teams', + }, + ], + default: [], + description: 'Additional details to include.', + }, + { + displayName: 'Is Overview', + name: 'isOverview', + type: 'boolean', + default: false, + description: 'If true, will return a subset of log entries that show only the most important changes to the incident.', + }, + { + displayName: 'Since', + name: 'since', + type: 'dateTime', + default: '', + description: 'The start of the date range over which you want to search. (the limit on date ranges is 6 months)', + }, + { + displayName: 'Timezone', + name: 'timeZone', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTimezones', + }, + default: '', + description: 'Time zone in which dates in the result will be rendered. If not set dates will return UTC', + }, + { + displayName: 'Until', + name: 'until', + type: 'dateTime', + default: '', + description: 'The end of the date range over which you want to search. (the limit on date ranges is 6 months)', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts b/packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts new file mode 100644 index 0000000000..6e887a1f94 --- /dev/null +++ b/packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts @@ -0,0 +1,361 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeTypeDescription, + INodeExecutionData, + INodeType, + INodePropertyOptions, +} from 'n8n-workflow'; + +import { + pagerDutyApiRequest, + pagerDutyApiRequestAllItems, + keysToSnakeCase, +} from './GenericFunctions'; + +import { + incidentFields, + incidentOperations, +} from './IncidentDescription'; + +import { + incidentNoteFields, + incidentNoteOperations, +} from './IncidentNoteDescription'; + +import { + logEntryFields, + logEntryOperations, +} from './logEntryDescription'; + +import { + IIncident, +} from './IncidentInterface'; + +import { + snakeCase, +} from 'change-case'; + +import * as moment from 'moment-timezone'; + +export class PagerDuty implements INodeType { + description: INodeTypeDescription = { + displayName: 'PagerDuty', + name: 'pagerDuty', + icon: 'file:pagerDuty.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume PagerDuty API', + defaults: { + name: 'PagerDuty', + color: '#49a25f', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'pagerDutyApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Incident', + value: 'incident', + }, + { + name: 'Incident Note', + value: 'incidentNote', + }, + { + name: 'Log Entry', + value: 'logEntry', + }, + ], + default: 'incident', + description: 'Resource to consume.', + }, + // INCIDENT + ...incidentOperations, + ...incidentFields, + // INCIDENT NOTE + ...incidentNoteOperations, + ...incidentNoteFields, + // LOG ENTRY + ...logEntryOperations, + ...logEntryFields, + ], + }; + + methods = { + loadOptions: { + // Get all the available escalation policies to display them to user so that he can + // select them easily + async getEscalationPolicies(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const escalationPolicies = await pagerDutyApiRequestAllItems.call(this, 'escalation_policies', 'GET', '/escalation_policies'); + for (const escalationPolicy of escalationPolicies) { + const escalationPolicyName = escalationPolicy.name; + const escalationPolicyId = escalationPolicy.id; + returnData.push({ + name: escalationPolicyName, + value: escalationPolicyId, + }); + } + return returnData; + }, + // Get all the available priorities to display them to user so that he can + // select them easily + async getPriorities(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const priorities = await pagerDutyApiRequestAllItems.call(this, 'priorities', 'GET', '/priorities'); + for (const priority of priorities) { + const priorityName = priority.name; + const priorityId = priority.id; + const priorityDescription = priority.description; + returnData.push({ + name: priorityName, + value: priorityId, + description: priorityDescription, + }); + } + return returnData; + }, + // Get all the available services to display them to user so that he can + // select them easily + async getServices(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const services = await pagerDutyApiRequestAllItems.call(this, 'services', 'GET', '/services'); + for (const service of services) { + const serviceName = service.name; + const serviceId = service.id; + returnData.push({ + name: serviceName, + value: serviceId, + }); + } + return returnData; + }, + // Get all the timezones to display them to user so that he can + // select them easily + async getTimezones(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + for (const timezone of moment.tz.names()) { + const timezoneName = timezone; + const timezoneId = timezone; + returnData.push({ + name: timezoneName, + value: timezoneId, + }); + } + return returnData; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + let responseData; + const qs: IDataObject = {}; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { + if (resource === 'incident') { + //https://api-reference.pagerduty.com/#!/Incidents/post_incidents + if (operation === 'create') { + const title = this.getNodeParameter('title', i) as string; + const serviceId = this.getNodeParameter('serviceId', i) as string; + const email = this.getNodeParameter('email', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const conferenceBridge = (this.getNodeParameter('conferenceBridgeUi', i) as IDataObject).conferenceBridgeValues as IDataObject; + const body: IIncident = { + type: 'incident', + title, + service: { + id: serviceId, + type: 'service_reference', + }, + }; + if (additionalFields.details) { + body.body = { + type: 'incident_body', + details: additionalFields.details, + }; + } + if (additionalFields.priorityId) { + body.priority = { + id: additionalFields.priorityId, + type: 'priority_reference', + }; + } + if (additionalFields.escalationPolicyId) { + body.escalation_policy = { + id: additionalFields.escalationPolicyId, + type: 'escalation_policy_reference', + }; + } + if (additionalFields.urgency) { + body.urgency = additionalFields.urgency as string; + } + if (additionalFields.incidentKey) { + body.incident_key = additionalFields.incidentKey as string; + } + if (conferenceBridge) { + body.conference_bridge = { + conference_number: conferenceBridge.conferenceNumber, + conference_url: conferenceBridge.conferenceUrl, + }; + } + responseData = await pagerDutyApiRequest.call(this, 'POST', '/incidents', { incident: body }, {}, undefined, { from: email }); + responseData = responseData.incident; + } + //https://api-reference.pagerduty.com/#!/Incidents/get_incidents_id + if (operation === 'get') { + const incidentId = this.getNodeParameter('incidentId', i) as string; + responseData = await pagerDutyApiRequest.call(this, 'GET', `/incidents/${incidentId}`); + responseData = responseData.incident; + } + //https://api-reference.pagerduty.com/#!/Incidents/get_incidents + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + const options = this.getNodeParameter('options', 0) as IDataObject; + if (options.userIds) { + options.userIds = (options.userIds as string).split(',') as string[]; + } + if (options.teamIds) { + options.teamIds = (options.teamIds as string).split(',') as string[]; + } + if (options.include) { + options.include = (options.include as string[]).map((e) => snakeCase(e)); + } + if (options.sortBy) { + options.sortBy = options.sortBy as string; + } + Object.assign(qs, keysToSnakeCase(options)[0]); + if (returnAll) { + responseData = await pagerDutyApiRequestAllItems.call(this, 'incidents', 'GET', '/incidents', {}, qs); + } else { + qs.limit = this.getNodeParameter('limit', 0) as number; + responseData = await pagerDutyApiRequest.call(this, 'GET', '/incidents', {}, qs); + responseData = responseData.incidents; + } + } + //https://api-reference.pagerduty.com/#!/Incidents/put_incidents_id + if (operation === 'update') { + const incidentId = this.getNodeParameter('incidentId', i) as string; + const email = this.getNodeParameter('email', i) as string; + const conferenceBridge = (this.getNodeParameter('conferenceBridgeUi', i) as IDataObject).conferenceBridgeValues as IDataObject; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const body: IIncident = { + type: 'incident', + }; + if (updateFields.title) { + body.title = updateFields.title as string; + } + if (updateFields.escalationLevel) { + body.escalation_level = updateFields.escalationLevel as number; + } + if (updateFields.details) { + body.body = { + type: 'incident_body', + details: updateFields.details, + }; + } + if (updateFields.priorityId) { + body.priority = { + id: updateFields.priorityId, + type: 'priority_reference', + }; + } + if (updateFields.escalationPolicyId) { + body.escalation_policy = { + id: updateFields.escalationPolicyId, + type: 'escalation_policy_reference', + }; + } + if (updateFields.urgency) { + body.urgency = updateFields.urgency as string; + } + if (updateFields.resolution) { + body.resolution = updateFields.resolution as string; + } + if (updateFields.status) { + body.status = updateFields.status as string; + } + if (conferenceBridge) { + body.conference_bridge = { + conference_number: conferenceBridge.conferenceNumber, + conference_url: conferenceBridge.conferenceUrl, + }; + } + responseData = await pagerDutyApiRequest.call(this, 'PUT', `/incidents/${incidentId}`, { incident: body }, {}, undefined, { from: email }); + responseData = responseData.incident; + } + } + if (resource === 'incidentNote') { + //https://api-reference.pagerduty.com/#!/Incidents/post_incidents_id_notes + if (operation === 'create') { + const incidentId = this.getNodeParameter('incidentId', i) as string; + const content = this.getNodeParameter('content', i) as string; + const email = this.getNodeParameter('email', i) as string; + const body: IDataObject = { + content, + }; + responseData = await pagerDutyApiRequest.call(this, 'POST', `/incidents/${incidentId}/notes`, { note: body }, {}, undefined, { from: email }); + } + //https://api-reference.pagerduty.com/#!/Incidents/get_incidents_id_notes + if (operation === 'getAll') { + const incidentId = this.getNodeParameter('incidentId', i) as string; + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + if (returnAll) { + responseData = await pagerDutyApiRequestAllItems.call(this, 'notes', 'GET', `/incidents/${incidentId}/notes`, {}, qs); + } else { + qs.limit = this.getNodeParameter('limit', 0) as number; + responseData = await pagerDutyApiRequest.call(this, 'GET', `/incidents/${incidentId}/notes`, {}, qs); + responseData = responseData.notes; + } + } + } + if (resource === 'logEntry') { + //https://api-reference.pagerduty.com/#!/Log_Entries/get_log_entries_id + if (operation === 'get') { + const logEntryId = this.getNodeParameter('logEntryId', i) as string; + responseData = await pagerDutyApiRequest.call(this, 'GET', `/log_entries/${logEntryId}`); + responseData = responseData.log_entry; + } + //https://api-reference.pagerduty.com/#!/Log_Entries/get_log_entries + if (operation === 'getAll') { + const options = this.getNodeParameter('options', i) as IDataObject; + Object.assign(qs, options); + keysToSnakeCase(qs); + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + if (returnAll) { + responseData = await pagerDutyApiRequestAllItems.call(this, 'log_entries', 'GET', '/log_entries', {}, qs); + } else { + qs.limit = this.getNodeParameter('limit', 0) as number; + responseData = await pagerDutyApiRequest.call(this, 'GET', 'log_entries', {}, qs); + responseData = responseData.log_entries; + } + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/PagerDuty/pagerDuty.png b/packages/nodes-base/nodes/PagerDuty/pagerDuty.png new file mode 100644 index 0000000000..340840bb36 Binary files /dev/null and b/packages/nodes-base/nodes/PagerDuty/pagerDuty.png differ diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index e197bd6ca4..1f8b6625e7 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -75,13 +75,14 @@ "dist/credentials/MandrillApi.credentials.js", "dist/credentials/MattermostApi.credentials.js", "dist/credentials/MauticApi.credentials.js", - "dist/credentials/MoceanApi.credentials.js", + "dist/credentials/MoceanApi.credentials.js", "dist/credentials/MondayComApi.credentials.js", "dist/credentials/MongoDb.credentials.js", "dist/credentials/Msg91Api.credentials.js", "dist/credentials/MySql.credentials.js", "dist/credentials/NextCloudApi.credentials.js", "dist/credentials/OpenWeatherMapApi.credentials.js", + "dist/credentials/PagerDutyApi.credentials.js", "dist/credentials/PayPalApi.credentials.js", "dist/credentials/PipedriveApi.credentials.js", "dist/credentials/Postgres.credentials.js", @@ -196,6 +197,7 @@ "dist/nodes/NextCloud/NextCloud.node.js", "dist/nodes/NoOp.node.js", "dist/nodes/OpenWeatherMap.node.js", + "dist/nodes/PagerDuty/PagerDuty.node.js", "dist/nodes/PayPal/PayPal.node.js", "dist/nodes/PayPal/PayPalTrigger.node.js", "dist/nodes/Pipedrive/Pipedrive.node.js",