From f18fc9d9102bb6a334c898cb07bacaf2067c6250 Mon Sep 17 00:00:00 2001 From: Romain Dunand Date: Sun, 10 Nov 2019 01:54:25 +0100 Subject: [PATCH 01/67] Auth & get records management --- .../credentials/FileMaker.credentials.ts | 39 ++ .../nodes/FileMaker/FileMaker.node.ts | 421 ++++++++++++++++++ .../nodes/FileMaker/GenericFunctions.ts | 185 ++++++++ .../nodes-base/nodes/FileMaker/filemaker.png | Bin 0 -> 16244 bytes packages/nodes-base/nodes/HttpRequest.node.ts | 1 - packages/nodes-base/package.json | 2 + 6 files changed, 647 insertions(+), 1 deletion(-) create mode 100644 packages/nodes-base/credentials/FileMaker.credentials.ts create mode 100644 packages/nodes-base/nodes/FileMaker/FileMaker.node.ts create mode 100644 packages/nodes-base/nodes/FileMaker/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/FileMaker/filemaker.png diff --git a/packages/nodes-base/credentials/FileMaker.credentials.ts b/packages/nodes-base/credentials/FileMaker.credentials.ts new file mode 100644 index 0000000000..3d0e67b7ca --- /dev/null +++ b/packages/nodes-base/credentials/FileMaker.credentials.ts @@ -0,0 +1,39 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class FileMaker implements ICredentialType { + name = 'FileMaker'; + displayName = 'FileMaker'; + properties = [ + { + displayName: 'Host', + name: 'host', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Database', + name: 'db', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Login', + name: 'login', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Password', + name: 'password', + type: 'string' as NodePropertyTypes, + typeOptions: { + password: true, + }, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts new file mode 100644 index 0000000000..8caaca47d8 --- /dev/null +++ b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts @@ -0,0 +1,421 @@ +import {IExecuteFunctions} from 'n8n-core'; +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, INodePropertyOptions, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + + +import {OptionsWithUri} from 'request'; +import {layoutsApiRequest, getFields, getToken, logout} from "./GenericFunctions"; + +export class FileMaker implements INodeType { + description: INodeTypeDescription = { + displayName: 'FileMaker', + name: 'filemaker', + icon: 'file:filemaker.png', + group: ['input'], + version: 1, + description: 'Retrieve data from FileMaker data API.', + defaults: { + name: 'FileMaker', + color: '#665533', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'FileMaker', + required: true, + }, + ], + properties: [ + { + displayName: 'Action', + name: 'action', + type: 'options', + options: [ + /*{ + name: 'Login', + value: 'login', + }, + { + name: 'Logout', + value: 'logout', + },*/ + { + name: 'Find Records', + value: 'find', + }, + { + name: 'get Records', + value: 'records', + }, + { + name: 'Get Records By Id', + value: 'record', + }, + { + name: 'Perform Script', + value: 'performscript', + }, + { + name: 'Create Record', + value: 'create', + }, + { + name: 'Edit Record', + value: 'edit', + }, + { + name: 'Duplicate Record', + value: 'duplicate', + }, + { + name: 'Delete Record', + value: 'delete', + }, + ], + default: 'login', + description: 'Action to perform.', + }, + + // ---------------------------------- + // shared + // ---------------------------------- + { + displayName: 'Layout', + name: 'layout', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLayouts', + }, + options: [], + default: '', + required: true, + displayOptions: { + hide: { + action: [ + 'performscript' + ], + }, + }, + placeholder: 'Layout Name', + description: 'FileMaker Layout Name.', + }, + { + displayName: 'Record Id', + name: 'recid', + type: 'number', + default: '', + required: true, + displayOptions: { + show: { + action: [ + 'record', + 'edit', + 'delete', + 'duplicate', + ], + }, + }, + placeholder: 'Record ID', + description: 'Internal Record ID returned by get (recordid)', + }, + // ---------------------------------- + // find/records + // ---------------------------------- + { + displayName: 'offset', + name: 'offset', + placeholder: '0', + description: 'The record number of the first record in the range of records.', + type: 'number', + default: '1', + displayOptions: { + show: { + action: [ + 'find', + 'records', + ], + }, + } + }, + { + displayName: 'limit', + name: 'limit', + placeholder: '100', + description: 'The maximum number of records that should be returned. If not specified, the default value is 100.', + type: 'number', + default: '100', + displayOptions: { + show: { + action: [ + 'find', + 'records', + ], + }, + } + }, + { + displayName: 'Sort', + name: 'sortParametersUi', + placeholder: 'Add Sort Rules', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + action: [ + 'find', + 'records', + ], + }, + }, + description: 'Sort rules', + default: {}, + options: [ + { + name: 'rules', + displayName: 'Rules', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getFields', + }, + options: [], + description: 'Field Name.', + }, + { + displayName: 'Value', + name: 'value', + type: 'options', + default: 'ascend', + options: [ + { + name: 'Ascend', + value: 'ascend' + }, + { + name: 'Descend', + value: 'descend' + }, + ], + description: 'Sort order.', + }, + ] + }, + ], + }, + // ---------------------------------- + // create/edit + // ---------------------------------- + { + displayName: 'fieldData', + name: 'fieldData', + placeholder: '{"field1": "value", "field2": "value", ...}', + description: 'Additional fields to add.', + type: 'string', + default: '{}', + displayOptions: { + show: { + action: [ + 'create', + 'edit', + ], + }, + } + }, + { + displayName: 'Fields', + name: 'Fields', + type: 'collection', + typeOptions: { + loadOptionsMethod: 'getFields', + }, + options: [], + default: '', + required: true, + displayOptions: { + show: { + action: [ + 'create', + 'edit', + ], + }, + }, + placeholder: 'Layout Name', + description: 'FileMaker Layout Name.', + }, + // ---------------------------------- + // performscript + // ---------------------------------- + { + displayName: 'Script Name', + name: 'script', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getScripts', + }, + options: [], + default: '', + required: true, + displayOptions: { + show: { + action: [ + 'performscript' + ], + }, + }, + placeholder: 'Layout Name', + description: 'FileMaker Layout Name.', + }, + ] + }; + + methods = { + loadOptions: { + // Get all the available topics to display them to user so that he can + // select them easily + async getLayouts(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let layouts; + try { + layouts = await layoutsApiRequest.call(this); + } catch (err) { + throw new Error(`FileMaker Error: ${err}`); + } + for (const layout of layouts) { + returnData.push({ + name: layout.name, + value: layout.name, + }); + } + return returnData; + }, + + async getFields(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + + let fields; + try { + fields = await getFields.call(this); + } catch (err) { + throw new Error(`FileMaker Error: ${err}`); + } + for (const field of fields) { + returnData.push({ + name: field.name, + value: field.name, + }); + } + return returnData; + }, + }, + }; + + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: INodeExecutionData[] = []; + + const credentials = this.getCredentials('FileMaker'); + + const action = this.getNodeParameter('action', 0) as string; + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + const staticData = this.getWorkflowStaticData('global'); + // Operations which overwrite the returned data + const overwriteDataOperations = []; + // Operations which overwrite the returned data and return arrays + // and has so to be merged with the data of other items + const overwriteDataOperationsArray = []; + + let requestOptions: OptionsWithUri; + + const host = credentials.host as string; + const database = credentials.db as string; + + //const layout = this.getNodeParameter('layout', 0, null) as string; + //const recid = this.getNodeParameter('recid', 0, null) as number; + + const url = `https://${host}/fmi/data/v1`; + //const fullOperation = `${resource}:${operation}`; + + for (let i = 0; i < items.length; i++) { + // Reset all values + requestOptions = { + uri: '', + headers: {}, + method: 'GET', + json: true + //rejectUnauthorized: !this.getNodeParameter('allowUnauthorizedCerts', itemIndex, false) as boolean, + }; + + const layout = this.getNodeParameter('layout', 0) as string; + const token = await getToken.call(this); + + if (action === 'record') { + const recid = this.getNodeParameter('recid', 0) as string; + + requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records/${recid}`; + requestOptions.method = 'GET'; + requestOptions.headers = { + 'Authorization': `Bearer ${token}`, + }; + } else if (action === 'records') { + requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records`; + requestOptions.method = 'GET'; + requestOptions.headers = { + 'Authorization': `Bearer ${token}`, + }; + + const sort = []; + const sortParametersUi = this.getNodeParameter('sortParametersUi', 0, {}) as IDataObject; + if (sortParametersUi.parameter !== undefined) { + // @ts-ignore + for (const parameterData of sortParametersUi!.rules as IDataObject[]) { + // @ts-ignore + sort.push({ + 'fieldName': parameterData!.name as string, + 'sortOrder': parameterData!.value + }); + } + } + requestOptions.qs = { + '_offset': this.getNodeParameter('offset', 0), + '_limit': this.getNodeParameter('limit', 0), + '_sort': JSON.stringify(sort), + }; + } else { + throw new Error(`The action "${action}" is not implemented yet!`); + } + + // Now that the options are all set make the actual http request + let response; + try { + response = await this.helpers.request(requestOptions); + } catch (error) { + response = error.response.body; + } + + if (typeof response === 'string') { + throw new Error('Response body is not valid JSON. Change "Response Format" to "String"'); + } + await logout.call(this, token); + + returnData.push({json: response}); + } + + return this.prepareOutputData(returnData); + } +} \ No newline at end of file diff --git a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts new file mode 100644 index 0000000000..c76350e1f4 --- /dev/null +++ b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts @@ -0,0 +1,185 @@ +import { + IExecuteFunctions, + ILoadOptionsFunctions, + IExecuteSingleFunctions +} from 'n8n-core'; + +import { + ICredentialDataDecryptedObject, + IDataObject, +} from 'n8n-workflow'; + +import { OptionsWithUri } from 'request'; + + +/** + * Make an API request to ActiveCampaign + * + * @param {IHookFunctions} this + * @param {string} method + * @returns {Promise} + */ +export async function layoutsApiRequest(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions): Promise { // tslint:disable-line:no-any + const token = await getToken.call(this); + const credentials = this.getCredentials('FileMaker'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + const host = credentials.host as string; + const db = credentials.db as string; + + const url = `https://${host}/fmi/data/v1/databases/${db}/layouts`; + const options: OptionsWithUri = { + headers: { + 'Authorization': `Bearer ${token}`, + }, + method: 'GET', + uri: url, + json: true + }; + + try { + const responseData = await this.helpers.request!(options); + return responseData.response.layouts; + + } catch (error) { + // If that data does not exist for some reason return the actual error + throw error; + } +} + +/** + * Make an API request to ActiveCampaign + * + * @returns {Promise} + * @param layout + */ +export async function getFields(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions): Promise { // tslint:disable-line:no-any + const token = await getToken.call(this); + const credentials = this.getCredentials('FileMaker'); + const layout = this.getCurrentNodeParameter('layout') as string; + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + const host = credentials.host as string; + const db = credentials.db as string; + + const url = `https://${host}/fmi/data/v1/databases/${db}/layouts/${layout}`; + const options: OptionsWithUri = { + headers: { + 'Authorization': `Bearer ${token}`, + }, + method: 'GET', + uri: url, + json: true + }; + + try { + const responseData = await this.helpers.request!(options); + return responseData.response.fieldMetaData; + + } catch (error) { + // If that data does not exist for some reason return the actual error + throw error; + } +} + +export async function getToken(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('FileMaker'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const host = credentials.host as string; + const db = credentials.db as string; + const login = credentials.login as string; + const password = credentials.password as string; + + const url = `https://${host}/fmi/data/v1/databases/${db}/sessions`; + + let requestOptions: OptionsWithUri; + // Reset all values + requestOptions = { + uri: url, + headers: {}, + method: 'POST', + json: true + //rejectUnauthorized: !this.getNodeParameter('allowUnauthorizedCerts', itemIndex, false) as boolean, + }; + requestOptions.auth = { + user: login as string, + pass: password as string, + }; + requestOptions.body = { + "fmDataSource": [ + { + "database": host, + "username": login as string, + "password": password as string + } + ] + }; + + try { + const response = await this.helpers.request!(requestOptions); + + if (typeof response === 'string') { + throw new Error('Response body is not valid JSON. Change "Response Format" to "String"'); + } + + return response.response.token; + } catch (error) { + console.error(error); + + const errorMessage = error.response.body.messages[0].message + '(' + error.response.body.messages[0].message + ')'; + + if (errorMessage !== undefined) { + throw errorMessage; + } + throw error.response.body; + } +} + +export async function logout(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions, token: string): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('FileMaker'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const host = credentials.host as string; + const db = credentials.db as string; + + const url = `https://${host}/fmi/data/v1/databases/${db}/sessions/${token}`; + + let requestOptions: OptionsWithUri; + // Reset all values + requestOptions = { + uri: url, + headers: {}, + method: 'DELETE', + json: true + //rejectUnauthorized: !this.getNodeParameter('allowUnauthorizedCerts', itemIndex, false) as boolean, + }; + + try { + const response = await this.helpers.request!(requestOptions); + + if (typeof response === 'string') { + throw new Error('Response body is not valid JSON. Change "Response Format" to "String"'); + } + + return response; + } catch (error) { + console.error(error); + + const errorMessage = error.response.body.messages[0].message + '(' + error.response.body.messages[0].message + ')'; + + if (errorMessage !== undefined) { + throw errorMessage; + } + throw error.response.body; + } +} + diff --git a/packages/nodes-base/nodes/FileMaker/filemaker.png b/packages/nodes-base/nodes/FileMaker/filemaker.png new file mode 100644 index 0000000000000000000000000000000000000000..ec691433dac024e2f3596fc5f9c310e5acc4c3a1 GIT binary patch literal 16244 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 Date: Sun, 10 Nov 2019 03:14:10 +0100 Subject: [PATCH 02/67] Handle portal selection & improve UX --- .../nodes/FileMaker/FileMaker.node.ts | 112 +++++++++++++++--- .../nodes/FileMaker/GenericFunctions.ts | 40 ++++++- 2 files changed, 134 insertions(+), 18 deletions(-) diff --git a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts index 8caaca47d8..e7435fe15f 100644 --- a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts +++ b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts @@ -9,7 +9,7 @@ import { import {OptionsWithUri} from 'request'; -import {layoutsApiRequest, getFields, getToken, logout} from "./GenericFunctions"; +import {layoutsApiRequest, getFields, getPortals, getToken, logout} from "./GenericFunctions"; export class FileMaker implements INodeType { description: INodeTypeDescription = { @@ -36,6 +36,7 @@ export class FileMaker implements INodeType { displayName: 'Action', name: 'action', type: 'options', + default: 'record', options: [ /*{ name: 'Login', @@ -78,7 +79,6 @@ export class FileMaker implements INodeType { value: 'delete', }, ], - default: 'login', description: 'Action to perform.', }, @@ -124,9 +124,7 @@ export class FileMaker implements INodeType { placeholder: 'Record ID', description: 'Internal Record ID returned by get (recordid)', }, - // ---------------------------------- - // find/records - // ---------------------------------- + { displayName: 'offset', name: 'offset', @@ -159,6 +157,49 @@ export class FileMaker implements INodeType { }, } }, + { + displayName: 'Get portals', + name: 'getPortals', + type: 'boolean', + default: false, + description: 'Should we get portal data as well ?', + }, + { + displayName: 'Portals', + name: 'portals', + type: 'options', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add portal', + loadOptionsMethod: 'getPortals', + }, + options: [], + default: [], + displayOptions: { + show: { + action: [ + 'record', + 'records', + 'find', + ], + getPortals: [ + true, + ], + }, + }, + placeholder: 'Portals', + description: 'The portal result set to return. Use the portal object name or portal table name. If this parameter is omitted, the API will return all portal objects and records in the layout. For best performance, pass the portal object name or portal table name.', + }, + // ---------------------------------- + // find/records + // ---------------------------------- + { + displayName: 'Sort data ?', + name: 'setSort', + type: 'boolean', + default: false, + description: 'Should we sort data ?', + }, { displayName: 'Sort', name: 'sortParametersUi', @@ -169,6 +210,9 @@ export class FileMaker implements INodeType { }, displayOptions: { show: { + setSort: [ + true, + ], action: [ 'find', 'records', @@ -183,7 +227,7 @@ export class FileMaker implements INodeType { displayName: 'Rules', values: [ { - displayName: 'Name', + displayName: 'Field', name: 'name', type: 'options', default: '', @@ -194,7 +238,7 @@ export class FileMaker implements INodeType { description: 'Field Name.', }, { - displayName: 'Value', + displayName: 'Order', name: 'value', type: 'options', default: 'ascend', @@ -318,6 +362,25 @@ export class FileMaker implements INodeType { } return returnData; }, + + async getPortals(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + + let portals; + try { + portals = await getPortals.call(this); + } catch (err) { + throw new Error(`FileMaker Error: ${err}`); + } + Object.keys(portals).forEach((portal) => { + returnData.push({ + name: portal, + value: portal, + }); + }); + + return returnData; + }, }, }; @@ -379,22 +442,39 @@ export class FileMaker implements INodeType { 'Authorization': `Bearer ${token}`, }; - const sort = []; - const sortParametersUi = this.getNodeParameter('sortParametersUi', 0, {}) as IDataObject; - if (sortParametersUi.parameter !== undefined) { - // @ts-ignore - for (const parameterData of sortParametersUi!.rules as IDataObject[]) { + //Handle Sort + let sort; + const setSort = this.getNodeParameter('setSort', 0, false); + if (setSort) { + sort = null; + } else { + const sortParametersUi = this.getNodeParameter('sortParametersUi', 0, {}) as IDataObject; + if (sortParametersUi.rules !== undefined) { // @ts-ignore - sort.push({ - 'fieldName': parameterData!.name as string, - 'sortOrder': parameterData!.value - }); + for (const parameterData of sortParametersUi!.rules as IDataObject[]) { + // @ts-ignore + sort.push({ + 'fieldName': parameterData!.name as string, + 'sortOrder': parameterData!.value + }); + } } } + + //handle portals + let portals; + const getPortals = this.getNodeParameter('getPortals', 0); + if (!getPortals) { + portals = []; + } else { + portals = this.getNodeParameter('portals', 0); + } + requestOptions.qs = { '_offset': this.getNodeParameter('offset', 0), '_limit': this.getNodeParameter('limit', 0), '_sort': JSON.stringify(sort), + 'portal': JSON.stringify(portals), }; } else { throw new Error(`The action "${action}" is not implemented yet!`); diff --git a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts index c76350e1f4..5fc1763b79 100644 --- a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts +++ b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts @@ -53,9 +53,8 @@ export async function layoutsApiRequest(this: ILoadOptionsFunctions | IExecuteFu * Make an API request to ActiveCampaign * * @returns {Promise} - * @param layout */ -export async function getFields(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions): Promise { // tslint:disable-line:no-any +export async function getFields(this: ILoadOptionsFunctions): Promise { // tslint:disable-line:no-any const token = await getToken.call(this); const credentials = this.getCredentials('FileMaker'); const layout = this.getCurrentNodeParameter('layout') as string; @@ -86,6 +85,43 @@ export async function getFields(this: ILoadOptionsFunctions | IExecuteFunctions } } + +/** + * Make an API request to ActiveCampaign + * + * @returns {Promise} + */ +export async function getPortals(this: ILoadOptionsFunctions): Promise { // tslint:disable-line:no-any + const token = await getToken.call(this); + const credentials = this.getCredentials('FileMaker'); + const layout = this.getCurrentNodeParameter('layout') as string; + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + const host = credentials.host as string; + const db = credentials.db as string; + + const url = `https://${host}/fmi/data/v1/databases/${db}/layouts/${layout}`; + const options: OptionsWithUri = { + headers: { + 'Authorization': `Bearer ${token}`, + }, + method: 'GET', + uri: url, + json: true + }; + + try { + const responseData = await this.helpers.request!(options); + return responseData.response.portalMetaData; + + } catch (error) { + // If that data does not exist for some reason return the actual error + throw error; + } +} + export async function getToken(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('FileMaker'); if (credentials === undefined) { From 0852a74469f5c5eca9dd844bfeef106de95e9a74 Mon Sep 17 00:00:00 2001 From: Romain Dunand Date: Tue, 12 Nov 2019 02:01:43 +0100 Subject: [PATCH 03/67] Handle Perform script Handle Perform script in queries Handle create/edit --- .../nodes/FileMaker/FileMaker.node.ts | 511 +++++++++++++++--- .../nodes/FileMaker/GenericFunctions.ts | 139 +++++ 2 files changed, 581 insertions(+), 69 deletions(-) diff --git a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts index e7435fe15f..c747dc7c3e 100644 --- a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts +++ b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts @@ -9,7 +9,19 @@ import { import {OptionsWithUri} from 'request'; -import {layoutsApiRequest, getFields, getPortals, getToken, logout} from "./GenericFunctions"; +import { + layoutsApiRequest, + getFields, + getPortals, + getScripts, + getToken, + parseSort, + parsePortals, + parseQuery, + parseScripts, + parseFields, + logout +} from "./GenericFunctions"; export class FileMaker implements INodeType { description: INodeTypeDescription = { @@ -96,11 +108,6 @@ export class FileMaker implements INodeType { default: '', required: true, displayOptions: { - hide: { - action: [ - 'performscript' - ], - }, }, placeholder: 'Layout Name', description: 'FileMaker Layout Name.', @@ -124,7 +131,6 @@ export class FileMaker implements INodeType { placeholder: 'Record ID', description: 'Internal Record ID returned by get (recordid)', }, - { displayName: 'offset', name: 'offset', @@ -163,6 +169,15 @@ export class FileMaker implements INodeType { type: 'boolean', default: false, description: 'Should we get portal data as well ?', + displayOptions: { + show: { + action: [ + 'record', + 'records', + 'find', + ], + }, + }, }, { displayName: 'Portals', @@ -193,12 +208,107 @@ export class FileMaker implements INodeType { // ---------------------------------- // find/records // ---------------------------------- + { + displayName: 'Response Layout', + name: 'responseLayout', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLayouts', + }, + options: [], + default: '', + required: false, + displayOptions: { + show: { + action: [ + 'find' + ], + }, + }, + }, + { + displayName: 'Queries', + name: 'queries', + placeholder: 'Add query', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + action: [ + 'find', + ], + }, + }, + description: 'Queries ', + default: {}, + options: [ + { + name: 'query', + displayName: 'Query', + values: [ + { + displayName: 'Fields', + name: 'fields', + placeholder: 'Add field', + type: 'fixedCollection', + default: {}, + typeOptions: { + multipleValues: true, + }, + options: [{ + name: 'field', + displayName: 'Field', + values: [ + { + displayName: 'Field', + name: 'name', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getFields', + }, + options: [], + description: 'Search Field', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Value to search', + }, + ] + } + ], + description: 'Field Name', + }, + { + displayName: 'Omit', + name: 'omit', + type: 'boolean', + default: false + }, + ] + }, + ], + }, { displayName: 'Sort data ?', name: 'setSort', type: 'boolean', default: false, description: 'Should we sort data ?', + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + }, + }, }, { displayName: 'Sort', @@ -258,6 +368,192 @@ export class FileMaker implements INodeType { }, ], }, + { + displayName: 'Before find script', + name: 'setScriptBefore', + type: 'boolean', + default: false, + description: 'Define a script to be run before the action specified by the API call and after the subsequent sort.', + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + } + }, + }, + { + displayName: 'Script Name', + name: 'scriptBefore', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getScripts', + }, + options: [], + default: '', + required: true, + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + setScriptBefore: [ + true + ], + }, + }, + placeholder: 'Script Name', + description: 'The name of the FileMaker script to be run after the action specified by the API call and after the subsequent sort.', + }, + { + displayName: 'Script Parameter', + name: 'scriptBeforeParam', + type: 'string', + default: '', + required: false, + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + setScriptBefore: [ + true + ], + }, + }, + placeholder: 'Script Parameters', + description: 'A parameter for the FileMaker script.', + }, + { + displayName: 'Before sort script', + name: 'setScriptSort', + type: 'boolean', + default: false, + description: 'Define a script to be run after the action specified by the API call but before the subsequent sort.', + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + } + }, + }, + { + displayName: 'Script Name', + name: 'scriptSort', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getScripts', + }, + options: [], + default: '', + required: true, + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + setScriptSort: [ + true + ], + }, + }, + placeholder: 'Script Name', + description: 'The name of the FileMaker script to be run after the action specified by the API call but before the subsequent sort.', + }, + { + displayName: 'Script Parameter', + name: 'scriptSortParam', + type: 'string', + default: '', + required: false, + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + setScriptSort: [ + true + ], + }, + }, + placeholder: 'Script Parameters', + description: 'A parameter for the FileMaker script.', + }, + { + displayName: 'After sort script', + name: 'setScriptAfter', + type: 'boolean', + default: false, + description: 'Define a script to be run after the action specified by the API call but before the subsequent sort.', + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + } + }, + }, + { + displayName: 'Script Name', + name: 'scriptAfter', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getScripts', + }, + options: [], + default: '', + required: true, + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + setScriptAfter: [ + true + ], + }, + }, + placeholder: 'Script Name', + description: 'The name of the FileMaker script to be run after the action specified by the API call and after the subsequent sort.', + }, + { + displayName: 'Script Parameter', + name: 'scriptAfterParam', + type: 'string', + default: '', + required: false, + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + setScriptAfter: [ + true + ], + }, + }, + placeholder: 'Script Parameters', + description: 'A parameter for the FileMaker script.', + }, // ---------------------------------- // create/edit // ---------------------------------- @@ -278,15 +574,27 @@ export class FileMaker implements INodeType { } }, { - displayName: 'Fields', - name: 'Fields', - type: 'collection', - typeOptions: { - loadOptionsMethod: 'getFields', - }, - options: [], + displayName: 'Mod Id', + name: 'modId', + description: 'The last modification ID. When you use modId, a record is edited only when the modId matches.', + type: 'number', default: '', - required: true, + displayOptions: { + show: { + action: [ + 'edit', + ], + }, + } + }, + { + displayName: 'Fields', + name: 'fieldsParametersUi', + placeholder: 'Add field', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, displayOptions: { show: { action: [ @@ -295,8 +603,33 @@ export class FileMaker implements INodeType { ], }, }, - placeholder: 'Layout Name', - description: 'FileMaker Layout Name.', + description: 'Fields to define', + default: {}, + options: [ + { + name: 'fields', + displayName: 'Fields', + values: [ + { + displayName: 'Field', + name: 'name', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getFields', + }, + options: [], + description: 'Field Name.', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + }, + ] + }, + ], }, // ---------------------------------- // performscript @@ -318,8 +651,24 @@ export class FileMaker implements INodeType { ], }, }, - placeholder: 'Layout Name', - description: 'FileMaker Layout Name.', + placeholder: 'Script Name', + description: 'The name of the FileMaker script to be run.', + }, + { + displayName: 'Script Parameter', + name: 'scriptParam', + type: 'string', + default: '', + required: false, + displayOptions: { + show: { + action: [ + 'performscript' + ], + }, + }, + placeholder: 'Script Parameters', + description: 'A parameter for the FileMaker script.', }, ] }; @@ -363,6 +712,26 @@ export class FileMaker implements INodeType { return returnData; }, + async getScripts(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + + let scripts; + try { + scripts = await getScripts.call(this); + } catch (err) { + throw new Error(`FileMaker Error: ${err}`); + } + for (const script of scripts) { + if (!script.isFolder) { + returnData.push({ + name: script.name, + value: script.name, + }); + } + } + return returnData; + }, + async getPortals(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; @@ -396,85 +765,89 @@ export class FileMaker implements INodeType { if (credentials === undefined) { throw new Error('No credentials got returned!'); } + const token = await getToken.call(this); + const staticData = this.getWorkflowStaticData('global'); - // Operations which overwrite the returned data - const overwriteDataOperations = []; - // Operations which overwrite the returned data and return arrays - // and has so to be merged with the data of other items - const overwriteDataOperationsArray = []; let requestOptions: OptionsWithUri; const host = credentials.host as string; const database = credentials.db as string; - //const layout = this.getNodeParameter('layout', 0, null) as string; - //const recid = this.getNodeParameter('recid', 0, null) as number; - const url = `https://${host}/fmi/data/v1`; - //const fullOperation = `${resource}:${operation}`; for (let i = 0; i < items.length; i++) { // Reset all values requestOptions = { uri: '', - headers: {}, + headers: { + 'Authorization': `Bearer ${token}`, + }, method: 'GET', json: true //rejectUnauthorized: !this.getNodeParameter('allowUnauthorizedCerts', itemIndex, false) as boolean, }; const layout = this.getNodeParameter('layout', 0) as string; - const token = await getToken.call(this); if (action === 'record') { const recid = this.getNodeParameter('recid', 0) as string; - requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records/${recid}`; - requestOptions.method = 'GET'; - requestOptions.headers = { - 'Authorization': `Bearer ${token}`, + requestOptions.qs = { + 'portal': JSON.stringify(parsePortals.call(this)), + ...parseScripts.call(this) }; } else if (action === 'records') { requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records`; - requestOptions.method = 'GET'; - requestOptions.headers = { - 'Authorization': `Bearer ${token}`, - }; - - //Handle Sort - let sort; - const setSort = this.getNodeParameter('setSort', 0, false); - if (setSort) { - sort = null; - } else { - const sortParametersUi = this.getNodeParameter('sortParametersUi', 0, {}) as IDataObject; - if (sortParametersUi.rules !== undefined) { - // @ts-ignore - for (const parameterData of sortParametersUi!.rules as IDataObject[]) { - // @ts-ignore - sort.push({ - 'fieldName': parameterData!.name as string, - 'sortOrder': parameterData!.value - }); - } - } - } - - //handle portals - let portals; - const getPortals = this.getNodeParameter('getPortals', 0); - if (!getPortals) { - portals = []; - } else { - portals = this.getNodeParameter('portals', 0); - } - requestOptions.qs = { '_offset': this.getNodeParameter('offset', 0), '_limit': this.getNodeParameter('limit', 0), - '_sort': JSON.stringify(sort), - 'portal': JSON.stringify(portals), + '_sort': JSON.stringify(parseSort.call(this)), + 'portal': JSON.stringify(parsePortals.call(this)), + ...parseScripts.call(this) + }; + } else if (action === 'find') { + requestOptions.uri = url + `/databases/${database}/layouts/${layout}/_find`; + requestOptions.method = 'POST'; + requestOptions.body = { + 'query': parseQuery.call(this), + 'offset': this.getNodeParameter('offset', 0), + 'limit': this.getNodeParameter('limit', 0), + 'layout.response': this.getNodeParameter('responseLayout', 0), + ...parseScripts.call(this) + }; + const sort = parseSort.call(this); + if (sort) { + requestOptions.body.sort = sort; + } + } else if (action === 'create') { + requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records`; + requestOptions.method = 'POST'; + requestOptions.headers['Content-Type'] = 'application/json'; + + //TODO: handle portalData + requestOptions.body = { + fieldData: {...parseFields.call(this)}, + portalData: {}, + ...parseScripts.call(this) + }; + } else if (action === 'edit') { + const recid = this.getNodeParameter('recid', 0) as string; + requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records/${recid}`; + requestOptions.method = 'PATCH'; + requestOptions.headers['Content-Type'] = 'application/json'; + + //TODO: handle portalData + requestOptions.body = { + fieldData: {...parseFields.call(this)}, + portalData: {}, + ...parseScripts.call(this) + }; + } else if (action === 'performscript') { + const scriptName = this.getNodeParameter('script', 0) as string; + requestOptions.uri = url + `/databases/${database}/layouts/${layout}/script/${scriptName}`; + requestOptions.qs = { + 'script.param': this.getNodeParameter('scriptParam', 0), }; } else { throw new Error(`The action "${action}" is not implemented yet!`); diff --git a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts index 5fc1763b79..5e24fe978b 100644 --- a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts +++ b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts @@ -122,6 +122,42 @@ export async function getPortals(this: ILoadOptionsFunctions): Promise { // } } +/** + * Make an API request to ActiveCampaign + * + * @returns {Promise} + */ +export async function getScripts(this: ILoadOptionsFunctions): Promise { // tslint:disable-line:no-any + const token = await getToken.call(this); + const credentials = this.getCredentials('FileMaker'); + const layout = this.getCurrentNodeParameter('layout') as string; + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + const host = credentials.host as string; + const db = credentials.db as string; + + const url = `https://${host}/fmi/data/v1/databases/${db}/scripts`; + const options: OptionsWithUri = { + headers: { + 'Authorization': `Bearer ${token}`, + }, + method: 'GET', + uri: url, + json: true + }; + + try { + const responseData = await this.helpers.request!(options); + return responseData.response.scripts; + + } catch (error) { + // If that data does not exist for some reason return the actual error + throw error; + } +} + export async function getToken(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('FileMaker'); if (credentials === undefined) { @@ -219,3 +255,106 @@ export async function logout(this: ILoadOptionsFunctions | IExecuteFunctions | I } } +export function parseSort(this: IExecuteFunctions): object | null { + let sort; + const setSort = this.getNodeParameter('setSort', 0, false); + if (!setSort) { + sort = null; + } else { + sort = []; + const sortParametersUi = this.getNodeParameter('sortParametersUi', 0, {}) as IDataObject; + if (sortParametersUi.rules !== undefined) { + // @ts-ignore + for (const parameterData of sortParametersUi!.rules as IDataObject[]) { + // @ts-ignore + sort.push({ + 'fieldName': parameterData!.name as string, + 'sortOrder': parameterData!.value + }); + } + } + } + return sort; +} + + +export function parseScripts(this: IExecuteFunctions): object | null { + const setScriptAfter = this.getNodeParameter('setScriptAfter', 0, false); + const setScriptBefore = this.getNodeParameter('setScriptBefore', 0, false); + const setScriptSort = this.getNodeParameter('setScriptSort', 0, false); + + if (!setScriptAfter && setScriptBefore && setScriptSort) { + return {}; + } else { + const scripts = { + }; + if (setScriptAfter) { + scripts.script = this.getNodeParameter('scriptAfter', 0); + scripts['script.param'] = this.getNodeParameter('scriptAfter', 0); + } + if (setScriptBefore) { + scripts['script.prerequest'] = this.getNodeParameter('scriptBefore', 0); + scripts['script.prerequest.param'] = this.getNodeParameter('scriptBeforeParam', 0); + } + if (setScriptSort) { + scripts['script.presort'] = this.getNodeParameter('scriptSort', 0); + scripts['script.presort.param'] = this.getNodeParameter('scriptSortParam', 0); + } + return scripts; + } +} + +export function parsePortals(this: IExecuteFunctions): object | null { + let portals; + const getPortals = this.getNodeParameter('getPortals', 0); + if (!getPortals) { + portals = []; + } else { + portals = this.getNodeParameter('portals', 0); + } + // @ts-ignore + return portals; +} + + +export function parseQuery(this: IExecuteFunctions): object | null { + let queries; + const queriesParamUi = this.getNodeParameter('queries', 0, {}) as IDataObject; + if (queriesParamUi.query !== undefined) { + // @ts-ignore + queries = []; + for (const queryParam of queriesParamUi!.query as IDataObject[]) { + const query = { + 'omit': queryParam.omit ? 'true' : 'false', + }; + // @ts-ignore + for (const field of queryParam!.fields!.field as IDataObject[]) { + // @ts-ignore + query[field.name] =field!.value; + } + queries.push(query); + } + } else { + queries = null; + } + // @ts-ignore + return queries; +} + +export function parseFields(this: IExecuteFunctions): object | null { + let fieldData; + const fieldsParametersUi = this.getNodeParameter('fieldsParametersUi', 0, {}) as IDataObject; + if (fieldsParametersUi.fields !== undefined) { + // @ts-ignore + fieldData = {}; + for (const field of fieldsParametersUi!.fields as IDataObject[]) { + // @ts-ignore + fieldData[field.name] =field!.value; + } + } else { + fieldData = null; + } + // @ts-ignore + return fieldData; +} + From a3594ae7a2315c65c6ae91eb990821613d7de147 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Tue, 12 Nov 2019 15:48:45 -0500 Subject: [PATCH 04/67] :rocket: node-setup --- .../credentials/MailchimpApi.credentials.ts | 23 ++++ .../nodes/Mailchimp/GenericFunctions.ts | 43 +++++++ .../nodes/Mailchimp/Mailchimp.node.ts | 117 ++++++++++++++++++ .../nodes-base/nodes/Mailchimp/mailchimp.png | Bin 0 -> 2965 bytes packages/nodes-base/package.json | 6 +- 5 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 packages/nodes-base/credentials/MailchimpApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts create mode 100644 packages/nodes-base/nodes/Mailchimp/mailchimp.png diff --git a/packages/nodes-base/credentials/MailchimpApi.credentials.ts b/packages/nodes-base/credentials/MailchimpApi.credentials.ts new file mode 100644 index 0000000000..407d112f53 --- /dev/null +++ b/packages/nodes-base/credentials/MailchimpApi.credentials.ts @@ -0,0 +1,23 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class Mailchimp implements ICredentialType { + name = 'mailchimpApi'; + displayName = 'Mailchimp API'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Datacenter', + name: 'datacenter', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts b/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts new file mode 100644 index 0000000000..27fda7364a --- /dev/null +++ b/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts @@ -0,0 +1,43 @@ +import { OptionsWithUri } from 'request'; + +import { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IExecuteSingleFunctions +} from 'n8n-core'; + +export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resource: string, method: string, body: any = {}, headers?: object): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('mailchimpApi'); + const datacenter = credentials!.datacenter as string; + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const headerWithAuthentication = Object.assign({}, headers, { Authorization: `apikey ${credentials.apiKey}` }); + + const endpoint = 'api.mailchimp.com/3.0'; + + const options: OptionsWithUri = { + headers: headerWithAuthentication, + method, + uri: `https://${datacenter}.${endpoint}${resource}`, + json: true + }; + + if (Object.keys(body).length !== 0) { + options.body = body; + } + + try { + return await this.helpers.request!(options); + } catch (error) { + const errorMessage = error.response.body.message || error.response.body.Message; + + if (errorMessage !== undefined) { + throw errorMessage; + } + throw error.response.body; + } +} diff --git a/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts new file mode 100644 index 0000000000..f2b3eca677 --- /dev/null +++ b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts @@ -0,0 +1,117 @@ +import { + IExecuteSingleFunctions, +} from 'n8n-core'; +import { + IDataObject, + INodeTypeDescription, + INodeExecutionData, + INodeType, + ILoadOptionsFunctions, + INodePropertyOptions, +} from 'n8n-workflow'; +import { + mailchimpApiRequest, +} from './GenericFunctions'; + +export class Mailchimp implements INodeType { + + description: INodeTypeDescription = { + displayName: 'Mailchimp', + name: 'mailchimp', + icon: 'file:mailchimp.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Mailchimp API', + defaults: { + name: 'Mailchimp', + color: '#c02428', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'mailchimpApi', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Member', + value: 'member', + description: 'Add member to list', + }, + ], + default: '', + required: true, + description: 'Resource to consume.', + }, + { + displayName: 'Operation', + name: 'operation', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'member', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new member', + }, + ], + default: '', + description: 'The operation to perform.', + }, + ] + }; + + + methods = { + loadOptions: { + // Get all the available projects to display them to user so that he can + // select them easily + async getLists(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let lists, response; + try { + response = await mailchimpApiRequest.call(this, '/lists', 'GET'); + lists = response.lists; + } catch (err) { + throw new Error(`Mailchimp Error: ${err}`); + } + for (const list of lists) { + const listName = list.name; + const listId = list.id; + + returnData.push({ + name: listName, + value: listId, + }); + } + + return returnData; + }, + } + }; + + async executeSingle(this: IExecuteSingleFunctions): Promise { + + const resource = this.getNodeParameter('resource') as string; + const opeation = this.getNodeParameter('operation') as string; + + return { + json: {} + }; + } +} diff --git a/packages/nodes-base/nodes/Mailchimp/mailchimp.png b/packages/nodes-base/nodes/Mailchimp/mailchimp.png new file mode 100644 index 0000000000000000000000000000000000000000..a4998396c3db04377d7e2ef555f878ba4f1dd3ce GIT binary patch literal 2965 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 literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 0343726121..6de583c5b3 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -42,7 +42,8 @@ "dist/credentials/HttpHeaderAuth.credentials.js", "dist/credentials/Imap.credentials.js", "dist/credentials/LinkFishApi.credentials.js", - "dist/credentials/MailgunApi.credentials.js", + "dist/credentials/MailgunApi.credentials.js", + "dist/credentials/MailchimpApi.credentials.js", "dist/credentials/MandrillApi.credentials.js", "dist/credentials/MattermostApi.credentials.js", "dist/credentials/MongoDb.credentials.js", @@ -93,7 +94,8 @@ "dist/nodes/If.node.js", "dist/nodes/Interval.node.js", "dist/nodes/LinkFish/LinkFish.node.js", - "dist/nodes/Mailgun/Mailgun.node.js", + "dist/nodes/Mailgun/Mailgun.node.js", + "dist/nodes/Mailchimp/Mailchimp.node.js", "dist/nodes/Mandrill/Mandrill.node.js", "dist/nodes/Mattermost/Mattermost.node.js", "dist/nodes/Merge.node.js", From 44b1f52d4eec320a51dcfa5bb62c171aeb637ebd Mon Sep 17 00:00:00 2001 From: Romain Dunand Date: Wed, 13 Nov 2019 00:00:58 +0100 Subject: [PATCH 05/67] Handle duplicate/delete actions Code cleanup Improve error handling --- .../nodes/FileMaker/FileMaker.node.ts | 65 +++++++++++++++---- .../nodes/FileMaker/GenericFunctions.ts | 25 +++++-- 2 files changed, 72 insertions(+), 18 deletions(-) diff --git a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts index c747dc7c3e..ff8b893fa5 100644 --- a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts +++ b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts @@ -213,7 +213,7 @@ export class FileMaker implements INodeType { name: 'responseLayout', type: 'options', typeOptions: { - loadOptionsMethod: 'getLayouts', + loadOptionsMethod: 'getResponseLayouts', }, options: [], default: '', @@ -557,7 +557,7 @@ export class FileMaker implements INodeType { // ---------------------------------- // create/edit // ---------------------------------- - { + /*{ displayName: 'fieldData', name: 'fieldData', placeholder: '{"field1": "value", "field2": "value", ...}', @@ -572,7 +572,7 @@ export class FileMaker implements INodeType { ], }, } - }, + },*/ { displayName: 'Mod Id', name: 'modId', @@ -679,6 +679,28 @@ export class FileMaker implements INodeType { // select them easily async getLayouts(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; + + let layouts; + try { + layouts = await layoutsApiRequest.call(this); + } catch (err) { + throw new Error(`FileMaker Error: ${err}`); + } + for (const layout of layouts) { + returnData.push({ + name: layout.name, + value: layout.name, + }); + } + return returnData; + }, + async getResponseLayouts(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + returnData.push({ + name: 'Use main layout', + value: '', + }); + let layouts; try { layouts = await layoutsApiRequest.call(this); @@ -760,14 +782,16 @@ export class FileMaker implements INodeType { const credentials = this.getCredentials('FileMaker'); - const action = this.getNodeParameter('action', 0) as string; - if (credentials === undefined) { throw new Error('No credentials got returned!'); } - const token = await getToken.call(this); - const staticData = this.getWorkflowStaticData('global'); + let token; + try { + token = await getToken.call(this); + } catch (e) { + throw new Error(`Login fail: ${e}`); + } let requestOptions: OptionsWithUri; @@ -776,6 +800,8 @@ export class FileMaker implements INodeType { const url = `https://${host}/fmi/data/v1`; + const action = this.getNodeParameter('action', 0) as string; + for (let i = 0; i < items.length; i++) { // Reset all values requestOptions = { @@ -785,7 +811,6 @@ export class FileMaker implements INodeType { }, method: 'GET', json: true - //rejectUnauthorized: !this.getNodeParameter('allowUnauthorizedCerts', itemIndex, false) as boolean, }; const layout = this.getNodeParameter('layout', 0) as string; @@ -802,10 +827,13 @@ export class FileMaker implements INodeType { requestOptions.qs = { '_offset': this.getNodeParameter('offset', 0), '_limit': this.getNodeParameter('limit', 0), - '_sort': JSON.stringify(parseSort.call(this)), 'portal': JSON.stringify(parsePortals.call(this)), ...parseScripts.call(this) }; + const sort = parseSort.call(this); + if (sort) { + requestOptions.body.sort = sort; + } } else if (action === 'find') { requestOptions.uri = url + `/databases/${database}/layouts/${layout}/_find`; requestOptions.method = 'POST'; @@ -823,7 +851,7 @@ export class FileMaker implements INodeType { } else if (action === 'create') { requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records`; requestOptions.method = 'POST'; - requestOptions.headers['Content-Type'] = 'application/json'; + requestOptions.headers!['Content-Type'] = 'application/json'; //TODO: handle portalData requestOptions.body = { @@ -835,7 +863,7 @@ export class FileMaker implements INodeType { const recid = this.getNodeParameter('recid', 0) as string; requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records/${recid}`; requestOptions.method = 'PATCH'; - requestOptions.headers['Content-Type'] = 'application/json'; + requestOptions.headers!['Content-Type'] = 'application/json'; //TODO: handle portalData requestOptions.body = { @@ -849,6 +877,21 @@ export class FileMaker implements INodeType { requestOptions.qs = { 'script.param': this.getNodeParameter('scriptParam', 0), }; + } else if (action === 'duplicate') { + const recid = this.getNodeParameter('recid', 0) as string; + requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records/${recid}`; + requestOptions.method = 'POST'; + requestOptions.headers!['Content-Type'] = 'application/json'; + requestOptions.qs = { + ...parseScripts.call(this) + }; + } else if (action === 'delete') { + const recid = this.getNodeParameter('recid', 0) as string; + requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records/${recid}`; + requestOptions.method = 'DELETE'; + requestOptions.qs = { + ...parseScripts.call(this) + }; } else { throw new Error(`The action "${action}" is not implemented yet!`); } diff --git a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts index 5e24fe978b..a0470bcb10 100644 --- a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts +++ b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts @@ -10,8 +10,16 @@ import { } from 'n8n-workflow'; import { OptionsWithUri } from 'request'; +import {Url} from "url"; - +interface ScriptsOptions { + script?: any; //tslint:disable-line:no-any + 'script.param'?: any; //tslint:disable-line:no-any + 'script.prerequest'?: any; //tslint:disable-line:no-any + 'script.prerequest.param'?: any; //tslint:disable-line:no-any + 'script.presort'?: any; //tslint:disable-line:no-any + 'script.presort.param'?: any; //tslint:disable-line:no-any +} /** * Make an API request to ActiveCampaign * @@ -205,12 +213,16 @@ export async function getToken(this: ILoadOptionsFunctions | IExecuteFunctions | } catch (error) { console.error(error); - const errorMessage = error.response.body.messages[0].message + '(' + error.response.body.messages[0].message + ')'; - + let errorMessage; + if (error.response) { + errorMessage = error.response.body.messages[0].message + '(' + error.response.body.messages[0].message + ')'; + } else { + errorMessage = `${error.message} (${error.name})`; + } if (errorMessage !== undefined) { throw errorMessage; } - throw error.response.body; + throw error.message; } } @@ -286,11 +298,10 @@ export function parseScripts(this: IExecuteFunctions): object | null { if (!setScriptAfter && setScriptBefore && setScriptSort) { return {}; } else { - const scripts = { - }; + const scripts = {} as ScriptsOptions; if (setScriptAfter) { scripts.script = this.getNodeParameter('scriptAfter', 0); - scripts['script.param'] = this.getNodeParameter('scriptAfter', 0); + scripts!['script.param'] = this.getNodeParameter('scriptAfter', 0); } if (setScriptBefore) { scripts['script.prerequest'] = this.getNodeParameter('scriptBefore', 0); From cfa871322692a06fdd9f51ac8b7a3b5945488221 Mon Sep 17 00:00:00 2001 From: Romain Dunand Date: Sun, 10 Nov 2019 01:54:25 +0100 Subject: [PATCH 06/67] Auth & get records management --- .../credentials/FileMaker.credentials.ts | 39 ++ .../nodes/FileMaker/FileMaker.node.ts | 421 ++++++++++++++++++ .../nodes/FileMaker/GenericFunctions.ts | 185 ++++++++ .../nodes-base/nodes/FileMaker/filemaker.png | Bin 0 -> 16244 bytes packages/nodes-base/nodes/HttpRequest.node.ts | 1 - packages/nodes-base/package.json | 2 + 6 files changed, 647 insertions(+), 1 deletion(-) create mode 100644 packages/nodes-base/credentials/FileMaker.credentials.ts create mode 100644 packages/nodes-base/nodes/FileMaker/FileMaker.node.ts create mode 100644 packages/nodes-base/nodes/FileMaker/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/FileMaker/filemaker.png diff --git a/packages/nodes-base/credentials/FileMaker.credentials.ts b/packages/nodes-base/credentials/FileMaker.credentials.ts new file mode 100644 index 0000000000..3d0e67b7ca --- /dev/null +++ b/packages/nodes-base/credentials/FileMaker.credentials.ts @@ -0,0 +1,39 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class FileMaker implements ICredentialType { + name = 'FileMaker'; + displayName = 'FileMaker'; + properties = [ + { + displayName: 'Host', + name: 'host', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Database', + name: 'db', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Login', + name: 'login', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Password', + name: 'password', + type: 'string' as NodePropertyTypes, + typeOptions: { + password: true, + }, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts new file mode 100644 index 0000000000..8caaca47d8 --- /dev/null +++ b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts @@ -0,0 +1,421 @@ +import {IExecuteFunctions} from 'n8n-core'; +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, INodePropertyOptions, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + + +import {OptionsWithUri} from 'request'; +import {layoutsApiRequest, getFields, getToken, logout} from "./GenericFunctions"; + +export class FileMaker implements INodeType { + description: INodeTypeDescription = { + displayName: 'FileMaker', + name: 'filemaker', + icon: 'file:filemaker.png', + group: ['input'], + version: 1, + description: 'Retrieve data from FileMaker data API.', + defaults: { + name: 'FileMaker', + color: '#665533', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'FileMaker', + required: true, + }, + ], + properties: [ + { + displayName: 'Action', + name: 'action', + type: 'options', + options: [ + /*{ + name: 'Login', + value: 'login', + }, + { + name: 'Logout', + value: 'logout', + },*/ + { + name: 'Find Records', + value: 'find', + }, + { + name: 'get Records', + value: 'records', + }, + { + name: 'Get Records By Id', + value: 'record', + }, + { + name: 'Perform Script', + value: 'performscript', + }, + { + name: 'Create Record', + value: 'create', + }, + { + name: 'Edit Record', + value: 'edit', + }, + { + name: 'Duplicate Record', + value: 'duplicate', + }, + { + name: 'Delete Record', + value: 'delete', + }, + ], + default: 'login', + description: 'Action to perform.', + }, + + // ---------------------------------- + // shared + // ---------------------------------- + { + displayName: 'Layout', + name: 'layout', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLayouts', + }, + options: [], + default: '', + required: true, + displayOptions: { + hide: { + action: [ + 'performscript' + ], + }, + }, + placeholder: 'Layout Name', + description: 'FileMaker Layout Name.', + }, + { + displayName: 'Record Id', + name: 'recid', + type: 'number', + default: '', + required: true, + displayOptions: { + show: { + action: [ + 'record', + 'edit', + 'delete', + 'duplicate', + ], + }, + }, + placeholder: 'Record ID', + description: 'Internal Record ID returned by get (recordid)', + }, + // ---------------------------------- + // find/records + // ---------------------------------- + { + displayName: 'offset', + name: 'offset', + placeholder: '0', + description: 'The record number of the first record in the range of records.', + type: 'number', + default: '1', + displayOptions: { + show: { + action: [ + 'find', + 'records', + ], + }, + } + }, + { + displayName: 'limit', + name: 'limit', + placeholder: '100', + description: 'The maximum number of records that should be returned. If not specified, the default value is 100.', + type: 'number', + default: '100', + displayOptions: { + show: { + action: [ + 'find', + 'records', + ], + }, + } + }, + { + displayName: 'Sort', + name: 'sortParametersUi', + placeholder: 'Add Sort Rules', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + action: [ + 'find', + 'records', + ], + }, + }, + description: 'Sort rules', + default: {}, + options: [ + { + name: 'rules', + displayName: 'Rules', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getFields', + }, + options: [], + description: 'Field Name.', + }, + { + displayName: 'Value', + name: 'value', + type: 'options', + default: 'ascend', + options: [ + { + name: 'Ascend', + value: 'ascend' + }, + { + name: 'Descend', + value: 'descend' + }, + ], + description: 'Sort order.', + }, + ] + }, + ], + }, + // ---------------------------------- + // create/edit + // ---------------------------------- + { + displayName: 'fieldData', + name: 'fieldData', + placeholder: '{"field1": "value", "field2": "value", ...}', + description: 'Additional fields to add.', + type: 'string', + default: '{}', + displayOptions: { + show: { + action: [ + 'create', + 'edit', + ], + }, + } + }, + { + displayName: 'Fields', + name: 'Fields', + type: 'collection', + typeOptions: { + loadOptionsMethod: 'getFields', + }, + options: [], + default: '', + required: true, + displayOptions: { + show: { + action: [ + 'create', + 'edit', + ], + }, + }, + placeholder: 'Layout Name', + description: 'FileMaker Layout Name.', + }, + // ---------------------------------- + // performscript + // ---------------------------------- + { + displayName: 'Script Name', + name: 'script', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getScripts', + }, + options: [], + default: '', + required: true, + displayOptions: { + show: { + action: [ + 'performscript' + ], + }, + }, + placeholder: 'Layout Name', + description: 'FileMaker Layout Name.', + }, + ] + }; + + methods = { + loadOptions: { + // Get all the available topics to display them to user so that he can + // select them easily + async getLayouts(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let layouts; + try { + layouts = await layoutsApiRequest.call(this); + } catch (err) { + throw new Error(`FileMaker Error: ${err}`); + } + for (const layout of layouts) { + returnData.push({ + name: layout.name, + value: layout.name, + }); + } + return returnData; + }, + + async getFields(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + + let fields; + try { + fields = await getFields.call(this); + } catch (err) { + throw new Error(`FileMaker Error: ${err}`); + } + for (const field of fields) { + returnData.push({ + name: field.name, + value: field.name, + }); + } + return returnData; + }, + }, + }; + + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: INodeExecutionData[] = []; + + const credentials = this.getCredentials('FileMaker'); + + const action = this.getNodeParameter('action', 0) as string; + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + const staticData = this.getWorkflowStaticData('global'); + // Operations which overwrite the returned data + const overwriteDataOperations = []; + // Operations which overwrite the returned data and return arrays + // and has so to be merged with the data of other items + const overwriteDataOperationsArray = []; + + let requestOptions: OptionsWithUri; + + const host = credentials.host as string; + const database = credentials.db as string; + + //const layout = this.getNodeParameter('layout', 0, null) as string; + //const recid = this.getNodeParameter('recid', 0, null) as number; + + const url = `https://${host}/fmi/data/v1`; + //const fullOperation = `${resource}:${operation}`; + + for (let i = 0; i < items.length; i++) { + // Reset all values + requestOptions = { + uri: '', + headers: {}, + method: 'GET', + json: true + //rejectUnauthorized: !this.getNodeParameter('allowUnauthorizedCerts', itemIndex, false) as boolean, + }; + + const layout = this.getNodeParameter('layout', 0) as string; + const token = await getToken.call(this); + + if (action === 'record') { + const recid = this.getNodeParameter('recid', 0) as string; + + requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records/${recid}`; + requestOptions.method = 'GET'; + requestOptions.headers = { + 'Authorization': `Bearer ${token}`, + }; + } else if (action === 'records') { + requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records`; + requestOptions.method = 'GET'; + requestOptions.headers = { + 'Authorization': `Bearer ${token}`, + }; + + const sort = []; + const sortParametersUi = this.getNodeParameter('sortParametersUi', 0, {}) as IDataObject; + if (sortParametersUi.parameter !== undefined) { + // @ts-ignore + for (const parameterData of sortParametersUi!.rules as IDataObject[]) { + // @ts-ignore + sort.push({ + 'fieldName': parameterData!.name as string, + 'sortOrder': parameterData!.value + }); + } + } + requestOptions.qs = { + '_offset': this.getNodeParameter('offset', 0), + '_limit': this.getNodeParameter('limit', 0), + '_sort': JSON.stringify(sort), + }; + } else { + throw new Error(`The action "${action}" is not implemented yet!`); + } + + // Now that the options are all set make the actual http request + let response; + try { + response = await this.helpers.request(requestOptions); + } catch (error) { + response = error.response.body; + } + + if (typeof response === 'string') { + throw new Error('Response body is not valid JSON. Change "Response Format" to "String"'); + } + await logout.call(this, token); + + returnData.push({json: response}); + } + + return this.prepareOutputData(returnData); + } +} \ No newline at end of file diff --git a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts new file mode 100644 index 0000000000..c76350e1f4 --- /dev/null +++ b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts @@ -0,0 +1,185 @@ +import { + IExecuteFunctions, + ILoadOptionsFunctions, + IExecuteSingleFunctions +} from 'n8n-core'; + +import { + ICredentialDataDecryptedObject, + IDataObject, +} from 'n8n-workflow'; + +import { OptionsWithUri } from 'request'; + + +/** + * Make an API request to ActiveCampaign + * + * @param {IHookFunctions} this + * @param {string} method + * @returns {Promise} + */ +export async function layoutsApiRequest(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions): Promise { // tslint:disable-line:no-any + const token = await getToken.call(this); + const credentials = this.getCredentials('FileMaker'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + const host = credentials.host as string; + const db = credentials.db as string; + + const url = `https://${host}/fmi/data/v1/databases/${db}/layouts`; + const options: OptionsWithUri = { + headers: { + 'Authorization': `Bearer ${token}`, + }, + method: 'GET', + uri: url, + json: true + }; + + try { + const responseData = await this.helpers.request!(options); + return responseData.response.layouts; + + } catch (error) { + // If that data does not exist for some reason return the actual error + throw error; + } +} + +/** + * Make an API request to ActiveCampaign + * + * @returns {Promise} + * @param layout + */ +export async function getFields(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions): Promise { // tslint:disable-line:no-any + const token = await getToken.call(this); + const credentials = this.getCredentials('FileMaker'); + const layout = this.getCurrentNodeParameter('layout') as string; + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + const host = credentials.host as string; + const db = credentials.db as string; + + const url = `https://${host}/fmi/data/v1/databases/${db}/layouts/${layout}`; + const options: OptionsWithUri = { + headers: { + 'Authorization': `Bearer ${token}`, + }, + method: 'GET', + uri: url, + json: true + }; + + try { + const responseData = await this.helpers.request!(options); + return responseData.response.fieldMetaData; + + } catch (error) { + // If that data does not exist for some reason return the actual error + throw error; + } +} + +export async function getToken(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('FileMaker'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const host = credentials.host as string; + const db = credentials.db as string; + const login = credentials.login as string; + const password = credentials.password as string; + + const url = `https://${host}/fmi/data/v1/databases/${db}/sessions`; + + let requestOptions: OptionsWithUri; + // Reset all values + requestOptions = { + uri: url, + headers: {}, + method: 'POST', + json: true + //rejectUnauthorized: !this.getNodeParameter('allowUnauthorizedCerts', itemIndex, false) as boolean, + }; + requestOptions.auth = { + user: login as string, + pass: password as string, + }; + requestOptions.body = { + "fmDataSource": [ + { + "database": host, + "username": login as string, + "password": password as string + } + ] + }; + + try { + const response = await this.helpers.request!(requestOptions); + + if (typeof response === 'string') { + throw new Error('Response body is not valid JSON. Change "Response Format" to "String"'); + } + + return response.response.token; + } catch (error) { + console.error(error); + + const errorMessage = error.response.body.messages[0].message + '(' + error.response.body.messages[0].message + ')'; + + if (errorMessage !== undefined) { + throw errorMessage; + } + throw error.response.body; + } +} + +export async function logout(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions, token: string): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('FileMaker'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const host = credentials.host as string; + const db = credentials.db as string; + + const url = `https://${host}/fmi/data/v1/databases/${db}/sessions/${token}`; + + let requestOptions: OptionsWithUri; + // Reset all values + requestOptions = { + uri: url, + headers: {}, + method: 'DELETE', + json: true + //rejectUnauthorized: !this.getNodeParameter('allowUnauthorizedCerts', itemIndex, false) as boolean, + }; + + try { + const response = await this.helpers.request!(requestOptions); + + if (typeof response === 'string') { + throw new Error('Response body is not valid JSON. Change "Response Format" to "String"'); + } + + return response; + } catch (error) { + console.error(error); + + const errorMessage = error.response.body.messages[0].message + '(' + error.response.body.messages[0].message + ')'; + + if (errorMessage !== undefined) { + throw errorMessage; + } + throw error.response.body; + } +} + diff --git a/packages/nodes-base/nodes/FileMaker/filemaker.png b/packages/nodes-base/nodes/FileMaker/filemaker.png new file mode 100644 index 0000000000000000000000000000000000000000..ec691433dac024e2f3596fc5f9c310e5acc4c3a1 GIT binary patch literal 16244 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 Date: Sun, 10 Nov 2019 03:14:10 +0100 Subject: [PATCH 07/67] Handle portal selection & improve UX --- .../nodes/FileMaker/FileMaker.node.ts | 112 +++++++++++++++--- .../nodes/FileMaker/GenericFunctions.ts | 40 ++++++- 2 files changed, 134 insertions(+), 18 deletions(-) diff --git a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts index 8caaca47d8..e7435fe15f 100644 --- a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts +++ b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts @@ -9,7 +9,7 @@ import { import {OptionsWithUri} from 'request'; -import {layoutsApiRequest, getFields, getToken, logout} from "./GenericFunctions"; +import {layoutsApiRequest, getFields, getPortals, getToken, logout} from "./GenericFunctions"; export class FileMaker implements INodeType { description: INodeTypeDescription = { @@ -36,6 +36,7 @@ export class FileMaker implements INodeType { displayName: 'Action', name: 'action', type: 'options', + default: 'record', options: [ /*{ name: 'Login', @@ -78,7 +79,6 @@ export class FileMaker implements INodeType { value: 'delete', }, ], - default: 'login', description: 'Action to perform.', }, @@ -124,9 +124,7 @@ export class FileMaker implements INodeType { placeholder: 'Record ID', description: 'Internal Record ID returned by get (recordid)', }, - // ---------------------------------- - // find/records - // ---------------------------------- + { displayName: 'offset', name: 'offset', @@ -159,6 +157,49 @@ export class FileMaker implements INodeType { }, } }, + { + displayName: 'Get portals', + name: 'getPortals', + type: 'boolean', + default: false, + description: 'Should we get portal data as well ?', + }, + { + displayName: 'Portals', + name: 'portals', + type: 'options', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add portal', + loadOptionsMethod: 'getPortals', + }, + options: [], + default: [], + displayOptions: { + show: { + action: [ + 'record', + 'records', + 'find', + ], + getPortals: [ + true, + ], + }, + }, + placeholder: 'Portals', + description: 'The portal result set to return. Use the portal object name or portal table name. If this parameter is omitted, the API will return all portal objects and records in the layout. For best performance, pass the portal object name or portal table name.', + }, + // ---------------------------------- + // find/records + // ---------------------------------- + { + displayName: 'Sort data ?', + name: 'setSort', + type: 'boolean', + default: false, + description: 'Should we sort data ?', + }, { displayName: 'Sort', name: 'sortParametersUi', @@ -169,6 +210,9 @@ export class FileMaker implements INodeType { }, displayOptions: { show: { + setSort: [ + true, + ], action: [ 'find', 'records', @@ -183,7 +227,7 @@ export class FileMaker implements INodeType { displayName: 'Rules', values: [ { - displayName: 'Name', + displayName: 'Field', name: 'name', type: 'options', default: '', @@ -194,7 +238,7 @@ export class FileMaker implements INodeType { description: 'Field Name.', }, { - displayName: 'Value', + displayName: 'Order', name: 'value', type: 'options', default: 'ascend', @@ -318,6 +362,25 @@ export class FileMaker implements INodeType { } return returnData; }, + + async getPortals(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + + let portals; + try { + portals = await getPortals.call(this); + } catch (err) { + throw new Error(`FileMaker Error: ${err}`); + } + Object.keys(portals).forEach((portal) => { + returnData.push({ + name: portal, + value: portal, + }); + }); + + return returnData; + }, }, }; @@ -379,22 +442,39 @@ export class FileMaker implements INodeType { 'Authorization': `Bearer ${token}`, }; - const sort = []; - const sortParametersUi = this.getNodeParameter('sortParametersUi', 0, {}) as IDataObject; - if (sortParametersUi.parameter !== undefined) { - // @ts-ignore - for (const parameterData of sortParametersUi!.rules as IDataObject[]) { + //Handle Sort + let sort; + const setSort = this.getNodeParameter('setSort', 0, false); + if (setSort) { + sort = null; + } else { + const sortParametersUi = this.getNodeParameter('sortParametersUi', 0, {}) as IDataObject; + if (sortParametersUi.rules !== undefined) { // @ts-ignore - sort.push({ - 'fieldName': parameterData!.name as string, - 'sortOrder': parameterData!.value - }); + for (const parameterData of sortParametersUi!.rules as IDataObject[]) { + // @ts-ignore + sort.push({ + 'fieldName': parameterData!.name as string, + 'sortOrder': parameterData!.value + }); + } } } + + //handle portals + let portals; + const getPortals = this.getNodeParameter('getPortals', 0); + if (!getPortals) { + portals = []; + } else { + portals = this.getNodeParameter('portals', 0); + } + requestOptions.qs = { '_offset': this.getNodeParameter('offset', 0), '_limit': this.getNodeParameter('limit', 0), '_sort': JSON.stringify(sort), + 'portal': JSON.stringify(portals), }; } else { throw new Error(`The action "${action}" is not implemented yet!`); diff --git a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts index c76350e1f4..5fc1763b79 100644 --- a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts +++ b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts @@ -53,9 +53,8 @@ export async function layoutsApiRequest(this: ILoadOptionsFunctions | IExecuteFu * Make an API request to ActiveCampaign * * @returns {Promise} - * @param layout */ -export async function getFields(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions): Promise { // tslint:disable-line:no-any +export async function getFields(this: ILoadOptionsFunctions): Promise { // tslint:disable-line:no-any const token = await getToken.call(this); const credentials = this.getCredentials('FileMaker'); const layout = this.getCurrentNodeParameter('layout') as string; @@ -86,6 +85,43 @@ export async function getFields(this: ILoadOptionsFunctions | IExecuteFunctions } } + +/** + * Make an API request to ActiveCampaign + * + * @returns {Promise} + */ +export async function getPortals(this: ILoadOptionsFunctions): Promise { // tslint:disable-line:no-any + const token = await getToken.call(this); + const credentials = this.getCredentials('FileMaker'); + const layout = this.getCurrentNodeParameter('layout') as string; + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + const host = credentials.host as string; + const db = credentials.db as string; + + const url = `https://${host}/fmi/data/v1/databases/${db}/layouts/${layout}`; + const options: OptionsWithUri = { + headers: { + 'Authorization': `Bearer ${token}`, + }, + method: 'GET', + uri: url, + json: true + }; + + try { + const responseData = await this.helpers.request!(options); + return responseData.response.portalMetaData; + + } catch (error) { + // If that data does not exist for some reason return the actual error + throw error; + } +} + export async function getToken(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('FileMaker'); if (credentials === undefined) { From bcfb7afe73a28d6339f438c4380fff70edf940a3 Mon Sep 17 00:00:00 2001 From: Romain Dunand Date: Tue, 12 Nov 2019 02:01:43 +0100 Subject: [PATCH 08/67] Handle Perform script Handle Perform script in queries Handle create/edit --- .../nodes/FileMaker/FileMaker.node.ts | 511 +++++++++++++++--- .../nodes/FileMaker/GenericFunctions.ts | 139 +++++ 2 files changed, 581 insertions(+), 69 deletions(-) diff --git a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts index e7435fe15f..c747dc7c3e 100644 --- a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts +++ b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts @@ -9,7 +9,19 @@ import { import {OptionsWithUri} from 'request'; -import {layoutsApiRequest, getFields, getPortals, getToken, logout} from "./GenericFunctions"; +import { + layoutsApiRequest, + getFields, + getPortals, + getScripts, + getToken, + parseSort, + parsePortals, + parseQuery, + parseScripts, + parseFields, + logout +} from "./GenericFunctions"; export class FileMaker implements INodeType { description: INodeTypeDescription = { @@ -96,11 +108,6 @@ export class FileMaker implements INodeType { default: '', required: true, displayOptions: { - hide: { - action: [ - 'performscript' - ], - }, }, placeholder: 'Layout Name', description: 'FileMaker Layout Name.', @@ -124,7 +131,6 @@ export class FileMaker implements INodeType { placeholder: 'Record ID', description: 'Internal Record ID returned by get (recordid)', }, - { displayName: 'offset', name: 'offset', @@ -163,6 +169,15 @@ export class FileMaker implements INodeType { type: 'boolean', default: false, description: 'Should we get portal data as well ?', + displayOptions: { + show: { + action: [ + 'record', + 'records', + 'find', + ], + }, + }, }, { displayName: 'Portals', @@ -193,12 +208,107 @@ export class FileMaker implements INodeType { // ---------------------------------- // find/records // ---------------------------------- + { + displayName: 'Response Layout', + name: 'responseLayout', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLayouts', + }, + options: [], + default: '', + required: false, + displayOptions: { + show: { + action: [ + 'find' + ], + }, + }, + }, + { + displayName: 'Queries', + name: 'queries', + placeholder: 'Add query', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + action: [ + 'find', + ], + }, + }, + description: 'Queries ', + default: {}, + options: [ + { + name: 'query', + displayName: 'Query', + values: [ + { + displayName: 'Fields', + name: 'fields', + placeholder: 'Add field', + type: 'fixedCollection', + default: {}, + typeOptions: { + multipleValues: true, + }, + options: [{ + name: 'field', + displayName: 'Field', + values: [ + { + displayName: 'Field', + name: 'name', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getFields', + }, + options: [], + description: 'Search Field', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + description: 'Value to search', + }, + ] + } + ], + description: 'Field Name', + }, + { + displayName: 'Omit', + name: 'omit', + type: 'boolean', + default: false + }, + ] + }, + ], + }, { displayName: 'Sort data ?', name: 'setSort', type: 'boolean', default: false, description: 'Should we sort data ?', + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + }, + }, }, { displayName: 'Sort', @@ -258,6 +368,192 @@ export class FileMaker implements INodeType { }, ], }, + { + displayName: 'Before find script', + name: 'setScriptBefore', + type: 'boolean', + default: false, + description: 'Define a script to be run before the action specified by the API call and after the subsequent sort.', + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + } + }, + }, + { + displayName: 'Script Name', + name: 'scriptBefore', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getScripts', + }, + options: [], + default: '', + required: true, + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + setScriptBefore: [ + true + ], + }, + }, + placeholder: 'Script Name', + description: 'The name of the FileMaker script to be run after the action specified by the API call and after the subsequent sort.', + }, + { + displayName: 'Script Parameter', + name: 'scriptBeforeParam', + type: 'string', + default: '', + required: false, + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + setScriptBefore: [ + true + ], + }, + }, + placeholder: 'Script Parameters', + description: 'A parameter for the FileMaker script.', + }, + { + displayName: 'Before sort script', + name: 'setScriptSort', + type: 'boolean', + default: false, + description: 'Define a script to be run after the action specified by the API call but before the subsequent sort.', + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + } + }, + }, + { + displayName: 'Script Name', + name: 'scriptSort', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getScripts', + }, + options: [], + default: '', + required: true, + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + setScriptSort: [ + true + ], + }, + }, + placeholder: 'Script Name', + description: 'The name of the FileMaker script to be run after the action specified by the API call but before the subsequent sort.', + }, + { + displayName: 'Script Parameter', + name: 'scriptSortParam', + type: 'string', + default: '', + required: false, + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + setScriptSort: [ + true + ], + }, + }, + placeholder: 'Script Parameters', + description: 'A parameter for the FileMaker script.', + }, + { + displayName: 'After sort script', + name: 'setScriptAfter', + type: 'boolean', + default: false, + description: 'Define a script to be run after the action specified by the API call but before the subsequent sort.', + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + } + }, + }, + { + displayName: 'Script Name', + name: 'scriptAfter', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getScripts', + }, + options: [], + default: '', + required: true, + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + setScriptAfter: [ + true + ], + }, + }, + placeholder: 'Script Name', + description: 'The name of the FileMaker script to be run after the action specified by the API call and after the subsequent sort.', + }, + { + displayName: 'Script Parameter', + name: 'scriptAfterParam', + type: 'string', + default: '', + required: false, + displayOptions: { + show: { + action: [ + 'find', + 'record', + 'records', + ], + setScriptAfter: [ + true + ], + }, + }, + placeholder: 'Script Parameters', + description: 'A parameter for the FileMaker script.', + }, // ---------------------------------- // create/edit // ---------------------------------- @@ -278,15 +574,27 @@ export class FileMaker implements INodeType { } }, { - displayName: 'Fields', - name: 'Fields', - type: 'collection', - typeOptions: { - loadOptionsMethod: 'getFields', - }, - options: [], + displayName: 'Mod Id', + name: 'modId', + description: 'The last modification ID. When you use modId, a record is edited only when the modId matches.', + type: 'number', default: '', - required: true, + displayOptions: { + show: { + action: [ + 'edit', + ], + }, + } + }, + { + displayName: 'Fields', + name: 'fieldsParametersUi', + placeholder: 'Add field', + type: 'fixedCollection', + typeOptions: { + multipleValues: true, + }, displayOptions: { show: { action: [ @@ -295,8 +603,33 @@ export class FileMaker implements INodeType { ], }, }, - placeholder: 'Layout Name', - description: 'FileMaker Layout Name.', + description: 'Fields to define', + default: {}, + options: [ + { + name: 'fields', + displayName: 'Fields', + values: [ + { + displayName: 'Field', + name: 'name', + type: 'options', + default: '', + typeOptions: { + loadOptionsMethod: 'getFields', + }, + options: [], + description: 'Field Name.', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + }, + ] + }, + ], }, // ---------------------------------- // performscript @@ -318,8 +651,24 @@ export class FileMaker implements INodeType { ], }, }, - placeholder: 'Layout Name', - description: 'FileMaker Layout Name.', + placeholder: 'Script Name', + description: 'The name of the FileMaker script to be run.', + }, + { + displayName: 'Script Parameter', + name: 'scriptParam', + type: 'string', + default: '', + required: false, + displayOptions: { + show: { + action: [ + 'performscript' + ], + }, + }, + placeholder: 'Script Parameters', + description: 'A parameter for the FileMaker script.', }, ] }; @@ -363,6 +712,26 @@ export class FileMaker implements INodeType { return returnData; }, + async getScripts(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + + let scripts; + try { + scripts = await getScripts.call(this); + } catch (err) { + throw new Error(`FileMaker Error: ${err}`); + } + for (const script of scripts) { + if (!script.isFolder) { + returnData.push({ + name: script.name, + value: script.name, + }); + } + } + return returnData; + }, + async getPortals(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; @@ -396,85 +765,89 @@ export class FileMaker implements INodeType { if (credentials === undefined) { throw new Error('No credentials got returned!'); } + const token = await getToken.call(this); + const staticData = this.getWorkflowStaticData('global'); - // Operations which overwrite the returned data - const overwriteDataOperations = []; - // Operations which overwrite the returned data and return arrays - // and has so to be merged with the data of other items - const overwriteDataOperationsArray = []; let requestOptions: OptionsWithUri; const host = credentials.host as string; const database = credentials.db as string; - //const layout = this.getNodeParameter('layout', 0, null) as string; - //const recid = this.getNodeParameter('recid', 0, null) as number; - const url = `https://${host}/fmi/data/v1`; - //const fullOperation = `${resource}:${operation}`; for (let i = 0; i < items.length; i++) { // Reset all values requestOptions = { uri: '', - headers: {}, + headers: { + 'Authorization': `Bearer ${token}`, + }, method: 'GET', json: true //rejectUnauthorized: !this.getNodeParameter('allowUnauthorizedCerts', itemIndex, false) as boolean, }; const layout = this.getNodeParameter('layout', 0) as string; - const token = await getToken.call(this); if (action === 'record') { const recid = this.getNodeParameter('recid', 0) as string; - requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records/${recid}`; - requestOptions.method = 'GET'; - requestOptions.headers = { - 'Authorization': `Bearer ${token}`, + requestOptions.qs = { + 'portal': JSON.stringify(parsePortals.call(this)), + ...parseScripts.call(this) }; } else if (action === 'records') { requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records`; - requestOptions.method = 'GET'; - requestOptions.headers = { - 'Authorization': `Bearer ${token}`, - }; - - //Handle Sort - let sort; - const setSort = this.getNodeParameter('setSort', 0, false); - if (setSort) { - sort = null; - } else { - const sortParametersUi = this.getNodeParameter('sortParametersUi', 0, {}) as IDataObject; - if (sortParametersUi.rules !== undefined) { - // @ts-ignore - for (const parameterData of sortParametersUi!.rules as IDataObject[]) { - // @ts-ignore - sort.push({ - 'fieldName': parameterData!.name as string, - 'sortOrder': parameterData!.value - }); - } - } - } - - //handle portals - let portals; - const getPortals = this.getNodeParameter('getPortals', 0); - if (!getPortals) { - portals = []; - } else { - portals = this.getNodeParameter('portals', 0); - } - requestOptions.qs = { '_offset': this.getNodeParameter('offset', 0), '_limit': this.getNodeParameter('limit', 0), - '_sort': JSON.stringify(sort), - 'portal': JSON.stringify(portals), + '_sort': JSON.stringify(parseSort.call(this)), + 'portal': JSON.stringify(parsePortals.call(this)), + ...parseScripts.call(this) + }; + } else if (action === 'find') { + requestOptions.uri = url + `/databases/${database}/layouts/${layout}/_find`; + requestOptions.method = 'POST'; + requestOptions.body = { + 'query': parseQuery.call(this), + 'offset': this.getNodeParameter('offset', 0), + 'limit': this.getNodeParameter('limit', 0), + 'layout.response': this.getNodeParameter('responseLayout', 0), + ...parseScripts.call(this) + }; + const sort = parseSort.call(this); + if (sort) { + requestOptions.body.sort = sort; + } + } else if (action === 'create') { + requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records`; + requestOptions.method = 'POST'; + requestOptions.headers['Content-Type'] = 'application/json'; + + //TODO: handle portalData + requestOptions.body = { + fieldData: {...parseFields.call(this)}, + portalData: {}, + ...parseScripts.call(this) + }; + } else if (action === 'edit') { + const recid = this.getNodeParameter('recid', 0) as string; + requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records/${recid}`; + requestOptions.method = 'PATCH'; + requestOptions.headers['Content-Type'] = 'application/json'; + + //TODO: handle portalData + requestOptions.body = { + fieldData: {...parseFields.call(this)}, + portalData: {}, + ...parseScripts.call(this) + }; + } else if (action === 'performscript') { + const scriptName = this.getNodeParameter('script', 0) as string; + requestOptions.uri = url + `/databases/${database}/layouts/${layout}/script/${scriptName}`; + requestOptions.qs = { + 'script.param': this.getNodeParameter('scriptParam', 0), }; } else { throw new Error(`The action "${action}" is not implemented yet!`); diff --git a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts index 5fc1763b79..5e24fe978b 100644 --- a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts +++ b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts @@ -122,6 +122,42 @@ export async function getPortals(this: ILoadOptionsFunctions): Promise { // } } +/** + * Make an API request to ActiveCampaign + * + * @returns {Promise} + */ +export async function getScripts(this: ILoadOptionsFunctions): Promise { // tslint:disable-line:no-any + const token = await getToken.call(this); + const credentials = this.getCredentials('FileMaker'); + const layout = this.getCurrentNodeParameter('layout') as string; + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + const host = credentials.host as string; + const db = credentials.db as string; + + const url = `https://${host}/fmi/data/v1/databases/${db}/scripts`; + const options: OptionsWithUri = { + headers: { + 'Authorization': `Bearer ${token}`, + }, + method: 'GET', + uri: url, + json: true + }; + + try { + const responseData = await this.helpers.request!(options); + return responseData.response.scripts; + + } catch (error) { + // If that data does not exist for some reason return the actual error + throw error; + } +} + export async function getToken(this: ILoadOptionsFunctions | IExecuteFunctions | IExecuteSingleFunctions): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('FileMaker'); if (credentials === undefined) { @@ -219,3 +255,106 @@ export async function logout(this: ILoadOptionsFunctions | IExecuteFunctions | I } } +export function parseSort(this: IExecuteFunctions): object | null { + let sort; + const setSort = this.getNodeParameter('setSort', 0, false); + if (!setSort) { + sort = null; + } else { + sort = []; + const sortParametersUi = this.getNodeParameter('sortParametersUi', 0, {}) as IDataObject; + if (sortParametersUi.rules !== undefined) { + // @ts-ignore + for (const parameterData of sortParametersUi!.rules as IDataObject[]) { + // @ts-ignore + sort.push({ + 'fieldName': parameterData!.name as string, + 'sortOrder': parameterData!.value + }); + } + } + } + return sort; +} + + +export function parseScripts(this: IExecuteFunctions): object | null { + const setScriptAfter = this.getNodeParameter('setScriptAfter', 0, false); + const setScriptBefore = this.getNodeParameter('setScriptBefore', 0, false); + const setScriptSort = this.getNodeParameter('setScriptSort', 0, false); + + if (!setScriptAfter && setScriptBefore && setScriptSort) { + return {}; + } else { + const scripts = { + }; + if (setScriptAfter) { + scripts.script = this.getNodeParameter('scriptAfter', 0); + scripts['script.param'] = this.getNodeParameter('scriptAfter', 0); + } + if (setScriptBefore) { + scripts['script.prerequest'] = this.getNodeParameter('scriptBefore', 0); + scripts['script.prerequest.param'] = this.getNodeParameter('scriptBeforeParam', 0); + } + if (setScriptSort) { + scripts['script.presort'] = this.getNodeParameter('scriptSort', 0); + scripts['script.presort.param'] = this.getNodeParameter('scriptSortParam', 0); + } + return scripts; + } +} + +export function parsePortals(this: IExecuteFunctions): object | null { + let portals; + const getPortals = this.getNodeParameter('getPortals', 0); + if (!getPortals) { + portals = []; + } else { + portals = this.getNodeParameter('portals', 0); + } + // @ts-ignore + return portals; +} + + +export function parseQuery(this: IExecuteFunctions): object | null { + let queries; + const queriesParamUi = this.getNodeParameter('queries', 0, {}) as IDataObject; + if (queriesParamUi.query !== undefined) { + // @ts-ignore + queries = []; + for (const queryParam of queriesParamUi!.query as IDataObject[]) { + const query = { + 'omit': queryParam.omit ? 'true' : 'false', + }; + // @ts-ignore + for (const field of queryParam!.fields!.field as IDataObject[]) { + // @ts-ignore + query[field.name] =field!.value; + } + queries.push(query); + } + } else { + queries = null; + } + // @ts-ignore + return queries; +} + +export function parseFields(this: IExecuteFunctions): object | null { + let fieldData; + const fieldsParametersUi = this.getNodeParameter('fieldsParametersUi', 0, {}) as IDataObject; + if (fieldsParametersUi.fields !== undefined) { + // @ts-ignore + fieldData = {}; + for (const field of fieldsParametersUi!.fields as IDataObject[]) { + // @ts-ignore + fieldData[field.name] =field!.value; + } + } else { + fieldData = null; + } + // @ts-ignore + return fieldData; +} + From 78070497cccb5f9b06bc6d413a25da2a8d19dae8 Mon Sep 17 00:00:00 2001 From: Romain Dunand Date: Wed, 13 Nov 2019 00:00:58 +0100 Subject: [PATCH 09/67] Handle duplicate/delete actions Code cleanup Improve error handling --- .../nodes/FileMaker/FileMaker.node.ts | 65 +++++++++++++++---- .../nodes/FileMaker/GenericFunctions.ts | 25 +++++-- 2 files changed, 72 insertions(+), 18 deletions(-) diff --git a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts index c747dc7c3e..ff8b893fa5 100644 --- a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts +++ b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts @@ -213,7 +213,7 @@ export class FileMaker implements INodeType { name: 'responseLayout', type: 'options', typeOptions: { - loadOptionsMethod: 'getLayouts', + loadOptionsMethod: 'getResponseLayouts', }, options: [], default: '', @@ -557,7 +557,7 @@ export class FileMaker implements INodeType { // ---------------------------------- // create/edit // ---------------------------------- - { + /*{ displayName: 'fieldData', name: 'fieldData', placeholder: '{"field1": "value", "field2": "value", ...}', @@ -572,7 +572,7 @@ export class FileMaker implements INodeType { ], }, } - }, + },*/ { displayName: 'Mod Id', name: 'modId', @@ -679,6 +679,28 @@ export class FileMaker implements INodeType { // select them easily async getLayouts(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; + + let layouts; + try { + layouts = await layoutsApiRequest.call(this); + } catch (err) { + throw new Error(`FileMaker Error: ${err}`); + } + for (const layout of layouts) { + returnData.push({ + name: layout.name, + value: layout.name, + }); + } + return returnData; + }, + async getResponseLayouts(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + returnData.push({ + name: 'Use main layout', + value: '', + }); + let layouts; try { layouts = await layoutsApiRequest.call(this); @@ -760,14 +782,16 @@ export class FileMaker implements INodeType { const credentials = this.getCredentials('FileMaker'); - const action = this.getNodeParameter('action', 0) as string; - if (credentials === undefined) { throw new Error('No credentials got returned!'); } - const token = await getToken.call(this); - const staticData = this.getWorkflowStaticData('global'); + let token; + try { + token = await getToken.call(this); + } catch (e) { + throw new Error(`Login fail: ${e}`); + } let requestOptions: OptionsWithUri; @@ -776,6 +800,8 @@ export class FileMaker implements INodeType { const url = `https://${host}/fmi/data/v1`; + const action = this.getNodeParameter('action', 0) as string; + for (let i = 0; i < items.length; i++) { // Reset all values requestOptions = { @@ -785,7 +811,6 @@ export class FileMaker implements INodeType { }, method: 'GET', json: true - //rejectUnauthorized: !this.getNodeParameter('allowUnauthorizedCerts', itemIndex, false) as boolean, }; const layout = this.getNodeParameter('layout', 0) as string; @@ -802,10 +827,13 @@ export class FileMaker implements INodeType { requestOptions.qs = { '_offset': this.getNodeParameter('offset', 0), '_limit': this.getNodeParameter('limit', 0), - '_sort': JSON.stringify(parseSort.call(this)), 'portal': JSON.stringify(parsePortals.call(this)), ...parseScripts.call(this) }; + const sort = parseSort.call(this); + if (sort) { + requestOptions.body.sort = sort; + } } else if (action === 'find') { requestOptions.uri = url + `/databases/${database}/layouts/${layout}/_find`; requestOptions.method = 'POST'; @@ -823,7 +851,7 @@ export class FileMaker implements INodeType { } else if (action === 'create') { requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records`; requestOptions.method = 'POST'; - requestOptions.headers['Content-Type'] = 'application/json'; + requestOptions.headers!['Content-Type'] = 'application/json'; //TODO: handle portalData requestOptions.body = { @@ -835,7 +863,7 @@ export class FileMaker implements INodeType { const recid = this.getNodeParameter('recid', 0) as string; requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records/${recid}`; requestOptions.method = 'PATCH'; - requestOptions.headers['Content-Type'] = 'application/json'; + requestOptions.headers!['Content-Type'] = 'application/json'; //TODO: handle portalData requestOptions.body = { @@ -849,6 +877,21 @@ export class FileMaker implements INodeType { requestOptions.qs = { 'script.param': this.getNodeParameter('scriptParam', 0), }; + } else if (action === 'duplicate') { + const recid = this.getNodeParameter('recid', 0) as string; + requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records/${recid}`; + requestOptions.method = 'POST'; + requestOptions.headers!['Content-Type'] = 'application/json'; + requestOptions.qs = { + ...parseScripts.call(this) + }; + } else if (action === 'delete') { + const recid = this.getNodeParameter('recid', 0) as string; + requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records/${recid}`; + requestOptions.method = 'DELETE'; + requestOptions.qs = { + ...parseScripts.call(this) + }; } else { throw new Error(`The action "${action}" is not implemented yet!`); } diff --git a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts index 5e24fe978b..a0470bcb10 100644 --- a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts +++ b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts @@ -10,8 +10,16 @@ import { } from 'n8n-workflow'; import { OptionsWithUri } from 'request'; +import {Url} from "url"; - +interface ScriptsOptions { + script?: any; //tslint:disable-line:no-any + 'script.param'?: any; //tslint:disable-line:no-any + 'script.prerequest'?: any; //tslint:disable-line:no-any + 'script.prerequest.param'?: any; //tslint:disable-line:no-any + 'script.presort'?: any; //tslint:disable-line:no-any + 'script.presort.param'?: any; //tslint:disable-line:no-any +} /** * Make an API request to ActiveCampaign * @@ -205,12 +213,16 @@ export async function getToken(this: ILoadOptionsFunctions | IExecuteFunctions | } catch (error) { console.error(error); - const errorMessage = error.response.body.messages[0].message + '(' + error.response.body.messages[0].message + ')'; - + let errorMessage; + if (error.response) { + errorMessage = error.response.body.messages[0].message + '(' + error.response.body.messages[0].message + ')'; + } else { + errorMessage = `${error.message} (${error.name})`; + } if (errorMessage !== undefined) { throw errorMessage; } - throw error.response.body; + throw error.message; } } @@ -286,11 +298,10 @@ export function parseScripts(this: IExecuteFunctions): object | null { if (!setScriptAfter && setScriptBefore && setScriptSort) { return {}; } else { - const scripts = { - }; + const scripts = {} as ScriptsOptions; if (setScriptAfter) { scripts.script = this.getNodeParameter('scriptAfter', 0); - scripts['script.param'] = this.getNodeParameter('scriptAfter', 0); + scripts!['script.param'] = this.getNodeParameter('scriptAfter', 0); } if (setScriptBefore) { scripts['script.prerequest'] = this.getNodeParameter('scriptBefore', 0); From 894a41e6d9ee025aa52f934a06a32ff8dd391848 Mon Sep 17 00:00:00 2001 From: Romain Dunand Date: Thu, 14 Nov 2019 13:02:57 +0100 Subject: [PATCH 10/67] Fix code indent style --- .../nodes/FileMaker/FileMaker.node.ts | 4 ++-- .../nodes/FileMaker/GenericFunctions.ts | 21 ++++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts index ff8b893fa5..6cd02a9bcb 100644 --- a/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts +++ b/packages/nodes-base/nodes/FileMaker/FileMaker.node.ts @@ -889,7 +889,7 @@ export class FileMaker implements INodeType { const recid = this.getNodeParameter('recid', 0) as string; requestOptions.uri = url + `/databases/${database}/layouts/${layout}/records/${recid}`; requestOptions.method = 'DELETE'; - requestOptions.qs = { + requestOptions.qs = { ...parseScripts.call(this) }; } else { @@ -914,4 +914,4 @@ export class FileMaker implements INodeType { return this.prepareOutputData(returnData); } -} \ No newline at end of file +} diff --git a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts index a0470bcb10..f2d9ff9ca3 100644 --- a/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts +++ b/packages/nodes-base/nodes/FileMaker/GenericFunctions.ts @@ -9,7 +9,7 @@ import { IDataObject, } from 'n8n-workflow'; -import { OptionsWithUri } from 'request'; +import {OptionsWithUri} from 'request'; import {Url} from "url"; interface ScriptsOptions { @@ -20,6 +20,7 @@ interface ScriptsOptions { 'script.presort'?: any; //tslint:disable-line:no-any 'script.presort.param'?: any; //tslint:disable-line:no-any } + /** * Make an API request to ActiveCampaign * @@ -269,12 +270,12 @@ export async function logout(this: ILoadOptionsFunctions | IExecuteFunctions | I export function parseSort(this: IExecuteFunctions): object | null { let sort; - const setSort = this.getNodeParameter('setSort', 0, false); + const setSort = this.getNodeParameter('setSort', 0, false); if (!setSort) { sort = null; } else { sort = []; - const sortParametersUi = this.getNodeParameter('sortParametersUi', 0, {}) as IDataObject; + const sortParametersUi = this.getNodeParameter('sortParametersUi', 0, {}) as IDataObject; if (sortParametersUi.rules !== undefined) { // @ts-ignore for (const parameterData of sortParametersUi!.rules as IDataObject[]) { @@ -291,9 +292,9 @@ export function parseSort(this: IExecuteFunctions): object | null { export function parseScripts(this: IExecuteFunctions): object | null { - const setScriptAfter = this.getNodeParameter('setScriptAfter', 0, false); - const setScriptBefore = this.getNodeParameter('setScriptBefore', 0, false); - const setScriptSort = this.getNodeParameter('setScriptSort', 0, false); + const setScriptAfter = this.getNodeParameter('setScriptAfter', 0, false); + const setScriptBefore = this.getNodeParameter('setScriptBefore', 0, false); + const setScriptSort = this.getNodeParameter('setScriptSort', 0, false); if (!setScriptAfter && setScriptBefore && setScriptSort) { return {}; @@ -330,7 +331,7 @@ export function parsePortals(this: IExecuteFunctions): object | null { export function parseQuery(this: IExecuteFunctions): object | null { let queries; - const queriesParamUi = this.getNodeParameter('queries', 0, {}) as IDataObject; + const queriesParamUi = this.getNodeParameter('queries', 0, {}) as IDataObject; if (queriesParamUi.query !== undefined) { // @ts-ignore queries = []; @@ -341,7 +342,7 @@ export function parseQuery(this: IExecuteFunctions): object | null { // @ts-ignore for (const field of queryParam!.fields!.field as IDataObject[]) { // @ts-ignore - query[field.name] =field!.value; + query[field.name] = field!.value; } queries.push(query); } @@ -354,13 +355,13 @@ export function parseQuery(this: IExecuteFunctions): object | null { export function parseFields(this: IExecuteFunctions): object | null { let fieldData; - const fieldsParametersUi = this.getNodeParameter('fieldsParametersUi', 0, {}) as IDataObject; + const fieldsParametersUi = this.getNodeParameter('fieldsParametersUi', 0, {}) as IDataObject; if (fieldsParametersUi.fields !== undefined) { // @ts-ignore fieldData = {}; for (const field of fieldsParametersUi!.fields as IDataObject[]) { // @ts-ignore - fieldData[field.name] =field!.value; + fieldData[field.name] = field!.value; } } else { fieldData = null; From 451c87d8a99ebdde41b36d47a94699f91f52f0c0 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Thu, 14 Nov 2019 18:44:07 -0500 Subject: [PATCH 11/67] :sparkles: Done --- .../credentials/MailchimpApi.credentials.ts | 2 +- .../nodes/Mailchimp/GenericFunctions.ts | 10 + .../nodes/Mailchimp/Mailchimp.node.ts | 453 +++++++++++++++++- 3 files changed, 458 insertions(+), 7 deletions(-) diff --git a/packages/nodes-base/credentials/MailchimpApi.credentials.ts b/packages/nodes-base/credentials/MailchimpApi.credentials.ts index 407d112f53..e2e98964da 100644 --- a/packages/nodes-base/credentials/MailchimpApi.credentials.ts +++ b/packages/nodes-base/credentials/MailchimpApi.credentials.ts @@ -3,7 +3,7 @@ import { NodePropertyTypes, } from 'n8n-workflow'; -export class Mailchimp implements ICredentialType { +export class MailchimpApi implements ICredentialType { name = 'mailchimpApi'; displayName = 'Mailchimp API'; properties = [ diff --git a/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts b/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts index 27fda7364a..fdf44bdc82 100644 --- a/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts @@ -41,3 +41,13 @@ export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctio throw error.response.body; } } + +export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any + let result; + try { + result = JSON.parse(json!); + } catch (exception) { + result = ''; + } + return result; +} diff --git a/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts index f2b3eca677..bea30e4ee8 100644 --- a/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts +++ b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts @@ -11,7 +11,38 @@ import { } from 'n8n-workflow'; import { mailchimpApiRequest, + validateJSON } from './GenericFunctions'; +import * as moment from 'moment'; + +enum Status { + subscribe = 'subscribe', + unsubscribed = 'unsubscribe', + cleaned = 'cleaned', + pending = 'pending', + transactional = 'transactional' +} + +interface ILocation { + latitude?: number; + longitude?: number; +} + +interface ICreateMemberBody { + listId: string; + email_address: string; + email_type?: string; + status?: Status; + language?: string; + vip?: boolean; + location?: ILocation; + ips_signup?: string; + timestamp_signup?: string; + ip_opt?: string; + timestamp_opt?: string; + tags?: string[]; + merge_fields?: IDataObject; +} export class Mailchimp implements INodeType { @@ -67,19 +98,352 @@ export class Mailchimp implements INodeType { { name: 'Create', value: 'create', - description: 'Create a new member', + description: 'Create a new member on list', }, ], default: '', description: 'The operation to perform.', }, + { + displayName: 'List', + name: 'list', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getLists', + }, + displayOptions: { + show: { + resource: [ + 'member' + ], + operation: [ + 'create', + ], + }, + }, + default: '', + options: [], + required: true, + description: 'List of lists' + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'create', + ], + }, + }, + default: '', + description: 'Email address for a subscriber.', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'member', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + name: 'Subscribed', + value: 'subscribed', + description: '', + }, + { + name: 'Unsubscribed', + value: 'unsubscribed', + description: '', + }, + { + name: 'Cleaned', + value: 'cleaned', + description: '', + }, + { + name: 'Pending', + value: 'pending', + description: '', + }, + { + name: 'Transactional', + value: 'transactional', + description: '', + }, + ], + default: '', + description: `Subscriber's current status.`, + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + resource:[ + 'member' + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + resource:[ + 'member' + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Email Type', + name: 'emailType', + type: 'options', + options: [ + { + name: 'Email', + value: 'email', + description: '', + }, + { + name: 'Text', + value: 'text', + description: '', + }, + ], + default: '', + description: 'Type of email this member asked to get', + }, + { + displayName: 'Signup IP', + name: 'ipSignup', + type: 'string', + default: '', + description: 'IP address the subscriber signed up from.', + }, + { + displayName: 'Opt-in IP', + name: 'ipOptIn', + type: 'string', + default: '', + description: 'The IP address the subscriber used to confirm their opt-in status.', + }, + { + displayName: 'Signup Timestamp', + name: 'timestampSignup', + type: 'dateTime', + default: '', + description: 'The date and time the subscriber signed up for the list in ISO 8601 format.', + }, + { + displayName: 'Language', + name: 'language', + type: 'string', + default: '', + description: `If set/detected, the subscriber's language.`, + }, + { + displayName: 'Vip', + name: 'vip', + type: 'boolean', + default: false, + description: `Vip status for subscribers`, + }, + { + displayName: 'Opt-in Timestamp', + name: 'timestampOpt', + type: 'dateTime', + default: '', + description: `The date and time the subscribe confirmed their opt-in status in ISO 8601 format.`, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + default: '', + description: `The tags that are associated with a member separeted by ,.`, + }, + ] + }, + { + displayName: 'Location', + name: 'locationFieldsUi', + type: 'fixedCollection', + placeholder: 'Add Location', + default: false, + description: `Subscriber location information.n`, + displayOptions: { + show: { + resource:[ + 'member' + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ], + }, + }, + options: [ + { + name: 'locationFieldsValues', + displayName: 'Location', + values: [ + { + displayName: 'Latitude', + name: 'latitude', + type: 'string', + required: true, + description: 'The location latitude.', + default: '' + }, + { + displayName: 'Longitude', + name: 'longitude', + type: 'string', + required: true, + description: 'The location longitude.', + default: '' + }, + ], + } + ], + }, + { + displayName: 'Merge Fields', + name: 'mergeFieldsUi', + placeholder: 'Add Merge Fields', + type: 'fixedCollection', + default: '', + typeOptions: { + multipleValues: true, + }, + displayOptions: { + show: { + resource:[ + 'member' + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ], + }, + }, + description: 'An individual merge var and value for a member.', + options: [ + { + name: 'mergeFieldsValues', + displayName: 'Field', + typeOptions: { + multipleValueButtonText: 'Add Field' + }, + values: [ + { + displayName: 'Merge Field Name', + name: 'name', + type: 'string', + required: true, + description: 'The tag used in Mailchimp campaigns and for the /members endpoint.', + default: '' + }, + { + displayName: 'Merge Field Value', + name: 'value', + required: true, + type: 'string', + default: '', + description: 'The name of the merge field.', + }, + ], + }, + ], + }, + { + displayName: 'Merge Fields', + name: 'mergeFieldsJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true + }, + default: '', + description: '', + displayOptions: { + show: { + resource:[ + 'member' + ], + operation: [ + 'create', + ], + jsonParameters: [ + true + ], + }, + }, + }, + { + displayName: 'Location', + name: 'locationJson', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true + }, + default: '', + description: '', + displayOptions: { + show: { + resource:[ + 'member' + ], + operation: [ + 'create', + ], + jsonParameters: [ + true + ], + }, + }, + }, ] }; methods = { loadOptions: { - // Get all the available projects to display them to user so that he can + + + // Get all the available lists to display them to user so that he can // select them easily async getLists(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; @@ -99,19 +463,96 @@ export class Mailchimp implements INodeType { value: listId, }); } - return returnData; }, } }; async executeSingle(this: IExecuteSingleFunctions): Promise { - + let response = {}; const resource = this.getNodeParameter('resource') as string; - const opeation = this.getNodeParameter('operation') as string; + const operation = this.getNodeParameter('operation') as string; + if (resource === 'member') { + if (operation === 'create') { + + const listId = this.getNodeParameter('list') as string; + const email = this.getNodeParameter('email') as string; + const status = this.getNodeParameter('status') as Status; + const options = this.getNodeParameter('options') as IDataObject; + const jsonActive = this.getNodeParameter('jsonParameters') as IDataObject; + + const body: ICreateMemberBody = { + listId, + email_address: email, + status + }; + if (options.emailType) { + body.email_type = options.emailType as string; + } + if (options.languaje) { + body.language = options.language as string; + } + if (options.vip) { + body.vip = options.vip as boolean; + } + if (options.ipSignup) { + body.ips_signup = options.ipSignup as string; + } + if (options.ipOptIn) { + body.ip_opt = options.ipOptIn as string; + } + if (options.timestampOpt) { + body.timestamp_opt = moment(options.timestampOpt as string).format('YYYY-MM-DD HH:MM:SS') as string; + } + if (options.timestampSignup) { + body.timestamp_signup = moment(options.timestampSignup as string).format('YYYY-MM-DD HH:MM:SS') as string; + } + if (options.tags) { + // @ts-ignore + body.tags = options.tags.split(',') as string[]; + } + if (!jsonActive) { + const locationValues = (this.getNodeParameter('locationFieldsUi') as IDataObject).locationFieldsValues as IDataObject; + if (locationValues) { + const location: ILocation = {}; + for (const key of Object.keys(locationValues)) { + if (key === 'latitude') { + location.latitude = parseInt(locationValues[key] as string, 10) as number; + } else if (key === 'longitude') { + location.longitude = parseInt(locationValues[key] as string, 10) as number; + } + } + body.location = location; + } + const mergeFieldsValues = (this.getNodeParameter('mergeFieldsUi') as IDataObject).mergeFieldsValues as IDataObject[]; + if (mergeFieldsValues) { + const mergeFields = {}; + for (let i = 0; i < mergeFieldsValues.length; i++) { + // @ts-ignore + mergeFields[mergeFieldsValues[i].name] = mergeFieldsValues[i].value; + } + body.merge_fields = mergeFields; + } + } else { + const locationJson = validateJSON(this.getNodeParameter('locationJson') as string); + const mergeFieldsJson = validateJSON(this.getNodeParameter('mergeFieldsJson') as string); + if (locationJson) { + body.location = locationJson; + } + if (mergeFieldsJson) { + body.merge_fields = mergeFieldsJson; + } + } + try { + response = await mailchimpApiRequest.call(this, `/lists/${listId}/members`, 'POST', body); + } catch (err) { + throw new Error(`Mailchimp Error: ${JSON.stringify(err)}`); + } + } + } return { - json: {} + json: response }; } } From 325fff318e32da0d0a73e2f27515216986f4ee38 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Thu, 14 Nov 2019 18:47:12 -0500 Subject: [PATCH 12/67] fixed description --- packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts index bea30e4ee8..a214fd41de 100644 --- a/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts +++ b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts @@ -374,7 +374,7 @@ export class Mailchimp implements INodeType { name: 'name', type: 'string', required: true, - description: 'The tag used in Mailchimp campaigns and for the /members endpoint.', + description: 'Merge Field name', default: '' }, { @@ -383,7 +383,7 @@ export class Mailchimp implements INodeType { required: true, type: 'string', default: '', - description: 'The name of the merge field.', + description: 'Merge field value.', }, ], }, From 8d886e37669419cdf4596448779624d8079e3a27 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Fri, 15 Nov 2019 15:10:51 -0500 Subject: [PATCH 13/67] :tada: node setup --- .../credentials/IntercomApi.credentials.ts | 18 ++++++ .../nodes/Intercom/Intercom.node.ts | 59 ++++++++++++++++++ .../nodes-base/nodes/Intercom/intercom.png | Bin 0 -> 3889 bytes packages/nodes-base/package.json | 6 +- 4 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 packages/nodes-base/credentials/IntercomApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Intercom/Intercom.node.ts create mode 100644 packages/nodes-base/nodes/Intercom/intercom.png diff --git a/packages/nodes-base/credentials/IntercomApi.credentials.ts b/packages/nodes-base/credentials/IntercomApi.credentials.ts new file mode 100644 index 0000000000..64263e889f --- /dev/null +++ b/packages/nodes-base/credentials/IntercomApi.credentials.ts @@ -0,0 +1,18 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + + +export class IntercomApi implements ICredentialType { + name = 'intercomApi'; + displayName = 'Intercom API'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Intercom/Intercom.node.ts b/packages/nodes-base/nodes/Intercom/Intercom.node.ts new file mode 100644 index 0000000000..55e61151b7 --- /dev/null +++ b/packages/nodes-base/nodes/Intercom/Intercom.node.ts @@ -0,0 +1,59 @@ +import { + IExecuteSingleFunctions, +} from 'n8n-core'; +import { + IDataObject, + INodeTypeDescription, + INodeExecutionData, + INodeType, + ILoadOptionsFunctions, + INodePropertyOptions, +} from 'n8n-workflow'; + +export class Intercom implements INodeType { + + description: INodeTypeDescription = { + displayName: 'Intercom', + name: 'intercom', + icon: 'file:intercom.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume intercom API', + defaults: { + name: 'Intercom', + color: '#c02428', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'intercomApi', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Lead', + value: 'lead', + description: '', + }, + ], + default: '', + description: 'Resource to consume.', + }, + ], + }; + + async executeSingle(this: IExecuteSingleFunctions): Promise { + + return { + json: {}, + }; + } +} diff --git a/packages/nodes-base/nodes/Intercom/intercom.png b/packages/nodes-base/nodes/Intercom/intercom.png new file mode 100644 index 0000000000000000000000000000000000000000..5fb79c11eaef5ddafeccef79968dce56aae790f0 GIT binary patch literal 3889 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 literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 1e3e1ceeae..193f0c0fc6 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -39,7 +39,8 @@ "dist/credentials/GoogleApi.credentials.js", "dist/credentials/HttpBasicAuth.credentials.js", "dist/credentials/HttpDigestAuth.credentials.js", - "dist/credentials/HttpHeaderAuth.credentials.js", + "dist/credentials/HttpHeaderAuth.credentials.js", + "dist/credentials/IntercomApi.credentials.js", "dist/credentials/Imap.credentials.js", "dist/credentials/LinkFishApi.credentials.js", "dist/credentials/MailgunApi.credentials.js", @@ -95,7 +96,8 @@ "dist/nodes/GraphQL/GraphQL.node.js", "dist/nodes/HttpRequest.node.js", "dist/nodes/If.node.js", - "dist/nodes/Interval.node.js", + "dist/nodes/Interval.node.js", + "dist/nodes/Intercom/Intercom.node.js", "dist/nodes/LinkFish/LinkFish.node.js", "dist/nodes/Mailgun/Mailgun.node.js", "dist/nodes/Mandrill/Mandrill.node.js", From b5544cba943096f0eb787e234461d27ecedd8875 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 15 Nov 2019 21:44:46 +0100 Subject: [PATCH 14/67] :zap: Small adjustments on Mailchimp node --- .../credentials/MailchimpApi.credentials.ts | 6 --- .../nodes/Mailchimp/GenericFunctions.ts | 15 ++++-- .../nodes/Mailchimp/Mailchimp.node.ts | 52 +++++++++---------- packages/nodes-base/package.json | 8 +-- 4 files changed, 40 insertions(+), 41 deletions(-) diff --git a/packages/nodes-base/credentials/MailchimpApi.credentials.ts b/packages/nodes-base/credentials/MailchimpApi.credentials.ts index e2e98964da..c6a6462bc1 100644 --- a/packages/nodes-base/credentials/MailchimpApi.credentials.ts +++ b/packages/nodes-base/credentials/MailchimpApi.credentials.ts @@ -13,11 +13,5 @@ export class MailchimpApi implements ICredentialType { type: 'string' as NodePropertyTypes, default: '', }, - { - displayName: 'Datacenter', - name: 'datacenter', - type: 'string' as NodePropertyTypes, - default: '', - }, ]; } diff --git a/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts b/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts index fdf44bdc82..6f0aeb0e8c 100644 --- a/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Mailchimp/GenericFunctions.ts @@ -7,9 +7,8 @@ import { IExecuteSingleFunctions } from 'n8n-core'; -export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resource: string, method: string, body: any = {}, headers?: object): Promise { // tslint:disable-line:no-any +export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, headers?: object): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('mailchimpApi'); - const datacenter = credentials!.datacenter as string; if (credentials === undefined) { throw new Error('No credentials got returned!'); @@ -17,13 +16,19 @@ export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctio const headerWithAuthentication = Object.assign({}, headers, { Authorization: `apikey ${credentials.apiKey}` }); - const endpoint = 'api.mailchimp.com/3.0'; + if (!(credentials.apiKey as string).includes('-')) { + throw new Error('The API key is not valid!'); + } + + const datacenter = (credentials.apiKey as string).split('-').pop(); + + const host = 'api.mailchimp.com/3.0'; const options: OptionsWithUri = { headers: headerWithAuthentication, method, - uri: `https://${datacenter}.${endpoint}${resource}`, - json: true + uri: `https://${datacenter}.${host}${endpoint}`, + json: true, }; if (Object.keys(body).length !== 0) { diff --git a/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts index a214fd41de..187512ff29 100644 --- a/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts +++ b/packages/nodes-base/nodes/Mailchimp/Mailchimp.node.ts @@ -1,26 +1,27 @@ +import * as moment from 'moment'; + import { IExecuteSingleFunctions, } from 'n8n-core'; import { IDataObject, + ILoadOptionsFunctions, INodeTypeDescription, INodeExecutionData, INodeType, - ILoadOptionsFunctions, INodePropertyOptions, } from 'n8n-workflow'; import { mailchimpApiRequest, - validateJSON + validateJSON, } from './GenericFunctions'; -import * as moment from 'moment'; enum Status { subscribe = 'subscribe', unsubscribed = 'unsubscribe', cleaned = 'cleaned', pending = 'pending', - transactional = 'transactional' + transactional = 'transactional', } interface ILocation { @@ -78,7 +79,7 @@ export class Mailchimp implements INodeType { description: 'Add member to list', }, ], - default: '', + default: 'member', required: true, description: 'Resource to consume.', }, @@ -101,7 +102,7 @@ export class Mailchimp implements INodeType { description: 'Create a new member on list', }, ], - default: '', + default: 'create', description: 'The operation to perform.', }, { @@ -114,7 +115,7 @@ export class Mailchimp implements INodeType { displayOptions: { show: { resource: [ - 'member' + 'member', ], operation: [ 'create', @@ -124,7 +125,7 @@ export class Mailchimp implements INodeType { default: '', options: [], required: true, - description: 'List of lists' + description: 'List of lists', }, { displayName: 'Email', @@ -215,7 +216,7 @@ export class Mailchimp implements INodeType { displayOptions: { show: { resource:[ - 'member' + 'member', ], operation: [ 'create', @@ -298,12 +299,12 @@ export class Mailchimp implements INodeType { name: 'locationFieldsUi', type: 'fixedCollection', placeholder: 'Add Location', - default: false, + default: {}, description: `Subscriber location information.n`, displayOptions: { show: { resource:[ - 'member' + 'member', ], operation: [ 'create', @@ -324,7 +325,7 @@ export class Mailchimp implements INodeType { type: 'string', required: true, description: 'The location latitude.', - default: '' + default: '', }, { displayName: 'Longitude', @@ -332,7 +333,7 @@ export class Mailchimp implements INodeType { type: 'string', required: true, description: 'The location longitude.', - default: '' + default: '', }, ], } @@ -343,7 +344,7 @@ export class Mailchimp implements INodeType { name: 'mergeFieldsUi', placeholder: 'Add Merge Fields', type: 'fixedCollection', - default: '', + default: {}, typeOptions: { multipleValues: true, }, @@ -366,19 +367,19 @@ export class Mailchimp implements INodeType { name: 'mergeFieldsValues', displayName: 'Field', typeOptions: { - multipleValueButtonText: 'Add Field' + multipleValueButtonText: 'Add Field', }, values: [ { - displayName: 'Merge Field Name', + displayName: 'Field Name', name: 'name', type: 'string', required: true, description: 'Merge Field name', - default: '' + default: '', }, { - displayName: 'Merge Field Value', + displayName: 'Field Value', name: 'value', required: true, type: 'string', @@ -394,20 +395,20 @@ export class Mailchimp implements INodeType { name: 'mergeFieldsJson', type: 'json', typeOptions: { - alwaysOpenEditWindow: true + alwaysOpenEditWindow: true, }, default: '', description: '', displayOptions: { show: { resource:[ - 'member' + 'member', ], operation: [ 'create', ], jsonParameters: [ - true + true, ], }, }, @@ -417,20 +418,20 @@ export class Mailchimp implements INodeType { name: 'locationJson', type: 'json', typeOptions: { - alwaysOpenEditWindow: true + alwaysOpenEditWindow: true, }, default: '', description: '', displayOptions: { show: { resource:[ - 'member' + 'member', ], operation: [ 'create', ], jsonParameters: [ - true + true, ], }, }, @@ -442,7 +443,6 @@ export class Mailchimp implements INodeType { methods = { loadOptions: { - // Get all the available lists to display them to user so that he can // select them easily async getLists(this: ILoadOptionsFunctions): Promise { @@ -552,7 +552,7 @@ export class Mailchimp implements INodeType { } } return { - json: response + json: response, }; } } diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 4c1afc425f..8b87d9e255 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -42,8 +42,8 @@ "dist/credentials/HttpHeaderAuth.credentials.js", "dist/credentials/Imap.credentials.js", "dist/credentials/LinkFishApi.credentials.js", - "dist/credentials/MailgunApi.credentials.js", - "dist/credentials/MailchimpApi.credentials.js", + "dist/credentials/MailchimpApi.credentials.js", + "dist/credentials/MailgunApi.credentials.js", "dist/credentials/MandrillApi.credentials.js", "dist/credentials/MattermostApi.credentials.js", "dist/credentials/MongoDb.credentials.js", @@ -98,8 +98,8 @@ "dist/nodes/If.node.js", "dist/nodes/Interval.node.js", "dist/nodes/LinkFish/LinkFish.node.js", - "dist/nodes/Mailgun/Mailgun.node.js", - "dist/nodes/Mailchimp/Mailchimp.node.js", + "dist/nodes/Mailchimp/Mailchimp.node.js", + "dist/nodes/Mailgun/Mailgun.node.js", "dist/nodes/Mandrill/Mandrill.node.js", "dist/nodes/Mattermost/Mattermost.node.js", "dist/nodes/Merge.node.js", From 83ad3342c217a57f4c32cde38aa333bbfc7442a6 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 15 Nov 2019 21:46:19 +0100 Subject: [PATCH 15/67] :zap: Fix version of @types/request-promise-native to fix build --- packages/nodes-base/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 8b87d9e255..60f70fc231 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -152,7 +152,7 @@ "@types/node": "^10.10.1", "@types/nodemailer": "^4.6.5", "@types/redis": "^2.8.11", - "@types/request-promise-native": "^1.0.15", + "@types/request-promise-native": "~1.0.15", "@types/xml2js": "^0.4.3", "gulp": "^4.0.0", "jest": "^24.9.0", From 4f1b292bbd0a5e2ada74341657cea895f1b37dd9 Mon Sep 17 00:00:00 2001 From: Manoj Talreja Date: Fri, 15 Nov 2019 16:02:14 -0800 Subject: [PATCH 16/67] Update CONTRIBUTING.md The link is broken, updating it to correct url. --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1b5ff8c6c3..061ce0d331 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,7 +38,7 @@ The most important directories: - [/packages/editor-ui](/packages/editor-ui) - Vue frontend workflow editor - [/packages/node-dev](/packages/node-dev) - Simple CLI to create new n8n-nodes - [/packages/nodes-base](/packages/nodes-base) - Base n8n nodes - - [/packages/worflow](/packages/worflow) - Workflow code with interfaces which + - [/packages/worflow](/packages/workflow) - Workflow code with interfaces which get used by front- & backend From 959271e4f5c71a41617881ec5f138a0984071847 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 16 Nov 2019 20:18:52 +0100 Subject: [PATCH 17/67] :bug: Fix typo --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 061ce0d331..d07ff701a6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,7 +38,7 @@ The most important directories: - [/packages/editor-ui](/packages/editor-ui) - Vue frontend workflow editor - [/packages/node-dev](/packages/node-dev) - Simple CLI to create new n8n-nodes - [/packages/nodes-base](/packages/nodes-base) - Base n8n nodes - - [/packages/worflow](/packages/workflow) - Workflow code with interfaces which + - [/packages/workflow](/packages/workflow) - Workflow code with interfaces which get used by front- & backend From be701f4b864e7f98957bad44b22ae70836863203 Mon Sep 17 00:00:00 2001 From: Torsten Curdt Date: Sat, 16 Nov 2019 22:33:03 +0100 Subject: [PATCH 18/67] added/updated descriptions to clarify node and credential fields --- packages/nodes-base/credentials/GoogleApi.credentials.ts | 2 ++ packages/nodes-base/credentials/TelegramApi.credentials.ts | 1 + packages/nodes-base/nodes/Google/GoogleSheets.node.ts | 6 +++--- packages/nodes-base/nodes/Telegram/Telegram.node.ts | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/nodes-base/credentials/GoogleApi.credentials.ts b/packages/nodes-base/credentials/GoogleApi.credentials.ts index ab676305cf..1c5d9d6f4e 100644 --- a/packages/nodes-base/credentials/GoogleApi.credentials.ts +++ b/packages/nodes-base/credentials/GoogleApi.credentials.ts @@ -13,6 +13,7 @@ export class GoogleApi implements ICredentialType { name: 'email', type: 'string' as NodePropertyTypes, default: '', + description: 'The Google Service account similar to user-808@project.iam.gserviceaccount.com.
See the tutorial on how to create one.', }, { @@ -21,6 +22,7 @@ export class GoogleApi implements ICredentialType { lines: 5, type: 'string' as NodePropertyTypes, default: '', + description: 'Use the multiline editor. Make sure there are exactly 3 lines.
-----BEGIN PRIVATE KEY-----
KEY IN A SINGLE LINE
-----END PRIVATE KEY-----', }, ]; } diff --git a/packages/nodes-base/credentials/TelegramApi.credentials.ts b/packages/nodes-base/credentials/TelegramApi.credentials.ts index f1deba6fc0..ca639e6036 100644 --- a/packages/nodes-base/credentials/TelegramApi.credentials.ts +++ b/packages/nodes-base/credentials/TelegramApi.credentials.ts @@ -13,6 +13,7 @@ export class TelegramApi implements ICredentialType { name: 'accessToken', type: 'string' as NodePropertyTypes, default: '', + description: 'Chat with the bot father to obtain the access token.', }, ]; } diff --git a/packages/nodes-base/nodes/Google/GoogleSheets.node.ts b/packages/nodes-base/nodes/Google/GoogleSheets.node.ts index 028011a3ff..be80ae375b 100644 --- a/packages/nodes-base/nodes/Google/GoogleSheets.node.ts +++ b/packages/nodes-base/nodes/Google/GoogleSheets.node.ts @@ -81,7 +81,7 @@ export class GoogleSheets implements INodeType { type: 'string', default: '', required: true, - description: 'The ID of the Google Sheet.', + description: 'The ID of the Google Sheet.
Found as part of the sheet URL https://docs.google.com/spreadsheets/d/{ID}/', }, { displayName: 'Range', @@ -89,7 +89,7 @@ export class GoogleSheets implements INodeType { type: 'string', default: 'A:F', required: true, - description: 'The columns to read and append data to.
If it contains multiple sheets it can also be
added like this: "MySheet!A:F"', + description: 'The table range to read from or to append data to. See the Google documentation for the details.
If it contains multiple sheets it can also be
added like this: "MySheet!A:F"', }, // ---------------------------------- @@ -208,7 +208,7 @@ export class GoogleSheets implements INodeType { }, }, default: 0, - description: 'Index of the row which contains the key. Starts with 0.', + description: 'Index of the row which contains the keys. Starts at 0.
The incoming node data is matched to the keys for assignment. The matching is case sensitve.', }, diff --git a/packages/nodes-base/nodes/Telegram/Telegram.node.ts b/packages/nodes-base/nodes/Telegram/Telegram.node.ts index b10b26cbfd..9697f94d5d 100644 --- a/packages/nodes-base/nodes/Telegram/Telegram.node.ts +++ b/packages/nodes-base/nodes/Telegram/Telegram.node.ts @@ -433,7 +433,7 @@ export class Telegram implements INodeType { }, }, required: true, - description: 'Unique identifier for the target chat or username of the target
channel (in the format @channelusername).', + description: 'Unique identifier for the target chat or username of the target
channel (in the format @channelusername). To find your chat id ask @get_id_bot.', }, { displayName: 'Message ID', From 7a1983783372f28813afee113349db4bee5319b0 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Sat, 16 Nov 2019 17:16:11 -0500 Subject: [PATCH 19/67] :lipstick: create lead UI done --- .../nodes/Intercom/GenericFunctions.ts | 45 +++++ .../nodes/Intercom/Intercom.node.ts | 35 ++++ .../nodes/Intercom/LeadDescription.ts | 172 ++++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 packages/nodes-base/nodes/Intercom/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Intercom/LeadDescription.ts diff --git a/packages/nodes-base/nodes/Intercom/GenericFunctions.ts b/packages/nodes-base/nodes/Intercom/GenericFunctions.ts new file mode 100644 index 0000000000..007aa700cb --- /dev/null +++ b/packages/nodes-base/nodes/Intercom/GenericFunctions.ts @@ -0,0 +1,45 @@ +import { OptionsWithUri } from 'request'; + +import { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IExecuteSingleFunctions +} from 'n8n-core'; + +export async function intercomApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resource: string, method: string, body: any = {}, headers?: object): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('intercomApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const headerWithAuthentication = Object.assign({}, headers, + { Authorization: `Bearer ${credentials.apiKey}`, Accept: 'application/json' }); + + const endpoint = 'api.intercom.io'; + + const options: OptionsWithUri = { + headers: headerWithAuthentication, + method, + uri: `https://${endpoint}${resource}`, + body, + json: true + }; + + console.log(options) + + + try { + return await this.helpers.request!(options); + } catch (error) { + console.error(error); + + const errorMessage = error.response.body.message || error.response.body.Message; + + if (errorMessage !== undefined) { + throw errorMessage; + } + throw error.response.body; + } +} diff --git a/packages/nodes-base/nodes/Intercom/Intercom.node.ts b/packages/nodes-base/nodes/Intercom/Intercom.node.ts index 55e61151b7..e746715fbb 100644 --- a/packages/nodes-base/nodes/Intercom/Intercom.node.ts +++ b/packages/nodes-base/nodes/Intercom/Intercom.node.ts @@ -9,6 +9,13 @@ import { ILoadOptionsFunctions, INodePropertyOptions, } from 'n8n-workflow'; +import { + leadOpeations, + leadFields, +} from './LeadDescription'; +import { + intercomApiRequest, +} from './GenericFunctions'; export class Intercom implements INodeType { @@ -47,9 +54,37 @@ export class Intercom implements INodeType { default: '', description: 'Resource to consume.', }, + ...leadOpeations, + ...leadFields, ], }; + methods = { + loadOptions: { + // Get all the available companies to display them to user so that he can + // select them easily + async getCompanies(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + let companies, response; + try { + response = await intercomApiRequest.call(this, '/companies', 'GET'); + } catch (err) { + throw new Error(`Intercom Error: ${err}`); + } + companies = response.companies; + for (const company of companies) { + const companyName = company.name; + const companyId = company.id; + returnData.push({ + name: companyName, + value: companyId, + }); + } + return returnData; + } + }, + }; + async executeSingle(this: IExecuteSingleFunctions): Promise { return { diff --git a/packages/nodes-base/nodes/Intercom/LeadDescription.ts b/packages/nodes-base/nodes/Intercom/LeadDescription.ts new file mode 100644 index 0000000000..ef4e8ea3ff --- /dev/null +++ b/packages/nodes-base/nodes/Intercom/LeadDescription.ts @@ -0,0 +1,172 @@ +import { INodeProperties } from "n8n-workflow"; + +export const leadOpeations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'lead', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new lead', + }, + ], + default: '', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const leadFields = [ + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'create', + ], + }, + }, + description: 'The email of the user.', + }, + { + displayName: 'JSON Parameters', + name: 'jsonParameters', + type: 'boolean', + default: false, + description: '', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'lead' + ], + }, + }, + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'lead' + ], + }, + }, + options: [ + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + required: false, + description: 'The phone number of the user', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + placeholder: '', + description: 'Name of the user', + }, + { + displayName: 'Unsubscribed From Emails', + name: 'unsubscribedFromEmails', + type: 'boolean', + default: '', + placeholder: '', + description: 'Whether the Lead is unsubscribed from emails', + }, + { + displayName: 'Update Last Request At', + name: 'updateLastRequestAt', + type: 'boolean', + default: false, + options: [], + required: false, + description: `A boolean value, which if true, instructs Intercom to update the users' last_request_at value to the current API service time in UTC. default value if not sent is false.`, + }, + { + displayName: 'Companies', + name: 'companies', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getCompanies', + }, + default: [], + required: false, + description: 'Identifies the companies this user belongs to.', + }, + ] + }, + { + displayName: 'Custom Attributes', + name: 'customAttributes', + type: 'fixedCollection', + default: '', + placeholder: 'Add Attribute', + typeOptions: { + multipleValues: true, + }, + required: false, + displayOptions: { + show: { + resource: [ + 'lead', + ], + operation: [ + 'create', + ], + jsonParameters: [ + false, + ], + }, + }, + options: [ + { + name: 'customAttributesValues', + displayName: 'Attributes', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + }, + ], + } + ], + description: 'A hash of key/value pairs to represent custom data you want to attribute to a user.', + }, +] as INodeProperties[]; + From edf216ba246f5b1b5a9030f72d1b01621f93cab9 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sat, 16 Nov 2019 23:36:50 +0100 Subject: [PATCH 20/67] :lipstick: Display new line in JSON data --- packages/editor-ui/package.json | 2 +- packages/editor-ui/src/components/RunData.vue | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 193e6cf8d8..64361c4890 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -71,7 +71,7 @@ "typescript": "~3.5.2", "vue": "^2.6.9", "vue-cli-plugin-webpack-bundle-analyzer": "^1.3.0", - "vue-json-pretty": "^1.4.1", + "vue-json-viewer": "^2.2.8", "vue-prism-editor": "^0.3.0", "vue-router": "^3.0.6", "vue-template-compiler": "^2.5.17", diff --git a/packages/editor-ui/src/components/RunData.vue b/packages/editor-ui/src/components/RunData.vue index 8e81d5e197..0a2142eb63 100644 --- a/packages/editor-ui/src/components/RunData.vue +++ b/packages/editor-ui/src/components/RunData.vue @@ -95,11 +95,12 @@ - - + :value="jsonData" + :expand-depth=10 + >
@@ -162,7 +163,7 @@