From 52cbd323f2f3f9141c1577527596bd32871b807a Mon Sep 17 00:00:00 2001 From: Krzysztof Janda Date: Sat, 4 Apr 2020 16:04:25 +0200 Subject: [PATCH] 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 0000000000..fcc76f4ef5 --- /dev/null +++ b/packages/nodes-base/credentials/CockpitApi.credentials.ts @@ -0,0 +1,24 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class CockpitApi implements ICredentialType { + name = 'cockpitApi'; + displayName = 'Cockpit API'; + properties = [ + { + displayName: 'Cockpit URL', + name: 'url', + type: 'string' as NodePropertyTypes, + default: '', + placeholder: 'https://example.com', + }, + { + displayName: 'Access Token', + name: 'accessToken', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Cockpit/Cockpit.node.ts b/packages/nodes-base/nodes/Cockpit/Cockpit.node.ts new file mode 100644 index 0000000000..7680f85d94 --- /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 0000000000..71483c3095 --- /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 0000000000..fff760ba02 --- /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 0000000000..834cbbb3b7 --- /dev/null +++ b/packages/nodes-base/nodes/Cockpit/CollectionInterface.ts @@ -0,0 +1,11 @@ +export interface ICollection { + fields?: object; + filter?: object; + limit?: number; + skip?: number; + sort?: object; + populate?: boolean; + simple?: boolean; + lang?: string; + data?: object; +} diff --git a/packages/nodes-base/nodes/Cockpit/FormDescription.ts b/packages/nodes-base/nodes/Cockpit/FormDescription.ts new file mode 100644 index 0000000000..cdb1266e38 --- /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 0000000000..437ed210a0 --- /dev/null +++ b/packages/nodes-base/nodes/Cockpit/FormFunctions.ts @@ -0,0 +1,16 @@ +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; +import { IForm } from './FormInterface'; +import { cockpitApiRequest } from './GenericFunctions'; + +export async function submitForm(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resourceName: string, form: IDataObject) { + const body: IForm = { + form: JSON.parse(form.toString()) + }; + + return cockpitApiRequest.call(this, 'post', `/forms/submit/${resourceName}`, body); +} diff --git a/packages/nodes-base/nodes/Cockpit/FormInterface.ts b/packages/nodes-base/nodes/Cockpit/FormInterface.ts new file mode 100644 index 0000000000..d1c218ae9d --- /dev/null +++ b/packages/nodes-base/nodes/Cockpit/FormInterface.ts @@ -0,0 +1,3 @@ +export interface IForm { + form: object; +} diff --git a/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts b/packages/nodes-base/nodes/Cockpit/GenericFunctions.ts new file mode 100644 index 0000000000..db37b57ab9 --- /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 0000000000..d10edafc30 --- /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 0000000000..5d07bb4290 --- /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 908b5699f9..e197bd6ca4 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",