diff --git a/README.md b/README.md index 70364ce8e4..fb2a62703e 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ received or lost a star. ## Available integrations -n8n has 80+ different nodes to automate workflows. The list can be found on: [https://n8n.io/nodes](https://n8n.io/nodes) +n8n has 100+ different nodes to automate workflows. The list can be found on: [https://n8n.io/nodes](https://n8n.io/nodes) ## Documentation diff --git a/docker/images/n8n/README.md b/docker/images/n8n/README.md index 90dbfe436b..16d12e226d 100644 --- a/docker/images/n8n/README.md +++ b/docker/images/n8n/README.md @@ -34,7 +34,7 @@ Slack notification every time a Github repository received or lost a star. ## Available integrations -n8n has 50+ different nodes to automate workflows. The list can be found on: [https://n8n.io/nodes](https://n8n.io/nodes) +n8n has 100+ different nodes to automate workflows. The list can be found on: [https://n8n.io/nodes](https://n8n.io/nodes) ## Documentation diff --git a/packages/cli/README.md b/packages/cli/README.md index 7495cecc23..e65df065d6 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -32,7 +32,7 @@ Slack notification every time a Github repository received or lost a star. ## Available integrations -n8n has 80+ different nodes to automate workflows. The list can be found on: [https://n8n.io/nodes](https://n8n.io/nodes) +n8n has 100+ different nodes to automate workflows. The list can be found on: [https://n8n.io/nodes](https://n8n.io/nodes) ## Documentation diff --git a/packages/cli/package.json b/packages/cli/package.json index ab390eab24..bd01ed6b1b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.49.0", + "version": "0.50.1", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", @@ -93,10 +93,10 @@ "localtunnel": "^2.0.0", "lodash.get": "^4.4.2", "mongodb": "^3.2.3", - "n8n-core": "~0.22.0", - "n8n-editor-ui": "~0.33.0", - "n8n-nodes-base": "~0.44.0", - "n8n-workflow": "~0.20.0", + "n8n-core": "~0.23.0", + "n8n-editor-ui": "~0.34.0", + "n8n-nodes-base": "~0.45.1", + "n8n-workflow": "~0.21.0", "open": "^7.0.0", "pg": "^7.11.0", "request-promise-native": "^1.0.7", diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index 9604fd9de6..25255988dc 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -117,22 +117,24 @@ export class ActiveWorkflowRunner { throw new ResponseHelper.ResponseError('The requested webhook is not registred.', 404, 404); } + const workflowData = await Db.collections.Workflow!.findOne(webhookData.workflowId); + if (workflowData === undefined) { + throw new ResponseHelper.ResponseError(`Could not find workflow with id "${webhookData.workflowId}"`, 404, 404); + } + + const nodeTypes = NodeTypes(); + const workflow = new Workflow(webhookData.workflowId, workflowData.nodes, workflowData.connections, workflowData.active, nodeTypes, workflowData.staticData, workflowData.settings); + // Get the node which has the webhook defined to know where to start from and to // get additional data - const workflowStartNode = webhookData.workflow.getNode(webhookData.node); + const workflowStartNode = workflow.getNode(webhookData.node); if (workflowStartNode === null) { throw new ResponseHelper.ResponseError('Could not find node to process webhook.', 404, 404); } - const executionMode = 'webhook'; - - const workflowData = await Db.collections.Workflow!.findOne(webhookData.workflow.id!); - - if (workflowData === undefined) { - throw new ResponseHelper.ResponseError(`Could not find workflow with id "${webhookData.workflow.id}"`, 404, 404); - } return new Promise((resolve, reject) => { - WebhookHelpers.executeWebhook(webhookData, workflowData, workflowStartNode, executionMode, undefined, req, res, (error: Error | null, data: object) => { + const executionMode = 'webhook'; + WebhookHelpers.executeWebhook(workflow, webhookData, workflowData, workflowStartNode, executionMode, undefined, req, res, (error: Error | null, data: object) => { if (error !== null) { return reject(error); } @@ -202,7 +204,9 @@ export class ActiveWorkflowRunner { const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData); for (const webhookData of webhooks) { - await this.activeWebhooks!.add(webhookData, mode); + await this.activeWebhooks!.add(workflow, webhookData, mode); + // Save static data! + await WorkflowHelpers.saveStaticData(workflow); } } @@ -214,8 +218,19 @@ export class ActiveWorkflowRunner { * @returns * @memberof ActiveWorkflowRunner */ - removeWorkflowWebhooks(workflowId: string): Promise { - return this.activeWebhooks!.removeByWorkflowId(workflowId); + async removeWorkflowWebhooks(workflowId: string): Promise { + const workflowData = await Db.collections.Workflow!.findOne(workflowId); + if (workflowData === undefined) { + throw new Error(`Could not find workflow with id "${workflowId}"`); + } + + const nodeTypes = NodeTypes(); + const workflow = new Workflow(workflowId, workflowData.nodes, workflowData.connections, workflowData.active, nodeTypes, workflowData.staticData, workflowData.settings); + + await this.activeWebhooks!.removeWorkflow(workflow); + + // Save the static workflow data if needed + await WorkflowHelpers.saveStaticData(workflow); } @@ -348,7 +363,7 @@ export class ActiveWorkflowRunner { await this.activeWorkflows.add(workflowId, workflowInstance, additionalData, getTriggerFunctions, getPollFunctions); if (this.activationErrors[workflowId] !== undefined) { - // If there were any activation errors delete them + // If there were activation errors delete them delete this.activationErrors[workflowId]; } } catch (error) { @@ -380,16 +395,9 @@ export class ActiveWorkflowRunner { */ async remove(workflowId: string): Promise { if (this.activeWorkflows !== null) { - const workflowData = this.activeWorkflows.get(workflowId); - // Remove all the webhooks of the workflow await this.removeWorkflowWebhooks(workflowId); - if (workflowData) { - // Save the static workflow data if needed - await WorkflowHelpers.saveStaticData(workflowData.workflow); - } - if (this.activationErrors[workflowId] !== undefined) { // If there were any activation errors delete them delete this.activationErrors[workflowId]; diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 8fff1ef217..de561725ed 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -132,7 +132,7 @@ class App { const authIgnoreRegex = new RegExp(`^\/(rest|healthz|${this.endpointWebhook}|${this.endpointWebhookTest})\/?.*$`); // Check for basic auth credentials if activated - const basicAuthActive = config.get('security.basicAuth.active') as boolean; + const basicAuthActive = config.get('security.basicAuth.active') as boolean; if (basicAuthActive === true) { const basicAuthUser = await GenericHelpers.getConfigValue('security.basicAuth.user') as string; if (basicAuthUser === '') { @@ -1072,7 +1072,16 @@ class App { // Removes a test webhook this.app.delete('/rest/test-webhook/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const workflowId = req.params.id; - return this.testWebhooks.cancelTestWebhook(workflowId); + + const workflowData = await Db.collections.Workflow!.findOne(workflowId); + if (workflowData === undefined) { + throw new ResponseHelper.ResponseError(`Could not find workflow with id "${workflowId}" so webhook could not be deleted!`); + } + + const nodeTypes = NodeTypes(); + const workflow = new Workflow(workflowId.toString(), workflowData.nodes, workflowData.connections, workflowData.active, nodeTypes, workflowData.staticData, workflowData.settings); + + return this.testWebhooks.cancelTestWebhook(workflowId, workflow); })); diff --git a/packages/cli/src/TestWebhooks.ts b/packages/cli/src/TestWebhooks.ts index 540aa9e0f8..8631f259ab 100644 --- a/packages/cli/src/TestWebhooks.ts +++ b/packages/cli/src/TestWebhooks.ts @@ -1,11 +1,18 @@ import * as express from 'express'; +import { + In as findIn, + FindManyOptions, +} from 'typeorm'; import { + Db, IResponseCallbackData, + IWorkflowDb, + NodeTypes, Push, ResponseHelper, WebhookHelpers, - IWorkflowDb, + WorkflowHelpers, } from './'; import { @@ -60,9 +67,17 @@ export class TestWebhooks { throw new ResponseHelper.ResponseError('The requested webhook is not registred.', 404, 404); } + const workflowData = await Db.collections.Workflow!.findOne(webhookData.workflowId); + if (workflowData === undefined) { + throw new ResponseHelper.ResponseError(`Could not find workflow with id "${webhookData.workflowId}"`, 404, 404); + } + + const nodeTypes = NodeTypes(); + const workflow = new Workflow(webhookData.workflowId, workflowData.nodes, workflowData.connections, workflowData.active, nodeTypes, workflowData.staticData, workflowData.settings); + // Get the node which has the webhook defined to know where to start from and to // get additional data - const workflowStartNode = webhookData.workflow.getNode(webhookData.node); + const workflowStartNode = workflow.getNode(webhookData.node); if (workflowStartNode === null) { throw new ResponseHelper.ResponseError('Could not find node to process webhook.', 404, 404); } @@ -72,8 +87,7 @@ export class TestWebhooks { return new Promise(async (resolve, reject) => { try { const executionMode = 'manual'; - - const executionId = await WebhookHelpers.executeWebhook(webhookData, this.testWebhookData[webhookKey].workflowData, workflowStartNode, executionMode, this.testWebhookData[webhookKey].sessionId, request, response, (error: Error | null, data: IResponseCallbackData) => { + const executionId = await WebhookHelpers.executeWebhook(workflow, webhookData, this.testWebhookData[webhookKey].workflowData, workflowStartNode, executionMode, this.testWebhookData[webhookKey].sessionId, request, response, (error: Error | null, data: IResponseCallbackData) => { if (error !== null) { return reject(error); } @@ -90,7 +104,7 @@ export class TestWebhooks { // Inform editor-ui that webhook got received if (this.testWebhookData[webhookKey].sessionId !== undefined) { const pushInstance = Push.getInstance(); - pushInstance.send('testWebhookReceived', { workflowId: webhookData.workflow.id, executionId }, this.testWebhookData[webhookKey].sessionId!); + pushInstance.send('testWebhookReceived', { workflowId: webhookData.workflowId, executionId }, this.testWebhookData[webhookKey].sessionId!); } } catch (error) { @@ -100,7 +114,7 @@ export class TestWebhooks { // Remove the webhook clearTimeout(this.testWebhookData[webhookKey].timeout); delete this.testWebhookData[webhookKey]; - this.activeWebhooks!.removeByWorkflowId(webhookData.workflow.id!.toString()); + this.activeWebhooks!.removeWorkflow(workflow); }); } @@ -125,7 +139,7 @@ export class TestWebhooks { // Remove test-webhooks automatically if they do not get called (after 120 seconds) const timeout = setTimeout(() => { - this.cancelTestWebhook(workflowData.id.toString()); + this.cancelTestWebhook(workflowData.id.toString(), workflow); }, 120000); let key: string; @@ -136,9 +150,12 @@ export class TestWebhooks { timeout, workflowData, }; - await this.activeWebhooks!.add(webhookData, mode); + await this.activeWebhooks!.add(workflow, webhookData, mode); } + // Save static data! + await WorkflowHelpers.saveStaticData(workflow); + return true; } @@ -150,7 +167,7 @@ export class TestWebhooks { * @returns {boolean} * @memberof TestWebhooks */ - cancelTestWebhook(workflowId: string): boolean { + cancelTestWebhook(workflowId: string, workflow: Workflow): boolean { let foundWebhook = false; for (const webhookKey of Object.keys(this.testWebhookData)) { const webhookData = this.testWebhookData[webhookKey]; @@ -175,7 +192,7 @@ export class TestWebhooks { // Remove the webhook delete this.testWebhookData[webhookKey]; - this.activeWebhooks!.removeByWorkflowId(workflowId); + this.activeWebhooks!.removeWorkflow(workflow); } return foundWebhook; @@ -189,8 +206,22 @@ export class TestWebhooks { if (this.activeWebhooks === null) { return; } + const nodeTypes = NodeTypes(); - return this.activeWebhooks.removeAll(); + const findQuery = { + where: { + id: findIn(this.activeWebhooks.getWorkflowIds()) + }, + } as FindManyOptions; + + const workflowsDb = await Db.collections.Workflow!.find(findQuery); + const workflows: Workflow[] = []; + for (const workflowData of workflowsDb) { + const workflow = new Workflow(workflowData.id.toString(), workflowData.nodes, workflowData.connections, workflowData.active, nodeTypes, workflowData.staticData, workflowData.settings); + workflows.push(workflow); + } + + return this.activeWebhooks.removeAll(workflows); } } diff --git a/packages/cli/src/WebhookHelpers.ts b/packages/cli/src/WebhookHelpers.ts index b13cc3b0ea..da95f7416a 100644 --- a/packages/cli/src/WebhookHelpers.ts +++ b/packages/cli/src/WebhookHelpers.ts @@ -84,9 +84,9 @@ export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflo * @param {((error: Error | null, data: IResponseCallbackData) => void)} responseCallback * @returns {(Promise)} */ - export async function executeWebhook(webhookData: IWebhookData, workflowData: IWorkflowDb, workflowStartNode: INode, executionMode: WorkflowExecuteMode, sessionId: string | undefined, req: express.Request, res: express.Response, responseCallback: (error: Error | null, data: IResponseCallbackData) => void): Promise { + export async function executeWebhook(workflow: Workflow, webhookData: IWebhookData, workflowData: IWorkflowDb, workflowStartNode: INode, executionMode: WorkflowExecuteMode, sessionId: string | undefined, req: express.Request, res: express.Response, responseCallback: (error: Error | null, data: IResponseCallbackData) => void): Promise { // Get the nodeType to know which responseMode is set - const nodeType = webhookData.workflow.nodeTypes.getByName(workflowStartNode.type); + const nodeType = workflow.nodeTypes.getByName(workflowStartNode.type); if (nodeType === undefined) { const errorMessage = `The type of the webhook node "${workflowStartNode.name}" is not known.`; responseCallback(new Error(errorMessage), {}); @@ -94,8 +94,8 @@ export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflo } // Get the responseMode - const responseMode = webhookData.workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseMode'], 'onReceived'); - const responseCode = webhookData.workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseCode'], 200) as number; + const responseMode = workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseMode'], 'onReceived'); + const responseCode = workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseCode'], 200) as number; if (!['onReceived', 'lastNode'].includes(responseMode as string)) { // If the mode is not known we error. Is probably best like that instead of using @@ -122,7 +122,7 @@ export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflo let webhookResultData: IWebhookResponseData; try { - webhookResultData = await webhookData.workflow.runWebhook(webhookData, workflowStartNode, additionalData, NodeExecuteFunctions, executionMode); + webhookResultData = await workflow.runWebhook(webhookData, workflowStartNode, additionalData, NodeExecuteFunctions, executionMode); } catch (e) { // Send error response to webhook caller const errorMessage = 'Workflow Webhook Error: Workflow could not be started!'; @@ -287,7 +287,7 @@ export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflo return data; } - const responseData = webhookData.workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseData'], 'firstEntryJson'); + const responseData = workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseData'], 'firstEntryJson'); if (didSendResponse === false) { let data: IDataObject | IDataObject[]; @@ -296,13 +296,13 @@ export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflo // Return the JSON data of the first entry data = returnData.data!.main[0]![0].json; - const responsePropertyName = webhookData.workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responsePropertyName'], undefined); + const responsePropertyName = workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responsePropertyName'], undefined); if (responsePropertyName !== undefined) { data = get(data, responsePropertyName as string) as IDataObject; } - const responseContentType = webhookData.workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseContentType'], undefined); + const responseContentType = workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseContentType'], undefined); if (responseContentType !== undefined) { // Send the webhook response manually to be able to set the content-type @@ -329,7 +329,7 @@ export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflo didSendResponse = true; } - const responseBinaryPropertyName = webhookData.workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseBinaryPropertyName'], 'data'); + const responseBinaryPropertyName = workflow.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseBinaryPropertyName'], 'data'); if (responseBinaryPropertyName === undefined && didSendResponse === false) { responseCallback(new Error('No "responseBinaryPropertyName" is set.'), {}); diff --git a/packages/cli/src/WorkflowExecuteAdditionalData.ts b/packages/cli/src/WorkflowExecuteAdditionalData.ts index d5b471def9..fa7f0abc0a 100644 --- a/packages/cli/src/WorkflowExecuteAdditionalData.ts +++ b/packages/cli/src/WorkflowExecuteAdditionalData.ts @@ -9,6 +9,7 @@ import { Push, ResponseHelper, WebhookHelpers, + WorkflowCredentials, WorkflowHelpers, } from './'; @@ -306,6 +307,10 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi const additionalDataIntegrated = await getBase(additionalData.credentials); additionalDataIntegrated.hooks = getWorkflowHooksIntegrated(mode, executionId, workflowData!, { parentProcessMode: additionalData.hooks!.mode }); + // Get the needed credentials for the current workflow as they will differ to the ones of the + // calling workflow. + additionalDataIntegrated.credentials = await WorkflowCredentials(workflowData!.nodes); + // Find Start-Node const requiredNodeTypes = ['n8n-nodes-base.start']; let startNode: INode | undefined; diff --git a/packages/core/package.json b/packages/core/package.json index 8f69797a5e..bd6ea3e099 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "0.22.0", + "version": "0.23.0", "description": "Core functionality of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", @@ -43,7 +43,7 @@ "crypto-js": "^3.1.9-1", "lodash.get": "^4.4.2", "mmmagic": "^0.5.2", - "n8n-workflow": "~0.20.0", + "n8n-workflow": "~0.21.0", "p-cancelable": "^2.0.0", "request-promise-native": "^1.0.7" }, diff --git a/packages/core/src/ActiveWebhooks.ts b/packages/core/src/ActiveWebhooks.ts index 6468044d47..17cf753830 100644 --- a/packages/core/src/ActiveWebhooks.ts +++ b/packages/core/src/ActiveWebhooks.ts @@ -1,6 +1,7 @@ import { IWebhookData, WebhookHttpMethod, + Workflow, WorkflowExecuteMode, } from 'n8n-workflow'; @@ -29,29 +30,26 @@ export class ActiveWebhooks { * @returns {Promise} * @memberof ActiveWebhooks */ - async add(webhookData: IWebhookData, mode: WorkflowExecuteMode): Promise { - if (webhookData.workflow.id === undefined) { + async add(workflow: Workflow, webhookData: IWebhookData, mode: WorkflowExecuteMode): Promise { + if (workflow.id === undefined) { throw new Error('Webhooks can only be added for saved workflows as an id is needed!'); } - if (this.workflowWebhooks[webhookData.workflow.id] === undefined) { - this.workflowWebhooks[webhookData.workflow.id] = []; + if (this.workflowWebhooks[webhookData.workflowId] === undefined) { + this.workflowWebhooks[webhookData.workflowId] = []; } // Make the webhook available directly because sometimes to create it successfully // it gets called this.webhookUrls[this.getWebhookKey(webhookData.httpMethod, webhookData.path)] = webhookData; - const webhookExists = await webhookData.workflow.runWebhookMethod('checkExists', webhookData, NodeExecuteFunctions, mode, this.testWebhooks); + const webhookExists = await workflow.runWebhookMethod('checkExists', webhookData, NodeExecuteFunctions, mode, this.testWebhooks); if (webhookExists === false) { // If webhook does not exist yet create it - await webhookData.workflow.runWebhookMethod('create', webhookData, NodeExecuteFunctions, mode, this.testWebhooks); + await workflow.runWebhookMethod('create', webhookData, NodeExecuteFunctions, mode, this.testWebhooks); } - // Run the "activate" hooks on the nodes - await webhookData.workflow.runNodeHooks('activate', webhookData, NodeExecuteFunctions, mode); - - this.workflowWebhooks[webhookData.workflow.id].push(webhookData); + this.workflowWebhooks[webhookData.workflowId].push(webhookData); } @@ -73,6 +71,17 @@ export class ActiveWebhooks { } + /** + * Returns the ids of all the workflows which have active webhooks + * + * @returns {string[]} + * @memberof ActiveWebhooks + */ + getWorkflowIds(): string[] { + return Object.keys(this.workflowWebhooks); + } + + /** * Returns key to uniquely identify a webhook * @@ -89,11 +98,13 @@ export class ActiveWebhooks { /** * Removes all webhooks of a workflow * - * @param {string} workflowId + * @param {Workflow} workflow * @returns {boolean} * @memberof ActiveWebhooks */ - async removeByWorkflowId(workflowId: string): Promise { + async removeWorkflow(workflow: Workflow): Promise { + const workflowId = workflow.id!.toString(); + if (this.workflowWebhooks[workflowId] === undefined) { // If it did not exist then there is nothing to remove return false; @@ -105,10 +116,7 @@ export class ActiveWebhooks { // Go through all the registered webhooks of the workflow and remove them for (const webhookData of webhooks) { - await webhookData.workflow.runWebhookMethod('delete', webhookData, NodeExecuteFunctions, mode, this.testWebhooks); - - // Run the "deactivate" hooks on the nodes - await webhookData.workflow.runNodeHooks('deactivate', webhookData, NodeExecuteFunctions, mode); + await workflow.runWebhookMethod('delete', webhookData, NodeExecuteFunctions, mode, this.testWebhooks); delete this.webhookUrls[this.getWebhookKey(webhookData.httpMethod, webhookData.path)]; } @@ -121,55 +129,16 @@ export class ActiveWebhooks { /** - * Removes all the currently active webhooks + * Removes all the webhooks of the given workflow */ - async removeAll(): Promise { - const workflowIds = Object.keys(this.workflowWebhooks); - + async removeAll(workflows: Workflow[]): Promise { const removePromises = []; - for (const workflowId of workflowIds) { - removePromises.push(this.removeByWorkflowId(workflowId)); + for (const workflow of workflows) { + removePromises.push(this.removeWorkflow(workflow)); } await Promise.all(removePromises); return; } - - // /** - // * Removes a single webhook by its key. - // * Currently not used, runNodeHooks for "deactivate" is missing - // * - // * @param {string} webhookKey - // * @returns {boolean} - // * @memberof ActiveWebhooks - // */ - // removeByWebhookKey(webhookKey: string): boolean { - // if (this.webhookUrls[webhookKey] === undefined) { - // // If it did not exist then there is nothing to remove - // return false; - // } - - // const webhookData = this.webhookUrls[webhookKey]; - - // // Remove from workflow-webhooks - // const workflowWebhooks = this.workflowWebhooks[webhookData.workflowId]; - // for (let index = 0; index < workflowWebhooks.length; index++) { - // if (workflowWebhooks[index].path === webhookData.path) { - // workflowWebhooks.splice(index, 1); - // break; - // } - // } - - // if (workflowWebhooks.length === 0) { - // // When there are no webhooks left for any workflow remove it totally - // delete this.workflowWebhooks[webhookData.workflowId]; - // } - - // // Remove from webhook urls - // delete this.webhookUrls[webhookKey]; - - // return true; - // } - } diff --git a/packages/core/src/ActiveWorkflows.ts b/packages/core/src/ActiveWorkflows.ts index f450a28064..5576aeed3e 100644 --- a/packages/core/src/ActiveWorkflows.ts +++ b/packages/core/src/ActiveWorkflows.ts @@ -69,9 +69,7 @@ export class ActiveWorkflows { async add(id: string, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, getTriggerFunctions: IGetExecuteTriggerFunctions, getPollFunctions: IGetExecutePollFunctions): Promise { console.log('ADD ID (active): ' + id); - this.workflowData[id] = { - workflow - }; + this.workflowData[id] = {}; const triggerNodes = workflow.getTriggerNodes(); let triggerResponse: ITriggerResponse | undefined; @@ -170,7 +168,6 @@ export class ActiveWorkflows { const pollResponse = await workflow.runPoll(node, pollFunctions); if (pollResponse !== null) { - // TODO: Run workflow pollFunctions.__emit(pollResponse); } }; diff --git a/packages/core/src/Interfaces.ts b/packages/core/src/Interfaces.ts index a45d393dc3..cfbc19bb8e 100644 --- a/packages/core/src/Interfaces.ts +++ b/packages/core/src/Interfaces.ts @@ -14,7 +14,6 @@ import { ITriggerResponse, IWebhookFunctions as IWebhookFunctionsBase, IWorkflowSettings as IWorkflowSettingsWorkflow, - Workflow, } from 'n8n-workflow'; @@ -125,5 +124,4 @@ export interface INodeInputDataConnections { export interface IWorkflowData { pollResponses?: IPollResponse[]; triggerResponses?: ITriggerResponse[]; - workflow: Workflow; } diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index a73819aa91..9d6ab56eba 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "0.33.0", + "version": "0.34.0", "description": "Workflow Editor UI for n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", @@ -63,7 +63,7 @@ "lodash.debounce": "^4.0.8", "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", - "n8n-workflow": "~0.20.0", + "n8n-workflow": "~0.21.0", "node-sass": "^4.12.0", "prismjs": "^1.17.1", "quill": "^2.0.0-dev.3", diff --git a/packages/editor-ui/src/components/NodeSettings.vue b/packages/editor-ui/src/components/NodeSettings.vue index 4189d78c35..ca3cfde88d 100644 --- a/packages/editor-ui/src/components/NodeSettings.vue +++ b/packages/editor-ui/src/components/NodeSettings.vue @@ -57,7 +57,7 @@ import ParameterInputFull from '@/components/ParameterInputFull.vue'; import ParameterInputList from '@/components/ParameterInputList.vue'; import NodeCredentials from '@/components/NodeCredentials.vue'; import NodeWebhooks from '@/components/NodeWebhooks.vue'; -import { get, set } from 'lodash'; +import { get, set, unset } from 'lodash'; import { genericHelpers } from '@/components/mixins/genericHelpers'; import { nodeHelpers } from '@/components/mixins/nodeHelpers'; @@ -369,8 +369,11 @@ export default mixins( Vue.set(nodeParameters as object, path, data); } } else { - // For everything else - set(nodeParameters as object, parameterPath, newValue); + if (newValue === undefined) { + unset(nodeParameters as object, parameterPath); + } else { + set(nodeParameters as object, parameterPath, newValue); + } } // Get the parameters with the now new defaults according to the diff --git a/packages/editor-ui/src/components/ParameterInputFull.vue b/packages/editor-ui/src/components/ParameterInputFull.vue index c7ce18c98a..4d488faf92 100644 --- a/packages/editor-ui/src/components/ParameterInputFull.vue +++ b/packages/editor-ui/src/components/ParameterInputFull.vue @@ -30,6 +30,9 @@ export default Vue }, computed: { isMultiLineParameter () { + if (this.level > 4) { + return true; + } const rows = this.getArgument('rows'); if (rows !== undefined && rows > 1) { return true; @@ -37,6 +40,9 @@ export default Vue return false; }, + level (): number { + return this.path.split('.').length; + }, }, props: [ 'displayOptions', diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index b9db49e03f..4df79a9658 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -376,7 +376,7 @@ export default mixins( this.createNodeActive = false; this.$store.commit('setActiveNode', null); } else if (e.key === 'Tab') { - this.createNodeActive = !this.createNodeActive; + this.createNodeActive = !this.createNodeActive && !this.isReadOnly; } else if (e.key === this.controlKeyCode) { this.ctrlKeyPressed = true; } else if (e.key === 'F2') { diff --git a/packages/nodes-base/credentials/DisqusApi.credentials.ts b/packages/nodes-base/credentials/DisqusApi.credentials.ts new file mode 100644 index 0000000000..c4b2f33aed --- /dev/null +++ b/packages/nodes-base/credentials/DisqusApi.credentials.ts @@ -0,0 +1,18 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class DisqusApi implements ICredentialType { + name = 'disqusApi'; + displayName = 'Disqus API'; + properties = [ + { + displayName: 'Access Token', + name: 'accessToken', + type: 'string' as NodePropertyTypes, + default: '', + description: 'Visit your account details page, and grab the Access Token. See Disqus auth.' + }, + ]; +} diff --git a/packages/nodes-base/credentials/HunterApi.credentials.ts b/packages/nodes-base/credentials/HunterApi.credentials.ts new file mode 100644 index 0000000000..802ff5159b --- /dev/null +++ b/packages/nodes-base/credentials/HunterApi.credentials.ts @@ -0,0 +1,17 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class HunterApi implements ICredentialType { + name = 'hunterApi'; + displayName = 'Hunter API'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/credentials/SegmentApi.credentials.ts b/packages/nodes-base/credentials/SegmentApi.credentials.ts new file mode 100644 index 0000000000..45858d3277 --- /dev/null +++ b/packages/nodes-base/credentials/SegmentApi.credentials.ts @@ -0,0 +1,17 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class SegmentApi implements ICredentialType { + name = 'segmentApi'; + displayName = 'Segment API'; + properties = [ + { + displayName: 'Write Key', + name: 'writekey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts b/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts index 20ee32b65d..38032890b1 100644 --- a/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts +++ b/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts @@ -125,6 +125,22 @@ export class ClickUp implements INodeType { } return returnData; }, + // Get all the available lists without a folder to display them to user so that he can + // select them easily + async getFolderlessLists(this: ILoadOptionsFunctions): Promise { + const spaceId = this.getCurrentNodeParameter('space') as string; + const returnData: INodePropertyOptions[] = []; + const { lists } = await clickupApiRequest.call(this, 'GET', `/space/${spaceId}/list`); + for (const list of lists) { + const listName = list.name; + const listId = list.id; + returnData.push({ + name: listName, + value: listId, + }); + } + return returnData; + }, // Get all the available assignees to display them to user so that he can // select them easily async getAssignees(this: ILoadOptionsFunctions): Promise { diff --git a/packages/nodes-base/nodes/ClickUp/TaskDescription.ts b/packages/nodes-base/nodes/ClickUp/TaskDescription.ts index b3883040c3..62b8c570ff 100644 --- a/packages/nodes-base/nodes/ClickUp/TaskDescription.ts +++ b/packages/nodes-base/nodes/ClickUp/TaskDescription.ts @@ -87,6 +87,23 @@ export const taskFields = [ }, required: true, }, + { + displayName: 'Folderless List', + name: 'folderless', + type: 'boolean', + default: false, + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'create', + ], + }, + }, + required: true, + }, { displayName: 'Folder', name: 'folder', @@ -100,13 +117,16 @@ export const taskFields = [ operation: [ 'create', ], + folderless: [ + false, + ], }, }, typeOptions: { loadOptionsMethod: 'getFolders', loadOptionsDependsOn: [ 'space', - ] + ], }, required: true, }, @@ -123,6 +143,35 @@ export const taskFields = [ operation: [ 'create', ], + folderless: [ + true, + ], + }, + }, + typeOptions: { + loadOptionsMethod: 'getFolderlessLists', + loadOptionsDependsOn: [ + 'space', + ], + }, + required: true, + }, + { + displayName: 'List', + name: 'list', + type: 'options', + default: '', + displayOptions: { + show: { + resource: [ + 'task', + ], + operation: [ + 'create', + ], + folderless: [ + false, + ], }, }, typeOptions: { @@ -225,6 +274,7 @@ export const taskFields = [ name: 'priority', type: 'number', typeOptions: { + minValue: 1, maxValue: 4, }, description: 'Integer mapping as 1 : Urgent, 2 : High, 3 : Normal, 4 : Low', @@ -358,6 +408,7 @@ export const taskFields = [ name: 'priority', type: 'number', typeOptions: { + minValue: 1, maxValue: 4, }, description: 'Integer mapping as 1 : Urgent, 2 : High, 3 : Normal, 4 : Low', diff --git a/packages/nodes-base/nodes/Disqus/Disqus.node.ts b/packages/nodes-base/nodes/Disqus/Disqus.node.ts new file mode 100644 index 0000000000..df07e89456 --- /dev/null +++ b/packages/nodes-base/nodes/Disqus/Disqus.node.ts @@ -0,0 +1,784 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; +import { + IDataObject, + INodeTypeDescription, + INodeExecutionData, + INodeType, +} from 'n8n-workflow'; + +import { disqusApiRequest, disqusApiRequestAllItems } from './GenericFunctions'; + + +export class Disqus implements INodeType { + description: INodeTypeDescription = { + displayName: 'Disqus', + name: 'disqus', + icon: 'file:disqus.png', + group: ['input'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Access data on Disqus', + defaults: { + name: 'Disqus', + color: '#22BB44', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'disqusApi', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Forum', + value: 'forum', + }, + ], + default: 'forum', + description: 'The resource to operate on.', + }, + + // ---------------------------------- + // forum + // ---------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'forum', + ], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + description: 'Returns forum details.', + }, + { + name: 'Get All Categories', + value: 'getCategories', + description: 'Returns a list of categories within a forum.', + }, + { + name: 'Get All Threads', + value: 'getThreads', + description: 'Returns a list of threads within a forum.', + }, + { + name: 'Get All Posts', + value: 'getPosts', + description: 'Returns a list of posts within a forum.', + } + ], + default: 'get', + description: 'The operation to perform.', + }, + + // ---------------------------------- + // forum:get + // ---------------------------------- + { + displayName: 'Forum name', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'forum', + ], + }, + }, + description: 'The short name(aka ID) of the forum to get.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'forum', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Attach', + name: 'attach', + type: 'multiOptions', + options: [ + { + name: 'counters', + value: 'counters', + }, + { + name: 'followsForum', + value: 'followsForum', + }, + { + name: 'forumCanDisableAds', + value: 'forumCanDisableAds', + }, + { + name: 'forumDaysAlive', + value: 'forumDaysAlive', + }, + { + name: 'forumFeatures', + value: 'forumFeatures', + }, + { + name: 'forumForumCategory', + value: 'forumForumCategory', + }, + { + name: 'forumIntegration', + value: 'forumIntegration', + }, + { + name: 'forumNewPolicy', + value: 'forumNewPolicy', + }, + { + name: 'forumPermissions', + value: 'forumPermissions', + }, + ], + default: [], + description: 'The resource to operate on.', + }, + { + displayName: 'Related', + name: 'related', + type: 'multiOptions', + options: [ + { + name: 'author', + value: 'author', + }, + ], + default: [], + description: 'You may specify relations to include with your response', + }, + ], + }, + + // ---------------------------------- + // forum:getPosts + // ---------------------------------- + { + displayName: 'Forum name', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getPosts', + ], + resource: [ + 'forum', + ], + }, + }, + description: 'The short name(aka ID) of the forum to get.', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'forum', + ], + operation: [ + 'getPosts', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'forum', + ], + operation: [ + 'getPosts', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'getPosts', + ], + resource: [ + 'forum', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Filters', + name: 'filters', + type: 'multiOptions', + options: [ + { + name: 'Has_Bad_Word', + value: 'Has_Bad_Word', + }, + { + name: 'Has_Link', + value: 'Has_Link', + }, + { + name: 'Has_Low_Rep_Author', + value: 'Has_Low_Rep_Author', + }, + { + name: 'Has_Media', + value: 'Has_Media', + }, + { + name: 'Is_Anonymous', + value: 'Is_Anonymous', + }, + { + name: 'Is_Flagged', + value: 'Is_Flagged', + }, + { + name: 'No_Issue', + value: 'No_Issue', + }, + { + name: 'Is_At_Flag_Limit', + value: 'Is_At_Flag_Limit', + }, + { + name: 'Is_Toxic', + value: 'Is_Toxic', + }, + { + name: 'Modified_By_Rule', + value: 'Modified_By_Rule', + }, + { + name: 'Shadow_Banned', + value: 'Shadow_Banned', + }, + ], + default: [], + description: 'You may specify filters for your response.' + }, + { + displayName: 'Include', + name: 'include', + type: 'multiOptions', + options: [ + { + name: 'approved', + value: 'approved', + }, + ], + default: [], + description: 'You may specify relations to include with your response.', + }, + { + displayName: 'Order', + name: 'order', + type: 'options', + options: [ + { + name: 'ASC', + value: 'asc', + }, + { + name: 'DESC', + value: 'desc', + } + ], + default: 'asc', + description: 'You may specify order to sort your response.', + }, + { + displayName: 'Query', + name: 'query', + type: 'string', + default: '', + description: 'You may specify query forChoices: asc, desc your response.', + }, + { + displayName: 'Related', + name: 'related', + type: 'multiOptions', + options: [ + { + name: 'thread', + value: 'thread', + }, + ], + default: [], + description: 'You may specify relations to include with your response', + }, + { + displayName: 'Since', + name: 'since', + type: 'dateTime', + default: '', + description: 'Unix timestamp (or ISO datetime standard)', + }, + ], + }, + + // ---------------------------------- + // forum:getCategories + // ---------------------------------- + { + displayName: 'Forum name', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getCategories', + ], + resource: [ + 'forum', + ], + }, + }, + description: 'The short name(aka ID) of the forum to get Categories.', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'forum', + ], + operation: [ + 'getCategories', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'forum', + ], + operation: [ + 'getCategories', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'getCategories', + ], + resource: [ + 'forum', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Order', + name: 'order', + type: 'options', + options: [ + { + name: 'ASC', + value: 'asc', + }, + { + name: 'DESC', + value: 'desc', + } + ], + default: 'asc', + description: 'You may specify order to sort your response.', + }, + ], + }, + + // ---------------------------------- + // forum:getThreads + // ---------------------------------- + { + displayName: 'Forum name', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getThreads', + ], + resource: [ + 'forum', + ], + }, + }, + description: 'The short name(aka ID) of the forum to get Threads.', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'forum', + ], + operation: [ + 'getThreads', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'forum', + ], + operation: [ + 'getThreads', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'getThreads', + ], + resource: [ + 'forum', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Related', + name: 'related', + type: 'multiOptions', + options: [ + { + name: 'author', + value: 'author', + }, + { + name: 'forum', + value: 'forum', + }, + ], + default: [], + description: 'You may specify relations to include with your response', + }, + { + displayName: 'Include', + name: 'include', + type: 'multiOptions', + options: [ + { + name: 'closed', + value: 'closed', + }, + { + name: 'open', + value: 'open', + }, + { + name: 'killed', + value: 'killed', + }, + ], + default: [], + description: 'You may specify relations to include with your response.', + }, + { + displayName: 'Order', + name: 'order', + type: 'options', + options: [ + { + name: 'ASC', + value: 'asc', + }, + { + name: 'DESC', + value: 'desc', + } + ], + default: 'asc', + description: 'You may specify order to sort your response.', + }, + { + displayName: 'Since', + name: 'since', + type: 'dateTime', + default: '', + description: 'Unix timestamp (or ISO datetime standard)', + }, + { + displayName: 'Thread', + name: 'threadId', + type: 'string', + default: '', + description: 'Looks up a thread by ID. You may pass us the "ident"
query type instead of an ID by including "forum". You may
pass us the "link" query type to filter by URL. You must pass
the "forum" if you do not have the Pro API Access addon.', + }, + ], + } + ], + }; + + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + + + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + let endpoint = ''; + let requestMethod = ''; + let body: IDataObject | Buffer; + let qs: IDataObject; + + + for (let i = 0; i < items.length; i++) { + body = {}; + qs = {}; + + if (resource === 'forum') { + if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + + requestMethod = 'GET'; + + endpoint = 'forums/details.json'; + + const id = this.getNodeParameter('id', i) as string; + qs.forum = id; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + Object.assign(qs, additionalFields); + + try { + const responseData = await disqusApiRequest.call(this, requestMethod, qs, endpoint); + returnData.push(responseData.response); + } catch (error) { + throw error; + } + + } else if (operation === 'getPosts') { + // ---------------------------------- + // getPosts + // ---------------------------------- + + requestMethod = 'GET'; + + endpoint = 'forums/listPosts.json'; + + const id = this.getNodeParameter('id', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + qs.forum = id; + qs.limit = 100; + + try { + let responseData: IDataObject = {}; + if(returnAll) { + responseData.response = await disqusApiRequestAllItems.call(this, requestMethod, qs, endpoint); + } else { + const limit = this.getNodeParameter('limit', i) as string; + qs.limit = limit; + responseData = await disqusApiRequest.call(this, requestMethod, qs, endpoint); + } + returnData.push.apply(returnData, responseData.response as IDataObject[]); + } catch (error) { + throw error; + } + + } else if (operation === 'getCategories') { + // ---------------------------------- + // getCategories + // ---------------------------------- + + requestMethod = 'GET'; + + endpoint = 'forums/listCategories.json'; + + const id = this.getNodeParameter('id', i) as string; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + + qs.forum = id; + qs.limit = 100; + + try { + let responseData: IDataObject = {}; + + if(returnAll) { + responseData.response = await disqusApiRequestAllItems.call(this, requestMethod, qs, endpoint); + } else { + const limit = this.getNodeParameter('limit', i) as string; + qs.limit = limit; + responseData = await disqusApiRequest.call(this, requestMethod, qs, endpoint) as IDataObject; + } + returnData.push.apply(returnData, responseData.response as IDataObject[]) ; + } catch (error) { + throw error; + } + + } else if (operation === 'getThreads') { + // ---------------------------------- + // getThreads + // ---------------------------------- + + requestMethod = 'GET'; + + endpoint = 'forums/listThreads.json'; + + const id = this.getNodeParameter('id', i) as string; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + + qs.forum = id; + qs.limit = 100; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + Object.assign(qs, additionalFields); + + try { + let responseData: IDataObject = {}; + if(returnAll) { + responseData.response = await disqusApiRequestAllItems.call(this, requestMethod, qs, endpoint); + } else { + const limit = this.getNodeParameter('limit', i) as string; + qs.limit = limit; + responseData = await disqusApiRequest.call(this, requestMethod, qs, endpoint); + } + returnData.push.apply(returnData, responseData.response as IDataObject[]); + } catch (error) { + throw error; + } + + } else { + throw new Error(`The operation "${operation}" is not known!`); + } + + } else { + throw new Error(`The resource "${resource}" is not known!`); + } + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Disqus/GenericFunctions.ts b/packages/nodes-base/nodes/Disqus/GenericFunctions.ts new file mode 100644 index 0000000000..5f5f88bfd4 --- /dev/null +++ b/packages/nodes-base/nodes/Disqus/GenericFunctions.ts @@ -0,0 +1,97 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; + +export async function disqusApiRequest( + this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + method: string, + qs: IDataObject = {}, + uri?: string, + body: IDataObject = {}, + option: IDataObject = {}, + ): Promise { // tslint:disable-line:no-any + + const credentials = this.getCredentials('disqusApi') as IDataObject; + qs.api_key = credentials.accessToken; + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + // Convert to query string into a format the API can read + const queryStringElements: string[] = []; + for (const key of Object.keys(qs)) { + if (Array.isArray(qs[key])) { + (qs[key] as string[]).forEach(value => { + queryStringElements.push(`${key}=${value}`); + }); + } else { + queryStringElements.push(`${key}=${qs[key]}`); + } + } + + let options: OptionsWithUri = { + method, + body, + uri: `https://disqus.com/api/3.0/${uri}?${queryStringElements.join('&')}`, + json: true + }; + + options = Object.assign({}, options, option); + if (Object.keys(options.body).length === 0) { + delete options.body; + } + try { + const result = await this.helpers.request!(options); + return result; + } catch (error) { + if (error.statusCode === 401) { + // Return a clear error + throw new Error('The Disqus credentials are not valid!'); + } + + if (error.error && error.error.error_summary) { + // Try to return the error prettier + throw new Error(`Disqus error response [${error.statusCode}]: ${error.error.error_summary}`); + } + + // If that data does not exist for some reason return the actual error + throw error; + } +} + +/** + * Make an API request to paginated flow endpoint + * and return all results + */ +export async function disqusApiRequestAllItems( + this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + method: string, + qs: IDataObject = {}, + uri?: string, + body: IDataObject = {}, + option: IDataObject = {}, + ): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + + try { + do { + responseData = await disqusApiRequest.call(this, method, qs, uri, body, option); + qs.cursor = responseData.cursor.id; + returnData.push.apply(returnData, responseData.response); + } while ( + responseData.cursor.more === true && + responseData.cursor.hasNext === true + ); + return returnData; + } catch(error) { + throw error; + } +} diff --git a/packages/nodes-base/nodes/Disqus/disqus.png b/packages/nodes-base/nodes/Disqus/disqus.png new file mode 100644 index 0000000000..2b3b5cf3a8 Binary files /dev/null and b/packages/nodes-base/nodes/Disqus/disqus.png differ diff --git a/packages/nodes-base/nodes/Function.node.ts b/packages/nodes-base/nodes/Function.node.ts index 7892a0a5c0..7f5ea4c1ea 100644 --- a/packages/nodes-base/nodes/Function.node.ts +++ b/packages/nodes-base/nodes/Function.node.ts @@ -84,7 +84,7 @@ export class Function implements INodeType { try { // Execute the function code - items = (await vm.run(`module.exports = async function() {${functionCode}}()`, './')); + items = (await vm.run(`module.exports = async function() {${functionCode}}()`, __dirname)); } catch (e) { return Promise.reject(e); } diff --git a/packages/nodes-base/nodes/FunctionItem.node.ts b/packages/nodes-base/nodes/FunctionItem.node.ts index 01a134fdc8..6352fc3f98 100644 --- a/packages/nodes-base/nodes/FunctionItem.node.ts +++ b/packages/nodes-base/nodes/FunctionItem.node.ts @@ -90,7 +90,7 @@ export class FunctionItem implements INodeType { let jsonData: IDataObject; try { // Execute the function code - jsonData = await vm.run(`module.exports = async function() {${functionCode}}()`, './'); + jsonData = await vm.run(`module.exports = async function() {${functionCode}}()`, __dirname); } catch (e) { return Promise.reject(e); } diff --git a/packages/nodes-base/nodes/Hunter/GenericFunctions.ts b/packages/nodes-base/nodes/Hunter/GenericFunctions.ts new file mode 100644 index 0000000000..e566db93e8 --- /dev/null +++ b/packages/nodes-base/nodes/Hunter/GenericFunctions.ts @@ -0,0 +1,56 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; + +export async function hunterApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('hunterApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + qs = Object.assign({ api_key: credentials.apiKey }, qs); + let options: OptionsWithUri = { + method, + qs, + body, + uri: uri ||`https://api.hunter.io/v2${resource}`, + json: true + }; + options = Object.assign({}, options, option); + if (Object.keys(options.body).length === 0) { + delete options.body; + } + try { + return await this.helpers.request!(options); + } catch (err) { + throw new Error(err); + } +} + +/** + * Make an API request to paginated flow endpoint + * and return all results + */ +export async function hunterApiRequestAllItems(this: IHookFunctions | IExecuteFunctions| ILoadOptionsFunctions, propertyName: string, method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + query.offset = 0; + query.limit = 100; + + do { + responseData = await hunterApiRequest.call(this, method, resource, body, query); + returnData.push(responseData[propertyName]); + query.offset += query.limit; + } while ( + responseData.meta !== undefined && + responseData.meta.results !== undefined && + responseData.meta.offset <= responseData.meta.results + ); + return returnData; +} diff --git a/packages/nodes-base/nodes/Hunter/Hunter.node.ts b/packages/nodes-base/nodes/Hunter/Hunter.node.ts new file mode 100644 index 0000000000..c27da7a1d4 --- /dev/null +++ b/packages/nodes-base/nodes/Hunter/Hunter.node.ts @@ -0,0 +1,378 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; +import { + IDataObject, + INodeTypeDescription, + INodeExecutionData, + INodeType, +} from 'n8n-workflow'; +import { + hunterApiRequest, + hunterApiRequestAllItems, +} from './GenericFunctions'; + +export class Hunter implements INodeType { + description: INodeTypeDescription = { + displayName: 'Hunter', + name: 'hunter', + icon: 'file:hunter.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"]}}', + description: 'Consume Hunter API', + defaults: { + name: 'Hunter', + color: '#ff3807', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'hunterApi', + required: true, + } + ], + properties: [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + options: [ + { + name: ' Domain Search', + value: 'domainSearch', + description: 'Get every email address found on the internet using a given domain name, with sources.', + }, + { + name: ' Email Finder', + value: 'emailFinder', + description: 'Generates or retrieves the most likely email address from a domain name, a first name and a last name.', + }, + { + name: 'Email Verifier', + value: 'emailVerifier', + description: 'Allows you to verify the deliverability of an email address.', + }, + ], + default: 'domainSearch', + description: 'operation to consume.', + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + displayOptions: { + show: { + operation: [ + 'domainSearch', + ], + }, + }, + default: '', + required: true, + description: 'Domain name from which you want to find the email addresses. For example, "stripe.com".', + }, + { + displayName: 'Only Emails', + name: 'onlyEmails', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'domainSearch', + ], + }, + }, + default: true, + description: 'Return only the the found emails.', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'domainSearch', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'domainSearch', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'domainSearch', + ], + }, + }, + options: [ + { + displayName: 'Type', + name: 'type', + type: 'options', + default: '', + options: [ + { + name: 'Personal', + value: 'personal', + }, + { + name: 'Generic', + value: 'generic', + }, + ] + }, + { + displayName: 'Seniority', + name: 'seniority', + type: 'multiOptions', + default: [], + options: [ + { + name: 'Junior', + value: 'junior', + }, + { + name: 'Senior', + value: 'senior', + }, + { + name: 'Executive', + value: 'executive', + }, + ] + }, + { + displayName: 'Department', + name: 'department', + type: 'multiOptions', + default: [], + options: [ + { + name: 'Executive', + value: 'executive', + }, + { + name: 'IT', + value: 'it', + }, + { + name: 'Finance', + value: 'finance', + }, + { + name: 'Management', + value: 'management', + }, + { + name: 'Sales', + value: 'sales', + }, + { + name: 'Legal', + value: 'legal', + }, + { + name: 'Support', + value: 'support', + }, + { + name: 'HR', + value: 'hr', + }, + { + name: 'Marketing', + value: 'marketing', + }, + { + name: 'Communication', + value: 'communication', + }, + ] + }, + ], + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + default: '', + displayOptions: { + show: { + operation: [ + 'emailFinder', + ], + }, + }, + required: true, + description: 'Domain name from which you want to find the email addresses. For example, "stripe.com".', + }, + { + displayName: 'First Name', + name: 'firstname', + type: 'string', + displayOptions: { + show: { + operation: [ + 'emailFinder', + ], + }, + }, + default: '', + required: true, + description: `The person's first name. It doesn't need to be in lowercase.`, + }, + { + displayName: 'Last Name', + name: 'lastname', + type: 'string', + displayOptions: { + show: { + operation: [ + 'emailFinder', + ], + }, + }, + default: '', + required: true, + description: `The person's last name. It doesn't need to be in lowercase.`, + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + displayOptions: { + show: { + operation: [ + 'emailVerifier', + ], + }, + }, + default: '', + required: true, + description: 'The email address you want to verify.', + }, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + const qs: IDataObject = {}; + let responseData; + for (let i = 0; i < length; i++) { + const operation = this.getNodeParameter('operation', 0) as string; + //https://hunter.io/api-documentation/v2#domain-search + if (operation === 'domainSearch') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const filters = this.getNodeParameter('filters', i) as IDataObject; + const domain = this.getNodeParameter('domain', i) as string; + const onlyEmails = this.getNodeParameter('onlyEmails', i, false) as boolean; + + qs.domain = domain; + if (filters.type){ + qs.type = filters.type; + } + if (filters.seniority){ + qs.seniority = (filters.seniority as string[]).join(','); + } + if (filters.department){ + qs.department = (filters.department as string[]).join(','); + } + if (returnAll) { + responseData = await hunterApiRequestAllItems.call(this, 'data', 'GET', '/domain-search', {}, qs); + + // Make sure that the company information is there only once and + // the emails are combined underneath it. + if (onlyEmails === false) { + let tempReturnData: IDataObject = {}; + + for (let i = 0; i < responseData.length; i++) { + if (i === 0) { + tempReturnData = responseData[i]; + continue; + } + ((tempReturnData as IDataObject).emails as IDataObject[]).push.apply(tempReturnData.emails, responseData[i].emails); + } + + responseData = tempReturnData; + } + } else { + const limit = this.getNodeParameter('limit', i) as number; + qs.limit = limit; + responseData = await hunterApiRequest.call(this, 'GET', '/domain-search', {}, qs); + responseData = responseData.data; + } + + if (onlyEmails === true) { + let tempReturnData: IDataObject[] = []; + + if (Array.isArray(responseData)) { + for (const data of responseData) { + tempReturnData.push.apply(tempReturnData, data.emails); + } + } else { + tempReturnData = responseData.emails; + } + + responseData = tempReturnData; + } + } + //https://hunter.io/api-documentation/v2#email-finder + if (operation === 'emailFinder') { + const domain = this.getNodeParameter('domain', i) as string; + const firstname = this.getNodeParameter('firstname', i) as string; + const lastname = this.getNodeParameter('lastname', i) as string; + qs.first_name = firstname; + qs.last_name = lastname; + qs.domain = domain; + responseData = await hunterApiRequest.call(this, 'GET', '/email-finder', {}, qs); + responseData = responseData.data; + } + //https://hunter.io/api-documentation/v2#email-verifier + if (operation === 'emailVerifier') { + const email = this.getNodeParameter('email', i) as string; + qs.email = email; + responseData = await hunterApiRequest.call(this, 'GET', '/email-verifier', {}, qs); + responseData = responseData.data; + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Hunter/hunter.png b/packages/nodes-base/nodes/Hunter/hunter.png new file mode 100644 index 0000000000..fb214f80ee Binary files /dev/null and b/packages/nodes-base/nodes/Hunter/hunter.png differ diff --git a/packages/nodes-base/nodes/Salesmate/ActivityDescription.ts b/packages/nodes-base/nodes/Salesmate/ActivityDescription.ts index fe737729a7..656bb8f15f 100644 --- a/packages/nodes-base/nodes/Salesmate/ActivityDescription.ts +++ b/packages/nodes-base/nodes/Salesmate/ActivityDescription.ts @@ -19,9 +19,9 @@ export const activityOperations = [ description: 'Create a activity', }, { - name: 'Update', - value: 'update', - description: 'Update a activity', + name: 'Delete', + value: 'delete', + description: 'Delete a activity', }, { name: 'Get', @@ -34,9 +34,9 @@ export const activityOperations = [ description: 'Get all companies', }, { - name: 'Delete', - value: 'delete', - description: 'Delete a activity', + name: 'Update', + value: 'update', + description: 'Update a activity', }, ], default: 'create', @@ -427,12 +427,14 @@ export const activityFields = [ name: 'fields', type: 'string', default: '', + description: 'Comma separated list of fields to return.', }, { displayName: 'Sort By', name: 'sortBy', type: 'string', default: '', + description: 'The field to sort by.', }, { displayName: 'Sort Order', @@ -440,11 +442,11 @@ export const activityFields = [ type: 'options', options: [ { - name: 'Asc', + name: 'ASC', value: 'asc', }, { - name: 'Desc', + name: 'DESC', value: 'desc', }, ], @@ -508,11 +510,11 @@ export const activityFields = [ type: 'options', options: [ { - name: 'And', + name: 'AND', value: 'AND', }, { - name: 'Or', + name: 'OR', value: 'OR', }, ], diff --git a/packages/nodes-base/nodes/Salesmate/CompanyDescription.ts b/packages/nodes-base/nodes/Salesmate/CompanyDescription.ts index a2dbc2e762..dbf20eb1bd 100644 --- a/packages/nodes-base/nodes/Salesmate/CompanyDescription.ts +++ b/packages/nodes-base/nodes/Salesmate/CompanyDescription.ts @@ -19,9 +19,9 @@ export const companyOperations = [ description: 'Create a company', }, { - name: 'Update', - value: 'update', - description: 'Update a company', + name: 'Delete', + value: 'delete', + description: 'Delete a company', }, { name: 'Get', @@ -34,9 +34,9 @@ export const companyOperations = [ description: 'Get all companies', }, { - name: 'Delete', - value: 'delete', - description: 'Delete a company', + name: 'Update', + value: 'update', + description: 'Update a company', }, ], default: 'create', @@ -520,12 +520,14 @@ export const companyFields = [ name: 'fields', type: 'string', default: '', + description: 'Comma separated list of fields to return.', }, { displayName: 'Sort By', name: 'sortBy', type: 'string', default: '', + description: 'The field to sort by.', }, { displayName: 'Sort Order', @@ -533,11 +535,11 @@ export const companyFields = [ type: 'options', options: [ { - name: 'Asc', + name: 'ASC', value: 'asc', }, { - name: 'Desc', + name: 'DESC', value: 'desc', }, ], @@ -601,11 +603,11 @@ export const companyFields = [ type: 'options', options: [ { - name: 'And', + name: 'AND', value: 'AND', }, { - name: 'Or', + name: 'OR', value: 'OR', }, ], diff --git a/packages/nodes-base/nodes/Salesmate/DealDescription.ts b/packages/nodes-base/nodes/Salesmate/DealDescription.ts index 343c02b55a..b4291c872d 100644 --- a/packages/nodes-base/nodes/Salesmate/DealDescription.ts +++ b/packages/nodes-base/nodes/Salesmate/DealDescription.ts @@ -19,9 +19,9 @@ export const dealOperations = [ description: 'Create a deal', }, { - name: 'Update', - value: 'update', - description: 'Update a deal', + name: 'Delete', + value: 'delete', + description: 'Delete a deal', }, { name: 'Get', @@ -34,9 +34,9 @@ export const dealOperations = [ description: 'Get all companies', }, { - name: 'Delete', - value: 'delete', - description: 'Delete a deal', + name: 'Update', + value: 'update', + description: 'Update a deal', }, ], default: 'create', @@ -691,12 +691,14 @@ export const dealFields = [ name: 'fields', type: 'string', default: '', + description: 'Comma separated list of fields to return.', }, { displayName: 'Sort By', name: 'sortBy', type: 'string', default: '', + description: 'The field to sort by.', }, { displayName: 'Sort Order', @@ -704,11 +706,11 @@ export const dealFields = [ type: 'options', options: [ { - name: 'Asc', + name: 'ASC', value: 'asc', }, { - name: 'Desc', + name: 'DESC', value: 'desc', }, ], @@ -772,11 +774,11 @@ export const dealFields = [ type: 'options', options: [ { - name: 'And', + name: 'AND', value: 'AND', }, { - name: 'Or', + name: 'OR', value: 'OR', }, ], diff --git a/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts b/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts index a84dbc95e2..1471ae4a6c 100644 --- a/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Salesmate/GenericFunctions.ts @@ -65,3 +65,21 @@ export function validateJSON(json: string | undefined): any { // tslint:disable- } return result; } + + +/** + * Converts data from the Salesmate format into a simple object + * + * @export + * @param {IDataObject[]} data + * @returns {IDataObject} + */ +export function simplifySalesmateData(data: IDataObject[]): IDataObject { + const returnData: IDataObject = {}; + + for (const item of data) { + returnData[item.fieldName as string] = item.value; + } + + return returnData; +} diff --git a/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts b/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts index a57b8b7410..e8f5a49528 100644 --- a/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts +++ b/packages/nodes-base/nodes/Salesmate/Salesmate.node.ts @@ -12,6 +12,7 @@ import { import { salesmateApiRequest, salesmateApiRequestAllItems, + simplifySalesmateData, validateJSON, } from './GenericFunctions'; import { @@ -289,12 +290,9 @@ export class Salesmate implements INodeType { const rawData = this.getNodeParameter('rawData', i) as boolean; responseData = await salesmateApiRequest.call(this, 'GET', `/v1/companies/${companyId}`); responseData = responseData.Data; + if (!rawData) { - responseData = responseData.map((company: IDataObject) => { - const aux: IDataObject = {}; - aux[company.fieldName as string] = company.value; - return aux; - }); + responseData = simplifySalesmateData(responseData); } } if (operation === 'getAll') { @@ -314,9 +312,26 @@ export class Salesmate implements INodeType { qs.sortOrder = options.sortOrder as string; } if (options.fields) { + if ((options.fields as string).trim() === '') { + throw new Error('You have to add at least one field'); + } body.fields = (options.fields as string).split(',') as string[]; } else { - throw new Error('You have to add at least one field'); + body.fields = [ + 'name', + 'description', + 'billingAddressLine1', + 'billingAddressLine2', + 'billingCity', + 'billingZipCode', + 'billingState', + 'billingCountry', + 'website', + 'owner', + 'tags', + 'photo', + 'createdAt', + ]; } if (!jsonActive) { const filters: IDataObject[] = []; @@ -332,7 +347,7 @@ export class Salesmate implements INodeType { }; filter.condition = condition.condition; filter.data = condition.value; - filters.push(filter) + filters.push(filter); } } } @@ -440,12 +455,9 @@ export class Salesmate implements INodeType { const rawData = this.getNodeParameter('rawData', i) as boolean; responseData = await salesmateApiRequest.call(this, 'GET', `/v1/activities/${activityId}`); responseData = responseData.Data; + if (!rawData) { - responseData = responseData.map((activity: IDataObject) => { - const aux: IDataObject = {}; - aux[activity.fieldName as string] = activity.value; - return aux; - }); + responseData = simplifySalesmateData(responseData); } } if (operation === 'getAll') { @@ -465,9 +477,27 @@ export class Salesmate implements INodeType { qs.sortOrder = options.sortOrder as string; } if (options.fields) { + if ((options.fields as string).trim() === '') { + throw new Error('You have to add at least one field'); + } body.fields = (options.fields as string).split(',') as string[]; } else { - throw new Error('You have to add at least one field'); + body.fields = [ + 'title', + 'dueDate', + 'description', + 'duration', + 'owner', + 'Deal.title', + 'PrimaryContact.name', + 'PrimaryContact.email', + 'PrimaryCompany.name', + 'PrimaryCompany.email', + 'tags', + 'type', + 'createdAt', + 'isCompleted', + ]; } if (!jsonActive) { const filters: IDataObject[] = []; @@ -483,7 +513,7 @@ export class Salesmate implements INodeType { }; filter.condition = condition.condition; filter.data = condition.value; - filters.push(filter) + filters.push(filter); } } } @@ -617,12 +647,9 @@ export class Salesmate implements INodeType { const rawData = this.getNodeParameter('rawData', i) as boolean; responseData = await salesmateApiRequest.call(this, 'GET', `/v1/deals/${dealId}`); responseData = responseData.Data; + if (!rawData) { - responseData = responseData.map((deal: IDataObject) => { - const aux: IDataObject = {}; - aux[deal.fieldName as string] = deal.value; - return aux; - }); + responseData = simplifySalesmateData(responseData); } } if (operation === 'getAll') { @@ -641,10 +668,26 @@ export class Salesmate implements INodeType { if (options.sortOrder) { qs.sortOrder = options.sortOrder as string; } - if (options.fields) { + if (options.fields !== undefined) { + if ((options.fields as string).trim() === '') { + throw new Error('You have to add at least one field'); + } body.fields = (options.fields as string).split(',') as string[]; } else { - throw new Error('You have to add at least one field'); + body.fields = [ + 'title', + 'PrimaryContact.name', + 'PrimaryContact.email', + 'PrimaryCompany.name', + 'PrimaryCompany.email', + 'dealValue', + 'priority', + 'stage', + 'status', + 'owner', + 'tags', + 'createdAt', + ]; } if (!jsonActive) { const filters: IDataObject[] = []; @@ -660,7 +703,7 @@ export class Salesmate implements INodeType { }; filter.condition = condition.condition; filter.data = condition.value; - filters.push(filter) + filters.push(filter); } } } diff --git a/packages/nodes-base/nodes/Segment/GenericFunctions.ts b/packages/nodes-base/nodes/Segment/GenericFunctions.ts new file mode 100644 index 0000000000..bd3ba6b877 --- /dev/null +++ b/packages/nodes-base/nodes/Segment/GenericFunctions.ts @@ -0,0 +1,36 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IWebhookFunctions, +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; + +export async function segmentApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('segmentApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + const base64Key = Buffer.from(`${credentials.writekey}:`).toString('base64'); + const options: OptionsWithUri = { + headers: { + Authorization: `Basic ${base64Key}`, + 'Content-Type': 'application/json', + }, + method, + qs, + body, + uri: uri ||`https://api.segment.io/v1${resource}`, + json: true + }; + if (!Object.keys(body).length) { + delete options.body; + } + try { + return await this.helpers.request!(options); + } catch (error) { + throw new Error('Segment Error: ' + error); + } +} diff --git a/packages/nodes-base/nodes/Segment/IdentifyDescription.ts b/packages/nodes-base/nodes/Segment/IdentifyDescription.ts new file mode 100644 index 0000000000..f80fb2bc39 --- /dev/null +++ b/packages/nodes-base/nodes/Segment/IdentifyDescription.ts @@ -0,0 +1,508 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const identifyOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'identify', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create an identity', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const identifyFields = [ + +/* -------------------------------------------------------------------------- */ +/* identify:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'User ID', + name: 'userId', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'identify', + ], + operation: [ + 'create', + ], + }, + }, + required: false, + }, + { + displayName: 'Traits', + name: 'traits', + placeholder: 'Add Trait', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'identify', + ], + operation: [ + 'create', + ], + }, + }, + default: {}, + options: [ + { + name: 'traitsUi', + displayName: 'Trait', + values: [ + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + description: 'Email address of a user', + }, + { + displayName: 'First Name', + name: 'firstname', + type: 'string', + default: '', + description: 'First name of a user', + }, + { + displayName: 'Last Name', + name: 'lastname', + type: 'string', + default: '', + description: 'Last name of a user', + }, + { + displayName: 'Gender', + name: 'gender', + type: 'string', + default: '', + description: 'Gender of a user', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + description: 'Phone number of a user', + }, + { + displayName: 'Username', + name: 'username', + type: 'string', + default: '', + description: 'User’s username', + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + description: 'Website of a user', + }, + { + displayName: 'Age', + name: 'age', + type: 'number', + default: 1, + description: 'Age of a user', + }, + { + displayName: 'Avatar', + name: 'avatar', + type: 'string', + default: '', + description: 'URL to an avatar image for the user', + }, + { + displayName: 'Birthday', + name: 'birthday', + type: 'dateTime', + default: '', + description: 'User’s birthday', + }, + { + displayName: 'Created At', + name: 'createdAt', + type: 'dateTime', + default: '', + description: 'Date the user’s account was first created', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'Description of the user', + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + description: 'Unique ID in your database for a user', + }, + { + displayName: 'Company', + name: 'company', + placeholder: 'Add Company', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'companyUi', + displayName: 'Company', + values: [ + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Industry', + name: 'industry', + type: 'string', + default: '', + }, + { + displayName: 'Employee Count', + name: 'employeeCount', + type: 'number', + default: 1, + }, + { + displayName: 'Plan', + name: 'plan', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Address', + name: 'address', + placeholder: 'Add Address', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'addressUi', + displayName: 'Address', + values: [ + { + displayName: 'Street', + name: 'street', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'postalCode', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + }, + ] + }, + ], + }, + ] + }, + ], + }, + { + displayName: 'Context', + name: 'context', + placeholder: 'Add Context', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'identify', + ], + operation: [ + 'create', + ], + }, + }, + default: {}, + options: [ + { + name: 'contextUi', + displayName: 'Context', + values: [ + { + displayName: 'Active', + name: 'active', + type: 'boolean', + default: '', + description: 'Whether a user is active', + }, + { + displayName: 'IP', + name: 'ip', + type: 'string', + default: '', + description: 'Current user’s IP address.', + }, + { + displayName: 'Locale', + name: 'locate', + type: 'string', + default: '', + description: 'Locale string for the current user, for example en-US.', + }, + { + displayName: 'Page', + name: 'page', + type: 'string', + default: '', + description: 'Dictionary of information about the current page in the browser, containing hash, path, referrer, search, title and url', + }, + { + displayName: 'Timezone', + name: 'timezone', + type: 'string', + default: '', + description: 'Timezones are sent as tzdata strings to add user timezone information which might be stripped from the timestamp, for example America/New_York', + }, + { + displayName: 'App', + name: 'app', + placeholder: 'Add App', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'appUi', + displayName: 'App', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Version', + name: 'version', + type: 'string', + default: '', + }, + { + displayName: 'Build', + name: 'build', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Campaign', + name: 'campaign', + placeholder: 'Campaign App', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'campaignUi', + displayName: 'Campaign', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Source', + name: 'source', + type: 'string', + default: '', + }, + { + displayName: 'Medium', + name: 'medium', + type: 'string', + default: '', + }, + { + displayName: 'Term', + name: 'term', + type: 'string', + default: '', + }, + { + displayName: 'Content', + name: 'content', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Device', + name: 'device', + placeholder: 'Add Device', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'deviceUi', + displayName: 'Device', + values: [ + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + }, + { + displayName: 'Manufacturer', + name: 'manufacturer', + type: 'string', + default: '', + }, + { + displayName: 'Model', + name: 'model', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Type', + name: 'type', + type: 'string', + default: '', + }, + { + displayName: 'Version', + name: 'version', + type: 'string', + default: '', + }, + ], + }, + ], + }, + ] + }, + ], + }, + { + displayName: 'Integration', + name: 'integrations', + placeholder: 'Add Integration', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'identify', + ], + operation: [ + 'create', + ], + }, + }, + default: {}, + options: [ + { + name: 'integrationsUi', + displayName: 'Integration', + values: [ + { + displayName: 'All', + name: 'all', + type: 'boolean', + default: true, + }, + { + displayName: 'Salesforce', + name: 'salesforce', + type: 'boolean', + default: false, + }, + ], + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Segment/IdentifyInterface.ts b/packages/nodes-base/nodes/Segment/IdentifyInterface.ts new file mode 100644 index 0000000000..ae3d1e4c7b --- /dev/null +++ b/packages/nodes-base/nodes/Segment/IdentifyInterface.ts @@ -0,0 +1,10 @@ +import { IDataObject } from "n8n-workflow"; + +export interface IIdentify { + userId?: string; + anonymousId?: string; + traits?: IDataObject; + context?: IDataObject; + integrations?: IDataObject; + timestamp?: string; +} diff --git a/packages/nodes-base/nodes/Segment/Segment.node.ts b/packages/nodes-base/nodes/Segment/Segment.node.ts new file mode 100644 index 0000000000..bd281366b0 --- /dev/null +++ b/packages/nodes-base/nodes/Segment/Segment.node.ts @@ -0,0 +1,774 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; +import { + segmentApiRequest, +} from './GenericFunctions'; +import { + identifyFields, + identifyOperations, +} from './IdentifyDescription'; +import { + IIdentify, +} from './IdentifyInterface'; +import { + trackOperations, + trackFields, +} from './TrackDescription'; +import { ITrack } from './TrackInterface'; +import * as uuid from 'uuid/v4'; + +export class Segment implements INodeType { + description: INodeTypeDescription = { + displayName: 'Segment', + name: 'segment', + icon: 'file:segment.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ":" + $parameter["resource"]}}', + description: 'Consume Segment API', + defaults: { + name: 'Segment', + color: '#6ebb99', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'segmentApi', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Identify', + value: 'identify', + description: 'Identify lets you tie a user to their actions.' + }, + { + name: 'Track', + value: 'track', + description: 'Track lets you record events', + }, + ], + default: 'identify', + description: 'Resource to consume.', + }, + ...identifyOperations, + ...trackOperations, + ...identifyFields, + ...trackFields, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + const qs: IDataObject = {}; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { + if (resource === 'identify') { + //https://segment.com/docs/connections/sources/catalog/libraries/server/http-api/#identify + if (operation === 'create') { + const userId = this.getNodeParameter('userId', i) as string; + const traits = (this.getNodeParameter('traits', i) as IDataObject).traitsUi as IDataObject; + const context = (this.getNodeParameter('context', i) as IDataObject).contextUi as IDataObject; + const integrations = (this.getNodeParameter('integrations', i) as IDataObject).integrationsUi as IDataObject; + const body: IIdentify = { + traits: { + company: {}, + address: {}, + }, + context: { + app: {}, + campaign: {}, + device: {}, + }, + integrations: {}, + }; + if (userId) { + body.userId = userId as string; + } else { + body.anonymousId = uuid(); + } + if (traits) { + if (traits.email) { + body.traits!.email = traits.email as string; + } + if (traits.firstname) { + body.traits!.firstname = traits.firstname as string; + } + if (traits.lastname) { + body.traits!.lastname = traits.lastname as string; + } + if (traits.gender) { + body.traits!.gender = traits.gender as string; + } + if (traits.phone) { + body.traits!.phone = traits.phone as string; + } + if (traits.username) { + body.traits!.username = traits.username as string; + } + if (traits.website) { + body.traits!.website = traits.website as string; + } + if (traits.age) { + body.traits!.age = traits.age as number; + } + if (traits.avatar) { + body.traits!.avatar = traits.avatar as string; + } + if (traits.birthday) { + body.traits!.birthday = traits.birthday as string; + } + if (traits.createdAt) { + body.traits!.createdAt = traits.createdAt as string; + } + if (traits.description) { + body.traits!.description = traits.description as string; + } + if (traits.id) { + body.traits!.id = traits.id as string; + } + if (traits.company) { + const company = (traits.company as IDataObject).companyUi as IDataObject; + if (company) { + if (company.id) { + //@ts-ignore + body.traits.company.id = company.id as string; + } + if (company.name) { + //@ts-ignore + body.traits.company.name = company.name as string; + } + if (company.industry) { + //@ts-ignore + body.traits.company.industry = company.industry as string; + } + if (company.employeeCount) { + //@ts-ignore + body.traits.company.employeeCount = company.employeeCount as number; + } + if (company.plan) { + //@ts-ignore + body.traits.company.plan = company.plan as string; + } + } + } + if (traits.address) { + const address = (traits.address as IDataObject).addressUi as IDataObject; + if (address) { + if (address.street) { + //@ts-ignore + body.traits.address.street = address.street as string; + } + if (address.city) { + //@ts-ignore + body.traits.address.city = address.city as string; + } + if (address.state) { + //@ts-ignore + body.traits.address.state = address.state as string; + } + if (address.postalCode) { + //@ts-ignore + body.traits.address.postalCode = address.postalCode as string; + } + if (address.country) { + //@ts-ignore + body.traits.address.country = address.country as string; + } + } + } + } + if (context) { + if (context.active) { + body.context!.active = context.active as boolean; + } + if (context.ip) { + body.context!.ip = context.ip as string; + } + if (context.locate) { + body.context!.locate = context.locate as string; + } + if (context.page) { + body.context!.page = context.page as string; + } + if (context.timezone) { + body.context!.timezone = context.timezone as string; + } + if (context.timezone) { + body.context!.timezone = context.timezone as string; + } + if (context.app) { + const app = (context.app as IDataObject).appUi as IDataObject; + if (app) { + if (app.name) { + //@ts-ignore + body.context.app.name = app.name as string; + } + if (app.version) { + //@ts-ignore + body.context.app.version = app.version as string; + } + if (app.build) { + //@ts-ignore + body.context.app.build = app.build as string; + } + } + } + if (context.campaign) { + const campaign = (context.campaign as IDataObject).campaignUi as IDataObject; + if (campaign) { + if (campaign.name) { + //@ts-ignore + body.context.campaign.name = campaign.name as string; + } + if (campaign.source) { + //@ts-ignore + body.context.campaign.source = campaign.source as string; + } + if (campaign.medium) { + //@ts-ignore + body.context.campaign.medium = campaign.medium as string; + } + if (campaign.term) { + //@ts-ignore + body.context.campaign.term = campaign.term as string; + } + if (campaign.content) { + //@ts-ignore + body.context.campaign.content = campaign.content as string; + } + } + } + + if (context.device) { + const device = (context.device as IDataObject).deviceUi as IDataObject; + if (device) { + if (device.id) { + //@ts-ignore + body.context.device.id = device.id as string; + } + if (device.manufacturer) { + //@ts-ignore + body.context.device.manufacturer = device.manufacturer as string; + } + if (device.model) { + //@ts-ignore + body.context.device.model = device.model as string; + } + if (device.type) { + //@ts-ignore + body.context.device.type = device.type as string; + } + if (device.version) { + //@ts-ignore + body.context.device.version = device.version as string; + } + } + } + } + if (integrations) { + if (integrations.all) { + body.integrations!.all = integrations.all as boolean; + } + if (integrations.salesforce) { + body.integrations!.salesforce = integrations.salesforce as boolean; + } + } + responseData = await segmentApiRequest.call(this, 'POST', '/identify', body); + } + } + if (resource === 'track') { + //https://segment.com/docs/connections/sources/catalog/libraries/server/http-api/#track + if (operation === 'event') { + const userId = this.getNodeParameter('userId', i) as string; + const event = this.getNodeParameter('event', i) as string; + const traits = (this.getNodeParameter('traits', i) as IDataObject).traitsUi as IDataObject; + const context = (this.getNodeParameter('context', i) as IDataObject).contextUi as IDataObject; + const integrations = (this.getNodeParameter('integrations', i) as IDataObject).integrationsUi as IDataObject; + const properties = (this.getNodeParameter('properties', i) as IDataObject).propertiesUi as IDataObject; + const body: ITrack = { + event, + traits: { + company: {}, + address: {}, + }, + context: { + app: {}, + campaign: {}, + device: {}, + }, + integrations: {}, + properties: {}, + }; + if (userId) { + body.userId = userId as string; + } else { + body.anonymousId = uuid(); + } + if (traits) { + if (traits.email) { + body.traits!.email = traits.email as string; + } + if (traits.firstname) { + body.traits!.firstname = traits.firstname as string; + } + if (traits.lastname) { + body.traits!.lastname = traits.lastname as string; + } + if (traits.gender) { + body.traits!.gender = traits.gender as string; + } + if (traits.phone) { + body.traits!.phone = traits.phone as string; + } + if (traits.username) { + body.traits!.username = traits.username as string; + } + if (traits.website) { + body.traits!.website = traits.website as string; + } + if (traits.age) { + body.traits!.age = traits.age as number; + } + if (traits.avatar) { + body.traits!.avatar = traits.avatar as string; + } + if (traits.birthday) { + body.traits!.birthday = traits.birthday as string; + } + if (traits.createdAt) { + body.traits!.createdAt = traits.createdAt as string; + } + if (traits.description) { + body.traits!.description = traits.description as string; + } + if (traits.id) { + body.traits!.id = traits.id as string; + } + if (traits.company) { + const company = (traits.company as IDataObject).companyUi as IDataObject; + if (company) { + if (company.id) { + //@ts-ignore + body.traits.company.id = company.id as string; + } + if (company.name) { + //@ts-ignore + body.traits.company.name = company.name as string; + } + if (company.industry) { + //@ts-ignore + body.traits.company.industry = company.industry as string; + } + if (company.employeeCount) { + //@ts-ignore + body.traits.company.employeeCount = company.employeeCount as number; + } + if (company.plan) { + //@ts-ignore + body.traits.company.plan = company.plan as string; + } + } + } + if (traits.address) { + const address = (traits.address as IDataObject).addressUi as IDataObject; + if (address) { + if (address.street) { + //@ts-ignore + body.traits.address.street = address.street as string; + } + if (address.city) { + //@ts-ignore + body.traits.address.city = address.city as string; + } + if (address.state) { + //@ts-ignore + body.traits.address.state = address.state as string; + } + if (address.postalCode) { + //@ts-ignore + body.traits.address.postalCode = address.postalCode as string; + } + if (address.country) { + //@ts-ignore + body.traits.address.country = address.country as string; + } + } + } + } + if (context) { + if (context.active) { + body.context!.active = context.active as boolean; + } + if (context.ip) { + body.context!.ip = context.ip as string; + } + if (context.locate) { + body.context!.locate = context.locate as string; + } + if (context.page) { + body.context!.page = context.page as string; + } + if (context.timezone) { + body.context!.timezone = context.timezone as string; + } + if (context.timezone) { + body.context!.timezone = context.timezone as string; + } + if (context.app) { + const app = (context.app as IDataObject).appUi as IDataObject; + if (app) { + if (app.name) { + //@ts-ignore + body.context.app.name = app.name as string; + } + if (app.version) { + //@ts-ignore + body.context.app.version = app.version as string; + } + if (app.build) { + //@ts-ignore + body.context.app.build = app.build as string; + } + } + } + if (context.campaign) { + const campaign = (context.campaign as IDataObject).campaignUi as IDataObject; + if (campaign) { + if (campaign.name) { + //@ts-ignore + body.context.campaign.name = campaign.name as string; + } + if (campaign.source) { + //@ts-ignore + body.context.campaign.source = campaign.source as string; + } + if (campaign.medium) { + //@ts-ignore + body.context.campaign.medium = campaign.medium as string; + } + if (campaign.term) { + //@ts-ignore + body.context.campaign.term = campaign.term as string; + } + if (campaign.content) { + //@ts-ignore + body.context.campaign.content = campaign.content as string; + } + } + } + + if (context.device) { + const device = (context.device as IDataObject).deviceUi as IDataObject; + if (device) { + if (device.id) { + //@ts-ignore + body.context.device.id = device.id as string; + } + if (device.manufacturer) { + //@ts-ignore + body.context.device.manufacturer = device.manufacturer as string; + } + if (device.model) { + //@ts-ignore + body.context.device.model = device.model as string; + } + if (device.type) { + //@ts-ignore + body.context.device.type = device.type as string; + } + if (device.version) { + //@ts-ignore + body.context.device.version = device.version as string; + } + } + } + } + if (integrations) { + if (integrations.all) { + body.integrations!.all = integrations.all as boolean; + } + if (integrations.salesforce) { + body.integrations!.salesforce = integrations.salesforce as boolean; + } + } + if (properties) { + if (properties.revenue) { + body.properties!.revenue = properties.revenue as number; + } + if (properties.currency) { + body.properties!.currency = properties.currency as string; + } + if (properties.value) { + body.properties!.value = properties.value as string; + } + } + responseData = await segmentApiRequest.call(this, 'POST', '/track', body); + } + //https://segment.com/docs/connections/sources/catalog/libraries/server/http-api/#page + if (operation === 'page') { + const userId = this.getNodeParameter('userId', i) as string; + const event = this.getNodeParameter('event', i) as string; + const traits = (this.getNodeParameter('traits', i) as IDataObject).traitsUi as IDataObject; + const context = (this.getNodeParameter('context', i) as IDataObject).contextUi as IDataObject; + const integrations = (this.getNodeParameter('integrations', i) as IDataObject).integrationsUi as IDataObject; + const properties = (this.getNodeParameter('properties', i) as IDataObject).propertiesUi as IDataObject; + const body: ITrack = { + event, + traits: { + company: {}, + address: {}, + }, + context: { + app: {}, + campaign: {}, + device: {}, + }, + integrations: {}, + properties: {}, + }; + if (userId) { + body.userId = userId as string; + } else { + body.anonymousId = uuid(); + } + if (traits) { + if (traits.email) { + body.traits!.email = traits.email as string; + } + if (traits.firstname) { + body.traits!.firstname = traits.firstname as string; + } + if (traits.lastname) { + body.traits!.lastname = traits.lastname as string; + } + if (traits.gender) { + body.traits!.gender = traits.gender as string; + } + if (traits.phone) { + body.traits!.phone = traits.phone as string; + } + if (traits.username) { + body.traits!.username = traits.username as string; + } + if (traits.website) { + body.traits!.website = traits.website as string; + } + if (traits.age) { + body.traits!.age = traits.age as number; + } + if (traits.avatar) { + body.traits!.avatar = traits.avatar as string; + } + if (traits.birthday) { + body.traits!.birthday = traits.birthday as string; + } + if (traits.createdAt) { + body.traits!.createdAt = traits.createdAt as string; + } + if (traits.description) { + body.traits!.description = traits.description as string; + } + if (traits.id) { + body.traits!.id = traits.id as string; + } + if (traits.company) { + const company = (traits.company as IDataObject).companyUi as IDataObject; + if (company) { + if (company.id) { + //@ts-ignore + body.traits.company.id = company.id as string; + } + if (company.name) { + //@ts-ignore + body.traits.company.name = company.name as string; + } + if (company.industry) { + //@ts-ignore + body.traits.company.industry = company.industry as string; + } + if (company.employeeCount) { + //@ts-ignore + body.traits.company.employeeCount = company.employeeCount as number; + } + if (company.plan) { + //@ts-ignore + body.traits.company.plan = company.plan as string; + } + } + } + if (traits.address) { + const address = (traits.address as IDataObject).addressUi as IDataObject; + if (address) { + if (address.street) { + //@ts-ignore + body.traits.address.street = address.street as string; + } + if (address.city) { + //@ts-ignore + body.traits.address.city = address.city as string; + } + if (address.state) { + //@ts-ignore + body.traits.address.state = address.state as string; + } + if (address.postalCode) { + //@ts-ignore + body.traits.address.postalCode = address.postalCode as string; + } + if (address.country) { + //@ts-ignore + body.traits.address.country = address.country as string; + } + } + } + } + if (context) { + if (context.active) { + body.context!.active = context.active as boolean; + } + if (context.ip) { + body.context!.ip = context.ip as string; + } + if (context.locate) { + body.context!.locate = context.locate as string; + } + if (context.page) { + body.context!.page = context.page as string; + } + if (context.timezone) { + body.context!.timezone = context.timezone as string; + } + if (context.timezone) { + body.context!.timezone = context.timezone as string; + } + if (context.app) { + const app = (context.app as IDataObject).appUi as IDataObject; + if (app) { + if (app.name) { + //@ts-ignore + body.context.app.name = app.name as string; + } + if (app.version) { + //@ts-ignore + body.context.app.version = app.version as string; + } + if (app.build) { + //@ts-ignore + body.context.app.build = app.build as string; + } + } + } + if (context.campaign) { + const campaign = (context.campaign as IDataObject).campaignUi as IDataObject; + if (campaign) { + if (campaign.name) { + //@ts-ignore + body.context.campaign.name = campaign.name as string; + } + if (campaign.source) { + //@ts-ignore + body.context.campaign.source = campaign.source as string; + } + if (campaign.medium) { + //@ts-ignore + body.context.campaign.medium = campaign.medium as string; + } + if (campaign.term) { + //@ts-ignore + body.context.campaign.term = campaign.term as string; + } + if (campaign.content) { + //@ts-ignore + body.context.campaign.content = campaign.content as string; + } + } + } + + if (context.device) { + const device = (context.device as IDataObject).deviceUi as IDataObject; + if (device) { + if (device.id) { + //@ts-ignore + body.context.device.id = device.id as string; + } + if (device.manufacturer) { + //@ts-ignore + body.context.device.manufacturer = device.manufacturer as string; + } + if (device.model) { + //@ts-ignore + body.context.device.model = device.model as string; + } + if (device.type) { + //@ts-ignore + body.context.device.type = device.type as string; + } + if (device.version) { + //@ts-ignore + body.context.device.version = device.version as string; + } + } + } + } + if (integrations) { + if (integrations.all) { + body.integrations!.all = integrations.all as boolean; + } + if (integrations.salesforce) { + body.integrations!.salesforce = integrations.salesforce as boolean; + } + } + if (properties) { + if (properties.name) { + body.properties!.name = properties.name as number; + } + if (properties.path) { + body.properties!.path = properties.path as string; + } + if (properties.referrer) { + body.properties!.referrer = properties.referrer as string; + } + if (properties.search) { + body.properties!.search = properties.search as string; + } + if (properties.title) { + body.properties!.title = properties.title as string; + } + if (properties.url) { + body.properties!.url = properties.url as string; + } + if (properties.keywords) { + body.properties!.keywords = properties.keywords as string; + } + } + responseData = await segmentApiRequest.call(this, 'POST', '/page', body); + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Segment/TrackDescription.ts b/packages/nodes-base/nodes/Segment/TrackDescription.ts new file mode 100644 index 0000000000..8077f412df --- /dev/null +++ b/packages/nodes-base/nodes/Segment/TrackDescription.ts @@ -0,0 +1,1155 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const trackOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'track', + ], + }, + }, + options: [ + { + name: 'Event', + value: 'event', + description: 'lets you record the actions your users perform.Every action triggers what we call an “event”, which can also have associated properties.', + }, + { + name: 'Page', + value: 'page', + description: ' lets you record page views on your website, along with optional extra information about the page being viewed.', + }, + ], + default: 'event', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const trackFields = [ + +/* -------------------------------------------------------------------------- */ +/* track:event */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'User ID', + name: 'userId', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'event', + ], + }, + }, + required: false, + }, + { + displayName: 'Event', + name: 'event', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'event', + ], + }, + }, + description: 'Name of the action that a user has performed.', + required: true, + }, + { + displayName: 'Traits', + name: 'traits', + placeholder: 'Add Trait', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'event', + ], + }, + }, + default: {}, + options: [ + { + name: 'traitsUi', + displayName: 'Trait', + values: [ + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + description: 'Email address of a user', + }, + { + displayName: 'First Name', + name: 'firstname', + type: 'string', + default: '', + description: 'First name of a user', + }, + { + displayName: 'Last Name', + name: 'lastname', + type: 'string', + default: '', + description: 'Last name of a user', + }, + { + displayName: 'Gender', + name: 'gender', + type: 'string', + default: '', + description: 'Gender of a user', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + description: 'Phone number of a user', + }, + { + displayName: 'Username', + name: 'username', + type: 'string', + default: '', + description: 'User’s username', + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + description: 'Website of a user', + }, + { + displayName: 'Age', + name: 'age', + type: 'number', + default: 1, + description: 'Age of a user', + }, + { + displayName: 'Avatar', + name: 'avatar', + type: 'string', + default: '', + description: 'URL to an avatar image for the user', + }, + { + displayName: 'Birthday', + name: 'birthday', + type: 'dateTime', + default: '', + description: 'User’s birthday', + }, + { + displayName: 'Created At', + name: 'createdAt', + type: 'dateTime', + default: '', + description: 'Date the user’s account was first created at', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'Description of the user', + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + description: 'Unique ID in your database for a user', + }, + { + displayName: 'Company', + name: 'company', + placeholder: 'Add Company', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'companyUi', + displayName: 'Company', + values: [ + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Industry', + name: 'industry', + type: 'string', + default: '', + }, + { + displayName: 'Employee Count', + name: 'employeeCount', + type: 'number', + default: 1, + }, + { + displayName: 'Plan', + name: 'plan', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Address', + name: 'address', + placeholder: 'Add Address', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'addressUi', + displayName: 'Address', + values: [ + { + displayName: 'Street', + name: 'street', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'postalCode', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + }, + ] + }, + ], + }, + ] + }, + ], + }, + { + displayName: 'Context', + name: 'context', + placeholder: 'Add Context', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'event', + ], + }, + }, + default: {}, + options: [ + { + name: 'contextUi', + displayName: 'Context', + values: [ + { + displayName: 'Active', + name: 'active', + type: 'boolean', + default: '', + description: 'Whether a user is active', + }, + { + displayName: 'IP', + name: 'ip', + type: 'string', + default: '', + description: 'Current user’s IP address.', + }, + { + displayName: 'Locale', + name: 'locate', + type: 'string', + default: '', + description: 'Locale string for the current user, for example en-US.', + }, + { + displayName: 'Page', + name: 'page', + type: 'string', + default: '', + description: 'Dictionary of information about the current page in the browser, containing hash, path, referrer, search, title and url', + }, + { + displayName: 'Timezone', + name: 'timezone', + type: 'string', + default: '', + description: 'Timezones are sent as tzdata strings to add user timezone information which might be stripped from the timestamp, for example America/New_York', + }, + { + displayName: 'App', + name: 'app', + placeholder: 'Add App', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'appUi', + displayName: 'App', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Version', + name: 'version', + type: 'string', + default: '', + }, + { + displayName: 'Build', + name: 'build', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Campaign', + name: 'campaign', + placeholder: 'Campaign App', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'campaignUi', + displayName: 'Campaign', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Source', + name: 'source', + type: 'string', + default: '', + }, + { + displayName: 'Medium', + name: 'medium', + type: 'string', + default: '', + }, + { + displayName: 'Term', + name: 'term', + type: 'string', + default: '', + }, + { + displayName: 'Content', + name: 'content', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Device', + name: 'device', + placeholder: 'Add Device', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'deviceUi', + displayName: 'Device', + values: [ + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + }, + { + displayName: 'Manufacturer', + name: 'manufacturer', + type: 'string', + default: '', + }, + { + displayName: 'Model', + name: 'model', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Type', + name: 'type', + type: 'string', + default: '', + }, + { + displayName: 'Version', + name: 'version', + type: 'string', + default: '', + }, + ], + }, + ], + }, + ] + }, + ], + }, + { + displayName: 'Integration', + name: 'integrations', + placeholder: 'Add Integration', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'event', + ], + }, + }, + default: {}, + options: [ + { + name: 'integrationsUi', + displayName: 'Integration', + values: [ + { + displayName: 'All', + name: 'all', + type: 'boolean', + default: true, + }, + { + displayName: 'Salesforce', + name: 'salesforce', + type: 'boolean', + default: false, + }, + ], + }, + ], + }, + { + displayName: 'Properties', + name: 'properties', + placeholder: 'Add Properties', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'event', + ], + }, + }, + default: {}, + options: [ + { + name: 'propertiesUi', + displayName: 'Properties', + values: [ + { + displayName: 'Revenue', + name: 'revenue', + type: 'number', + typeOptions: { + numberPrecision: 2, + }, + default: 1, + description: 'Amount of revenue an event resulted in. This should be a decimal value, so a shirt worth $19.99 would result in a revenue of 19.99.' + }, + { + displayName: 'Currency', + name: 'currency', + type: 'string', + default: '', + description: 'Currency of the revenue an event resulted in

This should be sent in the ISO 4127 format. If this is not set, we assume the revenue to be in US dollars.

' + }, + { + displayName: 'Value', + name: 'value', + type: 'number', + default: '', + description: 'An abstract “value” to associate with an event. This is typically used in situations where the event doesn’t generate real-dollar revenue, but has an intrinsic value to a marketing team, like newsletter signups.' + }, + ], + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* track:page */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'User ID', + name: 'userId', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'page', + ], + }, + }, + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'page', + ], + }, + }, + description: 'Name of the page For example, most sites have a “Signup” page that can be useful to tag, so you can see users as they move through your funnel', + }, + { + displayName: 'Traits', + name: 'traits', + placeholder: 'Add Trait', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'page', + ], + }, + }, + default: {}, + options: [ + { + name: 'traitsUi', + displayName: 'Trait', + values: [ + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + description: 'Email address of a user', + }, + { + displayName: 'First Name', + name: 'firstname', + type: 'string', + default: '', + description: 'First name of a user', + }, + { + displayName: 'Last Name', + name: 'lastname', + type: 'string', + default: '', + description: 'Last name of a user', + }, + { + displayName: 'Gender', + name: 'gender', + type: 'string', + default: '', + description: 'Gender of a user', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + description: 'Phone number of a user', + }, + { + displayName: 'Username', + name: 'username', + type: 'string', + default: '', + description: 'User’s username', + }, + { + displayName: 'Website', + name: 'website', + type: 'string', + default: '', + description: 'Website of a user', + }, + { + displayName: 'Age', + name: 'age', + type: 'number', + default: 1, + description: 'Age of a user', + }, + { + displayName: 'Avatar', + name: 'avatar', + type: 'string', + default: '', + description: 'URL to an avatar image for the user', + }, + { + displayName: 'Birthday', + name: 'birthday', + type: 'dateTime', + default: '', + description: 'User’s birthday', + }, + { + displayName: 'Created At', + name: 'createdAt', + type: 'dateTime', + default: '', + description: 'Date the user’s account was first created at', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'Description of the user', + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + description: 'Unique ID in your database for a user', + }, + { + displayName: 'Company', + name: 'company', + placeholder: 'Add Company', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'companyUi', + displayName: 'Company', + values: [ + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Industry', + name: 'industry', + type: 'string', + default: '', + }, + { + displayName: 'Employee Count', + name: 'employeeCount', + type: 'number', + default: 1, + }, + { + displayName: 'Plan', + name: 'plan', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Address', + name: 'address', + placeholder: 'Add Address', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'addressUi', + displayName: 'Address', + values: [ + { + displayName: 'Street', + name: 'street', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'postalCode', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + type: 'string', + default: '', + }, + ] + }, + ], + }, + ] + }, + ], + }, + { + displayName: 'Context', + name: 'context', + placeholder: 'Add Context', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'page', + ], + }, + }, + default: {}, + options: [ + { + name: 'contextUi', + displayName: 'Context', + values: [ + { + displayName: 'Active', + name: 'active', + type: 'boolean', + default: '', + description: 'Whether a user is active', + }, + { + displayName: 'IP', + name: 'ip', + type: 'string', + default: '', + description: 'Current user’s IP address.', + }, + { + displayName: 'Locale', + name: 'locate', + type: 'string', + default: '', + description: 'Locale string for the current user, for example en-US.', + }, + { + displayName: 'Page', + name: 'page', + type: 'string', + default: '', + description: 'Dictionary of information about the current page in the browser, containing hash, path, referrer, search, title and url', + }, + { + displayName: 'Timezone', + name: 'timezone', + type: 'string', + default: '', + description: 'Timezones are sent as tzdata strings to add user timezone information which might be stripped from the timestamp, for example America/New_York', + }, + { + displayName: 'App', + name: 'app', + placeholder: 'Add App', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'appUi', + displayName: 'App', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Version', + name: 'version', + type: 'string', + default: '', + }, + { + displayName: 'Build', + name: 'build', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Campaign', + name: 'campaign', + placeholder: 'Campaign App', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'campaignUi', + displayName: 'Campaign', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Source', + name: 'source', + type: 'string', + default: '', + }, + { + displayName: 'Medium', + name: 'medium', + type: 'string', + default: '', + }, + { + displayName: 'Term', + name: 'term', + type: 'string', + default: '', + }, + { + displayName: 'Content', + name: 'content', + type: 'string', + default: '', + }, + ] + }, + ], + }, + { + displayName: 'Device', + name: 'device', + placeholder: 'Add Device', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'deviceUi', + displayName: 'Device', + values: [ + { + displayName: 'ID', + name: 'id', + type: 'string', + default: '', + }, + { + displayName: 'Manufacturer', + name: 'manufacturer', + type: 'string', + default: '', + }, + { + displayName: 'Model', + name: 'model', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Type', + name: 'type', + type: 'string', + default: '', + }, + { + displayName: 'Version', + name: 'version', + type: 'string', + default: '', + }, + ], + }, + ], + }, + ] + }, + ], + }, + { + displayName: 'Integration', + name: 'integrations', + placeholder: 'Add Integration', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'page', + ], + }, + }, + default: {}, + options: [ + { + name: 'integrationsUi', + displayName: 'Integration', + values: [ + { + displayName: 'All', + name: 'all', + type: 'boolean', + default: true, + }, + { + displayName: 'Salesforce', + name: 'salesforce', + type: 'boolean', + default: false, + }, + ], + }, + ], + }, + { + displayName: 'Properties', + name: 'properties', + placeholder: 'Add Properties', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + displayOptions: { + show: { + resource: [ + 'track', + ], + operation: [ + 'page', + ], + }, + }, + default: {}, + options: [ + { + name: 'propertiesUi', + displayName: 'Properties', + values: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'Name of the page. This is reserved for future use.' + }, + { + displayName: 'Path', + name: 'path', + type: 'string', + default: '', + description: 'Path portion of the URL of the page. Equivalent to canonical path which defaults to location.pathname from the DOM API.' + }, + { + displayName: 'Referrer', + name: 'referrer', + type: 'string', + default: '', + description: 'Full URL of the previous page. Equivalent to document.referrer from the DOM API.' + }, + { + displayName: 'Search', + name: 'search', + type: 'string', + default: '', + description: 'Query string portion of the URL of the page. Equivalent to location.search from the DOM API.' + }, + { + displayName: 'Title', + name: 'title', + type: 'string', + default: '', + description: 'Title of the page. Equivalent to document.title from the DOM API.' + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + default: '', + description: 'Full URL of the page. First we look for the canonical url. If the canonical url is not provided, we use location.href from the DOM API.' + }, + { + displayName: 'Keywords', + name: 'keywords', + type: 'string', + default: '', + description: 'A list/array of keywords describing the content of the page. The keywords would most likely be the same as, or similar to, the keywords you would find in an html meta tag for SEO purposes.' + }, + ], + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Segment/TrackInterface.ts b/packages/nodes-base/nodes/Segment/TrackInterface.ts new file mode 100644 index 0000000000..85ce0924e5 --- /dev/null +++ b/packages/nodes-base/nodes/Segment/TrackInterface.ts @@ -0,0 +1,13 @@ +import { IDataObject } from "n8n-workflow"; + +export interface ITrack { + event?: string; + userId?: string; + name?: string; + anonymousId?: string; + traits?: IDataObject; + context?: IDataObject; + timestamp?: string; + properties?: IDataObject; + integrations?: IDataObject; +} diff --git a/packages/nodes-base/nodes/Segment/segment.png b/packages/nodes-base/nodes/Segment/segment.png new file mode 100644 index 0000000000..5dbfcaf09d Binary files /dev/null and b/packages/nodes-base/nodes/Segment/segment.png differ diff --git a/packages/nodes-base/nodes/Trello/AttachmentDescription.ts b/packages/nodes-base/nodes/Trello/AttachmentDescription.ts new file mode 100644 index 0000000000..a04f507518 --- /dev/null +++ b/packages/nodes-base/nodes/Trello/AttachmentDescription.ts @@ -0,0 +1,276 @@ +import { INodeProperties } from "n8n-workflow"; + +export const attachmentOperations = [ + // ---------------------------------- + // attachment + // ---------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'attachment', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new attachment for a card', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete an attachment', + }, + { + name: 'Get', + value: 'get', + description: 'Get the data of an attachments', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Returns all attachments for the card', + } + ], + default: 'getAll', + description: 'The operation to perform.', + }, + +] as INodeProperties[]; + +export const attachmentFields = [ + + // ---------------------------------- + // attachment:create + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'attachment', + ], + }, + }, + description: 'The ID of the card to add attachment to.', + }, + { + displayName: 'Source URL', + name: 'url', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'attachment', + ], + }, + }, + description: 'The URL of the attachment to add.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'attachment', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'MIME Type', + name: 'mimeType', + type: 'string', + default: '', + placeholder: 'image/png', + description: 'The MIME type of the attachment to add.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'The name of the attachment to add.', + }, + ], + }, + + // ---------------------------------- + // attachment:delete + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'attachment', + ], + }, + }, + description: 'The ID of the card that attachment belongs to.', + }, + { + displayName: 'Attachment ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'attachment', + ], + }, + }, + description: 'The ID of the attachment to delete.', + }, + + // ---------------------------------- + // attachment:getAll + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'attachment', + ], + }, + }, + description: 'The ID of the card to get attachments.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'attachment', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list of fields.', + }, + ], + }, + + // ---------------------------------- + // attachment:get + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'attachment', + ], + }, + }, + description: 'The ID of the card to get attachment.', + }, + { + displayName: 'Attachment ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'attachment', + ], + }, + }, + description: 'The ID of the attachment to get.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'attachment', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list of fields.', + }, + ], + }, + +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Trello/BoardDescription.ts b/packages/nodes-base/nodes/Trello/BoardDescription.ts new file mode 100644 index 0000000000..b089d689ce --- /dev/null +++ b/packages/nodes-base/nodes/Trello/BoardDescription.ts @@ -0,0 +1,457 @@ +import { INodeProperties } from "n8n-workflow"; + +export const boardOperations = [ + // ---------------------------------- + // board + // ---------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'board', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new board', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a board', + }, + { + name: 'Get', + value: 'get', + description: 'Get the data of a board', + }, + { + name: 'Update', + value: 'update', + description: 'Update a board', + }, + ], + default: 'create', + description: 'The operation to perform.', + } +] as INodeProperties[]; + +export const boardFields = [ + + // ---------------------------------- + // board:create + // ---------------------------------- + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + placeholder: 'My board', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'board', + ], + }, + }, + description: 'The name of the board', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'board', + ], + }, + }, + description: 'The description of the board', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'board', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Aging', + name: 'prefs_cardAging', + type: 'options', + options: [ + { + name: 'Pirate', + value: 'pirate', + }, + { + name: 'Regular', + value: 'regular', + }, + ], + default: 'regular', + description: 'Determines the type of card aging that should take place on the board if card aging is enabled.', + }, + { + displayName: 'Background', + name: 'prefs_background', + type: 'string', + default: 'blue', + description: 'The id of a custom background or one of: blue, orange, green, red, purple, pink, lime, sky, grey.', + }, + { + displayName: 'Comments', + name: 'prefs_comments', + type: 'options', + options: [ + { + name: 'Disabled', + value: 'disabled', + }, + { + name: 'Members', + value: 'members', + }, + { + name: 'Observers', + value: 'observers', + }, + { + name: 'Organization', + value: 'org', + }, + { + name: 'Public', + value: 'public', + }, + ], + default: 'members', + description: 'Who can comment on cards on this board.', + }, + { + displayName: 'Covers', + name: 'prefs_cardCovers', + type: 'boolean', + default: true, + description: 'Determines whether card covers are enabled.', + }, + { + displayName: 'Invitations', + name: 'prefs_invitations', + type: 'options', + options: [ + { + name: 'Admins', + value: 'admins', + }, + { + name: 'Members', + value: 'members', + }, + ], + default: 'members', + description: 'Determines what types of members can invite users to join.', + }, + { + displayName: 'Keep From Source', + name: 'keepFromSource', + type: 'string', + default: 'none', + description: 'To keep cards from the original board pass in the value cards.', + }, + { + displayName: 'Labels', + name: 'defaultLabels', + type: 'boolean', + default: true, + description: 'Determines whether to use the default set of labels.', + }, + { + displayName: 'Lists', + name: 'defaultLists', + type: 'boolean', + default: true, + description: 'Determines whether to add the default set of lists to a board(To Do, Doing, Done).It is ignored if idBoardSource is provided.', + }, + { + displayName: 'Organization ID', + name: 'idOrganization', + type: 'string', + default: '', + description: 'The id or name of the team the board should belong to.', + }, + { + displayName: 'Permission Level', + name: 'prefs_permissionLevel', + type: 'options', + options: [ + { + name: 'Organization', + value: 'org', + }, + { + name: 'Private', + value: 'private', + }, + { + name: 'Public', + value: 'public', + }, + ], + default: 'private', + description: 'The permissions level of the board.', + }, + { + displayName: 'Power Ups', + name: 'powerUps', + type: 'options', + options: [ + { + name: 'All', + value: 'all', + }, + { + name: 'Calendar', + value: 'calendar', + }, + { + name: 'Card Aging', + value: 'cardAging', + }, + { + name: 'Recap', + value: 'recap', + }, + { + name: 'Voting', + value: 'voting', + }, + ], + default: 'all', + description: 'The Power-Ups that should be enabled on the new board.', + }, + { + displayName: 'Self Join', + name: 'prefs_selfJoin', + type: 'boolean', + default: true, + description: 'Determines whether users can join the boards themselves or whether they have to be invited.', + }, + { + displayName: 'Source IDs', + name: 'idBoardSource', + type: 'string', + default: '', + description: 'The id of a board to copy into the new board.', + }, + { + displayName: 'Voting', + name: 'prefs_voting', + type: 'options', + options: [ + { + name: 'Disabled', + value: 'disabled', + }, + { + name: 'Members', + value: 'members', + }, + { + name: 'Observers', + value: 'observers', + }, + { + name: 'Organization', + value: 'org', + }, + { + name: 'Public', + value: 'public', + }, + ], + default: 'disabled', + description: 'Who can vote on this board.', + }, + ], + }, + + // ---------------------------------- + // board:delete + // ---------------------------------- + { + displayName: 'Board ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'board', + ], + }, + }, + description: 'The ID of the board to delete.', + }, + + // ---------------------------------- + // board:get + // ---------------------------------- + { + displayName: 'Board ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'board', + ], + }, + }, + description: 'The ID of the board to get.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'board', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list:
closed, dateLastActivity, dateLastView, desc, descData,
idOrganization, invitations, invited, labelNames, memberships,
name, pinned, powerUps, prefs, shortLink, shortUrl,
starred, subscribed, url', + }, + { + displayName: 'Plugin Data', + name: 'pluginData', + type: 'boolean', + default: false, + description: 'Whether to include pluginData on the card with the response.', + }, + ], + }, + + // ---------------------------------- + // board:update + // ---------------------------------- + { + displayName: 'Board ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'board', + ], + }, + }, + description: 'The ID of the board to update.', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'board', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Closed', + name: 'closed', + type: 'boolean', + default: false, + description: 'Whether the board is closed.', + }, + { + displayName: 'Description', + name: 'desc', + type: 'string', + default: '', + description: 'New description of the board', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'New name of the board', + }, + { + displayName: 'Organization ID', + name: 'idOrganization', + type: 'string', + default: '', + description: 'The id of the team the board should be moved to.', + }, + { + displayName: 'Subscribed', + name: 'subscribed', + type: 'boolean', + default: false, + description: 'Whether the acting user is subscribed to the board.', + }, + ], + }, + +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Trello/CardDescription.ts b/packages/nodes-base/nodes/Trello/CardDescription.ts new file mode 100644 index 0000000000..6b5158e6d1 --- /dev/null +++ b/packages/nodes-base/nodes/Trello/CardDescription.ts @@ -0,0 +1,429 @@ +import { INodeProperties } from "n8n-workflow"; + +export const cardOperations = [ + // ---------------------------------- + // card + // ---------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'card', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new card', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a card', + }, + { + name: 'Get', + value: 'get', + description: 'Get the data of a card', + }, + { + name: 'Update', + value: 'update', + description: 'Update a card', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const cardFields = [ + // ---------------------------------- + // card:create + // ---------------------------------- + { + displayName: 'List ID', + name: 'listId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'card', + ], + }, + }, + description: 'The id of the list to create card in', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + placeholder: 'My card', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'card', + ], + }, + }, + description: 'The name of the card', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'card', + ], + }, + }, + description: 'The description of the card', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'card', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Due Date', + name: 'due', + type: 'dateTime', + default: '', + description: 'A due date for the card.', + }, + { + displayName: 'Due Complete', + name: 'dueComplete', + type: 'boolean', + default: false, + description: 'If the card is completed.', + }, + { + displayName: 'Position', + name: 'pos', + type: 'string', + default: 'bottom', + description: 'The position of the new card. top, bottom, or a positive float.', + }, + { + displayName: 'Member IDs', + name: 'idMembers', + type: 'string', + default: '', + description: 'Comma-separated list of member IDs to add to the card.', + }, + { + displayName: 'Label IDs', + name: 'idLabels', + type: 'string', + default: '', + description: 'Comma-separated list of label IDs to add to the card.', + }, + { + displayName: 'URL Source', + name: 'urlSource', + type: 'string', + default: '', + description: 'A source URL to attach to card.', + }, + { + displayName: 'Source ID', + name: 'idCardSource', + type: 'string', + default: '', + description: 'The ID of a card to copy into the new card.', + }, + { + displayName: 'Keep from source', + name: 'keepFromSource', + type: 'string', + default: 'all', + description: 'If using idCardSource you can specify which properties to copy over. all or comma-separated list of: attachments, checklists, comments, due, labels, members, stickers', + }, + ], + }, + + // ---------------------------------- + // card:delete + // ---------------------------------- + { + displayName: 'Card ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'card', + ], + }, + }, + description: 'The ID of the card to delete.', + }, + + // ---------------------------------- + // card:get + // ---------------------------------- + { + displayName: 'Card ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'card', + ], + }, + }, + description: 'The ID of the card to get.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'card', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list:
badges, checkItemStates, closed, dateLastActivity, desc,
descData, due, email, idBoard, idChecklists, idLabels, idList,
idMembers, idShort, idAttachmentCover, manualCoverAttachment
, labels, name, pos, shortUrl, url', + }, + { + displayName: 'Board', + name: 'board', + type: 'boolean', + default: false, + description: 'Whether to return the board object the card is on.', + }, + { + displayName: 'Board Fields', + name: 'board_fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list:
name, desc, descData, closed, idOrganization, pinned, url, prefs', + }, + { + displayName: 'Custom Field Items', + name: 'customFieldItems', + type: 'boolean', + default: false, + description: 'Whether to include the customFieldItems.', + }, + { + displayName: 'Members', + name: 'members', + type: 'boolean', + default: false, + description: 'Whether to return member objects for members on the card.', + }, + { + displayName: 'Member Fields', + name: 'member_fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list:
avatarHash, fullName, initials, username', + }, + { + displayName: 'Plugin Data', + name: 'pluginData', + type: 'boolean', + default: false, + description: 'Whether to include pluginData on the card with the response.', + }, + { + displayName: 'Stickers', + name: 'stickers', + type: 'boolean', + default: false, + description: 'Whether to include sticker models with the response.', + }, + { + displayName: 'Sticker Fields', + name: 'sticker_fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list of sticker fields.', + }, + ], + }, + + // ---------------------------------- + // card:update + // ---------------------------------- + { + displayName: 'Card ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'card', + ], + }, + }, + description: 'The ID of the card to update.', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'card', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Attachment Cover', + name: 'idAttachmentCover', + type: 'string', + default: '', + description: 'The ID of the image attachment the card should use as its cover, or null for none.', + }, + { + displayName: 'Board ID', + name: 'idBoard', + type: 'string', + default: '', + description: 'The ID of the board the card should be on.', + }, + { + displayName: 'Closed', + name: 'closed', + type: 'boolean', + default: false, + description: 'Whether the board is closed.', + }, + { + displayName: 'Description', + name: 'desc', + type: 'string', + default: '', + description: 'New description of the board.', + }, + { + displayName: 'Due Date', + name: 'due', + type: 'dateTime', + default: '', + description: 'A due date for the card.', + }, + { + displayName: 'Due Complete', + name: 'dueComplete', + type: 'boolean', + default: false, + description: 'If the card is completed.', + }, + { + displayName: 'Label IDs', + name: 'idLabels', + type: 'string', + default: '', + description: 'Comma-separated list of label IDs to set on card.', + }, + { + displayName: 'List ID', + name: 'idList', + type: 'string', + default: '', + description: 'The ID of the list the card should be in.', + }, + { + displayName: 'Member IDs', + name: 'idMembers', + type: 'string', + default: '', + description: 'Comma-separated list of member IDs to set on card.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'New name of the board', + }, + { + displayName: 'Position', + name: 'pos', + type: 'string', + default: 'bottom', + description: 'The position of the card. top, bottom, or a positive float.', + }, + { + displayName: 'Subscribed', + name: 'subscribed', + type: 'boolean', + default: false, + description: 'Whether the acting user is subscribed to the board.', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Trello/ChecklistDescription.ts b/packages/nodes-base/nodes/Trello/ChecklistDescription.ts new file mode 100644 index 0000000000..34036f83e2 --- /dev/null +++ b/packages/nodes-base/nodes/Trello/ChecklistDescription.ts @@ -0,0 +1,528 @@ +import { INodeProperties } from "n8n-workflow"; + +export const checklistOperations = [ + // ---------------------------------- + // checklist + // ---------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'checklist', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a new checklist', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a checklist', + }, + { + name: 'Delete Checklist Item', + value: 'deleteCheckItem', + description: 'Delete a checklist item', + }, + { + name: 'Get', + value: 'get', + description: 'Get the data of a checklist', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Returns all checklists for the card', + }, + { + name: 'Get Checklist Items', + value: 'getCheckItem', + description: 'Get a specific Checklist on a card', + }, + { + name: 'Get Completed Checklist Items', + value: 'completedCheckItems', + description: 'Get the completed Checklist items on a card', + }, + { + name: 'Update Checklist Item', + value: 'updateCheckItem', + description: 'Update an item in a checklist on a card.', + }, + ], + default: 'getAll', + description: 'The operation to perform.', + }, + +] as INodeProperties[]; + +export const checklistFields = [ + // ---------------------------------- + // checklist:create + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the card to add checklist to.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The URL of the checklist to add.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'checklist', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Id Of Checklist Source', + name: 'idChecklistSource', + type: 'string', + default: '', + description: 'The ID of a source checklist to copy into the new one.', + }, + { + displayName: 'Position', + name: 'pos', + type: 'string', + default: '', + description: 'The position of the checklist on the card. One of: top, bottom, or a positive number.', + }, + ], + }, + + // ---------------------------------- + // checklist:delete + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the card that checklist belongs to.', + }, + { + displayName: 'Checklist ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the checklist to delete.', + }, + + + // ---------------------------------- + // checklist:getAll + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the card to get checklists.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'checklist', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list of fields.', + }, + ], + }, + + // ---------------------------------- + // checklist:get + // ---------------------------------- + { + displayName: 'Checklist ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the checklist to get.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'checklist', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list of fields.', + }, + ], + }, + + // ---------------------------------- + // checklist:deleteCheckItem + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'deleteCheckItem', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the card that checklist belongs to.', + }, + { + displayName: 'CheckItem ID', + name: 'checkItemId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'deleteCheckItem', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the checklist item to delete.', + }, + + // ---------------------------------- + // checklist:getCheckItem + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getCheckItem', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the card that checklist belongs to.', + }, + { + displayName: 'CheckItem ID', + name: 'checkItemId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getCheckItem', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the checklist item to get.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'getCheckItem', + ], + resource: [ + 'checklist', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list of fields.', + }, + ], + }, + + // ---------------------------------- + // checklist:updateCheckItem + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'updateCheckItem', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the card that checklist belongs to.', + }, + { + displayName: 'CheckItem ID', + name: 'checkItemId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'updateCheckItem', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the checklist item to update.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'updateCheckItem', + ], + resource: [ + 'checklist', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'The new name for the checklist item.', + }, + { + displayName: 'State', + name: 'state', + type: 'options', + options: [ + { + name: 'complete', + value: 'complete' + }, + { + name: 'incomplete', + value: 'incomplete', + }, + ], + default: 'complete', + description: 'The resource to operate on.', + }, + { + displayName: 'Checklist ID', + name: 'checklistId', + type: 'string', + default: '', + description: 'The ID of the checklist this item is in', + }, + { + displayName: 'Position', + name: 'pos', + type: 'string', + default: '', + description: 'The position of the checklist on the card. One of: top, bottom, or a positive number.', + }, + ], + }, + + // ---------------------------------- + // checklist:completedCheckItems + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'completedCheckItems', + ], + resource: [ + 'checklist', + ], + }, + }, + description: 'The ID of the card for checkItems.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'completedCheckItems', + ], + resource: [ + 'checklist', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list of: "idCheckItem", "state".', + }, + ], + }, + +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Trello/LabelDescription.ts b/packages/nodes-base/nodes/Trello/LabelDescription.ts new file mode 100644 index 0000000000..9c23053282 --- /dev/null +++ b/packages/nodes-base/nodes/Trello/LabelDescription.ts @@ -0,0 +1,467 @@ +import { INodeProperties } from "n8n-workflow"; + +export const labelOperations = [ + // ---------------------------------- + // label + // ---------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'label', + ], + }, + }, + options: [ + { + name: 'Add to Card', + value: 'addLabel', + description: 'Add a label to a card.', + }, + { + name: 'Create', + value: 'create', + description: 'Create a new label', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a label', + }, + { + name: 'Get', + value: 'get', + description: 'Get the data of a label', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Returns all label for the board', + }, + { + name: 'Remove From Card', + value: 'removeLabel', + description: 'Remove a label from a card.', + }, + { + name: 'Update', + value: 'update', + description: 'Update a label.', + } + + ], + default: 'getAll', + description: 'The operation to perform.', + }, + +] as INodeProperties[]; + +export const labelFields = [ + // ---------------------------------- + // label:create + // ---------------------------------- + { + displayName: 'Board ID', + name: 'boardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'label', + ], + }, + }, + description: 'The ID of the board to create the label on.', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'label', + ], + }, + }, + description: 'Name for the label.', + }, + { + displayName: 'Color', + name: 'color', + type: 'options', + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'label', + ], + }, + }, + options: [ + { + name: 'black', + value: 'black', + }, + { + name: 'blue', + value: 'blue', + }, + { + name: 'green', + value: 'green' + }, + { + name: 'orange', + value: 'orange', + }, + { + name: 'lime', + value: 'lime', + }, + { + name: 'null', + value: 'null', + }, + { + name: 'pink', + value: 'pink', + }, + { + name: 'purple', + value: 'purple', + }, + { + name: 'red', + value: 'red', + }, + { + name: 'sky', + value: 'sky', + }, + { + name: 'yellow', + value: 'yellow' + }, + ], + default: 'null', + description: 'The color for the label.', + }, + + + // ---------------------------------- + // label:delete + // ---------------------------------- + { + displayName: 'Label ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'label', + ], + }, + }, + description: 'The ID of the label to delete.', + }, + + // ---------------------------------- + // label:getAll + // ---------------------------------- + { + displayName: 'Board ID', + name: 'boardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'label', + ], + }, + }, + description: 'The ID of the board to get label.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'label', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list of fields.', + }, + ], + }, + + // ---------------------------------- + // label:get + // ---------------------------------- + { + displayName: 'Label ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'label', + ], + }, + }, + description: 'Get information about a label by ID.', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'label', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Fields', + name: 'fields', + type: 'string', + default: 'all', + description: 'Fields to return. Either "all" or a comma-separated list of fields.', + }, + ], + }, + + // ---------------------------------- + // label:addLabel + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'addLabel', + ], + resource: [ + 'label', + ], + }, + }, + description: 'The ID of the card to get label.', + }, + { + displayName: 'Label ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'addLabel', + ], + resource: [ + 'label', + ], + }, + }, + description: 'The ID of the label to add.', + }, + + // ---------------------------------- + // label:removeLabel + // ---------------------------------- + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'removeLabel', + ], + resource: [ + 'label', + ], + }, + }, + description: 'The ID of the card to remove label from.', + }, + { + displayName: 'Label ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'removeLabel', + ], + resource: [ + 'label', + ], + }, + }, + description: 'The ID of the label to remove.', + }, + + // ---------------------------------- + // label:update + // ---------------------------------- + { + displayName: 'Label ID', + name: 'id', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'label', + ], + }, + }, + description: 'The ID of the label to update.', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'label', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'Name of the label.', + }, + { + displayName: 'Color', + name: 'color', + type: 'options', + options: [ + { + name: 'black', + value: 'black', + }, + { + name: 'blue', + value: 'blue', + }, + { + name: 'green', + value: 'green' + }, + { + name: 'orange', + value: 'orange', + }, + { + name: 'lime', + value: 'lime', + }, + { + name: 'null', + value: 'null', + }, + { + name: 'pink', + value: 'pink', + }, + { + name: 'purple', + value: 'purple', + }, + { + name: 'red', + value: 'red', + }, + { + name: 'sky', + value: 'sky', + }, + { + name: 'yellow', + value: 'yellow' + }, + ], + default: 'null', + description: 'The color for the label.', + }, + ], + }, + +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Trello/ListDescription.ts b/packages/nodes-base/nodes/Trello/ListDescription.ts new file mode 100644 index 0000000000..b6b4565632 --- /dev/null +++ b/packages/nodes-base/nodes/Trello/ListDescription.ts @@ -0,0 +1,247 @@ +import { INodeProperties } from "n8n-workflow"; + +export const listOperations = [ + // ---------------------------------- + // list + // ---------------------------------- + { + displayName: "Operation", + name: "operation", + type: "options", + displayOptions: { + show: { + resource: ["list"] + } + }, + options: [ + { + name: "Archive", + value: "archive", + description: "Archive/Unarchive a list" + }, + { + name: "Create", + value: "create", + description: "Create a new list" + }, + { + name: "Get", + value: "get", + description: "Get the data of a list" + }, + { + name: "Update", + value: "update", + description: "Update a list" + } + ], + default: "create", + description: "The operation to perform." + } +] as INodeProperties[]; + +export const listFields = [ + // ---------------------------------- + // list:archive + // ---------------------------------- + { + displayName: "List ID", + name: "id", + type: "string", + default: "", + required: true, + displayOptions: { + show: { + operation: ["archive"], + resource: ["list"] + } + }, + description: "The ID of the list to archive or unarchive." + }, + { + displayName: "Archive", + name: "archive", + type: "boolean", + default: false, + displayOptions: { + show: { + operation: ["archive"], + resource: ["list"] + } + }, + description: "If the list should be archived or unarchived." + }, + + // ---------------------------------- + // list:create + // ---------------------------------- + { + displayName: "Board ID", + name: "idBoard", + type: "string", + default: "", + required: true, + displayOptions: { + show: { + operation: ["create"], + resource: ["list"] + } + }, + description: "The ID of the board the list should be created in" + }, + { + displayName: "Name", + name: "name", + type: "string", + default: "", + placeholder: "My list", + required: true, + displayOptions: { + show: { + operation: ["create"], + resource: ["list"] + } + }, + description: "The name of the list" + }, + { + displayName: "Additional Fields", + name: "additionalFields", + type: "collection", + placeholder: "Add Field", + displayOptions: { + show: { + operation: ["create"], + resource: ["list"] + } + }, + default: {}, + options: [ + { + displayName: "List Source", + name: "idListSource", + type: "string", + default: "", + description: "ID of the list to copy into the new list." + }, + { + displayName: "Position", + name: "pos", + type: "string", + default: "bottom", + description: + "The position of the new list. top, bottom, or a positive float." + } + ] + }, + + // ---------------------------------- + // list:get + // ---------------------------------- + { + displayName: "List ID", + name: "id", + type: "string", + default: "", + required: true, + displayOptions: { + show: { + operation: ["get"], + resource: ["list"] + } + }, + description: "The ID of the list to get." + }, + { + displayName: "Additional Fields", + name: "additionalFields", + type: "collection", + placeholder: "Add Field", + displayOptions: { + show: { + operation: ["get"], + resource: ["list"] + } + }, + default: {}, + options: [ + { + displayName: "Fields", + name: "fields", + type: "string", + default: "all", + description: + 'Fields to return. Either "all" or a comma-separated list of fields.' + } + ] + }, + + // ---------------------------------- + // list:update + // ---------------------------------- + { + displayName: "List ID", + name: "id", + type: "string", + default: "", + required: true, + displayOptions: { + show: { + operation: ["update"], + resource: ["list"] + } + }, + description: "The ID of the list to update." + }, + { + displayName: "Update Fields", + name: "updateFields", + type: "collection", + placeholder: "Add Field", + displayOptions: { + show: { + operation: ["update"], + resource: ["list"] + } + }, + default: {}, + options: [ + { + displayName: "Board ID", + name: "idBoard", + type: "string", + default: "", + description: "ID of a board the list should be moved to." + }, + { + displayName: "Closed", + name: "closed", + type: "boolean", + default: false, + description: "Whether the list is closed." + }, + { + displayName: "Name", + name: "name", + type: "string", + default: "", + description: "New name of the list" + }, + { + displayName: "Position", + name: "pos", + type: "string", + default: "bottom", + description: + "The position of the list. top, bottom, or a positive float." + }, + { + displayName: "Subscribed", + name: "subscribed", + type: "boolean", + default: false, + description: "Whether the acting user is subscribed to the list." + } + ] + } +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Trello/Trello.node.ts b/packages/nodes-base/nodes/Trello/Trello.node.ts index 5c242b47f6..527c94bfb1 100644 --- a/packages/nodes-base/nodes/Trello/Trello.node.ts +++ b/packages/nodes-base/nodes/Trello/Trello.node.ts @@ -1,6 +1,4 @@ -import { - IExecuteFunctions, -} from 'n8n-core'; +import { IExecuteFunctions } from "n8n-core"; import { IDataObject, INodeTypeDescription, @@ -8,9 +6,13 @@ import { INodeType, } from 'n8n-workflow'; -import { - apiRequest, -} from './GenericFunctions'; +import { apiRequest } from "./GenericFunctions"; +import { attachmentOperations, attachmentFields } from './AttachmentDescription'; +import { boardOperations, boardFields } from './BoardDescription'; +import { cardOperations, cardFields } from './CardDescription'; +import { checklistOperations, checklistFields } from './ChecklistDescription'; +import { labelOperations, labelFields } from './LabelDescription'; +import { listOperations, listFields } from './ListDescription'; export class Trello implements INodeType { description: INodeTypeDescription = { @@ -40,8 +42,8 @@ export class Trello implements INodeType { type: 'options', options: [ { - name: "Attachment", - value: "attachment" + name: 'Attachment', + value: 'attachment' }, { name: 'Board', @@ -51,6 +53,14 @@ export class Trello implements INodeType { name: 'Card', value: 'card', }, + { + name: 'Checklist', + value: 'checklist', + }, + { + name: 'Label', + value: 'label' + }, { name: 'List', value: 'list', @@ -60,1432 +70,28 @@ export class Trello implements INodeType { description: 'The resource to operate on.', }, - + // ---------------------------------- + // operations + // ---------------------------------- + ...attachmentOperations, + ...boardOperations, + ...cardOperations, + ...checklistOperations, + ...labelOperations, + ...listOperations, // ---------------------------------- - // board + // fields // ---------------------------------- - { - displayName: 'Operation', - name: 'operation', - type: 'options', - displayOptions: { - show: { - resource: [ - 'board', - ], - }, - }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Create a new board', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a board', - }, - { - name: 'Get', - value: 'get', - description: 'Get the data of a board', - }, - { - name: 'Update', - value: 'update', - description: 'Update a board', - }, - ], - default: 'create', - description: 'The operation to perform.', - }, - - // ---------------------------------- - // board:create - // ---------------------------------- - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - placeholder: 'My board', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'board', - ], - }, - }, - description: 'The name of the board', - }, - { - displayName: 'Description', - name: 'description', - type: 'string', - default: '', - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'board', - ], - }, - }, - description: 'The description of the board', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'board', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Aging', - name: 'prefs_cardAging', - type: 'options', - options: [ - { - name: 'Pirate', - value: 'pirate', - }, - { - name: 'Regular', - value: 'regular', - }, - ], - default: 'regular', - description: 'Determines the type of card aging that should take place on the board if card aging is enabled.', - }, - { - displayName: 'Background', - name: 'prefs_background', - type: 'string', - default: 'blue', - description: 'The id of a custom background or one of: blue, orange, green, red, purple, pink, lime, sky, grey.', - }, - { - displayName: 'Comments', - name: 'prefs_comments', - type: 'options', - options: [ - { - name: 'Disabled', - value: 'disabled', - }, - { - name: 'Members', - value: 'members', - }, - { - name: 'Observers', - value: 'observers', - }, - { - name: 'Organization', - value: 'org', - }, - { - name: 'Public', - value: 'public', - }, - ], - default: 'members', - description: 'Who can comment on cards on this board.', - }, - { - displayName: 'Covers', - name: 'prefs_cardCovers', - type: 'boolean', - default: true, - description: 'Determines whether card covers are enabled.', - }, - { - displayName: 'Invitations', - name: 'prefs_invitations', - type: 'options', - options: [ - { - name: 'Admins', - value: 'admins', - }, - { - name: 'Members', - value: 'members', - }, - ], - default: 'members', - description: 'Determines what types of members can invite users to join.', - }, - { - displayName: 'Keep From Source', - name: 'keepFromSource', - type: 'string', - default: 'none', - description: 'To keep cards from the original board pass in the value cards.', - }, - { - displayName: 'Labels', - name: 'defaultLabels', - type: 'boolean', - default: true, - description: 'Determines whether to use the default set of labels.', - }, - { - displayName: 'Lists', - name: 'defaultLists', - type: 'boolean', - default: true, - description: 'Determines whether to add the default set of lists to a board(To Do, Doing, Done).It is ignored if idBoardSource is provided.', - }, - { - displayName: 'Organization ID', - name: 'idOrganization', - type: 'string', - default: '', - description: 'The id or name of the team the board should belong to.', - }, - { - displayName: 'Permission Level', - name: 'prefs_permissionLevel', - type: 'options', - options: [ - { - name: 'Organization', - value: 'org', - }, - { - name: 'Private', - value: 'private', - }, - { - name: 'Public', - value: 'public', - }, - ], - default: 'private', - description: 'The permissions level of the board.', - }, - { - displayName: 'Power Ups', - name: 'powerUps', - type: 'options', - options: [ - { - name: 'All', - value: 'all', - }, - { - name: 'Calendar', - value: 'calendar', - }, - { - name: 'Card Aging', - value: 'cardAging', - }, - { - name: 'Recap', - value: 'recap', - }, - { - name: 'Voting', - value: 'voting', - }, - ], - default: 'all', - description: 'The Power-Ups that should be enabled on the new board.', - }, - { - displayName: 'Self Join', - name: 'prefs_selfJoin', - type: 'boolean', - default: true, - description: 'Determines whether users can join the boards themselves or whether they have to be invited.', - }, - { - displayName: 'Source IDs', - name: 'idBoardSource', - type: 'string', - default: '', - description: 'The id of a board to copy into the new board.', - }, - { - displayName: 'Voting', - name: 'prefs_voting', - type: 'options', - options: [ - { - name: 'Disabled', - value: 'disabled', - }, - { - name: 'Members', - value: 'members', - }, - { - name: 'Observers', - value: 'observers', - }, - { - name: 'Organization', - value: 'org', - }, - { - name: 'Public', - value: 'public', - }, - ], - default: 'disabled', - description: 'Who can vote on this board.', - }, - ], - }, - - // ---------------------------------- - // board:delete - // ---------------------------------- - { - displayName: 'Board ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'delete', - ], - resource: [ - 'board', - ], - }, - }, - description: 'The ID of the board to delete.', - }, - - // ---------------------------------- - // board:get - // ---------------------------------- - { - displayName: 'Board ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'board', - ], - }, - }, - description: 'The ID of the board to get.', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'board', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list:
closed, dateLastActivity, dateLastView, desc, descData,
idOrganization, invitations, invited, labelNames, memberships,
name, pinned, powerUps, prefs, shortLink, shortUrl,
starred, subscribed, url', - }, - { - displayName: 'Plugin Data', - name: 'pluginData', - type: 'boolean', - default: false, - description: 'Whether to include pluginData on the card with the response.', - }, - ], - }, - - // ---------------------------------- - // board:update - // ---------------------------------- - { - displayName: 'Board ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'update', - ], - resource: [ - 'board', - ], - }, - }, - description: 'The ID of the board to update.', - }, - { - displayName: 'Update Fields', - name: 'updateFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'update', - ], - resource: [ - 'board', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Closed', - name: 'closed', - type: 'boolean', - default: false, - description: 'Whether the board is closed.', - }, - { - displayName: 'Description', - name: 'desc', - type: 'string', - default: '', - description: 'New description of the board', - }, - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - description: 'New name of the board', - }, - { - displayName: 'Organization ID', - name: 'idOrganization', - type: 'string', - default: '', - description: 'The id of the team the board should be moved to.', - }, - { - displayName: 'Subscribed', - name: 'subscribed', - type: 'boolean', - default: false, - description: 'Whether the acting user is subscribed to the board.', - }, - ], - }, - - - - // ---------------------------------- - // card - // ---------------------------------- - { - displayName: 'Operation', - name: 'operation', - type: 'options', - displayOptions: { - show: { - resource: [ - 'card', - ], - }, - }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Create a new card', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a card', - }, - { - name: 'Get', - value: 'get', - description: 'Get the data of a card', - }, - { - name: 'Update', - value: 'update', - description: 'Update a card', - }, - ], - default: 'create', - description: 'The operation to perform.', - }, - - // ---------------------------------- - // card:create - // ---------------------------------- - { - displayName: 'List ID', - name: 'listId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'card', - ], - }, - }, - description: 'The id of the list to create card in', - }, - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - placeholder: 'My card', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'card', - ], - }, - }, - description: 'The name of the card', - }, - { - displayName: 'Description', - name: 'description', - type: 'string', - default: '', - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'card', - ], - }, - }, - description: 'The description of the card', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'card', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Due Date', - name: 'due', - type: 'dateTime', - default: '', - description: 'A due date for the card.', - }, - { - displayName: 'Due Complete', - name: 'dueComplete', - type: 'boolean', - default: false, - description: 'If the card is completed.', - }, - { - displayName: 'Position', - name: 'pos', - type: 'string', - default: 'bottom', - description: 'The position of the new card. top, bottom, or a positive float.', - }, - { - displayName: 'Member IDs', - name: 'idMembers', - type: 'string', - default: '', - description: 'Comma-separated list of member IDs to add to the card.', - }, - { - displayName: 'Label IDs', - name: 'idLabels', - type: 'string', - default: '', - description: 'Comma-separated list of label IDs to add to the card.', - }, - { - displayName: 'URL Source', - name: 'urlSource', - type: 'string', - default: '', - description: 'A source URL to attach to card.', - }, - { - displayName: 'Source ID', - name: 'idCardSource', - type: 'string', - default: '', - description: 'The ID of a card to copy into the new card.', - }, - { - displayName: 'Keep from source', - name: 'keepFromSource', - type: 'string', - default: 'all', - description: 'If using idCardSource you can specify which properties to copy over. all or comma-separated list of: attachments, checklists, comments, due, labels, members, stickers', - }, - ], - }, - - // ---------------------------------- - // card:delete - // ---------------------------------- - { - displayName: 'Card ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'delete', - ], - resource: [ - 'card', - ], - }, - }, - description: 'The ID of the card to delete.', - }, - - // ---------------------------------- - // card:get - // ---------------------------------- - { - displayName: 'Card ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'card', - ], - }, - }, - description: 'The ID of the card to get.', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'card', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list:
badges, checkItemStates, closed, dateLastActivity, desc,
descData, due, email, idBoard, idChecklists, idLabels, idList,
idMembers, idShort, idAttachmentCover, manualCoverAttachment
, labels, name, pos, shortUrl, url', - }, - { - displayName: 'Board', - name: 'board', - type: 'boolean', - default: false, - description: 'Whether to return the board object the card is on.', - }, - { - displayName: 'Board Fields', - name: 'board_fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list:
name, desc, descData, closed, idOrganization, pinned, url, prefs', - }, - { - displayName: 'Custom Field Items', - name: 'customFieldItems', - type: 'boolean', - default: false, - description: 'Whether to include the customFieldItems.', - }, - { - displayName: 'Members', - name: 'members', - type: 'boolean', - default: false, - description: 'Whether to return member objects for members on the card.', - }, - { - displayName: 'Member Fields', - name: 'member_fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list:
avatarHash, fullName, initials, username', - }, - { - displayName: 'Plugin Data', - name: 'pluginData', - type: 'boolean', - default: false, - description: 'Whether to include pluginData on the card with the response.', - }, - { - displayName: 'Stickers', - name: 'stickers', - type: 'boolean', - default: false, - description: 'Whether to include sticker models with the response.', - }, - { - displayName: 'Sticker Fields', - name: 'sticker_fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list of sticker fields.', - }, - ], - }, - - // ---------------------------------- - // card:update - // ---------------------------------- - { - displayName: 'Card ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'update', - ], - resource: [ - 'card', - ], - }, - }, - description: 'The ID of the card to update.', - }, - { - displayName: 'Update Fields', - name: 'updateFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'update', - ], - resource: [ - 'card', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Attachment Cover', - name: 'idAttachmentCover', - type: 'string', - default: '', - description: 'The ID of the image attachment the card should use as its cover, or null for none.', - }, - { - displayName: 'Board ID', - name: 'idBoard', - type: 'string', - default: '', - description: 'The ID of the board the card should be on.', - }, - { - displayName: 'Closed', - name: 'closed', - type: 'boolean', - default: false, - description: 'Whether the board is closed.', - }, - { - displayName: 'Description', - name: 'desc', - type: 'string', - default: '', - description: 'New description of the board.', - }, - { - displayName: 'Due Date', - name: 'due', - type: 'dateTime', - default: '', - description: 'A due date for the card.', - }, - { - displayName: 'Due Complete', - name: 'dueComplete', - type: 'boolean', - default: false, - description: 'If the card is completed.', - }, - { - displayName: 'Label IDs', - name: 'idLabels', - type: 'string', - default: '', - description: 'Comma-separated list of label IDs to set on card.', - }, - { - displayName: 'List ID', - name: 'idList', - type: 'string', - default: '', - description: 'The ID of the list the card should be in.', - }, - { - displayName: 'Member IDs', - name: 'idMembers', - type: 'string', - default: '', - description: 'Comma-separated list of member IDs to set on card.', - }, - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - description: 'New name of the board', - }, - { - displayName: 'Position', - name: 'pos', - type: 'string', - default: 'bottom', - description: 'The position of the card. top, bottom, or a positive float.', - }, - { - displayName: 'Subscribed', - name: 'subscribed', - type: 'boolean', - default: false, - description: 'Whether the acting user is subscribed to the board.', - }, - ], - }, - - - - // ---------------------------------- - // list - // ---------------------------------- - { - displayName: 'Operation', - name: 'operation', - type: 'options', - displayOptions: { - show: { - resource: [ - 'list', - ], - }, - }, - options: [ - { - name: 'Archive', - value: 'archive', - description: 'Archive/Unarchive a list', - }, - { - name: 'Create', - value: 'create', - description: 'Create a new list', - }, - { - name: 'Get', - value: 'get', - description: 'Get the data of a list', - }, - { - name: 'Update', - value: 'update', - description: 'Update a list', - }, - ], - default: 'create', - description: 'The operation to perform.', - }, - - // ---------------------------------- - // list:archive - // ---------------------------------- - { - displayName: 'List ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'archive', - ], - resource: [ - 'list', - ], - }, - }, - description: 'The ID of the list to archive or unarchive.', - }, - { - displayName: 'Archive', - name: 'archive', - type: 'boolean', - default: false, - displayOptions: { - show: { - operation: [ - 'archive', - ], - resource: [ - 'list', - ], - }, - }, - description: 'If the list should be archived or unarchived.', - }, - - // ---------------------------------- - // list:create - // ---------------------------------- - { - displayName: 'Board ID', - name: 'idBoard', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'list', - ], - }, - }, - description: 'The ID of the board the list should be created in', - }, - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - placeholder: 'My list', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'list', - ], - }, - }, - description: 'The name of the list', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'list', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'List Source', - name: 'idListSource', - type: 'string', - default: '', - description: 'ID of the list to copy into the new list.', - }, - { - displayName: 'Position', - name: 'pos', - type: 'string', - default: 'bottom', - description: 'The position of the new list. top, bottom, or a positive float.', - }, - ], - }, - - // ---------------------------------- - // list:get - // ---------------------------------- - { - displayName: 'List ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'list', - ], - }, - }, - description: 'The ID of the list to get.', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'list', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list of fields.', - }, - ], - }, - - // ---------------------------------- - // list:update - // ---------------------------------- - { - displayName: 'List ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'update', - ], - resource: [ - 'list', - ], - }, - }, - description: 'The ID of the list to update.', - }, - { - displayName: 'Update Fields', - name: 'updateFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'update', - ], - resource: [ - 'list', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Board ID', - name: 'idBoard', - type: 'string', - default: '', - description: 'ID of a board the list should be moved to.', - }, - { - displayName: 'Closed', - name: 'closed', - type: 'boolean', - default: false, - description: 'Whether the list is closed.', - }, - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - description: 'New name of the list', - }, - { - displayName: 'Position', - name: 'pos', - type: 'string', - default: 'bottom', - description: 'The position of the list. top, bottom, or a positive float.', - }, - { - displayName: 'Subscribed', - name: 'subscribed', - type: 'boolean', - default: false, - description: 'Whether the acting user is subscribed to the list.', - }, - ], - }, - - // ---------------------------------- - // attachment - // ---------------------------------- - { - displayName: 'Operation', - name: 'operation', - type: 'options', - displayOptions: { - show: { - resource: [ - 'attachment', - ], - }, - }, - options: [ - { - name: 'Create', - value: 'create', - description: 'Create a new board', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete a board', - }, - { - name: 'Get', - value: 'get', - description: 'Get the data of an attachments', - }, - { - name: 'Get All', - value: 'getAll', - description: 'Returns all attachments for the card', - } - ], - default: 'getAll', - description: 'The operation to perform.', - }, - - // ---------------------------------- - // attachment:create - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'attachment', - ], - }, - }, - description: 'The ID of the card to add attachment to.', - }, - { - displayName: 'Source URL', - name: 'url', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'attachment', - ], - }, - }, - description: 'The URL of the attachment to add.', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'create', - ], - resource: [ - 'attachment', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'MIME Type', - name: 'mimeType', - type: 'string', - default: '', - placeholder: 'image/png', - description: 'The MIME type of the attachment to add.', - }, - { - displayName: 'Name', - name: 'name', - type: 'string', - default: '', - description: 'The name of the attachment to add.', - }, - ], - }, - - // ---------------------------------- - // attachment:delete - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'delete', - ], - resource: [ - 'attachment', - ], - }, - }, - description: 'The ID of the card to get delete.', - }, - { - displayName: 'Attachment ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'delete', - ], - resource: [ - 'attachment', - ], - }, - }, - description: 'The ID of the attachment to delete.', - }, - - // ---------------------------------- - // attachment:getAll - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'getAll', - ], - resource: [ - 'attachment', - ], - }, - }, - description: 'The ID of the card to get attachments.', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'getAll', - ], - resource: [ - 'attachment', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list of fields.', - }, - ], - }, - - // ---------------------------------- - // attachment:get - // ---------------------------------- - { - displayName: 'Card ID', - name: 'cardId', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'attachment', - ], - }, - }, - description: 'The ID of the card to get attachment.', - }, - { - displayName: 'Attachment ID', - name: 'id', - type: 'string', - default: '', - required: true, - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'attachment', - ], - }, - }, - description: 'The ID of the attachment to get.', - }, - { - displayName: 'Additional Fields', - name: 'additionalFields', - type: 'collection', - placeholder: 'Add Field', - displayOptions: { - show: { - operation: [ - 'get', - ], - resource: [ - 'attachment', - ], - }, - }, - default: {}, - options: [ - { - displayName: 'Fields', - name: 'fields', - type: 'string', - default: 'all', - description: 'Fields to return. Either "all" or a comma-separated list of fields.', - }, - ], - }, + ...attachmentFields, + ...boardFields, + ...cardFields, + ...checklistFields, + ...labelFields, + ...listFields ], + }; @@ -1630,7 +236,6 @@ export class Trello implements INodeType { throw new Error(`The operation "${operation}" is not known!`); } - } else if (resource === 'list') { if (operation === 'archive') { @@ -1756,6 +361,228 @@ export class Trello implements INodeType { } else { throw new Error(`The operation "${operation}" is not known!`); } + + } else if (resource === 'checklist') { + + if (operation === 'create') { + // ---------------------------------- + // create + // ---------------------------------- + + requestMethod = 'POST'; + + const cardId = this.getNodeParameter('cardId', i) as string; + const name = this.getNodeParameter('name', i) as string; + + Object.assign(qs, { name }); + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + + endpoint = `cards/${cardId}/checklists`; + + } else if (operation === 'delete') { + // ---------------------------------- + // delete + // ---------------------------------- + + requestMethod = 'DELETE'; + + const cardId = this.getNodeParameter('cardId', i) as string; + const id = this.getNodeParameter('id', i) as string; + + endpoint = `cards/${cardId}/checklists/${id}`; + + } else if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + + requestMethod = 'GET'; + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `checklists/${id}`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + + } else if (operation === 'getAll') { + // ---------------------------------- + // getAll + // ---------------------------------- + + requestMethod = 'GET'; + + const cardId = this.getNodeParameter('cardId', i) as string; + + endpoint = `cards/${cardId}/checklists`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + + } else if (operation === 'getCheckItem') { + // ---------------------------------- + // getCheckItem + // ---------------------------------- + + requestMethod = 'GET'; + + const cardId = this.getNodeParameter('cardId', i) as string; + const checkItemId = this.getNodeParameter('checkItemId', i) as string; + + endpoint = `cards/${cardId}/checkItem/${checkItemId}`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + + } else if (operation === 'deleteCheckItem') { + // ---------------------------------- + // deleteCheckItem + // ---------------------------------- + + requestMethod = 'DELETE'; + + const cardId = this.getNodeParameter('cardId', i) as string; + const checkItemId = this.getNodeParameter('checkItemId', i) as string; + + endpoint = `cards/${cardId}/checkItem/${checkItemId}`; + + } else if (operation === 'updateCheckItem') { + // ---------------------------------- + // updateCheckItem + // ---------------------------------- + + requestMethod = 'PUT'; + + const cardId = this.getNodeParameter('cardId', i) as string; + const checkItemId = this.getNodeParameter('checkItemId', i) as string; + + endpoint = `cards/${cardId}/checkItem/${checkItemId}`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + + } else if (operation ==='completedCheckItems') { + // ---------------------------------- + // completedCheckItems + // ---------------------------------- + + requestMethod = 'GET'; + + const cardId = this.getNodeParameter('cardId', i) as string; + + endpoint = `cards/${cardId}/checkItemStates`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + + } else { + throw new Error(`The operation "${operation}" is not known!`); + } + } else if (resource === 'label') { + + if (operation === 'create') { + // ---------------------------------- + // create + // ---------------------------------- + + requestMethod = 'POST'; + + const idBoard = this.getNodeParameter('boardId', i) as string; + const name = this.getNodeParameter('name', i) as string; + const color = this.getNodeParameter('color', i) as string; + + Object.assign(qs, { + idBoard, + name, + color + }); + + endpoint = 'labels'; + + } else if (operation === 'delete') { + // ---------------------------------- + // delete + // ---------------------------------- + + requestMethod = 'DELETE'; + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `labels/${id}`; + + } else if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + + requestMethod = 'GET'; + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `labels/${id}`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(qs, additionalFields); + + } else if (operation === 'getAll') { + // ---------------------------------- + // getAll + // ---------------------------------- + + requestMethod = 'GET'; + + const idBoard = this.getNodeParameter('boardId', i) as string; + + endpoint = `board/${idBoard}/labels`; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + Object.assign(qs, additionalFields); + } else if (operation === 'update') { + // ---------------------------------- + // update + // ---------------------------------- + + requestMethod = 'PUT'; + + const id = this.getNodeParameter('id', i) as string; + + endpoint = `labels/${id}`; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + Object.assign(qs, updateFields); + + } else if (operation === 'addLabel') { + // ---------------------------------- + // addLabel + // ---------------------------------- + + requestMethod = 'POST'; + + const cardId = this.getNodeParameter('cardId', i) as string; + const id = this.getNodeParameter('id', i) as string; + + qs.value = id; + + endpoint = `/cards/${cardId}/idLabels`; + + } else if (operation === 'removeLabel') { + // ---------------------------------- + // removeLabel + // ---------------------------------- + + requestMethod = 'DELETE'; + + const cardId = this.getNodeParameter('cardId', i) as string; + const id = this.getNodeParameter('id', i) as string; + + endpoint = `/cards/${cardId}/idLabels/${id}`; + + } else { + throw new Error(`The operation "${operation}" is not known!`); + } } else { throw new Error(`The resource "${resource}" is not known!`); } diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 6f21480d63..f8b27f7b51 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.44.0", + "version": "0.45.1", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", @@ -38,6 +38,7 @@ "dist/credentials/CodaApi.credentials.js", "dist/credentials/CopperApi.credentials.js", "dist/credentials/ClearbitApi.credentials.js", + "dist/credentials/DisqusApi.credentials.js", "dist/credentials/DropboxApi.credentials.js", "dist/credentials/EventbriteApi.credentials.js", "dist/credentials/FreshdeskApi.credentials.js", @@ -51,6 +52,7 @@ "dist/credentials/HttpDigestAuth.credentials.js", "dist/credentials/HttpHeaderAuth.credentials.js", "dist/credentials/HubspotApi.credentials.js", + "dist/credentials/HunterApi.credentials.js", "dist/credentials/Imap.credentials.js", "dist/credentials/IntercomApi.credentials.js", "dist/credentials/JiraSoftwareCloudApi.credentials.js", @@ -75,6 +77,7 @@ "dist/credentials/Smtp.credentials.js", "dist/credentials/StripeApi.credentials.js", "dist/credentials/SalesmateApi.credentials.js", + "dist/credentials/SegmentApi.credentials.js", "dist/credentials/TelegramApi.credentials.js", "dist/credentials/TodoistApi.credentials.js", "dist/credentials/TrelloApi.credentials.js", @@ -107,6 +110,7 @@ "dist/nodes/Clearbit/Clearbit.node.js", "dist/nodes/Cron.node.js", "dist/nodes/Discord/Discord.node.js", + "dist/nodes/Disqus/Disqus.node.js", "dist/nodes/Dropbox/Dropbox.node.js", "dist/nodes/EditImage.node.js", "dist/nodes/EmailReadImap.node.js", @@ -132,6 +136,7 @@ "dist/nodes/HtmlExtract/HtmlExtract.node.js", "dist/nodes/HttpRequest.node.js", "dist/nodes/Hubspot/Hubspot.node.js", + "dist/nodes/Hunter/Hunter.node.js", "dist/nodes/If.node.js", "dist/nodes/Intercom/Intercom.node.js", "dist/nodes/Interval.node.js", @@ -174,6 +179,7 @@ "dist/nodes/Stripe/StripeTrigger.node.js", "dist/nodes/Switch.node.js", "dist/nodes/Salesmate/Salesmate.node.js", + "dist/nodes/Segment/Segment.node.js", "dist/nodes/Telegram/Telegram.node.js", "dist/nodes/Telegram/TelegramTrigger.node.js", "dist/nodes/Todoist/Todoist.node.js", @@ -208,10 +214,11 @@ "@types/nodemailer": "^4.6.5", "@types/redis": "^2.8.11", "@types/request-promise-native": "~1.0.15", + "@types/uuid": "^3.4.6", "@types/xml2js": "^0.4.3", "gulp": "^4.0.0", "jest": "^24.9.0", - "n8n-workflow": "~0.20.0", + "n8n-workflow": "~0.21.0", "ts-jest": "^24.0.2", "tslint": "^5.17.0", "typescript": "~3.7.4" @@ -232,13 +239,14 @@ "lodash.unset": "^4.5.2", "mongodb": "^3.3.2", "mysql2": "^2.0.1", - "n8n-core": "~0.22.0", + "n8n-core": "~0.23.0", "nodemailer": "^5.1.1", "pdf-parse": "^1.1.1", "pg-promise": "^9.0.3", "redis": "^2.8.0", "rhea": "^1.0.11", "rss-parser": "^3.7.0", + "uuid": "^3.4.0", "vm2": "^3.6.10", "xlsx": "^0.14.3", "xml2js": "^0.4.22" diff --git a/packages/workflow/package.json b/packages/workflow/package.json index af2733ba3d..f00bfe3b69 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -1,6 +1,6 @@ { "name": "n8n-workflow", - "version": "0.20.0", + "version": "0.21.0", "description": "Workflow base code of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 8fd424ba19..2070215911 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -495,7 +495,7 @@ export interface IWebhookData { node: string; path: string; webhookDescription: IWebhookDescription; - workflow: Workflow; + workflowId: string; workflowExecuteAdditionalData: IWorkflowExecuteAdditionalData; } diff --git a/packages/workflow/src/NodeHelpers.ts b/packages/workflow/src/NodeHelpers.ts index e3c6a79b8a..76d8f8b55f 100644 --- a/packages/workflow/src/NodeHelpers.ts +++ b/packages/workflow/src/NodeHelpers.ts @@ -771,7 +771,7 @@ export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData: node: node.name, path, webhookDescription, - workflow, + workflowId: workflow.id, workflowExecuteAdditionalData: additionalData, }); } diff --git a/packages/workflow/src/Workflow.ts b/packages/workflow/src/Workflow.ts index 1ee7991391..9bb8391aed 100644 --- a/packages/workflow/src/Workflow.ts +++ b/packages/workflow/src/Workflow.ts @@ -907,41 +907,6 @@ export class Workflow { } - /** - * Executes the hooks of the node - * - * @param {string} hookName The name of the hook to execute - * @param {IWebhookData} webhookData - * @param {INodeExecuteFunctions} nodeExecuteFunctions - * @param {WorkflowExecuteMode} mode - * @returns {Promise} - * @memberof Workflow - */ - async runNodeHooks(hookName: string, webhookData: IWebhookData, nodeExecuteFunctions: INodeExecuteFunctions, mode: WorkflowExecuteMode): Promise { - const node = this.getNode(webhookData.node) as INode; - const nodeType = this.nodeTypes.getByName(node.type) as INodeType; - - if (nodeType.description.hooks === undefined) { - return; - } - - - if (nodeType.description.hooks[hookName] === undefined) { - return; - } - - - if (nodeType.hooks === undefined && nodeType.description.hooks[hookName]!.length !== 0) { - // There should be hook functions but they do not exist - throw new Error('There are hooks defined to run but are not implemented.'); - } - - for (const hookDescription of nodeType.description.hooks[hookName]!) { - const thisArgs = nodeExecuteFunctions.getExecuteHookFunctions(this, node, webhookData.workflowExecuteAdditionalData, mode); - await nodeType.hooks![hookDescription.method].call(thisArgs); - } - } - /** * Executes the Webhooks method of the node