@@ -259,9 +259,7 @@ export default mixins(
return title;
},
displayValue (): string | number | boolean | null {
- if (this.remoteParameterOptionsLoadingIssues !== null) {
- return 'Error loading...';
- } else if (this.remoteParameterOptionsLoading === true) {
+ if (this.remoteParameterOptionsLoading === true) {
// If it is loading options from server display
// to user that the data is loading. If not it would
// display the user the key instead of the value it
diff --git a/packages/nodes-base/credentials/CockpitApi.credentials.ts b/packages/nodes-base/credentials/CockpitApi.credentials.ts
new file mode 100644
index 0000000000..fcc76f4ef5
--- /dev/null
+++ b/packages/nodes-base/credentials/CockpitApi.credentials.ts
@@ -0,0 +1,24 @@
+import {
+ ICredentialType,
+ NodePropertyTypes,
+} from 'n8n-workflow';
+
+export class CockpitApi implements ICredentialType {
+ name = 'cockpitApi';
+ displayName = 'Cockpit API';
+ properties = [
+ {
+ displayName: 'Cockpit URL',
+ name: 'url',
+ type: 'string' as NodePropertyTypes,
+ default: '',
+ placeholder: 'https://example.com',
+ },
+ {
+ displayName: 'Access Token',
+ name: 'accessToken',
+ type: 'string' as NodePropertyTypes,
+ default: '',
+ },
+ ];
+}
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/Cockpit/Cockpit.node.ts b/packages/nodes-base/nodes/Cockpit/Cockpit.node.ts
new file mode 100644
index 0000000000..bac4b2213f
--- /dev/null
+++ b/packages/nodes-base/nodes/Cockpit/Cockpit.node.ts
@@ -0,0 +1,168 @@
+import { IExecuteFunctions } from 'n8n-core';
+import {
+ IDataObject,
+ ILoadOptionsFunctions,
+ INodeExecutionData,
+ INodePropertyOptions,
+ INodeType,
+ INodeTypeDescription,
+} from 'n8n-workflow';
+import {
+ collectionFields,
+ collectionOperations,
+} from './CollectionDescription';
+import {
+ createCollectionEntry,
+ getAllCollectionEntries,
+ getAllCollectionNames,
+} from './CollectionFunctions';
+import {
+ formFields,
+ formOperations
+} from './FormDescription';
+import { submitForm } from './FormFunctions';
+import {
+ singletonFields,
+ singletonOperations,
+} from './SingletonDescription';
+import {
+ getSingleton,
+ getAllSingletonNames,
+} from './SingletonFunctions';
+
+export class Cockpit implements INodeType {
+ description: INodeTypeDescription = {
+ displayName: 'Cockpit',
+ name: 'cockpit',
+ icon: 'file:cockpit.png',
+ group: ['output'],
+ version: 1,
+ subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}',
+ description: 'Consume Cockpit API',
+ defaults: {
+ name: 'Cockpit',
+ color: '#000000',
+ },
+ inputs: ['main'],
+ outputs: ['main'],
+ credentials: [
+ {
+ name: 'cockpitApi',
+ required: true,
+ },
+ ],
+ properties: [
+ {
+ displayName: 'Resource',
+ name: 'resource',
+ type: 'options',
+ default: 'collections',
+ description: 'Resource to consume.',
+ options: [
+ {
+ name: 'Collection',
+ value: 'collection',
+ },
+ {
+ name: 'Form',
+ value: 'form',
+ },
+ {
+ name: 'Singleton',
+ value: 'singleton',
+ },
+ ],
+ },
+
+
+ ...collectionOperations,
+ ...collectionFields,
+ ...formOperations,
+ ...formFields,
+ ...singletonOperations,
+ ...singletonFields,
+ ],
+ };
+
+
+ methods = {
+ loadOptions: {
+ async getCollections(this: ILoadOptionsFunctions): Promise
{
+ const collections = await getAllCollectionNames.call(this);
+
+ return collections.map(itemName => {
+ return {
+ name: itemName,
+ value: itemName,
+ };
+ });
+ },
+
+ async getSingletons(this: ILoadOptionsFunctions): Promise {
+ const singletons = await getAllSingletonNames.call(this);
+
+ return singletons.map(itemName => {
+ return {
+ name: itemName,
+ value: itemName,
+ };
+ });
+ },
+ },
+ };
+
+ async execute(this: IExecuteFunctions): Promise {
+ const items = this.getInputData();
+ const returnData: IDataObject[] = [];
+ const length = items.length as unknown as number;
+ const resource = this.getNodeParameter('resource', 0) as string;
+ const operation = this.getNodeParameter('operation', 0) as string;
+
+ let responseData;
+
+ for (let i = 0; i < length; i++) {
+ if (resource === 'collection') {
+ const collectionName = this.getNodeParameter('collection', i) as string;
+ if (operation === 'create') {
+ const data = this.getNodeParameter('data', i) as IDataObject;
+
+ responseData = await createCollectionEntry.call(this, collectionName, data);
+ } else if (operation === 'getAll') {
+ const options = this.getNodeParameter('options', i) as IDataObject;
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+
+ if (returnAll !== true) {
+ options.limit = this.getNodeParameter('limit', i) as number;
+ }
+
+ responseData = await getAllCollectionEntries.call(this, collectionName, options);
+ } else if (operation === 'update') {
+ const id = this.getNodeParameter('id', i) as string;
+ const data = this.getNodeParameter('data', i) as IDataObject;
+
+ responseData = await createCollectionEntry.call(this, collectionName, data, id);
+ }
+ } else if (resource === 'form') {
+ const formName = this.getNodeParameter('form', i) as string;
+ if (operation === 'submit') {
+ const form = this.getNodeParameter('form', i) as IDataObject;
+
+ responseData = await submitForm.call(this, formName, form);
+ }
+ } else if (resource === 'singleton') {
+ const singletonName = this.getNodeParameter('singleton', i) as string;
+ if (operation === 'get') {
+ responseData = await getSingleton.call(this, singletonName);
+ }
+ }
+
+ 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/Cockpit/CollectionDescription.ts b/packages/nodes-base/nodes/Cockpit/CollectionDescription.ts
new file mode 100644
index 0000000000..e448ce670e
--- /dev/null
+++ b/packages/nodes-base/nodes/Cockpit/CollectionDescription.ts
@@ -0,0 +1,238 @@
+import { INodeProperties } from 'n8n-workflow';
+
+export const collectionOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'collection',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Create an entry',
+ value: 'create',
+ description: 'Create a collection entry',
+ },
+ {
+ name: 'Get all entries',
+ value: 'getAll',
+ description: 'Get all collection entries',
+ },
+ {
+ name: 'Update an entry',
+ value: 'update',
+ description: 'Update a collection entries',
+ },
+ ],
+ default: 'getAll',
+ description: 'The operation to perform.',
+ },
+] as INodeProperties[];
+
+export const collectionFields = [
+ {
+ displayName: 'Collection',
+ name: 'collection',
+ type: 'options',
+ default: '',
+ typeOptions: {
+ loadOptionsMethod: 'getCollections',
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'collection',
+ ],
+ },
+ },
+ required: true,
+ description: 'Name of the collection to operate on.'
+ },
+
+ // Collection:entry:create
+ {
+ displayName: 'Data',
+ name: 'data',
+ type: 'json',
+ required: true,
+ default: '',
+ typeOptions: {
+ alwaysOpenEditWindow: true,
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'collection',
+ ],
+ operation: [
+ 'create',
+ ]
+ },
+ },
+ description: 'The data to create.',
+ },
+
+ // Collection:entry:getAll
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'collection',
+ ],
+ },
+ },
+ 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: [
+ 'collection',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 500,
+ },
+ default: 100,
+ description: 'How many results to return.',
+ },
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'collection',
+ ],
+ operation: [
+ 'getAll',
+ ]
+ },
+ },
+ options: [
+ {
+ displayName: 'Fields',
+ name: 'fields',
+ type: 'json',
+ default: '',
+ typeOptions: {
+ alwaysOpenEditWindow: true,
+ },
+ description: 'Fields to get.',
+ },
+ {
+ displayName: 'Filter',
+ name: 'filter',
+ type: 'json',
+ default: '',
+ typeOptions: {
+ alwaysOpenEditWindow: true,
+ },
+ description: 'Filter result by fields.',
+ },
+ {
+ displayName: 'Language',
+ name: 'language',
+ type: 'string',
+ default: '',
+ description: 'Return normalized language fields.',
+ },
+ {
+ displayName: 'Populate',
+ name: 'populate',
+ type: 'boolean',
+ required: true,
+ default: true,
+ description: 'Resolve linked collection items.',
+ },
+ {
+ displayName: 'RAW Data',
+ name: 'rawData',
+ type: 'boolean',
+ default: false,
+ description: `Returns the data exactly in the way it got received from the API.`,
+ },
+ {
+ displayName: 'Skip',
+ name: 'skip',
+ type: 'number',
+ default: '',
+ description: 'Skip number of entries.',
+ },
+ {
+ displayName: 'Sort',
+ name: 'sort',
+ type: 'json',
+ default: '',
+ description: 'Sort result by fields.',
+ },
+ ],
+ },
+
+ // Collection:entry:update
+ {
+ displayName: 'Entry ID',
+ name: 'id',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'collection',
+ ],
+ operation: [
+ 'update',
+ ]
+ },
+ },
+ description: 'The entry ID.',
+ },
+ {
+ displayName: 'Data',
+ name: 'data',
+ type: 'json',
+ required: true,
+ default: '',
+ typeOptions: {
+ alwaysOpenEditWindow: true,
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'collection',
+ ],
+ operation: [
+ 'update',
+ ]
+ },
+ },
+ description: 'The data to update.',
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/Cockpit/CollectionFunctions.ts b/packages/nodes-base/nodes/Cockpit/CollectionFunctions.ts
new file mode 100644
index 0000000000..4a2c640f84
--- /dev/null
+++ b/packages/nodes-base/nodes/Cockpit/CollectionFunctions.ts
@@ -0,0 +1,68 @@
+import {
+ IExecuteFunctions,
+ IExecuteSingleFunctions,
+ ILoadOptionsFunctions
+} from 'n8n-core';
+import { IDataObject } from 'n8n-workflow';
+import { ICollection } from './CollectionInterface';
+import { cockpitApiRequest } from './GenericFunctions';
+
+export async function createCollectionEntry(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resourceName: string, data: IDataObject, id?: string): Promise { // tslint:disable-line:no-any
+ const body: ICollection = {
+ data: JSON.parse(data.toString())
+ };
+
+ if (id) {
+ body.data = {
+ _id: id,
+ ...body.data
+ };
+ }
+
+ return cockpitApiRequest.call(this, 'post', `/collections/save/${resourceName}`, body);
+}
+
+
+export async function getAllCollectionEntries(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resourceName: string, options: IDataObject): Promise { // tslint:disable-line:no-any
+ const body: ICollection = {};
+
+ if (options.fields) {
+ body.fields = JSON.parse(options.fields.toString());
+ }
+
+ if (options.filter) {
+ body.filter = JSON.parse(options.filter.toString());
+ }
+
+ if (options.limit) {
+ body.limit = options.limit as number;
+ }
+
+ if (options.skip) {
+ body.skip = options.skip as number;
+ }
+
+ if (options.sort) {
+ body.sort = JSON.parse(options.sort.toString());
+ }
+
+ if (options.populate) {
+ body.populate = options.populate as boolean;
+ }
+
+ body.simple = true;
+ if (options.rawData) {
+ body.simple = !options.rawData as boolean;
+ }
+
+ if (options.language) {
+ body.lang = options.language as string;
+ }
+
+ return cockpitApiRequest.call(this, 'post', `/collections/get/${resourceName}`, body);
+}
+
+
+export async function getAllCollectionNames(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions): Promise {
+ return cockpitApiRequest.call(this, 'GET', `/collections/listCollections`, {});
+}
diff --git a/packages/nodes-base/nodes/Cockpit/CollectionInterface.ts b/packages/nodes-base/nodes/Cockpit/CollectionInterface.ts
new file mode 100644
index 0000000000..834cbbb3b7
--- /dev/null
+++ b/packages/nodes-base/nodes/Cockpit/CollectionInterface.ts
@@ -0,0 +1,11 @@
+export interface ICollection {
+ fields?: object;
+ filter?: object;
+ limit?: number;
+ skip?: number;
+ sort?: object;
+ populate?: boolean;
+ simple?: boolean;
+ lang?: string;
+ data?: object;
+}
diff --git a/packages/nodes-base/nodes/Cockpit/FormDescription.ts b/packages/nodes-base/nodes/Cockpit/FormDescription.ts
new file mode 100644
index 0000000000..8488cbe095
--- /dev/null
+++ b/packages/nodes-base/nodes/Cockpit/FormDescription.ts
@@ -0,0 +1,64 @@
+import { INodeProperties } from 'n8n-workflow';
+
+export const formOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'form',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Submit a form',
+ value: 'submit',
+ description: 'Store submission of a form',
+ },
+
+ ],
+ default: 'submit',
+ description: 'The operation to perform.',
+ }
+] as INodeProperties[];
+
+export const formFields = [
+ {
+ displayName: 'Form',
+ name: 'form',
+ type: 'string',
+ displayOptions: {
+ show: {
+ resource: [
+ 'form',
+ ],
+ },
+ },
+ default: '',
+ required: true,
+ description: 'Name of the form to operate on.'
+ },
+
+ // Form:submit
+ {
+ displayName: 'Form data',
+ name: 'form',
+ type: 'json',
+ required: true,
+ default: '',
+ typeOptions: {
+ alwaysOpenEditWindow: true,
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'form',
+ ],
+ },
+ },
+ description: 'The data to save.',
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/Cockpit/FormFunctions.ts b/packages/nodes-base/nodes/Cockpit/FormFunctions.ts
new file mode 100644
index 0000000000..437ed210a0
--- /dev/null
+++ b/packages/nodes-base/nodes/Cockpit/FormFunctions.ts
@@ -0,0 +1,16 @@
+import {
+ IExecuteFunctions,
+ IExecuteSingleFunctions,
+ ILoadOptionsFunctions
+} from 'n8n-core';
+import { IDataObject } from 'n8n-workflow';
+import { IForm } from './FormInterface';
+import { cockpitApiRequest } from './GenericFunctions';
+
+export async function submitForm(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resourceName: string, form: IDataObject) {
+ const body: IForm = {
+ form: JSON.parse(form.toString())
+ };
+
+ return cockpitApiRequest.call(this, 'post', `/forms/submit/${resourceName}`, body);
+}
diff --git a/packages/nodes-base/nodes/Cockpit/FormInterface.ts b/packages/nodes-base/nodes/Cockpit/FormInterface.ts
new file mode 100644
index 0000000000..d1c218ae9d
--- /dev/null
+++ b/packages/nodes-base/nodes/Cockpit/FormInterface.ts
@@ -0,0 +1,3 @@
+export interface IForm {
+ form: object;
+}
diff --git a/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts b/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts
new file mode 100644
index 0000000000..ed923d3bda
--- /dev/null
+++ b/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts
@@ -0,0 +1,46 @@
+import {
+ IExecuteFunctions,
+ IExecuteSingleFunctions,
+ ILoadOptionsFunctions,
+} from 'n8n-core';
+import { IDataObject } from 'n8n-workflow';
+import { OptionsWithUri } from 'request';
+
+export async function cockpitApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any
+ const credentials = this.getCredentials('cockpitApi');
+
+ if (credentials === undefined) {
+ throw new Error('No credentials available.');
+ }
+
+ let options: OptionsWithUri = {
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ method,
+ qs: {
+ token: credentials!.accessToken
+ },
+ body,
+ uri: uri || `${credentials!.url}/api${resource}`,
+ json: true
+ };
+
+ options = Object.assign({}, options, option);
+
+ if (Object.keys(options.body).length === 0) {
+ delete options.body;
+ }
+
+ try {
+ return await this.helpers.request!(options);
+ } catch (error) {
+ let errorMessage = error.message;
+ if (error.error) {
+ errorMessage = error.error.message || error.error.error;
+ }
+
+ throw new Error(`Cockpit error [${error.statusCode}]: ` + errorMessage);
+ }
+}
diff --git a/packages/nodes-base/nodes/Cockpit/SingletonDescription.ts b/packages/nodes-base/nodes/Cockpit/SingletonDescription.ts
new file mode 100644
index 0000000000..e9774b4f5f
--- /dev/null
+++ b/packages/nodes-base/nodes/Cockpit/SingletonDescription.ts
@@ -0,0 +1,46 @@
+import { INodeProperties } from 'n8n-workflow';
+
+export const singletonOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'singleton',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Gets a singleton',
+ },
+ ],
+ default: 'get',
+ description: 'The operation to perform.',
+ }
+] as INodeProperties[];
+
+export const singletonFields = [
+ {
+ displayName: 'Singleton',
+ name: 'singleton',
+ type: 'options',
+ default: '',
+ typeOptions: {
+ loadOptionsMethod: 'getSingletons',
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'singleton',
+ ],
+ },
+ },
+ required: true,
+ description: 'Name of the singleton to operate on.'
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/Cockpit/SingletonFunctions.ts b/packages/nodes-base/nodes/Cockpit/SingletonFunctions.ts
new file mode 100644
index 0000000000..dab17f21f3
--- /dev/null
+++ b/packages/nodes-base/nodes/Cockpit/SingletonFunctions.ts
@@ -0,0 +1,14 @@
+import {
+ IExecuteFunctions,
+ IExecuteSingleFunctions,
+ ILoadOptionsFunctions
+} from 'n8n-core';
+import { cockpitApiRequest } from './GenericFunctions';
+
+export async function getSingleton(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resourceName: string): Promise { // tslint:disable-line:no-any
+ return cockpitApiRequest.call(this, 'get', `/singletons/get/${resourceName}`);
+}
+
+export async function getAllSingletonNames(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions): Promise {
+ return cockpitApiRequest.call(this, 'GET', `/singletons/listSingletons`, {});
+}
diff --git a/packages/nodes-base/nodes/Cockpit/cockpit.png b/packages/nodes-base/nodes/Cockpit/cockpit.png
new file mode 100644
index 0000000000..ddbe6ead67
Binary files /dev/null and b/packages/nodes-base/nodes/Cockpit/cockpit.png differ
diff --git a/packages/nodes-base/nodes/MySql/MySql.node.ts b/packages/nodes-base/nodes/MySql/MySql.node.ts
index 04052789d0..0aa6ab372c 100644
--- a/packages/nodes-base/nodes/MySql/MySql.node.ts
+++ b/packages/nodes-base/nodes/MySql/MySql.node.ts
@@ -245,9 +245,12 @@ export class MySql implements INodeType {
returnItems = this.helpers.returnJsonArray(queryResult as IDataObject[]);
} else {
+ await connection.end();
throw new Error(`The operation "${operation}" is not supported!`);
}
+ await connection.end();
+
return this.prepareOutputData(returnItems);
}
}
diff --git a/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts b/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts
new file mode 100644
index 0000000000..c360114144
--- /dev/null
+++ b/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts
@@ -0,0 +1,92 @@
+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;
+ query.limit = 100;
+ query.offset = 0;
+
+ do {
+ responseData = await pagerDutyApiRequest.call(this, method, endpoint, body, query);
+ 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..10c4e4b132
--- /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..d8dc0082ca
--- /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..4ffc85b6de
--- /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..9d2b9409ce
--- /dev/null
+++ b/packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts
@@ -0,0 +1,361 @@
+import {
+ IExecuteFunctions,
+} from 'n8n-core';
+
+import {
+ IDataObject,
+ ILoadOptionsFunctions,
+ INodeExecutionData,
+ INodePropertyOptions,
+ INodeType,
+ INodeTypeDescription,
+} from 'n8n-workflow';
+
+import {
+ keysToSnakeCase,
+ pagerDutyApiRequest,
+ pagerDutyApiRequestAllItems,
+} 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..a54f906409
Binary files /dev/null and b/packages/nodes-base/nodes/PagerDuty/pagerDuty.png differ
diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.ts
index f9db1a01e1..2fa010576b 100644
--- a/packages/nodes-base/nodes/Postgres/Postgres.node.ts
+++ b/packages/nodes-base/nodes/Postgres/Postgres.node.ts
@@ -325,6 +325,7 @@ export class Postgres implements INodeType {
returnItems = this.helpers.returnJsonArray(updateItems as IDataObject[]);
} else {
+ await pgp.end();
throw new Error(`The operation "${operation}" is not supported!`);
}
diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json
index 02b4b21eda..2e6afca3dd 100644
--- a/packages/nodes-base/package.json
+++ b/packages/nodes-base/package.json
@@ -39,6 +39,7 @@
"dist/credentials/ClearbitApi.credentials.js",
"dist/credentials/ClickUpApi.credentials.js",
"dist/credentials/ClockifyApi.credentials.js",
+ "dist/credentials/CockpitApi.credentials.js",
"dist/credentials/CodaApi.credentials.js",
"dist/credentials/CopperApi.credentials.js",
"dist/credentials/CalendlyApi.credentials.js",
@@ -88,6 +89,7 @@
"dist/credentials/NextCloudApi.credentials.js",
"dist/credentials/OAuth2Api.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",
@@ -141,6 +143,7 @@
"dist/nodes/ClickUp/ClickUp.node.js",
"dist/nodes/ClickUp/ClickUpTrigger.node.js",
"dist/nodes/Clockify/ClockifyTrigger.node.js",
+ "dist/nodes/Cockpit/Cockpit.node.js",
"dist/nodes/Coda/Coda.node.js",
"dist/nodes/Copper/CopperTrigger.node.js",
"dist/nodes/Cron.node.js",
@@ -210,6 +213,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",