diff --git a/packages/nodes-base/nodes/Cockpit/Cockpit.node.ts b/packages/nodes-base/nodes/Cockpit/Cockpit.node.ts index 7680f85d94..350531f78f 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 71483c3095..cdcba40d2c 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 fff760ba02..b2b8531b0f 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 cdb1266e38..8a1e6284c7 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 db37b57ab9..ed923d3bda 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 d10edafc30..402e23747a 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 5d07bb4290..5a5cf2da0e 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 b99e95037c..ddbe6ead67 100644 Binary files a/packages/nodes-base/nodes/Cockpit/cockpit.png and b/packages/nodes-base/nodes/Cockpit/cockpit.png differ