From 52cbd323f2f3f9141c1577527596bd32871b807a Mon Sep 17 00:00:00 2001 From: Krzysztof Janda Date: Sat, 4 Apr 2020 16:04:25 +0200 Subject: [PATCH 1/8] Add Cockpit node --- .../credentials/CockpitApi.credentials.ts | 24 +++ .../nodes-base/nodes/Cockpit/Cockpit.node.ts | 130 ++++++++++++ .../nodes/Cockpit/CollectionDescription.ts | 186 ++++++++++++++++++ .../nodes/Cockpit/CollectionFunctions.ts | 61 ++++++ .../nodes/Cockpit/CollectionInterface.ts | 11 ++ .../nodes/Cockpit/FormDescription.ts | 48 +++++ .../nodes-base/nodes/Cockpit/FormFunctions.ts | 16 ++ .../nodes-base/nodes/Cockpit/FormInterface.ts | 3 + .../nodes/Cockpit/GenericFunctions.ts | 43 ++++ .../nodes/Cockpit/SingletonDescription.ts | 25 +++ .../nodes/Cockpit/SingletonFunctions.ts | 10 + packages/nodes-base/nodes/Cockpit/cockpit.png | Bin 0 -> 2944 bytes packages/nodes-base/package.json | 2 + 13 files changed, 559 insertions(+) create mode 100644 packages/nodes-base/credentials/CockpitApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Cockpit/Cockpit.node.ts create mode 100644 packages/nodes-base/nodes/Cockpit/CollectionDescription.ts create mode 100644 packages/nodes-base/nodes/Cockpit/CollectionFunctions.ts create mode 100644 packages/nodes-base/nodes/Cockpit/CollectionInterface.ts create mode 100644 packages/nodes-base/nodes/Cockpit/FormDescription.ts create mode 100644 packages/nodes-base/nodes/Cockpit/FormFunctions.ts create mode 100644 packages/nodes-base/nodes/Cockpit/FormInterface.ts create mode 100644 packages/nodes-base/nodes/Cockpit/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Cockpit/SingletonDescription.ts create mode 100644 packages/nodes-base/nodes/Cockpit/SingletonFunctions.ts create mode 100644 packages/nodes-base/nodes/Cockpit/cockpit.png diff --git a/packages/nodes-base/credentials/CockpitApi.credentials.ts b/packages/nodes-base/credentials/CockpitApi.credentials.ts new file mode 100644 index 000000000..fcc76f4ef --- /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/nodes/Cockpit/Cockpit.node.ts b/packages/nodes-base/nodes/Cockpit/Cockpit.node.ts new file mode 100644 index 000000000..7680f85d9 --- /dev/null +++ b/packages/nodes-base/nodes/Cockpit/Cockpit.node.ts @@ -0,0 +1,130 @@ +import { IExecuteFunctions } from 'n8n-core'; +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription +} from 'n8n-workflow'; +import { + collectionFields, + collectionOperations +} from './CollectionDescription'; +import { + getCollectionEntries, + saveCollectionEntry +} from './CollectionFunctions'; +import { + formFields, + formOperations +} from './FormDescription'; +import { submitForm } from './FormFunctions'; +import { singletonOperations } from "./SingletonDescription"; +import { getSingleton } 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"] + "/" + $parameter["resourceName"]}}', + 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: 'collections', + }, + { + name: 'Form', + value: 'forms', + }, + { + name: 'Singleton', + value: 'singletons', + }, + ], + }, + { + displayName: 'Resource name', + name: 'resourceName', + type: 'string', + default: '', + required: true, + description: 'Name of resource to consume.' + }, + ...collectionOperations, + ...collectionFields, + ...formOperations, + ...formFields, + ...singletonOperations, + ], + }; + + 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 resourceName = this.getNodeParameter('resourceName', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + let responseData; + + for (let i = 0; i < length; i++) { + if (resource === 'collections') { + if (operation === 'save') { + const data = this.getNodeParameter('data', i) as IDataObject; + + responseData = await saveCollectionEntry.call(this, resourceName, data); + } else if (operation === 'get') { + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + responseData = await getCollectionEntries.call(this, resourceName, additionalFields); + } else if (operation === 'update') { + const id = this.getNodeParameter('id', i) as string; + const data = this.getNodeParameter('data', i) as IDataObject; + + responseData = await saveCollectionEntry.call(this, resourceName, data, id); + } + } else if (resource === 'forms') { + if (operation === 'submit') { + const form = this.getNodeParameter('form', i) as IDataObject; + + responseData = await submitForm.call(this, resourceName, form); + } + } else if (resource === 'singletons') { + if (operation === 'get') { + responseData = await getSingleton.call(this, resourceName); + } + } + + 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 000000000..71483c309 --- /dev/null +++ b/packages/nodes-base/nodes/Cockpit/CollectionDescription.ts @@ -0,0 +1,186 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const collectionOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'collections', + ], + }, + }, + options: [ + { + name: 'Create an entry', + value: 'save', + description: 'Create a collection entry', + }, + { + name: 'Get all entries', + value: 'get', + description: 'Get all collection entries', + }, + { + name: 'Update an entry', + value: 'update', + description: 'Update a collection entries', + }, + ], + default: 'get', + description: 'The operation to perform.', + } +] as INodeProperties[]; + +export const collectionFields = [ + // Collections:entry:save + { + displayName: 'Data', + name: 'data', + type: 'json', + required: true, + default: '', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + resource: [ + 'collections', + ], + operation: [ + 'save', + ] + }, + }, + description: 'The data to save.', + }, + + // Collections:entry:get + { + displayName: 'Additional fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add field', + default: {}, + displayOptions: { + show: { + resource: [ + 'collections', + ], + operation: [ + 'get', + ] + }, + }, + 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: 'Limit', + name: 'limit', + type: 'number', + default: '', + description: 'Limit number of returned entries.', + }, + { + displayName: 'Skip', + name: 'skip', + type: 'number', + default: '', + description: 'Skip number of entries.', + }, + { + displayName: 'Sort', + name: 'sort', + type: 'json', + default: '', + description: 'Sort result by fields.', + }, + { + displayName: 'Populate', + name: 'populate', + type: 'boolean', + required: true, + default: true, + description: 'Resolve linked collection items.', + }, + { + displayName: 'Simple', + name: 'simple', + type: 'boolean', + required: true, + default: true, + description: 'Return only result entries.', + }, + { + displayName: 'Language', + name: 'language', + type: 'string', + default: '', + description: 'Return normalized language fields.', + }, + ], + }, + + // Collections:entry:update + { + displayName: 'Entry ID', + name: 'id', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'collections', + ], + operation: [ + 'update', + ] + }, + }, + description: 'The entry ID.', + }, + { + displayName: 'Data', + name: 'data', + type: 'json', + required: true, + default: '', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + resource: [ + 'collections', + ], + 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 000000000..fff760ba0 --- /dev/null +++ b/packages/nodes-base/nodes/Cockpit/CollectionFunctions.ts @@ -0,0 +1,61 @@ +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; +import { ICollection } from './CollectionInterface'; +import { cockpitApiRequest } from './GenericFunctions'; + +export async function saveCollectionEntry(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 getCollectionEntries(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resourceName: string, additionalFields: IDataObject): Promise { // tslint:disable-line:no-any + const body: ICollection = {}; + + if (additionalFields.fields) { + body.fields = JSON.parse(additionalFields.fields.toString()); + } + + if (additionalFields.filter) { + body.filter = JSON.parse(additionalFields.filter.toString()); + } + + if (additionalFields.limit) { + body.limit = additionalFields.limit as number; + } + + if (additionalFields.skip) { + body.skip = additionalFields.skip as number; + } + + if (additionalFields.sort) { + body.sort = JSON.parse(additionalFields.sort.toString()); + } + + if (additionalFields.populate) { + body.populate = additionalFields.populate as boolean; + } + + if (additionalFields.simple) { + body.simple = additionalFields.simple as boolean; + } + + if (additionalFields.language) { + body.lang = additionalFields.language as string; + } + + return cockpitApiRequest.call(this, 'post', `/collections/get/${resourceName}`, body); +} diff --git a/packages/nodes-base/nodes/Cockpit/CollectionInterface.ts b/packages/nodes-base/nodes/Cockpit/CollectionInterface.ts new file mode 100644 index 000000000..834cbbb3b --- /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 000000000..cdb1266e3 --- /dev/null +++ b/packages/nodes-base/nodes/Cockpit/FormDescription.ts @@ -0,0 +1,48 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const formOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'forms', + ], + }, + }, + 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 = [ + // Forms:submit + { + displayName: 'Form data', + name: 'form', + type: 'json', + required: true, + default: '', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + resource: [ + 'forms', + ], + }, + }, + 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 000000000..437ed210a --- /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 000000000..d1c218ae9 --- /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 000000000..db37b57ab --- /dev/null +++ b/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts @@ -0,0 +1,43 @@ +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) { + const errorMessage = error.error.message || error.error.error; + + throw new Error('Cockpit error: ' + errorMessage); + } +} diff --git a/packages/nodes-base/nodes/Cockpit/SingletonDescription.ts b/packages/nodes-base/nodes/Cockpit/SingletonDescription.ts new file mode 100644 index 000000000..d10edafc3 --- /dev/null +++ b/packages/nodes-base/nodes/Cockpit/SingletonDescription.ts @@ -0,0 +1,25 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const singletonOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'singletons', + ], + }, + }, + options: [ + { + name: 'Get data', + value: 'get', + description: 'Get singleton data', + }, + ], + default: 'get', + description: 'The operation to perform.', + } +] 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 000000000..5d07bb429 --- /dev/null +++ b/packages/nodes-base/nodes/Cockpit/SingletonFunctions.ts @@ -0,0 +1,10 @@ +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}`); +} diff --git a/packages/nodes-base/nodes/Cockpit/cockpit.png b/packages/nodes-base/nodes/Cockpit/cockpit.png new file mode 100644 index 0000000000000000000000000000000000000000..b99e95037c62c3a222fbb5a77e668e96ded6b9af GIT binary patch literal 2944 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 literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 908b5699f..e197bd6ca 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", @@ -131,6 +132,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", From f34a1d577cbbbcd10fa2fafb0335861ac92c7d42 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 5 Apr 2020 15:49:47 +0200 Subject: [PATCH 2/8] :zap: Small improvements on Cockpit-Node --- .../nodes-base/nodes/Cockpit/Cockpit.node.ts | 83 ++++++++++----- .../nodes/Cockpit/CollectionDescription.ts | 96 +++++++++++------- .../nodes/Cockpit/CollectionFunctions.ts | 50 +++++---- .../nodes/Cockpit/FormDescription.ts | 16 +++ .../nodes/Cockpit/GenericFunctions.ts | 7 +- .../nodes/Cockpit/SingletonDescription.ts | 29 +++++- .../nodes/Cockpit/SingletonFunctions.ts | 6 +- packages/nodes-base/nodes/Cockpit/cockpit.png | Bin 2944 -> 834 bytes 8 files changed, 201 insertions(+), 86 deletions(-) diff --git a/packages/nodes-base/nodes/Cockpit/Cockpit.node.ts b/packages/nodes-base/nodes/Cockpit/Cockpit.node.ts index 7680f85d9..350531f78 100644 --- a/packages/nodes-base/nodes/Cockpit/Cockpit.node.ts +++ b/packages/nodes-base/nodes/Cockpit/Cockpit.node.ts @@ -1,25 +1,34 @@ import { IExecuteFunctions } from 'n8n-core'; import { IDataObject, + ILoadOptionsFunctions, INodeExecutionData, + INodePropertyOptions, INodeType, - INodeTypeDescription + INodeTypeDescription, } from 'n8n-workflow'; import { collectionFields, - collectionOperations + collectionOperations, } from './CollectionDescription'; import { - getCollectionEntries, - saveCollectionEntry + createCollectionEntry, + getAllCollectionEntries, + getAllCollectionNames, } from './CollectionFunctions'; import { formFields, formOperations } from './FormDescription'; import { submitForm } from './FormFunctions'; -import { singletonOperations } from "./SingletonDescription"; -import { getSingleton } from "./SingletonFunctions"; +import { + singletonFields, + singletonOperations, +} from './SingletonDescription'; +import { + getAllSingleton, + getAllSingletonNames, +} from './SingletonFunctions'; export class Cockpit implements INodeType { description: INodeTypeDescription = { @@ -28,7 +37,7 @@ export class Cockpit implements INodeType { icon: 'file:cockpit.png', group: ['output'], version: 1, - subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"] + "/" + $parameter["resourceName"]}}', + subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}', description: 'Consume Cockpit API', defaults: { name: 'Cockpit', @@ -64,57 +73,81 @@ export class Cockpit implements INodeType { }, ], }, - { - displayName: 'Resource name', - name: 'resourceName', - type: 'string', - default: '', - required: true, - description: 'Name of resource to consume.' - }, + + ...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 resourceName = this.getNodeParameter('resourceName', 0) as string; const operation = this.getNodeParameter('operation', 0) as string; let responseData; for (let i = 0; i < length; i++) { if (resource === 'collections') { - if (operation === 'save') { + const collectionName = this.getNodeParameter('collection', i) as string; + if (operation === 'create') { const data = this.getNodeParameter('data', i) as IDataObject; - responseData = await saveCollectionEntry.call(this, resourceName, data); - } else if (operation === 'get') { - const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + responseData = await createCollectionEntry.call(this, collectionName, data); + } else if (operation === 'getAll') { + const options = this.getNodeParameter('options', i) as IDataObject; - responseData = await getCollectionEntries.call(this, resourceName, additionalFields); + 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 saveCollectionEntry.call(this, resourceName, data, id); + responseData = await createCollectionEntry.call(this, collectionName, data, id); } } else if (resource === 'forms') { + const formName = this.getNodeParameter('form', i) as string; if (operation === 'submit') { const form = this.getNodeParameter('form', i) as IDataObject; - responseData = await submitForm.call(this, resourceName, form); + responseData = await submitForm.call(this, formName, form); } } else if (resource === 'singletons') { - if (operation === 'get') { - responseData = await getSingleton.call(this, resourceName); + const singletonName = this.getNodeParameter('singleton', i) as string; + if (operation === 'getAll') { + responseData = await getAllSingleton.call(this, singletonName); } } diff --git a/packages/nodes-base/nodes/Cockpit/CollectionDescription.ts b/packages/nodes-base/nodes/Cockpit/CollectionDescription.ts index 71483c309..cdcba40d2 100644 --- a/packages/nodes-base/nodes/Cockpit/CollectionDescription.ts +++ b/packages/nodes-base/nodes/Cockpit/CollectionDescription.ts @@ -15,12 +15,12 @@ export const collectionOperations = [ options: [ { name: 'Create an entry', - value: 'save', + value: 'create', description: 'Create a collection entry', }, { name: 'Get all entries', - value: 'get', + value: 'getAll', description: 'Get all collection entries', }, { @@ -29,13 +29,32 @@ export const collectionOperations = [ description: 'Update a collection entries', }, ], - default: 'get', + default: 'getAll', description: 'The operation to perform.', - } + }, ] as INodeProperties[]; export const collectionFields = [ - // Collections:entry:save + { + displayName: 'Collection', + name: 'collection', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getCollections', + }, + displayOptions: { + show: { + resource: [ + 'collections', + ], + }, + }, + required: true, + description: 'Name of the collection to operate on.' + }, + + // Collections:entry:create { displayName: 'Data', name: 'data', @@ -51,19 +70,19 @@ export const collectionFields = [ 'collections', ], operation: [ - 'save', + 'create', ] }, }, - description: 'The data to save.', + description: 'The data to create.', }, - // Collections:entry:get + // Collections:entry:getAll { - displayName: 'Additional fields', - name: 'additionalFields', + displayName: 'Options', + name: 'options', type: 'collection', - placeholder: 'Add field', + placeholder: 'Add Option', default: {}, displayOptions: { show: { @@ -71,7 +90,7 @@ export const collectionFields = [ 'collections', ], operation: [ - 'get', + 'getAll', ] }, }, @@ -96,6 +115,13 @@ export const collectionFields = [ }, description: 'Filter result by fields.', }, + { + displayName: 'Language', + name: 'language', + type: 'string', + default: '', + description: 'Return normalized language fields.', + }, { displayName: 'Limit', name: 'limit', @@ -103,6 +129,29 @@ export const collectionFields = [ default: '', description: 'Limit number of returned entries.', }, + { + 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: 'Simple', + name: 'simple', + type: 'boolean', + required: true, + default: true, + description: 'Return only result entries.', + }, { displayName: 'Skip', name: 'skip', @@ -117,29 +166,6 @@ export const collectionFields = [ default: '', description: 'Sort result by fields.', }, - { - displayName: 'Populate', - name: 'populate', - type: 'boolean', - required: true, - default: true, - description: 'Resolve linked collection items.', - }, - { - displayName: 'Simple', - name: 'simple', - type: 'boolean', - required: true, - default: true, - description: 'Return only result entries.', - }, - { - displayName: 'Language', - name: 'language', - type: 'string', - default: '', - description: 'Return normalized language fields.', - }, ], }, diff --git a/packages/nodes-base/nodes/Cockpit/CollectionFunctions.ts b/packages/nodes-base/nodes/Cockpit/CollectionFunctions.ts index fff760ba0..b2b8531b0 100644 --- a/packages/nodes-base/nodes/Cockpit/CollectionFunctions.ts +++ b/packages/nodes-base/nodes/Cockpit/CollectionFunctions.ts @@ -7,7 +7,7 @@ import { IDataObject } from 'n8n-workflow'; import { ICollection } from './CollectionInterface'; import { cockpitApiRequest } from './GenericFunctions'; -export async function saveCollectionEntry(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resourceName: string, data: IDataObject, id?: string): Promise { // tslint:disable-line:no-any +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()) }; @@ -22,40 +22,52 @@ export async function saveCollectionEntry(this: IExecuteFunctions | IExecuteSing return cockpitApiRequest.call(this, 'post', `/collections/save/${resourceName}`, body); } -export async function getCollectionEntries(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resourceName: string, additionalFields: IDataObject): Promise { // tslint:disable-line:no-any + +export async function getAllCollectionEntries(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resourceName: string, options: IDataObject): Promise { // tslint:disable-line:no-any const body: ICollection = {}; - if (additionalFields.fields) { - body.fields = JSON.parse(additionalFields.fields.toString()); + if (options.fields) { + body.fields = JSON.parse(options.fields.toString()); } - if (additionalFields.filter) { - body.filter = JSON.parse(additionalFields.filter.toString()); + if (options.filter) { + body.filter = JSON.parse(options.filter.toString()); } - if (additionalFields.limit) { - body.limit = additionalFields.limit as number; + if (options.limit) { + body.limit = options.limit as number; } - if (additionalFields.skip) { - body.skip = additionalFields.skip as number; + if (options.skip) { + body.skip = options.skip as number; } - if (additionalFields.sort) { - body.sort = JSON.parse(additionalFields.sort.toString()); + if (options.sort) { + body.sort = JSON.parse(options.sort.toString()); } - if (additionalFields.populate) { - body.populate = additionalFields.populate as boolean; + if (options.populate) { + body.populate = options.populate as boolean; } - if (additionalFields.simple) { - body.simple = additionalFields.simple as boolean; + if (options.simple) { + body.simple = options.simple as boolean; } - if (additionalFields.language) { - body.lang = additionalFields.language as string; + if (options.language) { + body.lang = options.language as string; } - return cockpitApiRequest.call(this, 'post', `/collections/get/${resourceName}`, body); + const resultData = await cockpitApiRequest.call(this, 'post', `/collections/get/${resourceName}`, body); + + if (options.rawData === true) { + return resultData; + } + + return (resultData as unknown as IDataObject).entries; } + + +export async function getAllCollectionNames(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions): Promise { + return cockpitApiRequest.call(this, 'GET', `/collections/listCollections`, {}); +} \ No newline at end of file diff --git a/packages/nodes-base/nodes/Cockpit/FormDescription.ts b/packages/nodes-base/nodes/Cockpit/FormDescription.ts index cdb1266e3..8a1e6284c 100644 --- a/packages/nodes-base/nodes/Cockpit/FormDescription.ts +++ b/packages/nodes-base/nodes/Cockpit/FormDescription.ts @@ -26,6 +26,22 @@ export const formOperations = [ ] as INodeProperties[]; export const formFields = [ + { + displayName: 'Form', + name: 'form', + type: 'string', + displayOptions: { + show: { + resource: [ + 'forms', + ], + }, + }, + default: '', + required: true, + description: 'Name of the form to operate on.' + }, + // Forms:submit { displayName: 'Form data', diff --git a/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts b/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts index db37b57ab..ed923d3bd 100644 --- a/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts @@ -36,8 +36,11 @@ export async function cockpitApiRequest(this: IExecuteFunctions | IExecuteSingle try { return await this.helpers.request!(options); } catch (error) { - const errorMessage = error.error.message || error.error.error; + let errorMessage = error.message; + if (error.error) { + errorMessage = error.error.message || error.error.error; + } - throw new Error('Cockpit error: ' + errorMessage); + 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 index d10edafc3..402e23747 100644 --- a/packages/nodes-base/nodes/Cockpit/SingletonDescription.ts +++ b/packages/nodes-base/nodes/Cockpit/SingletonDescription.ts @@ -14,12 +14,33 @@ export const singletonOperations = [ }, options: [ { - name: 'Get data', - value: 'get', - description: 'Get singleton data', + name: 'Get All', + value: 'getAll', + description: 'Get all singletons', }, ], - default: 'get', + default: 'getAll', description: 'The operation to perform.', } ] as INodeProperties[]; + +export const singletonFields = [ + { + displayName: 'Singleton', + name: 'singleton', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getSingletons', + }, + displayOptions: { + show: { + resource: [ + 'singletons', + ], + }, + }, + required: true, + description: 'Name of the singleton to operate on.' + }, +] as INodeProperties[]; \ No newline at end of file diff --git a/packages/nodes-base/nodes/Cockpit/SingletonFunctions.ts b/packages/nodes-base/nodes/Cockpit/SingletonFunctions.ts index 5d07bb429..5a5cf2da0 100644 --- a/packages/nodes-base/nodes/Cockpit/SingletonFunctions.ts +++ b/packages/nodes-base/nodes/Cockpit/SingletonFunctions.ts @@ -5,6 +5,10 @@ import { } from 'n8n-core'; import { cockpitApiRequest } from './GenericFunctions'; -export async function getSingleton(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resourceName: string): Promise { // tslint:disable-line:no-any +export async function getAllSingleton(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`, {}); +} \ No newline at end of file diff --git a/packages/nodes-base/nodes/Cockpit/cockpit.png b/packages/nodes-base/nodes/Cockpit/cockpit.png index b99e95037c62c3a222fbb5a77e668e96ded6b9af..ddbe6ead67664a8d4cf86d0fc108d589541a93a3 100644 GIT binary patch literal 834 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw3=&b&bO2Ik0(?STfi&6R|NsAg|NdoTW4m(Y z%E5yNRa8_SJ$fW2CMF~##L3C|>C>kZCr&(g@W8~xL{n3c@{CFJ8R3 zef##tjT@Vqn!LQcbaZt1`1o$zxUqZp?k!ukELpOoy}dm%GczJ0!otErQBm>y`Sa(_ zom;zhZC_tsVPT=4pPzw&!P~cQw{PFRa^=d|vuDT0$D5j(a+m&&0(wTcB*-tAfqn11 z?_WN=y}NE=w7;8$k@Uq2FAqz|S%+Qq9+fkj-(UG^>8jVKXZrgd-&V4iD}?v%ChPZxTgzDg z2V`b1U*)^G?f;6IzHe2w7cnx5PZfSMjgPbH^^DsqoW1rI9@*Q{W+cExHx^5ZO= zH&L)gdCE!IqO9D7F>5MBCd)p)!a8Y&flOX_OKHfFx58fLE=8_fRW%P6No}4eyvb#~ zGmmZ20q&xl#+=4aX36XJcfY>SB0sO{X1OiXQK1u7Mq$@o&qdFxYw-Q{m2JTbrkf7O z^4q!M8d`St+c|9RtPS94j9J>a+vdiEw^wzT@?OMCH3o^EIPt@HY2kE_JvDph^}lG< zt330*Q+u+w$-QH#g>%oI+benbzU2-b@89#>-YtK#!$y9`+raP7xq4RR9KK^}negoM zSF4rWeTufLtkd7yxpm}yzArDxRq|3ba_!EyC7@+$Du)ZU=w j{DtdIx9u=aw`eBblD&-ZTMd&mtA3NSHT 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 From 86f0468e2be76bc939b3634e1fb4b90c93c3c2f9 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sun, 5 Apr 2020 21:06:23 -0400 Subject: [PATCH 3/8] :sparkles: PagerDuty node --- .../credentials/PagerDutyApi.credentials.ts | 17 + .../nodes/PagerDuty/GenericFunctions.ts | 94 +++ .../nodes/PagerDuty/IncidentDescription.ts | 642 ++++++++++++++++++ .../nodes/PagerDuty/IncidentInterface.ts | 19 + .../PagerDuty/IncidentNoteDescription.ts | 158 +++++ .../nodes/PagerDuty/LogEntryDescription.ts | 175 +++++ .../nodes/PagerDuty/PagerDuty.node.ts | 361 ++++++++++ .../nodes-base/nodes/PagerDuty/pagerDuty.png | Bin 0 -> 2687 bytes packages/nodes-base/package.json | 4 +- 9 files changed, 1469 insertions(+), 1 deletion(-) create mode 100644 packages/nodes-base/credentials/PagerDutyApi.credentials.ts create mode 100644 packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/PagerDuty/IncidentDescription.ts create mode 100644 packages/nodes-base/nodes/PagerDuty/IncidentInterface.ts create mode 100644 packages/nodes-base/nodes/PagerDuty/IncidentNoteDescription.ts create mode 100644 packages/nodes-base/nodes/PagerDuty/LogEntryDescription.ts create mode 100644 packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts create mode 100644 packages/nodes-base/nodes/PagerDuty/pagerDuty.png diff --git a/packages/nodes-base/credentials/PagerDutyApi.credentials.ts b/packages/nodes-base/credentials/PagerDutyApi.credentials.ts new file mode 100644 index 000000000..3acac2416 --- /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 000000000..1e6e20bdd --- /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 000000000..5971bc5e8 --- /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 000000000..5663d9a08 --- /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 000000000..c76152f17 --- /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 000000000..7164d3121 --- /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 000000000..6e887a1f9 --- /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 0000000000000000000000000000000000000000..340840bb36a4a5f14515fbd242cf40b1cc91021a GIT binary patch literal 2687 zcmY*bc|4Ts7k`H#YskKoG1XX-89Q^0atB$4>=%hK7(2s^UAV};#?1(2siADQ1=((+ z8tX-5x%RSFmMC|)h~G^0>;8Vv`#JCVKIeST_dMr$|9Mj_%rEhBNpJxGz-w%zZ^iJ2 zOygi@yn~eH6B!Qdsb{7K01vad4_w(8dx)Enl^Fm;!T=zSk>6v4;@$y32m$~;U;zM0 z1^`h)af_u6BN6UlZ;UrH0~8pT17HK409Y9a#CQOZ1i(~dAixA9`2$;l|g1reO@4x^EQ`Z$27(zlpq0B}<*0*z# zyz&2b3Lt(Ti!mUanSm=SA>co>8KpYRDDrZ!w+Ev$Q(sqE=PUF7V&D7dz?see=P=(U z{TgMAs>`JV|8Z@)T$tyw4vY`ZW2~=d6AoH0HYEz#iY3i$yl-|_afMxEmjSVr<($wL z&G+s5P2`?PRNtNQSZZu3caxGFSIn>6zvSDwM##(SZ~Quqem-3}wSzKt6QsOwGEd^! zmPgJI_vqIK|4~{g4w%%AJ&gXnrG;zoGd~#QyfUwK+_6?9jRw`xD=aB7&?qva@?+Ya zH5>KNTRiy~SYDirn6UEJqV5oFEh6(MQE_^l+|l?z7K__FgFS&z2IC$Grl*1yXd^o= zCAi+lnNyWoxSiS zpTtN-I`x%R@pX|gc>b0S$a@_K=)3e_GO1}SvB@GitTJoFg%_;- zNL&9QDn4*S3gU}p(V&=`MyQl@t!+UqJRi3T#k_*fOzD0~v~jG$&F{G_#yDS(YGpg} zy7g1U#q@LBO=)R9ycxOa8+ka>ijJ}6c|o2MNaVBIP2OmZ@GX;B^inMfC>U=3BIk0& z@UWL9Crop?reohe(Tz7_i2R5xSo$!J%~(brU(wwz<(O61WYvpXsz{K#Go8R%uyH!1 zBI^xc`;a4fp08dDyb|m;wqCyQ0@|%x=DUvAZ6S>|qZgiTjaoj@?S2uVAKOd5$#GfS z%YPi_3^Ad`i9ba>xlVm$BITjrm{wXPW_spK$Nj8`r$KKy%xjL)M^Nl5EcNv@8dz~I zv4&`BO~^aCMCY>aru9bBqu~M(_wAYK<6;E|eq6Odd=05;r$kk~7Ud|4nOmlIf+948 z$Fs?B3BG6Kj_sx+VBzg-#~2!fu6>7AQtg%Cn(HsWIzV->y{f-!;R3Qsf6OO{Y4(!t zD-Vri`RoaMs8X1K<}X`%=f|3SQ#BTmJ-lKqcYru{1b#dkcR$()tMP zk(Lat@jQmGQ32z6HsIFQmU0hX8MHF8NF-pWm970HxAkKpgRpWpC4gn&DkqWrdxt5aIh*VyzX)j>M>@;u$uq}IJ=6k*vb_ycgX4`wSbi(?cj~l%j%#vu~PzS zmDy2K5o$8U8#WVt#$OU_(*4-wD_D~peWZlfh∈W%J;MQAIB`TYAnH6?COj2v999 zkN70Q3u^lfrYhQ@FQhqzd2g1DPGqs3j61z4z*?d8vleDz$T?`lg9l>v3kMGDv`r3Z zyu_V>48v&YCD{#G26sY0xsu+`8ut}X!sk{RLJ!p(sDje#Jv<5Us6hc*zs8QIm)XW- z6=hXdpx2~$2^f75u>gY^T&ky$N~#I$%4=QvR0FD+kEx}h3( z$Pn%mw|cx~L9^uF3)$kE+q>L>t(D_9F@4EZtI}qQ*FyOlq9y8%*2p~-wo6TZAL_+FEm#+k5j%qZvH)GaFr9r}MfPrE zMjdVBt@f>z!o6#Sf_DDku1k25*?alu>U_h6*POd-jmOTHy16QDby<0dzOMQ>I3E;| z@tW6xSoQkuaPB{1wk>O*{X!uM{+!seHsI(le0|dbt+ie%jV4wvlTzfl1NumeuyhSo z&URijw{fyAR5)2ixLZ=&xY@b1AH5;8cSk$A^a2|6IU`Xy=>*CU;-yJ3xl;1n(o6o7 z?ENg4<~~nz_2Z}RXWir6dYh1XB4t8DuLT|JtY^3NvY>UzO78raGI8ltn~MSmWnC}V zKD48oRY)h3Wjf2Ut<@(luD`5oG%@eJb8Tkf1yRek?G!;5%+$@Yca$-z7@zCdkbV<> za8h^t%Z&-1-!ES^DlN=3F%8MzsH-4^h|gXY-u+0x2g0o%pWR8-Agd!Na7$87#55Hw zY@~U65HchgHhHt*JyMA^Bn={TVYs-~6t14r>~ei4On9KSu}5cpN2{!^wC3I%`@1tQ z@g?er6BZry;af5${S$|Ywr!(O?kWK*gS5{(VAjpM%MMPvp4RLsg_^TAI9WS7)d=~a ztn$|nGq=`bu2|LGHBDSuZNW^%Q5?#b{N^qml>RO3OxAmHgHasPRL&8Yv4;# z)}*(SpU!U3RsP6tAM4QaeG;ZBaB{EXD6+7r92@{nyNYlhMyhuX6x=qm-sh0iP7X^Z zp0y3g1f-&{BmQL#I=hSfH|g2AIncA-Dc!Kru-LZi+v;>H)3vCz;HF2%lJ=^rq;a#@ zH;zS=M9H_vnmL+ptKrF@lB+euU&<%gPN#<(y!ngmwe6LRPCZUCIn;!D28-i(X(KC= z7ndr-Z|&cd&}HMF#DbZM?9pBu^!B-tC!h>n8}Mnxs>LjxlXx?%k$tgh8$Z{HwVX-W ze0VI0%QxQV6CL@fnpYLoJStNB_htiq=5m*U7;01D+EE-_^EGK35|e&-SR@ZV$aYBh zL+h#VuK7XZF~i{=pIy1r(&cJT#+T6Pl-ImUZbu`)CsyhOP4h4xr T)_x@BFP5=^xqjWx*!cedtjLxV literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 908b5699f..3c0a330dc 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -74,13 +74,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", @@ -194,6 +195,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", From 7e17486383f1b8373c5a790b2b26fd4d960fd798 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 6 Apr 2020 10:03:49 +0200 Subject: [PATCH 4/8] :zap: Small improvements on Cockpit-Node --- .../nodes-base/nodes/Cockpit/Cockpit.node.ts | 27 ++++--- .../nodes/Cockpit/CollectionDescription.ts | 74 +++++++++++++------ .../nodes/Cockpit/CollectionFunctions.ts | 15 ++-- .../nodes/Cockpit/FormDescription.ts | 8 +- .../nodes/Cockpit/SingletonDescription.ts | 14 ++-- .../nodes/Cockpit/SingletonFunctions.ts | 4 +- 6 files changed, 84 insertions(+), 58 deletions(-) diff --git a/packages/nodes-base/nodes/Cockpit/Cockpit.node.ts b/packages/nodes-base/nodes/Cockpit/Cockpit.node.ts index 350531f78..bac4b2213 100644 --- a/packages/nodes-base/nodes/Cockpit/Cockpit.node.ts +++ b/packages/nodes-base/nodes/Cockpit/Cockpit.node.ts @@ -26,7 +26,7 @@ import { singletonOperations, } from './SingletonDescription'; import { - getAllSingleton, + getSingleton, getAllSingletonNames, } from './SingletonFunctions'; @@ -61,15 +61,15 @@ export class Cockpit implements INodeType { options: [ { name: 'Collection', - value: 'collections', + value: 'collection', }, { name: 'Form', - value: 'forms', + value: 'form', }, { name: 'Singleton', - value: 'singletons', + value: 'singleton', }, ], }, @@ -94,7 +94,7 @@ export class Cockpit implements INodeType { return { name: itemName, value: itemName, - } + }; }); }, @@ -105,7 +105,7 @@ export class Cockpit implements INodeType { return { name: itemName, value: itemName, - } + }; }); }, }, @@ -121,7 +121,7 @@ export class Cockpit implements INodeType { let responseData; for (let i = 0; i < length; i++) { - if (resource === 'collections') { + if (resource === 'collection') { const collectionName = this.getNodeParameter('collection', i) as string; if (operation === 'create') { const data = this.getNodeParameter('data', i) as IDataObject; @@ -129,6 +129,11 @@ export class Cockpit implements INodeType { 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') { @@ -137,17 +142,17 @@ export class Cockpit implements INodeType { responseData = await createCollectionEntry.call(this, collectionName, data, id); } - } else if (resource === 'forms') { + } 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 === 'singletons') { + } else if (resource === 'singleton') { const singletonName = this.getNodeParameter('singleton', i) as string; - if (operation === 'getAll') { - responseData = await getAllSingleton.call(this, singletonName); + if (operation === 'get') { + responseData = await getSingleton.call(this, singletonName); } } diff --git a/packages/nodes-base/nodes/Cockpit/CollectionDescription.ts b/packages/nodes-base/nodes/Cockpit/CollectionDescription.ts index cdcba40d2..e448ce670 100644 --- a/packages/nodes-base/nodes/Cockpit/CollectionDescription.ts +++ b/packages/nodes-base/nodes/Cockpit/CollectionDescription.ts @@ -8,7 +8,7 @@ export const collectionOperations = [ displayOptions: { show: { resource: [ - 'collections', + 'collection', ], }, }, @@ -46,7 +46,7 @@ export const collectionFields = [ displayOptions: { show: { resource: [ - 'collections', + 'collection', ], }, }, @@ -54,7 +54,7 @@ export const collectionFields = [ description: 'Name of the collection to operate on.' }, - // Collections:entry:create + // Collection:entry:create { displayName: 'Data', name: 'data', @@ -67,7 +67,7 @@ export const collectionFields = [ displayOptions: { show: { resource: [ - 'collections', + 'collection', ], operation: [ 'create', @@ -77,7 +77,48 @@ export const collectionFields = [ description: 'The data to create.', }, - // Collections:entry:getAll + // 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', @@ -87,7 +128,7 @@ export const collectionFields = [ displayOptions: { show: { resource: [ - 'collections', + 'collection', ], operation: [ 'getAll', @@ -122,13 +163,6 @@ export const collectionFields = [ default: '', description: 'Return normalized language fields.', }, - { - displayName: 'Limit', - name: 'limit', - type: 'number', - default: '', - description: 'Limit number of returned entries.', - }, { displayName: 'Populate', name: 'populate', @@ -144,14 +178,6 @@ export const collectionFields = [ default: false, description: `Returns the data exactly in the way it got received from the API.`, }, - { - displayName: 'Simple', - name: 'simple', - type: 'boolean', - required: true, - default: true, - description: 'Return only result entries.', - }, { displayName: 'Skip', name: 'skip', @@ -169,7 +195,7 @@ export const collectionFields = [ ], }, - // Collections:entry:update + // Collection:entry:update { displayName: 'Entry ID', name: 'id', @@ -179,7 +205,7 @@ export const collectionFields = [ displayOptions: { show: { resource: [ - 'collections', + 'collection', ], operation: [ 'update', @@ -200,7 +226,7 @@ export const collectionFields = [ displayOptions: { show: { resource: [ - 'collections', + 'collection', ], operation: [ 'update', diff --git a/packages/nodes-base/nodes/Cockpit/CollectionFunctions.ts b/packages/nodes-base/nodes/Cockpit/CollectionFunctions.ts index b2b8531b0..4a2c640f8 100644 --- a/packages/nodes-base/nodes/Cockpit/CollectionFunctions.ts +++ b/packages/nodes-base/nodes/Cockpit/CollectionFunctions.ts @@ -50,24 +50,19 @@ export async function getAllCollectionEntries(this: IExecuteFunctions | IExecute body.populate = options.populate as boolean; } - if (options.simple) { - body.simple = options.simple as boolean; + body.simple = true; + if (options.rawData) { + body.simple = !options.rawData as boolean; } if (options.language) { body.lang = options.language as string; } - const resultData = await cockpitApiRequest.call(this, 'post', `/collections/get/${resourceName}`, body); - - if (options.rawData === true) { - return resultData; - } - - return (resultData as unknown as IDataObject).entries; + 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`, {}); -} \ No newline at end of file +} diff --git a/packages/nodes-base/nodes/Cockpit/FormDescription.ts b/packages/nodes-base/nodes/Cockpit/FormDescription.ts index 8a1e6284c..8488cbe09 100644 --- a/packages/nodes-base/nodes/Cockpit/FormDescription.ts +++ b/packages/nodes-base/nodes/Cockpit/FormDescription.ts @@ -8,7 +8,7 @@ export const formOperations = [ displayOptions: { show: { resource: [ - 'forms', + 'form', ], }, }, @@ -33,7 +33,7 @@ export const formFields = [ displayOptions: { show: { resource: [ - 'forms', + 'form', ], }, }, @@ -42,7 +42,7 @@ export const formFields = [ description: 'Name of the form to operate on.' }, - // Forms:submit + // Form:submit { displayName: 'Form data', name: 'form', @@ -55,7 +55,7 @@ export const formFields = [ displayOptions: { show: { resource: [ - 'forms', + 'form', ], }, }, diff --git a/packages/nodes-base/nodes/Cockpit/SingletonDescription.ts b/packages/nodes-base/nodes/Cockpit/SingletonDescription.ts index 402e23747..e9774b4f5 100644 --- a/packages/nodes-base/nodes/Cockpit/SingletonDescription.ts +++ b/packages/nodes-base/nodes/Cockpit/SingletonDescription.ts @@ -8,18 +8,18 @@ export const singletonOperations = [ displayOptions: { show: { resource: [ - 'singletons', + 'singleton', ], }, }, options: [ { - name: 'Get All', - value: 'getAll', - description: 'Get all singletons', + name: 'Get', + value: 'get', + description: 'Gets a singleton', }, ], - default: 'getAll', + default: 'get', description: 'The operation to perform.', } ] as INodeProperties[]; @@ -36,11 +36,11 @@ export const singletonFields = [ displayOptions: { show: { resource: [ - 'singletons', + 'singleton', ], }, }, required: true, description: 'Name of the singleton to operate on.' }, -] as INodeProperties[]; \ No newline at end of file +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Cockpit/SingletonFunctions.ts b/packages/nodes-base/nodes/Cockpit/SingletonFunctions.ts index 5a5cf2da0..dab17f21f 100644 --- a/packages/nodes-base/nodes/Cockpit/SingletonFunctions.ts +++ b/packages/nodes-base/nodes/Cockpit/SingletonFunctions.ts @@ -5,10 +5,10 @@ import { } from 'n8n-core'; import { cockpitApiRequest } from './GenericFunctions'; -export async function getAllSingleton(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resourceName: string): Promise { // tslint:disable-line:no-any +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`, {}); -} \ No newline at end of file +} From 931b73e1bd038a5782886e8a066d550dc75ff344 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 6 Apr 2020 10:15:46 +0200 Subject: [PATCH 5/8] :zap: Display "options" parameter as "string" if options could not be loaded --- packages/editor-ui/src/components/ParameterInput.vue | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/editor-ui/src/components/ParameterInput.vue b/packages/editor-ui/src/components/ParameterInput.vue index 9f65d462e..893c61aa3 100644 --- a/packages/editor-ui/src/components/ParameterInput.vue +++ b/packages/editor-ui/src/components/ParameterInput.vue @@ -2,7 +2,7 @@
-
+
@@ -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 From 367b5aa29bba4a540514724feb9f831cb4720d1b Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Mon, 6 Apr 2020 20:07:32 +0200 Subject: [PATCH 6/8] :zap: Minor improvements to PagerDuty-Node --- .../nodes/PagerDuty/GenericFunctions.ts | 4 +--- .../nodes/PagerDuty/IncidentDescription.ts | 2 +- .../nodes/PagerDuty/IncidentInterface.ts | 2 +- .../nodes/PagerDuty/IncidentNoteDescription.ts | 2 +- .../nodes/PagerDuty/PagerDuty.node.ts | 10 +++++----- .../nodes-base/nodes/PagerDuty/pagerDuty.png | Bin 2687 -> 330 bytes 6 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts b/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts index 1e6e20bdd..c36011414 100644 --- a/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts +++ b/packages/nodes-base/nodes/PagerDuty/GenericFunctions.ts @@ -62,12 +62,11 @@ export async function pagerDutyApiRequestAllItems(this: IExecuteFunctions | ILoa const returnData: IDataObject[] = []; let responseData; - let uri; query.limit = 100; query.offset = 0; do { - responseData = await pagerDutyApiRequest.call(this, method, endpoint, body, query, uri); + responseData = await pagerDutyApiRequest.call(this, method, endpoint, body, query); query.offset++; returnData.push.apply(returnData, responseData[propertyName]); } while ( @@ -91,4 +90,3 @@ export function keysToSnakeCase(elements: IDataObject[] | IDataObject) : IDataOb } return elements; } - diff --git a/packages/nodes-base/nodes/PagerDuty/IncidentDescription.ts b/packages/nodes-base/nodes/PagerDuty/IncidentDescription.ts index 5971bc5e8..10c4e4b13 100644 --- a/packages/nodes-base/nodes/PagerDuty/IncidentDescription.ts +++ b/packages/nodes-base/nodes/PagerDuty/IncidentDescription.ts @@ -1,6 +1,6 @@ import { INodeProperties, - } from 'n8n-workflow'; +} from 'n8n-workflow'; export const incidentOperations = [ { diff --git a/packages/nodes-base/nodes/PagerDuty/IncidentInterface.ts b/packages/nodes-base/nodes/PagerDuty/IncidentInterface.ts index 5663d9a08..d8dc0082c 100644 --- a/packages/nodes-base/nodes/PagerDuty/IncidentInterface.ts +++ b/packages/nodes-base/nodes/PagerDuty/IncidentInterface.ts @@ -1,6 +1,6 @@ import { IDataObject, - } from "n8n-workflow"; +} from 'n8n-workflow'; export interface IIncident { assignments?: IDataObject[]; diff --git a/packages/nodes-base/nodes/PagerDuty/IncidentNoteDescription.ts b/packages/nodes-base/nodes/PagerDuty/IncidentNoteDescription.ts index c76152f17..4ffc85b6d 100644 --- a/packages/nodes-base/nodes/PagerDuty/IncidentNoteDescription.ts +++ b/packages/nodes-base/nodes/PagerDuty/IncidentNoteDescription.ts @@ -1,6 +1,6 @@ import { INodeProperties, - } from 'n8n-workflow'; +} from 'n8n-workflow'; export const incidentNoteOperations = [ { diff --git a/packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts b/packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts index 6e887a1f9..9d2b9409c 100644 --- a/packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts +++ b/packages/nodes-base/nodes/PagerDuty/PagerDuty.node.ts @@ -5,16 +5,16 @@ import { import { IDataObject, ILoadOptionsFunctions, - INodeTypeDescription, INodeExecutionData, - INodeType, INodePropertyOptions, + INodeType, + INodeTypeDescription, } from 'n8n-workflow'; import { + keysToSnakeCase, pagerDutyApiRequest, pagerDutyApiRequestAllItems, - keysToSnakeCase, } from './GenericFunctions'; import { @@ -30,7 +30,7 @@ import { import { logEntryFields, logEntryOperations, -} from './logEntryDescription'; +} from './LogEntryDescription'; import { IIncident, @@ -345,7 +345,7 @@ export class PagerDuty implements INodeType { 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 = await pagerDutyApiRequest.call(this, 'GET', '/log_entries', {}, qs); responseData = responseData.log_entries; } } diff --git a/packages/nodes-base/nodes/PagerDuty/pagerDuty.png b/packages/nodes-base/nodes/PagerDuty/pagerDuty.png index 340840bb36a4a5f14515fbd242cf40b1cc91021a..a54f9064092144a95239ba4bf23abf3b47bd259c 100644 GIT binary patch literal 330 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw3=&b&bO2Hw0X`wFKw4j4pJ7?JiHQkUkDqjs zgPOZJ%iIw5X+a91HX2T*JRQE08BYB5UNUj^jEh2r%iS$3EI1|xXjvMG<+&*NTj`h? z^0oNXw`~3cG>fq$$S;_|;n|HeAZLT8i(`n#@we9w@--_6xL#~hvT$$=`2D{=>YkJN zTc=}7I5o@7yC)~OJiPdO^R$Oar^RhLx_M>m6K3^w6zR%6jWmc$)pLv5XxKINhW*4Y zH>L3Q^GnZ3X`kom*%okYX7j}3!NnW3A6Kxe>&2bUm|5Xp5$?0+)TJYbo+M6Qap>5h zKyMMXfA{!iCnWnXV%q;@;>N&0?f)C1BP^c-t+9L17k$9u#kX(U(w%oa61x8S?=thM XHr~YNZ>)KMZfEdx^>bP0l+XkKj}?2y literal 2687 zcmY*bc|4Ts7k`H#YskKoG1XX-89Q^0atB$4>=%hK7(2s^UAV};#?1(2siADQ1=((+ z8tX-5x%RSFmMC|)h~G^0>;8Vv`#JCVKIeST_dMr$|9Mj_%rEhBNpJxGz-w%zZ^iJ2 zOygi@yn~eH6B!Qdsb{7K01vad4_w(8dx)Enl^Fm;!T=zSk>6v4;@$y32m$~;U;zM0 z1^`h)af_u6BN6UlZ;UrH0~8pT17HK409Y9a#CQOZ1i(~dAixA9`2$;l|g1reO@4x^EQ`Z$27(zlpq0B}<*0*z# zyz&2b3Lt(Ti!mUanSm=SA>co>8KpYRDDrZ!w+Ev$Q(sqE=PUF7V&D7dz?see=P=(U z{TgMAs>`JV|8Z@)T$tyw4vY`ZW2~=d6AoH0HYEz#iY3i$yl-|_afMxEmjSVr<($wL z&G+s5P2`?PRNtNQSZZu3caxGFSIn>6zvSDwM##(SZ~Quqem-3}wSzKt6QsOwGEd^! zmPgJI_vqIK|4~{g4w%%AJ&gXnrG;zoGd~#QyfUwK+_6?9jRw`xD=aB7&?qva@?+Ya zH5>KNTRiy~SYDirn6UEJqV5oFEh6(MQE_^l+|l?z7K__FgFS&z2IC$Grl*1yXd^o= zCAi+lnNyWoxSiS zpTtN-I`x%R@pX|gc>b0S$a@_K=)3e_GO1}SvB@GitTJoFg%_;- zNL&9QDn4*S3gU}p(V&=`MyQl@t!+UqJRi3T#k_*fOzD0~v~jG$&F{G_#yDS(YGpg} zy7g1U#q@LBO=)R9ycxOa8+ka>ijJ}6c|o2MNaVBIP2OmZ@GX;B^inMfC>U=3BIk0& z@UWL9Crop?reohe(Tz7_i2R5xSo$!J%~(brU(wwz<(O61WYvpXsz{K#Go8R%uyH!1 zBI^xc`;a4fp08dDyb|m;wqCyQ0@|%x=DUvAZ6S>|qZgiTjaoj@?S2uVAKOd5$#GfS z%YPi_3^Ad`i9ba>xlVm$BITjrm{wXPW_spK$Nj8`r$KKy%xjL)M^Nl5EcNv@8dz~I zv4&`BO~^aCMCY>aru9bBqu~M(_wAYK<6;E|eq6Odd=05;r$kk~7Ud|4nOmlIf+948 z$Fs?B3BG6Kj_sx+VBzg-#~2!fu6>7AQtg%Cn(HsWIzV->y{f-!;R3Qsf6OO{Y4(!t zD-Vri`RoaMs8X1K<}X`%=f|3SQ#BTmJ-lKqcYru{1b#dkcR$()tMP zk(Lat@jQmGQ32z6HsIFQmU0hX8MHF8NF-pWm970HxAkKpgRpWpC4gn&DkqWrdxt5aIh*VyzX)j>M>@;u$uq}IJ=6k*vb_ycgX4`wSbi(?cj~l%j%#vu~PzS zmDy2K5o$8U8#WVt#$OU_(*4-wD_D~peWZlfh∈W%J;MQAIB`TYAnH6?COj2v999 zkN70Q3u^lfrYhQ@FQhqzd2g1DPGqs3j61z4z*?d8vleDz$T?`lg9l>v3kMGDv`r3Z zyu_V>48v&YCD{#G26sY0xsu+`8ut}X!sk{RLJ!p(sDje#Jv<5Us6hc*zs8QIm)XW- z6=hXdpx2~$2^f75u>gY^T&ky$N~#I$%4=QvR0FD+kEx}h3( z$Pn%mw|cx~L9^uF3)$kE+q>L>t(D_9F@4EZtI}qQ*FyOlq9y8%*2p~-wo6TZAL_+FEm#+k5j%qZvH)GaFr9r}MfPrE zMjdVBt@f>z!o6#Sf_DDku1k25*?alu>U_h6*POd-jmOTHy16QDby<0dzOMQ>I3E;| z@tW6xSoQkuaPB{1wk>O*{X!uM{+!seHsI(le0|dbt+ie%jV4wvlTzfl1NumeuyhSo z&URijw{fyAR5)2ixLZ=&xY@b1AH5;8cSk$A^a2|6IU`Xy=>*CU;-yJ3xl;1n(o6o7 z?ENg4<~~nz_2Z}RXWir6dYh1XB4t8DuLT|JtY^3NvY>UzO78raGI8ltn~MSmWnC}V zKD48oRY)h3Wjf2Ut<@(luD`5oG%@eJb8Tkf1yRek?G!;5%+$@Yca$-z7@zCdkbV<> za8h^t%Z&-1-!ES^DlN=3F%8MzsH-4^h|gXY-u+0x2g0o%pWR8-Agd!Na7$87#55Hw zY@~U65HchgHhHt*JyMA^Bn={TVYs-~6t14r>~ei4On9KSu}5cpN2{!^wC3I%`@1tQ z@g?er6BZry;af5${S$|Ywr!(O?kWK*gS5{(VAjpM%MMPvp4RLsg_^TAI9WS7)d=~a ztn$|nGq=`bu2|LGHBDSuZNW^%Q5?#b{N^qml>RO3OxAmHgHasPRL&8Yv4;# z)}*(SpU!U3RsP6tAM4QaeG;ZBaB{EXD6+7r92@{nyNYlhMyhuX6x=qm-sh0iP7X^Z zp0y3g1f-&{BmQL#I=hSfH|g2AIncA-Dc!Kru-LZi+v;>H)3vCz;HF2%lJ=^rq;a#@ zH;zS=M9H_vnmL+ptKrF@lB+euU&<%gPN#<(y!ngmwe6LRPCZUCIn;!D28-i(X(KC= z7ndr-Z|&cd&}HMF#DbZM?9pBu^!B-tC!h>n8}Mnxs>LjxlXx?%k$tgh8$Z{HwVX-W ze0VI0%QxQV6CL@fnpYLoJStNB_htiq=5m*U7;01D+EE-_^EGK35|e&-SR@ZV$aYBh zL+h#VuK7XZF~i{=pIy1r(&cJT#+T6Pl-ImUZbu`)CsyhOP4h4xr T)_x@BFP5=^xqjWx*!cedtjLxV From a00c9e0efb4c9be60c62650f646200d193b7df64 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 8 Apr 2020 08:43:16 +0200 Subject: [PATCH 7/8] :bug: Fix issue that connections did not get closed --- packages/nodes-base/nodes/MySql/MySql.node.ts | 3 +++ packages/nodes-base/nodes/Postgres/Postgres.node.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/packages/nodes-base/nodes/MySql/MySql.node.ts b/packages/nodes-base/nodes/MySql/MySql.node.ts index 04052789d..0aa6ab372 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/Postgres/Postgres.node.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.ts index f9db1a01e..2fa010576 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!`); } From 7a27979ddd5a129417e523fd90bfcc241639cee3 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 8 Apr 2020 10:08:42 +0200 Subject: [PATCH 8/8] :zap: Fix build issue with new interfaces --- packages/cli/src/Server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 2664dcf5a..d71ada00e 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -209,8 +209,8 @@ class App { }); } - jwt.verify(token, getKey, {}, (err: Error, decoded: object) => { - if (err) return ResponseHelper.jwtAuthAuthorizationError(res, "Invalid token"); + jwt.verify(token, getKey, {}, (err: jwt.VerifyErrors, decoded: object) => { + if (err) return ResponseHelper.jwtAuthAuthorizationError(res, 'Invalid token'); next(); });