🔀 Merge branch 'master' into add-tags
10
.github/workflows/docker-images-rpi.yml
vendored
|
@ -4,6 +4,12 @@ on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- n8n@*
|
- n8n@*
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: 'n8n version to build docker image for.'
|
||||||
|
required: true
|
||||||
|
default: '0.112.0'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
armv7_job:
|
armv7_job:
|
||||||
|
@ -28,7 +34,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
docker buildx build \
|
docker buildx build \
|
||||||
--platform linux/arm/v7 \
|
--platform linux/arm/v7 \
|
||||||
--build-arg N8N_VERSION=${{steps.vars.outputs.tag}} \
|
--build-arg N8N_VERSION=${{github.event.inputs.version || steps.vars.outputs.tag}} \
|
||||||
-t ${{ secrets.DOCKER_USERNAME }}/n8n:${{steps.vars.outputs.tag}}-rpi \
|
-t ${{ secrets.DOCKER_USERNAME }}/n8n:${{github.event.inputs.version || steps.vars.outputs.tag}}-rpi \
|
||||||
-t ${{ secrets.DOCKER_USERNAME }}/n8n:latest-rpi \
|
-t ${{ secrets.DOCKER_USERNAME }}/n8n:latest-rpi \
|
||||||
--output type=image,push=true docker/images/n8n-rpi
|
--output type=image,push=true docker/images/n8n-rpi
|
||||||
|
|
2
.github/workflows/tests.yml
vendored
|
@ -9,7 +9,7 @@ jobs:
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [10.x, 12.x, 14.x]
|
node-version: [12.x, 14.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
|
|
|
@ -15,6 +15,7 @@ ENV NODE_ENV production
|
||||||
|
|
||||||
WORKDIR /data
|
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.
|
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
|
## 0.105.0
|
||||||
|
|
||||||
### What changed?
|
### What changed?
|
||||||
|
|
|
@ -21,7 +21,7 @@ import {
|
||||||
WorkflowCredentials,
|
WorkflowCredentials,
|
||||||
WorkflowHelpers,
|
WorkflowHelpers,
|
||||||
WorkflowRunner,
|
WorkflowRunner,
|
||||||
} from "../src";
|
} from '../src';
|
||||||
|
|
||||||
|
|
||||||
export class Execute extends Command {
|
export class Execute extends Command {
|
||||||
|
@ -127,7 +127,7 @@ export class Execute extends Command {
|
||||||
// Check if the workflow contains the required "Start" node
|
// Check if the workflow contains the required "Start" node
|
||||||
// "requiredNodeTypes" are also defined in editor-ui/views/NodeView.vue
|
// "requiredNodeTypes" are also defined in editor-ui/views/NodeView.vue
|
||||||
const requiredNodeTypes = ['n8n-nodes-base.start'];
|
const requiredNodeTypes = ['n8n-nodes-base.start'];
|
||||||
let startNode: INode | undefined= undefined;
|
let startNode: INode | undefined = undefined;
|
||||||
for (const node of workflowData!.nodes) {
|
for (const node of workflowData!.nodes) {
|
||||||
if (requiredNodeTypes.includes(node.type)) {
|
if (requiredNodeTypes.includes(node.type)) {
|
||||||
startNode = node;
|
startNode = node;
|
||||||
|
|
|
@ -140,7 +140,7 @@ export class ExportCredentialsCommand extends Command {
|
||||||
let fileContents: string, i: number;
|
let fileContents: string, i: number;
|
||||||
for (i = 0; i < credentials.length; i++) {
|
for (i = 0; i < credentials.length; i++) {
|
||||||
fileContents = JSON.stringify(credentials[i], null, flags.pretty ? 2 : undefined);
|
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);
|
fs.writeFileSync(filename, fileContents);
|
||||||
}
|
}
|
||||||
console.log('Successfully exported', i, 'credentials.');
|
console.log('Successfully exported', i, 'credentials.');
|
||||||
|
|
|
@ -116,7 +116,7 @@ export class ExportWorkflowsCommand extends Command {
|
||||||
let fileContents: string, i: number;
|
let fileContents: string, i: number;
|
||||||
for (i = 0; i < workflows.length; i++) {
|
for (i = 0; i < workflows.length; i++) {
|
||||||
fileContents = JSON.stringify(workflows[i], null, flags.pretty ? 2 : undefined);
|
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);
|
fs.writeFileSync(filename, fileContents);
|
||||||
}
|
}
|
||||||
console.log('Successfully exported', i, 'workflows.');
|
console.log('Successfully exported', i, 'workflows.');
|
||||||
|
|
|
@ -56,10 +56,22 @@ export class ImportCredentialsCommand extends Command {
|
||||||
try {
|
try {
|
||||||
await Db.init();
|
await Db.init();
|
||||||
let i;
|
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) {
|
if (flags.separate) {
|
||||||
const files = await glob((flags.input.endsWith(path.sep) ? flags.input : flags.input + path.sep) + '*.json');
|
const files = await glob((flags.input.endsWith(path.sep) ? flags.input : flags.input + path.sep) + '*.json');
|
||||||
for (i = 0; i < files.length; i++) {
|
for (i = 0; i < files.length; i++) {
|
||||||
const credential = JSON.parse(fs.readFileSync(files[i], { encoding: 'utf8' }));
|
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);
|
await Db.collections.Credentials!.save(credential);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -69,10 +81,6 @@ export class ImportCredentialsCommand extends Command {
|
||||||
throw new Error(`File does not seem to contain credentials.`);
|
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++) {
|
for (i = 0; i < fileContents.length; i++) {
|
||||||
if (typeof fileContents[i].data === 'object') {
|
if (typeof fileContents[i].data === 'object') {
|
||||||
// plain data / decrypted input. Should be encrypted first.
|
// plain data / decrypted input. Should be encrypted first.
|
||||||
|
|
|
@ -17,11 +17,12 @@ import {
|
||||||
Db,
|
Db,
|
||||||
ExternalHooks,
|
ExternalHooks,
|
||||||
GenericHelpers,
|
GenericHelpers,
|
||||||
|
IExecutionsCurrentSummary,
|
||||||
LoadNodesAndCredentials,
|
LoadNodesAndCredentials,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
Server,
|
Server,
|
||||||
TestWebhooks,
|
TestWebhooks,
|
||||||
} from "../src";
|
} from '../src';
|
||||||
import { IDataObject } from 'n8n-workflow';
|
import { IDataObject } from 'n8n-workflow';
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,12 +98,15 @@ export class Start extends Command {
|
||||||
|
|
||||||
// Wait for active workflow executions to finish
|
// Wait for active workflow executions to finish
|
||||||
const activeExecutionsInstance = ActiveExecutions.getInstance();
|
const activeExecutionsInstance = ActiveExecutions.getInstance();
|
||||||
let executingWorkflows = activeExecutionsInstance.getActiveExecutions();
|
let executingWorkflows = activeExecutionsInstance.getActiveExecutions() as IExecutionsCurrentSummary[];
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
while (executingWorkflows.length !== 0) {
|
while (executingWorkflows.length !== 0) {
|
||||||
if (count++ % 4 === 0) {
|
if (count++ % 4 === 0) {
|
||||||
console.log(`Waiting for ${executingWorkflows.length} active executions to finish...`);
|
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) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(resolve, 500);
|
setTimeout(resolve, 500);
|
||||||
|
@ -129,7 +133,7 @@ export class Start extends Command {
|
||||||
await (async () => {
|
await (async () => {
|
||||||
try {
|
try {
|
||||||
// Start directly with the init of the database to improve startup time
|
// 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}`);
|
console.error(`There was an error initializing DB: ${error.message}`);
|
||||||
|
|
||||||
processExistCode = 1;
|
processExistCode = 1;
|
||||||
|
@ -168,7 +172,7 @@ export class Start extends Command {
|
||||||
const redisDB = config.get('queue.bull.redis.db');
|
const redisDB = config.get('queue.bull.redis.db');
|
||||||
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
|
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
|
||||||
let lastTimer = 0, cumulativeTimeout = 0;
|
let lastTimer = 0, cumulativeTimeout = 0;
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
retryStrategy: (times: number): number | null => {
|
retryStrategy: (times: number): number | null => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
@ -180,7 +184,7 @@ export class Start extends Command {
|
||||||
cumulativeTimeout += now - lastTimer;
|
cumulativeTimeout += now - lastTimer;
|
||||||
lastTimer = now;
|
lastTimer = now;
|
||||||
if (cumulativeTimeout > redisConnectionTimeoutLimit) {
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,7 +204,7 @@ export class Start extends Command {
|
||||||
if (redisDB) {
|
if (redisDB) {
|
||||||
settings.db = redisDB;
|
settings.db = redisDB;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This connection is going to be our heartbeat
|
// This connection is going to be our heartbeat
|
||||||
// IORedis automatically pings redis and tries to reconnect
|
// IORedis automatically pings redis and tries to reconnect
|
||||||
// We will be using the retryStrategy above
|
// 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;
|
const dbType = await GenericHelpers.getConfigValue('database.type') as DatabaseType;
|
||||||
|
|
||||||
if (dbType === 'sqlite') {
|
if (dbType === 'sqlite') {
|
||||||
const shouldRunVacuum = config.get('database.sqlite.executeVacuumOnStartup') as number;
|
const shouldRunVacuum = config.get('database.sqlite.executeVacuumOnStartup') as number;
|
||||||
if (shouldRunVacuum) {
|
if (shouldRunVacuum) {
|
||||||
Db.collections.Execution!.query("VACUUM;");
|
Db.collections.Execution!.query('VACUUM;');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,7 +284,7 @@ export class Start extends Command {
|
||||||
Start.openBrowser();
|
Start.openBrowser();
|
||||||
}
|
}
|
||||||
this.log(`\nPress "o" to open in Browser.`);
|
this.log(`\nPress "o" to open in Browser.`);
|
||||||
process.stdin.on("data", (key : string) => {
|
process.stdin.on('data', (key: string) => {
|
||||||
if (key === 'o') {
|
if (key === 'o') {
|
||||||
Start.openBrowser();
|
Start.openBrowser();
|
||||||
inputText = '';
|
inputText = '';
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
import {
|
import {
|
||||||
Db,
|
Db,
|
||||||
GenericHelpers,
|
GenericHelpers,
|
||||||
} from "../../src";
|
} from '../../src';
|
||||||
|
|
||||||
|
|
||||||
export class UpdateWorkflowCommand extends Command {
|
export class UpdateWorkflowCommand extends Command {
|
||||||
|
|
|
@ -17,7 +17,7 @@ import {
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
TestWebhooks,
|
TestWebhooks,
|
||||||
WebhookServer,
|
WebhookServer,
|
||||||
} from "../src";
|
} from '../src';
|
||||||
import { IDataObject } from 'n8n-workflow';
|
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
|
// Wrap that the process does not close but we can still use async
|
||||||
await (async () => {
|
await (async () => {
|
||||||
if (config.get('executions.mode') !== 'queue') {
|
if (config.get('executions.mode') !== 'queue') {
|
||||||
/**
|
/**
|
||||||
* It is technically possible to run without queues but
|
* It is technically possible to run without queues but
|
||||||
* there are 2 known bugs when running in this mode:
|
* there are 2 known bugs when running in this mode:
|
||||||
* - Executions list will be problematic as the main process
|
* - 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 redisDB = config.get('queue.bull.redis.db');
|
||||||
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
|
const redisConnectionTimeoutLimit = config.get('queue.bull.redis.timeoutThreshold');
|
||||||
let lastTimer = 0, cumulativeTimeout = 0;
|
let lastTimer = 0, cumulativeTimeout = 0;
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
retryStrategy: (times: number): number | null => {
|
retryStrategy: (times: number): number | null => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
@ -166,7 +166,7 @@ export class Webhook extends Command {
|
||||||
cumulativeTimeout += now - lastTimer;
|
cumulativeTimeout += now - lastTimer;
|
||||||
lastTimer = now;
|
lastTimer = now;
|
||||||
if (cumulativeTimeout > redisConnectionTimeoutLimit) {
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ export class Webhook extends Command {
|
||||||
if (redisDB) {
|
if (redisDB) {
|
||||||
settings.db = redisDB;
|
settings.db = redisDB;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This connection is going to be our heartbeat
|
// This connection is going to be our heartbeat
|
||||||
// IORedis automatically pings redis and tries to reconnect
|
// IORedis automatically pings redis and tries to reconnect
|
||||||
// We will be using the retryStrategy above
|
// We will be using the retryStrategy above
|
||||||
|
@ -201,7 +201,7 @@ export class Webhook extends Command {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await WebhookServer.start();
|
await WebhookServer.start();
|
||||||
|
|
||||||
// Start to get active workflows and run their triggers
|
// Start to get active workflows and run their triggers
|
||||||
|
|
|
@ -35,7 +35,7 @@ import {
|
||||||
ResponseHelper,
|
ResponseHelper,
|
||||||
WorkflowCredentials,
|
WorkflowCredentials,
|
||||||
WorkflowExecuteAdditionalData,
|
WorkflowExecuteAdditionalData,
|
||||||
} from "../src";
|
} from '../src';
|
||||||
|
|
||||||
import * as config from '../config';
|
import * as config from '../config';
|
||||||
import * as Bull from 'bull';
|
import * as Bull from 'bull';
|
||||||
|
@ -132,7 +132,7 @@ export class Worker extends Command {
|
||||||
const credentials = await WorkflowCredentials(currentExecutionDb.workflowData.nodes);
|
const credentials = await WorkflowCredentials(currentExecutionDb.workflowData.nodes);
|
||||||
|
|
||||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
|
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 workflowExecute: WorkflowExecute;
|
||||||
let workflowRun: PCancelable<IRun>;
|
let workflowRun: PCancelable<IRun>;
|
||||||
|
@ -241,7 +241,7 @@ export class Worker extends Command {
|
||||||
cumulativeTimeout += now - lastTimer;
|
cumulativeTimeout += now - lastTimer;
|
||||||
lastTimer = now;
|
lastTimer = now;
|
||||||
if (cumulativeTimeout > redisConnectionTimeoutLimit) {
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -446,6 +446,20 @@ const config = convict({
|
||||||
},
|
},
|
||||||
|
|
||||||
endpoints: {
|
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: {
|
rest: {
|
||||||
format: String,
|
format: String,
|
||||||
default: 'rest',
|
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.',
|
doc: 'Disable production webhooks from main process. This helps ensures no http traffic load to main process when using webhook-specific processes.',
|
||||||
},
|
},
|
||||||
skipWebhoooksDeregistrationOnShutdown: {
|
skipWebhoooksDeregistrationOnShutdown: {
|
||||||
/**
|
/**
|
||||||
* Longer explanation: n8n deregisters webhooks on shutdown / deactivation
|
* Longer explanation: n8n deregisters webhooks on shutdown / deactivation
|
||||||
* and registers on startup / activation. If we skip
|
* and registers on startup / activation. If we skip
|
||||||
* deactivation on shutdown, webhooks will remain active on 3rd party services.
|
* deactivation on shutdown, webhooks will remain active on 3rd party services.
|
||||||
|
|
|
@ -1,137 +1,138 @@
|
||||||
{
|
{
|
||||||
"name": "n8n",
|
"name": "n8n",
|
||||||
"version": "0.110.3",
|
"version": "0.114.0",
|
||||||
"description": "n8n Workflow Automation Tool",
|
"description": "n8n Workflow Automation Tool",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"homepage": "https://n8n.io",
|
"homepage": "https://n8n.io",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Jan Oberhauser",
|
"name": "Jan Oberhauser",
|
||||||
"email": "jan@n8n.io"
|
"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": {
|
"testURL": "http://localhost/",
|
||||||
"type": "git",
|
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||||
"url": "git+https://github.com/n8n-io/n8n.git"
|
"testPathIgnorePatterns": [
|
||||||
},
|
"/dist/",
|
||||||
"main": "dist/index",
|
"/node_modules/"
|
||||||
"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": {
|
"moduleFileExtensions": [
|
||||||
"node": ">=12.0.0"
|
"ts",
|
||||||
},
|
"tsx",
|
||||||
"files": [
|
"js",
|
||||||
"bin",
|
"json"
|
||||||
"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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import {
|
||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
WebhookHttpMethod,
|
WebhookHttpMethod,
|
||||||
Workflow,
|
Workflow,
|
||||||
|
WorkflowActivateMode,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
@ -66,7 +67,7 @@ export class ActiveWorkflowRunner {
|
||||||
for (const workflowData of workflowsData) {
|
for (const workflowData of workflowsData) {
|
||||||
console.log(` - ${workflowData.name}`);
|
console.log(` - ${workflowData.name}`);
|
||||||
try {
|
try {
|
||||||
await this.add(workflowData.id.toString(), workflowData);
|
await this.add(workflowData.id.toString(), 'init', workflowData);
|
||||||
console.log(` => Started`);
|
console.log(` => Started`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(` => ERROR: Workflow could not be activated:`);
|
console.log(` => ERROR: Workflow could not be activated:`);
|
||||||
|
@ -273,7 +274,7 @@ export class ActiveWorkflowRunner {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
* @memberof ActiveWorkflowRunner
|
* @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);
|
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData);
|
||||||
let path = '' as string | undefined;
|
let path = '' as string | undefined;
|
||||||
|
|
||||||
|
@ -319,10 +320,10 @@ export class ActiveWorkflowRunner {
|
||||||
|
|
||||||
await Db.collections.Webhook?.insert(webhook);
|
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 (webhookExists !== true) {
|
||||||
// If webhook does not exist yet create it
|
// 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) {
|
} catch (error) {
|
||||||
|
@ -378,7 +379,7 @@ export class ActiveWorkflowRunner {
|
||||||
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData);
|
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData);
|
||||||
|
|
||||||
for (const webhookData of webhooks) {
|
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);
|
await WorkflowHelpers.saveStaticData(workflow);
|
||||||
|
@ -446,9 +447,9 @@ export class ActiveWorkflowRunner {
|
||||||
* @returns {IGetExecutePollFunctions}
|
* @returns {IGetExecutePollFunctions}
|
||||||
* @memberof ActiveWorkflowRunner
|
* @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) => {
|
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 => {
|
returnFunctions.__emit = (data: INodeExecutionData[][]): void => {
|
||||||
this.runWorkflow(workflowData, node, data, additionalData, mode);
|
this.runWorkflow(workflowData, node, data, additionalData, mode);
|
||||||
};
|
};
|
||||||
|
@ -467,9 +468,9 @@ export class ActiveWorkflowRunner {
|
||||||
* @returns {IGetExecuteTriggerFunctions}
|
* @returns {IGetExecuteTriggerFunctions}
|
||||||
* @memberof ActiveWorkflowRunner
|
* @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) => {
|
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 => {
|
returnFunctions.emit = (data: INodeExecutionData[][]): void => {
|
||||||
WorkflowHelpers.saveStaticData(workflow);
|
WorkflowHelpers.saveStaticData(workflow);
|
||||||
this.runWorkflow(workflowData, node, data, additionalData, mode).catch((err) => console.error(err));
|
this.runWorkflow(workflowData, node, data, additionalData, mode).catch((err) => console.error(err));
|
||||||
|
@ -486,7 +487,7 @@ export class ActiveWorkflowRunner {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
* @memberof ActiveWorkflowRunner
|
* @memberof ActiveWorkflowRunner
|
||||||
*/
|
*/
|
||||||
async add(workflowId: string, workflowData?: IWorkflowDb): Promise<void> {
|
async add(workflowId: string, activation: WorkflowActivateMode, workflowData?: IWorkflowDb): Promise<void> {
|
||||||
if (this.activeWorkflows === null) {
|
if (this.activeWorkflows === null) {
|
||||||
throw new Error(`The "activeWorkflows" instance did not get initialized yet.`);
|
throw new Error(`The "activeWorkflows" instance did not get initialized yet.`);
|
||||||
}
|
}
|
||||||
|
@ -511,15 +512,15 @@ export class ActiveWorkflowRunner {
|
||||||
const mode = 'trigger';
|
const mode = 'trigger';
|
||||||
const credentials = await WorkflowCredentials(workflowData.nodes);
|
const credentials = await WorkflowCredentials(workflowData.nodes);
|
||||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
|
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
|
||||||
const getTriggerFunctions = this.getExecuteTriggerFunctions(workflowData, additionalData, mode);
|
const getTriggerFunctions = this.getExecuteTriggerFunctions(workflowData, additionalData, mode, activation);
|
||||||
const getPollFunctions = this.getExecutePollFunctions(workflowData, additionalData, mode);
|
const getPollFunctions = this.getExecutePollFunctions(workflowData, additionalData, mode, activation);
|
||||||
|
|
||||||
// Add the workflows which have webhooks defined
|
// 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
|
if (workflowInstance.getTriggerNodes().length !== 0
|
||||||
|| workflowInstance.getPollNodes().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) {
|
if (this.activationErrors[workflowId] !== undefined) {
|
||||||
|
|
|
@ -95,10 +95,10 @@ function extractSchemaForKey(configKey: string, configSchema: IDataObject): IDat
|
||||||
for (const key of configKeyParts) {
|
for (const key of configKeyParts) {
|
||||||
if (configSchema[key] === undefined) {
|
if (configSchema[key] === undefined) {
|
||||||
throw new Error(`Key "${key}" of ConfigKey "${configKey}" does not exist`);
|
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;
|
configSchema = configSchema[key] as IDataObject;
|
||||||
} else {
|
} else {
|
||||||
configSchema = (configSchema[key] as IDataObject).properties as IDataObject;
|
configSchema = (configSchema[key] as IDataObject)._cvtProperties as IDataObject;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return configSchema;
|
return configSchema;
|
||||||
|
@ -114,7 +114,8 @@ function extractSchemaForKey(configKey: string, configSchema: IDataObject): IDat
|
||||||
export async function getConfigValue(configKey: string): Promise<string | boolean | number | undefined> {
|
export async function getConfigValue(configKey: string): Promise<string | boolean | number | undefined> {
|
||||||
// Get the environment variable
|
// Get the environment variable
|
||||||
const configSchema = config.getSchema();
|
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
|
// Check if environment variable is defined for config key
|
||||||
if (currentSchema.env === undefined) {
|
if (currentSchema.env === undefined) {
|
||||||
// No environment variable defined, so return value from config
|
// 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 {
|
export function getConfigValueSync(configKey: string): string | boolean | number | undefined {
|
||||||
// Get the environment variable
|
// Get the environment variable
|
||||||
const configSchema = config.getSchema();
|
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
|
// Check if environment variable is defined for config key
|
||||||
if (currentSchema.env === undefined) {
|
if (currentSchema.env === undefined) {
|
||||||
// No environment variable defined, so return value from config
|
// 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 * as requestPromise from 'request-promise-native';
|
||||||
import { createHmac } from 'crypto';
|
import { createHmac } from 'crypto';
|
||||||
import { compare } from 'bcryptjs';
|
import { compare } from 'bcryptjs';
|
||||||
|
import * as promClient from 'prom-client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ActiveExecutions,
|
ActiveExecutions,
|
||||||
|
@ -91,9 +92,7 @@ import {
|
||||||
import {
|
import {
|
||||||
FindManyOptions,
|
FindManyOptions,
|
||||||
FindOneOptions,
|
FindOneOptions,
|
||||||
LessThan,
|
|
||||||
LessThanOrEqual,
|
LessThanOrEqual,
|
||||||
MoreThanOrEqual,
|
|
||||||
Not,
|
Not,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
|
@ -108,6 +107,7 @@ import * as parseUrl from 'parseurl';
|
||||||
import * as querystring from 'querystring';
|
import * as querystring from 'querystring';
|
||||||
import * as Queue from '../src/Queue';
|
import * as Queue from '../src/Queue';
|
||||||
import { OptionsWithUrl } from 'request-promise-native';
|
import { OptionsWithUrl } from 'request-promise-native';
|
||||||
|
import { Registry } from 'prom-client';
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
|
|
||||||
|
@ -197,6 +197,16 @@ class App {
|
||||||
|
|
||||||
async config(): Promise<void> {
|
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.versions = await GenericHelpers.getVersions();
|
||||||
this.frontendSettings.versionCli = this.versions.cli;
|
this.frontendSettings.versionCli = this.versions.cli;
|
||||||
|
|
||||||
|
@ -204,7 +214,7 @@ class App {
|
||||||
|
|
||||||
const excludeEndpoints = config.get('security.excludeEndpoints') as string;
|
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(':'));
|
ignoredEndpoints.push.apply(ignoredEndpoints, excludeEndpoints.split(':'));
|
||||||
|
|
||||||
const authIgnoreRegex = new RegExp(`^\/(${_(ignoredEndpoints).compact().join('|')})\/?.*$`);
|
const authIgnoreRegex = new RegExp(`^\/(${_(ignoredEndpoints).compact().join('|')})\/?.*$`);
|
||||||
|
@ -386,7 +396,7 @@ class App {
|
||||||
this.app.use(history({
|
this.app.use(history({
|
||||||
rewrites: [
|
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) => {
|
to: (context) => {
|
||||||
return context.parsedUrl!.pathname!.toString();
|
return context.parsedUrl!.pathname!.toString();
|
||||||
},
|
},
|
||||||
|
@ -454,7 +464,16 @@ class App {
|
||||||
ResponseHelper.sendSuccessResponse(res, responseData, true, 200);
|
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
|
// Workflow
|
||||||
|
@ -603,7 +622,7 @@ class App {
|
||||||
try {
|
try {
|
||||||
await this.externalHooks.run('workflow.activate', [responseData]);
|
await this.externalHooks.run('workflow.activate', [responseData]);
|
||||||
|
|
||||||
await this.activeWorkflowRunner.add(id);
|
await this.activeWorkflowRunner.add(id, isActive ? 'update' : 'activate');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If workflow could not be activated set it again to inactive
|
// If workflow could not be activated set it again to inactive
|
||||||
newWorkflowData.active = false;
|
newWorkflowData.active = false;
|
||||||
|
@ -649,6 +668,7 @@ class App {
|
||||||
const startNodes: string[] | undefined = req.body.startNodes;
|
const startNodes: string[] | undefined = req.body.startNodes;
|
||||||
const destinationNode: string | undefined = req.body.destinationNode;
|
const destinationNode: string | undefined = req.body.destinationNode;
|
||||||
const executionMode = 'manual';
|
const executionMode = 'manual';
|
||||||
|
const activationMode = 'manual';
|
||||||
|
|
||||||
const sessionId = GenericHelpers.getSessionId(req);
|
const sessionId = GenericHelpers.getSessionId(req);
|
||||||
|
|
||||||
|
@ -658,7 +678,7 @@ class App {
|
||||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
|
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
|
||||||
const nodeTypes = NodeTypes();
|
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 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) {
|
if (needsWebhook === true) {
|
||||||
return {
|
return {
|
||||||
waitingForWebhook: true,
|
waitingForWebhook: true,
|
||||||
|
@ -1728,7 +1748,7 @@ class App {
|
||||||
stoppedAt: result.stoppedAt ? new Date(result.stoppedAt) : undefined,
|
stoppedAt: result.stoppedAt ? new Date(result.stoppedAt) : undefined,
|
||||||
finished: result.finished,
|
finished: result.finished,
|
||||||
};
|
};
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
IWorkflowExecuteAdditionalData,
|
IWorkflowExecuteAdditionalData,
|
||||||
WebhookHttpMethod,
|
WebhookHttpMethod,
|
||||||
Workflow,
|
Workflow,
|
||||||
|
WorkflowActivateMode,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
@ -161,7 +162,7 @@ export class TestWebhooks {
|
||||||
* @returns {(Promise<IExecutionDb | undefined>)}
|
* @returns {(Promise<IExecutionDb | undefined>)}
|
||||||
* @memberof TestWebhooks
|
* @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);
|
const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData, destinationNode);
|
||||||
|
|
||||||
if (webhooks.length === 0) {
|
if (webhooks.length === 0) {
|
||||||
|
@ -193,7 +194,7 @@ export class TestWebhooks {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.activeWebhooks!.add(workflow, webhookData, mode);
|
await this.activeWebhooks!.add(workflow, webhookData, mode, activation);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
activatedKey.forEach(deleteKey => delete this.testWebhookData[deleteKey] );
|
activatedKey.forEach(deleteKey => delete this.testWebhookData[deleteKey] );
|
||||||
await this.activeWebhooks!.removeWorkflow(workflow);
|
await this.activeWebhooks!.removeWorkflow(workflow);
|
||||||
|
|
|
@ -230,54 +230,64 @@ export function hookFunctionsPreExecute(parentProcessMode?: string): IWorkflowEx
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const execution = await Db.collections.Execution!.findOne(this.executionId);
|
try {
|
||||||
|
const execution = await Db.collections.Execution!.findOne(this.executionId);
|
||||||
|
|
||||||
if (execution === undefined) {
|
if (execution === undefined) {
|
||||||
// Something went badly wrong if this happens.
|
// Something went badly wrong if this happens.
|
||||||
// This check is here mostly to make typescript happy.
|
// This check is here mostly to make typescript happy.
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const fullExecutionData: IExecutionResponse = ResponseHelper.unflattenExecutionData(execution);
|
const fullExecutionData: IExecutionResponse = ResponseHelper.unflattenExecutionData(execution);
|
||||||
|
|
||||||
if (fullExecutionData.finished) {
|
if (fullExecutionData.finished) {
|
||||||
// We already received ´workflowExecuteAfter´ webhook, so this is just an async call
|
// 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
|
// that was left behind. We skip saving because the other call should have saved everything
|
||||||
// so this one is safe to ignore
|
// so this one is safe to ignore
|
||||||
return;
|
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);
|
executeErrorWorkflow(this.workflowData, fullRunData, this.mode, undefined, this.retryOf);
|
||||||
}
|
}
|
||||||
// Data is always saved, so we remove from database
|
// Data is always saved, so we remove from database
|
||||||
Db.collections.Execution!.delete(this.executionId);
|
await Db.collections.Execution!.delete(this.executionId);
|
||||||
return;
|
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> {
|
export async function getRunData(workflowData: IWorkflowBase, inputData?: INodeExecutionData[]): Promise<IWorkflowExecutionDataProcess> {
|
||||||
const mode = 'integrated';
|
const mode = 'integrated';
|
||||||
|
|
||||||
|
@ -544,6 +625,7 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
||||||
return returnData!.data!.main;
|
return returnData!.data!.main;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
await ActiveExecutions.getInstance().remove(executionId, data);
|
||||||
// Workflow did fail
|
// Workflow did fail
|
||||||
const error = new Error(data.data.resultData.error!.message);
|
const error = new Error(data.data.resultData.error!.message);
|
||||||
error.stack = data.data.resultData.error!.stack;
|
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);
|
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
|
* 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
|
// Normally also static data should be supplied here but as it only used for sending
|
||||||
// data to editor-UI is not needed.
|
// data to editor-UI is not needed.
|
||||||
hooks.executeHookFunctions('workflowExecuteAfter', [runData]);
|
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);
|
resolve(runData);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -41,8 +41,18 @@ export class WorkflowRunnerProcess {
|
||||||
workflowExecute: WorkflowExecute | undefined;
|
workflowExecute: WorkflowExecute | undefined;
|
||||||
executionIdCallback: (executionId: string) => void | 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> {
|
async runWorkflow(inputData: IWorkflowExecutionDataProcessWithExecution): Promise<IRun> {
|
||||||
|
process.on('SIGTERM', WorkflowRunnerProcess.stopProcess);
|
||||||
|
process.on('SIGINT', WorkflowRunnerProcess.stopProcess);
|
||||||
|
|
||||||
this.data = inputData;
|
this.data = inputData;
|
||||||
let className: string;
|
let className: string;
|
||||||
let tempNode: INodeType;
|
let tempNode: INodeType;
|
||||||
|
@ -111,7 +121,15 @@ export class WorkflowRunnerProcess {
|
||||||
resolve(executionId);
|
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 });
|
await sendToParentProcess('finishExecution', { executionId, result });
|
||||||
|
|
||||||
const returnData = WorkflowHelpers.getDataLastExecutedNodeData(result);
|
const returnData = WorkflowHelpers.getDataLastExecutedNodeData(result);
|
||||||
|
@ -174,8 +192,8 @@ export class WorkflowRunnerProcess {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
nodeExecuteAfter: [
|
nodeExecuteAfter: [
|
||||||
async (nodeName: string, data: ITaskData, executionData: IRunExecutionData): Promise<void> => {
|
async (nodeName: string, data: ITaskData): Promise<void> => {
|
||||||
this.sendHookToParentProcess('nodeExecuteAfter', [nodeName, data, executionData]);
|
this.sendHookToParentProcess('nodeExecuteAfter', [nodeName, data]);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
workflowExecuteBefore: [
|
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 { CreateIndexStoppedAt1594902918301 } from './1594902918301-CreateIndexStoppedAt';
|
||||||
import { AddWebhookId1611149998770 } from './1611149998770-AddWebhookId';
|
import { AddWebhookId1611149998770 } from './1611149998770-AddWebhookId';
|
||||||
import { MakeStoppedAtNullable1607431743767 } from './1607431743767-MakeStoppedAtNullable';
|
import { MakeStoppedAtNullable1607431743767 } from './1607431743767-MakeStoppedAtNullable';
|
||||||
|
import { ChangeDataSize1615306975123 } from './1615306975123-ChangeDataSize';
|
||||||
import { CreateTagEntity1617268711084 } from './1617268711084-CreateTagEntity';
|
import { CreateTagEntity1617268711084 } from './1617268711084-CreateTagEntity';
|
||||||
|
|
||||||
export const mysqlMigrations = [
|
export const mysqlMigrations = [
|
||||||
|
@ -11,5 +12,6 @@ export const mysqlMigrations = [
|
||||||
CreateIndexStoppedAt1594902918301,
|
CreateIndexStoppedAt1594902918301,
|
||||||
AddWebhookId1611149998770,
|
AddWebhookId1611149998770,
|
||||||
MakeStoppedAtNullable1607431743767,
|
MakeStoppedAtNullable1607431743767,
|
||||||
|
ChangeDataSize1615306975123,
|
||||||
CreateTagEntity1617268711084,
|
CreateTagEntity1617268711084,
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,74 +1,74 @@
|
||||||
{
|
{
|
||||||
"name": "n8n-core",
|
"name": "n8n-core",
|
||||||
"version": "0.64.0",
|
"version": "0.67.0",
|
||||||
"description": "Core functionality of n8n",
|
"description": "Core functionality of n8n",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"homepage": "https://n8n.io",
|
"homepage": "https://n8n.io",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Jan Oberhauser",
|
"name": "Jan Oberhauser",
|
||||||
"email": "jan@n8n.io"
|
"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": {
|
"testURL": "http://localhost/",
|
||||||
"type": "git",
|
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||||
"url": "git+https://github.com/n8n-io/n8n.git"
|
"testPathIgnorePatterns": [
|
||||||
},
|
"/dist/",
|
||||||
"main": "dist/src/index",
|
"/node_modules/"
|
||||||
"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": {
|
"moduleFileExtensions": [
|
||||||
"@types/cron": "^1.7.1",
|
"ts",
|
||||||
"@types/crypto-js": "^4.0.1",
|
"tsx",
|
||||||
"@types/express": "^4.17.6",
|
"js",
|
||||||
"@types/jest": "^26.0.13",
|
"json",
|
||||||
"@types/lodash.get": "^4.4.6",
|
"node"
|
||||||
"@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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {
|
||||||
IWebhookData,
|
IWebhookData,
|
||||||
WebhookHttpMethod,
|
WebhookHttpMethod,
|
||||||
Workflow,
|
Workflow,
|
||||||
|
WorkflowActivateMode,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ export class ActiveWebhooks {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
* @memberof ActiveWebhooks
|
* @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) {
|
if (workflow.id === undefined) {
|
||||||
throw new Error('Webhooks can only be added for saved workflows as an id is needed!');
|
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);
|
this.webhookUrls[webhookKey].push(webhookData);
|
||||||
|
|
||||||
try {
|
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 (webhookExists !== true) {
|
||||||
// If webhook does not exist yet create it
|
// 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) {
|
} catch (error) {
|
||||||
|
@ -183,7 +184,7 @@ export class ActiveWebhooks {
|
||||||
|
|
||||||
// Go through all the registered webhooks of the workflow and remove them
|
// Go through all the registered webhooks of the workflow and remove them
|
||||||
for (const webhookData of webhooks) {
|
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)];
|
delete this.webhookUrls[this.getWebhookKey(webhookData.httpMethod, webhookData.path, webhookData.webhookId)];
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ import {
|
||||||
ITriggerResponse,
|
ITriggerResponse,
|
||||||
IWorkflowExecuteAdditionalData,
|
IWorkflowExecuteAdditionalData,
|
||||||
Workflow,
|
Workflow,
|
||||||
|
WorkflowActivateMode,
|
||||||
|
WorkflowExecuteMode,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -66,14 +68,14 @@ export class ActiveWorkflows {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
* @memberof ActiveWorkflows
|
* @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] = {};
|
this.workflowData[id] = {};
|
||||||
const triggerNodes = workflow.getTriggerNodes();
|
const triggerNodes = workflow.getTriggerNodes();
|
||||||
|
|
||||||
let triggerResponse: ITriggerResponse | undefined;
|
let triggerResponse: ITriggerResponse | undefined;
|
||||||
this.workflowData[id].triggerResponses = [];
|
this.workflowData[id].triggerResponses = [];
|
||||||
for (const triggerNode of triggerNodes) {
|
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 (triggerResponse !== undefined) {
|
||||||
// If a response was given save it
|
// If a response was given save it
|
||||||
this.workflowData[id].triggerResponses!.push(triggerResponse);
|
this.workflowData[id].triggerResponses!.push(triggerResponse);
|
||||||
|
@ -84,7 +86,7 @@ export class ActiveWorkflows {
|
||||||
if (pollNodes.length) {
|
if (pollNodes.length) {
|
||||||
this.workflowData[id].pollResponses = [];
|
this.workflowData[id].pollResponses = [];
|
||||||
for (const pollNode of pollNodes) {
|
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>}
|
* @returns {Promise<IPollResponse>}
|
||||||
* @memberof ActiveWorkflows
|
* @memberof ActiveWorkflows
|
||||||
*/
|
*/
|
||||||
async activatePolling(node: INode, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, getPollFunctions: IGetExecutePollFunctions): Promise<IPollResponse> {
|
async activatePolling(node: INode, workflow: Workflow, additionalData: IWorkflowExecuteAdditionalData, getPollFunctions: IGetExecutePollFunctions, mode: WorkflowExecuteMode, activation: WorkflowActivateMode): Promise<IPollResponse> {
|
||||||
const mode = 'trigger';
|
const pollFunctions = getPollFunctions(workflow, node, additionalData, mode, activation);
|
||||||
|
|
||||||
const pollFunctions = getPollFunctions(workflow, node, additionalData, mode);
|
|
||||||
|
|
||||||
const pollTimes = pollFunctions.getNodeParameter('pollTimes') as unknown as {
|
const pollTimes = pollFunctions.getNodeParameter('pollTimes') as unknown as {
|
||||||
item: ITriggerTime[];
|
item: ITriggerTime[];
|
||||||
|
|
|
@ -34,6 +34,7 @@ import {
|
||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
NodeParameterValue,
|
NodeParameterValue,
|
||||||
Workflow,
|
Workflow,
|
||||||
|
WorkflowActivateMode,
|
||||||
WorkflowDataProxy,
|
WorkflowDataProxy,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
@ -103,6 +104,9 @@ export async function prepareBinaryData(binaryData: Buffer, filePath?: string, m
|
||||||
|
|
||||||
const filePathParts = path.parse(filePath as string);
|
const filePathParts = path.parse(filePath as string);
|
||||||
|
|
||||||
|
if (filePathParts.dir !== '') {
|
||||||
|
returnData.directory = filePathParts.dir;
|
||||||
|
}
|
||||||
returnData.fileName = filePathParts.base;
|
returnData.fileName = filePathParts.base;
|
||||||
|
|
||||||
// Remove the dot
|
// Remove the dot
|
||||||
|
@ -532,7 +536,7 @@ export function getWorkflowMetadata(workflow: Workflow): IWorkflowMetadata {
|
||||||
* @returns {ITriggerFunctions}
|
* @returns {ITriggerFunctions}
|
||||||
*/
|
*/
|
||||||
// TODO: Check if I can get rid of: additionalData, and so then maybe also at ActiveWorkflowRunner.add
|
// 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 ((workflow: Workflow, node: INode) => {
|
||||||
return {
|
return {
|
||||||
__emit: (data: INodeExecutionData[][]): void => {
|
__emit: (data: INodeExecutionData[][]): void => {
|
||||||
|
@ -544,6 +548,9 @@ export function getExecutePollFunctions(workflow: Workflow, node: INode, additio
|
||||||
getMode: (): WorkflowExecuteMode => {
|
getMode: (): WorkflowExecuteMode => {
|
||||||
return mode;
|
return mode;
|
||||||
},
|
},
|
||||||
|
getActivationMode: (): WorkflowActivateMode => {
|
||||||
|
return activation;
|
||||||
|
},
|
||||||
getNode: () => {
|
getNode: () => {
|
||||||
return getNode(node);
|
return getNode(node);
|
||||||
},
|
},
|
||||||
|
@ -595,7 +602,7 @@ export function getExecutePollFunctions(workflow: Workflow, node: INode, additio
|
||||||
* @returns {ITriggerFunctions}
|
* @returns {ITriggerFunctions}
|
||||||
*/
|
*/
|
||||||
// TODO: Check if I can get rid of: additionalData, and so then maybe also at ActiveWorkflowRunner.add
|
// 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 ((workflow: Workflow, node: INode) => {
|
||||||
return {
|
return {
|
||||||
emit: (data: INodeExecutionData[][]): void => {
|
emit: (data: INodeExecutionData[][]): void => {
|
||||||
|
@ -610,6 +617,9 @@ export function getExecuteTriggerFunctions(workflow: Workflow, node: INode, addi
|
||||||
getMode: (): WorkflowExecuteMode => {
|
getMode: (): WorkflowExecuteMode => {
|
||||||
return mode;
|
return mode;
|
||||||
},
|
},
|
||||||
|
getActivationMode: (): WorkflowActivateMode => {
|
||||||
|
return activation;
|
||||||
|
},
|
||||||
getNodeParameter: (parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object => { //tslint:disable-line:no-any
|
getNodeParameter: (parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object => { //tslint:disable-line:no-any
|
||||||
const runExecutionData: IRunExecutionData | null = null;
|
const runExecutionData: IRunExecutionData | null = null;
|
||||||
const itemIndex = 0;
|
const itemIndex = 0;
|
||||||
|
@ -907,7 +917,7 @@ export function getLoadOptionsFunctions(workflow: Workflow, node: INode, additio
|
||||||
* @param {WorkflowExecuteMode} mode
|
* @param {WorkflowExecuteMode} mode
|
||||||
* @returns {IHookFunctions}
|
* @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) => {
|
return ((workflow: Workflow, node: INode) => {
|
||||||
const that = {
|
const that = {
|
||||||
getCredentials(type: string): ICredentialDataDecryptedObject | undefined {
|
getCredentials(type: string): ICredentialDataDecryptedObject | undefined {
|
||||||
|
@ -916,6 +926,9 @@ export function getExecuteHookFunctions(workflow: Workflow, node: INode, additio
|
||||||
getMode: (): WorkflowExecuteMode => {
|
getMode: (): WorkflowExecuteMode => {
|
||||||
return mode;
|
return mode;
|
||||||
},
|
},
|
||||||
|
getActivationMode: (): WorkflowActivateMode => {
|
||||||
|
return activation;
|
||||||
|
},
|
||||||
getNode: () => {
|
getNode: () => {
|
||||||
return getNode(node);
|
return getNode(node);
|
||||||
},
|
},
|
||||||
|
|
|
@ -557,7 +557,7 @@ export class WorkflowExecute {
|
||||||
executionData = this.runExecutionData.executionData!.nodeExecutionStack.shift() as IExecuteData;
|
executionData = this.runExecutionData.executionData!.nodeExecutionStack.shift() as IExecuteData;
|
||||||
executionNode = executionData.node;
|
executionNode = executionData.node;
|
||||||
|
|
||||||
this.executeHook('nodeExecuteBefore', [executionNode.name]);
|
await this.executeHook('nodeExecuteBefore', [executionNode.name]);
|
||||||
|
|
||||||
// Get the index of the current run
|
// Get the index of the current run
|
||||||
runIndex = 0;
|
runIndex = 0;
|
||||||
|
@ -722,7 +722,7 @@ export class WorkflowExecute {
|
||||||
// Add the execution data again so that it can get restarted
|
// Add the execution data again so that it can get restarted
|
||||||
this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionData);
|
this.runExecutionData.executionData!.nodeExecutionStack.unshift(executionData);
|
||||||
|
|
||||||
this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
await this.executeHook('nodeExecuteAfter', [executionNode.name, taskData, this.runExecutionData]);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,3 +6,10 @@ indent_style = tab
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = 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",
|
"name": "n8n-editor-ui",
|
||||||
"version": "0.80.0",
|
"version": "0.84.0",
|
||||||
"description": "Workflow Editor UI for n8n",
|
"description": "Workflow Editor UI for n8n",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"homepage": "https://n8n.io",
|
"homepage": "https://n8n.io",
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.set": "^4.3.2",
|
"lodash.set": "^4.3.2",
|
||||||
"n8n-workflow": "~0.53.0",
|
"n8n-workflow": "~0.55.0",
|
||||||
"node-sass": "^4.12.0",
|
"node-sass": "^4.12.0",
|
||||||
"normalize-wheel": "^1.0.1",
|
"normalize-wheel": "^1.0.1",
|
||||||
"prismjs": "^1.17.1",
|
"prismjs": "^1.17.1",
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
{{parameter.displayName}}:
|
{{parameter.displayName}}:
|
||||||
</div>
|
</div>
|
||||||
<div class="text-editor" @keydown.stop>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
@ -14,42 +14,43 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import PrismEditor from 'vue-prism-editor';
|
import PrismEditor from 'vue-prism-editor';
|
||||||
|
|
||||||
import {
|
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||||
Workflow,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
import mixins from 'vue-typed-mixins';
|
||||||
name: 'CodeEdit',
|
|
||||||
props: [
|
|
||||||
'dialogVisible',
|
|
||||||
'parameter',
|
|
||||||
'value',
|
|
||||||
],
|
|
||||||
components: {
|
|
||||||
PrismEditor,
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
valueChanged (value: string) {
|
|
||||||
this.$emit('valueChanged', value);
|
|
||||||
},
|
|
||||||
|
|
||||||
closeDialog () {
|
export default mixins(
|
||||||
// Handle the close externally as the visible parameter is an external prop
|
genericHelpers,
|
||||||
// and is so not allowed to be changed here.
|
)
|
||||||
this.$emit('closeDialog');
|
.extend({
|
||||||
return false;
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -175,6 +175,10 @@ import {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
range as _range,
|
||||||
|
} from 'lodash';
|
||||||
|
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
|
|
||||||
export default mixins(
|
export default mixins(
|
||||||
|
@ -433,8 +437,24 @@ export default mixins(
|
||||||
this.$store.commit('setActiveExecutions', results[1]);
|
this.$store.commit('setActiveExecutions', results[1]);
|
||||||
|
|
||||||
const alreadyPresentExecutionIds = this.finishedExecutions.map(exec => exec.id);
|
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--) {
|
for(let i = results[0].results.length - 1; i >= 0; i--) {
|
||||||
const currentItem = results[0].results[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
|
// Check new results from end to start
|
||||||
// Add new items accordingly.
|
// Add new items accordingly.
|
||||||
const executionIndex = alreadyPresentExecutionIds.indexOf(currentItem.id);
|
const executionIndex = alreadyPresentExecutionIds.indexOf(currentItem.id);
|
||||||
|
@ -464,6 +484,7 @@ export default mixins(
|
||||||
this.finishedExecutions.unshift(currentItem);
|
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;
|
this.finishedExecutionsCount = results[0].count;
|
||||||
},
|
},
|
||||||
async loadFinishedExecutions (): Promise<void> {
|
async loadFinishedExecutions (): Promise<void> {
|
||||||
|
|
|
@ -12,23 +12,22 @@ import 'quill/dist/quill.core.css';
|
||||||
|
|
||||||
import Quill, { DeltaOperation } from 'quill';
|
import Quill, { DeltaOperation } from 'quill';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import AutoFormat, { AutoformatHelperAttribute } from 'quill-autoformat';
|
import AutoFormat from 'quill-autoformat';
|
||||||
import {
|
import {
|
||||||
NodeParameterValue,
|
NodeParameterValue,
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowDataProxy,
|
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IExecutionResponse,
|
|
||||||
IVariableItemSelected,
|
IVariableItemSelected,
|
||||||
IVariableSelectorOption,
|
|
||||||
} from '@/Interface';
|
} from '@/Interface';
|
||||||
|
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||||
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
||||||
|
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
|
|
||||||
export default mixins(
|
export default mixins(
|
||||||
|
genericHelpers,
|
||||||
workflowHelpers,
|
workflowHelpers,
|
||||||
)
|
)
|
||||||
.extend({
|
.extend({
|
||||||
|
@ -119,7 +118,7 @@ export default mixins(
|
||||||
};
|
};
|
||||||
|
|
||||||
this.editor = new Quill(this.$refs['expression-editor'] as Element, {
|
this.editor = new Quill(this.$refs['expression-editor'] as Element, {
|
||||||
readOnly: !!this.resolvedValue,
|
readOnly: !!this.resolvedValue || this.isReadOnly,
|
||||||
modules: {
|
modules: {
|
||||||
autoformat: {},
|
autoformat: {},
|
||||||
keyboard: {
|
keyboard: {
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<prism-editor v-if="!codeEditDialogVisible" :lineNumbers="true" :readonly="true" :code="displayValue" language="js"></prism-editor>
|
<prism-editor v-if="!codeEditDialogVisible" :lineNumbers="true" :readonly="true" :code="displayValue" language="js"></prism-editor>
|
||||||
</div>
|
</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()" />
|
<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>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
|
@ -523,9 +523,6 @@ export default mixins(
|
||||||
this.valueChanged(value);
|
this.valueChanged(value);
|
||||||
},
|
},
|
||||||
setFocus () {
|
setFocus () {
|
||||||
if (this.isReadOnly === true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.isValueExpression) {
|
if (this.isValueExpression) {
|
||||||
this.expressionEditDialogVisible = true;
|
this.expressionEditDialogVisible = true;
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -157,6 +157,10 @@
|
||||||
<div class="label">File Name: </div>
|
<div class="label">File Name: </div>
|
||||||
<div class="value">{{binaryData.fileName}}</div>
|
<div class="value">{{binaryData.fileName}}</div>
|
||||||
</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 v-if="binaryData.fileExtension">
|
||||||
<div class="label">File Extension:</div>
|
<div class="label">File Extension:</div>
|
||||||
<div class="value">{{binaryData.fileExtension}}</div>
|
<div class="value">{{binaryData.fileExtension}}</div>
|
||||||
|
|
|
@ -114,6 +114,9 @@ export default mixins(
|
||||||
// Has still options left so return
|
// Has still options left so return
|
||||||
inputData.options = this.sortOptions(newOptions);
|
inputData.options = this.sortOptions(newOptions);
|
||||||
return inputData;
|
return inputData;
|
||||||
|
} else if (Array.isArray(newOptions) && newOptions.length === 0) {
|
||||||
|
delete inputData.options;
|
||||||
|
return inputData;
|
||||||
}
|
}
|
||||||
// Has no options left so remove
|
// Has no options left so remove
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -1,70 +1,70 @@
|
||||||
{
|
{
|
||||||
"name": "n8n-node-dev",
|
"name": "n8n-node-dev",
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"description": "CLI to simplify n8n credentials/node development",
|
"description": "CLI to simplify n8n credentials/node development",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"homepage": "https://n8n.io",
|
"homepage": "https://n8n.io",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Jan Oberhauser",
|
"name": "Jan Oberhauser",
|
||||||
"email": "jan@n8n.io"
|
"email": "jan@n8n.io"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/n8n-io/n8n.git"
|
"url": "git+https://github.com/n8n-io/n8n.git"
|
||||||
},
|
},
|
||||||
"main": "dist/src/index",
|
"main": "dist/src/index",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
"oclif": {
|
"oclif": {
|
||||||
"commands": "./dist/commands",
|
"commands": "./dist/commands",
|
||||||
"bin": "n8n-node-dev"
|
"bin": "n8n-node-dev"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm run watch",
|
"dev": "npm run watch",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"postpack": "rm -f oclif.manifest.json",
|
"postpack": "rm -f oclif.manifest.json",
|
||||||
"prepack": "echo \"Building project...\" && rm -rf dist && tsc -b && oclif-dev manifest",
|
"prepack": "echo \"Building project...\" && rm -rf dist && tsc -b && oclif-dev manifest",
|
||||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||||
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
"tslintfix": "tslint --fix -p tsconfig.json -c tslint.json",
|
||||||
"watch": "tsc --watch"
|
"watch": "tsc --watch"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"n8n-node-dev": "./bin/n8n-node-dev"
|
"n8n-node-dev": "./bin/n8n-node-dev"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"development",
|
"development",
|
||||||
"node",
|
"node",
|
||||||
"helper",
|
"helper",
|
||||||
"n8n"
|
"n8n"
|
||||||
],
|
],
|
||||||
"files": [
|
"files": [
|
||||||
"bin",
|
"bin",
|
||||||
"dist",
|
"dist",
|
||||||
"templates",
|
"templates",
|
||||||
"oclif.manifest.json",
|
"oclif.manifest.json",
|
||||||
"src/tsconfig-build.json"
|
"src/tsconfig-build.json"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@oclif/dev-cli": "^1.22.2",
|
"@oclif/dev-cli": "^1.22.2",
|
||||||
"@types/copyfiles": "^2.1.1",
|
"@types/copyfiles": "^2.1.1",
|
||||||
"@types/inquirer": "^6.5.0",
|
"@types/inquirer": "^6.5.0",
|
||||||
"@types/tmp": "^0.1.0",
|
"@types/tmp": "^0.1.0",
|
||||||
"@types/vorpal": "^1.11.0",
|
"@types/vorpal": "^1.11.0",
|
||||||
"tslint": "^6.1.2"
|
"tslint": "^6.1.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oclif/command": "^1.5.18",
|
"@oclif/command": "^1.5.18",
|
||||||
"@oclif/errors": "^1.2.2",
|
"@oclif/errors": "^1.2.2",
|
||||||
"@types/express": "^4.17.6",
|
"@types/express": "^4.17.6",
|
||||||
"@types/node": "14.0.27",
|
"@types/node": "14.0.27",
|
||||||
"change-case": "^4.1.1",
|
"change-case": "^4.1.1",
|
||||||
"copyfiles": "^2.1.1",
|
"copyfiles": "^2.1.1",
|
||||||
"inquirer": "^7.0.1",
|
"inquirer": "^7.0.1",
|
||||||
"n8n-core": "^0.48.0",
|
"n8n-core": "^0.48.0",
|
||||||
"n8n-workflow": "^0.42.0",
|
"n8n-workflow": "^0.42.0",
|
||||||
"oauth-1.0a": "^2.2.6",
|
"oauth-1.0a": "^2.2.6",
|
||||||
"replace-in-file": "^6.0.0",
|
"replace-in-file": "^6.0.0",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"tmp-promise": "^2.0.2",
|
"tmp-promise": "^2.0.2",
|
||||||
"typescript": "~3.9.7"
|
"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: '',
|
default: '',
|
||||||
placeholder: 'https://email.{region}.amazonaws.com',
|
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',
|
displayName: 'S3 Endpoint',
|
||||||
name: 's3Endpoint',
|
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,
|
type: 'string' as NodePropertyTypes,
|
||||||
default: '',
|
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.write',
|
||||||
'files.content.read',
|
'files.content.read',
|
||||||
'sharing.read',
|
'sharing.read',
|
||||||
|
'account_info.read',
|
||||||
];
|
];
|
||||||
|
|
||||||
export class DropboxOAuth2Api implements ICredentialType {
|
export class DropboxOAuth2Api implements ICredentialType {
|
||||||
|
@ -41,7 +42,7 @@ export class DropboxOAuth2Api implements ICredentialType {
|
||||||
displayName: 'Auth URI Query Parameters',
|
displayName: 'Auth URI Query Parameters',
|
||||||
name: 'authQueryParameters',
|
name: 'authQueryParameters',
|
||||||
type: 'hidden' as NodePropertyTypes,
|
type: 'hidden' as NodePropertyTypes,
|
||||||
default: 'token_access_type=offline',
|
default: 'token_access_type=offline&force_reapprove=true',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Authentication',
|
displayName: 'Authentication',
|
||||||
|
@ -49,5 +50,21 @@ export class DropboxOAuth2Api implements ICredentialType {
|
||||||
type: 'hidden' as NodePropertyTypes,
|
type: 'hidden' as NodePropertyTypes,
|
||||||
default: 'header',
|
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,
|
type: 'boolean' as NodePropertyTypes,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Authentication',
|
||||||
|
name: 'authentication',
|
||||||
|
type: 'boolean' as NodePropertyTypes,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Username',
|
displayName: 'Username',
|
||||||
name: 'username',
|
name: 'username',
|
||||||
type: 'string' as NodePropertyTypes,
|
type: 'string' as NodePropertyTypes,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
authentication: [
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
default: '',
|
default: '',
|
||||||
description: 'Optional username if authenticated is required.',
|
description: 'Optional username if authenticated is required.',
|
||||||
},
|
},
|
||||||
|
@ -39,11 +52,46 @@ export class Kafka implements ICredentialType {
|
||||||
displayName: 'Password',
|
displayName: 'Password',
|
||||||
name: 'password',
|
name: 'password',
|
||||||
type: 'string' as NodePropertyTypes,
|
type: 'string' as NodePropertyTypes,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
authentication: [
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
typeOptions: {
|
typeOptions: {
|
||||||
password: true,
|
password: true,
|
||||||
},
|
},
|
||||||
default: '',
|
default: '',
|
||||||
description: 'Optional password if authenticated is required.',
|
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',
|
displayName: 'Scope',
|
||||||
name: 'scope',
|
name: 'scope',
|
||||||
type: 'hidden' as NodePropertyTypes,
|
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',
|
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
|
// delete
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
{
|
||||||
displayName: 'Id',
|
displayName: 'ID',
|
||||||
name: 'id',
|
name: 'id',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
|
@ -317,7 +317,7 @@ export class Airtable implements INodeType {
|
||||||
// read
|
// read
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
{
|
||||||
displayName: 'Id',
|
displayName: 'ID',
|
||||||
name: 'id',
|
name: 'id',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
|
@ -336,7 +336,7 @@ export class Airtable implements INodeType {
|
||||||
// update
|
// update
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
{
|
{
|
||||||
displayName: 'Id',
|
displayName: 'ID',
|
||||||
name: 'id',
|
name: 'id',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
|
@ -499,7 +499,7 @@ export class Airtable implements INodeType {
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
id = this.getNodeParameter('id', i) as string;
|
id = this.getNodeParameter('id', i) as string;
|
||||||
|
|
||||||
endpoint = `${application}/${table}/${id}`;
|
endpoint = `${application}/${table}`;
|
||||||
|
|
||||||
// Make one request after another. This is slower but makes
|
// Make one request after another. This is slower but makes
|
||||||
// sure that we do not run into the rate limit they have in
|
// 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
|
// functionality in core should make it easy to make requests
|
||||||
// according to specific rules like not more than 5 requests
|
// according to specific rules like not more than 5 requests
|
||||||
// per seconds.
|
// per seconds.
|
||||||
|
qs.records = [id];
|
||||||
|
|
||||||
responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
returnData.push(responseData);
|
returnData.push(...responseData.records);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (operation === 'list') {
|
} else if (operation === 'list') {
|
||||||
|
@ -586,7 +588,6 @@ export class Airtable implements INodeType {
|
||||||
let updateAllFields: boolean;
|
let updateAllFields: boolean;
|
||||||
let fields: string[];
|
let fields: string[];
|
||||||
let options: IDataObject;
|
let options: IDataObject;
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
updateAllFields = this.getNodeParameter('updateAllFields', i) as boolean;
|
updateAllFields = this.getNodeParameter('updateAllFields', i) as boolean;
|
||||||
options = this.getNodeParameter('options', i, {}) as IDataObject;
|
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;
|
id = this.getNodeParameter('id', i) as string;
|
||||||
|
|
||||||
endpoint = `${application}/${table}/${id}`;
|
endpoint = `${application}/${table}`;
|
||||||
|
|
||||||
// Make one request after another. This is slower but makes
|
// Make one request after another. This is slower but makes
|
||||||
// sure that we do not run into the rate limit they have in
|
// 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
|
// according to specific rules like not more than 5 requests
|
||||||
// per seconds.
|
// 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 {
|
} else {
|
||||||
|
|
|
@ -58,6 +58,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa
|
||||||
body,
|
body,
|
||||||
qs: query,
|
qs: query,
|
||||||
uri: uri || `https://api.airtable.com/v0/${endpoint}`,
|
uri: uri || `https://api.airtable.com/v0/${endpoint}`,
|
||||||
|
useQuerystring: false,
|
||||||
json: true,
|
json: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ export class Asana implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'Asana',
|
displayName: 'Asana',
|
||||||
name: 'asana',
|
name: 'asana',
|
||||||
icon: 'file:asana.png',
|
icon: 'file:asana.svg',
|
||||||
group: ['input'],
|
group: ['input'],
|
||||||
version: 1,
|
version: 1,
|
||||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
|
|
|
@ -25,7 +25,7 @@ export class AsanaTrigger implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'Asana Trigger',
|
displayName: 'Asana Trigger',
|
||||||
name: 'asanaTrigger',
|
name: 'asanaTrigger',
|
||||||
icon: 'file:asana.png',
|
icon: 'file:asana.svg',
|
||||||
group: ['trigger'],
|
group: ['trigger'],
|
||||||
version: 1,
|
version: 1,
|
||||||
description: 'Starts the workflow when Asana events occure.',
|
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;
|
endpoint = credentials.lambdaEndpoint;
|
||||||
} else if (service === 'sns' && credentials.snsEndpoint) {
|
} else if (service === 'sns' && credentials.snsEndpoint) {
|
||||||
endpoint = credentials.snsEndpoint;
|
endpoint = credentials.snsEndpoint;
|
||||||
|
} else if (service === 'sqs' && credentials.sqsEndpoint) {
|
||||||
|
endpoint = credentials.sqsEndpoint;
|
||||||
} else {
|
} else {
|
||||||
endpoint = `https://${service}.${credentials.region}.amazonaws.com`;
|
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[]);
|
setParameter(params, 'Destination.BccAddresses.member', additionalFields.bccAddresses as string[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (additionalFields.ccAddressesUi) {
|
if (additionalFields.ccAddresses) {
|
||||||
let ccAddresses = (additionalFields.ccAddressesUi as IDataObject).ccAddressesValues as string[];
|
setParameter(params, 'Destination.CcAddresses.member', additionalFields.ccAddresses as string[]);
|
||||||
//@ts-ignore
|
|
||||||
ccAddresses = ccAddresses.map(o => o.address);
|
|
||||||
if (ccAddresses) {
|
|
||||||
setParameter(params, 'Destination.CcAddresses.member', ccAddresses);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
responseData = await awsApiRequestSOAP.call(this, 'email', 'POST', '/?Action=SendEmail&' + params.join('&'));
|
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[]);
|
setParameter(params, 'Destination.BccAddresses.member', additionalFields.bccAddresses as string[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (additionalFields.ccAddressesUi) {
|
if (additionalFields.ccAddresses) {
|
||||||
let ccAddresses = (additionalFields.ccAddressesUi as IDataObject).ccAddressesValues as string[];
|
setParameter(params, 'Destination.CcAddresses.member', additionalFields.ccAddresses as string[]);
|
||||||
//@ts-ignore
|
|
||||||
ccAddresses = ccAddresses.map(o => o.address);
|
|
||||||
if (ccAddresses) {
|
|
||||||
setParameter(params, 'Destination.CcAddresses.member', ccAddresses);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (templateDataUi) {
|
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
|
// group: update
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
const body = {} as IDataObject;
|
const groupId = this.getNodeParameter('groupId', i);
|
||||||
|
|
||||||
const updateFields = this.getNodeParameter('updateFields', i) as GroupUpdateFields;
|
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}.`);
|
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) {
|
if (collections) {
|
||||||
body.collections = collections.map((collectionId) => ({
|
body.collections = collections.map((collectionId) => ({
|
||||||
|
@ -320,20 +338,11 @@ export class Bitwarden implements INodeType {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name) {
|
|
||||||
body.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (externalId) {
|
if (externalId) {
|
||||||
body.externalId = externalId;
|
body.externalId = externalId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accessAll !== undefined) {
|
const endpoint = `/public/groups/${groupId}`;
|
||||||
body.AccessAll = accessAll;
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = this.getNodeParameter('groupId', i);
|
|
||||||
const endpoint = `/public/groups/${id}`;
|
|
||||||
responseData = await bitwardenApiRequest.call(this, 'PUT', endpoint, {}, body);
|
responseData = await bitwardenApiRequest.call(this, 'PUT', endpoint, {}, body);
|
||||||
|
|
||||||
} else if (operation === 'updateMembers') {
|
} else if (operation === 'updateMembers') {
|
||||||
|
|
|
@ -157,9 +157,9 @@ export async function loadResource(
|
||||||
|
|
||||||
const { data } = await bitwardenApiRequest.call(this, 'GET', endpoint, {}, {}, token);
|
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({
|
returnData.push({
|
||||||
name: name || id,
|
name: externalId || name || id,
|
||||||
value: id,
|
value: id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -203,12 +203,12 @@ export const groupFields = [
|
||||||
placeholder: 'Add Field',
|
placeholder: 'Add Field',
|
||||||
default: {},
|
default: {},
|
||||||
options: [
|
options: [
|
||||||
|
|
||||||
{
|
{
|
||||||
displayName: 'Access All',
|
displayName: 'Access All',
|
||||||
name: 'accessAll',
|
name: 'accessAll',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default: false,
|
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',
|
displayName: 'Collections',
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
import {
|
import {
|
||||||
IExecuteFunctions,
|
IExecuteFunctions,
|
||||||
} from 'n8n-core';
|
} from 'n8n-core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
INodeType,
|
INodeType,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
clearbitApiRequest,
|
clearbitApiRequest,
|
||||||
} from './GenericFunctions';
|
} from './GenericFunctions';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
companyFields,
|
companyFields,
|
||||||
companyOperations,
|
companyOperations,
|
||||||
} from './CompanyDescription';
|
} from './CompanyDescription';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
personFields,
|
personFields,
|
||||||
personOperations,
|
personOperations,
|
||||||
|
@ -23,7 +27,7 @@ export class Clearbit implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'Clearbit',
|
displayName: 'Clearbit',
|
||||||
name: 'clearbit',
|
name: 'clearbit',
|
||||||
icon: 'file:clearbit.png',
|
icon: 'file:clearbit.svg',
|
||||||
group: ['output'],
|
group: ['output'],
|
||||||
version: 1,
|
version: 1,
|
||||||
subtitle: '={{$parameter["operation"] + ":" + $parameter["resource"]}}',
|
subtitle: '={{$parameter["operation"] + ":" + $parameter["resource"]}}',
|
||||||
|
@ -109,7 +113,7 @@ export class Clearbit implements INodeType {
|
||||||
if (additionalFields.facebook) {
|
if (additionalFields.facebook) {
|
||||||
qs.facebook = additionalFields.facebook as string;
|
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') {
|
if (resource === 'company') {
|
||||||
|
@ -129,7 +133,7 @@ export class Clearbit implements INodeType {
|
||||||
if (additionalFields.facebook) {
|
if (additionalFields.facebook) {
|
||||||
qs.facebook = additionalFields.facebook as string;
|
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') {
|
if (operation === 'autocomplete') {
|
||||||
const name = this.getNodeParameter('name', i) as string;
|
const name = this.getNodeParameter('name', i) as string;
|
||||||
|
|
|
@ -13,16 +13,16 @@ export const companyOperations = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
{
|
|
||||||
name: 'Enrich',
|
|
||||||
value: 'enrich',
|
|
||||||
description: 'Look up person and company data based on an email or domain',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'Autocomplete',
|
name: 'Autocomplete',
|
||||||
value: 'autocomplete',
|
value: 'autocomplete',
|
||||||
description: 'Auto-complete company names and retrieve logo and domain',
|
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',
|
default: 'enrich',
|
||||||
description: 'The operation to perform.',
|
description: 'The operation to perform.',
|
||||||
|
@ -31,9 +31,9 @@ export const companyOperations = [
|
||||||
|
|
||||||
export const companyFields = [
|
export const companyFields = [
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* company:enrich */
|
/* company:enrich */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Domain',
|
displayName: 'Domain',
|
||||||
name: 'domain',
|
name: 'domain',
|
||||||
|
@ -99,25 +99,26 @@ export const companyFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* company:autocomplete */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* company:autocomplete */
|
||||||
{
|
/* -------------------------------------------------------------------------- */
|
||||||
displayName: 'Name',
|
{
|
||||||
name: 'name',
|
displayName: 'Name',
|
||||||
type: 'string',
|
name: 'name',
|
||||||
default: '',
|
type: 'string',
|
||||||
required: true,
|
default: '',
|
||||||
displayOptions: {
|
required: true,
|
||||||
show: {
|
displayOptions: {
|
||||||
resource: [
|
show: {
|
||||||
'company',
|
resource: [
|
||||||
],
|
'company',
|
||||||
operation: [
|
],
|
||||||
'autocomplete',
|
operation: [
|
||||||
],
|
'autocomplete',
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
description: 'Name is the partial name of the company.',
|
||||||
},
|
},
|
||||||
description: 'Name is the partial name of the company.',
|
|
||||||
},
|
|
||||||
] as INodeProperties[];
|
] as INodeProperties[];
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
import { OptionsWithUri } from 'request';
|
import {
|
||||||
|
OptionsWithUri,
|
||||||
|
} from 'request';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IExecuteFunctions,
|
IExecuteFunctions,
|
||||||
IExecuteSingleFunctions,
|
IExecuteSingleFunctions,
|
||||||
IHookFunctions,
|
IHookFunctions,
|
||||||
ILoadOptionsFunctions,
|
ILoadOptionsFunctions,
|
||||||
} from 'n8n-core';
|
} 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
|
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');
|
const credentials = this.getCredentials('clearbitApi');
|
||||||
|
@ -13,11 +19,11 @@ export async function clearbitApiRequest(this: IHookFunctions | IExecuteFunction
|
||||||
throw new Error('No credentials got returned!');
|
throw new Error('No credentials got returned!');
|
||||||
}
|
}
|
||||||
let options: OptionsWithUri = {
|
let options: OptionsWithUri = {
|
||||||
headers: { Authorization: `Bearer ${credentials.apiKey}`},
|
headers: { Authorization: `Bearer ${credentials.apiKey}` },
|
||||||
method,
|
method,
|
||||||
qs,
|
qs,
|
||||||
body,
|
body,
|
||||||
uri: uri ||`https://${api}-stream.clearbit.com${resource}`,
|
uri: uri || `https://${api}.clearbit.com${resource}`,
|
||||||
json: true,
|
json: true,
|
||||||
};
|
};
|
||||||
options = Object.assign({}, options, option);
|
options = Object.assign({}, options, option);
|
||||||
|
|
|
@ -26,9 +26,9 @@ export const personOperations = [
|
||||||
|
|
||||||
export const personFields = [
|
export const personFields = [
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* person:enrich */
|
/* person:enrich */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Email',
|
displayName: 'Email',
|
||||||
name: '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 {
|
import {
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export const checklistOperations = [
|
export const checklistOperations = [
|
||||||
{
|
{
|
||||||
|
@ -38,9 +38,9 @@ export const checklistOperations = [
|
||||||
|
|
||||||
export const checklistFields = [
|
export const checklistFields = [
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* checklist:create */
|
/* checklist:create */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Task ID',
|
displayName: 'Task ID',
|
||||||
name: 'task',
|
name: 'task',
|
||||||
|
@ -75,9 +75,9 @@ export const checklistFields = [
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* checklist:delete */
|
/* checklist:delete */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Checklist ID',
|
displayName: 'Checklist ID',
|
||||||
name: 'checklist',
|
name: 'checklist',
|
||||||
|
@ -95,9 +95,9 @@ export const checklistFields = [
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* checklist:update */
|
/* checklist:update */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Checklist ID',
|
displayName: 'Checklist ID',
|
||||||
name: 'checklist',
|
name: 'checklist',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export const checklistItemOperations = [
|
export const checklistItemOperations = [
|
||||||
{
|
{
|
||||||
|
@ -38,9 +38,9 @@ export const checklistItemOperations = [
|
||||||
|
|
||||||
export const checklistItemFields = [
|
export const checklistItemFields = [
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* checklistItem:create */
|
/* checklistItem:create */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Checklist ID',
|
displayName: 'Checklist ID',
|
||||||
name: 'checklist',
|
name: 'checklist',
|
||||||
|
@ -100,9 +100,10 @@ export const checklistItemFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* checklistItem:delete */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* checklistItem:delete */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Checklist ID',
|
displayName: 'Checklist ID',
|
||||||
name: 'checklist',
|
name: 'checklist',
|
||||||
|
@ -137,9 +138,10 @@ export const checklistItemFields = [
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* checklistItem:update */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* checklistItem:update */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Checklist ID',
|
displayName: 'Checklist ID',
|
||||||
name: 'checklist',
|
name: 'checklist',
|
||||||
|
|
|
@ -57,6 +57,21 @@ import {
|
||||||
taskOperations,
|
taskOperations,
|
||||||
} from './TaskDescription';
|
} from './TaskDescription';
|
||||||
|
|
||||||
|
import {
|
||||||
|
taskListFields,
|
||||||
|
taskListOperations,
|
||||||
|
} from './TaskListDescription';
|
||||||
|
|
||||||
|
import {
|
||||||
|
taskTagFields,
|
||||||
|
taskTagOperations,
|
||||||
|
} from './TaskTagDescription';
|
||||||
|
|
||||||
|
import {
|
||||||
|
spaceTagFields,
|
||||||
|
spaceTagOperations,
|
||||||
|
} from './SpaceTagDescription';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
taskDependencyFields,
|
taskDependencyFields,
|
||||||
taskDependencyOperations,
|
taskDependencyOperations,
|
||||||
|
@ -91,7 +106,7 @@ export class ClickUp implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'ClickUp',
|
displayName: 'ClickUp',
|
||||||
name: 'clickUp',
|
name: 'clickUp',
|
||||||
icon: 'file:clickup.png',
|
icon: 'file:clickup.svg',
|
||||||
group: ['output'],
|
group: ['output'],
|
||||||
version: 1,
|
version: 1,
|
||||||
subtitle: '={{$parameter["operation"] + ":" + $parameter["resource"]}}',
|
subtitle: '={{$parameter["operation"] + ":" + $parameter["resource"]}}',
|
||||||
|
@ -180,10 +195,22 @@ export class ClickUp implements INodeType {
|
||||||
name: 'List',
|
name: 'List',
|
||||||
value: 'list',
|
value: 'list',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Space Tag',
|
||||||
|
value: 'spaceTag',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Task',
|
name: 'Task',
|
||||||
value: 'task',
|
value: 'task',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Task List',
|
||||||
|
value: 'taskList',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Task Tag',
|
||||||
|
value: 'taskTag',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Task Dependency',
|
name: 'Task Dependency',
|
||||||
value: 'taskDependency',
|
value: 'taskDependency',
|
||||||
|
@ -221,6 +248,15 @@ export class ClickUp implements INodeType {
|
||||||
// GUEST
|
// GUEST
|
||||||
// ...guestOperations,
|
// ...guestOperations,
|
||||||
// ...guestFields,
|
// ...guestFields,
|
||||||
|
// TASK TAG
|
||||||
|
...taskTagOperations,
|
||||||
|
...taskTagFields,
|
||||||
|
// TASK LIST
|
||||||
|
...taskListOperations,
|
||||||
|
...taskListFields,
|
||||||
|
// SPACE TAG
|
||||||
|
...spaceTagOperations,
|
||||||
|
...spaceTagFields,
|
||||||
// TASK
|
// TASK
|
||||||
...taskOperations,
|
...taskOperations,
|
||||||
...taskFields,
|
...taskFields,
|
||||||
|
@ -1022,6 +1058,40 @@ export class ClickUp implements INodeType {
|
||||||
responseData = { success: true };
|
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 (resource === 'taskDependency') {
|
||||||
if (operation === 'create') {
|
if (operation === 'create') {
|
||||||
const taskId = this.getNodeParameter('task', i) as string;
|
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 (resource === 'list') {
|
||||||
if (operation === 'create') {
|
if (operation === 'create') {
|
||||||
const spaceId = this.getNodeParameter('space', i) as string;
|
const spaceId = this.getNodeParameter('space', i) as string;
|
||||||
|
|
|
@ -16,13 +16,15 @@ import {
|
||||||
clickupApiRequest,
|
clickupApiRequest,
|
||||||
} from './GenericFunctions';
|
} from './GenericFunctions';
|
||||||
|
|
||||||
import { createHmac } from 'crypto';
|
import {
|
||||||
|
createHmac,
|
||||||
|
} from 'crypto';
|
||||||
|
|
||||||
export class ClickUpTrigger implements INodeType {
|
export class ClickUpTrigger implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'ClickUp Trigger',
|
displayName: 'ClickUp Trigger',
|
||||||
name: 'clickUpTrigger',
|
name: 'clickUpTrigger',
|
||||||
icon: 'file:clickup.png',
|
icon: 'file:clickup.svg',
|
||||||
group: ['trigger'],
|
group: ['trigger'],
|
||||||
version: 1,
|
version: 1,
|
||||||
description: 'Handle ClickUp events via webhooks (Beta)',
|
description: 'Handle ClickUp events via webhooks (Beta)',
|
||||||
|
@ -302,16 +304,16 @@ export class ClickUpTrigger implements INodeType {
|
||||||
body.events = '*';
|
body.events = '*';
|
||||||
}
|
}
|
||||||
if (filters.listId) {
|
if (filters.listId) {
|
||||||
body.list_id = (filters.listId as string).replace('#','');
|
body.list_id = (filters.listId as string).replace('#', '');
|
||||||
}
|
}
|
||||||
if (filters.taskId) {
|
if (filters.taskId) {
|
||||||
body.task_id = (filters.taskId as string).replace('#','');
|
body.task_id = (filters.taskId as string).replace('#', '');
|
||||||
}
|
}
|
||||||
if (filters.spaceId) {
|
if (filters.spaceId) {
|
||||||
body.space_id = (filters.spaceId as string).replace('#','');
|
body.space_id = (filters.spaceId as string).replace('#', '');
|
||||||
}
|
}
|
||||||
if (filters.folderId) {
|
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);
|
const { webhook } = await clickupApiRequest.call(this, 'POST', endpoint, body);
|
||||||
webhookData.webhookId = webhook.id;
|
webhookData.webhookId = webhook.id;
|
||||||
|
@ -323,7 +325,7 @@ export class ClickUpTrigger implements INodeType {
|
||||||
const endpoint = `/webhook/${webhookData.webhookId}`;
|
const endpoint = `/webhook/${webhookData.webhookId}`;
|
||||||
try {
|
try {
|
||||||
await clickupApiRequest.call(this, 'DELETE', endpoint);
|
await clickupApiRequest.call(this, 'DELETE', endpoint);
|
||||||
} catch(error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
delete webhookData.webhookId;
|
delete webhookData.webhookId;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export const commentOperations = [
|
export const commentOperations = [
|
||||||
{
|
{
|
||||||
|
@ -43,9 +43,9 @@ export const commentOperations = [
|
||||||
|
|
||||||
export const commentFields = [
|
export const commentFields = [
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* comment:create */
|
/* comment:create */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Comment On',
|
displayName: 'Comment On',
|
||||||
name: 'commentOn',
|
name: 'commentOn',
|
||||||
|
@ -141,9 +141,10 @@ export const commentFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* comment:delete */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* comment:delete */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Comment ID',
|
displayName: 'Comment ID',
|
||||||
name: 'comment',
|
name: 'comment',
|
||||||
|
@ -161,9 +162,10 @@ export const commentFields = [
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* comment:getAll */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* comment:getAll */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Comments On',
|
displayName: 'Comments On',
|
||||||
name: 'commentsOn',
|
name: 'commentsOn',
|
||||||
|
@ -232,9 +234,10 @@ export const commentFields = [
|
||||||
default: 50,
|
default: 50,
|
||||||
description: 'How many results to return.',
|
description: 'How many results to return.',
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* comment:update */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* comment:update */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Comment ID',
|
displayName: 'Comment ID',
|
||||||
name: 'comment',
|
name: 'comment',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export const folderOperations = [
|
export const folderOperations = [
|
||||||
{
|
{
|
||||||
|
@ -48,9 +48,9 @@ export const folderOperations = [
|
||||||
|
|
||||||
export const folderFields = [
|
export const folderFields = [
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* folder:create */
|
/* folder:create */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -111,9 +111,10 @@ export const folderFields = [
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* folder:delete */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* folder:delete */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -180,9 +181,10 @@ export const folderFields = [
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* folder:get */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* folder:get */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -249,9 +251,10 @@ export const folderFields = [
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* folder:getAll */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* folder:getAll */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -341,9 +344,10 @@ export const folderFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* folder:update */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* folder:update */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
OptionsWithUri,
|
OptionsWithUri,
|
||||||
} from 'request';
|
} from 'request';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IExecuteFunctions,
|
IExecuteFunctions,
|
||||||
|
@ -13,7 +13,7 @@ import {
|
||||||
import {
|
import {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
IOAuth2Options,
|
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
|
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 = {
|
const options: OptionsWithUri = {
|
||||||
|
@ -23,7 +23,7 @@ export async function clickupApiRequest(this: IHookFunctions | IExecuteFunctions
|
||||||
method,
|
method,
|
||||||
qs,
|
qs,
|
||||||
body,
|
body,
|
||||||
uri: uri ||`https://api.clickup.com/api/v2${resource}`,
|
uri: uri || `https://api.clickup.com/api/v2${resource}`,
|
||||||
json: true,
|
json: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ export async function clickupApiRequest(this: IHookFunctions | IExecuteFunctions
|
||||||
return await this.helpers.requestOAuth2!.call(this, 'clickUpOAuth2Api', options, oAuth2Options);
|
return await this.helpers.requestOAuth2!.call(this, 'clickUpOAuth2Api', options, oAuth2Options);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch(error) {
|
} catch (error) {
|
||||||
let errorMessage = error;
|
let errorMessage = error;
|
||||||
if (error.err) {
|
if (error.err) {
|
||||||
errorMessage = 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[] = [];
|
const returnData: IDataObject[] = [];
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export const goalOperations = [
|
export const goalOperations = [
|
||||||
{
|
{
|
||||||
|
@ -48,9 +48,9 @@ export const goalOperations = [
|
||||||
|
|
||||||
export const goalFields = [
|
export const goalFields = [
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* goal:create */
|
/* goal:create */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -140,9 +140,10 @@ export const goalFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* goal:delete */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* goal:delete */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Goal ID',
|
displayName: 'Goal ID',
|
||||||
name: 'goal',
|
name: 'goal',
|
||||||
|
@ -160,9 +161,10 @@ export const goalFields = [
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* goal:get */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* goal:get */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Goal ID',
|
displayName: 'Goal ID',
|
||||||
name: 'goal',
|
name: 'goal',
|
||||||
|
@ -180,9 +182,10 @@ export const goalFields = [
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* goal:getAll */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* goal:getAll */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -224,9 +227,10 @@ export const goalFields = [
|
||||||
default: 50,
|
default: 50,
|
||||||
description: 'How many results to return.',
|
description: 'How many results to return.',
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* goal:update */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* goal:update */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Goal ID',
|
displayName: 'Goal ID',
|
||||||
name: 'goal',
|
name: 'goal',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export const goalKeyResultOperations = [
|
export const goalKeyResultOperations = [
|
||||||
{
|
{
|
||||||
|
@ -38,9 +38,9 @@ export const goalKeyResultOperations = [
|
||||||
|
|
||||||
export const goalKeyResultFields = [
|
export const goalKeyResultFields = [
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* goalKeyResult:create */
|
/* goalKeyResult:create */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Goal ID',
|
displayName: 'Goal ID',
|
||||||
name: 'goal',
|
name: 'goal',
|
||||||
|
@ -178,9 +178,10 @@ export const goalKeyResultFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* goalKeyResult:delete */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* goalKeyResult:delete */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Key Result ID',
|
displayName: 'Key Result ID',
|
||||||
name: 'keyResult',
|
name: 'keyResult',
|
||||||
|
@ -198,9 +199,10 @@ export const goalKeyResultFields = [
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* goalKeyResult:update */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* goalKeyResult:update */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Key Result ID',
|
displayName: 'Key Result ID',
|
||||||
name: 'keyResult',
|
name: 'keyResult',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export const guestOperations = [
|
export const guestOperations = [
|
||||||
{
|
{
|
||||||
|
@ -43,9 +43,9 @@ export const guestOperations = [
|
||||||
|
|
||||||
export const guestFields = [
|
export const guestFields = [
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* guest:create */
|
/* guest:create */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -119,9 +119,10 @@ export const guestFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* guest:delete */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* guest:delete */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -159,9 +160,10 @@ export const guestFields = [
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* guest:get */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* guest:get */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -199,9 +201,10 @@ export const guestFields = [
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* guest:update */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* guest:update */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export const listOperations = [
|
export const listOperations = [
|
||||||
{
|
{
|
||||||
|
@ -58,9 +58,9 @@ export const listOperations = [
|
||||||
|
|
||||||
export const listFields = [
|
export const listFields = [
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* list:create */
|
/* list:create */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -233,9 +233,10 @@ export const listFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* list:member */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* list:member */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'List ID',
|
displayName: 'List ID',
|
||||||
name: 'id',
|
name: 'id',
|
||||||
|
@ -295,9 +296,9 @@ export const listFields = [
|
||||||
default: 50,
|
default: 50,
|
||||||
description: 'How many results to return.',
|
description: 'How many results to return.',
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* list:customFields */
|
/* list:customFields */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team',
|
displayName: 'Team',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -436,9 +437,10 @@ export const listFields = [
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* list:delete */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* list:delete */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -542,9 +544,10 @@ export const listFields = [
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* list:get */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* list:get */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -648,9 +651,10 @@ export const listFields = [
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* list:getAll */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* list:getAll */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -783,9 +787,10 @@ export const listFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* list:update */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* list:update */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
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 {
|
import {
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export const taskDependencyOperations = [
|
export const taskDependencyOperations = [
|
||||||
{
|
{
|
||||||
|
@ -33,9 +33,9 @@ export const taskDependencyOperations = [
|
||||||
|
|
||||||
export const taskDependencyFields = [
|
export const taskDependencyFields = [
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* taskDependency:create */
|
/* taskDependency:create */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Task ID',
|
displayName: 'Task ID',
|
||||||
name: 'task',
|
name: 'task',
|
||||||
|
@ -69,9 +69,10 @@ export const taskDependencyFields = [
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* taskDependency:delete */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* taskDependency:delete */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Task ID',
|
displayName: 'Task ID',
|
||||||
name: 'task',
|
name: 'task',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export const taskOperations = [
|
export const taskOperations = [
|
||||||
{
|
{
|
||||||
|
@ -58,9 +58,9 @@ export const taskOperations = [
|
||||||
|
|
||||||
export const taskFields = [
|
export const taskFields = [
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* task:create */
|
/* task:create */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -352,9 +352,10 @@ export const taskFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* task:update */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* task:update */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Task ID',
|
displayName: 'Task ID',
|
||||||
name: 'id',
|
name: 'id',
|
||||||
|
@ -489,9 +490,10 @@ export const taskFields = [
|
||||||
],
|
],
|
||||||
|
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* task:get */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* task:get */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Task ID',
|
displayName: 'Task ID',
|
||||||
name: 'id',
|
name: 'id',
|
||||||
|
@ -510,9 +512,10 @@ export const taskFields = [
|
||||||
},
|
},
|
||||||
description: 'Task ID',
|
description: 'Task ID',
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* task:getAll */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* task:getAll */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -920,9 +923,10 @@ export const taskFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* task:delete */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* task:delete */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Task ID',
|
displayName: 'Task ID',
|
||||||
name: 'id',
|
name: 'id',
|
||||||
|
@ -941,9 +945,10 @@ export const taskFields = [
|
||||||
},
|
},
|
||||||
description: 'task ID',
|
description: 'task ID',
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* task:member */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* task:member */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Task ID',
|
displayName: 'Task ID',
|
||||||
name: 'id',
|
name: 'id',
|
||||||
|
@ -1003,9 +1008,10 @@ export const taskFields = [
|
||||||
default: 50,
|
default: 50,
|
||||||
description: 'How many results to return.',
|
description: 'How many results to return.',
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* task:setCustomField */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* task:setCustomField */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Task ID',
|
displayName: 'Task ID',
|
||||||
name: 'task',
|
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 {
|
import {
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export const timeEntryTagOperations = [
|
export const timeEntryTagOperations = [
|
||||||
{
|
{
|
||||||
|
@ -28,7 +28,7 @@ export const timeEntryTagOperations = [
|
||||||
{
|
{
|
||||||
name: 'Remove',
|
name: 'Remove',
|
||||||
value: 'remove',
|
value: 'remove',
|
||||||
description:'Remove tag from time entry',
|
description: 'Remove tag from time entry',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
default: 'add',
|
default: 'add',
|
||||||
|
@ -38,9 +38,9 @@ export const timeEntryTagOperations = [
|
||||||
|
|
||||||
export const timeEntryTagFields = [
|
export const timeEntryTagFields = [
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* timeEntryTag:getAll */
|
/* timeEntryTag:getAll */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -102,9 +102,10 @@ export const timeEntryTagFields = [
|
||||||
default: 5,
|
default: 5,
|
||||||
description: 'How many results to return.',
|
description: 'How many results to return.',
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* timeEntryTag:add */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* timeEntryTag:add */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
name: 'team',
|
||||||
|
@ -188,9 +189,10 @@ export const timeEntryTagFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* timeEntryTag:remove */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* timeEntryTag:remove */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Team ID',
|
displayName: 'Team ID',
|
||||||
name: 'team',
|
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 = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'Copper Trigger',
|
displayName: 'Copper Trigger',
|
||||||
name: 'copperTrigger',
|
name: 'copperTrigger',
|
||||||
icon: 'file:copper.png',
|
icon: 'file:copper.svg',
|
||||||
group: ['trigger'],
|
group: ['trigger'],
|
||||||
version: 1,
|
version: 1,
|
||||||
description: 'Handle Copper events via webhooks',
|
description: 'Handle Copper events via webhooks',
|
||||||
|
@ -147,7 +147,7 @@ export class CopperTrigger implements INodeType {
|
||||||
const endpoint = `/webhooks/${webhookData.webhookId}`;
|
const endpoint = `/webhooks/${webhookData.webhookId}`;
|
||||||
try {
|
try {
|
||||||
await copperApiRequest.call(this, 'DELETE', endpoint);
|
await copperApiRequest.call(this, 'DELETE', endpoint);
|
||||||
} catch(error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
delete webhookData.webhookId;
|
delete webhookData.webhookId;
|
||||||
|
|