Merge branch 'master' into testing-framework
|
@ -7,6 +7,7 @@
|
|||
"build": "lerna exec npm run build",
|
||||
"dev": "lerna exec npm run dev --parallel",
|
||||
"clean:dist": "lerna exec -- rimraf ./dist",
|
||||
"optimize-svg": "find ./packages -name '*.svg' -print0 | xargs -0 -P16 -L20 npx svgo",
|
||||
"start": "run-script-os",
|
||||
"start:default": "cd packages/cli/bin && ./n8n",
|
||||
"start:windows": "cd packages/cli/bin && n8n",
|
||||
|
|
|
@ -2,6 +2,42 @@
|
|||
|
||||
This list shows all the versions which include breaking changes and how to upgrade.
|
||||
|
||||
## 0.117.0
|
||||
|
||||
### What changed?
|
||||
Removed the "Activation Trigger" node. This node was replaced by two other nodes.
|
||||
|
||||
The "Activation Trigger" node was added on version 0.113.0 but was not fully compliant to UX, so we decided to refactor and change it ASAP so it affects the least possible users.
|
||||
|
||||
The new nodes are "n8n Trigger" and "Workflow Trigger". Behavior-wise, the nodes do the same, we just split the functionality to make it more intuitive to users.
|
||||
|
||||
### When is action necessary?
|
||||
|
||||
If you use the "Activation Trigger" in any of your workflows, please replace it by the new nodes.
|
||||
|
||||
### How to upgrade:
|
||||
|
||||
Remove the previous node and add the new ones according to your workflows.
|
||||
|
||||
----------------------------
|
||||
|
||||
Changed the behavior for nodes that use Postgres Wire Protocol: Postgres, QuestDB, CrateDB and TimescaleDB.
|
||||
|
||||
All nodes have been standardized and now follow the same patterns. Behavior will be the same for most cases, but new added functionality can now be explored.
|
||||
|
||||
You can now also inform how you would like n8n to execute queries. Default mode is `Multiple queries` which translates to previous behavior, but you can now run them `Independently` or `Transaction`. Also, `Continue on Fail` now plays a major role for the new modes.
|
||||
|
||||
The node output for `insert` operations now rely on the new parameter `Return fields`, just like `update` operations did previously.
|
||||
|
||||
### When is action necessary?
|
||||
|
||||
If you rely on the output returned by `insert` operations for any of the mentioned nodes, we recommend you review your workflows.
|
||||
|
||||
By default, all `insert` operations will have `Return fields: *` as the default, setting, returning all information inserted.
|
||||
|
||||
Previously, the node would return all information it received, without taking into account what actually happened in the database.
|
||||
|
||||
|
||||
## 0.113.0
|
||||
|
||||
### What changed?
|
||||
|
|
|
@ -170,10 +170,11 @@ export class Execute extends Command {
|
|||
this.log('====================================');
|
||||
this.log(JSON.stringify(data, null, 2));
|
||||
|
||||
// console.log(data.data.resultData.error);
|
||||
const error = new Error(data.data.resultData.error.message);
|
||||
error.stack = data.data.resultData.error.stack;
|
||||
throw error;
|
||||
const { error } = data.data.resultData;
|
||||
throw {
|
||||
...error,
|
||||
stack: error.stack,
|
||||
};
|
||||
}
|
||||
if (flags.rawOutput === undefined) {
|
||||
this.log('Execution was successfull:');
|
||||
|
@ -186,7 +187,6 @@ export class Execute extends Command {
|
|||
console.error(e.message);
|
||||
console.error(e.stack);
|
||||
this.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
this.exit();
|
||||
|
|
|
@ -127,11 +127,22 @@ export class Worker extends Command {
|
|||
staticData = workflowData.staticData;
|
||||
}
|
||||
|
||||
let workflowTimeout = config.get('executions.timeout') as number; // initialize with default
|
||||
if (currentExecutionDb.workflowData.settings && currentExecutionDb.workflowData.settings.executionTimeout) {
|
||||
workflowTimeout = currentExecutionDb.workflowData.settings!.executionTimeout as number; // preference on workflow setting
|
||||
}
|
||||
|
||||
let executionTimeoutTimestamp: number | undefined;
|
||||
if (workflowTimeout > 0) {
|
||||
workflowTimeout = Math.min(workflowTimeout, config.get('executions.maxTimeout') as number);
|
||||
executionTimeoutTimestamp = Date.now() + workflowTimeout * 1000;
|
||||
}
|
||||
|
||||
const workflow = new Workflow({ id: currentExecutionDb.workflowData.id as string, name: currentExecutionDb.workflowData.name, nodes: currentExecutionDb.workflowData!.nodes, connections: currentExecutionDb.workflowData!.connections, active: currentExecutionDb.workflowData!.active, nodeTypes, staticData, settings: currentExecutionDb.workflowData!.settings });
|
||||
|
||||
const credentials = await WorkflowCredentials(currentExecutionDb.workflowData.nodes);
|
||||
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials, undefined, executionTimeoutTimestamp);
|
||||
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksWorkerExecuter(currentExecutionDb.mode, job.data.executionId, currentExecutionDb.workflowData, { retryOf: currentExecutionDb.retryOf as string });
|
||||
|
||||
let workflowExecute: WorkflowExecute;
|
||||
|
|
|
@ -446,6 +446,12 @@ const config = convict({
|
|||
},
|
||||
|
||||
endpoints: {
|
||||
payloadSizeMax: {
|
||||
format: Number,
|
||||
default: 16,
|
||||
env: 'N8N_PAYLOAD_SIZE_MAX',
|
||||
doc: 'Maximum payload size in MB.',
|
||||
},
|
||||
metrics: {
|
||||
enable: {
|
||||
format: 'Boolean',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n",
|
||||
"version": "0.114.0",
|
||||
"version": "0.117.0",
|
||||
"description": "n8n Workflow Automation Tool",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -55,7 +55,6 @@
|
|||
"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",
|
||||
|
@ -80,12 +79,12 @@
|
|||
"typescript": "~3.9.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@node-rs/bcrypt": "^1.2.0",
|
||||
"@oclif/command": "^1.5.18",
|
||||
"@oclif/errors": "^1.2.2",
|
||||
"@types/json-diff": "^0.5.1",
|
||||
"@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",
|
||||
|
@ -105,11 +104,11 @@
|
|||
"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",
|
||||
"mysql2": "~2.2.0",
|
||||
"n8n-core": "~0.68.0",
|
||||
"n8n-editor-ui": "~0.87.0",
|
||||
"n8n-nodes-base": "~0.114.0",
|
||||
"n8n-workflow": "~0.56.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"open": "^7.0.0",
|
||||
"pg": "^8.3.0",
|
||||
|
@ -117,7 +116,7 @@
|
|||
"request-promise-native": "^1.0.7",
|
||||
"sqlite3": "^5.0.1",
|
||||
"sse-channel": "^3.1.1",
|
||||
"tslib": "1.11.2",
|
||||
"tslib": "1.13.0",
|
||||
"typeorm": "^0.2.30"
|
||||
},
|
||||
"jest": {
|
||||
|
|
|
@ -20,7 +20,6 @@ import {
|
|||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
IExecuteData,
|
||||
IGetExecutePollFunctions,
|
||||
IGetExecuteTriggerFunctions,
|
||||
|
@ -283,23 +282,11 @@ export class ActiveWorkflowRunner {
|
|||
const node = workflow.getNode(webhookData.node) as INode;
|
||||
node.name = webhookData.node;
|
||||
|
||||
path = node.parameters.path as string;
|
||||
|
||||
if (node.parameters.path === undefined) {
|
||||
path = workflow.expression.getSimpleParameterValue(node, webhookData.webhookDescription['path'], mode) as string | undefined;
|
||||
|
||||
if (path === undefined) {
|
||||
// TODO: Use a proper logger
|
||||
console.error(`No webhook path could be found for node "${node.name}" in workflow "${workflow.id}".`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const isFullPath: boolean = workflow.expression.getSimpleParameterValue(node, webhookData.webhookDescription['isFullPath'], mode, false) as boolean;
|
||||
path = webhookData.path;
|
||||
|
||||
const webhook = {
|
||||
workflowId: webhookData.workflowId,
|
||||
webhookPath: NodeHelpers.getNodeWebhookPath(workflow.id as string, node, path, isFullPath),
|
||||
webhookPath: path,
|
||||
node: node.name,
|
||||
method: webhookData.httpMethod,
|
||||
} as IWebhookDb;
|
||||
|
@ -317,7 +304,6 @@ export class ActiveWorkflowRunner {
|
|||
}
|
||||
|
||||
try {
|
||||
|
||||
await Db.collections.Webhook?.insert(webhook);
|
||||
|
||||
const webhookExists = await workflow.runWebhookMethod('checkExists', webhookData, NodeExecuteFunctions, mode, activation, false);
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
import {
|
||||
ExecutionError,
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialsDecrypted,
|
||||
ICredentialsEncrypted,
|
||||
ICredentialType,
|
||||
IDataObject,
|
||||
IExecutionError,
|
||||
IRun,
|
||||
IRunData,
|
||||
IRunExecutionData,
|
||||
ITaskData,
|
||||
IWorkflowBase as IWorkflowBaseWorkflow,
|
||||
IWorkflowCredentials,
|
||||
Workflow,
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
IDeferredPromise,
|
||||
IDeferredPromise, WorkflowExecute,
|
||||
} from 'n8n-core';
|
||||
|
||||
|
||||
import * as PCancelable from 'p-cancelable';
|
||||
import { ObjectID, Repository } from 'typeorm';
|
||||
|
||||
|
@ -374,10 +374,10 @@ export interface ITransferNodeTypes {
|
|||
|
||||
|
||||
export interface IWorkflowErrorData {
|
||||
[key: string]: IDataObject | string | number | IExecutionError;
|
||||
[key: string]: IDataObject | string | number | ExecutionError;
|
||||
execution: {
|
||||
id?: string;
|
||||
error: IExecutionError;
|
||||
error: ExecutionError;
|
||||
lastNodeExecuted: string;
|
||||
mode: WorkflowExecuteMode;
|
||||
};
|
||||
|
@ -411,3 +411,9 @@ export interface IWorkflowExecutionDataProcessWithExecution extends IWorkflowExe
|
|||
executionId: string;
|
||||
nodeTypeData: ITransferNodeTypes;
|
||||
}
|
||||
|
||||
export interface IWorkflowExecuteProcess {
|
||||
startedAt: Date;
|
||||
workflow: Workflow;
|
||||
workflowExecute: WorkflowExecute;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import { RequestOptions } from 'oauth-1.0a';
|
|||
import * as csrf from 'csrf';
|
||||
import * as requestPromise from 'request-promise-native';
|
||||
import { createHmac } from 'crypto';
|
||||
import { compare } from 'bcryptjs';
|
||||
import { compare } from '@node-rs/bcrypt';
|
||||
import * as promClient from 'prom-client';
|
||||
|
||||
import {
|
||||
|
@ -132,6 +132,7 @@ class App {
|
|||
protocol: string;
|
||||
sslKey: string;
|
||||
sslCert: string;
|
||||
payloadSizeMax: number;
|
||||
|
||||
presetCredentialsLoaded: boolean;
|
||||
|
||||
|
@ -145,6 +146,7 @@ class App {
|
|||
this.saveManualExecutions = config.get('executions.saveDataManualExecutions') as boolean;
|
||||
this.executionTimeout = config.get('executions.timeout') as number;
|
||||
this.maxExecutionTimeout = config.get('executions.maxTimeout') as number;
|
||||
this.payloadSizeMax = config.get('endpoints.payloadSizeMax') as number;
|
||||
this.timezone = config.get('generic.timezone') as string;
|
||||
this.restEndpoint = config.get('endpoints.rest') as string;
|
||||
|
||||
|
@ -369,7 +371,7 @@ class App {
|
|||
|
||||
// Support application/json type post data
|
||||
this.app.use(bodyParser.json({
|
||||
limit: '16mb', verify: (req, res, buf) => {
|
||||
limit: this.payloadSizeMax + 'mb', verify: (req, res, buf) => {
|
||||
// @ts-ignore
|
||||
req.rawBody = buf;
|
||||
},
|
||||
|
@ -378,7 +380,7 @@ class App {
|
|||
// Support application/xml type post data
|
||||
// @ts-ignore
|
||||
this.app.use(bodyParser.xml({
|
||||
limit: '16mb', xmlParseOptions: {
|
||||
limit: this.payloadSizeMax + 'mb', xmlParseOptions: {
|
||||
normalize: true, // Trim whitespace inside text nodes
|
||||
normalizeTags: true, // Transform tags to lowercase
|
||||
explicitArray: false, // Only put properties in array if length > 1
|
||||
|
@ -386,7 +388,7 @@ class App {
|
|||
}));
|
||||
|
||||
this.app.use(bodyParser.text({
|
||||
limit: '16mb', verify: (req, res, buf) => {
|
||||
limit: this.payloadSizeMax + 'mb', verify: (req, res, buf) => {
|
||||
// @ts-ignore
|
||||
req.rawBody = buf;
|
||||
},
|
||||
|
|
|
@ -144,7 +144,7 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
|||
|
||||
try {
|
||||
webhookResultData = await workflow.runWebhook(webhookData, workflowStartNode, additionalData, NodeExecuteFunctions, executionMode);
|
||||
} catch (e) {
|
||||
} catch (err) {
|
||||
// Send error response to webhook caller
|
||||
const errorMessage = 'Workflow Webhook Error: Workflow could not be started!';
|
||||
responseCallback(new Error(errorMessage), {});
|
||||
|
@ -156,8 +156,9 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
|||
runData: {},
|
||||
lastNodeExecuted: workflowStartNode.name,
|
||||
error: {
|
||||
message: e.message,
|
||||
stack: e.stack,
|
||||
...err,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
IExecutionResponse,
|
||||
IPushDataExecutionFinished,
|
||||
IWorkflowBase,
|
||||
IWorkflowExecuteProcess,
|
||||
IWorkflowExecutionDataProcess,
|
||||
NodeTypes,
|
||||
Push,
|
||||
|
@ -569,7 +570,7 @@ export async function getWorkflowData(workflowInfo: IExecuteWorkflowInfo): Promi
|
|||
* @param {INodeExecutionData[]} [inputData]
|
||||
* @returns {(Promise<Array<INodeExecutionData[] | null>>)}
|
||||
*/
|
||||
export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additionalData: IWorkflowExecuteAdditionalData, inputData?: INodeExecutionData[], parentExecutionId?: string, loadedWorkflowData?: IWorkflowBase, loadedRunData?: IWorkflowExecutionDataProcess): Promise<Array<INodeExecutionData[] | null> | IRun> {
|
||||
export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additionalData: IWorkflowExecuteAdditionalData, inputData?: INodeExecutionData[], parentExecutionId?: string, loadedWorkflowData?: IWorkflowBase, loadedRunData?: IWorkflowExecutionDataProcess): Promise<Array<INodeExecutionData[] | null> | IWorkflowExecuteProcess> {
|
||||
const externalHooks = ExternalHooks();
|
||||
await externalHooks.init();
|
||||
|
||||
|
@ -605,10 +606,19 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
|||
// This one already contains changes to talk to parent process
|
||||
// and get executionID from `activeExecutions` running on main process
|
||||
additionalDataIntegrated.executeWorkflow = additionalData.executeWorkflow;
|
||||
additionalDataIntegrated.executionTimeoutTimestamp = additionalData.executionTimeoutTimestamp;
|
||||
|
||||
|
||||
// Execute the workflow
|
||||
const workflowExecute = new WorkflowExecute(additionalDataIntegrated, runData.executionMode, runExecutionData);
|
||||
if (parentExecutionId !== undefined) {
|
||||
// Must be changed to become typed
|
||||
return {
|
||||
startedAt: new Date(),
|
||||
workflow,
|
||||
workflowExecute,
|
||||
};
|
||||
}
|
||||
const data = await workflowExecute.processRunExecutionData(workflow);
|
||||
|
||||
await externalHooks.run('workflow.postExecute', [data, workflowData]);
|
||||
|
@ -616,20 +626,17 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
|||
if (data.finished === true) {
|
||||
// Workflow did finish successfully
|
||||
|
||||
if (parentExecutionId !== undefined) {
|
||||
return data;
|
||||
} else {
|
||||
await ActiveExecutions.getInstance().remove(executionId, data);
|
||||
|
||||
const returnData = WorkflowHelpers.getDataLastExecutedNodeData(data);
|
||||
return returnData!.data!.main;
|
||||
}
|
||||
await ActiveExecutions.getInstance().remove(executionId, data);
|
||||
const returnData = WorkflowHelpers.getDataLastExecutedNodeData(data);
|
||||
return returnData!.data!.main;
|
||||
} else {
|
||||
await ActiveExecutions.getInstance().remove(executionId, data);
|
||||
// Workflow did fail
|
||||
const error = new Error(data.data.resultData.error!.message);
|
||||
error.stack = data.data.resultData.error!.stack;
|
||||
throw error;
|
||||
const { error } = data.data.resultData;
|
||||
throw {
|
||||
...error,
|
||||
stack: error!.stack,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -642,7 +649,7 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
|||
* @param {INodeParameters} currentNodeParameters
|
||||
* @returns {Promise<IWorkflowExecuteAdditionalData>}
|
||||
*/
|
||||
export async function getBase(credentials: IWorkflowCredentials, currentNodeParameters?: INodeParameters): Promise<IWorkflowExecuteAdditionalData> {
|
||||
export async function getBase(credentials: IWorkflowCredentials, currentNodeParameters?: INodeParameters, executionTimeoutTimestamp?: number): Promise<IWorkflowExecuteAdditionalData> {
|
||||
const urlBaseWebhook = WebhookHelpers.getWebhookBaseUrl();
|
||||
|
||||
const timezone = config.get('generic.timezone') as string;
|
||||
|
@ -664,6 +671,7 @@ export async function getBase(credentials: IWorkflowCredentials, currentNodePara
|
|||
webhookBaseUrl,
|
||||
webhookTestBaseUrl,
|
||||
currentNodeParameters,
|
||||
executionTimeoutTimestamp,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -27,12 +27,12 @@ import {
|
|||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
IExecutionError,
|
||||
ExecutionError,
|
||||
IRun,
|
||||
Workflow,
|
||||
WorkflowExecuteMode,
|
||||
WorkflowHooks,
|
||||
WorkflowOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import * as config from '../config';
|
||||
|
@ -78,13 +78,13 @@ export class WorkflowRunner {
|
|||
/**
|
||||
* The process did error
|
||||
*
|
||||
* @param {IExecutionError} error
|
||||
* @param {ExecutionError} error
|
||||
* @param {Date} startedAt
|
||||
* @param {WorkflowExecuteMode} executionMode
|
||||
* @param {string} executionId
|
||||
* @memberof WorkflowRunner
|
||||
*/
|
||||
processError(error: IExecutionError, startedAt: Date, executionMode: WorkflowExecuteMode, executionId: string) {
|
||||
processError(error: ExecutionError, startedAt: Date, executionMode: WorkflowExecuteMode, executionId: string) {
|
||||
const fullRunData: IRun = {
|
||||
data: {
|
||||
resultData: {
|
||||
|
@ -158,8 +158,22 @@ export class WorkflowRunner {
|
|||
|
||||
const nodeTypes = NodeTypes();
|
||||
|
||||
|
||||
// Soft timeout to stop workflow execution after current running node
|
||||
// Changes were made by adding the `workflowTimeout` to the `additionalData`
|
||||
// So that the timeout will also work for executions with nested workflows.
|
||||
let executionTimeout: NodeJS.Timeout;
|
||||
let workflowTimeout = config.get('executions.timeout') as number; // initialize with default
|
||||
if (data.workflowData.settings && data.workflowData.settings.executionTimeout) {
|
||||
workflowTimeout = data.workflowData.settings!.executionTimeout as number; // preference on workflow setting
|
||||
}
|
||||
|
||||
if (workflowTimeout > 0) {
|
||||
workflowTimeout = Math.min(workflowTimeout, config.get('executions.maxTimeout') as number);
|
||||
}
|
||||
|
||||
const workflow = new Workflow({ id: data.workflowData.id as string | undefined, name: data.workflowData.name, nodes: data.workflowData!.nodes, connections: data.workflowData!.connections, active: data.workflowData!.active, nodeTypes, staticData: data.workflowData!.staticData });
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(data.credentials);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(data.credentials, undefined, workflowTimeout <= 0 ? undefined : Date.now() + workflowTimeout * 1000);
|
||||
|
||||
// Register the active execution
|
||||
const executionId = await this.activeExecutions.add(data, undefined);
|
||||
|
@ -184,14 +198,7 @@ export class WorkflowRunner {
|
|||
|
||||
this.activeExecutions.attachWorkflowExecution(executionId, workflowExecution);
|
||||
|
||||
// Soft timeout to stop workflow execution after current running node
|
||||
let executionTimeout: NodeJS.Timeout;
|
||||
let workflowTimeout = config.get('executions.timeout') as number > 0 && config.get('executions.timeout') as number; // initialize with default
|
||||
if (data.workflowData.settings && data.workflowData.settings.executionTimeout) {
|
||||
workflowTimeout = data.workflowData.settings!.executionTimeout as number > 0 && data.workflowData.settings!.executionTimeout as number; // preference on workflow setting
|
||||
}
|
||||
|
||||
if (workflowTimeout) {
|
||||
if (workflowTimeout > 0) {
|
||||
const timeout = Math.min(workflowTimeout, config.get('executions.maxTimeout') as number) * 1000; // as seconds
|
||||
executionTimeout = setTimeout(() => {
|
||||
this.activeExecutions.stopExecution(executionId, 'timeout');
|
||||
|
@ -250,9 +257,7 @@ export class WorkflowRunner {
|
|||
const fullRunData :IRun = {
|
||||
data: {
|
||||
resultData: {
|
||||
error: {
|
||||
message: 'Workflow has been canceled!',
|
||||
} as IExecutionError,
|
||||
error: new WorkflowOperationError('Workflow has been canceled!'),
|
||||
runData: {},
|
||||
},
|
||||
},
|
||||
|
@ -282,7 +287,6 @@ export class WorkflowRunner {
|
|||
* the database. *
|
||||
*************************************************/
|
||||
let watchDogInterval: NodeJS.Timeout | undefined;
|
||||
let resolved = false;
|
||||
|
||||
const watchDog = new Promise((res) => {
|
||||
watchDogInterval = setInterval(async () => {
|
||||
|
@ -303,28 +307,9 @@ export class WorkflowRunner {
|
|||
}
|
||||
};
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
jobData.then((data) => {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
clearWatchdogInterval();
|
||||
res(data);
|
||||
}
|
||||
}).catch((e) => {
|
||||
if(!resolved) {
|
||||
resolved = true;
|
||||
clearWatchdogInterval();
|
||||
rej(e);
|
||||
}
|
||||
});
|
||||
watchDog.then((data) => {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
clearWatchdogInterval();
|
||||
res(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
await Promise.race([jobData, watchDog]);
|
||||
clearWatchdogInterval();
|
||||
|
||||
} else {
|
||||
await jobData;
|
||||
}
|
||||
|
@ -366,7 +351,7 @@ export class WorkflowRunner {
|
|||
// 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);
|
||||
});
|
||||
|
||||
|
@ -385,7 +370,7 @@ export class WorkflowRunner {
|
|||
* @memberof WorkflowRunner
|
||||
*/
|
||||
async runSubprocess(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean): Promise<string> {
|
||||
const startedAt = new Date();
|
||||
let startedAt = new Date();
|
||||
const subprocess = fork(pathJoin(__dirname, 'WorkflowRunnerProcess.js'));
|
||||
|
||||
if (loadStaticData === true && data.workflowData.id) {
|
||||
|
@ -428,7 +413,6 @@ export class WorkflowRunner {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).executionId = executionId;
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).nodeTypeData = nodeTypeData;
|
||||
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsOverwrite = credentialsOverwrites;
|
||||
|
@ -441,64 +425,93 @@ export class WorkflowRunner {
|
|||
|
||||
// Start timeout for the execution
|
||||
let executionTimeout: NodeJS.Timeout;
|
||||
let workflowTimeout = config.get('executions.timeout') as number > 0 && config.get('executions.timeout') as number; // initialize with default
|
||||
let workflowTimeout = config.get('executions.timeout') as number; // initialize with default
|
||||
if (data.workflowData.settings && data.workflowData.settings.executionTimeout) {
|
||||
workflowTimeout = data.workflowData.settings!.executionTimeout as number > 0 && data.workflowData.settings!.executionTimeout as number; // preference on workflow setting
|
||||
workflowTimeout = data.workflowData.settings!.executionTimeout as number; // preference on workflow setting
|
||||
}
|
||||
|
||||
if (workflowTimeout) {
|
||||
const timeout = Math.min(workflowTimeout, config.get('executions.maxTimeout') as number) * 1000; // as seconds
|
||||
executionTimeout = setTimeout(() => {
|
||||
this.activeExecutions.stopExecution(executionId, 'timeout');
|
||||
const processTimeoutFunction = (timeout: number) => {
|
||||
this.activeExecutions.stopExecution(executionId, 'timeout');
|
||||
executionTimeout = setTimeout(() => subprocess.kill(), Math.max(timeout * 0.2, 5000)); // minimum 5 seconds
|
||||
};
|
||||
|
||||
executionTimeout = setTimeout(() => subprocess.kill(), Math.max(timeout * 0.2, 5000)); // minimum 5 seconds
|
||||
}, timeout);
|
||||
if (workflowTimeout > 0) {
|
||||
workflowTimeout = Math.min(workflowTimeout, config.get('executions.maxTimeout') as number) * 1000; // as seconds
|
||||
// Start timeout already now but give process at least 5 seconds to start.
|
||||
// Without it could would it be possible that the workflow executions times out before it even got started if
|
||||
// the timeout time is very short as the process start time can be quite long.
|
||||
executionTimeout = setTimeout(processTimeoutFunction, Math.max(5000, workflowTimeout), workflowTimeout);
|
||||
}
|
||||
|
||||
// Create a list of child spawned executions
|
||||
// If after the child process exits we have
|
||||
// outstanding executions, we remove them
|
||||
const childExecutionIds: string[] = [];
|
||||
|
||||
// Listen to data from the subprocess
|
||||
subprocess.on('message', async (message: IProcessMessage) => {
|
||||
if (message.type === 'end') {
|
||||
if (message.type === 'start') {
|
||||
// Now that the execution actually started set the timeout again so that does not time out to early.
|
||||
startedAt = new Date();
|
||||
if (workflowTimeout > 0) {
|
||||
clearTimeout(executionTimeout);
|
||||
executionTimeout = setTimeout(processTimeoutFunction, workflowTimeout, workflowTimeout);
|
||||
}
|
||||
|
||||
} else if (message.type === 'end') {
|
||||
clearTimeout(executionTimeout);
|
||||
this.activeExecutions.remove(executionId!, message.data.runData);
|
||||
|
||||
} else if (message.type === 'processError') {
|
||||
clearTimeout(executionTimeout);
|
||||
const executionError = message.data.executionError as IExecutionError;
|
||||
const executionError = message.data.executionError as ExecutionError;
|
||||
this.processError(executionError, startedAt, data.executionMode, executionId);
|
||||
|
||||
} else if (message.type === 'processHook') {
|
||||
this.processHookMessage(workflowHooks, message.data as IProcessMessageDataHook);
|
||||
} else if (message.type === 'timeout') {
|
||||
// Execution timed out and its process has been terminated
|
||||
const timeoutError = { message: 'Workflow execution timed out!' } as IExecutionError;
|
||||
const timeoutError = new WorkflowOperationError('Workflow execution timed out!');
|
||||
|
||||
this.processError(timeoutError, startedAt, data.executionMode, executionId);
|
||||
} else if (message.type === 'startExecution') {
|
||||
const executionId = await this.activeExecutions.add(message.data.runData);
|
||||
childExecutionIds.push(executionId);
|
||||
subprocess.send({ type: 'executionId', data: {executionId} } as IProcessMessage);
|
||||
} else if (message.type === 'finishExecution') {
|
||||
const executionIdIndex = childExecutionIds.indexOf(message.data.executionId);
|
||||
if (executionIdIndex !== -1) {
|
||||
childExecutionIds.splice(executionIdIndex, 1);
|
||||
}
|
||||
|
||||
await this.activeExecutions.remove(message.data.executionId, message.data.result);
|
||||
}
|
||||
});
|
||||
|
||||
// Also get informed when the processes does exit especially when it did crash or timed out
|
||||
subprocess.on('exit', (code, signal) => {
|
||||
subprocess.on('exit', async (code, signal) => {
|
||||
if (signal === 'SIGTERM'){
|
||||
// Execution timed out and its process has been terminated
|
||||
const timeoutError = {
|
||||
message: 'Workflow execution timed out!',
|
||||
} as IExecutionError;
|
||||
const timeoutError = new WorkflowOperationError('Workflow execution timed out!');
|
||||
|
||||
this.processError(timeoutError, startedAt, data.executionMode, executionId);
|
||||
} else if (code !== 0) {
|
||||
// Process did exit with error code, so something went wrong.
|
||||
const executionError = {
|
||||
message: 'Workflow execution process did crash for an unknown reason!',
|
||||
} as IExecutionError;
|
||||
const executionError = new WorkflowOperationError('Workflow execution process did crash for an unknown reason!');
|
||||
|
||||
this.processError(executionError, startedAt, data.executionMode, executionId);
|
||||
}
|
||||
|
||||
for(const executionId of childExecutionIds) {
|
||||
// When the child process exits, if we still have
|
||||
// pending child executions, we mark them as finished
|
||||
// They will display as unknown to the user
|
||||
// Instead of pending forever as executing when it
|
||||
// actually isn't anymore.
|
||||
await this.activeExecutions.remove(executionId);
|
||||
}
|
||||
|
||||
|
||||
clearTimeout(executionTimeout);
|
||||
});
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
CredentialTypes,
|
||||
Db,
|
||||
ExternalHooks,
|
||||
IWorkflowExecuteProcess,
|
||||
IWorkflowExecutionDataProcessWithExecution,
|
||||
NodeTypes,
|
||||
WorkflowExecuteAdditionalData,
|
||||
|
@ -16,10 +17,9 @@ import {
|
|||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
ExecutionError,
|
||||
IDataObject,
|
||||
IExecuteData,
|
||||
IExecuteWorkflowInfo,
|
||||
IExecutionError,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeData,
|
||||
|
@ -30,6 +30,7 @@ import {
|
|||
IWorkflowExecuteHooks,
|
||||
Workflow,
|
||||
WorkflowHooks,
|
||||
WorkflowOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import * as config from '../config';
|
||||
|
@ -40,6 +41,9 @@ export class WorkflowRunnerProcess {
|
|||
workflow: Workflow | undefined;
|
||||
workflowExecute: WorkflowExecute | undefined;
|
||||
executionIdCallback: (executionId: string) => void | undefined;
|
||||
childExecutions: {
|
||||
[key: string]: IWorkflowExecuteProcess,
|
||||
} = {};
|
||||
|
||||
static async stopProcess() {
|
||||
setTimeout(() => {
|
||||
|
@ -107,8 +111,18 @@ export class WorkflowRunnerProcess {
|
|||
await Db.init();
|
||||
}
|
||||
|
||||
// Start timeout for the execution
|
||||
let workflowTimeout = config.get('executions.timeout') as number; // initialize with default
|
||||
if (this.data.workflowData.settings && this.data.workflowData.settings.executionTimeout) {
|
||||
workflowTimeout = this.data.workflowData.settings!.executionTimeout as number; // preference on workflow setting
|
||||
}
|
||||
|
||||
if (workflowTimeout > 0) {
|
||||
workflowTimeout = Math.min(workflowTimeout, config.get('executions.maxTimeout') as number);
|
||||
}
|
||||
|
||||
this.workflow = new Workflow({ id: this.data.workflowData.id as string | undefined, name: this.data.workflowData.name, nodes: this.data.workflowData!.nodes, connections: this.data.workflowData!.connections, active: this.data.workflowData!.active, nodeTypes, staticData: this.data.workflowData!.staticData, settings: this.data.workflowData!.settings });
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(this.data.credentials);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(this.data.credentials, undefined, workflowTimeout <= 0 ? undefined : Date.now() + workflowTimeout * 1000);
|
||||
additionalData.hooks = this.getProcessForwardHooks();
|
||||
|
||||
const executeWorkflowFunction = additionalData.executeWorkflow;
|
||||
|
@ -123,14 +137,20 @@ export class WorkflowRunnerProcess {
|
|||
});
|
||||
let result: IRun;
|
||||
try {
|
||||
result = await executeWorkflowFunction(workflowInfo, additionalData, inputData, executionId, workflowData, runData);
|
||||
const executeWorkflowFunctionOutput = await executeWorkflowFunction(workflowInfo, additionalData, inputData, executionId, workflowData, runData) as {workflowExecute: WorkflowExecute, workflow: Workflow} as IWorkflowExecuteProcess;
|
||||
const workflowExecute = executeWorkflowFunctionOutput.workflowExecute;
|
||||
this.childExecutions[executionId] = executeWorkflowFunctionOutput;
|
||||
const workflow = executeWorkflowFunctionOutput.workflow;
|
||||
result = await workflowExecute.processRunExecutionData(workflow) as IRun;
|
||||
await externalHooks.run('workflow.postExecute', [result, workflowData]);
|
||||
await sendToParentProcess('finishExecution', { executionId, result });
|
||||
delete this.childExecutions[executionId];
|
||||
} catch (e) {
|
||||
await sendToParentProcess('finishExecution', { executionId });
|
||||
// Throw same error we had
|
||||
throw e;
|
||||
delete this.childExecutions[executionId];
|
||||
// Throw same error we had
|
||||
throw e;
|
||||
}
|
||||
|
||||
await sendToParentProcess('finishExecution', { executionId, result });
|
||||
|
||||
const returnData = WorkflowHelpers.getDataLastExecutedNodeData(result);
|
||||
return returnData!.data!.main;
|
||||
|
@ -254,6 +274,8 @@ const workflowRunner = new WorkflowRunnerProcess();
|
|||
process.on('message', async (message: IProcessMessage) => {
|
||||
try {
|
||||
if (message.type === 'startWorkflow') {
|
||||
await sendToParentProcess('start', {});
|
||||
|
||||
const runData = await workflowRunner.runWorkflow(message.data);
|
||||
|
||||
await sendToParentProcess('end', {
|
||||
|
@ -267,10 +289,22 @@ process.on('message', async (message: IProcessMessage) => {
|
|||
let runData: IRun;
|
||||
|
||||
if (workflowRunner.workflowExecute !== undefined) {
|
||||
|
||||
const executionIds = Object.keys(workflowRunner.childExecutions);
|
||||
|
||||
for (const executionId of executionIds) {
|
||||
const childWorkflowExecute = workflowRunner.childExecutions[executionId];
|
||||
runData = childWorkflowExecute.workflowExecute.getFullRunData(workflowRunner.childExecutions[executionId].startedAt);
|
||||
const timeOutError = message.type === 'timeout' ? new WorkflowOperationError('Workflow execution timed out!') : undefined;
|
||||
|
||||
// If there is any data send it to parent process, if execution timedout add the error
|
||||
await childWorkflowExecute.workflowExecute.processSuccessExecution(workflowRunner.childExecutions[executionId].startedAt, childWorkflowExecute.workflow, timeOutError);
|
||||
}
|
||||
|
||||
// Workflow started already executing
|
||||
runData = workflowRunner.workflowExecute.getFullRunData(workflowRunner.startedAt);
|
||||
|
||||
const timeOutError = message.type === 'timeout' ? { message: 'Workflow execution timed out!' } as IExecutionError : undefined;
|
||||
const timeOutError = message.type === 'timeout' ? new WorkflowOperationError('Workflow execution timed out!') : undefined;
|
||||
|
||||
// If there is any data send it to parent process, if execution timedout add the error
|
||||
await workflowRunner.workflowExecute.processSuccessExecution(workflowRunner.startedAt, workflowRunner.workflow!, timeOutError);
|
||||
|
@ -301,11 +335,14 @@ process.on('message', async (message: IProcessMessage) => {
|
|||
workflowRunner.executionIdCallback(message.data.executionId);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
// Catch all uncaught errors and forward them to parent process
|
||||
const executionError = {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
} as IExecutionError;
|
||||
...error,
|
||||
name: error!.name || 'Error',
|
||||
message: error!.message,
|
||||
stack: error!.stack,
|
||||
} as ExecutionError;
|
||||
|
||||
await sendToParentProcess('processError', {
|
||||
executionError,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-core",
|
||||
"version": "0.67.0",
|
||||
"version": "0.68.0",
|
||||
"description": "Core functionality of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -32,7 +32,7 @@
|
|||
"@types/jest": "^26.0.13",
|
||||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/node": "14.0.27",
|
||||
"@types/node": "^14.14.40",
|
||||
"@types/request-promise-native": "~1.0.15",
|
||||
"jest": "^26.4.2",
|
||||
"source-map-support": "^0.5.9",
|
||||
|
@ -47,7 +47,7 @@
|
|||
"file-type": "^14.6.2",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mime-types": "^2.1.27",
|
||||
"n8n-workflow": "~0.55.0",
|
||||
"n8n-workflow": "~0.56.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"request": "^2.88.2",
|
||||
|
|
|
@ -18,7 +18,6 @@ import {
|
|||
IWorkflowSettings as IWorkflowSettingsWorkflow,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
import { OptionsWithUri, OptionsWithUrl } from 'request';
|
||||
import * as requestPromise from 'request-promise-native';
|
||||
|
||||
|
@ -26,7 +25,6 @@ interface Constructable<T> {
|
|||
new(): T;
|
||||
}
|
||||
|
||||
|
||||
export interface IProcessMessage {
|
||||
data?: any; // tslint:disable-line:no-any
|
||||
type: string;
|
||||
|
|
|
@ -32,6 +32,7 @@ import {
|
|||
IWorkflowExecuteAdditionalData,
|
||||
IWorkflowMetadata,
|
||||
NodeHelpers,
|
||||
NodeOperationError,
|
||||
NodeParameterValue,
|
||||
Workflow,
|
||||
WorkflowActivateMode,
|
||||
|
@ -51,6 +52,9 @@ import { createHmac } from 'crypto';
|
|||
import { fromBuffer } from 'file-type';
|
||||
import { lookup } from 'mime-types';
|
||||
|
||||
const requestPromiseWithDefaults = requestPromise.defaults({
|
||||
timeout: 300000, // 5 minutes
|
||||
});
|
||||
|
||||
/**
|
||||
* Takes a buffer and converts it into the format n8n uses. It encodes the binary data as
|
||||
|
@ -309,16 +313,16 @@ export function getCredentials(workflow: Workflow, node: INode, type: string, ad
|
|||
// Get the NodeType as it has the information if the credentials are required
|
||||
const nodeType = workflow.nodeTypes.getByName(node.type);
|
||||
if (nodeType === undefined) {
|
||||
throw new Error(`Node type "${node.type}" is not known so can not get credentials!`);
|
||||
throw new NodeOperationError(node, `Node type "${node.type}" is not known so can not get credentials!`);
|
||||
}
|
||||
|
||||
if (nodeType.description.credentials === undefined) {
|
||||
throw new Error(`Node type "${node.type}" does not have any credentials defined!`);
|
||||
throw new NodeOperationError(node, `Node type "${node.type}" does not have any credentials defined!`);
|
||||
}
|
||||
|
||||
const nodeCredentialDescription = nodeType.description.credentials.find((credentialTypeDescription) => credentialTypeDescription.name === type);
|
||||
if (nodeCredentialDescription === undefined) {
|
||||
throw new Error(`Node type "${node.type}" does not have any credentials of type "${type}" defined!`);
|
||||
throw new NodeOperationError(node, `Node type "${node.type}" does not have any credentials of type "${type}" defined!`);
|
||||
}
|
||||
|
||||
if (NodeHelpers.displayParameter(additionalData.currentNodeParameters || node.parameters, nodeCredentialDescription, node.parameters) === false) {
|
||||
|
@ -333,10 +337,10 @@ export function getCredentials(workflow: Workflow, node: INode, type: string, ad
|
|||
if (nodeCredentialDescription.required === true) {
|
||||
// Credentials are required so error
|
||||
if (!node.credentials) {
|
||||
throw new Error('Node does not have any credentials set!');
|
||||
throw new NodeOperationError(node,'Node does not have any credentials set!');
|
||||
}
|
||||
if (!node.credentials[type]) {
|
||||
throw new Error(`Node does not have any credentials set for "${type}"!`);
|
||||
throw new NodeOperationError(node,`Node does not have any credentials set for "${type}"!`);
|
||||
}
|
||||
} else {
|
||||
// Credentials are not required so resolve with undefined
|
||||
|
@ -576,7 +580,7 @@ export function getExecutePollFunctions(workflow: Workflow, node: INode, additio
|
|||
},
|
||||
helpers: {
|
||||
prepareBinaryData,
|
||||
request: requestPromise,
|
||||
request: requestPromiseWithDefaults,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any> { // tslint:disable-line:no-any
|
||||
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
|
||||
},
|
||||
|
@ -642,7 +646,7 @@ export function getExecuteTriggerFunctions(workflow: Workflow, node: INode, addi
|
|||
},
|
||||
helpers: {
|
||||
prepareBinaryData,
|
||||
request: requestPromise,
|
||||
request: requestPromiseWithDefaults,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any> { // tslint:disable-line:no-any
|
||||
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
|
||||
},
|
||||
|
@ -738,7 +742,7 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx
|
|||
prepareOutputData: NodeHelpers.prepareOutputData,
|
||||
helpers: {
|
||||
prepareBinaryData,
|
||||
request: requestPromise,
|
||||
request: requestPromiseWithDefaults,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any> { // tslint:disable-line:no-any
|
||||
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
|
||||
},
|
||||
|
@ -836,7 +840,7 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData:
|
|||
},
|
||||
helpers: {
|
||||
prepareBinaryData,
|
||||
request: requestPromise,
|
||||
request: requestPromiseWithDefaults,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any> { // tslint:disable-line:no-any
|
||||
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
|
||||
},
|
||||
|
@ -892,7 +896,7 @@ export function getLoadOptionsFunctions(workflow: Workflow, node: INode, additio
|
|||
return additionalData.restApiUrl;
|
||||
},
|
||||
helpers: {
|
||||
request: requestPromise,
|
||||
request: requestPromiseWithDefaults,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any> { // tslint:disable-line:no-any
|
||||
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
|
||||
},
|
||||
|
@ -962,7 +966,7 @@ export function getExecuteHookFunctions(workflow: Workflow, node: INode, additio
|
|||
return workflow.getStaticData(type, node);
|
||||
},
|
||||
helpers: {
|
||||
request: requestPromise,
|
||||
request: requestPromiseWithDefaults,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any> { // tslint:disable-line:no-any
|
||||
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
|
||||
},
|
||||
|
@ -1062,7 +1066,7 @@ export function getExecuteWebhookFunctions(workflow: Workflow, node: INode, addi
|
|||
prepareOutputData: NodeHelpers.prepareOutputData,
|
||||
helpers: {
|
||||
prepareBinaryData,
|
||||
request: requestPromise,
|
||||
request: requestPromiseWithDefaults,
|
||||
requestOAuth2(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, oAuth2Options?: IOAuth2Options): Promise<any> { // tslint:disable-line:no-any
|
||||
return requestOAuth2.call(this, credentialsType, requestOptions, node, additionalData, oAuth2Options);
|
||||
},
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as PCancelable from 'p-cancelable';
|
||||
|
||||
import {
|
||||
ExecutionError,
|
||||
IConnection,
|
||||
IDataObject,
|
||||
IExecuteData,
|
||||
IExecutionError,
|
||||
INode,
|
||||
INodeConnections,
|
||||
INodeExecutionData,
|
||||
|
@ -17,6 +17,7 @@ import {
|
|||
IWorkflowExecuteAdditionalData,
|
||||
Workflow,
|
||||
WorkflowExecuteMode,
|
||||
WorkflowOperationError,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
NodeExecuteFunctions,
|
||||
|
@ -490,7 +491,7 @@ export class WorkflowExecute {
|
|||
|
||||
// Variables which hold temporary data for each node-execution
|
||||
let executionData: IExecuteData;
|
||||
let executionError: IExecutionError | undefined;
|
||||
let executionError: ExecutionError | undefined;
|
||||
let executionNode: INode;
|
||||
let nodeSuccessData: INodeExecutionData[][] | null | undefined;
|
||||
let runIndex: number;
|
||||
|
@ -517,8 +518,10 @@ export class WorkflowExecute {
|
|||
try {
|
||||
await this.executeHook('workflowExecuteBefore', [workflow]);
|
||||
} catch (error) {
|
||||
|
||||
// Set the error that it can be saved correctly
|
||||
executionError = {
|
||||
...error,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
};
|
||||
|
@ -547,6 +550,10 @@ export class WorkflowExecute {
|
|||
executionLoop:
|
||||
while (this.runExecutionData.executionData!.nodeExecutionStack.length !== 0) {
|
||||
|
||||
if (this.additionalData.executionTimeoutTimestamp !== undefined && Date.now() >= this.additionalData.executionTimeoutTimestamp) {
|
||||
gotCancel = true;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (gotCancel === true) {
|
||||
return Promise.resolve();
|
||||
|
@ -683,9 +690,11 @@ export class WorkflowExecute {
|
|||
|
||||
break;
|
||||
} catch (error) {
|
||||
|
||||
this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
|
||||
|
||||
executionError = {
|
||||
...error,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
};
|
||||
|
@ -784,7 +793,7 @@ export class WorkflowExecute {
|
|||
})()
|
||||
.then(async () => {
|
||||
if (gotCancel && executionError === undefined) {
|
||||
return this.processSuccessExecution(startedAt, workflow, { message: 'Workflow has been canceled!' } as IExecutionError);
|
||||
return this.processSuccessExecution(startedAt, workflow, new WorkflowOperationError('Workflow has been canceled!'));
|
||||
}
|
||||
return this.processSuccessExecution(startedAt, workflow, executionError);
|
||||
})
|
||||
|
@ -792,6 +801,7 @@ export class WorkflowExecute {
|
|||
const fullRunData = this.getFullRunData(startedAt);
|
||||
|
||||
fullRunData.data.resultData.error = {
|
||||
...error,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
};
|
||||
|
@ -815,7 +825,7 @@ export class WorkflowExecute {
|
|||
|
||||
|
||||
// @ts-ignore
|
||||
async processSuccessExecution(startedAt: Date, workflow: Workflow, executionError?: IExecutionError): PCancelable<IRun> {
|
||||
async processSuccessExecution(startedAt: Date, workflow: Workflow, executionError?: ExecutionError): PCancelable<IRun> {
|
||||
const fullRunData = this.getFullRunData(startedAt);
|
||||
|
||||
if (executionError !== undefined) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-editor-ui",
|
||||
"version": "0.84.0",
|
||||
"version": "0.87.0",
|
||||
"description": "Workflow Editor UI for n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -36,7 +36,7 @@
|
|||
"@types/jest": "^26.0.13",
|
||||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/lodash.set": "^4.3.6",
|
||||
"@types/node": "14.0.27",
|
||||
"@types/node": "^14.14.40",
|
||||
"@types/quill": "^2.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^2.13.0",
|
||||
"@typescript-eslint/parser": "^2.13.0",
|
||||
|
@ -65,7 +65,7 @@
|
|||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"n8n-workflow": "~0.55.0",
|
||||
"n8n-workflow": "~0.56.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"normalize-wheel": "^1.0.1",
|
||||
"prismjs": "^1.17.1",
|
||||
|
|
|
@ -428,3 +428,20 @@ export interface ITimeoutHMS {
|
|||
}
|
||||
|
||||
export type WorkflowTitleStatus = 'EXECUTING' | 'IDLE' | 'ERROR';
|
||||
|
||||
export type MenuItemType = 'link';
|
||||
export type MenuItemPosition = 'top' | 'bottom';
|
||||
|
||||
export interface IMenuItem {
|
||||
id: string;
|
||||
type: MenuItemType;
|
||||
position: MenuItemPosition;
|
||||
properties: ILinkMenuItemProperties;
|
||||
}
|
||||
|
||||
export interface ILinkMenuItemProperties {
|
||||
title: string;
|
||||
icon: string;
|
||||
href: string;
|
||||
newWindow?: boolean;
|
||||
}
|
|
@ -30,7 +30,7 @@
|
|||
Credential type:
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<el-select v-model="credentialType" filterable placeholder="Select Type" size="small">
|
||||
<el-select v-model="credentialType" filterable placeholder="Select Type" size="small" ref="credentialsDropdown">
|
||||
<el-option
|
||||
v-for="item in credentialTypes"
|
||||
:key="item.name"
|
||||
|
@ -198,6 +198,9 @@ export default mixins(
|
|||
|
||||
this.credentialData = currentCredentials;
|
||||
} else {
|
||||
Vue.nextTick(() => {
|
||||
(this.$refs.credentialsDropdown as HTMLDivElement).focus();
|
||||
});
|
||||
if (this.credentialType || this.setCredentialType) {
|
||||
const credentialType = this.$store.getters.credentialType(this.credentialType || this.setCredentialType);
|
||||
if (credentialType === null) {
|
||||
|
|
177
packages/editor-ui/src/components/Error/NodeViewError.vue
Normal file
|
@ -0,0 +1,177 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="error-header">
|
||||
<div class="error-message">ERROR: {{error.message}}</div>
|
||||
<div class="error-description" v-if="error.description">{{error.description}}</div>
|
||||
</div>
|
||||
<details>
|
||||
<summary class="error-details__summary">
|
||||
<font-awesome-icon class="error-details__icon" icon="angle-right" /> Details
|
||||
</summary>
|
||||
<div class="error-details__content">
|
||||
<div v-if="error.timestamp">
|
||||
<el-card class="box-card" shadow="never">
|
||||
<div slot="header" class="clearfix box-card__title">
|
||||
<span>Time</span>
|
||||
</div>
|
||||
<div>
|
||||
{{new Date(error.timestamp).toLocaleString()}}
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<div v-if="error.httpCode">
|
||||
<el-card class="box-card" shadow="never">
|
||||
<div slot="header" class="clearfix box-card__title">
|
||||
<span>HTTP-Code</span>
|
||||
</div>
|
||||
<div>
|
||||
{{error.httpCode}}
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<div v-if="error.cause">
|
||||
<el-card class="box-card" shadow="never">
|
||||
<div slot="header" class="clearfix box-card__title">
|
||||
<span>Cause</span>
|
||||
<br>
|
||||
<span class="box-card__subtitle">Data below may contain sensitive information. Proceed with caution when sharing.</span>
|
||||
</div>
|
||||
<div>
|
||||
<el-button class="copy-button" @click="copyCause" circle type="text" title="Copy to clipboard">
|
||||
<font-awesome-icon icon="copy" />
|
||||
</el-button>
|
||||
<vue-json-pretty
|
||||
:data="error.cause"
|
||||
:deep="3"
|
||||
:showLength="true"
|
||||
selectableType="single"
|
||||
path="error"
|
||||
class="json-data"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<div v-if="error.stack">
|
||||
<el-card class="box-card" shadow="never">
|
||||
<div slot="header" class="clearfix box-card__title">
|
||||
<span>Stack</span>
|
||||
</div>
|
||||
<div>
|
||||
<pre><code>{{error.stack}}</code></pre>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
//@ts-ignore
|
||||
import VueJsonPretty from 'vue-json-pretty';
|
||||
import { copyPaste } from '@/components/mixins/copyPaste';
|
||||
import { showMessage } from '@/components/mixins/showMessage';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
|
||||
export default mixins(
|
||||
copyPaste,
|
||||
showMessage,
|
||||
).extend({
|
||||
name: 'NodeErrorView',
|
||||
props: [
|
||||
'error',
|
||||
],
|
||||
components: {
|
||||
VueJsonPretty,
|
||||
},
|
||||
methods: {
|
||||
copyCause() {
|
||||
this.copyToClipboard(JSON.stringify(this.error.cause));
|
||||
this.copySuccess();
|
||||
},
|
||||
copySuccess() {
|
||||
this.$showMessage({
|
||||
title: 'Copied to clipboard',
|
||||
message: '',
|
||||
type: 'info',
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.error-header {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff0000;
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.error-description {
|
||||
margin-top: 10px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.error-details__summary {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
outline:none;
|
||||
}
|
||||
|
||||
.error-details__icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
details > summary {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
details > summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
details[open] {
|
||||
.error-details__icon {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.error-details__content {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.el-divider__text {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.box-card {
|
||||
margin-top: 1em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.box-card__title {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.box-card__subtitle {
|
||||
font-weight: 200;
|
||||
font-style: italic;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
position: absolute;
|
||||
font-size: 1.1rem;
|
||||
right: 50px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -21,6 +21,22 @@
|
|||
</a>
|
||||
</el-menu-item>
|
||||
|
||||
<el-menu-item
|
||||
v-for="item in sidebarMenuTopItems"
|
||||
:key="item.id"
|
||||
:index="item.id"
|
||||
>
|
||||
<a
|
||||
v-if="item.type === 'link'"
|
||||
:href="item.properties.href"
|
||||
:target="item.properties.newWindow ? '_blank' : '_self'"
|
||||
class="primary-item"
|
||||
>
|
||||
<font-awesome-icon :icon="item.properties.icon" />
|
||||
<span slot="title" class="item-title-root">{{ item.properties.title }}</span>
|
||||
</a>
|
||||
</el-menu-item>
|
||||
|
||||
<el-submenu index="workflow" title="Workflow">
|
||||
<template slot="title">
|
||||
<font-awesome-icon icon="network-wired"/>
|
||||
|
@ -152,6 +168,22 @@
|
|||
</el-menu-item>
|
||||
</el-submenu>
|
||||
|
||||
<el-menu-item
|
||||
v-for="item in sidebarMenuBottomItems"
|
||||
:key="item.id"
|
||||
:index="item.id"
|
||||
>
|
||||
<a
|
||||
v-if="item.type === 'link'"
|
||||
:href="item.properties.href"
|
||||
:target="item.properties.newWindow ? '_blank' : '_self'"
|
||||
class="primary-item"
|
||||
>
|
||||
<font-awesome-icon :icon="item.properties.icon" />
|
||||
<span slot="title" class="item-title-root">{{ item.properties.title }}</span>
|
||||
</a>
|
||||
</el-menu-item>
|
||||
|
||||
</el-menu>
|
||||
|
||||
</div>
|
||||
|
@ -167,6 +199,7 @@ import {
|
|||
IExecutionResponse,
|
||||
IExecutionsStopData,
|
||||
IWorkflowDataUpdate,
|
||||
IMenuItem,
|
||||
} from '../Interface';
|
||||
|
||||
import About from '@/components/About.vue';
|
||||
|
@ -266,6 +299,12 @@ export default mixins(
|
|||
workflowRunning (): boolean {
|
||||
return this.$store.getters.isActionActive('workflowRunning');
|
||||
},
|
||||
sidebarMenuTopItems(): IMenuItem[] {
|
||||
return this.$store.getters.sidebarMenuItems.filter((item: IMenuItem) => item.position === 'top');
|
||||
},
|
||||
sidebarMenuBottomItems(): IMenuItem[] {
|
||||
return this.$store.getters.sidebarMenuItems.filter((item: IMenuItem) => item.position === 'bottom');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clearExecutionData () {
|
||||
|
@ -533,6 +572,11 @@ export default mixins(
|
|||
.el-menu-item {
|
||||
a {
|
||||
color: #666;
|
||||
|
||||
&.primary-item {
|
||||
color: $--color-primary;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
|
||||
&.logo-item {
|
||||
|
|
|
@ -81,8 +81,7 @@
|
|||
<div class="data-display-content">
|
||||
<span v-if="node && workflowRunData !== null && workflowRunData.hasOwnProperty(node.name)">
|
||||
<div v-if="workflowRunData[node.name][runIndex].error" class="error-display">
|
||||
<div class="error-message">ERROR: {{workflowRunData[node.name][runIndex].error.message}}</div>
|
||||
<pre><code>{{workflowRunData[node.name][runIndex].error.stack}}</code></pre>
|
||||
<NodeErrorView :error="workflowRunData[node.name][runIndex].error" />
|
||||
</div>
|
||||
<span v-else>
|
||||
<div v-if="showData === false" class="to-much-data">
|
||||
|
@ -226,6 +225,7 @@ import {
|
|||
} from '@/constants';
|
||||
|
||||
import BinaryDataDisplay from '@/components/BinaryDataDisplay.vue';
|
||||
import NodeErrorView from '@/components/Error/NodeViewError.vue';
|
||||
|
||||
import { copyPaste } from '@/components/mixins/copyPaste';
|
||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
|
@ -247,6 +247,7 @@ export default mixins(
|
|||
name: 'RunData',
|
||||
components: {
|
||||
BinaryDataDisplay,
|
||||
NodeErrorView,
|
||||
VueJsonPretty,
|
||||
},
|
||||
data () {
|
||||
|
@ -739,13 +740,6 @@ export default mixins(
|
|||
}
|
||||
}
|
||||
|
||||
.error-display {
|
||||
.error-message {
|
||||
color: #ff0000;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
text-align: left;
|
||||
|
|
|
@ -207,8 +207,19 @@ export const pushConnection = mixins(
|
|||
if (runDataExecuted.finished !== true) {
|
||||
// There was a problem with executing the workflow
|
||||
let errorMessage = 'There was a problem executing the workflow!';
|
||||
|
||||
if (runDataExecuted.data.resultData.error && runDataExecuted.data.resultData.error.message) {
|
||||
errorMessage = `There was a problem executing the workflow:<br /><strong>"${runDataExecuted.data.resultData.error.message}"</strong>`;
|
||||
let nodeName: string | undefined;
|
||||
if (runDataExecuted.data.resultData.error.node) {
|
||||
nodeName = typeof runDataExecuted.data.resultData.error.node === 'string'
|
||||
? runDataExecuted.data.resultData.error.node
|
||||
: runDataExecuted.data.resultData.error.node.name;
|
||||
}
|
||||
|
||||
const receivedError = nodeName
|
||||
? `${nodeName}: ${runDataExecuted.data.resultData.error.message}`
|
||||
: runDataExecuted.data.resultData.error.message;
|
||||
errorMessage = `There was a problem executing the workflow:<br /><strong>"${receivedError}"</strong>`;
|
||||
}
|
||||
this.$titleSet(workflow.name, 'ERROR');
|
||||
this.$showMessage({
|
||||
|
|
|
@ -56,6 +56,7 @@ import {
|
|||
faFilePdf,
|
||||
faFolderOpen,
|
||||
faHdd,
|
||||
faHome,
|
||||
faHourglass,
|
||||
faImage,
|
||||
faInbox,
|
||||
|
@ -136,6 +137,7 @@ library.add(faFileImport);
|
|||
library.add(faFilePdf);
|
||||
library.add(faFolderOpen);
|
||||
library.add(faHdd);
|
||||
library.add(faHome);
|
||||
library.add(faHourglass);
|
||||
library.add(faImage);
|
||||
library.add(faInbox);
|
||||
|
|
|
@ -459,6 +459,10 @@ h1, h2, h3, h4, h5, h6 {
|
|||
border: none;
|
||||
}
|
||||
|
||||
.el-notification__content {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
// Custom scrollbar
|
||||
::-webkit-scrollbar {
|
||||
|
|
|
@ -21,12 +21,13 @@ import {
|
|||
ICredentialsResponse,
|
||||
IExecutionResponse,
|
||||
IExecutionsCurrentSummaryExtended,
|
||||
IPushDataExecutionFinished,
|
||||
IPushDataNodeExecuteAfter,
|
||||
IWorkflowDb,
|
||||
IMenuItem,
|
||||
INodeUi,
|
||||
INodeUpdatePropertiesInformation,
|
||||
IPushDataExecutionFinished,
|
||||
IPushDataNodeExecuteAfter,
|
||||
IUpdateInformation,
|
||||
IWorkflowDb,
|
||||
XYPositon,
|
||||
} from './Interface';
|
||||
|
||||
|
@ -79,6 +80,7 @@ export const store = new Vuex.Store({
|
|||
nodes: [] as INodeUi[],
|
||||
settings: {} as IWorkflowSettings,
|
||||
} as IWorkflowDb,
|
||||
sidebarMenuItems: [] as IMenuItem[],
|
||||
},
|
||||
mutations: {
|
||||
// Active Actions
|
||||
|
@ -597,6 +599,11 @@ export const store = new Vuex.Store({
|
|||
Vue.set(state, 'nodeTypes', updatedNodes);
|
||||
state.nodeTypes = updatedNodes;
|
||||
},
|
||||
|
||||
addSidebarMenuItems (state, menuItems: IMenuItem[]) {
|
||||
const updated = state.sidebarMenuItems.concat(menuItems);
|
||||
Vue.set(state, 'sidebarMenuItems', updated);
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
|
||||
|
@ -834,6 +841,9 @@ export const store = new Vuex.Store({
|
|||
return workflowRunData[nodeName];
|
||||
},
|
||||
|
||||
sidebarMenuItems: (state): IMenuItem[] => {
|
||||
return state.sidebarMenuItems;
|
||||
},
|
||||
},
|
||||
|
||||
});
|
||||
|
|
|
@ -55,12 +55,12 @@
|
|||
"@oclif/command": "^1.5.18",
|
||||
"@oclif/errors": "^1.2.2",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/node": "14.0.27",
|
||||
"@types/node": "^14.14.40",
|
||||
"change-case": "^4.1.1",
|
||||
"copyfiles": "^2.1.1",
|
||||
"inquirer": "^7.0.1",
|
||||
"n8n-core": "^0.48.0",
|
||||
"n8n-workflow": "^0.42.0",
|
||||
"n8n-core": "^0.67.0",
|
||||
"n8n-workflow": "^0.55.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"replace-in-file": "^6.0.0",
|
||||
"request": "^2.88.2",
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
const scopes = [
|
||||
'https://www.googleapis.com/auth/bigquery',
|
||||
];
|
||||
|
||||
export class GoogleBigQueryOAuth2Api implements ICredentialType {
|
||||
name = 'googleBigQueryOAuth2Api';
|
||||
extends = [
|
||||
'googleOAuth2Api',
|
||||
];
|
||||
displayName = 'Google BigQuery OAuth2 API';
|
||||
documentationUrl = 'google';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: scopes.join(' '),
|
||||
},
|
||||
];
|
||||
}
|
18
packages/nodes-base/credentials/MailcheckApi.credentials.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class MailcheckApi implements ICredentialType {
|
||||
name = 'mailcheckApi';
|
||||
displayName = 'Mailcheck API';
|
||||
documentationUrl = 'mailcheck';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
const scopes = [
|
||||
'boards:write',
|
||||
'boards:read',
|
||||
];
|
||||
|
||||
export class MondayComOAuth2Api implements ICredentialType {
|
||||
name = 'mondayComOAuth2Api';
|
||||
extends = [
|
||||
'oAuth2Api',
|
||||
];
|
||||
displayName = 'Monday.com OAuth2 API';
|
||||
documentationUrl = 'monday';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Authorization URL',
|
||||
name: 'authUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://auth.monday.com/oauth2/authorize',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Access Token URL',
|
||||
name: 'accessTokenUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://auth.monday.com/oauth2/token',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: scopes.join(' '),
|
||||
},
|
||||
{
|
||||
displayName: 'Auth URI Query Parameters',
|
||||
name: 'authQueryParameters',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'body',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -48,6 +48,63 @@ export class MySql implements ICredentialType {
|
|||
type: 'number' as NodePropertyTypes,
|
||||
default: 10000,
|
||||
description: 'The milliseconds before a timeout occurs during the initial connection to the MySQL server.',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'SSL',
|
||||
name: 'ssl',
|
||||
type: 'boolean' as NodePropertyTypes,
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'CA Certificate',
|
||||
name: 'caCertificate',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
password: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
ssl: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Client Private Key',
|
||||
name: 'clientPrivateKey',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
password: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
ssl: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Client Certificate',
|
||||
name: 'clientCertificate',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
password: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
ssl: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
IDisplayOptions,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -51,8 +52,22 @@ export class RabbitMQ implements ICredentialType {
|
|||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Client Certificate',
|
||||
name: 'cert',
|
||||
displayName: 'Passwordless',
|
||||
name: 'passwordless',
|
||||
type: 'boolean' as NodePropertyTypes,
|
||||
displayOptions: {
|
||||
show: {
|
||||
ssl: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'Passwordless connection with certificates (SASL mechanism EXTERNAL)',
|
||||
},
|
||||
{
|
||||
displayName: 'CA Certificates',
|
||||
name: 'ca',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
typeOptions: {
|
||||
password: true,
|
||||
|
@ -65,6 +80,26 @@ export class RabbitMQ implements ICredentialType {
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'SSL CA Certificates to use.',
|
||||
},
|
||||
{
|
||||
displayName: 'Client Certificate',
|
||||
name: 'cert',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
typeOptions: {
|
||||
password: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
ssl: [
|
||||
true,
|
||||
],
|
||||
passwordless: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
} as IDisplayOptions,
|
||||
default: '',
|
||||
description: 'SSL Client Certificate to use.',
|
||||
},
|
||||
{
|
||||
|
@ -79,6 +114,9 @@ export class RabbitMQ implements ICredentialType {
|
|||
ssl: [
|
||||
true,
|
||||
],
|
||||
passwordless: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
|
@ -96,31 +134,13 @@ export class RabbitMQ implements ICredentialType {
|
|||
ssl: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'SSL passphrase to use.',
|
||||
},
|
||||
{
|
||||
displayName: 'CA Certificates',
|
||||
name: 'ca',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
typeOptions: {
|
||||
password: true,
|
||||
},
|
||||
// typeOptions: {
|
||||
// multipleValues: true,
|
||||
// multipleValueButtonText: 'Add Certificate',
|
||||
// },
|
||||
displayOptions: {
|
||||
show: {
|
||||
ssl: [
|
||||
passwordless: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'SSL CA Certificates to use.',
|
||||
description: 'SSL passphrase to use.',
|
||||
},
|
||||
// {
|
||||
// displayName: 'Client ID',
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
|
||||
export class SentryIoServerApi implements ICredentialType {
|
||||
name = 'sentryIoServerApi';
|
||||
displayName = 'Sentry.io API';
|
||||
displayName = 'Sentry.io Server API';
|
||||
documentationUrl = 'sentryIo';
|
||||
properties = [
|
||||
{
|
||||
|
|
|
@ -3,7 +3,6 @@ import {
|
|||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
export class WebflowOAuth2Api implements ICredentialType {
|
||||
name = 'webflowOAuth2Api';
|
||||
extends = [
|
||||
|
|
15
packages/nodes-base/nodes/ActivationTrigger.node.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"node": "n8n-nodes-base.activationTrigger",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": [
|
||||
"Core Nodes"
|
||||
],
|
||||
"resources": {
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.activationTrigger/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
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,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
"node": "n8n-nodes-base.activeCampaign",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"details": "ActiveCampaign is a cloud software platform that allows customer experience automation, which combines email marketing, marketing automation, sales automation, and CRM categories. Use this node when you want to interact with your ActiveCampaign account.",
|
||||
"categories": [
|
||||
"Marketing & Content"
|
||||
],
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -431,7 +432,7 @@ export class ActiveCampaign implements INodeType {
|
|||
addAdditionalFields(body.contact as IDataObject, updateFields);
|
||||
|
||||
} else {
|
||||
throw new Error(`The operation "${operation}" is not known`);
|
||||
throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
|
||||
}
|
||||
} else if (resource === 'account') {
|
||||
if (operation === 'create') {
|
||||
|
@ -512,7 +513,7 @@ export class ActiveCampaign implements INodeType {
|
|||
addAdditionalFields(body.account as IDataObject, updateFields);
|
||||
|
||||
} else {
|
||||
throw new Error(`The operation "${operation}" is not known`);
|
||||
throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
|
||||
}
|
||||
} else if (resource === 'accountContact') {
|
||||
if (operation === 'create') {
|
||||
|
@ -562,7 +563,7 @@ export class ActiveCampaign implements INodeType {
|
|||
endpoint = `/api/3/accountContacts/${accountContactId}`;
|
||||
|
||||
} else {
|
||||
throw new Error(`The operation "${operation}" is not known`);
|
||||
throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
|
||||
}
|
||||
} else if (resource === 'contactTag') {
|
||||
if (operation === 'add') {
|
||||
|
@ -592,7 +593,7 @@ export class ActiveCampaign implements INodeType {
|
|||
endpoint = `/api/3/contactTags/${contactTagId}`;
|
||||
|
||||
} else {
|
||||
throw new Error(`The operation "${operation}" is not known`);
|
||||
throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
|
||||
}
|
||||
} else if (resource === 'contactList') {
|
||||
if (operation === 'add') {
|
||||
|
@ -630,7 +631,7 @@ export class ActiveCampaign implements INodeType {
|
|||
dataKey = 'contacts';
|
||||
|
||||
} else {
|
||||
throw new Error(`The operation "${operation}" is not known`);
|
||||
throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
|
||||
}
|
||||
} else if (resource === 'list') {
|
||||
if (operation === 'getAll') {
|
||||
|
@ -732,7 +733,7 @@ export class ActiveCampaign implements INodeType {
|
|||
addAdditionalFields(body.tag as IDataObject, updateFields);
|
||||
|
||||
} else {
|
||||
throw new Error(`The operation "${operation}" is not known`);
|
||||
throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
|
||||
}
|
||||
} else if (resource === 'deal') {
|
||||
if (operation === 'create') {
|
||||
|
@ -851,7 +852,7 @@ export class ActiveCampaign implements INodeType {
|
|||
endpoint = `/api/3/deals/${dealId}/notes/${dealNoteId}`;
|
||||
|
||||
} else {
|
||||
throw new Error(`The operation "${operation}" is not known`);
|
||||
throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
|
||||
}
|
||||
} else if (resource === 'connection') {
|
||||
if (operation === 'create') {
|
||||
|
@ -926,7 +927,7 @@ export class ActiveCampaign implements INodeType {
|
|||
endpoint = `/api/3/connections`;
|
||||
|
||||
} else {
|
||||
throw new Error(`The operation "${operation}" is not known`);
|
||||
throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
|
||||
}
|
||||
} else if (resource === 'ecommerceOrder') {
|
||||
if (operation === 'create') {
|
||||
|
@ -1024,7 +1025,7 @@ export class ActiveCampaign implements INodeType {
|
|||
endpoint = `/api/3/ecomOrders`;
|
||||
|
||||
} else {
|
||||
throw new Error(`The operation "${operation}" is not known`);
|
||||
throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
|
||||
}
|
||||
} else if (resource === 'ecommerceCustomer') {
|
||||
if (operation === 'create') {
|
||||
|
@ -1114,7 +1115,7 @@ export class ActiveCampaign implements INodeType {
|
|||
endpoint = `/api/3/ecomCustomers`;
|
||||
|
||||
} else {
|
||||
throw new Error(`The operation "${operation}" is not known`);
|
||||
throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
|
||||
}
|
||||
} else if (resource === 'ecommerceOrderProducts') {
|
||||
if (operation === 'getByProductId') {
|
||||
|
@ -1160,11 +1161,11 @@ export class ActiveCampaign implements INodeType {
|
|||
endpoint = `/api/3/ecomOrderProducts`;
|
||||
|
||||
} else {
|
||||
throw new Error(`The operation "${operation}" is not known`);
|
||||
throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known`);
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new Error(`The resource "${resource}" is not known!`);
|
||||
throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`);
|
||||
}
|
||||
|
||||
let responseData;
|
||||
|
|
|
@ -116,7 +116,7 @@ export class ActiveCampaignTrigger implements INodeType {
|
|||
const endpoint = `/api/3/webhooks/${webhookData.webhookId}`;
|
||||
try {
|
||||
await activeCampaignApiRequest.call(this, 'GET', endpoint, {});
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject, ILoadOptionsFunctions, INodeProperties,
|
||||
IDataObject, ILoadOptionsFunctions, INodeProperties, NodeApiError, NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { OptionsWithUri } from 'request';
|
||||
|
@ -28,7 +28,7 @@ export interface IProduct {
|
|||
export async function activeCampaignApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: IDataObject, query?: IDataObject, dataKey?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('activeCampaignApi');
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
if (query === undefined) {
|
||||
|
@ -53,7 +53,7 @@ export async function activeCampaignApiRequest(this: IHookFunctions | IExecuteFu
|
|||
const responseData = await this.helpers.request!(options);
|
||||
|
||||
if (responseData.success === false) {
|
||||
throw new Error(`ActiveCampaign error response: ${responseData.error} (${responseData.error_info})`);
|
||||
throw new NodeApiError(this.getNode(), responseData);
|
||||
}
|
||||
|
||||
if (dataKey === undefined) {
|
||||
|
@ -63,13 +63,7 @@ export async function activeCampaignApiRequest(this: IHookFunctions | IExecuteFu
|
|||
}
|
||||
|
||||
} catch (error) {
|
||||
if (error.statusCode === 403) {
|
||||
// Return a clear error
|
||||
throw new Error('The ActiveCampaign credentials are not valid!');
|
||||
}
|
||||
|
||||
// If that data does not exist for some reason return the actual error
|
||||
throw error;
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
ILoadOptionsFunctions,
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-core';
|
||||
import { IDataObject } from 'n8n-workflow';
|
||||
import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow';
|
||||
|
||||
export async function acuitySchedulingApiRequest(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 authenticationMethod = this.getNodeParameter('authentication', 0);
|
||||
|
@ -27,7 +27,7 @@ export async function acuitySchedulingApiRequest(this: IHookFunctions | IExecute
|
|||
if (authenticationMethod === 'apiKey') {
|
||||
const credentials = this.getCredentials('acuitySchedulingApi');
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
options.auth = {
|
||||
|
@ -42,6 +42,6 @@ export async function acuitySchedulingApiRequest(this: IHookFunctions | IExecute
|
|||
return await this.helpers.requestOAuth2!.call(this, 'acuitySchedulingOAuth2Api', options, true);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error('Acuity Scheduling Error: ' + error.message);
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
INodeType,
|
||||
INodeTypeDescription,
|
||||
IWebhookResponseData,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -187,7 +188,7 @@ export class AffinityTrigger implements INodeType {
|
|||
const webhookUrl = this.getNodeWebhookUrl('default') as string;
|
||||
|
||||
if (webhookUrl.includes('%20')) {
|
||||
throw new Error('The name of the Affinity Trigger Node is not allowed to contain any spaces!');
|
||||
throw new NodeOperationError(this.getNode(), 'The name of the Affinity Trigger Node is not allowed to contain any spaces!');
|
||||
}
|
||||
|
||||
const events = this.getNodeParameter('events') as string[];
|
||||
|
|
|
@ -12,6 +12,8 @@ import {
|
|||
IDataObject,
|
||||
IHookFunctions,
|
||||
IWebhookFunctions,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function affinityApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
@ -19,7 +21,7 @@ export async function affinityApiRequest(this: IExecuteFunctions | IWebhookFunct
|
|||
const credentials = this.getCredentials('affinityApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
const apiKey = `:${credentials.apiKey}`;
|
||||
|
@ -47,11 +49,7 @@ export async function affinityApiRequest(this: IExecuteFunctions | IWebhookFunct
|
|||
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(`Affinity error response [${error.statusCode}]: ${errorMessage}`);
|
||||
}
|
||||
throw error;
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@ import {
|
|||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription
|
||||
INodeTypeDescription,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -149,7 +150,7 @@ export class AgileCrm implements INodeType {
|
|||
Object.assign(body, JSON.parse(additionalFieldsJson));
|
||||
|
||||
} else {
|
||||
throw new Error('Additional fields must be a valid JSON');
|
||||
throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -305,7 +306,7 @@ export class AgileCrm implements INodeType {
|
|||
Object.assign(body, JSON.parse(additionalFieldsJson));
|
||||
|
||||
} else {
|
||||
throw new Error('Additional fields must be a valid JSON');
|
||||
throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -483,7 +484,7 @@ export class AgileCrm implements INodeType {
|
|||
if (validateJSON(additionalFieldsJson) !== undefined) {
|
||||
Object.assign(body, JSON.parse(additionalFieldsJson));
|
||||
} else {
|
||||
throw new Error('Additional fields must be a valid JSON');
|
||||
throw new NodeOperationError(this.getNode(), 'Additional fields must be a valid JSON');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -525,7 +526,7 @@ export class AgileCrm implements INodeType {
|
|||
Object.assign(body, JSON.parse(additionalFieldsJson));
|
||||
|
||||
} else {
|
||||
throw new Error('Additional fields must be valid JSON');
|
||||
throw new NodeOperationError(this.getNode(), 'Additional fields must be valid JSON');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
IDataObject, NodeApiError,
|
||||
} from 'n8n-workflow';
|
||||
import { IContactUpdate } from './ContactInterface';
|
||||
|
||||
|
@ -39,7 +39,7 @@ export async function agileCrmApiRequest(this: IHookFunctions | IExecuteFunction
|
|||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
throw new Error(`AgileCRM error response: ${error.message}`);
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -114,9 +114,9 @@ export async function agileCrmApiRequestUpdate(this: IHookFunctions | IExecuteFu
|
|||
|
||||
} catch (error) {
|
||||
if (successfulUpdates.length === 0) {
|
||||
throw new Error(`AgileCRM error response: ${error.message}`);
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
} else {
|
||||
throw new Error(`Not all properties updated. Updated properties: ${successfulUpdates.join(', ')} \n \nAgileCRM error response: ${error.message}`);
|
||||
throw new NodeApiError(this.getNode(), error, { message: `Not all properties updated. Updated properties: ${successfulUpdates.join(', ')}`, description: error.message, httpCode: error.statusCode });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,9 +17,50 @@
|
|||
}
|
||||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "2021 Goals: Level Up Your Vocabulary With Vonage and n8n",
|
||||
"icon": "🎯",
|
||||
"url": "https://n8n.io/blog/2021-goals-level-up-your-vocabulary-with-vonage-and-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "2021: The Year to Automate the New You with n8n",
|
||||
"icon": "☀️",
|
||||
"url": "https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "Building an expense tracking app in 10 minutes",
|
||||
"icon": "📱",
|
||||
"url": "https://n8n.io/blog/building-an-expense-tracking-app-in-10-minutes/"
|
||||
},
|
||||
{
|
||||
"label": "Why this Product Manager loves workflow automation with n8n",
|
||||
"icon": "🧠",
|
||||
"url": "https://n8n.io/blog/why-this-product-manager-loves-workflow-automation-with-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "Learn to Build Powerful API Endpoints Using Webhooks",
|
||||
"icon": "🧰",
|
||||
"url": "https://n8n.io/blog/learn-to-build-powerful-api-endpoints-using-webhooks/"
|
||||
},
|
||||
{
|
||||
"label": "Sending SMS the Low-Code Way with Airtable, Twilio Programmable SMS, and n8n",
|
||||
"icon": "📱",
|
||||
"url": "https://n8n.io/blog/sending-sms-the-low-code-way-with-airtable-twilio-programmable-sms-and-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "Automating Conference Organization Processes with n8n",
|
||||
"url": "https://medium.com/n8n-io/automating-conference-organization-processes-with-n8n-ab8f64a7a520"
|
||||
"icon": "🙋♀️",
|
||||
"url": "https://n8n.io/blog/automating-conference-organization-processes-with-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "Benefits of automation and n8n: An interview with HubSpot's Hugh Durkin",
|
||||
"icon": "🎖",
|
||||
"url": "https://n8n.io/blog/benefits-of-automation-and-n8n-an-interview-with-hubspots-hugh-durkin/"
|
||||
},
|
||||
{
|
||||
"label": "How Goomer automated their operations with over 200 n8n workflows",
|
||||
"icon": "🛵",
|
||||
"url": "https://n8n.io/blog/how-goomer-automated-their-operations-with-over-200-n8n-workflows/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -636,7 +637,7 @@ export class Airtable implements INodeType {
|
|||
}
|
||||
|
||||
} else {
|
||||
throw new Error(`The operation "${operation}" is not known!`);
|
||||
throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`);
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -170,7 +171,7 @@ export class AirtableTrigger implements INodeType {
|
|||
|
||||
if (Array.isArray(records) && records.length) {
|
||||
if (this.getMode() === 'manual' && records[0].fields[triggerField] === undefined) {
|
||||
throw new Error(`The Field "${triggerField}" does not exist.`);
|
||||
throw new NodeOperationError(this.getNode(), `The Field "${triggerField}" does not exist.`);
|
||||
}
|
||||
|
||||
if (downloadAttachments === true) {
|
||||
|
|
|
@ -13,6 +13,8 @@ import {
|
|||
IDataObject,
|
||||
INodeExecutionData,
|
||||
IPollFunctions,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
|
@ -41,7 +43,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa
|
|||
const credentials = this.getCredentials('airtableApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
query = query || {};
|
||||
|
@ -73,23 +75,7 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa
|
|||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
if (error.statusCode === 401) {
|
||||
// Return a clear error
|
||||
throw new Error('The Airtable credentials are not valid!');
|
||||
}
|
||||
|
||||
if (error.response && error.response.body && error.response.body.error) {
|
||||
// Try to return the error prettier
|
||||
|
||||
const airtableError = error.response.body.error;
|
||||
|
||||
if (airtableError.type && airtableError.message) {
|
||||
throw new Error(`Airtable error response [${airtableError.type}]: ${airtableError.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Expected error data did not get returned so rhow the actual error
|
||||
throw error;
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +1 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<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="0 0 200 170" style="enable-background:new 0 0 200 170;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FCB400;}
|
||||
.st1{fill:#18BFFF;}
|
||||
.st2{fill:#F82B60;}
|
||||
.st3{fill:#BA1E45;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M89,4.8L16.2,34.9c-4.1,1.7-4,7.4,0.1,9.1l73.2,29c6.4,2.6,13.6,2.6,20,0l73.2-29c4.1-1.6,4.1-7.4,0.1-9.1
|
||||
L109.8,4.8C103.2,2,95.7,2,89,4.8"/>
|
||||
<path class="st1" d="M105.9,88.9v72.5c0,3.4,3.5,5.8,6.7,4.5l81.6-31.7c1.9-0.7,3.1-2.5,3.1-4.5V57.2c0-3.4-3.5-5.8-6.7-4.5
|
||||
L109,84.3C107.1,85.1,105.9,86.9,105.9,88.9"/>
|
||||
<path class="st2" d="M86.9,92.6l-24.2,11.7l-2.5,1.2L9.1,130c-3.2,1.6-7.4-0.8-7.4-4.4V57.5c0-1.3,0.7-2.4,1.6-3.3
|
||||
c0.4-0.4,0.8-0.7,1.2-0.9c1.2-0.7,3-0.9,4.4-0.3l77.5,30.7C90.4,85.2,90.7,90.8,86.9,92.6"/>
|
||||
<path class="st3" d="M86.9,92.6l-24.2,11.7L3.3,54.3c0.4-0.4,0.8-0.7,1.2-0.9c1.2-0.7,3-0.9,4.4-0.3l77.5,30.7
|
||||
C90.4,85.2,90.7,90.8,86.9,92.6"/>
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 170"><path d="M89 4.8L16.2 34.9c-4.1 1.7-4 7.4.1 9.1l73.2 29c6.4 2.6 13.6 2.6 20 0l73.2-29c4.1-1.6 4.1-7.4.1-9.1l-73-30.1C103.2 2 95.7 2 89 4.8" fill="#fcb400"/><path d="M105.9 88.9v72.5c0 3.4 3.5 5.8 6.7 4.5l81.6-31.7c1.9-.7 3.1-2.5 3.1-4.5V57.2c0-3.4-3.5-5.8-6.7-4.5L109 84.3c-1.9.8-3.1 2.6-3.1 4.6" fill="#18bfff"/><path d="M86.9 92.6l-24.2 11.7-2.5 1.2L9.1 130c-3.2 1.6-7.4-.8-7.4-4.4V57.5c0-1.3.7-2.4 1.6-3.3.4-.4.8-.7 1.2-.9 1.2-.7 3-.9 4.4-.3l77.5 30.7c4 1.5 4.3 7.1.5 8.9" fill="#f82b60"/><path d="M86.9 92.6l-24.2 11.7-59.4-50c.4-.4.8-.7 1.2-.9 1.2-.7 3-.9 4.4-.3l77.5 30.7c4 1.4 4.3 7 .5 8.8" fill="#ba1e45"/></svg>
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 682 B |
|
@ -19,9 +19,9 @@
|
|||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "Smart Factory Automation using IoT and Sensor Data with n8n",
|
||||
"label": "Learn to Automate Your Factory's Incident Reporting: A Step by Step Guide",
|
||||
"icon": "🏭",
|
||||
"url": "https://medium.com/n8n-io/smart-factory-automation-using-iot-and-sensor-data-with-n8n-27675de9943b"
|
||||
"url": "https://n8n.io/blog/learn-to-automate-your-factorys-incident-reporting-a-step-by-step-guide/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class Amqp implements INodeType {
|
||||
|
@ -98,7 +99,7 @@ export class Amqp implements INodeType {
|
|||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const credentials = this.getCredentials('amqp');
|
||||
if (!credentials) {
|
||||
throw new Error('Credentials are mandatory!');
|
||||
throw new NodeOperationError(this.getNode(), 'Credentials are mandatory!');
|
||||
}
|
||||
|
||||
const sink = this.getNodeParameter('sink', 0, '') as string;
|
||||
|
@ -116,7 +117,7 @@ export class Amqp implements INodeType {
|
|||
}
|
||||
|
||||
if (sink === '') {
|
||||
throw new Error('Queue or Topic required!');
|
||||
throw new NodeOperationError(this.getNode(), 'Queue or Topic required!');
|
||||
}
|
||||
|
||||
const container = create_container();
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "Smart Factory Automation using IoT and Sensor Data with n8n",
|
||||
"label": "Learn to Automate Your Factory's Incident Reporting: A Step by Step Guide",
|
||||
"icon": "🏭",
|
||||
"url": "https://medium.com/n8n-io/smart-factory-automation-using-iot-and-sensor-data-with-n8n-27675de9943b"
|
||||
"url": "https://n8n.io/blog/learn-to-automate-your-factorys-incident-reporting-a-step-by-step-guide/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
INodeType,
|
||||
INodeTypeDescription,
|
||||
ITriggerResponse,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
|
@ -133,7 +134,7 @@ export class AmqpTrigger implements INodeType {
|
|||
|
||||
const credentials = this.getCredentials('amqp');
|
||||
if (!credentials) {
|
||||
throw new Error('Credentials are mandatory!');
|
||||
throw new NodeOperationError(this.getNode(), 'Credentials are mandatory!');
|
||||
}
|
||||
|
||||
const sink = this.getNodeParameter('sink', '') as string;
|
||||
|
@ -146,7 +147,7 @@ export class AmqpTrigger implements INodeType {
|
|||
const containerReconnectLimit = options.reconnectLimit as number || 50;
|
||||
|
||||
if (sink === '') {
|
||||
throw new Error('Queue or Topic required!');
|
||||
throw new NodeOperationError(this.getNode(), 'Queue or Topic required!');
|
||||
}
|
||||
|
||||
let durable = false;
|
||||
|
|
|
@ -17,4 +17,4 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import {
|
|||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -466,7 +467,7 @@ export class ApiTemplateIo implements INodeType {
|
|||
if (overrideJson !== '') {
|
||||
const data = validateJSON(overrideJson);
|
||||
if (data === undefined) {
|
||||
throw new Error('A valid JSON must be provided.');
|
||||
throw new NodeOperationError(this.getNode(), 'A valid JSON must be provided.');
|
||||
}
|
||||
body.overrides = data;
|
||||
}
|
||||
|
@ -523,14 +524,14 @@ export class ApiTemplateIo implements INodeType {
|
|||
if (jsonParameters === false) {
|
||||
const properties = (this.getNodeParameter('propertiesUi', i) as IDataObject || {}).propertyValues as IDataObject[] || [];
|
||||
if (properties.length === 0) {
|
||||
throw new Error('The parameter properties cannot be empty');
|
||||
throw new NodeOperationError(this.getNode(), 'The parameter properties cannot be empty');
|
||||
}
|
||||
data = properties.reduce((obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }), {});
|
||||
} else {
|
||||
const propertiesJson = this.getNodeParameter('propertiesJson', i) as string;
|
||||
data = validateJSON(propertiesJson);
|
||||
if (data === undefined) {
|
||||
throw new Error('A valid JSON must be provided.');
|
||||
throw new NodeOperationError(this.getNode(), 'A valid JSON must be provided.');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
IExecuteFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
|
||||
export async function apiTemplateIoApiRequest(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
|
@ -42,14 +43,11 @@ export async function apiTemplateIoApiRequest(
|
|||
try {
|
||||
const response = await this.helpers.request!(options);
|
||||
if (response.status === 'error') {
|
||||
throw new Error(response.message);
|
||||
throw new NodeApiError(this.getNode(), response.message);
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (error?.response?.body?.message) {
|
||||
throw new Error(`APITemplate.io error response [${error.statusCode}]: ${error.response.body.message}`);
|
||||
}
|
||||
throw error;
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,102 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="80"
|
||||
height="80"
|
||||
viewBox="0 0 23.166659 21.166671"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="1.0.1 (1.0.1+r75)"
|
||||
sodipodi:docname="favicon.svg"
|
||||
inkscape:export-filename="/mnt/shared/bktan81@gmail.com/Companies/AlphaCloud/APITemplate.io/export/favicon-color.png"
|
||||
inkscape:export-xdpi="614.40002"
|
||||
inkscape:export-ydpi="614.40002">
|
||||
<defs
|
||||
id="defs2">
|
||||
<rect
|
||||
x="5.5194178"
|
||||
y="6.7918406"
|
||||
width="82.495544"
|
||||
height="30.566183"
|
||||
id="rect835" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.6962788"
|
||||
inkscape:cx="84.04094"
|
||||
inkscape:cy="31.651389"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-rotation="0"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1014"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="36"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
style="fill:#000000"
|
||||
id="g944"
|
||||
transform="matrix(0.26072249,-0.0450346,0.0450346,0.26072249,-4.4090493,0.80238047)">
|
||||
<path
|
||||
fill="#91bce5"
|
||||
d="m 85.926965,13.698648 -63.285045,30.243807 15.697805,11.196666 1.472218,15.148608 13.242212,-6.250661 9.822444,5.345054 z"
|
||||
id="path918" />
|
||||
<path
|
||||
fill="#1f212b"
|
||||
d="m 39.778008,71.288154 c -0.158903,-0.0056 -0.31648,-0.04907 -0.45874,-0.130089 -0.283589,-0.159995 -0.470626,-0.448698 -0.501329,-0.772967 L 37.38898,55.688275 22.06215,44.756717 c -0.288015,-0.205176 -0.446151,-0.547904 -0.415808,-0.901061 0.03035,-0.353155 0.245283,-0.662843 0.564816,-0.815784 L 85.495203,12.796029 c 0.373477,-0.179075 0.817321,-0.109542 1.119534,0.176179 0.301247,0.284686 0.393931,0.72519 0.235443,1.108892 L 63.800813,69.764609 c -0.108224,0.262384 -0.32346,0.465996 -0.589929,0.560754 -0.268434,0.09369 -0.562801,0.0704 -0.811249,-0.06435 l -9.376677,-5.101349 -12.782391,6.034577 c -0.147425,0.0669 -0.305655,0.09939 -0.462559,0.09391 z M 24.604162,44.11304 38.920495,54.324893 c 0.235269,0.168314 0.386237,0.429742 0.414198,0.717894 l 1.33547,13.734974 11.957261,-5.644136 c 0.287923,-0.136035 0.624788,-0.126272 0.904658,0.02559 l 8.84643,4.812668 21.627569,-52.247035 z"
|
||||
id="path920" />
|
||||
<path
|
||||
fill="#3a84c1"
|
||||
d="M 40.73124,69.46431 46.129963,59.222484 85.897126,14.553126 39.059778,54.433821 Z"
|
||||
id="path922" />
|
||||
<path
|
||||
fill="#1f212b"
|
||||
d="m 40.71279,69.963971 c -0.07296,-0.0026 -0.146351,-0.02112 -0.215122,-0.05754 -0.244661,-0.128616 -0.338162,-0.431064 -0.208546,-0.675689 l 5.398723,-10.241827 c 0.01925,-0.03535 0.04242,-0.06856 0.06852,-0.09967 L 80.940068,19.368061 38.684781,54.856992 c -0.211297,0.176733 -0.526545,0.149715 -0.705278,-0.06165 -0.177698,-0.212332 -0.149715,-0.526547 0.06165,-0.705279 L 85.574314,14.169627 c 0.201987,-0.168053 0.497667,-0.153725 0.680201,0.03476 0.182533,0.188486 0.189157,0.485899 0.01525,0.680947 L 46.544181,59.510112 41.173361,69.69788 c -0.09216,0.17489 -0.27468,0.272579 -0.460568,0.266088 z"
|
||||
id="path924" />
|
||||
<path
|
||||
fill="#1f212b"
|
||||
d="m 28.066921,71.78593 c -4.37019,-0.64391 -8.339996,-2.867809 -10.598292,-6.766997 -1.432549,-2.473502 -1.528128,-6.298167 2.122716,-6.172679 3.274075,0.112327 4.26409,2.596776 4.740567,5.406801 1.694711,8.06304 -1.290339,11.045322 -9.002137,14.829352 -0.594821,0.242389 -0.134492,1.113471 0.457295,0.871978 5.181377,-2.944492 7.754842,-4.282847 9.399081,-7.76557 1.370916,-4.981188 0.489139,-11.773963 -2.947089,-13.689368 -3.921594,-2.143168 -8.01567,0.59562 -6.601411,4.939623 1.751362,5.378399 7.074496,8.562112 12.394405,9.345251 0.635099,0.09422 0.665001,-0.905343 0.03486,-0.998391 z"
|
||||
id="path926"
|
||||
sodipodi:nodetypes="cccccccccccc" />
|
||||
<path
|
||||
fill="#1f212b"
|
||||
d="m 32.211855,72.060754 c -0.960938,-0.01857 -1.924304,-0.08221 -2.882497,-0.15069 -0.64277,-0.04646 -0.676671,0.952965 -0.0349,0.999392 0.958193,0.06848 1.921524,0.133135 2.882497,0.150689 0.642958,0.01242 0.679856,-0.986875 0.0349,-0.999391 z m 4.771615,-1.109149 c -1.135251,0.302564 -2.280575,0.549744 -3.442445,0.726303 -0.635768,0.09687 -0.401574,1.069637 0.232195,0.972696 1.16187,-0.176559 2.307194,-0.423738 3.442444,-0.726302 0.620118,-0.164459 0.391991,-1.139015 -0.232194,-0.972697 z"
|
||||
id="path928" />
|
||||
<path
|
||||
fill="#1f212b"
|
||||
d="m 54.069217,64.636113 c -0.08695,-0.003 -0.175091,-0.02913 -0.253366,-0.07989 l -7.481853,-4.834056 c -0.231899,-0.150184 -0.298138,-0.459685 -0.148952,-0.69162 0.150185,-0.231899 0.460683,-0.298103 0.691619,-0.148953 l 7.481852,4.834058 c 0.231899,0.150184 0.298139,0.459686 0.148953,0.69162 -0.09943,0.153622 -0.268357,0.234778 -0.438253,0.228845 z"
|
||||
id="path926-0"
|
||||
sodipodi:nodetypes="sccccccs"
|
||||
style="fill:#000000" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 23.167 21.167"><path fill="#91bce5" d="M18.611.504L3.473 11.24l4.597 2.213 1.066 3.883 3.171-2.226 2.802.951z"/><path fill="#1f212b" d="M9.172 17.597a.264.264 0 01-.29-.192l-1.035-3.767-4.488-2.16a.265.265 0 01-.039-.454L18.458.288a.264.264 0 01.41.274l-3.5 15.556a.265.265 0 01-.344.193l-2.674-.908-3.061 2.15a.273.273 0 01-.117.044zm-5.18-6.401l4.193 2.017c.069.034.12.095.14.169l.967 3.52 2.863-2.01a.265.265 0 01.237-.033l2.524.856L18.2 1.119z"/><path fill="#3a84c1" d="M9.339 17.079l.946-2.913L18.642.728 8.226 13.235z"/><path fill="#1f212b" d="M9.356 17.21a.132.132 0 01-.143-.172l.946-2.913a.16.16 0 01.014-.03l7.393-11.888-9.419 11.156a.133.133 0 01-.202-.171L18.54.642a.132.132 0 01.214.156l-8.348 13.424-.941 2.898a.132.132 0 01-.109.09zm-3.215 1.045c-1.168.029-2.303-.373-3.067-1.287-.485-.58-.683-1.574.275-1.705.859-.119 1.229.485 1.48 1.196.804 2.026.16 2.938-1.68 4.272-.144.09.015.296.159.206 1.218-1 1.829-1.466 2.1-2.448.134-1.36-.402-3.091-1.384-3.436-1.12-.382-2.063.516-1.499 1.585.699 1.324 2.23 1.914 3.652 1.878.17-.004.133-.266-.036-.261z"/><path fill="#1f212b" d="M7.235 18.14a14.95 14.95 0 01-.759.09c-.17.017-.133.279.036.262.253-.025.507-.052.758-.09.169-.026.133-.288-.035-.262zm1.194-.504a8.46 8.46 0 01-.865.344c-.162.054-.057.297.104.243a8.37 8.37 0 00.865-.344c.154-.071.05-.315-.104-.243z"/><path d="M12.599 15.22a.131.131 0 01-.07-.01l-2.168-.924a.133.133 0 01.103-.243l2.169.923a.133.133 0 01-.034.253z"/></svg>
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 1.5 KiB |
|
@ -15,6 +15,13 @@
|
|||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.asana/"
|
||||
}
|
||||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "How a digital strategist uses n8n for online marketing",
|
||||
"icon": "💻",
|
||||
"url": "https://n8n.io/blog/how-a-digital-strategist-uses-n8n-for-online-marketing/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -9,6 +9,8 @@ import {
|
|||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -1639,7 +1641,7 @@ export class Asana implements INodeType {
|
|||
const responseData = await asanaApiRequest.call(this, 'GET', endpoint, {});
|
||||
|
||||
if (responseData.data === undefined) {
|
||||
throw new Error('No data got returned');
|
||||
throw new NodeApiError(this.getNode(), responseData, { message: 'No data got returned' });
|
||||
}
|
||||
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
|
@ -1674,7 +1676,7 @@ export class Asana implements INodeType {
|
|||
const responseData = await asanaApiRequest.call(this, 'GET', endpoint, {});
|
||||
|
||||
if (responseData.data === undefined) {
|
||||
throw new Error('No data got returned');
|
||||
throw new NodeApiError(this.getNode(), responseData, { message: 'No data got returned' });
|
||||
}
|
||||
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
|
@ -1711,7 +1713,7 @@ export class Asana implements INodeType {
|
|||
// to retrieve the teams from an organization just work with workspaces that are an organization
|
||||
|
||||
if (workspace.is_organization === false) {
|
||||
throw Error('To filter by team, the workspace selected has to be an organization');
|
||||
throw new NodeOperationError(this.getNode(), 'To filter by team, the workspace selected has to be an organization');
|
||||
}
|
||||
|
||||
const endpoint = `/organizations/${workspaceId}/teams`;
|
||||
|
@ -1750,15 +1752,15 @@ export class Asana implements INodeType {
|
|||
let taskData;
|
||||
try {
|
||||
taskData = await asanaApiRequest.call(this, 'GET', `/tasks/${taskId}`, {});
|
||||
} catch (e) {
|
||||
throw new Error(`Could not find task with id "${taskId}" so tags could not be loaded.`);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error, { message: `Could not find task with id "${taskId}" so tags could not be loaded.` });
|
||||
}
|
||||
|
||||
const workspace = taskData.data.workspace.gid;
|
||||
const responseData = await asanaApiRequest.call(this, 'GET', endpoint, {}, { workspace });
|
||||
|
||||
if (responseData.data === undefined) {
|
||||
throw new Error('No data got returned');
|
||||
throw new NodeApiError(this.getNode(), responseData, { message: 'No data got returned' });
|
||||
}
|
||||
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
|
@ -1790,7 +1792,7 @@ export class Asana implements INodeType {
|
|||
const responseData = await asanaApiRequest.call(this, 'GET', endpoint, {});
|
||||
|
||||
if (responseData.data === undefined) {
|
||||
throw new Error('No data got returned');
|
||||
throw new NodeApiError(this.getNode(), responseData, { message: 'No data got returned' });
|
||||
}
|
||||
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
INodeType,
|
||||
INodeTypeDescription,
|
||||
IWebhookResponseData,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -155,7 +156,7 @@ export class AsanaTrigger implements INodeType {
|
|||
const webhookUrl = this.getNodeWebhookUrl('default') as string;
|
||||
|
||||
if (webhookUrl.includes('%20')) {
|
||||
throw new Error('The name of the Asana Trigger Node is not allowed to contain any spaces!');
|
||||
throw new NodeOperationError(this.getNode(), 'The name of the Asana Trigger Node is not allowed to contain any spaces!');
|
||||
}
|
||||
|
||||
const resource = this.getNodeParameter('resource') as string;
|
||||
|
@ -189,7 +190,7 @@ export class AsanaTrigger implements INodeType {
|
|||
|
||||
try {
|
||||
await asanaApiRequest.call(this, 'DELETE', endpoint, body);
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ import {
|
|||
import {
|
||||
IDataObject,
|
||||
INodePropertyOptions,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -43,7 +45,7 @@ export async function asanaApiRequest(this: IHookFunctions | IExecuteFunctions |
|
|||
const credentials = this.getCredentials('asanaApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
options.headers!['Authorization'] = `Bearer ${credentials.accessToken}`;
|
||||
|
@ -54,25 +56,7 @@ export async function asanaApiRequest(this: IHookFunctions | IExecuteFunctions |
|
|||
return await this.helpers.requestOAuth2.call(this, 'asanaOAuth2Api', options);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.statusCode === 401) {
|
||||
// Return a clear error
|
||||
throw new Error('The Asana credentials are not valid!');
|
||||
}
|
||||
|
||||
if (error.statusCode === 403) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (error.response && error.response.body && error.response.body.errors) {
|
||||
// Try to return the error prettier
|
||||
const errorMessages = error.response.body.errors.map((errorData: { message: string }) => {
|
||||
return errorData.message;
|
||||
});
|
||||
throw new Error(`Asana error response [${error.statusCode}]: ${errorMessages.join(' | ')}`);
|
||||
}
|
||||
|
||||
// If that data does not exist for some reason return the actual error
|
||||
throw error;
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +1 @@
|
|||
<?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>
|
||||
<svg width="60" height="60" xmlns="http://www.w3.org/2000/svg"><defs><radialGradient cx="50%" cy="55%" fx="50%" fy="55%" r="72.507%" gradientTransform="matrix(.92404 0 0 1 .038 0)" id="a"><stop stop-color="#FFB900" offset="0%"/><stop stop-color="#F95D8F" offset="60%"/><stop stop-color="#F95353" offset="99.91%"/></radialGradient></defs><path d="M45.594 28.5c-6.994.003-12.664 5.673-12.667 12.667.003 6.995 5.673 12.664 12.667 12.668 6.995-.004 12.664-5.673 12.667-12.668-.003-6.994-5.672-12.664-12.667-12.667zm-32.927.001C5.673 28.505.003 34.174 0 41.17c.003 6.994 5.673 12.664 12.667 12.667 6.995-.003 12.664-5.673 12.668-12.667-.004-6.995-5.673-12.664-12.668-12.668zM41.79 12.667c-.002 6.995-5.671 12.665-12.666 12.67-6.995-.004-12.664-5.674-12.667-12.67C16.46 5.673 22.13.003 29.123 0c6.994.004 12.663 5.673 12.666 12.667z" transform="translate(.732 2.732)" fill="url(#a)" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 951 B |
|
@ -9,7 +9,7 @@ import {
|
|||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
IDataObject, NodeApiError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function automizyApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, option = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
@ -40,14 +40,7 @@ export async function automizyApiRequest(this: IExecuteFunctions | IExecuteSingl
|
|||
//@ts-ignore
|
||||
return await this.helpers.request.call(this, options);
|
||||
} catch (error) {
|
||||
if (error.response && error.response.body) {
|
||||
|
||||
throw new Error(
|
||||
`Automizy error response [${error.statusCode}]: ${error.response.body.title}`,
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
20
packages/nodes-base/nodes/Autopilot/Autopilot.node.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"node": "n8n-nodes-base.autopilot",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": [
|
||||
"Marketing & Content"
|
||||
],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/credentials/autopilot"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.autopilot/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"node": "n8n-nodes-base.autopilotTrigger",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": [
|
||||
"Marketing & Content"
|
||||
],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/credentials/autopilot"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.autopilotTrigger/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import {
|
|||
IDataObject,
|
||||
IHookFunctions,
|
||||
IWebhookFunctions,
|
||||
NodeApiError,
|
||||
} 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
|
||||
|
@ -42,11 +43,7 @@ export async function autopilotApiRequest(this: IExecuteFunctions | IWebhookFunc
|
|||
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;
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1 @@
|
|||
<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>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="38 26 35 35"><circle cx="50" cy="50" r="40" stroke="#18d4b2" stroke-width="3" fill="#18d4b2"/><path fill="#fff" d="M45.4 42.6h19.9l3.4-4.8H42l3.4 4.8zm3.1 8.3h13.1l3.4-4.8H45.4l3.1 4.8zm54-.7"/></svg>
|
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 249 B |
|
@ -6,6 +6,8 @@ import {
|
|||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { awsApiRequestREST } from './GenericFunctions';
|
||||
|
@ -130,13 +132,7 @@ export class AwsLambda implements INodeType {
|
|||
loadOptions: {
|
||||
async getFunctions(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = await awsApiRequestREST.call(this, 'lambda', 'GET', '/2015-03-31/functions/');
|
||||
} catch (err) {
|
||||
throw new Error(`AWS Error: ${err}`);
|
||||
}
|
||||
const data = await awsApiRequestREST.call(this, 'lambda', 'GET', '/2015-03-31/functions/');
|
||||
|
||||
for (const func of data.Functions!) {
|
||||
returnData.push({
|
||||
|
@ -162,22 +158,17 @@ export class AwsLambda implements INodeType {
|
|||
Qualifier: this.getNodeParameter('qualifier', i) as string,
|
||||
};
|
||||
|
||||
let responseData;
|
||||
try {
|
||||
responseData = await awsApiRequestREST.call(
|
||||
this,
|
||||
'lambda',
|
||||
'POST',
|
||||
`/2015-03-31/functions/${params.FunctionName}/invocations?Qualifier=${params.Qualifier}`,
|
||||
params.Payload,
|
||||
{
|
||||
'X-Amz-Invocation-Type': params.InvocationType,
|
||||
'Content-Type': 'application/x-amz-json-1.0',
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
throw new Error(`AWS Error: ${err}`);
|
||||
}
|
||||
const responseData = await awsApiRequestREST.call(
|
||||
this,
|
||||
'lambda',
|
||||
'POST',
|
||||
`/2015-03-31/functions/${params.FunctionName}/invocations?Qualifier=${params.Qualifier}`,
|
||||
params.Payload,
|
||||
{
|
||||
'X-Amz-Invocation-Type': params.InvocationType,
|
||||
'Content-Type': 'application/x-amz-json-1.0',
|
||||
},
|
||||
);
|
||||
|
||||
if (responseData !== null && responseData.errorMessage !== undefined) {
|
||||
let errorMessage = responseData.errorMessage;
|
||||
|
@ -186,7 +177,7 @@ export class AwsLambda implements INodeType {
|
|||
errorMessage += `\n\nStack trace:\n${responseData.stackTrace}`;
|
||||
}
|
||||
|
||||
throw new Error(errorMessage);
|
||||
throw new NodeApiError(this.getNode(), responseData);
|
||||
} else {
|
||||
returnData.push({
|
||||
result: responseData,
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"node": "n8n-nodes-base.awsSes",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Communication", "Development"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{ "url": "https://docs.n8n.io/credentials/aws" }
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{ "url": "https://docs.n8n.io/nodes/n8n-nodes-base.awsSes/" }
|
||||
]
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@ import {
|
|||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { awsApiRequestSOAP } from './GenericFunctions';
|
||||
|
@ -107,12 +109,7 @@ export class AwsSns implements INodeType {
|
|||
// select them easily
|
||||
async getTopics(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
let data;
|
||||
try {
|
||||
data = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=ListTopics');
|
||||
} catch (err) {
|
||||
throw new Error(`AWS Error: ${err}`);
|
||||
}
|
||||
const data = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=ListTopics');
|
||||
|
||||
let topics = data.ListTopicsResponse.ListTopicsResult.Topics.member;
|
||||
|
||||
|
@ -149,12 +146,8 @@ export class AwsSns implements INodeType {
|
|||
'Message=' + this.getNodeParameter('message', i) as string,
|
||||
];
|
||||
|
||||
let responseData;
|
||||
try {
|
||||
responseData = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=Publish&' + params.join('&'));
|
||||
} catch (err) {
|
||||
throw new Error(`AWS Error: ${err}`);
|
||||
}
|
||||
|
||||
const responseData = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=Publish&' + params.join('&'));
|
||||
returnData.push({MessageId: responseData.PublishResponse.PublishResult.MessageId} as IDataObject);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@ import {
|
|||
INodeType,
|
||||
INodeTypeDescription,
|
||||
IWebhookResponseData,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -68,12 +70,7 @@ export class AwsSnsTrigger implements INodeType {
|
|||
// select them easily
|
||||
async getTopics(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
let data;
|
||||
try {
|
||||
data = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=ListTopics');
|
||||
} catch (err) {
|
||||
throw new Error(`AWS Error: ${err}`);
|
||||
}
|
||||
const data = await awsApiRequestSOAP.call(this, 'sns', 'GET', '/?Action=ListTopics');
|
||||
|
||||
let topics = data.ListTopicsResponse.ListTopicsResult.Topics.member;
|
||||
|
||||
|
@ -134,7 +131,7 @@ export class AwsSnsTrigger implements INodeType {
|
|||
const topic = this.getNodeParameter('topic') as string;
|
||||
|
||||
if (webhookUrl.includes('%20')) {
|
||||
throw new Error('The name of the SNS Trigger Node is not allowed to contain any spaces!');
|
||||
throw new NodeOperationError(this.getNode(), 'The name of the SNS Trigger Node is not allowed to contain any spaces!');
|
||||
}
|
||||
|
||||
const params = [
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialDataDecryptedObject, NodeApiError, NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
function getEndpointForService(service: string, credentials: ICredentialDataDecryptedObject): string {
|
||||
|
@ -40,7 +40,7 @@ function getEndpointForService(service: string, credentials: ICredentialDataDecr
|
|||
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('aws');
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
// Concatenate path and instantiate URL object so it parses correctly query strings
|
||||
|
@ -61,17 +61,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
const errorMessage = (error.response && error.response.body.message) || (error.response && error.response.body.Message) || error.message;
|
||||
|
||||
if (error.statusCode === 403) {
|
||||
if (errorMessage === 'The security token included in the request is invalid.') {
|
||||
throw new Error('The AWS credentials are not valid!');
|
||||
} else if (errorMessage.startsWith('The request signature we calculated does not match the signature you provided')) {
|
||||
throw new Error('The AWS credentials are not valid!');
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`AWS error response [${error.statusCode}]: ${errorMessage}`);
|
||||
throw new NodeApiError(this.getNode(), error); // no XML parsing needed
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +69,7 @@ export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions
|
|||
const response = await awsApiRequest.call(this, service, method, path, body, headers);
|
||||
try {
|
||||
return JSON.parse(response);
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +85,7 @@ export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions
|
|||
resolve(data);
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 75 75"><defs><style>.cls-1{fill:url(#TurquoiseGradient);}.cls-2{fill:#fff;}</style><linearGradient id="TurquoiseGradient" x1="617.46" y1="-674.53" x2="723.53" y2="-568.46" gradientTransform="translate(659 708) rotate(-90)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#055f4e"/><stop offset="1" stop-color="#56c0a7"/></linearGradient></defs><title>Amazon-Comprehend</title><g id="Reference"><rect id="Turquoise_Gradient" data-name="Turquoise Gradient" class="cls-1" width="75" height="75"/><g id="Icon_Test" data-name="Icon Test"><path class="cls-2" d="M44.5,34.2V24.5a1,1,0,0,0-.29-.71l-11-11a1,1,0,0,0-.71-.29h-19a1,1,0,0,0-1,1v43a1,1,0,0,0,1,1h30a1,1,0,0,0,1-1V52.06a11.8,11.8,0,0,1-2-2.3V55.5h-28v-41h17v10a1,1,0,0,0,1,1h10v11A11.56,11.56,0,0,1,44.5,34.2Zm-11-10.7V15.91l7.59,7.59Zm-10,8h-6v-2h6Zm16,0h-14v-2h14Zm0,6h-22v-2h22Zm15.44,25H50.06a1,1,0,0,1-.93-.62l-1.21-3a1,1,0,0,1,.09-.94,1,1,0,0,1,.83-.44h7.32a1,1,0,0,1,.83.44,1,1,0,0,1,.09.94l-1.21,3A1,1,0,0,1,54.94,62.5Zm-4.21-2h3.54l.4-1H50.33Zm11.64-19A10,10,0,0,0,42.5,43.12a10,10,0,0,0,4.28,8.2,3.88,3.88,0,0,1,.72.59V55.5a1,1,0,0,0,1,1h8a1,1,0,0,0,1-1V51.9a4.33,4.33,0,0,1,.71-.57,9.92,9.92,0,0,0,4.29-8.2A10.19,10.19,0,0,0,62.37,41.48ZM57.05,49.7c-.58.4-1.55,1.07-1.55,2.1v2.7h-2v-7h2v-2h-6v2h2v7h-2V51.82c0-1-1-1.73-1.58-2.14A8,8,0,1,1,58,37.32a7.89,7.89,0,0,1,2.39,4.47A8,8,0,0,1,57.05,49.7ZM28.5,25.5h-11v-2h11Zm1,18h-12v-2h12Zm10,0h-8v-2h8Zm-9,6h-13v-2h13Z"/></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 75 75"><defs><linearGradient id="a" x1="617.46" y1="-674.53" x2="723.53" y2="-568.46" gradientTransform="rotate(-90 683.5 24.5)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#055f4e"/><stop offset="1" stop-color="#56c0a7"/></linearGradient></defs><path data-name="Turquoise Gradient" d="M0 0h75v75H0z" fill="url(#a)"/><path d="M44.5 34.2v-9.7a1 1 0 00-.29-.71l-11-11a1 1 0 00-.71-.29h-19a1 1 0 00-1 1v43a1 1 0 001 1h30a1 1 0 001-1v-4.44a11.8 11.8 0 01-2-2.3v5.74h-28v-41h17v10a1 1 0 001 1h10v11a11.56 11.56 0 012-2.3zm-11-10.7v-7.59l7.59 7.59zm-10 8h-6v-2h6zm16 0h-14v-2h14zm0 6h-22v-2h22zm15.44 25h-4.88a1 1 0 01-.93-.62l-1.21-3a1 1 0 01.09-.94 1 1 0 01.83-.44h7.32a1 1 0 01.83.44 1 1 0 01.09.94l-1.21 3a1 1 0 01-.93.62zm-4.21-2h3.54l.4-1h-4.34zm11.64-19a10 10 0 00-19.87 1.62 10 10 0 004.28 8.2 3.88 3.88 0 01.72.59v3.59a1 1 0 001 1h8a1 1 0 001-1v-3.6a4.33 4.33 0 01.71-.57 9.92 9.92 0 004.29-8.2 10.19 10.19 0 00-.13-1.65zm-5.32 8.2c-.58.4-1.55 1.07-1.55 2.1v2.7h-2v-7h2v-2h-6v2h2v7h-2v-2.68c0-1-1-1.73-1.58-2.14A8 8 0 1158 37.32a7.89 7.89 0 012.39 4.47 8 8 0 01-3.34 7.91zM28.5 25.5h-11v-2h11zm1 18h-12v-2h12zm10 0h-8v-2h8zm-9 6h-13v-2h13z" data-name="Icon Test" fill="#fff"/></svg>
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.2 KiB |
|
@ -1,7 +1,7 @@
|
|||
import { URL } from 'url';
|
||||
import { sign } from 'aws4';
|
||||
import { OptionsWithUri } from 'request';
|
||||
import { parseString } from 'xml2js';
|
||||
import { parseString as parseXml } from 'xml2js';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
|
@ -11,7 +11,7 @@ import {
|
|||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialDataDecryptedObject, NodeApiError, NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
function getEndpointForService(service: string, credentials: ICredentialDataDecryptedObject): string {
|
||||
|
@ -31,7 +31,7 @@ function getEndpointForService(service: string, credentials: ICredentialDataDecr
|
|||
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('aws');
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
// Concatenate path and instantiate URL object so it parses correctly query strings
|
||||
|
@ -52,17 +52,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
const errorMessage = (error.response && error.response.body.message) || (error.response && error.response.body.Message) || error.message;
|
||||
|
||||
if (error.statusCode === 403) {
|
||||
if (errorMessage === 'The security token included in the request is invalid.') {
|
||||
throw new Error('The AWS credentials are not valid!');
|
||||
} else if (errorMessage.startsWith('The request signature we calculated does not match the signature you provided')) {
|
||||
throw new Error('The AWS credentials are not valid!');
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`AWS error response [${error.statusCode}]: ${errorMessage}`);
|
||||
throw new NodeApiError(this.getNode(), error, { parseXml: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,7 +60,7 @@ export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions
|
|||
const response = await awsApiRequest.call(this, service, method, path, body, headers);
|
||||
try {
|
||||
return JSON.parse(response);
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
@ -79,14 +69,14 @@ export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions
|
|||
const response = await awsApiRequest.call(this, service, method, path, body, headers);
|
||||
try {
|
||||
return await new Promise((resolve, reject) => {
|
||||
parseString(response, { explicitArray: false }, (err, data) => {
|
||||
parseXml(response, { explicitArray: false }, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import {
|
|||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -459,11 +461,11 @@ export class AwsRekognition implements INodeType {
|
|||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string;
|
||||
|
||||
if (items[i].binary === undefined) {
|
||||
throw new Error('No binary data exists on item!');
|
||||
throw new NodeOperationError(this.getNode(), 'No binary data exists on item!');
|
||||
}
|
||||
|
||||
if ((items[i].binary as IBinaryKeyData)[binaryPropertyName] === undefined) {
|
||||
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`);
|
||||
throw new NodeOperationError(this.getNode(), `No binary data property "${binaryPropertyName}" does not exists on item!`);
|
||||
}
|
||||
|
||||
const binaryPropertyData = (items[i].binary as IBinaryKeyData)[binaryPropertyName];
|
||||
|
@ -494,7 +496,9 @@ export class AwsRekognition implements INodeType {
|
|||
body.Image.S3Object.Version = additionalFields.version as string;
|
||||
}
|
||||
}
|
||||
|
||||
responseData = await awsApiRequestREST.call(this, 'rekognition', 'POST', '', JSON.stringify(body), {}, { 'X-Amz-Target': action, 'Content-Type': 'application/x-amz-json-1.1' });
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ import {
|
|||
|
||||
import {
|
||||
IDataObject,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -36,7 +38,7 @@ import {
|
|||
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string | Buffer | IDataObject, query: IDataObject = {}, headers?: object, option: IDataObject = {}, region?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('aws');
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
const endpoint = new URL(((credentials.rekognitionEndpoint as string || '').replace('{region}', credentials.region as string) || `https://${service}.${credentials.region}.amazonaws.com`) + path);
|
||||
|
@ -59,17 +61,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
const errorMessage = (error.response && error.response.body.message) || (error.response && error.response.body.Message) || error.message;
|
||||
|
||||
if (error.statusCode === 403) {
|
||||
if (errorMessage === 'The security token included in the request is invalid.') {
|
||||
throw new Error('The AWS credentials are not valid!');
|
||||
} else if (errorMessage.startsWith('The request signature we calculated does not match the signature you provided')) {
|
||||
throw new Error('The AWS credentials are not valid!');
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`AWS error response [${error.statusCode}]: ${errorMessage}`);
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,7 +69,7 @@ export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions
|
|||
const response = await awsApiRequest.call(this, service, method, path, body, query, headers, options, region);
|
||||
try {
|
||||
return JSON.parse(response);
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
@ -93,8 +85,8 @@ export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions
|
|||
resolve(data);
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
return e;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 74.375 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 stroke="none"><path d="M2.886 52.8L16.8 51.268V28.732L2.886 27.2v25.6z" fill="#5294cf"/><g fill="#19486f"><path d="M67.207 28.898l-6.462 2.43-36.237-5.346L34.99 0l32.217 28.898z"/><path d="M3.504 28.966L35 12.234 45.543 26 16.81 30.224l-13.305-1.26z"/></g><g fill="#205b99"><path d="M35 24L0 30.624V16.556L35 0l17.016 18.478L35 24z"/><path d="M7.008 16.478L0 19.395v44.05l7.008 3.307V16.478z"/></g><path d="M70 16.566L35 0v24l35 6.624v-14.06z" fill="#5294cf"/><g fill="#99bce3"><path d="M1.154 51.26L34.99 80l10.554-26-28.734-4.224L1.154 51.26z"/><path d="M67.64 51.142l-6.493-2.527-36.64 5.405 10.48 25.22 32.65-28.097z"/></g><path d="M67.207 51.103l-13.965-1.327v-19.55L67.207 28.9v22.205zM35 56l15.13-16L35 24 16.356 40 35 56z" fill="#205b99"/><path d="M53.624 40L35 56V24l18.634 16z" fill="#5294cf"/><path d="M0 49.376L35 56l19.21 7.873L35 80 0 63.444V49.376z" fill="#205b99"/><g fill="#5294cf"><path d="M70 63.435L35 80V56l35-6.624v14.06z"/><path d="M62.97 66.75L70 63.434V16.566l-7.03-3.327V66.75z"/></g></g></symbol></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 74.375 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 stroke="none"><path d="M2.886 52.8L16.8 51.268V28.732L2.886 27.2v25.6z" fill="#5294cf"/><g fill="#19486f"><path d="M67.207 28.898l-6.462 2.43-36.237-5.346L34.99 0l32.217 28.898z"/><path d="M3.504 28.966L35 12.234 45.543 26 16.81 30.224l-13.305-1.26z"/></g><g fill="#205b99"><path d="M35 24L0 30.624V16.556L35 0l17.016 18.478L35 24z"/><path d="M7.008 16.478L0 19.395v44.05l7.008 3.307V16.478z"/></g><path d="M70 16.566L35 0v24l35 6.624v-14.06z" fill="#5294cf"/><g fill="#99bce3"><path d="M1.154 51.26L34.99 80l10.554-26-28.734-4.224L1.154 51.26z"/><path d="M67.64 51.142l-6.493-2.527-36.64 5.405 10.48 25.22 32.65-28.097z"/></g><path d="M67.207 51.103l-13.965-1.327v-19.55L67.207 28.9v22.205zM35 56l15.13-16L35 24 16.356 40 35 56z" fill="#205b99"/><path d="M53.624 40L35 56V24l18.634 16z" fill="#5294cf"/><path d="M0 49.376L35 56l19.21 7.873L35 80 0 63.444V49.376z" fill="#205b99"/><g fill="#5294cf"><path d="M70 63.435L35 80V56l35-6.624v14.06z"/><path d="M62.97 66.75L70 63.434V16.566l-7.03-3.327V66.75z"/></g></g></symbol></svg>
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
@ -19,9 +19,9 @@
|
|||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "Why this CEO loves n8n",
|
||||
"label": "Why business process automation with n8n can change your daily life",
|
||||
"icon": "🧬",
|
||||
"url": "https://medium.com/n8n-io/why-this-ceo-loves-n8n-ee7d102b0948"
|
||||
"url": "https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -422,7 +423,7 @@ export class AwsS3 implements INodeType {
|
|||
const fileName = fileKey.split('/')[fileKey.split('/').length - 1];
|
||||
|
||||
if (fileKey.substring(fileKey.length - 1) === '/') {
|
||||
throw new Error('Downloding a whole directory is not yet supported, please provide a file key');
|
||||
throw new NodeOperationError(this.getNode(), 'Downloding a whole directory is not yet supported, please provide a file key');
|
||||
}
|
||||
|
||||
let region = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', { location: '' });
|
||||
|
@ -588,11 +589,11 @@ export class AwsS3 implements INodeType {
|
|||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string;
|
||||
|
||||
if (items[i].binary === undefined) {
|
||||
throw new Error('No binary data exists on item!');
|
||||
throw new NodeOperationError(this.getNode(), 'No binary data exists on item!');
|
||||
}
|
||||
|
||||
if ((items[i].binary as IBinaryKeyData)[binaryPropertyName] === undefined) {
|
||||
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`);
|
||||
throw new NodeOperationError(this.getNode(), `No binary data property "${binaryPropertyName}" does not exists on item!`);
|
||||
}
|
||||
|
||||
const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName];
|
||||
|
|
|
@ -26,20 +26,20 @@ import {
|
|||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
IDataObject, NodeApiError, NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string | Buffer, query: IDataObject = {}, headers?: object, option: IDataObject = {}, region?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('aws');
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
const endpoint = new URL(((credentials.s3Endpoint as string || '').replace('{region}', credentials.region as string) || `https://${service}.${credentials.region}.amazonaws.com`) + path);
|
||||
|
||||
// Sign AWS API request with the user credentials
|
||||
const signOpts = {headers: headers || {}, host: endpoint.host, method, path: `${endpoint.pathname}?${queryToString(query).replace(/\+/g, '%2B')}`, body};
|
||||
|
||||
|
||||
|
||||
sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim()});
|
||||
|
||||
|
@ -57,17 +57,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
const errorMessage = (error.response && error.response.body.message) || (error.response && error.response.body.Message) || error.message;
|
||||
|
||||
if (error.statusCode === 403) {
|
||||
if (errorMessage === 'The security token included in the request is invalid.') {
|
||||
throw new Error('The AWS credentials are not valid!');
|
||||
} else if (errorMessage.startsWith('The request signature we calculated does not match the signature you provided')) {
|
||||
throw new Error('The AWS credentials are not valid!');
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`AWS error response [${error.statusCode}]: ${errorMessage}`);
|
||||
throw new NodeApiError(this.getNode(), error, { parseXml: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,7 +65,7 @@ export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions
|
|||
const response = await awsApiRequest.call(this, service, method, path, body, query, headers, options, region);
|
||||
try {
|
||||
return JSON.parse(response);
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
@ -91,8 +81,8 @@ export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions
|
|||
resolve(data);
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
return e;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "Why this CEO loves n8n",
|
||||
"label": "Why business process automation with n8n can change your daily life",
|
||||
"icon": "🧬",
|
||||
"url": "https://medium.com/n8n-io/why-this-ceo-loves-n8n-ee7d102b0948"
|
||||
"url": "https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -1098,7 +1099,7 @@ export class AwsSes implements INodeType {
|
|||
if (toAddresses.length) {
|
||||
setParameter(params, 'Destination.ToAddresses.member', toAddresses);
|
||||
} else {
|
||||
throw new Error('At least one "To Address" has to be added!');
|
||||
throw new NodeOperationError(this.getNode(), 'At least one "To Address" has to be added!');
|
||||
}
|
||||
|
||||
if (additionalFields.configurationSetName) {
|
||||
|
@ -1151,7 +1152,7 @@ export class AwsSes implements INodeType {
|
|||
if (toAddresses.length) {
|
||||
setParameter(params, 'Destination.ToAddresses.member', toAddresses);
|
||||
} else {
|
||||
throw new Error('At least one "To Address" has to be added!');
|
||||
throw new NodeOperationError(this.getNode(), 'At least one "To Address" has to be added!');
|
||||
}
|
||||
|
||||
if (additionalFields.configurationSetName) {
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
IDataObject, NodeApiError, NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -32,7 +32,7 @@ import {
|
|||
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('aws');
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
const endpoint = new URL(((credentials.sesEndpoint as string || '').replace('{region}', credentials.region as string) || `https://${service}.${credentials.region}.amazonaws.com`) + path);
|
||||
|
@ -52,17 +52,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
const errorMessage = (error.response && error.response.body.message) || (error.response && error.response.body.Message) || error.message;
|
||||
|
||||
if (error.statusCode === 403) {
|
||||
if (errorMessage === 'The security token included in the request is invalid.') {
|
||||
throw new Error('The AWS credentials are not valid!');
|
||||
} else if (errorMessage.startsWith('The request signature we calculated does not match the signature you provided')) {
|
||||
throw new Error('The AWS credentials are not valid!');
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`AWS error response [${error.statusCode}]: ${errorMessage}`);
|
||||
throw new NodeApiError(this.getNode(), error, { parseXml: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,7 +60,7 @@ export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions
|
|||
const response = await awsApiRequest.call(this, service, method, path, body, headers);
|
||||
try {
|
||||
return JSON.parse(response);
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +76,7 @@ export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions
|
|||
resolve(data);
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 74.375 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 stroke="none"><path d="M16.558 12.75L0 38.591l16.558 25.841 13.227-3.324.654-44.869-13.881-3.489z" fill="#876929"/><path d="M35.049 59.786l-18.491 4.645V12.75l18.491 4.645v42.391z" fill="#d9a741"/><g fill="#876929"><path d="M32.849 21.614L35.05 80 70 62.867l-.01-43.615-8.914 1.448-28.228.913z"/><path d="M46.184 33.149l10.906-.632 10.778-19.164L40.612 0 30.439 4.364l15.745 28.785z"/></g><path d="M40.612 0l27.256 13.353-10.778 19.164L40.612 0z" fill="#d9a741"/><path d="M35.049 5.539L57.09 44.742l3.788 22.595L35.049 80l-10.46-5.131V9.64l10.46-4.101z" fill="#876929"/><path d="M69.991 19.251L70 62.867 35.05 80V5.539l22.041 39.203 12.899-25.491z" fill="#d9a741"/></g></symbol></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 74.375 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 stroke="none"><path d="M16.558 12.75L0 38.591l16.558 25.841 13.227-3.324.654-44.869-13.881-3.489z" fill="#876929"/><path d="M35.049 59.786l-18.491 4.645V12.75l18.491 4.645v42.391z" fill="#d9a741"/><g fill="#876929"><path d="M32.849 21.614L35.05 80 70 62.867l-.01-43.615-8.914 1.448-28.228.913z"/><path d="M46.184 33.149l10.906-.632 10.778-19.164L40.612 0 30.439 4.364l15.745 28.785z"/></g><path d="M40.612 0l27.256 13.353L57.09 32.517 40.612 0z" fill="#d9a741"/><path d="M35.049 5.539L57.09 44.742l3.788 22.595L35.049 80l-10.46-5.131V9.64l10.46-4.101z" fill="#876929"/><path d="M69.991 19.251L70 62.867 35.05 80V5.539l22.041 39.203L69.99 19.251z" fill="#d9a741"/></g></symbol></svg>
|
Before Width: | Height: | Size: 961 B After Width: | Height: | Size: 959 B |
|
@ -1,5 +1,4 @@
|
|||
import {
|
||||
BINARY_ENCODING,
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
|
@ -11,6 +10,8 @@ import {
|
|||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -21,6 +22,10 @@ import {
|
|||
awsApiRequestSOAP,
|
||||
} from '../GenericFunctions';
|
||||
|
||||
import {
|
||||
pascalCase,
|
||||
} from 'change-case';
|
||||
|
||||
export class AwsSqs implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'AWS SQS',
|
||||
|
@ -266,12 +271,17 @@ export class AwsSqs implements INodeType {
|
|||
loadOptions: {
|
||||
// Get all the available queues to display them to user so that it can be selected easily
|
||||
async getQueues(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const params = [
|
||||
'Version=2012-11-05',
|
||||
`Action=ListQueues`,
|
||||
];
|
||||
|
||||
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}`);
|
||||
data = await awsApiRequestSOAP.call(this, 'sqs', 'GET', `?${params.join('&')}`);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
|
||||
let queues = data.ListQueuesResponse.ListQueuesResult.QueueUrl;
|
||||
|
@ -308,7 +318,11 @@ export class AwsSqs implements INodeType {
|
|||
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 params = [
|
||||
'Version=2012-11-05',
|
||||
`Action=${pascalCase(operation)}`,
|
||||
];
|
||||
|
||||
const options = this.getNodeParameter('options', i, {}) as IDataObject;
|
||||
const sendInputData = this.getNodeParameter('sendInputData', i) as boolean;
|
||||
|
@ -349,11 +363,11 @@ export class AwsSqs implements INodeType {
|
|||
const item = items[i];
|
||||
|
||||
if (item.binary === undefined) {
|
||||
throw new Error('No binary data set. So message attribute cannot be added!');
|
||||
throw new NodeOperationError(this.getNode(), '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!`);
|
||||
throw new NodeOperationError(this.getNode(), `The binary property "${dataPropertyName}" does not exist. So message attribute cannot be added!`);
|
||||
}
|
||||
|
||||
const binaryData = item.binary[dataPropertyName].data;
|
||||
|
@ -373,9 +387,9 @@ export class AwsSqs implements INodeType {
|
|||
|
||||
let responseData;
|
||||
try {
|
||||
responseData = await awsApiRequestSOAP.call(this, 'sqs', 'GET', `${queuePath}/?Action=${operation}&` + params.join('&'));
|
||||
} catch (err) {
|
||||
throw new Error(`AWS Error: ${err}`);
|
||||
responseData = await awsApiRequestSOAP.call(this, 'sqs', 'GET', `${queuePath}?${params.join('&')}`);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
|
||||
const result = responseData.SendMessageResponse.SendMessageResult;
|
||||
|
|
|
@ -1 +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>
|
||||
<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>
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
@ -19,7 +19,18 @@
|
|||
"generic": [
|
||||
{
|
||||
"label": "Automate Designs with Bannerbear and n8n",
|
||||
"url": "https://medium.com/n8n-io/automate-designs-with-bannerbear-and-n8n-2b64c94b54db"
|
||||
"icon": "🎨",
|
||||
"url": "https://n8n.io/blog/automate-designs-with-bannerbear-and-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "Automating Conference Organization Processes with n8n",
|
||||
"icon": "🙋♀️",
|
||||
"url": "https://n8n.io/blog/automating-conference-organization-processes-with-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "Benefits of automation and n8n: An interview with HubSpot's Hugh Durkin",
|
||||
"icon": "🎖",
|
||||
"url": "https://n8n.io/blog/benefits-of-automation-and-n8n-an-interview-with-hubspots-hugh-durkin/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import {
|
|||
IDataObject,
|
||||
IHookFunctions,
|
||||
IWebhookFunctions,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -22,7 +24,7 @@ export async function bannerbearApiRequest(this: IExecuteFunctions | IWebhookFun
|
|||
const credentials = this.getCredentials('bannerbearApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
|
@ -46,12 +48,7 @@ export async function bannerbearApiRequest(this: IExecuteFunctions | IWebhookFun
|
|||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
if (error.response && error.response.body && error.response.body.message) {
|
||||
// Try to return the error prettier
|
||||
//@ts-ignore
|
||||
throw new Error(`Bannerbear error response [${error.statusCode}]: ${error.response.body.message}`);
|
||||
}
|
||||
throw error;
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
IDataObject,
|
||||
IHookFunctions,
|
||||
IWebhookFunctions,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -18,7 +19,7 @@ export async function createDatapoint(this: IExecuteFunctions | IWebhookFunction
|
|||
const credentials = this.getCredentials('beeminderApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
const endpoint = `/users/${credentials.user}/goals/${data.goalName}/datapoints.json`;
|
||||
|
@ -30,7 +31,7 @@ export async function getAllDatapoints(this: IExecuteFunctions | IHookFunctions
|
|||
const credentials = this.getCredentials('beeminderApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
const endpoint = `/users/${credentials.user}/goals/${data.goalName}/datapoints.json`;
|
||||
|
@ -46,7 +47,7 @@ export async function updateDatapoint(this: IExecuteFunctions | IWebhookFunction
|
|||
const credentials = this.getCredentials('beeminderApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
const endpoint = `/users/${credentials.user}/goals/${data.goalName}/datapoints/${data.datapointId}.json`;
|
||||
|
@ -58,7 +59,7 @@ export async function deleteDatapoint(this: IExecuteFunctions | IWebhookFunction
|
|||
const credentials = this.getCredentials('beeminderApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
const endpoint = `/users/${credentials.user}/goals/${data.goalName}/datapoints/${data.datapointId}.json`;
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -308,7 +309,7 @@ export class Beeminder implements INodeType {
|
|||
const credentials = this.getCredentials('beeminderApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
const endpoint = `/users/${credentials.user}/goals.json`;
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
IDataObject,
|
||||
IHookFunctions,
|
||||
IWebhookFunctions,
|
||||
NodeApiError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
const BEEMINDER_URI = 'https://www.beeminder.com/api/v1';
|
||||
|
@ -40,10 +41,7 @@ export async function beeminderApiRequest(this: IExecuteFunctions | IWebhookFunc
|
|||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
if (error?.message) {
|
||||
throw new Error(`Beeminder error response [${error.statusCode}]: ${error.message}`);
|
||||
}
|
||||
throw error;
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -280,7 +280,7 @@ export class BitbucketTrigger implements INodeType {
|
|||
}
|
||||
try {
|
||||
await bitbucketApiRequest.call(this, 'GET', endpoint);
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -5,12 +5,12 @@ import {
|
|||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
import { IDataObject } from 'n8n-workflow';
|
||||
import { IDataObject, NodeApiError, NodeOperationError, } from 'n8n-workflow';
|
||||
|
||||
export async function bitbucketApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('bitbucketApi');
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
let options: OptionsWithUri = {
|
||||
method,
|
||||
|
@ -30,8 +30,8 @@ export async function bitbucketApiRequest(this: IHookFunctions | IExecuteFunctio
|
|||
|
||||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (err) {
|
||||
throw new Error('Bitbucket Error: ' + err.message);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
IDataObject, NodeApiError, NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function bitlyApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
@ -32,7 +32,7 @@ export async function bitlyApiRequest(this: IHookFunctions | IExecuteFunctions |
|
|||
if (authenticationMethod === 'accessToken') {
|
||||
const credentials = this.getCredentials('bitlyApi');
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
options.headers = { Authorization: `Bearer ${credentials.accessToken}`};
|
||||
|
||||
|
@ -42,15 +42,7 @@ export async function bitlyApiRequest(this: IHookFunctions | IExecuteFunctions |
|
|||
return await this.helpers.requestOAuth2!.call(this, 'bitlyOAuth2Api', options, { tokenType: 'Bearer' });
|
||||
}
|
||||
} catch(error) {
|
||||
|
||||
if (error.response && error.response.body && error.response.body.message) {
|
||||
// Try to return the error prettier
|
||||
const errorBody = error.response.body;
|
||||
throw new Error(`Bitly error response [${error.statusCode}]: ${errorBody.message}`);
|
||||
}
|
||||
|
||||
// Expected error data did not get returned so throw the actual error
|
||||
throw error;
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
20
packages/nodes-base/nodes/Bitwarden/Bitwarden.node.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"node": "n8n-nodes-base.bitwarden",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": [
|
||||
"Data & Storage"
|
||||
],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/credentials/bitwarden"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.bitwarden/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import {
|
|||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -177,7 +178,7 @@ export class Bitwarden implements INodeType {
|
|||
const updateFields = this.getNodeParameter('updateFields', i) as CollectionUpdateFields;
|
||||
|
||||
if (isEmpty(updateFields)) {
|
||||
throw new Error(`Please enter at least one field to update for the ${resource}.`);
|
||||
throw new NodeOperationError(this.getNode(), `Please enter at least one field to update for the ${resource}.`);
|
||||
}
|
||||
|
||||
const { groups, externalId } = updateFields;
|
||||
|
@ -308,7 +309,7 @@ export class Bitwarden implements INodeType {
|
|||
const updateFields = this.getNodeParameter('updateFields', i) as GroupUpdateFields;
|
||||
|
||||
if (isEmpty(updateFields)) {
|
||||
throw new Error(`Please enter at least one field to update for the ${resource}.`);
|
||||
throw new NodeOperationError(this.getNode(), `Please enter at least one field to update for the ${resource}.`);
|
||||
}
|
||||
|
||||
// set defaults for `name` and `accessAll`, required by Bitwarden but optional in n8n
|
||||
|
@ -452,7 +453,7 @@ export class Bitwarden implements INodeType {
|
|||
const updateFields = this.getNodeParameter('updateFields', i) as MemberUpdateFields;
|
||||
|
||||
if (isEmpty(updateFields)) {
|
||||
throw new Error(`Please enter at least one field to update for the ${resource}.`);
|
||||
throw new NodeOperationError(this.getNode(), `Please enter at least one field to update for the ${resource}.`);
|
||||
}
|
||||
|
||||
const { accessAll, collections, externalId, type } = updateFields;
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodePropertyOptions,
|
||||
NodeApiError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
@ -48,17 +49,7 @@ export async function bitwardenApiRequest(
|
|||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
|
||||
if (error.statusCode === 404) {
|
||||
throw new Error('Bitwarden error response [404]: Not found');
|
||||
}
|
||||
|
||||
if (error?.response?.body?.Message) {
|
||||
const message = error?.response?.body?.Message;
|
||||
throw new Error(`Bitwarden error response [${error.statusCode}]: ${message}`);
|
||||
}
|
||||
//TODO handle Errors array
|
||||
throw error;
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,7 +84,7 @@ export async function getAccessToken(
|
|||
const { access_token } = await this.helpers.request!(options);
|
||||
return access_token;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|