Merge branch 'master' into feature/clearbit-node

This commit is contained in:
Ricardo Espinoza 2020-01-26 23:31:13 -05:00 committed by GitHub
commit 2f3c4d2a32
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 6876 additions and 1649 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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",

View file

@ -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<boolean> {
return this.activeWebhooks!.removeByWorkflowId(workflowId);
async removeWorkflowWebhooks(workflowId: string): Promise<void> {
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<void> {
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];

View file

@ -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<boolean> => {
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);
}));

View file

@ -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);
}
}

View file

@ -84,9 +84,9 @@ export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflo
* @param {((error: Error | null, data: IResponseCallbackData) => void)} responseCallback
* @returns {(Promise<string | undefined>)}
*/
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<string | undefined> {
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<string | undefined> {
// 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.'), {});

View file

@ -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;

View file

@ -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"
},

View file

@ -1,6 +1,7 @@
import {
IWebhookData,
WebhookHttpMethod,
Workflow,
WorkflowExecuteMode,
} from 'n8n-workflow';
@ -29,29 +30,26 @@ export class ActiveWebhooks {
* @returns {Promise<void>}
* @memberof ActiveWebhooks
*/
async add(webhookData: IWebhookData, mode: WorkflowExecuteMode): Promise<void> {
if (webhookData.workflow.id === undefined) {
async add(workflow: Workflow, webhookData: IWebhookData, mode: WorkflowExecuteMode): Promise<void> {
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<boolean> {
async removeWorkflow(workflow: Workflow): Promise<boolean> {
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<void> {
const workflowIds = Object.keys(this.workflowWebhooks);
async removeAll(workflows: Workflow[]): Promise<void> {
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;
// }
}

View file

@ -69,9 +69,7 @@ export class ActiveWorkflows {
async add(id: string, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, getTriggerFunctions: IGetExecuteTriggerFunctions, getPollFunctions: IGetExecutePollFunctions): Promise<void> {
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);
}
};

View file

@ -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;
}

View file

@ -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",

View file

@ -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

View file

@ -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',

View file

@ -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') {

View file

@ -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 <a href="https://disqus.com/api/docs/auth/">Disqus auth</a>.'
},
];
}

View file

@ -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: '',
},
];
}

View file

@ -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: '',
},
];
}

View file

@ -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<INodePropertyOptions[]> {
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<INodePropertyOptions[]> {

View file

@ -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',

View file

@ -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"<br />query type instead of an ID by including "forum". You may<br />pass us the "link" query type to filter by URL. You must pass<br />the "forum" if you do not have the Pro API Access addon.',
},
],
}
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
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)];
}
}

View file

@ -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<any> { // 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<any> { // 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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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<any> { // 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<any> { // 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;
}

View file

@ -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<INodeExecutionData[][]> {
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)];
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View file

@ -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',
},
],

View file

@ -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',
},
],

View file

@ -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',
},
],

View file

@ -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;
}

View file

@ -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);
}
}
}

View file

@ -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<any> { // 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);
}
}

View file

@ -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: 'Users 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: 'Users birthday',
},
{
displayName: 'Created At',
name: 'createdAt',
type: 'dateTime',
default: '',
description: 'Date the users 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 users 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[];

View file

@ -0,0 +1,10 @@
import { IDataObject } from "n8n-workflow";
export interface IIdentify {
userId?: string;
anonymousId?: string;
traits?: IDataObject;
context?: IDataObject;
integrations?: IDataObject;
timestamp?: string;
}

View file

@ -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<INodeExecutionData[][]> {
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)];
}
}

File diff suppressed because it is too large Load diff

View file

@ -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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -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[];

View file

@ -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:<br />closed, dateLastActivity, dateLastView, desc, descData,<br />idOrganization, invitations, invited, labelNames, memberships,<br />name, pinned, powerUps, prefs, shortLink, shortUrl,<br />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[];

View file

@ -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:<br />badges, checkItemStates, closed, dateLastActivity, desc,<br />descData, due, email, idBoard, idChecklists, idLabels, idList,<br />idMembers, idShort, idAttachmentCover, manualCoverAttachment<br />, 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:<br />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:<br />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[];

View file

@ -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[];

View file

@ -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[];

View file

@ -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[];

File diff suppressed because it is too large Load diff

View file

@ -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"

View file

@ -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",

View file

@ -495,7 +495,7 @@ export interface IWebhookData {
node: string;
path: string;
webhookDescription: IWebhookDescription;
workflow: Workflow;
workflowId: string;
workflowExecuteAdditionalData: IWorkflowExecuteAdditionalData;
}

View file

@ -771,7 +771,7 @@ export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData:
node: node.name,
path,
webhookDescription,
workflow,
workflowId: workflow.id,
workflowExecuteAdditionalData: additionalData,
});
}

View file

@ -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<void>}
* @memberof Workflow
*/
async runNodeHooks(hookName: string, webhookData: IWebhookData, nodeExecuteFunctions: INodeExecuteFunctions, mode: WorkflowExecuteMode): Promise<void> {
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