🔀 Merge branch 'master' into add-tags
10
.github/workflows/docker-images-rpi.yml
vendored
|
@ -4,6 +4,12 @@ on:
|
|||
push:
|
||||
tags:
|
||||
- n8n@*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'n8n version to build docker image for.'
|
||||
required: true
|
||||
default: '0.112.0'
|
||||
|
||||
jobs:
|
||||
armv7_job:
|
||||
|
@ -28,7 +34,7 @@ jobs:
|
|||
run: |
|
||||
docker buildx build \
|
||||
--platform linux/arm/v7 \
|
||||
--build-arg N8N_VERSION=${{steps.vars.outputs.tag}} \
|
||||
-t ${{ secrets.DOCKER_USERNAME }}/n8n:${{steps.vars.outputs.tag}}-rpi \
|
||||
--build-arg N8N_VERSION=${{github.event.inputs.version || steps.vars.outputs.tag}} \
|
||||
-t ${{ secrets.DOCKER_USERNAME }}/n8n:${{github.event.inputs.version || steps.vars.outputs.tag}}-rpi \
|
||||
-t ${{ secrets.DOCKER_USERNAME }}/n8n:latest-rpi \
|
||||
--output type=image,push=true docker/images/n8n-rpi
|
||||
|
|
2
.github/workflows/tests.yml
vendored
|
@ -9,7 +9,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x, 12.x, 14.x]
|
||||
node-version: [12.x, 14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
|
|
@ -15,6 +15,7 @@ ENV NODE_ENV production
|
|||
|
||||
WORKDIR /data
|
||||
|
||||
USER node
|
||||
USER root
|
||||
|
||||
CMD n8n
|
||||
CMD chown -R node:node /home/node/.n8n \
|
||||
&& gosu node n8n
|
||||
|
|
|
@ -2,6 +2,36 @@
|
|||
|
||||
This list shows all the versions which include breaking changes and how to upgrade.
|
||||
|
||||
## 0.113.0
|
||||
|
||||
### What changed?
|
||||
In the Dropbox node, both credential types (Access Token & OAuth2) have a new parameter called "APP Access Type".
|
||||
|
||||
### When is action necessary?
|
||||
|
||||
If you are using a Dropbox APP with permission type, "App Folder".
|
||||
|
||||
### How to upgrade:
|
||||
|
||||
Open your Dropbox node's credentials and set the "APP Access Type" parameter to "App Folder".
|
||||
|
||||
## 0.111.0
|
||||
|
||||
### What changed?
|
||||
In the Dropbox node, now all operations are performed relative to the user's root directory.
|
||||
|
||||
### When is action necessary?
|
||||
|
||||
If you are using any resource/operation with OAuth2 authentication.
|
||||
|
||||
If you are using the `folder:list` operation with the parameter `Folder Path` empty (root path) and have a Team Space in your Dropbox account.
|
||||
|
||||
### How to upgrade:
|
||||
|
||||
Open the Dropbox node, go to the OAuth2 credential you are using and reconnect it again.
|
||||
|
||||
Also, if you are using the `folder:list` operation, make sure your logic is taking into account the team folders in the response.
|
||||
|
||||
## 0.105.0
|
||||
|
||||
### What changed?
|
||||
|
|
|
@ -21,7 +21,7 @@ import {
|
|||
WorkflowCredentials,
|
||||
WorkflowHelpers,
|
||||
WorkflowRunner,
|
||||
} from "../src";
|
||||
} from '../src';
|
||||
|
||||
|
||||
export class Execute extends Command {
|
||||
|
@ -127,7 +127,7 @@ export class Execute extends Command {
|
|||
// Check if the workflow contains the required "Start" node
|
||||
// "requiredNodeTypes" are also defined in editor-ui/views/NodeView.vue
|
||||
const requiredNodeTypes = ['n8n-nodes-base.start'];
|
||||
let startNode: INode | undefined= undefined;
|
||||
let startNode: INode | undefined = undefined;
|
||||
for (const node of workflowData!.nodes) {
|
||||
if (requiredNodeTypes.includes(node.type)) {
|
||||
startNode = node;
|
||||
|
|
|
@ -140,7 +140,7 @@ export class ExportCredentialsCommand extends Command {
|
|||
let fileContents: string, i: number;
|
||||
for (i = 0; i < credentials.length; i++) {
|
||||
fileContents = JSON.stringify(credentials[i], null, flags.pretty ? 2 : undefined);
|
||||
const filename = (flags.output!.endsWith(path.sep) ? flags.output! : flags.output + path.sep) + credentials[i].id + ".json";
|
||||
const filename = (flags.output!.endsWith(path.sep) ? flags.output! : flags.output + path.sep) + credentials[i].id + '.json';
|
||||
fs.writeFileSync(filename, fileContents);
|
||||
}
|
||||
console.log('Successfully exported', i, 'credentials.');
|
||||
|
|
|
@ -116,7 +116,7 @@ export class ExportWorkflowsCommand extends Command {
|
|||
let fileContents: string, i: number;
|
||||
for (i = 0; i < workflows.length; i++) {
|
||||
fileContents = JSON.stringify(workflows[i], null, flags.pretty ? 2 : undefined);
|
||||
const filename = (flags.output!.endsWith(path.sep) ? flags.output! : flags.output + path.sep) + workflows[i].id + ".json";
|
||||
const filename = (flags.output!.endsWith(path.sep) ? flags.output! : flags.output + path.sep) + workflows[i].id + '.json';
|
||||
fs.writeFileSync(filename, fileContents);
|
||||
}
|
||||
console.log('Successfully exported', i, 'workflows.');
|
||||
|
|
|
@ -56,10 +56,22 @@ export class ImportCredentialsCommand extends Command {
|
|||
try {
|
||||
await Db.init();
|
||||
let i;
|
||||
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
if (encryptionKey === undefined) {
|
||||
throw new Error('No encryption key got found to encrypt the credentials!');
|
||||
}
|
||||
|
||||
if (flags.separate) {
|
||||
const files = await glob((flags.input.endsWith(path.sep) ? flags.input : flags.input + path.sep) + '*.json');
|
||||
for (i = 0; i < files.length; i++) {
|
||||
const credential = JSON.parse(fs.readFileSync(files[i], { encoding: 'utf8' }));
|
||||
|
||||
if (typeof credential.data === 'object') {
|
||||
// plain data / decrypted input. Should be encrypted first.
|
||||
Credentials.prototype.setData.call(credential, credential.data, encryptionKey);
|
||||
}
|
||||
|
||||
await Db.collections.Credentials!.save(credential);
|
||||
}
|
||||
} else {
|
||||
|
@ -69,10 +81,6 @@ export class ImportCredentialsCommand extends Command {
|
|||
throw new Error(`File does not seem to contain credentials.`);
|
||||
}
|
||||
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
if (encryptionKey === undefined) {
|
||||
throw new Error('No encryption key got found to encrypt the credentials!');
|
||||
}
|
||||
for (i = 0; i < fileContents.length; i++) {
|
||||
if (typeof fileContents[i].data === 'object') {
|
||||
// plain data / decrypted input. Should be encrypted first.
|
||||
|
|
|
@ -17,11 +17,12 @@ import {
|
|||
Db,
|
||||
ExternalHooks,
|
||||
GenericHelpers,
|
||||
IExecutionsCurrentSummary,
|
||||
LoadNodesAndCredentials,
|
||||
NodeTypes,
|
||||
Server,
|
||||
TestWebhooks,
|
||||
} from "../src";
|
||||
} from '../src';
|
||||
import { IDataObject } from 'n8n-workflow';
|
||||
|
||||
|
||||
|
@ -97,12 +98,15 @@ export class Start extends Command {
|
|||
|
||||
// Wait for active workflow executions to finish
|
||||
const activeExecutionsInstance = ActiveExecutions.getInstance();
|
||||
let executingWorkflows = activeExecutionsInstance.getActiveExecutions();
|
||||
let executingWorkflows = activeExecutionsInstance.getActiveExecutions() as IExecutionsCurrentSummary[];
|
||||
|
||||
let count = 0;
|
||||
while (executingWorkflows.length !== 0) {
|
||||
if (count++ % 4 === 0) {
|
||||
console.log(`Waiting for ${executingWorkflows.length} active executions to finish...`);
|
||||
executingWorkflows.map(execution => {
|
||||
console.log(` - Execution ID ${execution.id}, workflow ID: ${execution.workflowId}`);
|
||||
});
|
||||
}
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 500);
|
||||
|
@ -129,7 +133,7 @@ export class Start extends Command {
|
|||
await (async () => {
|
||||
try {
|
||||
// Start directly with the init of the database to improve startup time
|
||||
const startDbInitPromise = Db.init().catch(error => {
|
||||
const startDbInitPromise = Db.init().catch((error: Error) => {
|
||||
console.error(`There was an error initializing DB: ${error.message}`);
|
||||
|
||||
processExistCode = 1;
|
||||
|
@ -168,7 +172,7 @@ export class Start extends Command {
|
|||
const redisDB = config.get('queue.bull.redis.db');
|
||||
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
|
||||
let lastTimer = 0, cumulativeTimeout = 0;
|
||||
|
||||
|
||||
const settings = {
|
||||
retryStrategy: (times: number): number | null => {
|
||||
const now = Date.now();
|
||||
|
@ -180,7 +184,7 @@ export class Start extends Command {
|
|||
cumulativeTimeout += now - lastTimer;
|
||||
lastTimer = now;
|
||||
if (cumulativeTimeout > redisConnectionTimeoutLimit) {
|
||||
console.error('Unable to connect to Redis after ' + redisConnectionTimeoutLimit + ". Exiting process.");
|
||||
console.error('Unable to connect to Redis after ' + redisConnectionTimeoutLimit + '. Exiting process.');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
@ -200,7 +204,7 @@ export class Start extends Command {
|
|||
if (redisDB) {
|
||||
settings.db = redisDB;
|
||||
}
|
||||
|
||||
|
||||
// This connection is going to be our heartbeat
|
||||
// IORedis automatically pings redis and tries to reconnect
|
||||
// We will be using the retryStrategy above
|
||||
|
@ -215,13 +219,13 @@ export class Start extends Command {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const dbType = await GenericHelpers.getConfigValue('database.type') as DatabaseType;
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
const shouldRunVacuum = config.get('database.sqlite.executeVacuumOnStartup') as number;
|
||||
if (shouldRunVacuum) {
|
||||
Db.collections.Execution!.query("VACUUM;");
|
||||
Db.collections.Execution!.query('VACUUM;');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -280,7 +284,7 @@ export class Start extends Command {
|
|||
Start.openBrowser();
|
||||
}
|
||||
this.log(`\nPress "o" to open in Browser.`);
|
||||
process.stdin.on("data", (key : string) => {
|
||||
process.stdin.on('data', (key: string) => {
|
||||
if (key === 'o') {
|
||||
Start.openBrowser();
|
||||
inputText = '';
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
import {
|
||||
Db,
|
||||
GenericHelpers,
|
||||
} from "../../src";
|
||||
} from '../../src';
|
||||
|
||||
|
||||
export class UpdateWorkflowCommand extends Command {
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
NodeTypes,
|
||||
TestWebhooks,
|
||||
WebhookServer,
|
||||
} from "../src";
|
||||
} from '../src';
|
||||
import { IDataObject } from 'n8n-workflow';
|
||||
|
||||
|
||||
|
@ -98,7 +98,7 @@ export class Webhook extends Command {
|
|||
// Wrap that the process does not close but we can still use async
|
||||
await (async () => {
|
||||
if (config.get('executions.mode') !== 'queue') {
|
||||
/**
|
||||
/**
|
||||
* It is technically possible to run without queues but
|
||||
* there are 2 known bugs when running in this mode:
|
||||
* - Executions list will be problematic as the main process
|
||||
|
@ -154,7 +154,7 @@ export class Webhook extends Command {
|
|||
const redisDB = config.get('queue.bull.redis.db');
|
||||
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
|
||||
let lastTimer = 0, cumulativeTimeout = 0;
|
||||
|
||||
|
||||
const settings = {
|
||||
retryStrategy: (times: number): number | null => {
|
||||
const now = Date.now();
|
||||
|
@ -166,7 +166,7 @@ export class Webhook extends Command {
|
|||
cumulativeTimeout += now - lastTimer;
|
||||
lastTimer = now;
|
||||
if (cumulativeTimeout > redisConnectionTimeoutLimit) {
|
||||
console.error('Unable to connect to Redis after ' + redisConnectionTimeoutLimit + ". Exiting process.");
|
||||
console.error('Unable to connect to Redis after ' + redisConnectionTimeoutLimit + '. Exiting process.');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ export class Webhook extends Command {
|
|||
if (redisDB) {
|
||||
settings.db = redisDB;
|
||||
}
|
||||
|
||||
|
||||
// This connection is going to be our heartbeat
|
||||
// IORedis automatically pings redis and tries to reconnect
|
||||
// We will be using the retryStrategy above
|
||||
|
@ -201,7 +201,7 @@ export class Webhook extends Command {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
await WebhookServer.start();
|
||||
|
||||
// Start to get active workflows and run their triggers
|
||||
|
|
|
@ -35,7 +35,7 @@ import {
|
|||
ResponseHelper,
|
||||
WorkflowCredentials,
|
||||
WorkflowExecuteAdditionalData,
|
||||
} from "../src";
|
||||
} from '../src';
|
||||
|
||||
import * as config from '../config';
|
||||
import * as Bull from 'bull';
|
||||
|
@ -132,7 +132,7 @@ export class Worker extends Command {
|
|||
const credentials = await WorkflowCredentials(currentExecutionDb.workflowData.nodes);
|
||||
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
|
||||
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksIntegrated(currentExecutionDb.mode, job.data.executionId, currentExecutionDb.workflowData, { retryOf: currentExecutionDb.retryOf as string });
|
||||
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(currentExecutionDb.mode, job.data.executionId, currentExecutionDb.workflowData, { retryOf: currentExecutionDb.retryOf as string });
|
||||
|
||||
let workflowExecute: WorkflowExecute;
|
||||
let workflowRun: PCancelable<IRun>;
|
||||
|
@ -241,7 +241,7 @@ export class Worker extends Command {
|
|||
cumulativeTimeout += now - lastTimer;
|
||||
lastTimer = now;
|
||||
if (cumulativeTimeout > redisConnectionTimeoutLimit) {
|
||||
console.error('Unable to connect to Redis after ' + redisConnectionTimeoutLimit + ". Exiting process.");
|
||||
console.error('Unable to connect to Redis after ' + redisConnectionTimeoutLimit + '. Exiting process.');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -446,6 +446,20 @@ const config = convict({
|
|||
},
|
||||
|
||||
endpoints: {
|
||||
metrics: {
|
||||
enable: {
|
||||
format: 'Boolean',
|
||||
default: false,
|
||||
env: 'N8N_METRICS',
|
||||
doc: 'Enable metrics endpoint',
|
||||
},
|
||||
prefix: {
|
||||
format: String,
|
||||
default: 'n8n_',
|
||||
env: 'N8N_METRICS_PREFIX',
|
||||
doc: 'An optional prefix for metric names. Default: n8n_',
|
||||
},
|
||||
},
|
||||
rest: {
|
||||
format: String,
|
||||
default: 'rest',
|
||||
|
@ -471,7 +485,7 @@ const config = convict({
|
|||
doc: 'Disable production webhooks from main process. This helps ensures no http traffic load to main process when using webhook-specific processes.',
|
||||
},
|
||||
skipWebhoooksDeregistrationOnShutdown: {
|
||||
/**
|
||||
/**
|
||||
* Longer explanation: n8n deregisters webhooks on shutdown / deactivation
|
||||
* and registers on startup / activation. If we skip
|
||||
* deactivation on shutdown, webhooks will remain active on 3rd party services.
|
||||
|
|
|
@ -1,137 +1,138 @@
|
|||
{
|
||||
"name": "n8n",
|
||||
"version": "0.110.3",
|
||||
"description": "n8n Workflow Automation Tool",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
"author": {
|
||||
"name": "Jan Oberhauser",
|
||||
"email": "jan@n8n.io"
|
||||
"name": "n8n",
|
||||
"version": "0.114.0",
|
||||
"description": "n8n Workflow Automation Tool",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
"author": {
|
||||
"name": "Jan Oberhauser",
|
||||
"email": "jan@n8n.io"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/n8n-io/n8n.git"
|
||||
},
|
||||
"main": "dist/index",
|
||||
"types": "dist/src/index.d.ts",
|
||||
"oclif": {
|
||||
"commands": "./dist/commands",
|
||||
"bin": "n8n"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "concurrently -k -n \"TypeScript,Node\" -c \"yellow.bold,cyan.bold\" \"npm run watch\" \"nodemon\"",
|
||||
"postpack": "rm -f oclif.manifest.json",
|
||||
"prepack": "echo \"Building project...\" && rm -rf dist && tsc -b && oclif-dev manifest",
|
||||
"start": "run-script-os",
|
||||
"start:default": "cd bin && ./n8n",
|
||||
"start:windows": "cd bin && n8n",
|
||||
"test": "jest",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"watch": "tsc --watch",
|
||||
"typeorm": "ts-node ./node_modules/typeorm/cli.js"
|
||||
},
|
||||
"bin": {
|
||||
"n8n": "./bin/n8n"
|
||||
},
|
||||
"keywords": [
|
||||
"automate",
|
||||
"automation",
|
||||
"IaaS",
|
||||
"iPaaS",
|
||||
"n8n",
|
||||
"workflow"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"templates",
|
||||
"dist",
|
||||
"oclif.manifest.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@oclif/dev-cli": "^1.22.2",
|
||||
"@types/basic-auth": "^1.1.2",
|
||||
"@types/bcryptjs": "^2.4.1",
|
||||
"@types/bull": "^3.3.10",
|
||||
"@types/compression": "1.0.1",
|
||||
"@types/connect-history-api-fallback": "^1.3.1",
|
||||
"@types/convict": "^4.2.1",
|
||||
"@types/dotenv": "^8.2.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^26.0.13",
|
||||
"@types/localtunnel": "^1.9.0",
|
||||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/node": "14.0.27",
|
||||
"@types/open": "^6.1.0",
|
||||
"@types/parseurl": "^1.3.1",
|
||||
"@types/request-promise-native": "~1.0.15",
|
||||
"concurrently": "^5.1.0",
|
||||
"jest": "^26.4.2",
|
||||
"nodemon": "^2.0.2",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"run-script-os": "^1.0.7",
|
||||
"ts-jest": "^26.3.0",
|
||||
"ts-node": "^8.9.1",
|
||||
"tslint": "^6.1.2",
|
||||
"typescript": "~3.9.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oclif/command": "^1.5.18",
|
||||
"@oclif/errors": "^1.2.2",
|
||||
"@types/jsonwebtoken": "^8.3.4",
|
||||
"basic-auth": "^2.0.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"body-parser": "^1.18.3",
|
||||
"body-parser-xml": "^1.1.0",
|
||||
"bull": "^3.19.0",
|
||||
"client-oauth2": "^4.2.5",
|
||||
"compression": "^1.7.4",
|
||||
"connect-history-api-fallback": "^1.6.0",
|
||||
"convict": "^6.0.1",
|
||||
"csrf": "^3.1.0",
|
||||
"dotenv": "^8.0.0",
|
||||
"express": "^4.16.4",
|
||||
"flatted": "^2.0.0",
|
||||
"glob-promise": "^3.4.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"inquirer": "^7.0.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jwks-rsa": "~1.12.1",
|
||||
"localtunnel": "^2.0.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mysql2": "~2.1.0",
|
||||
"n8n-core": "~0.67.0",
|
||||
"n8n-editor-ui": "~0.84.0",
|
||||
"n8n-nodes-base": "~0.111.0",
|
||||
"n8n-workflow": "~0.55.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"open": "^7.0.0",
|
||||
"pg": "^8.3.0",
|
||||
"prom-client": "^13.1.0",
|
||||
"request-promise-native": "^1.0.7",
|
||||
"sqlite3": "^5.0.1",
|
||||
"sse-channel": "^3.1.1",
|
||||
"tslib": "1.11.2",
|
||||
"typeorm": "^0.2.30"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.tsx?$": "ts-jest"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/n8n-io/n8n.git"
|
||||
},
|
||||
"main": "dist/index",
|
||||
"types": "dist/src/index.d.ts",
|
||||
"oclif": {
|
||||
"commands": "./dist/commands",
|
||||
"bin": "n8n"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "concurrently -k -n \"TypeScript,Node\" -c \"yellow.bold,cyan.bold\" \"npm run watch\" \"nodemon\"",
|
||||
"postpack": "rm -f oclif.manifest.json",
|
||||
"prepack": "echo \"Building project...\" && rm -rf dist && tsc -b && oclif-dev manifest",
|
||||
"start": "run-script-os",
|
||||
"start:default": "cd bin && ./n8n",
|
||||
"start:windows": "cd bin && n8n",
|
||||
"test": "jest",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"watch": "tsc --watch",
|
||||
"typeorm": "ts-node ./node_modules/typeorm/cli.js"
|
||||
},
|
||||
"bin": {
|
||||
"n8n": "./bin/n8n"
|
||||
},
|
||||
"keywords": [
|
||||
"automate",
|
||||
"automation",
|
||||
"IaaS",
|
||||
"iPaaS",
|
||||
"n8n",
|
||||
"workflow"
|
||||
"testURL": "http://localhost/",
|
||||
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||
"testPathIgnorePatterns": [
|
||||
"/dist/",
|
||||
"/node_modules/"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"templates",
|
||||
"dist",
|
||||
"oclif.manifest.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@oclif/dev-cli": "^1.22.2",
|
||||
"@types/basic-auth": "^1.1.2",
|
||||
"@types/bcryptjs": "^2.4.1",
|
||||
"@types/bull": "^3.3.10",
|
||||
"@types/compression": "1.0.1",
|
||||
"@types/connect-history-api-fallback": "^1.3.1",
|
||||
"@types/convict": "^4.2.1",
|
||||
"@types/dotenv": "^8.2.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^26.0.13",
|
||||
"@types/localtunnel": "^1.9.0",
|
||||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/node": "14.0.27",
|
||||
"@types/open": "^6.1.0",
|
||||
"@types/parseurl": "^1.3.1",
|
||||
"@types/request-promise-native": "~1.0.15",
|
||||
"concurrently": "^5.1.0",
|
||||
"jest": "^26.4.2",
|
||||
"nodemon": "^2.0.2",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"run-script-os": "^1.0.7",
|
||||
"ts-jest": "^26.3.0",
|
||||
"ts-node": "^8.9.1",
|
||||
"tslint": "^6.1.2",
|
||||
"typescript": "~3.9.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oclif/command": "^1.5.18",
|
||||
"@oclif/errors": "^1.2.2",
|
||||
"@types/jsonwebtoken": "^8.3.4",
|
||||
"basic-auth": "^2.0.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"body-parser": "^1.18.3",
|
||||
"body-parser-xml": "^1.1.0",
|
||||
"bull": "^3.19.0",
|
||||
"client-oauth2": "^4.2.5",
|
||||
"compression": "^1.7.4",
|
||||
"connect-history-api-fallback": "^1.6.0",
|
||||
"convict": "^5.0.0",
|
||||
"csrf": "^3.1.0",
|
||||
"dotenv": "^8.0.0",
|
||||
"express": "^4.16.4",
|
||||
"flatted": "^2.0.0",
|
||||
"glob-promise": "^3.4.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"inquirer": "^7.0.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jwks-rsa": "~1.12.1",
|
||||
"localtunnel": "^2.0.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mysql2": "~2.1.0",
|
||||
"n8n-core": "~0.64.0",
|
||||
"n8n-editor-ui": "~0.80.0",
|
||||
"n8n-nodes-base": "~0.107.3",
|
||||
"n8n-workflow": "~0.53.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"open": "^7.0.0",
|
||||
"pg": "^8.3.0",
|
||||
"request-promise-native": "^1.0.7",
|
||||
"sqlite3": "^5.0.1",
|
||||
"sse-channel": "^3.1.1",
|
||||
"tslib": "1.11.2",
|
||||
"typeorm": "^0.2.30"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.tsx?$": "ts-jest"
|
||||
},
|
||||
"testURL": "http://localhost/",
|
||||
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||
"testPathIgnorePatterns": [
|
||||
"/dist/",
|
||||
"/node_modules/"
|
||||
],
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"json"
|
||||
]
|
||||
}
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"json"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
NodeHelpers,
|
||||
WebhookHttpMethod,
|
||||
Workflow,
|
||||
WorkflowActivateMode,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -66,7 +67,7 @@ export class ActiveWorkflowRunner {
|
|||
for (const workflowData of workflowsData) {
|
||||
console.log(` - ${workflowData.name}`);
|
||||
try {
|
||||
await this.add(workflowData.id.toString(), workflowData);
|
||||
await this.add(workflowData.id.toString(), 'init', workflowData);
|
||||
console.log(` => Started`);
|
||||
} catch (error) {
|
||||
console.log(` => ERROR: Workflow could not be activated:`);
|
||||
|
@ -273,7 +274,7 @@ export class ActiveWorkflowRunner {
|
|||
* @returns {Promise<void>}
|
||||
* @memberof ActiveWorkflowRunner
|
||||
*/
|
||||
async addWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode): Promise<void> {
|
||||
async addWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): Promise<void> {
|
||||
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData);
|
||||
let path = '' as string | undefined;
|
||||
|
||||
|
@ -319,10 +320,10 @@ export class ActiveWorkflowRunner {
|
|||
|
||||
await Db.collections.Webhook?.insert(webhook);
|
||||
|
||||
const webhookExists = await workflow.runWebhookMethod('checkExists', webhookData, NodeExecuteFunctions, mode, false);
|
||||
const webhookExists = await workflow.runWebhookMethod('checkExists', webhookData, NodeExecuteFunctions, mode, activation, false);
|
||||
if (webhookExists !== true) {
|
||||
// If webhook does not exist yet create it
|
||||
await workflow.runWebhookMethod('create', webhookData, NodeExecuteFunctions, mode, false);
|
||||
await workflow.runWebhookMethod('create', webhookData, NodeExecuteFunctions, mode, activation, false);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
@ -378,7 +379,7 @@ export class ActiveWorkflowRunner {
|
|||
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData);
|
||||
|
||||
for (const webhookData of webhooks) {
|
||||
await workflow.runWebhookMethod('delete', webhookData, NodeExecuteFunctions, mode, false);
|
||||
await workflow.runWebhookMethod('delete', webhookData, NodeExecuteFunctions, mode, 'update', false);
|
||||
}
|
||||
|
||||
await WorkflowHelpers.saveStaticData(workflow);
|
||||
|
@ -446,9 +447,9 @@ export class ActiveWorkflowRunner {
|
|||
* @returns {IGetExecutePollFunctions}
|
||||
* @memberof ActiveWorkflowRunner
|
||||
*/
|
||||
getExecutePollFunctions(workflowData: IWorkflowDb, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode): IGetExecutePollFunctions {
|
||||
getExecutePollFunctions(workflowData: IWorkflowDb, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): IGetExecutePollFunctions {
|
||||
return ((workflow: Workflow, node: INode) => {
|
||||
const returnFunctions = NodeExecuteFunctions.getExecutePollFunctions(workflow, node, additionalData, mode);
|
||||
const returnFunctions = NodeExecuteFunctions.getExecutePollFunctions(workflow, node, additionalData, mode, activation);
|
||||
returnFunctions.__emit = (data: INodeExecutionData[][]): void => {
|
||||
this.runWorkflow(workflowData, node, data, additionalData, mode);
|
||||
};
|
||||
|
@ -467,9 +468,9 @@ export class ActiveWorkflowRunner {
|
|||
* @returns {IGetExecuteTriggerFunctions}
|
||||
* @memberof ActiveWorkflowRunner
|
||||
*/
|
||||
getExecuteTriggerFunctions(workflowData: IWorkflowDb, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode): IGetExecuteTriggerFunctions{
|
||||
getExecuteTriggerFunctions(workflowData: IWorkflowDb, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): IGetExecuteTriggerFunctions{
|
||||
return ((workflow: Workflow, node: INode) => {
|
||||
const returnFunctions = NodeExecuteFunctions.getExecuteTriggerFunctions(workflow, node, additionalData, mode);
|
||||
const returnFunctions = NodeExecuteFunctions.getExecuteTriggerFunctions(workflow, node, additionalData, mode, activation);
|
||||
returnFunctions.emit = (data: INodeExecutionData[][]): void => {
|
||||
WorkflowHelpers.saveStaticData(workflow);
|
||||
this.runWorkflow(workflowData, node, data, additionalData, mode).catch((err) => console.error(err));
|
||||
|
@ -486,7 +487,7 @@ export class ActiveWorkflowRunner {
|
|||
* @returns {Promise<void>}
|
||||
* @memberof ActiveWorkflowRunner
|
||||
*/
|
||||
async add(workflowId: string, workflowData?: IWorkflowDb): Promise<void> {
|
||||
async add(workflowId: string, activation: WorkflowActivateMode, workflowData?: IWorkflowDb): Promise<void> {
|
||||
if (this.activeWorkflows === null) {
|
||||
throw new Error(`The "activeWorkflows" instance did not get initialized yet.`);
|
||||
}
|
||||
|
@ -511,15 +512,15 @@ export class ActiveWorkflowRunner {
|
|||
const mode = 'trigger';
|
||||
const credentials = await WorkflowCredentials(workflowData.nodes);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
|
||||
const getTriggerFunctions = this.getExecuteTriggerFunctions(workflowData, additionalData, mode);
|
||||
const getPollFunctions = this.getExecutePollFunctions(workflowData, additionalData, mode);
|
||||
const getTriggerFunctions = this.getExecuteTriggerFunctions(workflowData, additionalData, mode, activation);
|
||||
const getPollFunctions = this.getExecutePollFunctions(workflowData, additionalData, mode, activation);
|
||||
|
||||
// Add the workflows which have webhooks defined
|
||||
await this.addWorkflowWebhooks(workflowInstance, additionalData, mode);
|
||||
await this.addWorkflowWebhooks(workflowInstance, additionalData, mode, activation);
|
||||
|
||||
if (workflowInstance.getTriggerNodes().length !== 0
|
||||
|| workflowInstance.getPollNodes().length !== 0) {
|
||||
await this.activeWorkflows.add(workflowId, workflowInstance, additionalData, getTriggerFunctions, getPollFunctions);
|
||||
await this.activeWorkflows.add(workflowId, workflowInstance, additionalData, mode, activation, getTriggerFunctions, getPollFunctions);
|
||||
}
|
||||
|
||||
if (this.activationErrors[workflowId] !== undefined) {
|
||||
|
|
|
@ -95,10 +95,10 @@ function extractSchemaForKey(configKey: string, configSchema: IDataObject): IDat
|
|||
for (const key of configKeyParts) {
|
||||
if (configSchema[key] === undefined) {
|
||||
throw new Error(`Key "${key}" of ConfigKey "${configKey}" does not exist`);
|
||||
} else if ((configSchema[key]! as IDataObject).properties === undefined) {
|
||||
} else if ((configSchema[key]! as IDataObject)._cvtProperties === undefined) {
|
||||
configSchema = configSchema[key] as IDataObject;
|
||||
} else {
|
||||
configSchema = (configSchema[key] as IDataObject).properties as IDataObject;
|
||||
configSchema = (configSchema[key] as IDataObject)._cvtProperties as IDataObject;
|
||||
}
|
||||
}
|
||||
return configSchema;
|
||||
|
@ -114,7 +114,8 @@ function extractSchemaForKey(configKey: string, configSchema: IDataObject): IDat
|
|||
export async function getConfigValue(configKey: string): Promise<string | boolean | number | undefined> {
|
||||
// Get the environment variable
|
||||
const configSchema = config.getSchema();
|
||||
const currentSchema = extractSchemaForKey(configKey, configSchema.properties as IDataObject);
|
||||
// @ts-ignore
|
||||
const currentSchema = extractSchemaForKey(configKey, configSchema._cvtProperties as IDataObject);
|
||||
// Check if environment variable is defined for config key
|
||||
if (currentSchema.env === undefined) {
|
||||
// No environment variable defined, so return value from config
|
||||
|
@ -152,7 +153,8 @@ export async function getConfigValue(configKey: string): Promise<string | boolea
|
|||
export function getConfigValueSync(configKey: string): string | boolean | number | undefined {
|
||||
// Get the environment variable
|
||||
const configSchema = config.getSchema();
|
||||
const currentSchema = extractSchemaForKey(configKey, configSchema.properties as IDataObject);
|
||||
// @ts-ignore
|
||||
const currentSchema = extractSchemaForKey(configKey, configSchema._cvtProperties as IDataObject);
|
||||
// Check if environment variable is defined for config key
|
||||
if (currentSchema.env === undefined) {
|
||||
// No environment variable defined, so return value from config
|
||||
|
|
|
@ -23,6 +23,7 @@ import * as csrf from 'csrf';
|
|||
import * as requestPromise from 'request-promise-native';
|
||||
import { createHmac } from 'crypto';
|
||||
import { compare } from 'bcryptjs';
|
||||
import * as promClient from 'prom-client';
|
||||
|
||||
import {
|
||||
ActiveExecutions,
|
||||
|
@ -91,9 +92,7 @@ import {
|
|||
import {
|
||||
FindManyOptions,
|
||||
FindOneOptions,
|
||||
LessThan,
|
||||
LessThanOrEqual,
|
||||
MoreThanOrEqual,
|
||||
Not,
|
||||
} from 'typeorm';
|
||||
|
||||
|
@ -108,6 +107,7 @@ import * as parseUrl from 'parseurl';
|
|||
import * as querystring from 'querystring';
|
||||
import * as Queue from '../src/Queue';
|
||||
import { OptionsWithUrl } from 'request-promise-native';
|
||||
import { Registry } from 'prom-client';
|
||||
|
||||
class App {
|
||||
|
||||
|
@ -197,6 +197,16 @@ class App {
|
|||
|
||||
async config(): Promise<void> {
|
||||
|
||||
const enableMetrics = config.get('endpoints.metrics.enable') as boolean;
|
||||
let register: Registry;
|
||||
|
||||
if (enableMetrics === true) {
|
||||
const prefix = config.get('endpoints.metrics.prefix') as string;
|
||||
register = new promClient.Registry();
|
||||
register.setDefaultLabels({ prefix });
|
||||
promClient.collectDefaultMetrics({ register });
|
||||
}
|
||||
|
||||
this.versions = await GenericHelpers.getVersions();
|
||||
this.frontendSettings.versionCli = this.versions.cli;
|
||||
|
||||
|
@ -204,7 +214,7 @@ class App {
|
|||
|
||||
const excludeEndpoints = config.get('security.excludeEndpoints') as string;
|
||||
|
||||
const ignoredEndpoints = ['healthz', this.endpointWebhook, this.endpointWebhookTest, this.endpointPresetCredentials];
|
||||
const ignoredEndpoints = ['healthz', 'metrics', this.endpointWebhook, this.endpointWebhookTest, this.endpointPresetCredentials];
|
||||
ignoredEndpoints.push.apply(ignoredEndpoints, excludeEndpoints.split(':'));
|
||||
|
||||
const authIgnoreRegex = new RegExp(`^\/(${_(ignoredEndpoints).compact().join('|')})\/?.*$`);
|
||||
|
@ -386,7 +396,7 @@ class App {
|
|||
this.app.use(history({
|
||||
rewrites: [
|
||||
{
|
||||
from: new RegExp(`^\/(${this.restEndpoint}|healthz|css|js|${this.endpointWebhook}|${this.endpointWebhookTest})\/?.*$`),
|
||||
from: new RegExp(`^\/(${this.restEndpoint}|healthz|metrics|css|js|${this.endpointWebhook}|${this.endpointWebhookTest})\/?.*$`),
|
||||
to: (context) => {
|
||||
return context.parsedUrl!.pathname!.toString();
|
||||
},
|
||||
|
@ -454,7 +464,16 @@ class App {
|
|||
ResponseHelper.sendSuccessResponse(res, responseData, true, 200);
|
||||
});
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// Metrics
|
||||
// ----------------------------------------
|
||||
if (enableMetrics === true) {
|
||||
this.app.get('/metrics', async (req: express.Request, res: express.Response) => {
|
||||
const response = await register.metrics();
|
||||
res.setHeader('Content-Type', register.contentType);
|
||||
ResponseHelper.sendSuccessResponse(res, response, true, 200);
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------
|
||||
// Workflow
|
||||
|
@ -603,7 +622,7 @@ class App {
|
|||
try {
|
||||
await this.externalHooks.run('workflow.activate', [responseData]);
|
||||
|
||||
await this.activeWorkflowRunner.add(id);
|
||||
await this.activeWorkflowRunner.add(id, isActive ? 'update' : 'activate');
|
||||
} catch (error) {
|
||||
// If workflow could not be activated set it again to inactive
|
||||
newWorkflowData.active = false;
|
||||
|
@ -649,6 +668,7 @@ class App {
|
|||
const startNodes: string[] | undefined = req.body.startNodes;
|
||||
const destinationNode: string | undefined = req.body.destinationNode;
|
||||
const executionMode = 'manual';
|
||||
const activationMode = 'manual';
|
||||
|
||||
const sessionId = GenericHelpers.getSessionId(req);
|
||||
|
||||
|
@ -658,7 +678,7 @@ class App {
|
|||
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
|
||||
const nodeTypes = NodeTypes();
|
||||
const workflowInstance = new Workflow({ id: workflowData.id, name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: false, nodeTypes, staticData: undefined, settings: workflowData.settings });
|
||||
const needsWebhook = await this.testWebhooks.needsWebhookData(workflowData, workflowInstance, additionalData, executionMode, sessionId, destinationNode);
|
||||
const needsWebhook = await this.testWebhooks.needsWebhookData(workflowData, workflowInstance, additionalData, executionMode, activationMode, sessionId, destinationNode);
|
||||
if (needsWebhook === true) {
|
||||
return {
|
||||
waitingForWebhook: true,
|
||||
|
@ -1728,7 +1748,7 @@ class App {
|
|||
stoppedAt: result.stoppedAt ? new Date(result.stoppedAt) : undefined,
|
||||
finished: result.finished,
|
||||
};
|
||||
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
IWorkflowExecuteAdditionalData,
|
||||
WebhookHttpMethod,
|
||||
Workflow,
|
||||
WorkflowActivateMode,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -161,7 +162,7 @@ export class TestWebhooks {
|
|||
* @returns {(Promise<IExecutionDb | undefined>)}
|
||||
* @memberof TestWebhooks
|
||||
*/
|
||||
async needsWebhookData(workflowData: IWorkflowDb, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, sessionId?: string, destinationNode?: string): Promise<boolean> {
|
||||
async needsWebhookData(workflowData: IWorkflowDb, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode, sessionId?: string, destinationNode?: string): Promise<boolean> {
|
||||
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, destinationNode);
|
||||
|
||||
if (webhooks.length === 0) {
|
||||
|
@ -193,7 +194,7 @@ export class TestWebhooks {
|
|||
};
|
||||
|
||||
try {
|
||||
await this.activeWebhooks!.add(workflow, webhookData, mode);
|
||||
await this.activeWebhooks!.add(workflow, webhookData, mode, activation);
|
||||
} catch (error) {
|
||||
activatedKey.forEach(deleteKey => delete this.testWebhookData[deleteKey] );
|
||||
await this.activeWebhooks!.removeWorkflow(workflow);
|
||||
|
|
|
@ -230,54 +230,64 @@ export function hookFunctionsPreExecute(parentProcessMode?: string): IWorkflowEx
|
|||
return;
|
||||
}
|
||||
|
||||
const execution = await Db.collections.Execution!.findOne(this.executionId);
|
||||
try {
|
||||
const execution = await Db.collections.Execution!.findOne(this.executionId);
|
||||
|
||||
if (execution === undefined) {
|
||||
// Something went badly wrong if this happens.
|
||||
// This check is here mostly to make typescript happy.
|
||||
return undefined;
|
||||
}
|
||||
const fullExecutionData: IExecutionResponse = ResponseHelper.unflattenExecutionData(execution);
|
||||
if (execution === undefined) {
|
||||
// Something went badly wrong if this happens.
|
||||
// This check is here mostly to make typescript happy.
|
||||
return undefined;
|
||||
}
|
||||
const fullExecutionData: IExecutionResponse = ResponseHelper.unflattenExecutionData(execution);
|
||||
|
||||
if (fullExecutionData.finished) {
|
||||
// We already received ´workflowExecuteAfter´ webhook, so this is just an async call
|
||||
// that was left behind. We skip saving because the other call should have saved everything
|
||||
// so this one is safe to ignore
|
||||
return;
|
||||
if (fullExecutionData.finished) {
|
||||
// We already received ´workflowExecuteAfter´ webhook, so this is just an async call
|
||||
// that was left behind. We skip saving because the other call should have saved everything
|
||||
// so this one is safe to ignore
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (fullExecutionData.data === undefined) {
|
||||
fullExecutionData.data = {
|
||||
startData: {
|
||||
},
|
||||
resultData: {
|
||||
runData: {},
|
||||
},
|
||||
executionData: {
|
||||
contextData: {},
|
||||
nodeExecutionStack: [],
|
||||
waitingExecution: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (Array.isArray(fullExecutionData.data.resultData.runData[nodeName])) {
|
||||
// Append data if array exists
|
||||
fullExecutionData.data.resultData.runData[nodeName].push(data);
|
||||
} else {
|
||||
// Initialize array and save data
|
||||
fullExecutionData.data.resultData.runData[nodeName] = [data];
|
||||
}
|
||||
|
||||
fullExecutionData.data.executionData = executionData.executionData;
|
||||
|
||||
// Set last executed node so that it may resume on failure
|
||||
fullExecutionData.data.resultData.lastNodeExecuted = nodeName;
|
||||
|
||||
const flattenedExecutionData = ResponseHelper.flattenExecutionData(fullExecutionData);
|
||||
|
||||
await Db.collections.Execution!.update(this.executionId, flattenedExecutionData as IExecutionFlattedDb);
|
||||
} catch (err) {
|
||||
// TODO: Improve in the future!
|
||||
// Errors here might happen because of database access
|
||||
// For busy machines, we may get "Database is locked" errors.
|
||||
|
||||
// We do this to prevent crashes and executions ending in `unknown` state.
|
||||
console.log(`Failed saving execution progress to database for execution ID ${this.executionId}`, err);
|
||||
}
|
||||
|
||||
|
||||
if (fullExecutionData.data === undefined) {
|
||||
fullExecutionData.data = {
|
||||
startData: {
|
||||
},
|
||||
resultData: {
|
||||
runData: {},
|
||||
},
|
||||
executionData: {
|
||||
contextData: {},
|
||||
nodeExecutionStack: [],
|
||||
waitingExecution: {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (Array.isArray(fullExecutionData.data.resultData.runData[nodeName])) {
|
||||
// Append data if array exists
|
||||
fullExecutionData.data.resultData.runData[nodeName].push(data);
|
||||
} else {
|
||||
// Initialize array and save data
|
||||
fullExecutionData.data.resultData.runData[nodeName] = [data];
|
||||
}
|
||||
|
||||
fullExecutionData.data.executionData = executionData.executionData;
|
||||
|
||||
// Set last executed node so that it may resume on failure
|
||||
fullExecutionData.data.resultData.lastNodeExecuted = nodeName;
|
||||
|
||||
const flattenedExecutionData = ResponseHelper.flattenExecutionData(fullExecutionData);
|
||||
|
||||
await Db.collections.Execution!.update(this.executionId, flattenedExecutionData as IExecutionFlattedDb);
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -343,7 +353,7 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
|||
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, undefined, this.retryOf);
|
||||
}
|
||||
// Data is always saved, so we remove from database
|
||||
Db.collections.Execution!.delete(this.executionId);
|
||||
await Db.collections.Execution!.delete(this.executionId);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -389,6 +399,77 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns hook functions to save workflow execution and call error workflow
|
||||
* for running with queues. Manual executions should never run on queues as
|
||||
* they are always executed in the main process.
|
||||
*
|
||||
* @returns {IWorkflowExecuteHooks}
|
||||
*/
|
||||
function hookFunctionsSaveWorker(): IWorkflowExecuteHooks {
|
||||
return {
|
||||
nodeExecuteBefore: [],
|
||||
nodeExecuteAfter: [],
|
||||
workflowExecuteBefore: [],
|
||||
workflowExecuteAfter: [
|
||||
async function (this: WorkflowHooks, fullRunData: IRun, newStaticData: IDataObject): Promise<void> {
|
||||
try {
|
||||
if (WorkflowHelpers.isWorkflowIdValid(this.workflowData.id as string) === true && newStaticData) {
|
||||
// Workflow is saved so update in database
|
||||
try {
|
||||
await WorkflowHelpers.saveStaticDataById(this.workflowData.id as string, newStaticData);
|
||||
} catch (e) {
|
||||
// TODO: Add proper logging!
|
||||
console.error(`There was a problem saving the workflow with id "${this.workflowData.id}" to save changed staticData: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check config to know if execution should be saved or not
|
||||
let saveDataErrorExecution = config.get('executions.saveDataOnError') as string;
|
||||
if (this.workflowData.settings !== undefined) {
|
||||
saveDataErrorExecution = (this.workflowData.settings.saveDataErrorExecution as string) || saveDataErrorExecution;
|
||||
}
|
||||
|
||||
const workflowDidSucceed = !fullRunData.data.resultData.error;
|
||||
if (workflowDidSucceed === false && saveDataErrorExecution === 'none') {
|
||||
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, undefined, this.retryOf);
|
||||
}
|
||||
|
||||
const fullExecutionData: IExecutionDb = {
|
||||
data: fullRunData.data,
|
||||
mode: fullRunData.mode,
|
||||
finished: fullRunData.finished ? fullRunData.finished : false,
|
||||
startedAt: fullRunData.startedAt,
|
||||
stoppedAt: fullRunData.stoppedAt,
|
||||
workflowData: this.workflowData,
|
||||
};
|
||||
|
||||
if (this.retryOf !== undefined) {
|
||||
fullExecutionData.retryOf = this.retryOf.toString();
|
||||
}
|
||||
|
||||
if (this.workflowData.id !== undefined && WorkflowHelpers.isWorkflowIdValid(this.workflowData.id.toString()) === true) {
|
||||
fullExecutionData.workflowId = this.workflowData.id.toString();
|
||||
}
|
||||
|
||||
const executionData = ResponseHelper.flattenExecutionData(fullExecutionData);
|
||||
|
||||
// Save the Execution in DB
|
||||
await Db.collections.Execution!.update(this.executionId, executionData as IExecutionFlattedDb);
|
||||
|
||||
if (fullRunData.finished === true && this.retryOf !== undefined) {
|
||||
// If the retry was successful save the reference it on the original execution
|
||||
// await Db.collections.Execution!.save(executionData as IExecutionFlattedDb);
|
||||
await Db.collections.Execution!.update(this.retryOf, { retrySuccessId: this.executionId });
|
||||
}
|
||||
} catch (error) {
|
||||
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, undefined, this.retryOf);
|
||||
}
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export async function getRunData(workflowData: IWorkflowBase, inputData?: INodeExecutionData[]): Promise<IWorkflowExecutionDataProcess> {
|
||||
const mode = 'integrated';
|
||||
|
||||
|
@ -544,6 +625,7 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
|||
return returnData!.data!.main;
|
||||
}
|
||||
} else {
|
||||
await ActiveExecutions.getInstance().remove(executionId, data);
|
||||
// Workflow did fail
|
||||
const error = new Error(data.data.resultData.error!.message);
|
||||
error.stack = data.data.resultData.error!.stack;
|
||||
|
@ -603,6 +685,22 @@ export function getWorkflowHooksIntegrated(mode: WorkflowExecuteMode, executionI
|
|||
return new WorkflowHooks(hookFunctions, mode, executionId, workflowData, optionalParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns WorkflowHooks instance for running integrated workflows
|
||||
* (Workflows which get started inside of another workflow)
|
||||
*/
|
||||
export function getWorkflowHooksWorkerExecuter(mode: WorkflowExecuteMode, executionId: string, workflowData: IWorkflowBase, optionalParameters?: IWorkflowHooksOptionalParameters): WorkflowHooks {
|
||||
optionalParameters = optionalParameters || {};
|
||||
const hookFunctions = hookFunctionsSaveWorker();
|
||||
const preExecuteFunctions = hookFunctionsPreExecute(optionalParameters.parentProcessMode);
|
||||
for (const key of Object.keys(preExecuteFunctions)) {
|
||||
if (hookFunctions[key] === undefined) {
|
||||
hookFunctions[key] = [];
|
||||
}
|
||||
hookFunctions[key]!.push.apply(hookFunctions[key], preExecuteFunctions[key]);
|
||||
}
|
||||
return new WorkflowHooks(hookFunctions, mode, executionId, workflowData, optionalParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns WorkflowHooks instance for main process if workflow runs via worker
|
||||
|
|
|
@ -346,7 +346,27 @@ export class WorkflowRunner {
|
|||
// Normally also static data should be supplied here but as it only used for sending
|
||||
// data to editor-UI is not needed.
|
||||
hooks.executeHookFunctions('workflowExecuteAfter', [runData]);
|
||||
try {
|
||||
// Check if this execution data has to be removed from database
|
||||
// based on workflow settings.
|
||||
let saveDataErrorExecution = config.get('executions.saveDataOnError') as string;
|
||||
let saveDataSuccessExecution = config.get('executions.saveDataOnSuccess') as string;
|
||||
if (data.workflowData.settings !== undefined) {
|
||||
saveDataErrorExecution = (data.workflowData.settings.saveDataErrorExecution as string) || saveDataErrorExecution;
|
||||
saveDataSuccessExecution = (data.workflowData.settings.saveDataSuccessExecution as string) || saveDataSuccessExecution;
|
||||
}
|
||||
|
||||
const workflowDidSucceed = !runData.data.resultData.error;
|
||||
if (workflowDidSucceed === true && saveDataSuccessExecution === 'none' ||
|
||||
workflowDidSucceed === false && saveDataErrorExecution === 'none'
|
||||
) {
|
||||
await Db.collections.Execution!.delete(executionId);
|
||||
}
|
||||
} catch (err) {
|
||||
// We don't want errors here to crash n8n. Just log and proceed.
|
||||
console.log('Error removing saved execution from database. More details: ', err);
|
||||
}
|
||||
|
||||
resolve(runData);
|
||||
});
|
||||
|
||||
|
|
|
@ -41,8 +41,18 @@ export class WorkflowRunnerProcess {
|
|||
workflowExecute: WorkflowExecute | undefined;
|
||||
executionIdCallback: (executionId: string) => void | undefined;
|
||||
|
||||
static async stopProcess() {
|
||||
setTimeout(() => {
|
||||
// Attempt a graceful shutdown, giving executions 30 seconds to finish
|
||||
process.exit(0);
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
|
||||
async runWorkflow(inputData: IWorkflowExecutionDataProcessWithExecution): Promise<IRun> {
|
||||
process.on('SIGTERM', WorkflowRunnerProcess.stopProcess);
|
||||
process.on('SIGINT', WorkflowRunnerProcess.stopProcess);
|
||||
|
||||
this.data = inputData;
|
||||
let className: string;
|
||||
let tempNode: INodeType;
|
||||
|
@ -111,7 +121,15 @@ export class WorkflowRunnerProcess {
|
|||
resolve(executionId);
|
||||
};
|
||||
});
|
||||
const result: IRun = await executeWorkflowFunction(workflowInfo, additionalData, inputData, executionId, workflowData, runData);
|
||||
let result: IRun;
|
||||
try {
|
||||
result = await executeWorkflowFunction(workflowInfo, additionalData, inputData, executionId, workflowData, runData);
|
||||
} catch (e) {
|
||||
await sendToParentProcess('finishExecution', { executionId });
|
||||
// Throw same error we had
|
||||
throw e;
|
||||
}
|
||||
|
||||
await sendToParentProcess('finishExecution', { executionId, result });
|
||||
|
||||
const returnData = WorkflowHelpers.getDataLastExecutedNodeData(result);
|
||||
|
@ -174,8 +192,8 @@ export class WorkflowRunnerProcess {
|
|||
},
|
||||
],
|
||||
nodeExecuteAfter: [
|
||||
async (nodeName: string, data: ITaskData, executionData: IRunExecutionData): Promise<void> => {
|
||||
this.sendHookToParentProcess('nodeExecuteAfter', [nodeName, data, executionData]);
|
||||
async (nodeName: string, data: ITaskData): Promise<void> => {
|
||||
this.sendHookToParentProcess('nodeExecuteAfter', [nodeName, data]);
|
||||
},
|
||||
],
|
||||
workflowExecuteBefore: [
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
import * as config from '../../../../config';
|
||||
|
||||
export class ChangeDataSize1615306975123 implements MigrationInterface {
|
||||
name = 'ChangeDataSize1615306975123';
|
||||
|
||||
async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const tablePrefix = config.get('database.tablePrefix');
|
||||
|
||||
await queryRunner.query('ALTER TABLE `' + tablePrefix + 'execution_entity` MODIFY COLUMN `data` MEDIUMTEXT NOT NULL');
|
||||
}
|
||||
|
||||
async down(queryRunner: QueryRunner): Promise<void> {
|
||||
const tablePrefix = config.get('database.tablePrefix');
|
||||
|
||||
await queryRunner.query('ALTER TABLE `' + tablePrefix + 'execution_entity` MODIFY COLUMN `data` TEXT NOT NULL');
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ import { WebhookModel1592447867632 } from './1592447867632-WebhookModel';
|
|||
import { CreateIndexStoppedAt1594902918301 } from './1594902918301-CreateIndexStoppedAt';
|
||||
import { AddWebhookId1611149998770 } from './1611149998770-AddWebhookId';
|
||||
import { MakeStoppedAtNullable1607431743767 } from './1607431743767-MakeStoppedAtNullable';
|
||||
import { ChangeDataSize1615306975123 } from './1615306975123-ChangeDataSize';
|
||||
import { CreateTagEntity1617268711084 } from './1617268711084-CreateTagEntity';
|
||||
|
||||
export const mysqlMigrations = [
|
||||
|
@ -11,5 +12,6 @@ export const mysqlMigrations = [
|
|||
CreateIndexStoppedAt1594902918301,
|
||||
AddWebhookId1611149998770,
|
||||
MakeStoppedAtNullable1607431743767,
|
||||
ChangeDataSize1615306975123,
|
||||
CreateTagEntity1617268711084,
|
||||
];
|
||||
|
|
|
@ -1,74 +1,74 @@
|
|||
{
|
||||
"name": "n8n-core",
|
||||
"version": "0.64.0",
|
||||
"description": "Core functionality of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
"author": {
|
||||
"name": "Jan Oberhauser",
|
||||
"email": "jan@n8n.io"
|
||||
"name": "n8n-core",
|
||||
"version": "0.67.0",
|
||||
"description": "Core functionality of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
"author": {
|
||||
"name": "Jan Oberhauser",
|
||||
"email": "jan@n8n.io"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/n8n-io/n8n.git"
|
||||
},
|
||||
"main": "dist/src/index",
|
||||
"types": "dist/src/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "npm run watch",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"watch": "tsc --watch",
|
||||
"test": "jest"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/cron": "^1.7.1",
|
||||
"@types/crypto-js": "^4.0.1",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^26.0.13",
|
||||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/node": "14.0.27",
|
||||
"@types/request-promise-native": "~1.0.15",
|
||||
"jest": "^26.4.2",
|
||||
"source-map-support": "^0.5.9",
|
||||
"ts-jest": "^26.3.0",
|
||||
"tslint": "^6.1.2",
|
||||
"typescript": "~3.9.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"client-oauth2": "^4.2.5",
|
||||
"cron": "^1.7.2",
|
||||
"crypto-js": "4.0.0",
|
||||
"file-type": "^14.6.2",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mime-types": "^2.1.27",
|
||||
"n8n-workflow": "~0.55.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"request": "^2.88.2",
|
||||
"request-promise-native": "^1.0.7"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.tsx?$": "ts-jest"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/n8n-io/n8n.git"
|
||||
},
|
||||
"main": "dist/src/index",
|
||||
"types": "dist/src/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "npm run watch",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"watch": "tsc --watch",
|
||||
"test": "jest"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"testURL": "http://localhost/",
|
||||
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||
"testPathIgnorePatterns": [
|
||||
"/dist/",
|
||||
"/node_modules/"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/cron": "^1.7.1",
|
||||
"@types/crypto-js": "^4.0.1",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^26.0.13",
|
||||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/node": "14.0.27",
|
||||
"@types/request-promise-native": "~1.0.15",
|
||||
"jest": "^26.4.2",
|
||||
"source-map-support": "^0.5.9",
|
||||
"ts-jest": "^26.3.0",
|
||||
"tslint": "^6.1.2",
|
||||
"typescript": "~3.9.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"client-oauth2": "^4.2.5",
|
||||
"cron": "^1.7.2",
|
||||
"crypto-js": "4.0.0",
|
||||
"file-type": "^14.6.2",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mime-types": "^2.1.27",
|
||||
"n8n-workflow": "~0.53.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"request": "^2.88.2",
|
||||
"request-promise-native": "^1.0.7"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.tsx?$": "ts-jest"
|
||||
},
|
||||
"testURL": "http://localhost/",
|
||||
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||
"testPathIgnorePatterns": [
|
||||
"/dist/",
|
||||
"/node_modules/"
|
||||
],
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"json",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"json",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
IWebhookData,
|
||||
WebhookHttpMethod,
|
||||
Workflow,
|
||||
WorkflowActivateMode,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -30,7 +31,7 @@ export class ActiveWebhooks {
|
|||
* @returns {Promise<void>}
|
||||
* @memberof ActiveWebhooks
|
||||
*/
|
||||
async add(workflow: Workflow, webhookData: IWebhookData, mode: WorkflowExecuteMode): Promise<void> {
|
||||
async add(workflow: Workflow, webhookData: IWebhookData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): Promise<void> {
|
||||
if (workflow.id === undefined) {
|
||||
throw new Error('Webhooks can only be added for saved workflows as an id is needed!');
|
||||
}
|
||||
|
@ -57,10 +58,10 @@ export class ActiveWebhooks {
|
|||
this.webhookUrls[webhookKey].push(webhookData);
|
||||
|
||||
try {
|
||||
const webhookExists = await workflow.runWebhookMethod('checkExists', webhookData, NodeExecuteFunctions, mode, this.testWebhooks);
|
||||
const webhookExists = await workflow.runWebhookMethod('checkExists', webhookData, NodeExecuteFunctions, mode, activation, this.testWebhooks);
|
||||
if (webhookExists !== true) {
|
||||
// If webhook does not exist yet create it
|
||||
await workflow.runWebhookMethod('create', webhookData, NodeExecuteFunctions, mode, this.testWebhooks);
|
||||
await workflow.runWebhookMethod('create', webhookData, NodeExecuteFunctions, mode, activation, this.testWebhooks);
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -183,7 +184,7 @@ export class ActiveWebhooks {
|
|||
|
||||
// Go through all the registered webhooks of the workflow and remove them
|
||||
for (const webhookData of webhooks) {
|
||||
await workflow.runWebhookMethod('delete', webhookData, NodeExecuteFunctions, mode, this.testWebhooks);
|
||||
await workflow.runWebhookMethod('delete', webhookData, NodeExecuteFunctions, mode, 'update', this.testWebhooks);
|
||||
|
||||
delete this.webhookUrls[this.getWebhookKey(webhookData.httpMethod, webhookData.path, webhookData.webhookId)];
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import {
|
|||
ITriggerResponse,
|
||||
IWorkflowExecuteAdditionalData,
|
||||
Workflow,
|
||||
WorkflowActivateMode,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -66,14 +68,14 @@ export class ActiveWorkflows {
|
|||
* @returns {Promise<void>}
|
||||
* @memberof ActiveWorkflows
|
||||
*/
|
||||
async add(id: string, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, getTriggerFunctions: IGetExecuteTriggerFunctions, getPollFunctions: IGetExecutePollFunctions): Promise<void> {
|
||||
async add(id: string, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode, getTriggerFunctions: IGetExecuteTriggerFunctions, getPollFunctions: IGetExecutePollFunctions): Promise<void> {
|
||||
this.workflowData[id] = {};
|
||||
const triggerNodes = workflow.getTriggerNodes();
|
||||
|
||||
let triggerResponse: ITriggerResponse | undefined;
|
||||
this.workflowData[id].triggerResponses = [];
|
||||
for (const triggerNode of triggerNodes) {
|
||||
triggerResponse = await workflow.runTrigger(triggerNode, getTriggerFunctions, additionalData, 'trigger');
|
||||
triggerResponse = await workflow.runTrigger(triggerNode, getTriggerFunctions, additionalData, mode, activation);
|
||||
if (triggerResponse !== undefined) {
|
||||
// If a response was given save it
|
||||
this.workflowData[id].triggerResponses!.push(triggerResponse);
|
||||
|
@ -84,7 +86,7 @@ export class ActiveWorkflows {
|
|||
if (pollNodes.length) {
|
||||
this.workflowData[id].pollResponses = [];
|
||||
for (const pollNode of pollNodes) {
|
||||
this.workflowData[id].pollResponses!.push(await this.activatePolling(pollNode, workflow, additionalData, getPollFunctions));
|
||||
this.workflowData[id].pollResponses!.push(await this.activatePolling(pollNode, workflow, additionalData, getPollFunctions, mode, activation));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,10 +102,8 @@ export class ActiveWorkflows {
|
|||
* @returns {Promise<IPollResponse>}
|
||||
* @memberof ActiveWorkflows
|
||||
*/
|
||||
async activatePolling(node: INode, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, getPollFunctions: IGetExecutePollFunctions): Promise<IPollResponse> {
|
||||
const mode = 'trigger';
|
||||
|
||||
const pollFunctions = getPollFunctions(workflow, node, additionalData, mode);
|
||||
async activatePolling(node: INode, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, getPollFunctions: IGetExecutePollFunctions, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): Promise<IPollResponse> {
|
||||
const pollFunctions = getPollFunctions(workflow, node, additionalData, mode, activation);
|
||||
|
||||
const pollTimes = pollFunctions.getNodeParameter('pollTimes') as unknown as {
|
||||
item: ITriggerTime[];
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
NodeHelpers,
|
||||
NodeParameterValue,
|
||||
Workflow,
|
||||
WorkflowActivateMode,
|
||||
WorkflowDataProxy,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
@ -103,6 +104,9 @@ export async function prepareBinaryData(binaryData: Buffer, filePath?: string, m
|
|||
|
||||
const filePathParts = path.parse(filePath as string);
|
||||
|
||||
if (filePathParts.dir !== '') {
|
||||
returnData.directory = filePathParts.dir;
|
||||
}
|
||||
returnData.fileName = filePathParts.base;
|
||||
|
||||
// Remove the dot
|
||||
|
@ -532,7 +536,7 @@ export function getWorkflowMetadata(workflow: Workflow): IWorkflowMetadata {
|
|||
* @returns {ITriggerFunctions}
|
||||
*/
|
||||
// TODO: Check if I can get rid of: additionalData, and so then maybe also at ActiveWorkflowRunner.add
|
||||
export function getExecutePollFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): IPollFunctions {
|
||||
export function getExecutePollFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): IPollFunctions {
|
||||
return ((workflow: Workflow, node: INode) => {
|
||||
return {
|
||||
__emit: (data: INodeExecutionData[][]): void => {
|
||||
|
@ -544,6 +548,9 @@ export function getExecutePollFunctions(workflow: Workflow, node: INode, additio
|
|||
getMode: (): WorkflowExecuteMode => {
|
||||
return mode;
|
||||
},
|
||||
getActivationMode: (): WorkflowActivateMode => {
|
||||
return activation;
|
||||
},
|
||||
getNode: () => {
|
||||
return getNode(node);
|
||||
},
|
||||
|
@ -595,7 +602,7 @@ export function getExecutePollFunctions(workflow: Workflow, node: INode, additio
|
|||
* @returns {ITriggerFunctions}
|
||||
*/
|
||||
// TODO: Check if I can get rid of: additionalData, and so then maybe also at ActiveWorkflowRunner.add
|
||||
export function getExecuteTriggerFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode): ITriggerFunctions {
|
||||
export function getExecuteTriggerFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): ITriggerFunctions {
|
||||
return ((workflow: Workflow, node: INode) => {
|
||||
return {
|
||||
emit: (data: INodeExecutionData[][]): void => {
|
||||
|
@ -610,6 +617,9 @@ export function getExecuteTriggerFunctions(workflow: Workflow, node: INode, addi
|
|||
getMode: (): WorkflowExecuteMode => {
|
||||
return mode;
|
||||
},
|
||||
getActivationMode: (): WorkflowActivateMode => {
|
||||
return activation;
|
||||
},
|
||||
getNodeParameter: (parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object => { //tslint:disable-line:no-any
|
||||
const runExecutionData: IRunExecutionData | null = null;
|
||||
const itemIndex = 0;
|
||||
|
@ -907,7 +917,7 @@ export function getLoadOptionsFunctions(workflow: Workflow, node: INode, additio
|
|||
* @param {WorkflowExecuteMode} mode
|
||||
* @returns {IHookFunctions}
|
||||
*/
|
||||
export function getExecuteHookFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, isTest?: boolean, webhookData?: IWebhookData): IHookFunctions {
|
||||
export function getExecuteHookFunctions(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData, mode: WorkflowExecuteMode, activation: WorkflowActivateMode, isTest?: boolean, webhookData?: IWebhookData): IHookFunctions {
|
||||
return ((workflow: Workflow, node: INode) => {
|
||||
const that = {
|
||||
getCredentials(type: string): ICredentialDataDecryptedObject | undefined {
|
||||
|
@ -916,6 +926,9 @@ export function getExecuteHookFunctions(workflow: Workflow, node: INode, additio
|
|||
getMode: (): WorkflowExecuteMode => {
|
||||
return mode;
|
||||
},
|
||||
getActivationMode: (): WorkflowActivateMode => {
|
||||
return activation;
|
||||
},
|
||||
getNode: () => {
|
||||
return getNode(node);
|
||||
},
|
||||
|
|
|
@ -557,7 +557,7 @@ export class WorkflowExecute {
|
|||
executionData = this.runExecutionData.executionData!.nodeExecutionStack.shift() as IExecuteData;
|
||||
executionNode = executionData.node;
|
||||
|
||||
this.executeHook('nodeExecuteBefore', [executionNode.name]);
|
||||
await this.executeHook('nodeExecuteBefore', [executionNode.name]);
|
||||
|
||||
// Get the index of the current run
|
||||
runIndex = 0;
|
||||
|
@ -722,7 +722,7 @@ export class WorkflowExecute {
|
|||
// Add the execution data again so that it can get restarted
|
||||
this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionData);
|
||||
|
||||
this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
||||
await this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -6,3 +6,10 @@ indent_style = tab
|
|||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[package.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-editor-ui",
|
||||
"version": "0.80.0",
|
||||
"version": "0.84.0",
|
||||
"description": "Workflow Editor UI for n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -65,7 +65,7 @@
|
|||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"n8n-workflow": "~0.53.0",
|
||||
"n8n-workflow": "~0.55.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"normalize-wheel": "^1.0.1",
|
||||
"prismjs": "^1.17.1",
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
{{parameter.displayName}}:
|
||||
</div>
|
||||
<div class="text-editor" @keydown.stop>
|
||||
<prism-editor :lineNumbers="true" :code="value" @change="valueChanged" language="js"></prism-editor>
|
||||
<prism-editor :lineNumbers="true" :code="value" :readonly="isReadOnly" @change="valueChanged" language="js"></prism-editor>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
@ -14,42 +14,43 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
// @ts-ignore
|
||||
import PrismEditor from 'vue-prism-editor';
|
||||
|
||||
import {
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'CodeEdit',
|
||||
props: [
|
||||
'dialogVisible',
|
||||
'parameter',
|
||||
'value',
|
||||
],
|
||||
components: {
|
||||
PrismEditor,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
valueChanged (value: string) {
|
||||
this.$emit('valueChanged', value);
|
||||
},
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
closeDialog () {
|
||||
// Handle the close externally as the visible parameter is an external prop
|
||||
// and is so not allowed to be changed here.
|
||||
this.$emit('closeDialog');
|
||||
return false;
|
||||
export default mixins(
|
||||
genericHelpers,
|
||||
)
|
||||
.extend({
|
||||
name: 'CodeEdit',
|
||||
props: [
|
||||
'dialogVisible',
|
||||
'parameter',
|
||||
'value',
|
||||
],
|
||||
components: {
|
||||
PrismEditor,
|
||||
},
|
||||
},
|
||||
});
|
||||
data () {
|
||||
return {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
valueChanged (value: string) {
|
||||
this.$emit('valueChanged', value);
|
||||
},
|
||||
|
||||
closeDialog () {
|
||||
// Handle the close externally as the visible parameter is an external prop
|
||||
// and is so not allowed to be changed here.
|
||||
this.$emit('closeDialog');
|
||||
return false;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -175,6 +175,10 @@ import {
|
|||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
range as _range,
|
||||
} from 'lodash';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
export default mixins(
|
||||
|
@ -433,8 +437,24 @@ export default mixins(
|
|||
this.$store.commit('setActiveExecutions', results[1]);
|
||||
|
||||
const alreadyPresentExecutionIds = this.finishedExecutions.map(exec => exec.id);
|
||||
let lastId = 0;
|
||||
const gaps = [] as number[];
|
||||
for(let i = results[0].results.length - 1; i >= 0; i--) {
|
||||
const currentItem = results[0].results[i];
|
||||
const currentId = parseInt(currentItem.id, 10);
|
||||
if (lastId !== 0 && isNaN(currentId) === false) {
|
||||
// We are doing this iteration to detect possible gaps.
|
||||
// The gaps are used to remove executions that finished
|
||||
// and were deleted from database but were displaying
|
||||
// in this list while running.
|
||||
if (currentId - lastId > 1) {
|
||||
// We have some gaps.
|
||||
const range = _range(lastId + 1, currentId);
|
||||
gaps.push(...range);
|
||||
}
|
||||
}
|
||||
lastId = parseInt(currentItem.id, 10) || 0;
|
||||
|
||||
// Check new results from end to start
|
||||
// Add new items accordingly.
|
||||
const executionIndex = alreadyPresentExecutionIds.indexOf(currentItem.id);
|
||||
|
@ -464,6 +484,7 @@ export default mixins(
|
|||
this.finishedExecutions.unshift(currentItem);
|
||||
}
|
||||
}
|
||||
this.finishedExecutions = this.finishedExecutions.filter(execution => !gaps.includes(parseInt(execution.id, 10)) && lastId >= parseInt(execution.id, 10));
|
||||
this.finishedExecutionsCount = results[0].count;
|
||||
},
|
||||
async loadFinishedExecutions (): Promise<void> {
|
||||
|
|
|
@ -12,23 +12,22 @@ import 'quill/dist/quill.core.css';
|
|||
|
||||
import Quill, { DeltaOperation } from 'quill';
|
||||
// @ts-ignore
|
||||
import AutoFormat, { AutoformatHelperAttribute } from 'quill-autoformat';
|
||||
import AutoFormat from 'quill-autoformat';
|
||||
import {
|
||||
NodeParameterValue,
|
||||
Workflow,
|
||||
WorkflowDataProxy,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
IExecutionResponse,
|
||||
IVariableItemSelected,
|
||||
IVariableSelectorOption,
|
||||
} from '@/Interface';
|
||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
export default mixins(
|
||||
genericHelpers,
|
||||
workflowHelpers,
|
||||
)
|
||||
.extend({
|
||||
|
@ -119,7 +118,7 @@ export default mixins(
|
|||
};
|
||||
|
||||
this.editor = new Quill(this.$refs['expression-editor'] as Element, {
|
||||
readOnly: !!this.resolvedValue,
|
||||
readOnly: !!this.resolvedValue || this.isReadOnly,
|
||||
modules: {
|
||||
autoformat: {},
|
||||
keyboard: {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<prism-editor v-if="!codeEditDialogVisible" :lineNumbers="true" :readonly="true" :code="displayValue" language="js"></prism-editor>
|
||||
</div>
|
||||
|
||||
<el-input v-else v-model="tempValue" ref="inputField" size="small" :type="getStringInputType" :rows="getArgument('rows')" :value="displayValue" :disabled="isReadOnly" @change="valueChanged" @keydown.stop @focus="setFocus" :title="displayTitle" :placeholder="isValueExpression?'':parameter.placeholder">
|
||||
<el-input v-else v-model="tempValue" ref="inputField" size="small" :type="getStringInputType" :rows="getArgument('rows')" :value="displayValue" :disabled="!isValueExpression && isReadOnly" @change="valueChanged" @keydown.stop @focus="setFocus" :title="displayTitle" :placeholder="isValueExpression?'':parameter.placeholder">
|
||||
<font-awesome-icon v-if="!isValueExpression && !isReadOnly" slot="suffix" icon="external-link-alt" class="edit-window-button clickable" title="Open Edit Window" @click="displayEditDialog()" />
|
||||
</el-input>
|
||||
</div>
|
||||
|
@ -523,9 +523,6 @@ export default mixins(
|
|||
this.valueChanged(value);
|
||||
},
|
||||
setFocus () {
|
||||
if (this.isReadOnly === true) {
|
||||
return;
|
||||
}
|
||||
if (this.isValueExpression) {
|
||||
this.expressionEditDialogVisible = true;
|
||||
return;
|
||||
|
|
|
@ -157,6 +157,10 @@
|
|||
<div class="label">File Name: </div>
|
||||
<div class="value">{{binaryData.fileName}}</div>
|
||||
</div>
|
||||
<div v-if="binaryData.directory">
|
||||
<div class="label">Directory: </div>
|
||||
<div class="value">{{binaryData.directory}}</div>
|
||||
</div>
|
||||
<div v-if="binaryData.fileExtension">
|
||||
<div class="label">File Extension:</div>
|
||||
<div class="value">{{binaryData.fileExtension}}</div>
|
||||
|
|
|
@ -114,6 +114,9 @@ export default mixins(
|
|||
// Has still options left so return
|
||||
inputData.options = this.sortOptions(newOptions);
|
||||
return inputData;
|
||||
} else if (Array.isArray(newOptions) && newOptions.length === 0) {
|
||||
delete inputData.options;
|
||||
return inputData;
|
||||
}
|
||||
// Has no options left so remove
|
||||
return null;
|
||||
|
|
|
@ -1,70 +1,70 @@
|
|||
{
|
||||
"name": "n8n-node-dev",
|
||||
"version": "0.11.0",
|
||||
"description": "CLI to simplify n8n credentials/node development",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
"author": {
|
||||
"name": "Jan Oberhauser",
|
||||
"email": "jan@n8n.io"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/n8n-io/n8n.git"
|
||||
},
|
||||
"main": "dist/src/index",
|
||||
"types": "dist/src/index.d.ts",
|
||||
"oclif": {
|
||||
"commands": "./dist/commands",
|
||||
"bin": "n8n-node-dev"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "npm run watch",
|
||||
"build": "tsc",
|
||||
"postpack": "rm -f oclif.manifest.json",
|
||||
"prepack": "echo \"Building project...\" && rm -rf dist && tsc -b && oclif-dev manifest",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"watch": "tsc --watch"
|
||||
},
|
||||
"bin": {
|
||||
"n8n-node-dev": "./bin/n8n-node-dev"
|
||||
},
|
||||
"keywords": [
|
||||
"development",
|
||||
"node",
|
||||
"helper",
|
||||
"n8n"
|
||||
],
|
||||
"files": [
|
||||
"bin",
|
||||
"dist",
|
||||
"templates",
|
||||
"oclif.manifest.json",
|
||||
"src/tsconfig-build.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@oclif/dev-cli": "^1.22.2",
|
||||
"@types/copyfiles": "^2.1.1",
|
||||
"@types/inquirer": "^6.5.0",
|
||||
"@types/tmp": "^0.1.0",
|
||||
"@types/vorpal": "^1.11.0",
|
||||
"tslint": "^6.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oclif/command": "^1.5.18",
|
||||
"@oclif/errors": "^1.2.2",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/node": "14.0.27",
|
||||
"change-case": "^4.1.1",
|
||||
"copyfiles": "^2.1.1",
|
||||
"inquirer": "^7.0.1",
|
||||
"n8n-core": "^0.48.0",
|
||||
"n8n-workflow": "^0.42.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"replace-in-file": "^6.0.0",
|
||||
"request": "^2.88.2",
|
||||
"tmp-promise": "^2.0.2",
|
||||
"typescript": "~3.9.7"
|
||||
}
|
||||
"name": "n8n-node-dev",
|
||||
"version": "0.11.0",
|
||||
"description": "CLI to simplify n8n credentials/node development",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
"author": {
|
||||
"name": "Jan Oberhauser",
|
||||
"email": "jan@n8n.io"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/n8n-io/n8n.git"
|
||||
},
|
||||
"main": "dist/src/index",
|
||||
"types": "dist/src/index.d.ts",
|
||||
"oclif": {
|
||||
"commands": "./dist/commands",
|
||||
"bin": "n8n-node-dev"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "npm run watch",
|
||||
"build": "tsc",
|
||||
"postpack": "rm -f oclif.manifest.json",
|
||||
"prepack": "echo \"Building project...\" && rm -rf dist && tsc -b && oclif-dev manifest",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||
"watch": "tsc --watch"
|
||||
},
|
||||
"bin": {
|
||||
"n8n-node-dev": "./bin/n8n-node-dev"
|
||||
},
|
||||
"keywords": [
|
||||
"development",
|
||||
"node",
|
||||
"helper",
|
||||
"n8n"
|
||||
],
|
||||
"files": [
|
||||
"bin",
|
||||
"dist",
|
||||
"templates",
|
||||
"oclif.manifest.json",
|
||||
"src/tsconfig-build.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@oclif/dev-cli": "^1.22.2",
|
||||
"@types/copyfiles": "^2.1.1",
|
||||
"@types/inquirer": "^6.5.0",
|
||||
"@types/tmp": "^0.1.0",
|
||||
"@types/vorpal": "^1.11.0",
|
||||
"tslint": "^6.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oclif/command": "^1.5.18",
|
||||
"@oclif/errors": "^1.2.2",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/node": "14.0.27",
|
||||
"change-case": "^4.1.1",
|
||||
"copyfiles": "^2.1.1",
|
||||
"inquirer": "^7.0.1",
|
||||
"n8n-core": "^0.48.0",
|
||||
"n8n-workflow": "^0.42.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"replace-in-file": "^6.0.0",
|
||||
"request": "^2.88.2",
|
||||
"tmp-promise": "^2.0.2",
|
||||
"typescript": "~3.9.7"
|
||||
}
|
||||
}
|
||||
|
|
18
packages/nodes-base/credentials/AutopilotApi.credentials.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class AutopilotApi implements ICredentialType {
|
||||
name = 'autopilotApi';
|
||||
displayName = 'Autopilot API';
|
||||
documentationUrl = 'autopilot';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -96,6 +96,21 @@ export class Aws implements ICredentialType {
|
|||
default: '',
|
||||
placeholder: 'https://email.{region}.amazonaws.com',
|
||||
},
|
||||
{
|
||||
displayName: 'SQS Endpoint',
|
||||
name: 'sqsEndpoint',
|
||||
description: 'If you use Amazon VPC to host n8n, you can establish a connection between your VPC and SQS using a VPC endpoint. Leave blank to use the default endpoint.',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
displayOptions: {
|
||||
show: {
|
||||
customEndpoints: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
placeholder: 'https://sqs.{region}.amazonaws.com',
|
||||
},
|
||||
{
|
||||
displayName: 'S3 Endpoint',
|
||||
name: 's3Endpoint',
|
||||
|
|
15
packages/nodes-base/credentials/DeepLApi.credentials.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { ICredentialType, NodePropertyTypes } from 'n8n-workflow';
|
||||
|
||||
export class DeepLApi implements ICredentialType {
|
||||
name = 'deepLApi';
|
||||
displayName = 'DeepL API';
|
||||
documentationUrl = 'deepL';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -14,5 +14,21 @@ export class DropboxApi implements ICredentialType {
|
|||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'APP Access Type',
|
||||
name: 'accessType',
|
||||
type: 'options' as NodePropertyTypes,
|
||||
options: [
|
||||
{
|
||||
name: 'App Folder',
|
||||
value: 'folder',
|
||||
},
|
||||
{
|
||||
name: 'Full Dropbox',
|
||||
value: 'full',
|
||||
},
|
||||
],
|
||||
default: 'full',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ const scopes = [
|
|||
'files.content.write',
|
||||
'files.content.read',
|
||||
'sharing.read',
|
||||
'account_info.read',
|
||||
];
|
||||
|
||||
export class DropboxOAuth2Api implements ICredentialType {
|
||||
|
@ -41,7 +42,7 @@ export class DropboxOAuth2Api implements ICredentialType {
|
|||
displayName: 'Auth URI Query Parameters',
|
||||
name: 'authQueryParameters',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'token_access_type=offline',
|
||||
default: 'token_access_type=offline&force_reapprove=true',
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
|
@ -49,5 +50,21 @@ export class DropboxOAuth2Api implements ICredentialType {
|
|||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'header',
|
||||
},
|
||||
{
|
||||
displayName: 'APP Access Type',
|
||||
name: 'accessType',
|
||||
type: 'options' as NodePropertyTypes,
|
||||
options: [
|
||||
{
|
||||
name: 'App Folder',
|
||||
value: 'folder',
|
||||
},
|
||||
{
|
||||
name: 'Full Dropbox',
|
||||
value: 'full',
|
||||
},
|
||||
],
|
||||
default: 'full',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
32
packages/nodes-base/credentials/ERPNextApi.credentials.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class ERPNextApi implements ICredentialType {
|
||||
name = 'erpNextApi';
|
||||
displayName = 'ERPNext API';
|
||||
documentationUrl = 'erpnext';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'API Secret',
|
||||
name: 'apiSecret',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Subdomain',
|
||||
name: 'subdomain',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
placeholder: 'n8n',
|
||||
description: 'ERPNext subdomain. For instance, entering n8n will make the url look like: https://n8n.erpnext.com/.',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
const scopes = [
|
||||
'https://www.googleapis.com/auth/drive.file',
|
||||
'https://www.googleapis.com/auth/presentations',
|
||||
];
|
||||
|
||||
export class GoogleSlidesOAuth2Api implements ICredentialType {
|
||||
name = 'googleSlidesOAuth2Api';
|
||||
extends = [
|
||||
'googleOAuth2Api',
|
||||
];
|
||||
displayName = 'Google Slides OAuth2 API';
|
||||
documentationUrl = 'google';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: scopes.join(' '),
|
||||
},
|
||||
];
|
||||
}
|
|
@ -28,10 +28,23 @@ export class Kafka implements ICredentialType {
|
|||
type: 'boolean' as NodePropertyTypes,
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'boolean' as NodePropertyTypes,
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Username',
|
||||
name: 'username',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Optional username if authenticated is required.',
|
||||
},
|
||||
|
@ -39,11 +52,46 @@ export class Kafka implements ICredentialType {
|
|||
displayName: 'Password',
|
||||
name: 'password',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
password: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Optional password if authenticated is required.',
|
||||
},
|
||||
{
|
||||
displayName: 'SASL mechanism',
|
||||
name: 'saslMechanism',
|
||||
type: 'options' as NodePropertyTypes,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'plain',
|
||||
value: 'plain',
|
||||
},
|
||||
{
|
||||
name: 'scram-sha-256',
|
||||
value: 'scram-sha-256',
|
||||
},
|
||||
{
|
||||
name: 'scram-sha-512',
|
||||
value: 'scram-sha-512',
|
||||
},
|
||||
],
|
||||
default: 'plain',
|
||||
description: 'The SASL mechanism.',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
18
packages/nodes-base/credentials/OuraApi.credentials.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class OuraApi implements ICredentialType {
|
||||
name = 'ouraApi';
|
||||
displayName = 'Oura API';
|
||||
documentationUrl = 'oura';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Personal Access Token',
|
||||
name: 'accessToken',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
24
packages/nodes-base/credentials/PlivoApi.credentials.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class PlivoApi implements ICredentialType {
|
||||
name = 'plivoApi';
|
||||
displayName = 'Plivo API';
|
||||
documentationUrl = 'plivo';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Auth ID',
|
||||
name: 'authId',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Auth Token',
|
||||
name: 'authToken',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -36,7 +36,7 @@ export class SpotifyOAuth2Api implements ICredentialType {
|
|||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'user-read-playback-state playlist-read-collaborative user-modify-playback-state playlist-modify-public user-read-currently-playing playlist-read-private user-read-recently-played playlist-modify-private',
|
||||
default: 'user-read-playback-state playlist-read-collaborative user-modify-playback-state playlist-modify-public user-read-currently-playing playlist-read-private user-read-recently-played playlist-modify-private user-library-read',
|
||||
},
|
||||
{
|
||||
displayName: 'Auth URI Query Parameters',
|
||||
|
|
|
@ -41,5 +41,11 @@ export class TheHiveApi implements ICredentialType {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Ignore SSL Issues',
|
||||
name: 'allowUnauthorizedCerts',
|
||||
type: 'boolean' as NodePropertyTypes,
|
||||
default: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
34
packages/nodes-base/credentials/WiseApi.credentials.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class WiseApi implements ICredentialType {
|
||||
name = 'wiseApi';
|
||||
displayName = 'Wise API';
|
||||
documentationUrl = 'wise';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'API Token',
|
||||
name: 'apiToken',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Environment',
|
||||
name: 'environment',
|
||||
type: 'options' as NodePropertyTypes,
|
||||
default: 'live',
|
||||
options: [
|
||||
{
|
||||
name: 'Live',
|
||||
value: 'live',
|
||||
},
|
||||
{
|
||||
name: 'Test',
|
||||
value: 'test',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
73
packages/nodes-base/nodes/ActivationTrigger.node.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
import { ITriggerFunctions } from 'n8n-core';
|
||||
import {
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
ITriggerResponse,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class ActivationTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Activation Trigger',
|
||||
name: 'activationTrigger',
|
||||
icon: 'fa:play-circle',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
description: 'Executes whenever the workflow becomes active.',
|
||||
defaults: {
|
||||
name: 'Activation Trigger',
|
||||
color: '#00e000',
|
||||
},
|
||||
inputs: [],
|
||||
outputs: ['main'],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Events',
|
||||
name: 'events',
|
||||
type: 'multiOptions',
|
||||
required: true,
|
||||
default: [],
|
||||
description: 'Specifies under which conditions an execution should happen:<br />' +
|
||||
'- <b>Activation</b>: Workflow gets activated<br />' +
|
||||
'- <b>Update</b>: Workflow gets saved while active<br>' +
|
||||
'- <b>Start</b>: n8n starts or restarts',
|
||||
options: [
|
||||
{
|
||||
name: 'Activation',
|
||||
value: 'activate',
|
||||
description: 'Run when workflow gets activated',
|
||||
},
|
||||
{
|
||||
name: 'Start',
|
||||
value: 'init',
|
||||
description: 'Run when n8n starts or restarts',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Run when workflow gets saved while it is active',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> {
|
||||
const events = this.getNodeParameter('events', []) as string[];
|
||||
|
||||
const activationMode = this.getActivationMode();
|
||||
|
||||
if (events.includes(activationMode)) {
|
||||
this.emit([this.helpers.returnJsonArray([{ activation: activationMode }])]);
|
||||
}
|
||||
|
||||
const self = this;
|
||||
async function manualTriggerFunction() {
|
||||
self.emit([self.helpers.returnJsonArray([{ activation: 'manual' }])]);
|
||||
}
|
||||
|
||||
return {
|
||||
manualTriggerFunction,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -137,7 +137,7 @@ export class Airtable implements INodeType {
|
|||
// delete
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Id',
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
|
@ -317,7 +317,7 @@ export class Airtable implements INodeType {
|
|||
// read
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Id',
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
|
@ -336,7 +336,7 @@ export class Airtable implements INodeType {
|
|||
// update
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Id',
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
|
@ -499,7 +499,7 @@ export class Airtable implements INodeType {
|
|||
for (let i = 0; i < items.length; i++) {
|
||||
id = this.getNodeParameter('id', i) as string;
|
||||
|
||||
endpoint = `${application}/${table}/${id}`;
|
||||
endpoint = `${application}/${table}`;
|
||||
|
||||
// Make one request after another. This is slower but makes
|
||||
// sure that we do not run into the rate limit they have in
|
||||
|
@ -507,9 +507,11 @@ export class Airtable implements INodeType {
|
|||
// functionality in core should make it easy to make requests
|
||||
// according to specific rules like not more than 5 requests
|
||||
// per seconds.
|
||||
qs.records = [id];
|
||||
|
||||
responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||
|
||||
returnData.push(responseData);
|
||||
returnData.push(...responseData.records);
|
||||
}
|
||||
|
||||
} else if (operation === 'list') {
|
||||
|
@ -586,7 +588,6 @@ export class Airtable implements INodeType {
|
|||
let updateAllFields: boolean;
|
||||
let fields: string[];
|
||||
let options: IDataObject;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
updateAllFields = this.getNodeParameter('updateAllFields', i) as boolean;
|
||||
options = this.getNodeParameter('options', i, {}) as IDataObject;
|
||||
|
@ -616,13 +617,9 @@ export class Airtable implements INodeType {
|
|||
}
|
||||
}
|
||||
|
||||
if (options.typecast === true) {
|
||||
body['typecast'] = true;
|
||||
}
|
||||
|
||||
id = this.getNodeParameter('id', i) as string;
|
||||
|
||||
endpoint = `${application}/${table}/${id}`;
|
||||
endpoint = `${application}/${table}`;
|
||||
|
||||
// Make one request after another. This is slower but makes
|
||||
// sure that we do not run into the rate limit they have in
|
||||
|
@ -631,9 +628,11 @@ export class Airtable implements INodeType {
|
|||
// according to specific rules like not more than 5 requests
|
||||
// per seconds.
|
||||
|
||||
responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||
const data = { records: [{ id, fields: body.fields }], typecast: (options.typecast) ? true : false };
|
||||
|
||||
returnData.push(responseData);
|
||||
responseData = await apiRequest.call(this, requestMethod, endpoint, data, qs);
|
||||
|
||||
returnData.push(...responseData.records);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
|
|
@ -58,6 +58,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa
|
|||
body,
|
||||
qs: query,
|
||||
uri: uri || `https://api.airtable.com/v0/${endpoint}`,
|
||||
useQuerystring: false,
|
||||
json: true,
|
||||
};
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ export class Asana implements INodeType {
|
|||
description: INodeTypeDescription = {
|
||||
displayName: 'Asana',
|
||||
name: 'asana',
|
||||
icon: 'file:asana.png',
|
||||
icon: 'file:asana.svg',
|
||||
group: ['input'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
|
|
|
@ -25,7 +25,7 @@ export class AsanaTrigger implements INodeType {
|
|||
description: INodeTypeDescription = {
|
||||
displayName: 'Asana Trigger',
|
||||
name: 'asanaTrigger',
|
||||
icon: 'file:asana.png',
|
||||
icon: 'file:asana.svg',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
description: 'Starts the workflow when Asana events occure.',
|
||||
|
|
Before Width: | Height: | Size: 1.7 KiB |
18
packages/nodes-base/nodes/Asana/asana.svg
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="60px" height="60px" viewBox="0 0 60 60" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>asana_node_icon</title>
|
||||
<defs>
|
||||
<radialGradient cx="50%" cy="55%" fx="50%" fy="55%" r="72.5074481%" gradientTransform="translate(0.500000,0.550000),scale(0.924043,1.000000),translate(-0.500000,-0.550000)" id="radialGradient-1">
|
||||
<stop stop-color="#FFB900" offset="0%"></stop>
|
||||
<stop stop-color="#F95D8F" offset="60%"></stop>
|
||||
<stop stop-color="#F95353" offset="99.91%"></stop>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<g id="asana_node_icon" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g id="asana" transform="translate(0.000000, 2.000000)" fill="url(#radialGradient-1)">
|
||||
<g id="Group" transform="translate(0.731707, 0.731707)">
|
||||
<path d="M45.5941463,28.5 C38.5995187,28.50323 32.9300593,34.1726894 32.9268293,41.1673171 C32.9300593,48.1619448 38.5995187,53.8314041 45.5941463,53.8346341 C52.588774,53.8314041 58.2582334,48.1619448 58.2614634,41.1673171 C58.2582334,34.1726894 52.588774,28.50323 45.5941463,28.5 L45.5941463,28.5 Z M12.6673171,28.5014634 C5.67268939,28.5046934 0.00323002946,34.1741528 -1.40700459e-15,41.1687805 C0.00323002946,48.1634082 5.67268939,53.8328675 12.6673171,53.8360976 C19.6619448,53.8328675 25.3314041,48.1634082 25.3346341,41.1687805 C25.3314041,34.1741528 19.6619448,28.5046934 12.6673171,28.5014634 L12.6673171,28.5014634 Z M41.7892683,12.6673171 C41.7868464,19.6619451 36.118042,25.3320595 29.1234146,25.3360976 C22.1282158,25.3328669 16.4585201,19.6625162 16.4560976,12.6673171 C16.4593276,5.67268939 22.128787,0.00323002946 29.1234146,-1.40700459e-15 C36.1174708,0.0040373938 41.7860389,5.67326048 41.7892683,12.6673171 L41.7892683,12.6673171 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
378
packages/nodes-base/nodes/Autopilot/Autopilot.node.ts
Normal file
|
@ -0,0 +1,378 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
autopilotApiRequest,
|
||||
autopilotApiRequestAllItems,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
contactFields,
|
||||
contactOperations,
|
||||
} from './ContactDescription';
|
||||
|
||||
import {
|
||||
contactJourneyFields,
|
||||
contactJourneyOperations,
|
||||
} from './ContactJourneyDescription';
|
||||
|
||||
import {
|
||||
contactListFields,
|
||||
contactListOperations,
|
||||
} from './ContactListDescription';
|
||||
|
||||
import {
|
||||
listFields,
|
||||
listOperations,
|
||||
} from './ListDescription';
|
||||
|
||||
export class Autopilot implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Autopilot',
|
||||
name: 'autopilot',
|
||||
icon: 'file:autopilot.svg',
|
||||
group: ['input'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume Autopilot API',
|
||||
defaults: {
|
||||
name: 'Autopilot',
|
||||
color: '#6ad7b9',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'autopilotApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Contact',
|
||||
value: 'contact',
|
||||
},
|
||||
{
|
||||
name: 'Contact Journey',
|
||||
value: 'contactJourney',
|
||||
},
|
||||
{
|
||||
name: 'Contact List',
|
||||
value: 'contactList',
|
||||
},
|
||||
{
|
||||
name: 'List',
|
||||
value: 'list',
|
||||
},
|
||||
],
|
||||
default: 'contact',
|
||||
description: 'The resource to operate on.',
|
||||
},
|
||||
|
||||
...contactOperations,
|
||||
...contactFields,
|
||||
...contactJourneyOperations,
|
||||
...contactJourneyFields,
|
||||
...contactListOperations,
|
||||
...contactListFields,
|
||||
...listOperations,
|
||||
...listFields,
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
async getCustomFields(
|
||||
this: ILoadOptionsFunctions,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const customFields = await autopilotApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'/contacts/custom_fields',
|
||||
);
|
||||
for (const customField of customFields) {
|
||||
returnData.push({
|
||||
name: customField.name,
|
||||
value: `${customField.name}-${customField.fieldType}`,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
async getLists(
|
||||
this: ILoadOptionsFunctions,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const { lists } = await autopilotApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'/lists',
|
||||
);
|
||||
for (const list of lists) {
|
||||
returnData.push({
|
||||
name: list.title,
|
||||
value: list.list_id,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
async getTriggers(
|
||||
this: ILoadOptionsFunctions,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const { triggers } = await autopilotApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'/triggers',
|
||||
);
|
||||
for (const trigger of triggers) {
|
||||
returnData.push({
|
||||
name: trigger.journey,
|
||||
value: trigger.trigger_id,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
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++) {
|
||||
try {
|
||||
if (resource === 'contact') {
|
||||
if (operation === 'upsert') {
|
||||
const email = this.getNodeParameter('email', i) as string;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
const body: IDataObject = {
|
||||
Email: email,
|
||||
};
|
||||
|
||||
Object.assign(body, additionalFields);
|
||||
|
||||
if (body.customFieldsUi) {
|
||||
const customFieldsValues = (body.customFieldsUi as IDataObject).customFieldsValues as IDataObject[];
|
||||
|
||||
body.custom = {};
|
||||
|
||||
for (const customField of customFieldsValues) {
|
||||
const [name, fieldType] = (customField.key as string).split('-');
|
||||
|
||||
const fieldName = name.replace(/\s/g, '--');
|
||||
|
||||
//@ts-ignore
|
||||
body.custom[`${fieldType}--${fieldName}`] = customField.value;
|
||||
}
|
||||
delete body.customFieldsUi;
|
||||
}
|
||||
|
||||
if (body.autopilotList) {
|
||||
body._autopilot_list = body.autopilotList;
|
||||
delete body.autopilotList;
|
||||
}
|
||||
|
||||
if (body.autopilotSessionId) {
|
||||
body._autopilot_session_id = body.autopilotSessionId;
|
||||
delete body.autopilotSessionId;
|
||||
}
|
||||
|
||||
if (body.newEmail) {
|
||||
body._NewEmail = body.newEmail;
|
||||
delete body.newEmail;
|
||||
}
|
||||
|
||||
responseData = await autopilotApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/contact`,
|
||||
{ contact: body },
|
||||
);
|
||||
}
|
||||
|
||||
if (operation === 'delete') {
|
||||
const contactId = this.getNodeParameter('contactId', i) as string;
|
||||
|
||||
responseData = await autopilotApiRequest.call(
|
||||
this,
|
||||
'DELETE',
|
||||
`/contact/${contactId}`,
|
||||
);
|
||||
|
||||
responseData = { success: true };
|
||||
}
|
||||
|
||||
if (operation === 'get') {
|
||||
const contactId = this.getNodeParameter('contactId', i) as string;
|
||||
|
||||
responseData = await autopilotApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/contact/${contactId}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
if (returnAll === false) {
|
||||
qs.limit = this.getNodeParameter('limit', i) as number;
|
||||
}
|
||||
responseData = await autopilotApiRequestAllItems.call(
|
||||
this,
|
||||
'contacts',
|
||||
'GET',
|
||||
`/contacts`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
|
||||
if (returnAll === false) {
|
||||
responseData = responseData.splice(0, qs.limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource === 'contactJourney') {
|
||||
if (operation === 'add') {
|
||||
|
||||
const triggerId = this.getNodeParameter('triggerId', i) as string;
|
||||
|
||||
const contactId = this.getNodeParameter('contactId', i) as string;
|
||||
|
||||
responseData = await autopilotApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/trigger/${triggerId}/contact/${contactId}`,
|
||||
);
|
||||
|
||||
responseData = { success: true };
|
||||
}
|
||||
}
|
||||
if (resource === 'contactList') {
|
||||
if (['add', 'remove', 'exist'].includes(operation)) {
|
||||
|
||||
const listId = this.getNodeParameter('listId', i) as string;
|
||||
|
||||
const contactId = this.getNodeParameter('contactId', i) as string;
|
||||
|
||||
const method: { [key: string]: string } = {
|
||||
'add': 'POST',
|
||||
'remove': 'DELETE',
|
||||
'exist': 'GET',
|
||||
};
|
||||
|
||||
const endpoint = `/list/${listId}/contact/${contactId}`;
|
||||
|
||||
if (operation === 'exist') {
|
||||
try {
|
||||
await autopilotApiRequest.call(this, method[operation], endpoint);
|
||||
responseData = { exist: true };
|
||||
} catch (error) {
|
||||
responseData = { exist: false };
|
||||
}
|
||||
} else if (operation === 'add' || operation === 'remove') {
|
||||
responseData = await autopilotApiRequest.call(this, method[operation], endpoint);
|
||||
responseData['success'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
const listId = this.getNodeParameter('listId', i) as string;
|
||||
|
||||
if (returnAll === false) {
|
||||
qs.limit = this.getNodeParameter('limit', i) as number;
|
||||
}
|
||||
responseData = await autopilotApiRequestAllItems.call(
|
||||
this,
|
||||
'contacts',
|
||||
'GET',
|
||||
`/list/${listId}/contacts`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
|
||||
if (returnAll === false) {
|
||||
responseData = responseData.splice(0, qs.limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource === 'list') {
|
||||
if (operation === 'create') {
|
||||
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
|
||||
const body: IDataObject = {
|
||||
name,
|
||||
};
|
||||
|
||||
responseData = await autopilotApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/list`,
|
||||
body,
|
||||
);
|
||||
}
|
||||
|
||||
if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
if (returnAll === false) {
|
||||
qs.limit = this.getNodeParameter('limit', i) as number;
|
||||
}
|
||||
responseData = await autopilotApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'/lists',
|
||||
);
|
||||
|
||||
responseData = responseData.lists;
|
||||
|
||||
if (returnAll === false) {
|
||||
responseData = responseData.splice(0, qs.limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else if (responseData !== undefined) {
|
||||
returnData.push(responseData as IDataObject);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ error: error.toString() });
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
140
packages/nodes-base/nodes/Autopilot/AutopilotTrigger.node.ts
Normal file
|
@ -0,0 +1,140 @@
|
|||
import {
|
||||
IHookFunctions,
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
IWebhookResponseData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
autopilotApiRequest,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
snakeCase,
|
||||
} from 'change-case';
|
||||
|
||||
export class AutopilotTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Autopilot Trigger',
|
||||
name: 'autopilotTrigger',
|
||||
icon: 'file:autopilot.svg',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["event"]}}',
|
||||
description: 'Handle Autopilot events via webhooks',
|
||||
defaults: {
|
||||
name: 'Autopilot Trigger',
|
||||
color: '#6ad7b9',
|
||||
},
|
||||
inputs: [],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'autopilotApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
webhooks: [
|
||||
{
|
||||
name: 'default',
|
||||
httpMethod: 'POST',
|
||||
responseMode: 'onReceived',
|
||||
path: 'webhook',
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Event',
|
||||
name: 'event',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: '',
|
||||
options: [
|
||||
{
|
||||
name: 'Contact Added',
|
||||
value: 'contactAdded',
|
||||
},
|
||||
{
|
||||
name: 'Contact Added To List',
|
||||
value: 'contactAddedToList',
|
||||
},
|
||||
{
|
||||
name: 'Contact Entered Segment',
|
||||
value: 'contactEnteredSegment',
|
||||
},
|
||||
{
|
||||
name: 'Contact Left Segment',
|
||||
value: 'contactLeftSegment',
|
||||
},
|
||||
{
|
||||
name: 'Contact Removed From List',
|
||||
value: 'contactRemovedFromList',
|
||||
},
|
||||
{
|
||||
name: 'Contact Unsubscribed',
|
||||
value: 'contactUnsubscribed',
|
||||
},
|
||||
{
|
||||
name: 'Contact Updated',
|
||||
value: 'contactUpdated',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
webhookMethods = {
|
||||
default: {
|
||||
async checkExists(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||
const event = this.getNodeParameter('event') as string;
|
||||
const { hooks: webhooks } = await autopilotApiRequest.call(this, 'GET', '/hooks');
|
||||
for (const webhook of webhooks) {
|
||||
if (webhook.target_url === webhookUrl && webhook.event === snakeCase(event)) {
|
||||
webhookData.webhookId = webhook.hook_id;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async create(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
const event = this.getNodeParameter('event') as string;
|
||||
const body: IDataObject = {
|
||||
event: snakeCase(event),
|
||||
target_url: webhookUrl,
|
||||
};
|
||||
const webhook = await autopilotApiRequest.call(this, 'POST', '/hook', body);
|
||||
webhookData.webhookId = webhook.hook_id;
|
||||
return true;
|
||||
},
|
||||
async delete(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
try {
|
||||
await autopilotApiRequest.call(this, 'DELETE', `/hook/${webhookData.webhookId}`);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
delete webhookData.webhookId;
|
||||
return true;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
||||
const req = this.getRequestObject();
|
||||
return {
|
||||
workflowData: [
|
||||
this.helpers.returnJsonArray(req.body),
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
369
packages/nodes-base/nodes/Autopilot/ContactDescription.ts
Normal file
|
@ -0,0 +1,369 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const contactOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create/Update',
|
||||
value: 'upsert',
|
||||
description: 'Create/Update a contact',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a contact',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a contact',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all contacts',
|
||||
},
|
||||
],
|
||||
default: 'upsert',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const contactFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:upsert */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
required: true,
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'upsert',
|
||||
],
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Email address of the contact.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'upsert',
|
||||
],
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
placeholder: 'Add Field',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Company',
|
||||
name: 'Company',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Custom Fields',
|
||||
name: 'customFieldsUi',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
placeholder: 'Add Custom Field',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
loadOptionsMethod: 'getCustomFields',
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'customFieldsValues',
|
||||
displayName: 'Custom Field',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getCustomFields',
|
||||
},
|
||||
description: 'User-specified key of user-defined data.',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
description: 'User-specified value of user-defined data.',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Fax',
|
||||
name: 'Fax',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'First Name',
|
||||
name: 'FirstName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Industry',
|
||||
name: 'Industry',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Last Name',
|
||||
name: 'LastName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Lead Source',
|
||||
name: 'LeadSource',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'LinkedIn URL',
|
||||
name: 'LinkedIn',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'List ID',
|
||||
name: 'autopilotList',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getLists',
|
||||
},
|
||||
default: '',
|
||||
description: 'List to which this contact will be added on creation.',
|
||||
},
|
||||
{
|
||||
displayName: 'Mailing Country',
|
||||
name: 'MailingCountry',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Mailing Postal Code',
|
||||
name: 'MailingPostalCode',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Mailing State',
|
||||
name: 'MailingState',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Mailing Street',
|
||||
name: 'MailingStreet',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Mailing City',
|
||||
name: 'MailingCity',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Mobile Phone',
|
||||
name: 'MobilePhone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'New Email',
|
||||
name: 'newEmail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'If provided, will change the email address of the contact identified by the Email field.',
|
||||
},
|
||||
{
|
||||
displayName: 'Notify',
|
||||
name: 'notify',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: `By default Autopilot notifies registered REST hook endpoints for contact_added/contact_updated events when</br> a new contact is added or an existing contact is updated via API. Disable to skip notifications.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Number of Employees',
|
||||
name: 'NumberOfEmployees',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
},
|
||||
{
|
||||
displayName: 'Owner Name',
|
||||
name: 'owner_name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Phone',
|
||||
name: 'Phone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Salutation',
|
||||
name: 'Salutation',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Session ID',
|
||||
name: 'autopilotSessionId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Used to associate a contact with a session.',
|
||||
},
|
||||
{
|
||||
displayName: 'Status',
|
||||
name: 'Status',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'Title',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Subscribe',
|
||||
name: 'unsubscribed',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to subscribe or un-subscribe a contact.',
|
||||
},
|
||||
{
|
||||
displayName: 'Website URL',
|
||||
name: 'Website',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
name: 'contactId',
|
||||
required: true,
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Can be ID or email.',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
name: 'contactId',
|
||||
required: true,
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Can be ID or email.',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
},
|
||||
},
|
||||
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: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
] as INodeProperties[];
|
|
@ -0,0 +1,73 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const contactJourneyOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contactJourney',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Add',
|
||||
value: 'add',
|
||||
description: 'Add contact to list',
|
||||
},
|
||||
],
|
||||
default: 'add',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const contactJourneyFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contactJourney:add */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Trigger ID',
|
||||
name: 'triggerId',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTriggers',
|
||||
},
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'add',
|
||||
],
|
||||
resource: [
|
||||
'contactJourney',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'List ID.',
|
||||
},
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
name: 'contactId',
|
||||
required: true,
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'add',
|
||||
],
|
||||
resource: [
|
||||
'contactJourney',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Can be ID or email.',
|
||||
},
|
||||
] as INodeProperties[];
|
138
packages/nodes-base/nodes/Autopilot/ContactListDescription.ts
Normal file
|
@ -0,0 +1,138 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const contactListOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contactList',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Add',
|
||||
value: 'add',
|
||||
description: 'Add contact to list.',
|
||||
},
|
||||
{
|
||||
name: 'Exist',
|
||||
value: 'exist',
|
||||
description: 'Check if contact is on list.',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all contacts on list.',
|
||||
},
|
||||
{
|
||||
name: 'Remove',
|
||||
value: 'remove',
|
||||
description: 'Remove a contact from a list.',
|
||||
},
|
||||
],
|
||||
default: 'add',
|
||||
description: 'Operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const contactListFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contactList:add */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'List ID',
|
||||
name: 'listId',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getLists',
|
||||
},
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'add',
|
||||
'remove',
|
||||
'exist',
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'contactList',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'ID of the list to operate on.',
|
||||
},
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
name: 'contactId',
|
||||
required: true,
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'add',
|
||||
'remove',
|
||||
'exist',
|
||||
],
|
||||
resource: [
|
||||
'contactList',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Can be ID or email.',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contactList:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'contactList',
|
||||
],
|
||||
},
|
||||
},
|
||||
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: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'contactList',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
] as INodeProperties[];
|
72
packages/nodes-base/nodes/Autopilot/GenericFunctions.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
IHookFunctions,
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function autopilotApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const credentials = this.getCredentials('autopilotApi') as IDataObject;
|
||||
|
||||
const apiKey = `${credentials.apiKey}`;
|
||||
|
||||
const endpoint = 'https://api2.autopilothq.com/v1';
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
autopilotapikey: apiKey,
|
||||
},
|
||||
method,
|
||||
body,
|
||||
qs: query,
|
||||
uri: uri || `${endpoint}${resource}`,
|
||||
json: true,
|
||||
};
|
||||
if (!Object.keys(body).length) {
|
||||
delete options.body;
|
||||
}
|
||||
if (!Object.keys(query).length) {
|
||||
delete options.qs;
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
const errorMessage = error.response.body.message || error.response.body.description || error.message;
|
||||
throw new Error(`Autopilot error response [${error.statusCode}]: ${errorMessage}`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function autopilotApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
const returnAll = this.getNodeParameter('returnAll', 0, false) as boolean;
|
||||
|
||||
const base = endpoint;
|
||||
|
||||
let responseData;
|
||||
do {
|
||||
responseData = await autopilotApiRequest.call(this, method, endpoint, body, query);
|
||||
endpoint = `${base}/${responseData.bookmark}`;
|
||||
returnData.push.apply(returnData, responseData[propertyName]);
|
||||
if (query.limit && returnData.length >= query.limit && returnAll === false) {
|
||||
return returnData;
|
||||
}
|
||||
} while (
|
||||
responseData.bookmark !== undefined
|
||||
);
|
||||
return returnData;
|
||||
}
|
102
packages/nodes-base/nodes/Autopilot/ListDescription.ts
Normal file
|
@ -0,0 +1,102 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const listOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a list.',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all lists.',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const listFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
required: true,
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Name of the list to create.',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
},
|
||||
},
|
||||
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: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
] as INodeProperties[];
|
8
packages/nodes-base/nodes/Autopilot/autopilot.svg
Normal file
|
@ -0,0 +1,8 @@
|
|||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="38 26 35 35">
|
||||
<circle cx="50" cy="50" r="40" stroke="#18d4b2" stroke-width="3" fill="#18d4b2" />
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#ffffff" d="M45.4,42.6h19.9l3.4-4.8H42L45.4,42.6z M48.5,50.9h13.1l3.4-4.8H45.4L48.5,50.9z M102.5,50.2"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 378 B |
|
@ -20,6 +20,8 @@ function getEndpointForService(service: string, credentials: ICredentialDataDecr
|
|||
endpoint = credentials.lambdaEndpoint;
|
||||
} else if (service === 'sns' && credentials.snsEndpoint) {
|
||||
endpoint = credentials.snsEndpoint;
|
||||
} else if (service === 'sqs' && credentials.sqsEndpoint) {
|
||||
endpoint = credentials.sqsEndpoint;
|
||||
} else {
|
||||
endpoint = `https://${service}.${credentials.region}.amazonaws.com`;
|
||||
}
|
||||
|
|
|
@ -1125,15 +1125,9 @@ export class AwsSes implements INodeType {
|
|||
setParameter(params, 'Destination.BccAddresses.member', additionalFields.bccAddresses as string[]);
|
||||
}
|
||||
|
||||
if (additionalFields.ccAddressesUi) {
|
||||
let ccAddresses = (additionalFields.ccAddressesUi as IDataObject).ccAddressesValues as string[];
|
||||
//@ts-ignore
|
||||
ccAddresses = ccAddresses.map(o => o.address);
|
||||
if (ccAddresses) {
|
||||
setParameter(params, 'Destination.CcAddresses.member', ccAddresses);
|
||||
}
|
||||
if (additionalFields.ccAddresses) {
|
||||
setParameter(params, 'Destination.CcAddresses.member', additionalFields.ccAddresses as string[]);
|
||||
}
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, 'email', 'POST', '/?Action=SendEmail&' + params.join('&'));
|
||||
}
|
||||
|
||||
|
@ -1184,13 +1178,8 @@ export class AwsSes implements INodeType {
|
|||
setParameter(params, 'Destination.BccAddresses.member', additionalFields.bccAddresses as string[]);
|
||||
}
|
||||
|
||||
if (additionalFields.ccAddressesUi) {
|
||||
let ccAddresses = (additionalFields.ccAddressesUi as IDataObject).ccAddressesValues as string[];
|
||||
//@ts-ignore
|
||||
ccAddresses = ccAddresses.map(o => o.address);
|
||||
if (ccAddresses) {
|
||||
setParameter(params, 'Destination.CcAddresses.member', ccAddresses);
|
||||
}
|
||||
if (additionalFields.ccAddresses) {
|
||||
setParameter(params, 'Destination.CcAddresses.member', additionalFields.ccAddresses as string[]);
|
||||
}
|
||||
|
||||
if (templateDataUi) {
|
||||
|
|
21
packages/nodes-base/nodes/Aws/SQS/AwsSqs.node.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"node": "n8n-nodes-base.awsSqs",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": [
|
||||
"Development",
|
||||
"Communication"
|
||||
],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/credentials/aws"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.awsSqs/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
387
packages/nodes-base/nodes/Aws/SQS/AwsSqs.node.ts
Normal file
|
@ -0,0 +1,387 @@
|
|||
import {
|
||||
BINARY_ENCODING,
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodeParameters,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
URL,
|
||||
} from 'url';
|
||||
|
||||
import {
|
||||
awsApiRequestSOAP,
|
||||
} from '../GenericFunctions';
|
||||
|
||||
export class AwsSqs implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'AWS SQS',
|
||||
name: 'awsSqs',
|
||||
icon: 'file:sqs.svg',
|
||||
group: ['output'],
|
||||
version: 1,
|
||||
subtitle: `={{$parameter["operation"]}}`,
|
||||
description: 'Sends messages to AWS SQS',
|
||||
defaults: {
|
||||
name: 'AWS SQS',
|
||||
color: '#FF9900',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'aws',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Send message',
|
||||
value: 'sendMessage',
|
||||
description: 'Send a message to a queue.',
|
||||
},
|
||||
],
|
||||
default: 'sendMessage',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
{
|
||||
displayName: 'Queue',
|
||||
name: 'queue',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getQueues',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'sendMessage',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [],
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'Queue to send a message to.',
|
||||
},
|
||||
{
|
||||
displayName: 'Queue Type',
|
||||
name: 'queueType',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'FIFO',
|
||||
value: 'fifo',
|
||||
description: 'FIFO SQS queue.',
|
||||
},
|
||||
{
|
||||
name: 'Standard',
|
||||
value: 'standard',
|
||||
description: 'Standard SQS queue.',
|
||||
},
|
||||
],
|
||||
default: 'standard',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
{
|
||||
displayName: 'Send Input Data',
|
||||
name: 'sendInputData',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Send the data the node receives as JSON to SQS.',
|
||||
},
|
||||
{
|
||||
displayName: 'Message',
|
||||
name: 'message',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'sendMessage',
|
||||
],
|
||||
sendInputData: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Message to send to the queue.',
|
||||
},
|
||||
{
|
||||
displayName: 'Message Group ID',
|
||||
name: 'messageGroupId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Tag that specifies that a message belongs to a specific message group. Applies only to FIFO (first-in-first-out) queues.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
queueType: [
|
||||
'fifo',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'sendMessage',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
placeholder: 'Add Option',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Delay Seconds',
|
||||
name: 'delaySeconds',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/queueType': [
|
||||
'standard',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'How long, in seconds, to delay a message for.',
|
||||
default: 0,
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
maxValue: 900,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Message Attributes',
|
||||
name: 'messageAttributes',
|
||||
placeholder: 'Add Attribute',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
description: 'Attributes to set.',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'binary',
|
||||
displayName: 'Binary',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of the attribute.',
|
||||
},
|
||||
{
|
||||
displayName: 'Property Name',
|
||||
name: 'dataPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
description: 'Name of the binary property which contains the data for the message attribute.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'number',
|
||||
displayName: 'Number',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of the attribute.',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: 'Number value of the attribute.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'string',
|
||||
displayName: 'String',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of the attribute.',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'String value of attribute.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Message Deduplication ID',
|
||||
name: 'messageDeduplicationId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Token used for deduplication of sent messages. Applies only to FIFO (first-in-first-out) queues.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/queueType': [
|
||||
'fifo',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
// Get all the available queues to display them to user so that it can be selected easily
|
||||
async getQueues(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
let data;
|
||||
try {
|
||||
// loads first 1000 queues from SQS
|
||||
data = await awsApiRequestSOAP.call(this, 'sqs', 'GET', `?Action=ListQueues`);
|
||||
} catch (err) {
|
||||
throw new Error(`AWS Error: ${err}`);
|
||||
}
|
||||
|
||||
let queues = data.ListQueuesResponse.ListQueuesResult.QueueUrl;
|
||||
if (!queues) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!Array.isArray(queues)) {
|
||||
// If user has only a single queue no array get returned so we make
|
||||
// one manually to be able to process everything identically
|
||||
queues = [queues];
|
||||
}
|
||||
|
||||
return queues.map((queueUrl: string) => {
|
||||
const urlParts = queueUrl.split('/');
|
||||
const name = urlParts[urlParts.length - 1];
|
||||
|
||||
return {
|
||||
name,
|
||||
value: queueUrl,
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const queueUrl = this.getNodeParameter('queue', i) as string;
|
||||
const queuePath = new URL(queueUrl).pathname;
|
||||
const params = [];
|
||||
|
||||
const options = this.getNodeParameter('options', i, {}) as IDataObject;
|
||||
const sendInputData = this.getNodeParameter('sendInputData', i) as boolean;
|
||||
|
||||
const message = sendInputData ? JSON.stringify(items[i].json) : this.getNodeParameter('message', i) as string;
|
||||
params.push(`MessageBody=${message}`);
|
||||
|
||||
if (options.delaySeconds) {
|
||||
params.push(`DelaySeconds=${options.delaySeconds}`);
|
||||
}
|
||||
|
||||
const queueType = this.getNodeParameter('queueType', i, {}) as string;
|
||||
if (queueType === 'fifo') {
|
||||
const messageDeduplicationId = this.getNodeParameter('options.messageDeduplicationId', i, '') as string;
|
||||
if (messageDeduplicationId) {
|
||||
params.push(`MessageDeduplicationId=${messageDeduplicationId}`);
|
||||
}
|
||||
|
||||
const messageGroupId = this.getNodeParameter('messageGroupId', i) as string;
|
||||
if (messageGroupId) {
|
||||
params.push(`MessageGroupId=${messageGroupId}`);
|
||||
}
|
||||
}
|
||||
|
||||
let attributeCount = 0;
|
||||
// Add string values
|
||||
(this.getNodeParameter('options.messageAttributes.string', i, []) as INodeParameters[]).forEach((attribute) => {
|
||||
attributeCount++;
|
||||
params.push(`MessageAttribute.${attributeCount}.Name=${attribute.name}`);
|
||||
params.push(`MessageAttribute.${attributeCount}.Value.StringValue=${attribute.value}`);
|
||||
params.push(`MessageAttribute.${attributeCount}.Value.DataType=String`);
|
||||
});
|
||||
|
||||
// Add binary values
|
||||
(this.getNodeParameter('options.messageAttributes.binary', i, []) as INodeParameters[]).forEach((attribute) => {
|
||||
attributeCount++;
|
||||
const dataPropertyName = attribute.dataPropertyName as string;
|
||||
const item = items[i];
|
||||
|
||||
if (item.binary === undefined) {
|
||||
throw new Error('No binary data set. So message attribute cannot be added!');
|
||||
}
|
||||
|
||||
if (item.binary[dataPropertyName] === undefined) {
|
||||
throw new Error(`The binary property "${dataPropertyName}" does not exist. So message attribute cannot be added!`);
|
||||
}
|
||||
|
||||
const binaryData = item.binary[dataPropertyName].data;
|
||||
|
||||
params.push(`MessageAttribute.${attributeCount}.Name=${attribute.name}`);
|
||||
params.push(`MessageAttribute.${attributeCount}.Value.BinaryValue=${binaryData}`);
|
||||
params.push(`MessageAttribute.${attributeCount}.Value.DataType=Binary`);
|
||||
});
|
||||
|
||||
// Add number values
|
||||
(this.getNodeParameter('options.messageAttributes.number', i, []) as INodeParameters[]).forEach((attribute) => {
|
||||
attributeCount++;
|
||||
params.push(`MessageAttribute.${attributeCount}.Name=${attribute.name}`);
|
||||
params.push(`MessageAttribute.${attributeCount}.Value.StringValue=${attribute.value}`);
|
||||
params.push(`MessageAttribute.${attributeCount}.Value.DataType=Number`);
|
||||
});
|
||||
|
||||
let responseData;
|
||||
try {
|
||||
responseData = await awsApiRequestSOAP.call(this, 'sqs', 'GET', `${queuePath}/?Action=${operation}&` + params.join('&'));
|
||||
} catch (err) {
|
||||
throw new Error(`AWS Error: ${err}`);
|
||||
}
|
||||
|
||||
const result = responseData.SendMessageResponse.SendMessageResult;
|
||||
returnData.push(result as IDataObject);
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
1
packages/nodes-base/nodes/Aws/SQS/sqs.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-5 0 85 85" fill="#fff" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><use xlink:href="#A" x="2.188" y="2.5"/><symbol id="A" overflow="visible"><g fill="#876929" stroke="none"><path d="M0 25.938L.021 63.44 35 80l34.98-16.56v-9.368l-29.745-3.508.02-21.127L70 25.948V16.57L35 0 0 16.56v9.378z"/><path d="M.021 54.062l34.98 9.942V80L.021 63.44v-9.378z"/><path d="M4.465 65.549L0 63.431V16.56l4.475-2.109-.01 51.098z"/></g><path d="M40.255 50.564l-35.79 4.762.01-30.661 35.78 4.772v21.127zM70 25.948l-35-9.951V0l35 16.57v9.378zm-.02 28.124L35 64.004V80l34.98-16.56v-9.368z" stroke="none" fill="#d9a741"/><path d="M22.109 48.581l12.892 1.526V29.815L22.109 31.36v17.221zM9.125 47.065l8.365.982V31.924l-8.365 1.001v14.14z" stroke="none" fill="#876929"/><path d="M4.475 24.665L35 15.996l35 9.951-29.745 3.489-35.78-4.772z" fill="#624a1e" stroke="none"/><path d="M4.465 55.326L35 64.004l34.98-9.932-29.724-3.508-35.79 4.762z" fill="#fad791" stroke="none"/><path d="M69.98 45.918L35 50.107V29.815l34.98 4.218v11.885z" fill="#d9a741" stroke="none"/></symbol></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -303,7 +303,7 @@ export class Bitwarden implements INodeType {
|
|||
// group: update
|
||||
// ----------------------------------
|
||||
|
||||
const body = {} as IDataObject;
|
||||
const groupId = this.getNodeParameter('groupId', i);
|
||||
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as GroupUpdateFields;
|
||||
|
||||
|
@ -311,7 +311,25 @@ export class Bitwarden implements INodeType {
|
|||
throw new Error(`Please enter at least one field to update for the ${resource}.`);
|
||||
}
|
||||
|
||||
const { name, collections, externalId, accessAll } = updateFields;
|
||||
// set defaults for `name` and `accessAll`, required by Bitwarden but optional in n8n
|
||||
|
||||
let { name, accessAll } = updateFields;
|
||||
|
||||
if (name === undefined) {
|
||||
responseData = await bitwardenApiRequest.call(this, 'GET', `/public/groups/${groupId}`, {}, {}) as { name: string };
|
||||
name = responseData.name;
|
||||
}
|
||||
|
||||
if (accessAll === undefined) {
|
||||
accessAll = false;
|
||||
}
|
||||
|
||||
const body = {
|
||||
name,
|
||||
AccessAll: accessAll,
|
||||
} as IDataObject;
|
||||
|
||||
const { collections, externalId } = updateFields;
|
||||
|
||||
if (collections) {
|
||||
body.collections = collections.map((collectionId) => ({
|
||||
|
@ -320,20 +338,11 @@ export class Bitwarden implements INodeType {
|
|||
}));
|
||||
}
|
||||
|
||||
if (name) {
|
||||
body.name = name;
|
||||
}
|
||||
|
||||
if (externalId) {
|
||||
body.externalId = externalId;
|
||||
}
|
||||
|
||||
if (accessAll !== undefined) {
|
||||
body.AccessAll = accessAll;
|
||||
}
|
||||
|
||||
const id = this.getNodeParameter('groupId', i);
|
||||
const endpoint = `/public/groups/${id}`;
|
||||
const endpoint = `/public/groups/${groupId}`;
|
||||
responseData = await bitwardenApiRequest.call(this, 'PUT', endpoint, {}, body);
|
||||
|
||||
} else if (operation === 'updateMembers') {
|
||||
|
|
|
@ -157,9 +157,9 @@ export async function loadResource(
|
|||
|
||||
const { data } = await bitwardenApiRequest.call(this, 'GET', endpoint, {}, {}, token);
|
||||
|
||||
data.forEach(({ id, name }: { id: string, name: string }) => {
|
||||
data.forEach(({ id, name, externalId }: { id: string, name: string, externalId?: string }) => {
|
||||
returnData.push({
|
||||
name: name || id,
|
||||
name: externalId || name || id,
|
||||
value: id,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -203,12 +203,12 @@ export const groupFields = [
|
|||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
options: [
|
||||
|
||||
{
|
||||
displayName: 'Access All',
|
||||
name: 'accessAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Allow this group to access all collections within the organization, instead of only its associated collections.<br>If set to true, this option overrides any collection assignments.',
|
||||
},
|
||||
{
|
||||
displayName: 'Collections',
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
clearbitApiRequest,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
companyFields,
|
||||
companyOperations,
|
||||
} from './CompanyDescription';
|
||||
|
||||
import {
|
||||
personFields,
|
||||
personOperations,
|
||||
|
@ -23,7 +27,7 @@ export class Clearbit implements INodeType {
|
|||
description: INodeTypeDescription = {
|
||||
displayName: 'Clearbit',
|
||||
name: 'clearbit',
|
||||
icon: 'file:clearbit.png',
|
||||
icon: 'file:clearbit.svg',
|
||||
group: ['output'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ":" + $parameter["resource"]}}',
|
||||
|
@ -109,7 +113,7 @@ export class Clearbit implements INodeType {
|
|||
if (additionalFields.facebook) {
|
||||
qs.facebook = additionalFields.facebook as string;
|
||||
}
|
||||
responseData = await clearbitApiRequest.call(this, 'GET', resource, '/v2/people/find', {}, qs);
|
||||
responseData = await clearbitApiRequest.call(this, 'GET', `${resource}-stream`, '/v2/people/find', {}, qs);
|
||||
}
|
||||
}
|
||||
if (resource === 'company') {
|
||||
|
@ -129,7 +133,7 @@ export class Clearbit implements INodeType {
|
|||
if (additionalFields.facebook) {
|
||||
qs.facebook = additionalFields.facebook as string;
|
||||
}
|
||||
responseData = await clearbitApiRequest.call(this, 'GET', resource, '/v2/companies/find', {}, qs);
|
||||
responseData = await clearbitApiRequest.call(this, 'GET', `${resource}-stream`, '/v2/companies/find', {}, qs);
|
||||
}
|
||||
if (operation === 'autocomplete') {
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
|
|
|
@ -13,16 +13,16 @@ export const companyOperations = [
|
|||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Enrich',
|
||||
value: 'enrich',
|
||||
description: 'Look up person and company data based on an email or domain',
|
||||
},
|
||||
{
|
||||
name: 'Autocomplete',
|
||||
value: 'autocomplete',
|
||||
description: 'Auto-complete company names and retrieve logo and domain',
|
||||
},
|
||||
{
|
||||
name: 'Enrich',
|
||||
value: 'enrich',
|
||||
description: 'Look up person and company data based on an email or domain',
|
||||
},
|
||||
],
|
||||
default: 'enrich',
|
||||
description: 'The operation to perform.',
|
||||
|
@ -31,9 +31,9 @@ export const companyOperations = [
|
|||
|
||||
export const companyFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:enrich */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:enrich */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Domain',
|
||||
name: 'domain',
|
||||
|
@ -99,25 +99,26 @@ export const companyFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:autocomplete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'company',
|
||||
],
|
||||
operation: [
|
||||
'autocomplete',
|
||||
],
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:autocomplete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'company',
|
||||
],
|
||||
operation: [
|
||||
'autocomplete',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Name is the partial name of the company.',
|
||||
},
|
||||
description: 'Name is the partial name of the company.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
import { OptionsWithUri } from 'request';
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
import { IDataObject } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function clearbitApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, api: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('clearbitApi');
|
||||
|
@ -13,11 +19,11 @@ export async function clearbitApiRequest(this: IHookFunctions | IExecuteFunction
|
|||
throw new Error('No credentials got returned!');
|
||||
}
|
||||
let options: OptionsWithUri = {
|
||||
headers: { Authorization: `Bearer ${credentials.apiKey}`},
|
||||
headers: { Authorization: `Bearer ${credentials.apiKey}` },
|
||||
method,
|
||||
qs,
|
||||
body,
|
||||
uri: uri ||`https://${api}-stream.clearbit.com${resource}`,
|
||||
uri: uri || `https://${api}.clearbit.com${resource}`,
|
||||
json: true,
|
||||
};
|
||||
options = Object.assign({}, options, option);
|
||||
|
|
|
@ -26,9 +26,9 @@ export const personOperations = [
|
|||
|
||||
export const personFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* person:enrich */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* person:enrich */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
|
|
Before Width: | Height: | Size: 634 B |
21
packages/nodes-base/nodes/Clearbit/clearbit.svg
Normal file
|
@ -0,0 +1,21 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="72" height="72" viewBox="0 0 72 72">
|
||||
<defs>
|
||||
<linearGradient id="color-a" x1="50%" x2="100%" y1="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#DEF2FE"/>
|
||||
<stop offset="100%" stop-color="#DBF1FE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="color-b" x1="0%" x2="50%" y1="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#57BCFD"/>
|
||||
<stop offset="100%" stop-color="#51B5FD"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="color-c" x1="37.5%" x2="62.5%" y1="0%" y2="100%">
|
||||
<stop offset="0%" stop-color="#1CA7FD"/>
|
||||
<stop offset="100%" stop-color="#148CFC"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path fill="url(#color-a)" d="M72,36 L72,52.770861 L71.9958819,53.6375008 C71.9369091,59.6728148 71.2457056,61.9905403 69.9965047,64.3263428 C68.6892015,66.7707872 66.7707872,68.6892015 64.3263428,69.9965047 L64.0001583,70.1671829 C61.6579559,71.3643165 59.1600786,72 52.770861,72 L36,72 L36,36 L72,36 Z"/>
|
||||
<path fill="url(#color-b)" d="M64.3263428,2.00349528 C66.7707872,3.31079847 68.6892015,5.22921278 69.9965047,7.67365722 L70.1671829,7.99984171 C71.3643165,10.3420441 72,12.8399214 72,19.229139 L72,36 L36,36 L36,0 L52.770861,0 C59.4572515,0 61.8818983,0.696192084 64.3263428,2.00349528 Z"/>
|
||||
<path fill="url(#color-c)" d="M36,0 L36,72 L19.229139,72 L18.3624992,71.9958819 C12.3271852,71.9369091 10.0094597,71.2457056 7.67365722,69.9965047 C5.22921278,68.6892015 3.31079847,66.7707872 2.00349528,64.3263428 L1.83281705,64.0001583 C0.635683537,61.6579559 0,59.1600786 0,52.770861 L0,19.229139 C0,12.5427485 0.696192084,10.1181017 2.00349528,7.67365722 C3.31079847,5.22921278 5.22921278,3.31079847 7.67365722,2.00349528 L7.99984171,1.83281705 C10.3420441,0.635683537 12.8399214,0 19.229139,0 L36,0 Z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const checklistOperations = [
|
||||
{
|
||||
|
@ -38,9 +38,9 @@ export const checklistOperations = [
|
|||
|
||||
export const checklistFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklist:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklist:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Task ID',
|
||||
name: 'task',
|
||||
|
@ -75,9 +75,9 @@ export const checklistFields = [
|
|||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklist:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklist:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Checklist ID',
|
||||
name: 'checklist',
|
||||
|
@ -95,9 +95,9 @@ export const checklistFields = [
|
|||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklist:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklist:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Checklist ID',
|
||||
name: 'checklist',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const checklistItemOperations = [
|
||||
{
|
||||
|
@ -38,9 +38,9 @@ export const checklistItemOperations = [
|
|||
|
||||
export const checklistItemFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklistItem:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklistItem:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Checklist ID',
|
||||
name: 'checklist',
|
||||
|
@ -100,9 +100,10 @@ export const checklistItemFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklistItem:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklistItem:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Checklist ID',
|
||||
name: 'checklist',
|
||||
|
@ -137,9 +138,10 @@ export const checklistItemFields = [
|
|||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklistItem:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklistItem:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Checklist ID',
|
||||
name: 'checklist',
|
||||
|
|
|
@ -57,6 +57,21 @@ import {
|
|||
taskOperations,
|
||||
} from './TaskDescription';
|
||||
|
||||
import {
|
||||
taskListFields,
|
||||
taskListOperations,
|
||||
} from './TaskListDescription';
|
||||
|
||||
import {
|
||||
taskTagFields,
|
||||
taskTagOperations,
|
||||
} from './TaskTagDescription';
|
||||
|
||||
import {
|
||||
spaceTagFields,
|
||||
spaceTagOperations,
|
||||
} from './SpaceTagDescription';
|
||||
|
||||
import {
|
||||
taskDependencyFields,
|
||||
taskDependencyOperations,
|
||||
|
@ -91,7 +106,7 @@ export class ClickUp implements INodeType {
|
|||
description: INodeTypeDescription = {
|
||||
displayName: 'ClickUp',
|
||||
name: 'clickUp',
|
||||
icon: 'file:clickup.png',
|
||||
icon: 'file:clickup.svg',
|
||||
group: ['output'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ":" + $parameter["resource"]}}',
|
||||
|
@ -180,10 +195,22 @@ export class ClickUp implements INodeType {
|
|||
name: 'List',
|
||||
value: 'list',
|
||||
},
|
||||
{
|
||||
name: 'Space Tag',
|
||||
value: 'spaceTag',
|
||||
},
|
||||
{
|
||||
name: 'Task',
|
||||
value: 'task',
|
||||
},
|
||||
{
|
||||
name: 'Task List',
|
||||
value: 'taskList',
|
||||
},
|
||||
{
|
||||
name: 'Task Tag',
|
||||
value: 'taskTag',
|
||||
},
|
||||
{
|
||||
name: 'Task Dependency',
|
||||
value: 'taskDependency',
|
||||
|
@ -221,6 +248,15 @@ export class ClickUp implements INodeType {
|
|||
// GUEST
|
||||
// ...guestOperations,
|
||||
// ...guestFields,
|
||||
// TASK TAG
|
||||
...taskTagOperations,
|
||||
...taskTagFields,
|
||||
// TASK LIST
|
||||
...taskListOperations,
|
||||
...taskListFields,
|
||||
// SPACE TAG
|
||||
...spaceTagOperations,
|
||||
...spaceTagFields,
|
||||
// TASK
|
||||
...taskOperations,
|
||||
...taskFields,
|
||||
|
@ -1022,6 +1058,40 @@ export class ClickUp implements INodeType {
|
|||
responseData = { success: true };
|
||||
}
|
||||
}
|
||||
if (resource === 'taskTag') {
|
||||
if (operation === 'add') {
|
||||
const taskId = this.getNodeParameter('taskId', i) as string;
|
||||
const name = this.getNodeParameter('tagName', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const qs: IDataObject = {};
|
||||
Object.assign(qs, additionalFields);
|
||||
responseData = await clickupApiRequest.call(this, 'POST', `/task/${taskId}/tag/${name}`, {}, qs);
|
||||
responseData = { success: true };
|
||||
}
|
||||
if (operation === 'remove') {
|
||||
const taskId = this.getNodeParameter('taskId', i) as string;
|
||||
const name = this.getNodeParameter('tagName', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const qs: IDataObject = {};
|
||||
Object.assign(qs, additionalFields);
|
||||
responseData = await clickupApiRequest.call(this, 'DELETE', `/task/${taskId}/tag/${name}`, {}, qs);
|
||||
responseData = { success: true };
|
||||
}
|
||||
}
|
||||
if (resource === 'taskList') {
|
||||
if (operation === 'add') {
|
||||
const taskId = this.getNodeParameter('taskId', i) as string;
|
||||
const listId = this.getNodeParameter('listId', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'POST', `/list/${listId}/task/${taskId}`);
|
||||
responseData = { success: true };
|
||||
}
|
||||
if (operation === 'remove') {
|
||||
const taskId = this.getNodeParameter('taskId', i) as string;
|
||||
const listId = this.getNodeParameter('listId', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'DELETE', `/list/${listId}/task/${taskId}`);
|
||||
responseData = { success: true };
|
||||
}
|
||||
}
|
||||
if (resource === 'taskDependency') {
|
||||
if (operation === 'create') {
|
||||
const taskId = this.getNodeParameter('task', i) as string;
|
||||
|
@ -1195,6 +1265,55 @@ export class ClickUp implements INodeType {
|
|||
}
|
||||
|
||||
}
|
||||
if (resource === 'spaceTag') {
|
||||
if (operation === 'create') {
|
||||
const spaceId = this.getNodeParameter('space', i) as string;
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
const foregroundColor = this.getNodeParameter('foregroundColor', i) as string;
|
||||
const backgroundColor = this.getNodeParameter('backgroundColor', i) as string;
|
||||
const body: IDataObject = {
|
||||
tag: {
|
||||
name,
|
||||
tag_bg: backgroundColor,
|
||||
tag_fg: foregroundColor,
|
||||
},
|
||||
};
|
||||
responseData = await clickupApiRequest.call(this, 'POST', `/space/${spaceId}/tag`, body);
|
||||
responseData = { success: true };
|
||||
}
|
||||
if (operation === 'delete') {
|
||||
const spaceId = this.getNodeParameter('space', i) as string;
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'DELETE', `/space/${spaceId}/tag/${name}`);
|
||||
responseData = { success: true };
|
||||
}
|
||||
if (operation === 'getAll') {
|
||||
const spaceId = this.getNodeParameter('space', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
responseData = await clickupApiRequest.call(this, 'GET', `/space/${spaceId}/tag`);
|
||||
responseData = responseData.tags;
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = responseData.splice(0, limit);
|
||||
}
|
||||
}
|
||||
if (operation === 'update') {
|
||||
const spaceId = this.getNodeParameter('space', i) as string;
|
||||
const tagName = this.getNodeParameter('name', i) as string;
|
||||
const newTagName = this.getNodeParameter('newName', i) as string;
|
||||
const foregroundColor = this.getNodeParameter('foregroundColor', i) as string;
|
||||
const backgroundColor = this.getNodeParameter('backgroundColor', i) as string;
|
||||
const body: IDataObject = {
|
||||
tag: {
|
||||
name: newTagName,
|
||||
tag_bg: backgroundColor,
|
||||
tag_fg: foregroundColor,
|
||||
},
|
||||
};
|
||||
await clickupApiRequest.call(this, 'PUT', `/space/${spaceId}/tag/${tagName}`, body);
|
||||
responseData = { success: true };
|
||||
}
|
||||
}
|
||||
if (resource === 'list') {
|
||||
if (operation === 'create') {
|
||||
const spaceId = this.getNodeParameter('space', i) as string;
|
||||
|
|
|
@ -16,13 +16,15 @@ import {
|
|||
clickupApiRequest,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import { createHmac } from 'crypto';
|
||||
import {
|
||||
createHmac,
|
||||
} from 'crypto';
|
||||
|
||||
export class ClickUpTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'ClickUp Trigger',
|
||||
name: 'clickUpTrigger',
|
||||
icon: 'file:clickup.png',
|
||||
icon: 'file:clickup.svg',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
description: 'Handle ClickUp events via webhooks (Beta)',
|
||||
|
@ -302,16 +304,16 @@ export class ClickUpTrigger implements INodeType {
|
|||
body.events = '*';
|
||||
}
|
||||
if (filters.listId) {
|
||||
body.list_id = (filters.listId as string).replace('#','');
|
||||
body.list_id = (filters.listId as string).replace('#', '');
|
||||
}
|
||||
if (filters.taskId) {
|
||||
body.task_id = (filters.taskId as string).replace('#','');
|
||||
body.task_id = (filters.taskId as string).replace('#', '');
|
||||
}
|
||||
if (filters.spaceId) {
|
||||
body.space_id = (filters.spaceId as string).replace('#','');
|
||||
body.space_id = (filters.spaceId as string).replace('#', '');
|
||||
}
|
||||
if (filters.folderId) {
|
||||
body.folder_id = (filters.folderId as string).replace('#','');
|
||||
body.folder_id = (filters.folderId as string).replace('#', '');
|
||||
}
|
||||
const { webhook } = await clickupApiRequest.call(this, 'POST', endpoint, body);
|
||||
webhookData.webhookId = webhook.id;
|
||||
|
@ -323,7 +325,7 @@ export class ClickUpTrigger implements INodeType {
|
|||
const endpoint = `/webhook/${webhookData.webhookId}`;
|
||||
try {
|
||||
await clickupApiRequest.call(this, 'DELETE', endpoint);
|
||||
} catch(error) {
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
delete webhookData.webhookId;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const commentOperations = [
|
||||
{
|
||||
|
@ -43,9 +43,9 @@ export const commentOperations = [
|
|||
|
||||
export const commentFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* comment:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* comment:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Comment On',
|
||||
name: 'commentOn',
|
||||
|
@ -141,9 +141,10 @@ export const commentFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* comment:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* comment:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Comment ID',
|
||||
name: 'comment',
|
||||
|
@ -161,9 +162,10 @@ export const commentFields = [
|
|||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* comment:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* comment:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Comments On',
|
||||
name: 'commentsOn',
|
||||
|
@ -232,9 +234,10 @@ export const commentFields = [
|
|||
default: 50,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* comment:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* comment:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Comment ID',
|
||||
name: 'comment',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const folderOperations = [
|
||||
{
|
||||
|
@ -48,9 +48,9 @@ export const folderOperations = [
|
|||
|
||||
export const folderFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
@ -111,9 +111,10 @@ export const folderFields = [
|
|||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
@ -180,9 +181,10 @@ export const folderFields = [
|
|||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
@ -249,9 +251,10 @@ export const folderFields = [
|
|||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
@ -341,9 +344,10 @@ export const folderFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
|
@ -13,7 +13,7 @@ import {
|
|||
import {
|
||||
IDataObject,
|
||||
IOAuth2Options,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function clickupApiRequest(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 options: OptionsWithUri = {
|
||||
|
@ -23,7 +23,7 @@ export async function clickupApiRequest(this: IHookFunctions | IExecuteFunctions
|
|||
method,
|
||||
qs,
|
||||
body,
|
||||
uri: uri ||`https://api.clickup.com/api/v2${resource}`,
|
||||
uri: uri || `https://api.clickup.com/api/v2${resource}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
|
@ -51,7 +51,7 @@ export async function clickupApiRequest(this: IHookFunctions | IExecuteFunctions
|
|||
return await this.helpers.requestOAuth2!.call(this, 'clickUpOAuth2Api', options, oAuth2Options);
|
||||
}
|
||||
|
||||
} catch(error) {
|
||||
} catch (error) {
|
||||
let errorMessage = error;
|
||||
if (error.err) {
|
||||
errorMessage = error.err;
|
||||
|
@ -61,7 +61,7 @@ export async function clickupApiRequest(this: IHookFunctions | IExecuteFunctions
|
|||
|
||||
}
|
||||
|
||||
export async function clickupApiRequestAllItems(this: IHookFunctions | IExecuteFunctions| ILoadOptionsFunctions, propertyName: string ,method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
export async function clickupApiRequestAllItems(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const goalOperations = [
|
||||
{
|
||||
|
@ -48,9 +48,9 @@ export const goalOperations = [
|
|||
|
||||
export const goalFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goal:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goal:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
@ -140,9 +140,10 @@ export const goalFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goal:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goal:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Goal ID',
|
||||
name: 'goal',
|
||||
|
@ -160,9 +161,10 @@ export const goalFields = [
|
|||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goal:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goal:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Goal ID',
|
||||
name: 'goal',
|
||||
|
@ -180,9 +182,10 @@ export const goalFields = [
|
|||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goal:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goal:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
@ -224,9 +227,10 @@ export const goalFields = [
|
|||
default: 50,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goal:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goal:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Goal ID',
|
||||
name: 'goal',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const goalKeyResultOperations = [
|
||||
{
|
||||
|
@ -38,9 +38,9 @@ export const goalKeyResultOperations = [
|
|||
|
||||
export const goalKeyResultFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goalKeyResult:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goalKeyResult:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Goal ID',
|
||||
name: 'goal',
|
||||
|
@ -178,9 +178,10 @@ export const goalKeyResultFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goalKeyResult:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goalKeyResult:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Key Result ID',
|
||||
name: 'keyResult',
|
||||
|
@ -198,9 +199,10 @@ export const goalKeyResultFields = [
|
|||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goalKeyResult:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goalKeyResult:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Key Result ID',
|
||||
name: 'keyResult',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const guestOperations = [
|
||||
{
|
||||
|
@ -43,9 +43,9 @@ export const guestOperations = [
|
|||
|
||||
export const guestFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* guest:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* guest:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
@ -119,9 +119,10 @@ export const guestFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* guest:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* guest:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
@ -159,9 +160,10 @@ export const guestFields = [
|
|||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* guest:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* guest:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
@ -199,9 +201,10 @@ export const guestFields = [
|
|||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* guest:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* guest:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const listOperations = [
|
||||
{
|
||||
|
@ -58,9 +58,9 @@ export const listOperations = [
|
|||
|
||||
export const listFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
@ -233,9 +233,10 @@ export const listFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:member */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:member */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'List ID',
|
||||
name: 'id',
|
||||
|
@ -295,9 +296,9 @@ export const listFields = [
|
|||
default: 50,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:customFields */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:customFields */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team',
|
||||
name: 'team',
|
||||
|
@ -436,9 +437,10 @@ export const listFields = [
|
|||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
@ -542,9 +544,10 @@ export const listFields = [
|
|||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
@ -648,9 +651,10 @@ export const listFields = [
|
|||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
@ -783,9 +787,10 @@ export const listFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
|
204
packages/nodes-base/nodes/ClickUp/SpaceTagDescription.ts
Normal file
|
@ -0,0 +1,204 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const spaceTagOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'spaceTag',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a space tag',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a space tag',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all space tags',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a space tag',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const spaceTagFields = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* spaceTag:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Space ID',
|
||||
name: 'space',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'spaceTag',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
'delete',
|
||||
'getAll',
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'spaceTag',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: [
|
||||
'space',
|
||||
],
|
||||
loadOptionsMethod: 'getTags',
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'spaceTag',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'New Name',
|
||||
name: 'newName',
|
||||
type: 'string',
|
||||
description: 'New name to set for the tag.',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'spaceTag',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Foreground Color',
|
||||
name: 'foregroundColor',
|
||||
type: 'color',
|
||||
default: '#000000',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'spaceTag',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Background Color',
|
||||
name: 'backgroundColor',
|
||||
type: 'color',
|
||||
default: '#000000',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'spaceTag',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'spaceTag',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'spaceTag',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
] as INodeProperties[];
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const taskDependencyOperations = [
|
||||
{
|
||||
|
@ -33,9 +33,9 @@ export const taskDependencyOperations = [
|
|||
|
||||
export const taskDependencyFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* taskDependency:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* taskDependency:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Task ID',
|
||||
name: 'task',
|
||||
|
@ -69,9 +69,10 @@ export const taskDependencyFields = [
|
|||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* taskDependency:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* taskDependency:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Task ID',
|
||||
name: 'task',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const taskOperations = [
|
||||
{
|
||||
|
@ -58,9 +58,9 @@ export const taskOperations = [
|
|||
|
||||
export const taskFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* task:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* task:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
@ -352,9 +352,10 @@ export const taskFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* task:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* task:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Task ID',
|
||||
name: 'id',
|
||||
|
@ -489,9 +490,10 @@ export const taskFields = [
|
|||
],
|
||||
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* task:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* task:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Task ID',
|
||||
name: 'id',
|
||||
|
@ -510,9 +512,10 @@ export const taskFields = [
|
|||
},
|
||||
description: 'Task ID',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* task:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* task:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
@ -920,9 +923,10 @@ export const taskFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* task:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* task:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Task ID',
|
||||
name: 'id',
|
||||
|
@ -941,9 +945,10 @@ export const taskFields = [
|
|||
},
|
||||
description: 'task ID',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* task:member */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* task:member */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Task ID',
|
||||
name: 'id',
|
||||
|
@ -1003,9 +1008,10 @@ export const taskFields = [
|
|||
default: 50,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* task:setCustomField */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* task:setCustomField */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Task ID',
|
||||
name: 'task',
|
||||
|
|
74
packages/nodes-base/nodes/ClickUp/TaskListDescription.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const taskListOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'taskList',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Add',
|
||||
value: 'add',
|
||||
description: 'Add a task to a list',
|
||||
},
|
||||
{
|
||||
name: 'Remove',
|
||||
value: 'remove',
|
||||
description: 'Remove a task from a list',
|
||||
},
|
||||
],
|
||||
default: 'add',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const taskListFields = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* taskList:add */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Task ID',
|
||||
name: 'taskId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'taskList',
|
||||
],
|
||||
operation: [
|
||||
'remove',
|
||||
'add',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'List ID',
|
||||
name: 'listId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'taskList',
|
||||
],
|
||||
operation: [
|
||||
'remove',
|
||||
'add',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
] as INodeProperties[];
|
111
packages/nodes-base/nodes/ClickUp/TaskTagDescription.ts
Normal file
|
@ -0,0 +1,111 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const taskTagOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'taskTag',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Add',
|
||||
value: 'add',
|
||||
description: 'Add a tag to a task',
|
||||
},
|
||||
{
|
||||
name: 'Remove',
|
||||
value: 'remove',
|
||||
description: 'Remove a tag from a task',
|
||||
},
|
||||
],
|
||||
default: 'add',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const taskTagFields = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* taskTag:add */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Task ID',
|
||||
name: 'taskId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'taskTag',
|
||||
],
|
||||
operation: [
|
||||
'remove',
|
||||
'add',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Tag Name',
|
||||
name: 'tagName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'taskTag',
|
||||
],
|
||||
operation: [
|
||||
'remove',
|
||||
'add',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'taskTag',
|
||||
],
|
||||
operation: [
|
||||
'remove',
|
||||
'add',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Custom Task IDs',
|
||||
name: 'custom_task_ids',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `If you want to reference a task by it's custom task id, this value must be true`,
|
||||
},
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team_id',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTeams',
|
||||
},
|
||||
default: '',
|
||||
description: `Only used when the parameter is set to custom_task_ids=true`,
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const timeEntryTagOperations = [
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ export const timeEntryTagOperations = [
|
|||
{
|
||||
name: 'Remove',
|
||||
value: 'remove',
|
||||
description:'Remove tag from time entry',
|
||||
description: 'Remove tag from time entry',
|
||||
},
|
||||
],
|
||||
default: 'add',
|
||||
|
@ -38,9 +38,9 @@ export const timeEntryTagOperations = [
|
|||
|
||||
export const timeEntryTagFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* timeEntryTag:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* timeEntryTag:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
@ -102,9 +102,10 @@ export const timeEntryTagFields = [
|
|||
default: 5,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* timeEntryTag:add */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* timeEntryTag:add */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
@ -188,9 +189,10 @@ export const timeEntryTagFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* timeEntryTag:remove */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* timeEntryTag:remove */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
|
|
Before Width: | Height: | Size: 1.7 KiB |
1
packages/nodes-base/nodes/ClickUp/clickup.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="130" height="155" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient x1="0%" y1="68.01%" y2="68.01%" id="a"><stop stop-color="#8930FD" offset="0%"/><stop stop-color="#49CCF9" offset="100%"/></linearGradient><linearGradient x1="0%" y1="68.01%" y2="68.01%" id="b"><stop stop-color="#FF02F0" offset="0%"/><stop stop-color="#FFC800" offset="100%"/></linearGradient></defs><g fill-rule="nonzero" fill="none"><path d="M.4 119.12l23.81-18.24C36.86 117.39 50.3 125 65.26 125c14.88 0 27.94-7.52 40.02-23.9l24.15 17.8C112 142.52 90.34 155 65.26 155c-25 0-46.87-12.4-64.86-35.88z" fill="url(#a)"/><path fill="url(#b)" d="M65.18 39.84L22.8 76.36 3.21 53.64 65.27.16l61.57 53.52-19.68 22.64z"/></g></svg>
|
After Width: | Height: | Size: 709 B |
704
packages/nodes-base/nodes/Copper/Copper.node.ts
Normal file
|
@ -0,0 +1,704 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
adjustCompanyFields,
|
||||
adjustLeadFields,
|
||||
adjustPersonFields,
|
||||
adjustTaskFields,
|
||||
copperApiRequest,
|
||||
handleListing,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
companyFields,
|
||||
companyOperations,
|
||||
customerSourceFields,
|
||||
customerSourceOperations,
|
||||
leadFields,
|
||||
leadOperations,
|
||||
opportunityFields,
|
||||
opportunityOperations,
|
||||
personFields,
|
||||
personOperations,
|
||||
projectFields,
|
||||
projectOperations,
|
||||
taskFields,
|
||||
taskOperations,
|
||||
userFields,
|
||||
userOperations,
|
||||
} from './descriptions';
|
||||
|
||||
export class Copper implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Copper',
|
||||
name: 'copper',
|
||||
icon: 'file:copper.svg',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume the Copper API',
|
||||
defaults: {
|
||||
name: 'Copper',
|
||||
color: '#ff2564',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'copperApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Company',
|
||||
value: 'company',
|
||||
},
|
||||
{
|
||||
name: 'Customer Source',
|
||||
value: 'customerSource',
|
||||
},
|
||||
{
|
||||
name: 'Lead',
|
||||
value: 'lead',
|
||||
},
|
||||
{
|
||||
name: 'Opportunity',
|
||||
value: 'opportunity',
|
||||
},
|
||||
{
|
||||
name: 'Person',
|
||||
value: 'person',
|
||||
},
|
||||
{
|
||||
name: 'Project',
|
||||
value: 'project',
|
||||
},
|
||||
{
|
||||
name: 'Task',
|
||||
value: 'task',
|
||||
},
|
||||
{
|
||||
name: 'User',
|
||||
value: 'user',
|
||||
},
|
||||
],
|
||||
default: 'company',
|
||||
description: 'Resource to consume',
|
||||
},
|
||||
...companyOperations,
|
||||
...companyFields,
|
||||
...customerSourceOperations,
|
||||
...customerSourceFields,
|
||||
...leadOperations,
|
||||
...leadFields,
|
||||
...opportunityOperations,
|
||||
...opportunityFields,
|
||||
...personOperations,
|
||||
...personFields,
|
||||
...projectOperations,
|
||||
...projectFields,
|
||||
...taskOperations,
|
||||
...taskFields,
|
||||
...userOperations,
|
||||
...userFields,
|
||||
],
|
||||
};
|
||||
|
||||
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 responseData;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
|
||||
try {
|
||||
|
||||
if (resource === 'company') {
|
||||
|
||||
// **********************************************************************
|
||||
// company
|
||||
// **********************************************************************
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// company: create
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/companies/create-a-new-company.html
|
||||
|
||||
const body: IDataObject = {
|
||||
name: this.getNodeParameter('name', i),
|
||||
};
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(additionalFields).length) {
|
||||
Object.assign(body, adjustCompanyFields(additionalFields));
|
||||
}
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'POST', '/companies', body);
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
|
||||
// ----------------------------------------
|
||||
// company: delete
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/companies/delete-a-company.html
|
||||
|
||||
const companyId = this.getNodeParameter('companyId', i);
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'DELETE', `/companies/${companyId}`);
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// company: get
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/companies/fetch-a-company-by-id.html
|
||||
|
||||
const companyId = this.getNodeParameter('companyId', i);
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'GET', `/companies/${companyId}`);
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// company: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/companies/list-companies-search.html
|
||||
|
||||
const body: IDataObject = {};
|
||||
const filterFields = this.getNodeParameter('filterFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(filterFields).length) {
|
||||
Object.assign(body, filterFields);
|
||||
}
|
||||
|
||||
responseData = await handleListing.call(this, 'POST', '/companies/search', body);
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// ----------------------------------------
|
||||
// company: update
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/companies/update-a-company.html
|
||||
|
||||
const companyId = this.getNodeParameter('companyId', i);
|
||||
|
||||
const body: IDataObject = {};
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(updateFields).length) {
|
||||
Object.assign(body, adjustCompanyFields(updateFields));
|
||||
}
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'PUT', `/companies/${companyId}`, body);
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'customerSource') {
|
||||
|
||||
// **********************************************************************
|
||||
// customerSource
|
||||
// **********************************************************************
|
||||
|
||||
if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// customerSource: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
responseData = await handleListing.call(this, 'GET', '/customer_sources');
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'lead') {
|
||||
|
||||
// **********************************************************************
|
||||
// lead
|
||||
// **********************************************************************
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// lead: create
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/leads/create-a-new-lead.html
|
||||
|
||||
const body: IDataObject = {
|
||||
name: this.getNodeParameter('name', i),
|
||||
};
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(additionalFields).length) {
|
||||
Object.assign(body, adjustLeadFields(additionalFields));
|
||||
}
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'POST', '/leads', body);
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
|
||||
// ----------------------------------------
|
||||
// lead: delete
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/leads/delete-a-lead.html
|
||||
|
||||
const leadId = this.getNodeParameter('leadId', i);
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'DELETE', `/leads/${leadId}`);
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// lead: get
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/leads/fetch-a-lead-by-id.html
|
||||
|
||||
const leadId = this.getNodeParameter('leadId', i);
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'GET', `/leads/${leadId}`);
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// lead: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
const body: IDataObject = {};
|
||||
const filterFields = this.getNodeParameter('filterFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(filterFields).length) {
|
||||
Object.assign(body, filterFields);
|
||||
}
|
||||
|
||||
responseData = await handleListing.call(this, 'POST', '/leads/search', body);
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// ----------------------------------------
|
||||
// lead: update
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/leads/update-a-lead.html
|
||||
|
||||
const leadId = this.getNodeParameter('leadId', i);
|
||||
|
||||
const body: IDataObject = {};
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(updateFields).length) {
|
||||
Object.assign(body, adjustLeadFields(updateFields));
|
||||
}
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'PUT', `/leads/${leadId}`, body);
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'opportunity') {
|
||||
|
||||
// **********************************************************************
|
||||
// opportunity
|
||||
// **********************************************************************
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// opportunity: create
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/opportunities/create-a-new-opportunity.html
|
||||
|
||||
const body: IDataObject = {
|
||||
name: this.getNodeParameter('name', i),
|
||||
customer_source_id: this.getNodeParameter('customerSourceId', i),
|
||||
primary_contact_id: this.getNodeParameter('primaryContactId', i),
|
||||
};
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'POST', '/opportunities', body);
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
|
||||
// ----------------------------------------
|
||||
// opportunity: delete
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/opportunities/delete-an-opportunity.html
|
||||
|
||||
const opportunityId = this.getNodeParameter('opportunityId', i);
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'DELETE', `/opportunities/${opportunityId}`);
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// opportunity: get
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/opportunities/fetch-an-opportunity-by-id.html
|
||||
|
||||
const opportunityId = this.getNodeParameter('opportunityId', i);
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'GET', `/opportunities/${opportunityId}`);
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// opportunity: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/opportunities/list-opportunities-search.html
|
||||
|
||||
const body: IDataObject = {};
|
||||
const filterFields = this.getNodeParameter('filterFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(filterFields).length) {
|
||||
Object.assign(body, filterFields);
|
||||
}
|
||||
|
||||
responseData = await handleListing.call(this, 'POST', '/opportunities/search', body);
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// ----------------------------------------
|
||||
// opportunity: update
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/opportunities/update-an-opportunity.html
|
||||
|
||||
const opportunityId = this.getNodeParameter('opportunityId', i);
|
||||
|
||||
const body: IDataObject = {};
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(updateFields).length) {
|
||||
Object.assign(body, updateFields);
|
||||
}
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'PUT', `/opportunities/${opportunityId}`, body);
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'person') {
|
||||
|
||||
// **********************************************************************
|
||||
// person
|
||||
// **********************************************************************
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// person: create
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/people/create-a-new-person.html
|
||||
|
||||
const body: IDataObject = {
|
||||
name: this.getNodeParameter('name', i),
|
||||
};
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(additionalFields).length) {
|
||||
Object.assign(body, adjustPersonFields(additionalFields));
|
||||
}
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'POST', '/people', body);
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
|
||||
// ----------------------------------------
|
||||
// person: delete
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/people/delete-a-person.html
|
||||
|
||||
const personId = this.getNodeParameter('personId', i);
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'DELETE', `/people/${personId}`);
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// person: get
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/people/fetch-a-person-by-id.html
|
||||
|
||||
const personId = this.getNodeParameter('personId', i);
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'GET', `/people/${personId}`);
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// person: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
const body: IDataObject = {};
|
||||
const filterFields = this.getNodeParameter('filterFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(filterFields).length) {
|
||||
Object.assign(body, filterFields);
|
||||
}
|
||||
|
||||
responseData = await handleListing.call(this, 'POST', '/people/search', body);
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// ----------------------------------------
|
||||
// person: update
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/people/update-a-person.html
|
||||
|
||||
const personId = this.getNodeParameter('personId', i);
|
||||
|
||||
const body: IDataObject = {};
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(updateFields).length) {
|
||||
Object.assign(body, adjustPersonFields(updateFields));
|
||||
}
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'PUT', `/people/${personId}`, body);
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'project') {
|
||||
|
||||
// **********************************************************************
|
||||
// project
|
||||
// **********************************************************************
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// project: create
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/projects/create-a-new-project.html
|
||||
|
||||
const body: IDataObject = {
|
||||
name: this.getNodeParameter('name', i),
|
||||
};
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(additionalFields).length) {
|
||||
Object.assign(body, additionalFields);
|
||||
}
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'POST', '/projects', body);
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
|
||||
// ----------------------------------------
|
||||
// project: delete
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/projects/delete-a-project.html
|
||||
|
||||
const projectId = this.getNodeParameter('projectId', i);
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'DELETE', `/projects/${projectId}`);
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// project: get
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/projects/fetch-a-project-by-id.html
|
||||
|
||||
const projectId = this.getNodeParameter('projectId', i);
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'GET', `/projects/${projectId}`);
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// project: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/projects/list-projects-search.html
|
||||
|
||||
const body: IDataObject = {};
|
||||
const filterFields = this.getNodeParameter('filterFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(filterFields).length) {
|
||||
Object.assign(body, filterFields);
|
||||
}
|
||||
|
||||
responseData = await handleListing.call(this, 'POST', '/projects/search', body);
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// ----------------------------------------
|
||||
// project: update
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/projects/update-a-project.html
|
||||
|
||||
const projectId = this.getNodeParameter('projectId', i);
|
||||
|
||||
const body: IDataObject = {};
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(updateFields).length) {
|
||||
Object.assign(body, updateFields);
|
||||
}
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'PUT', `/projects/${projectId}`, body);
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'task') {
|
||||
|
||||
// **********************************************************************
|
||||
// task
|
||||
// **********************************************************************
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// task: create
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/tasks/create-a-new-task.html
|
||||
|
||||
const body: IDataObject = {
|
||||
name: this.getNodeParameter('name', i),
|
||||
};
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(additionalFields).length) {
|
||||
Object.assign(body, additionalFields);
|
||||
}
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'POST', '/tasks', body);
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
|
||||
// ----------------------------------------
|
||||
// task: delete
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/tasks/delete-a-task.html
|
||||
|
||||
const taskId = this.getNodeParameter('taskId', i);
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'DELETE', `/tasks/${taskId}`);
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// task: get
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/tasks/fetch-a-task-by-id.html
|
||||
|
||||
const taskId = this.getNodeParameter('taskId', i);
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'GET', `/tasks/${taskId}`);
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// task: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/tasks/list-tasks-search.html
|
||||
|
||||
const body: IDataObject = {};
|
||||
const filterFields = this.getNodeParameter('filterFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(filterFields).length) {
|
||||
Object.assign(body, adjustTaskFields(filterFields));
|
||||
}
|
||||
|
||||
responseData = await handleListing.call(this, 'POST', '/tasks/search', body);
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// ----------------------------------------
|
||||
// task: update
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.copper.com/tasks/update-a-task.html
|
||||
|
||||
const taskId = this.getNodeParameter('taskId', i);
|
||||
|
||||
const body: IDataObject = {};
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(updateFields).length) {
|
||||
Object.assign(body, updateFields);
|
||||
}
|
||||
|
||||
responseData = await copperApiRequest.call(this, 'PUT', `/tasks/${taskId}`, body);
|
||||
}
|
||||
|
||||
} else if (resource === 'user') {
|
||||
|
||||
// **********************************************************************
|
||||
// user
|
||||
// **********************************************************************
|
||||
|
||||
if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// user: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
responseData = await handleListing.call(this, 'POST', '/users/search');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ error: error.toString() });
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
Array.isArray(responseData)
|
||||
? returnData.push(...responseData)
|
||||
: returnData.push(responseData);
|
||||
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ export class CopperTrigger implements INodeType {
|
|||
description: INodeTypeDescription = {
|
||||
displayName: 'Copper Trigger',
|
||||
name: 'copperTrigger',
|
||||
icon: 'file:copper.png',
|
||||
icon: 'file:copper.svg',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
description: 'Handle Copper events via webhooks',
|
||||
|
@ -147,7 +147,7 @@ export class CopperTrigger implements INodeType {
|
|||
const endpoint = `/webhooks/${webhookData.webhookId}`;
|
||||
try {
|
||||
await copperApiRequest.call(this, 'DELETE', endpoint);
|
||||
} catch(error) {
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
delete webhookData.webhookId;
|
||||
|
|