mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
Merge branch 'master' into feature/airtable-trigger
This commit is contained in:
commit
fd1897a288
|
@ -2,6 +2,21 @@
|
||||||
|
|
||||||
This list shows all the versions which include breaking changes and how to upgrade.
|
This list shows all the versions which include breaking changes and how to upgrade.
|
||||||
|
|
||||||
|
## 0.93.0
|
||||||
|
|
||||||
|
### What changed?
|
||||||
|
|
||||||
|
Change in naming of the Authentication field for the Pipedrive Trigger node.
|
||||||
|
|
||||||
|
### When is action necessary?
|
||||||
|
|
||||||
|
If you had set "Basic Auth" for the "Authentication" field in the node.
|
||||||
|
|
||||||
|
### How to upgrade:
|
||||||
|
|
||||||
|
The "Authentication" field has been renamed to "Incoming Authentication". Please set the parameter “Incoming Authentication” to “Basic Auth” to activate it again.
|
||||||
|
|
||||||
|
|
||||||
## 0.90.0
|
## 0.90.0
|
||||||
|
|
||||||
### What changed?
|
### What changed?
|
||||||
|
|
|
@ -15,7 +15,6 @@ import {
|
||||||
Db,
|
Db,
|
||||||
ExternalHooks,
|
ExternalHooks,
|
||||||
GenericHelpers,
|
GenericHelpers,
|
||||||
IExecutionsCurrentSummary,
|
|
||||||
LoadNodesAndCredentials,
|
LoadNodesAndCredentials,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
Server,
|
Server,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "n8n",
|
"name": "n8n",
|
||||||
"version": "0.92.0",
|
"version": "0.93.0",
|
||||||
"description": "n8n Workflow Automation Tool",
|
"description": "n8n Workflow Automation Tool",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"homepage": "https://n8n.io",
|
"homepage": "https://n8n.io",
|
||||||
|
@ -103,10 +103,10 @@
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"mongodb": "^3.5.5",
|
"mongodb": "^3.5.5",
|
||||||
"mysql2": "~2.1.0",
|
"mysql2": "~2.1.0",
|
||||||
"n8n-core": "~0.50.0",
|
"n8n-core": "~0.51.0",
|
||||||
"n8n-editor-ui": "~0.62.0",
|
"n8n-editor-ui": "~0.63.0",
|
||||||
"n8n-nodes-base": "~0.87.0",
|
"n8n-nodes-base": "~0.88.0",
|
||||||
"n8n-workflow": "~0.43.0",
|
"n8n-workflow": "~0.44.0",
|
||||||
"oauth-1.0a": "^2.2.6",
|
"oauth-1.0a": "^2.2.6",
|
||||||
"open": "^7.0.0",
|
"open": "^7.0.0",
|
||||||
"pg": "^8.3.0",
|
"pg": "^8.3.0",
|
||||||
|
|
|
@ -1534,17 +1534,21 @@ class App {
|
||||||
// Loads the currently saved workflow to execute instead of the
|
// Loads the currently saved workflow to execute instead of the
|
||||||
// one saved at the time of the execution.
|
// one saved at the time of the execution.
|
||||||
const workflowId = fullExecutionData.workflowData.id;
|
const workflowId = fullExecutionData.workflowData.id;
|
||||||
data.workflowData = await Db.collections.Workflow!.findOne(workflowId) as IWorkflowBase;
|
const workflowData = await Db.collections.Workflow!.findOne(workflowId) as IWorkflowBase;
|
||||||
|
|
||||||
if (data.workflowData === undefined) {
|
if (workflowData === undefined) {
|
||||||
throw new Error(`The workflow with the ID "${workflowId}" could not be found and so the data not be loaded for the retry.`);
|
throw new Error(`The workflow with the ID "${workflowId}" could not be found and so the data not be loaded for the retry.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.workflowData = workflowData;
|
||||||
|
const nodeTypes = NodeTypes();
|
||||||
|
const workflowInstance = new Workflow({ id: workflowData.id as string, name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: false, nodeTypes, staticData: undefined, settings: workflowData.settings });
|
||||||
|
|
||||||
// Replace all of the nodes in the execution stack with the ones of the new workflow
|
// Replace all of the nodes in the execution stack with the ones of the new workflow
|
||||||
for (const stack of data!.executionData!.executionData!.nodeExecutionStack) {
|
for (const stack of data!.executionData!.executionData!.nodeExecutionStack) {
|
||||||
// Find the data of the last executed node in the new workflow
|
// Find the data of the last executed node in the new workflow
|
||||||
const node = data.workflowData.nodes.find(node => node.name === stack.node.name);
|
const node = workflowInstance.getNode(stack.node.name);
|
||||||
if (node === undefined) {
|
if (node === null) {
|
||||||
throw new Error(`Could not find the node "${stack.node.name}" in workflow. It probably got deleted or renamed. Without it the workflow can sadly not be retried.`);
|
throw new Error(`Could not find the node "${stack.node.name}" in workflow. It probably got deleted or renamed. Without it the workflow can sadly not be retried.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -222,7 +222,7 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that we know that the workflow should run we can return the default respons
|
// Now that we know that the workflow should run we can return the default response
|
||||||
// directly if responseMode it set to "onReceived" and a respone should be sent
|
// directly if responseMode it set to "onReceived" and a respone should be sent
|
||||||
if (responseMode === 'onReceived' && didSendResponse === false) {
|
if (responseMode === 'onReceived' && didSendResponse === false) {
|
||||||
// Return response directly and do not wait for the workflow to finish
|
// Return response directly and do not wait for the workflow to finish
|
||||||
|
@ -302,6 +302,19 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
const returnData = WorkflowHelpers.getDataLastExecutedNodeData(data);
|
const returnData = WorkflowHelpers.getDataLastExecutedNodeData(data);
|
||||||
|
if(data.data.resultData.error || returnData?.error !== undefined) {
|
||||||
|
if (didSendResponse === false) {
|
||||||
|
responseCallback(null, {
|
||||||
|
data: {
|
||||||
|
message: 'Workflow did error.',
|
||||||
|
},
|
||||||
|
responseCode: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
didSendResponse = true;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
if (returnData === undefined) {
|
if (returnData === undefined) {
|
||||||
if (didSendResponse === false) {
|
if (didSendResponse === false) {
|
||||||
responseCallback(null, {
|
responseCallback(null, {
|
||||||
|
@ -313,17 +326,6 @@ export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] {
|
||||||
}
|
}
|
||||||
didSendResponse = true;
|
didSendResponse = true;
|
||||||
return data;
|
return data;
|
||||||
} else if (returnData.error !== undefined) {
|
|
||||||
if (didSendResponse === false) {
|
|
||||||
responseCallback(null, {
|
|
||||||
data: {
|
|
||||||
message: 'Workflow did error.',
|
|
||||||
},
|
|
||||||
responseCode: 500,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
didSendResponse = true;
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseData = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseData'], 'firstEntryJson');
|
const responseData = workflow.expression.getSimpleParameterValue(workflowStartNode, webhookData.webhookDescription['responseData'], 'firstEntryJson');
|
||||||
|
|
|
@ -202,6 +202,18 @@ function hookFunctionsPush(): IWorkflowExecuteHooks {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function hookFunctionsPreExecute(parentProcessMode?: string): IWorkflowExecuteHooks {
|
||||||
|
const externalHooks = ExternalHooks();
|
||||||
|
|
||||||
|
return {
|
||||||
|
workflowExecuteBefore: [
|
||||||
|
async function (this: WorkflowHooks, workflow: Workflow): Promise<void> {
|
||||||
|
await externalHooks.run('workflow.preExecute', [workflow, this.mode]);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns hook functions to save workflow execution and call error workflow
|
* Returns hook functions to save workflow execution and call error workflow
|
||||||
*
|
*
|
||||||
|
@ -337,7 +349,6 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
||||||
|
|
||||||
const externalHooks = ExternalHooks();
|
const externalHooks = ExternalHooks();
|
||||||
await externalHooks.init();
|
await externalHooks.init();
|
||||||
await externalHooks.run('workflow.execute', [workflowData, mode]);
|
|
||||||
|
|
||||||
const nodeTypes = NodeTypes();
|
const nodeTypes = NodeTypes();
|
||||||
|
|
||||||
|
@ -462,6 +473,10 @@ export async function getBase(credentials: IWorkflowCredentials, currentNodePara
|
||||||
export function getWorkflowHooksIntegrated(mode: WorkflowExecuteMode, executionId: string, workflowData: IWorkflowBase, optionalParameters?: IWorkflowHooksOptionalParameters): WorkflowHooks {
|
export function getWorkflowHooksIntegrated(mode: WorkflowExecuteMode, executionId: string, workflowData: IWorkflowBase, optionalParameters?: IWorkflowHooksOptionalParameters): WorkflowHooks {
|
||||||
optionalParameters = optionalParameters || {};
|
optionalParameters = optionalParameters || {};
|
||||||
const hookFunctions = hookFunctionsSave(optionalParameters.parentProcessMode);
|
const hookFunctions = hookFunctionsSave(optionalParameters.parentProcessMode);
|
||||||
|
const preExecuteFunctions = hookFunctionsPreExecute(optionalParameters.parentProcessMode);
|
||||||
|
for (const key of Object.keys(preExecuteFunctions)) {
|
||||||
|
hookFunctions[key]!.push.apply(hookFunctions[key], preExecuteFunctions[key]);
|
||||||
|
}
|
||||||
return new WorkflowHooks(hookFunctions, mode, executionId, workflowData, optionalParameters);
|
return new WorkflowHooks(hookFunctions, mode, executionId, workflowData, optionalParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -474,12 +489,19 @@ export function getWorkflowHooksIntegrated(mode: WorkflowExecuteMode, executionI
|
||||||
* @param {string} executionId
|
* @param {string} executionId
|
||||||
* @returns {WorkflowHooks}
|
* @returns {WorkflowHooks}
|
||||||
*/
|
*/
|
||||||
export function getWorkflowHooksMain(data: IWorkflowExecutionDataProcess, executionId: string): WorkflowHooks {
|
export function getWorkflowHooksMain(data: IWorkflowExecutionDataProcess, executionId: string, isMainProcess = false): WorkflowHooks {
|
||||||
const hookFunctions = hookFunctionsSave();
|
const hookFunctions = hookFunctionsSave();
|
||||||
const pushFunctions = hookFunctionsPush();
|
const pushFunctions = hookFunctionsPush();
|
||||||
for (const key of Object.keys(pushFunctions)) {
|
for (const key of Object.keys(pushFunctions)) {
|
||||||
hookFunctions[key]!.push.apply(hookFunctions[key], pushFunctions[key]);
|
hookFunctions[key]!.push.apply(hookFunctions[key], pushFunctions[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isMainProcess) {
|
||||||
|
const preExecuteFunctions = hookFunctionsPreExecute();
|
||||||
|
for (const key of Object.keys(preExecuteFunctions)) {
|
||||||
|
hookFunctions[key]!.push.apply(hookFunctions[key], preExecuteFunctions[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new WorkflowHooks(hookFunctions, data.executionMode, executionId, data.workflowData, { sessionId: data.sessionId, retryOf: data.retryOf as string});
|
return new WorkflowHooks(hookFunctions, data.executionMode, executionId, data.workflowData, { sessionId: data.sessionId, retryOf: data.retryOf as string});
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,9 +100,6 @@ export class WorkflowRunner {
|
||||||
* @memberof WorkflowRunner
|
* @memberof WorkflowRunner
|
||||||
*/
|
*/
|
||||||
async run(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean): Promise<string> {
|
async run(data: IWorkflowExecutionDataProcess, loadStaticData?: boolean): Promise<string> {
|
||||||
const externalHooks = ExternalHooks();
|
|
||||||
await externalHooks.run('workflow.execute', [data.workflowData, data.executionMode]);
|
|
||||||
|
|
||||||
const executionsProcess = config.get('executions.process') as string;
|
const executionsProcess = config.get('executions.process') as string;
|
||||||
|
|
||||||
let executionId: string;
|
let executionId: string;
|
||||||
|
@ -112,6 +109,7 @@ export class WorkflowRunner {
|
||||||
executionId = await this.runSubprocess(data, loadStaticData);
|
executionId = await this.runSubprocess(data, loadStaticData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const externalHooks = ExternalHooks();
|
||||||
if (externalHooks.exists('workflow.postExecute')) {
|
if (externalHooks.exists('workflow.postExecute')) {
|
||||||
this.activeExecutions.getPostExecutePromise(executionId)
|
this.activeExecutions.getPostExecutePromise(executionId)
|
||||||
.then(async (executionData) => {
|
.then(async (executionData) => {
|
||||||
|
@ -148,7 +146,7 @@ export class WorkflowRunner {
|
||||||
// Register the active execution
|
// Register the active execution
|
||||||
const executionId = this.activeExecutions.add(data, undefined);
|
const executionId = this.activeExecutions.add(data, undefined);
|
||||||
|
|
||||||
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(data, executionId);
|
additionalData.hooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(data, executionId, true);
|
||||||
|
|
||||||
let workflowExecution: PCancelable<IRun>;
|
let workflowExecution: PCancelable<IRun>;
|
||||||
if (data.executionData !== undefined) {
|
if (data.executionData !== undefined) {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import {
|
import {
|
||||||
CredentialsOverwrites,
|
CredentialsOverwrites,
|
||||||
CredentialTypes,
|
CredentialTypes,
|
||||||
|
ExternalHooks,
|
||||||
IWorkflowExecutionDataProcessWithExecution,
|
IWorkflowExecutionDataProcessWithExecution,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
WorkflowExecuteAdditionalData,
|
WorkflowExecuteAdditionalData,
|
||||||
|
@ -19,6 +20,7 @@ import {
|
||||||
INodeTypeData,
|
INodeTypeData,
|
||||||
IRun,
|
IRun,
|
||||||
ITaskData,
|
ITaskData,
|
||||||
|
IWorkflowExecuteHooks,
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowHooks,
|
WorkflowHooks,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
@ -68,6 +70,10 @@ export class WorkflowRunnerProcess {
|
||||||
const credentialsOverwrites = CredentialsOverwrites();
|
const credentialsOverwrites = CredentialsOverwrites();
|
||||||
await credentialsOverwrites.init(inputData.credentialsOverwrite);
|
await credentialsOverwrites.init(inputData.credentialsOverwrite);
|
||||||
|
|
||||||
|
// Load all external hooks
|
||||||
|
const externalHooks = ExternalHooks();
|
||||||
|
await externalHooks.init();
|
||||||
|
|
||||||
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});
|
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);
|
||||||
additionalData.hooks = this.getProcessForwardHooks();
|
additionalData.hooks = this.getProcessForwardHooks();
|
||||||
|
@ -121,7 +127,7 @@ export class WorkflowRunnerProcess {
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
getProcessForwardHooks(): WorkflowHooks {
|
getProcessForwardHooks(): WorkflowHooks {
|
||||||
const hookFunctions = {
|
const hookFunctions: IWorkflowExecuteHooks = {
|
||||||
nodeExecuteBefore: [
|
nodeExecuteBefore: [
|
||||||
async (nodeName: string): Promise<void> => {
|
async (nodeName: string): Promise<void> => {
|
||||||
this.sendHookToParentProcess('nodeExecuteBefore', [nodeName]);
|
this.sendHookToParentProcess('nodeExecuteBefore', [nodeName]);
|
||||||
|
@ -144,6 +150,11 @@ export class WorkflowRunnerProcess {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const preExecuteFunctions = WorkflowExecuteAdditionalData.hookFunctionsPreExecute();
|
||||||
|
for (const key of Object.keys(preExecuteFunctions)) {
|
||||||
|
hookFunctions[key]!.push.apply(hookFunctions[key], preExecuteFunctions[key]);
|
||||||
|
}
|
||||||
|
|
||||||
return new WorkflowHooks(hookFunctions, this.data!.executionMode, this.data!.executionId, this.data!.workflowData, { sessionId: this.data!.sessionId, retryOf: this.data!.retryOf as string });
|
return new WorkflowHooks(hookFunctions, this.data!.executionMode, this.data!.executionId, this.data!.workflowData, { sessionId: this.data!.sessionId, retryOf: this.data!.retryOf as string });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "n8n-core",
|
"name": "n8n-core",
|
||||||
"version": "0.50.0",
|
"version": "0.51.0",
|
||||||
"description": "Core functionality of n8n",
|
"description": "Core functionality of n8n",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"homepage": "https://n8n.io",
|
"homepage": "https://n8n.io",
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
"file-type": "^14.6.2",
|
"file-type": "^14.6.2",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"mime-types": "^2.1.27",
|
"mime-types": "^2.1.27",
|
||||||
"n8n-workflow": "~0.43.0",
|
"n8n-workflow": "~0.44.0",
|
||||||
"oauth-1.0a": "^2.2.6",
|
"oauth-1.0a": "^2.2.6",
|
||||||
"p-cancelable": "^2.0.0",
|
"p-cancelable": "^2.0.0",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
|
|
|
@ -127,7 +127,7 @@ export class ActiveWorkflows {
|
||||||
for (const item of pollTimes.item) {
|
for (const item of pollTimes.item) {
|
||||||
cronTime = [];
|
cronTime = [];
|
||||||
if (item.mode === 'custom') {
|
if (item.mode === 'custom') {
|
||||||
cronTimes.push(item.cronExpression as string);
|
cronTimes.push((item.cronExpression as string).trim());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (item.mode === 'everyMinute') {
|
if (item.mode === 'everyMinute') {
|
||||||
|
@ -178,6 +178,11 @@ export class ActiveWorkflows {
|
||||||
// Start the cron-jobs
|
// Start the cron-jobs
|
||||||
const cronJobs: CronJob[] = [];
|
const cronJobs: CronJob[] = [];
|
||||||
for (const cronTime of cronTimes) {
|
for (const cronTime of cronTimes) {
|
||||||
|
const cronTimeParts = cronTime.split(' ');
|
||||||
|
if (cronTimeParts.length > 0 && cronTimeParts[0].includes('*')) {
|
||||||
|
throw new Error('The polling interval is too short. It has to be at least a minute!');
|
||||||
|
}
|
||||||
|
|
||||||
cronJobs.push(new CronJob(cronTime, executeTrigger, undefined, true, timezone));
|
cronJobs.push(new CronJob(cronTime, executeTrigger, undefined, true, timezone));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -468,7 +468,6 @@ export class WorkflowExecute {
|
||||||
this.runExecutionData.startData = {};
|
this.runExecutionData.startData = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.executeHook('workflowExecuteBefore', []);
|
|
||||||
|
|
||||||
let currentExecutionTry = '';
|
let currentExecutionTry = '';
|
||||||
let lastExecutionTry = '';
|
let lastExecutionTry = '';
|
||||||
|
@ -482,6 +481,35 @@ export class WorkflowExecute {
|
||||||
});
|
});
|
||||||
|
|
||||||
const returnPromise = (async () => {
|
const returnPromise = (async () => {
|
||||||
|
try {
|
||||||
|
await this.executeHook('workflowExecuteBefore', [workflow]);
|
||||||
|
} catch (error) {
|
||||||
|
// Set the error that it can be saved correctly
|
||||||
|
executionError = {
|
||||||
|
message: error.message,
|
||||||
|
stack: error.stack,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set the incoming data of the node that it can be saved correctly
|
||||||
|
executionData = this.runExecutionData.executionData!.nodeExecutionStack[0] as IExecuteData;
|
||||||
|
this.runExecutionData.resultData = {
|
||||||
|
runData: {
|
||||||
|
[executionData.node.name]: [
|
||||||
|
{
|
||||||
|
startTime,
|
||||||
|
executionTime: (new Date().getTime()) - startTime,
|
||||||
|
data: ({
|
||||||
|
'main': executionData.data.main,
|
||||||
|
} as ITaskDataConnections),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
lastNodeExecuted: executionData.node.name,
|
||||||
|
error: executionError,
|
||||||
|
};
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
executionLoop:
|
executionLoop:
|
||||||
while (this.runExecutionData.executionData!.nodeExecutionStack.length !== 0) {
|
while (this.runExecutionData.executionData!.nodeExecutionStack.length !== 0) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "n8n-editor-ui",
|
"name": "n8n-editor-ui",
|
||||||
"version": "0.62.0",
|
"version": "0.63.0",
|
||||||
"description": "Workflow Editor UI for n8n",
|
"description": "Workflow Editor UI for n8n",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"homepage": "https://n8n.io",
|
"homepage": "https://n8n.io",
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.set": "^4.3.2",
|
"lodash.set": "^4.3.2",
|
||||||
"n8n-workflow": "~0.43.0",
|
"n8n-workflow": "~0.44.0",
|
||||||
"node-sass": "^4.12.0",
|
"node-sass": "^4.12.0",
|
||||||
"normalize-wheel": "^1.0.1",
|
"normalize-wheel": "^1.0.1",
|
||||||
"prismjs": "^1.17.1",
|
"prismjs": "^1.17.1",
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="doc-link-text">Need help? <a class="doc-hyperlink" :href="'https://docs.n8n.io/credentials/' + documentationUrl + '/?utm_source=n8n_app&utm_medium=left_nav_menu&utm_campaign=create_new_credentials_modal'" target="_blank">Open credential docs</a></span>
|
<span class="doc-link-text">Need help? <a class="doc-hyperlink" :href="documentationUrl" target="_blank">Open credential docs</a></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -119,7 +119,11 @@ export default mixins(
|
||||||
|
|
||||||
const credentialType = this.$store.getters.credentialType(credentialTypeName);
|
const credentialType = this.$store.getters.credentialType(credentialTypeName);
|
||||||
if (credentialType.documentationUrl !== undefined) {
|
if (credentialType.documentationUrl !== undefined) {
|
||||||
return `${credentialType.documentationUrl}`;
|
if (credentialType.documentationUrl.startsWith('http')) {
|
||||||
|
return credentialType.documentationUrl;
|
||||||
|
} else {
|
||||||
|
return 'https://docs.n8n.io/credentials/' + credentialType.documentationUrl + '/?utm_source=n8n_app&utm_medium=left_nav_menu&utm_campaign=create_new_credentials_modal';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
</div>
|
</div>
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<div v-if="showDocumentHelp && nodeType" class="doc-help-wrapper">
|
<div v-if="showDocumentHelp && nodeType" class="doc-help-wrapper">
|
||||||
<svg id="help-logo" v-if="showDocumentHelp && nodeType" :href="'https://docs.n8n.io/nodes/' + nodeType.name" target="_blank" width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<svg id="help-logo" v-if="showDocumentHelp && nodeType" :href="documentationUrl" target="_blank" width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<title>Node Documentation</title>
|
<title>Node Documentation</title>
|
||||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
<g transform="translate(-1127.000000, -836.000000)" fill-rule="nonzero">
|
<g transform="translate(-1127.000000, -836.000000)" fill-rule="nonzero">
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<div v-if="showDocumentHelp && nodeType" class="text">
|
<div v-if="showDocumentHelp && nodeType" class="text">
|
||||||
Need help? <a id="doc-hyperlink" v-if="showDocumentHelp && nodeType" :href="'https://docs.n8n.io/nodes/' + nodeType.name + '?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=' + nodeType.name" target="_blank">Open {{nodeType.displayName}} documentation</a>
|
Need help? <a id="doc-hyperlink" v-if="showDocumentHelp && nodeType" :href="documentationUrl" target="_blank">Open {{nodeType.displayName}} documentation</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
@ -65,6 +65,17 @@ export default Vue.extend({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
documentationUrl (): string {
|
||||||
|
if (!this.nodeType) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.nodeType.documentationUrl && this.nodeType.documentationUrl.startsWith('http')) {
|
||||||
|
return this.nodeType.documentationUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'https://docs.n8n.io/nodes/' + (this.nodeType.documentationUrl || this.nodeType.name) + '?utm_source=n8n_app&utm_medium=node_settings_modal-credential_link&utm_campaign=' + this.nodeType.name;
|
||||||
|
},
|
||||||
node (): INodeUi {
|
node (): INodeUi {
|
||||||
return this.$store.getters.activeNode;
|
return this.$store.getters.activeNode;
|
||||||
},
|
},
|
||||||
|
|
|
@ -82,8 +82,8 @@
|
||||||
</el-select>
|
</el-select>
|
||||||
|
|
||||||
<div v-else-if="parameter.type === 'color'" ref="inputField" class="color-input">
|
<div v-else-if="parameter.type === 'color'" ref="inputField" class="color-input">
|
||||||
<el-color-picker :value="displayValue" :disabled="isReadOnly" @change="valueChanged" size="small" class="color-picker" @focus="setFocus" :title="displayTitle" ></el-color-picker>
|
<el-color-picker :value="displayValue" :disabled="isReadOnly" @change="valueChanged" size="small" class="color-picker" @focus="setFocus" :title="displayTitle" :show-alpha="getArgument('showAlpha')"></el-color-picker>
|
||||||
<el-input v-model="tempValue" size="small" type="text" :value="displayValue" :disabled="isReadOnly" @change="valueChanged" @keydown.stop @focus="setFocus" :title="displayTitle" ></el-input>
|
<el-input v-model="tempValue" size="small" type="text" :value="tempValue" :disabled="isReadOnly" @change="valueChanged" @keydown.stop @focus="setFocus" :title="displayTitle" ></el-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="parameter.type === 'boolean'">
|
<div v-else-if="parameter.type === 'boolean'">
|
||||||
|
@ -213,6 +213,10 @@ export default mixins(
|
||||||
this.loadRemoteParameterOptions();
|
this.loadRemoteParameterOptions();
|
||||||
},
|
},
|
||||||
value () {
|
value () {
|
||||||
|
if (this.parameter.type === 'color' && this.getArgument('showAlpha') === true) {
|
||||||
|
// Do not set for color with alpha else wrong value gets displayed in field
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.tempValue = this.displayValue as string;
|
this.tempValue = this.displayValue as string;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -274,6 +278,18 @@ export default mixins(
|
||||||
returnValue = this.expressionValueComputed;
|
returnValue = this.expressionValueComputed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.parameter.type === 'color' && this.getArgument('showAlpha') === true && returnValue.charAt(0) === '#') {
|
||||||
|
// Convert the value to rgba that el-color-picker can display it correctly
|
||||||
|
const bigint = parseInt(returnValue.slice(1), 16);
|
||||||
|
const h = [];
|
||||||
|
h.push((bigint >> 24) & 255);
|
||||||
|
h.push((bigint >> 16) & 255);
|
||||||
|
h.push((bigint >> 8) & 255);
|
||||||
|
h.push((255 - bigint & 255) / 255);
|
||||||
|
|
||||||
|
returnValue = 'rgba('+h.join()+')';
|
||||||
|
}
|
||||||
|
|
||||||
if (returnValue !== undefined && returnValue !== null && this.parameter.type === 'string') {
|
if (returnValue !== undefined && returnValue !== null && this.parameter.type === 'string') {
|
||||||
const rows = this.getArgument('rows');
|
const rows = this.getArgument('rows');
|
||||||
if (rows === undefined || rows === 1) {
|
if (rows === undefined || rows === 1) {
|
||||||
|
@ -537,14 +553,35 @@ export default mixins(
|
||||||
// Set focus on field
|
// Set focus on field
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
(this.$refs.inputField.$el.querySelector('input') as HTMLInputElement).focus();
|
if (this.$refs.inputField.$el) {
|
||||||
|
// @ts-ignore
|
||||||
|
(this.$refs.inputField.$el.querySelector('input') as HTMLInputElement).focus();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
rgbaToHex (value: string): string | null {
|
||||||
|
// Convert rgba to hex from: https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
|
||||||
|
const valueMatch = (value as string).match(/^rgba\((\d+),\s*(\d+),\s*(\d+),\s*(\d+(\.\d+)?)\)$/);
|
||||||
|
if (valueMatch === null) {
|
||||||
|
// TODO: Display something if value is not valid
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const [r, g, b, a] = valueMatch.splice(1, 4).map(v => Number(v));
|
||||||
|
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1) + ((1 << 8) + Math.floor((1-a)*255)).toString(16).slice(1);
|
||||||
|
},
|
||||||
valueChanged (value: string | number | boolean | Date | null) {
|
valueChanged (value: string | number | boolean | Date | null) {
|
||||||
if (value instanceof Date) {
|
if (value instanceof Date) {
|
||||||
value = value.toISOString();
|
value = value.toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.parameter.type === 'color' && this.getArgument('showAlpha') === true && value !== null && value.toString().charAt(0) !== '#') {
|
||||||
|
const newValue = this.rgbaToHex(value as string);
|
||||||
|
if (newValue !== null) {
|
||||||
|
this.tempValue = newValue;
|
||||||
|
value = newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const parameterData = {
|
const parameterData = {
|
||||||
node: this.node !== null ? this.node.name : this.nodeName,
|
node: this.node !== null ? this.node.name : this.nodeName,
|
||||||
name: this.path,
|
name: this.path,
|
||||||
|
@ -570,6 +607,13 @@ export default mixins(
|
||||||
this.nodeName = this.node.name;
|
this.nodeName = this.node.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.parameter.type === 'color' && this.getArgument('showAlpha') === true && this.displayValue !== null && this.displayValue.toString().charAt(0) !== '#') {
|
||||||
|
const newValue = this.rgbaToHex(this.displayValue as string);
|
||||||
|
if (newValue !== null) {
|
||||||
|
this.tempValue = newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.remoteMethod !== undefined && this.node !== null) {
|
if (this.remoteMethod !== undefined && this.node !== null) {
|
||||||
// Make sure to load the parameter options
|
// Make sure to load the parameter options
|
||||||
// directly and whenever the credentials change
|
// directly and whenever the credentials change
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import {
|
||||||
|
ICredentialType,
|
||||||
|
NodePropertyTypes,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class GetResponseApi implements ICredentialType {
|
||||||
|
name = 'getResponseApi';
|
||||||
|
displayName = 'GetResponse API';
|
||||||
|
properties = [
|
||||||
|
{
|
||||||
|
displayName: 'API Key',
|
||||||
|
name: 'apiKey',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
import {
|
||||||
|
ICredentialType,
|
||||||
|
NodePropertyTypes,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class GetResponseOAuth2Api implements ICredentialType {
|
||||||
|
name = 'getResponseOAuth2Api';
|
||||||
|
extends = [
|
||||||
|
'oAuth2Api',
|
||||||
|
];
|
||||||
|
displayName = 'GetResponse OAuth2 API';
|
||||||
|
properties = [
|
||||||
|
{
|
||||||
|
displayName: 'Authorization URL',
|
||||||
|
name: 'authUrl',
|
||||||
|
type: 'hidden' as NodePropertyTypes,
|
||||||
|
default: 'https://app.getresponse.com/oauth2_authorize.html',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Access Token URL',
|
||||||
|
name: 'accessTokenUrl',
|
||||||
|
type: 'hidden' as NodePropertyTypes,
|
||||||
|
default: 'https://api.getresponse.com/v3/token',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Scope',
|
||||||
|
name: 'scope',
|
||||||
|
type: 'hidden' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Auth URI Query Parameters',
|
||||||
|
name: 'authQueryParameters',
|
||||||
|
type: 'hidden' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Authentication',
|
||||||
|
name: 'authentication',
|
||||||
|
type: 'hidden' as NodePropertyTypes,
|
||||||
|
default: 'header',
|
||||||
|
description: 'Resource to consume.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
33
packages/nodes-base/credentials/GotifyApi.credentials.ts
Normal file
33
packages/nodes-base/credentials/GotifyApi.credentials.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import {
|
||||||
|
ICredentialType,
|
||||||
|
NodePropertyTypes,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class GotifyApi implements ICredentialType {
|
||||||
|
name = 'gotifyApi';
|
||||||
|
displayName = 'Gotify API';
|
||||||
|
documentationUrl = 'gotify';
|
||||||
|
properties = [
|
||||||
|
{
|
||||||
|
displayName: 'App API Token',
|
||||||
|
name: 'appApiToken',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
description: '(Optional) Needed for message creation.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Client API Token',
|
||||||
|
name: 'clientApiToken',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
description: '(Optional) Needed for everything (delete, getAll) but message creation.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'URL',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
description: 'The URL of the Gotify host.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
import {
|
||||||
|
ICredentialType,
|
||||||
|
NodePropertyTypes,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class LineNotifyOAuth2Api implements ICredentialType {
|
||||||
|
name = 'lineNotifyOAuth2Api';
|
||||||
|
extends = [
|
||||||
|
'oAuth2Api',
|
||||||
|
];
|
||||||
|
displayName = 'Line Notify OAuth2 API';
|
||||||
|
documentationUrl = 'line';
|
||||||
|
properties = [
|
||||||
|
{
|
||||||
|
displayName: 'Authorization URL',
|
||||||
|
name: 'authUrl',
|
||||||
|
type: 'hidden' as NodePropertyTypes,
|
||||||
|
default: 'https://notify-bot.line.me/oauth/authorize',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Access Token URL',
|
||||||
|
name: 'accessTokenUrl',
|
||||||
|
type: 'hidden' as NodePropertyTypes,
|
||||||
|
default: 'https://notify-bot.line.me/oauth/token',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Scope',
|
||||||
|
name: 'scope',
|
||||||
|
type: 'hidden' as NodePropertyTypes,
|
||||||
|
default: 'notify',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Auth URI Query Parameters',
|
||||||
|
name: 'authQueryParameters',
|
||||||
|
type: 'hidden' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Authentication',
|
||||||
|
name: 'authentication',
|
||||||
|
type: 'hidden' as NodePropertyTypes,
|
||||||
|
default: 'body',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
|
@ -44,5 +44,11 @@ export class MicrosoftSql implements ICredentialType {
|
||||||
type: 'string' as NodePropertyTypes,
|
type: 'string' as NodePropertyTypes,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'TLS',
|
||||||
|
name: 'tls',
|
||||||
|
type: 'boolean' as NodePropertyTypes,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,5 +38,25 @@ export class Sftp implements ICredentialType {
|
||||||
},
|
},
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Private Key',
|
||||||
|
name: 'privateKey',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
typeOptions: {
|
||||||
|
alwaysOpenEditWindow: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'String that contains a private key for either key-based or hostbased user authentication (OpenSSH format).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Passphrase',
|
||||||
|
name: 'passphrase',
|
||||||
|
typeOptions: {
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
description: 'For an encrypted private key, this is the passphrase used to decrypt it.',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
33
packages/nodes-base/credentials/StrapiApi.credentials.ts
Normal file
33
packages/nodes-base/credentials/StrapiApi.credentials.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import {
|
||||||
|
ICredentialType,
|
||||||
|
NodePropertyTypes,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class StrapiApi implements ICredentialType {
|
||||||
|
name = 'strapiApi';
|
||||||
|
displayName = 'Strapi API';
|
||||||
|
properties = [
|
||||||
|
{
|
||||||
|
displayName: 'Email',
|
||||||
|
name: 'email',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Password',
|
||||||
|
name: 'password',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
typeOptions: {
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'URL',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
placeholder: 'https://api.example.com',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { ContainerOptions, Delivery } from 'rhea';
|
import { ContainerOptions, Delivery } from 'rhea';
|
||||||
|
|
||||||
import { IExecuteSingleFunctions } from 'n8n-core';
|
import { IExecuteFunctions } from 'n8n-core';
|
||||||
import {
|
import {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
|
@ -69,17 +69,15 @@ export class Amqp implements INodeType {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
async executeSingle(this: IExecuteSingleFunctions): Promise<INodeExecutionData> {
|
async execute(this: IExecuteFunctions): Promise < INodeExecutionData[][] > {
|
||||||
const item = this.getInputData();
|
|
||||||
|
|
||||||
const credentials = this.getCredentials('amqp');
|
const credentials = this.getCredentials('amqp');
|
||||||
if (!credentials) {
|
if (!credentials) {
|
||||||
throw new Error('Credentials are mandatory!');
|
throw new Error('Credentials are mandatory!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const sink = this.getNodeParameter('sink', '') as string;
|
const sink = this.getNodeParameter('sink', 0, '') as string;
|
||||||
const applicationProperties = this.getNodeParameter('headerParametersJson', {}) as string | object;
|
const applicationProperties = this.getNodeParameter('headerParametersJson', 0, {}) as string | object;
|
||||||
const options = this.getNodeParameter('options', {}) as IDataObject;
|
const options = this.getNodeParameter('options', 0, {}) as IDataObject;
|
||||||
|
|
||||||
let headerProperties = applicationProperties;
|
let headerProperties = applicationProperties;
|
||||||
if (typeof applicationProperties === 'string' && applicationProperties !== '') {
|
if (typeof applicationProperties === 'string' && applicationProperties !== '') {
|
||||||
|
@ -109,35 +107,43 @@ export class Amqp implements INodeType {
|
||||||
connectOptions.transport = credentials.transportType;
|
connectOptions.transport = credentials.transportType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allSent = new Promise(( resolve ) => {
|
const conn = container.connect(connectOptions);
|
||||||
container.on('sendable', (context: any) => { // tslint:disable-line:no-any
|
const sender = conn.open_sender(sink);
|
||||||
|
|
||||||
let body: IDataObject | string = item.json;
|
const responseData: IDataObject[] = await new Promise((resolve) => {
|
||||||
const sendOnlyProperty = options.sendOnlyProperty as string;
|
container.once('sendable', (context: any) => { // tslint:disable-line:no-any
|
||||||
|
const returnData = [];
|
||||||
|
|
||||||
if (sendOnlyProperty) {
|
const items = this.getInputData();
|
||||||
body = body[sendOnlyProperty] as string;
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const item = items[i];
|
||||||
|
|
||||||
|
let body: IDataObject | string = item.json;
|
||||||
|
const sendOnlyProperty = options.sendOnlyProperty as string;
|
||||||
|
|
||||||
|
if (sendOnlyProperty) {
|
||||||
|
body = body[sendOnlyProperty] as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.dataAsObject !== true) {
|
||||||
|
body = JSON.stringify(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = context.sender.send({
|
||||||
|
application_properties: headerProperties,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
returnData.push({ id: result.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.dataAsObject !== true) {
|
resolve(returnData);
|
||||||
body = JSON.stringify(body);
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = {
|
|
||||||
application_properties: headerProperties,
|
|
||||||
body,
|
|
||||||
};
|
|
||||||
|
|
||||||
const sendResult = context.sender.send(message);
|
|
||||||
|
|
||||||
resolve(sendResult);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
container.connect(connectOptions).open_sender(sink);
|
sender.close();
|
||||||
|
conn.close();
|
||||||
|
|
||||||
const sendResult: Delivery = await allSent as Delivery; // sendResult has a a property that causes circular reference if returned
|
return [this.helpers.returnJsonArray(responseData)];
|
||||||
|
|
||||||
return { json: { id: sendResult.id } } as INodeExecutionData;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,20 @@ export class AmqpTrigger implements INodeType {
|
||||||
default: false,
|
default: false,
|
||||||
description: 'Returns only the body property.',
|
description: 'Returns only the body property.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Messages per Cicle',
|
||||||
|
name: 'pullMessagesNumber',
|
||||||
|
type: 'number',
|
||||||
|
default: 100,
|
||||||
|
description: 'Number of messages to pull from the bus for every cicle',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Sleep Time',
|
||||||
|
name: 'sleepTime',
|
||||||
|
type: 'number',
|
||||||
|
default: 10,
|
||||||
|
description: 'Milliseconds to sleep after every cicle.',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -99,6 +113,7 @@ export class AmqpTrigger implements INodeType {
|
||||||
const clientname = this.getNodeParameter('clientname', '') as string;
|
const clientname = this.getNodeParameter('clientname', '') as string;
|
||||||
const subscription = this.getNodeParameter('subscription', '') as string;
|
const subscription = this.getNodeParameter('subscription', '') as string;
|
||||||
const options = this.getNodeParameter('options', {}) as IDataObject;
|
const options = this.getNodeParameter('options', {}) as IDataObject;
|
||||||
|
const pullMessagesNumber = options.pullMessagesNumber || 100;
|
||||||
|
|
||||||
if (sink === '') {
|
if (sink === '') {
|
||||||
throw new Error('Queue or Topic required!');
|
throw new Error('Queue or Topic required!');
|
||||||
|
@ -130,10 +145,13 @@ export class AmqpTrigger implements INodeType {
|
||||||
connectOptions.transport = credentials.transportType;
|
connectOptions.transport = credentials.transportType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let lastMsgId: number | undefined = undefined;
|
let lastMsgId: number | undefined = undefined;
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
|
container.on('receiver_open', (context: any) => { // tslint:disable-line:no-any
|
||||||
|
context.receiver.add_credit(pullMessagesNumber);
|
||||||
|
});
|
||||||
|
|
||||||
container.on('message', (context: any) => { // tslint:disable-line:no-any
|
container.on('message', (context: any) => { // tslint:disable-line:no-any
|
||||||
// ignore duplicate message check, don't think it's necessary, but it was in the rhea-lib example code
|
// ignore duplicate message check, don't think it's necessary, but it was in the rhea-lib example code
|
||||||
if (context.message.message_id && context.message.message_id === lastMsgId) {
|
if (context.message.message_id && context.message.message_id === lastMsgId) {
|
||||||
|
@ -143,6 +161,12 @@ export class AmqpTrigger implements INodeType {
|
||||||
|
|
||||||
let data = context.message;
|
let data = context.message;
|
||||||
|
|
||||||
|
if (options.jsonConvertByteArrayToString === true && data.body.content !== undefined) {
|
||||||
|
// The buffer is not ready... Stringify and parse back to load it.
|
||||||
|
const cont = JSON.stringify(data.body.content);
|
||||||
|
data.body = String.fromCharCode.apply(null, JSON.parse(cont).data);
|
||||||
|
}
|
||||||
|
|
||||||
if (options.jsonConvertByteArrayToString === true && data.body.content !== undefined) {
|
if (options.jsonConvertByteArrayToString === true && data.body.content !== undefined) {
|
||||||
// The buffer is not ready... Stringify and parse back to load it.
|
// The buffer is not ready... Stringify and parse back to load it.
|
||||||
const content = JSON.stringify(data.body.content);
|
const content = JSON.stringify(data.body.content);
|
||||||
|
@ -158,6 +182,12 @@ export class AmqpTrigger implements INodeType {
|
||||||
|
|
||||||
|
|
||||||
self.emit([self.helpers.returnJsonArray([data])]);
|
self.emit([self.helpers.returnJsonArray([data])]);
|
||||||
|
|
||||||
|
if (context.receiver.credit === 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
context.receiver.add_credit(pullMessagesNumber);
|
||||||
|
}, options.sleepTime as number || 10);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const connection = container.connect(connectOptions);
|
const connection = container.connect(connectOptions);
|
||||||
|
@ -170,14 +200,14 @@ export class AmqpTrigger implements INodeType {
|
||||||
durable: 2,
|
durable: 2,
|
||||||
expiry_policy: 'never',
|
expiry_policy: 'never',
|
||||||
},
|
},
|
||||||
credit_window: 1, // prefetch 1
|
credit_window: 0, // prefetch 1
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
clientOptions = {
|
clientOptions = {
|
||||||
source: {
|
source: {
|
||||||
address: sink,
|
address: sink,
|
||||||
},
|
},
|
||||||
credit_window: 1, // prefetch 1
|
credit_window: 0, // prefetch 1
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
connection.open_receiver(clientOptions);
|
connection.open_receiver(clientOptions);
|
||||||
|
@ -186,6 +216,8 @@ export class AmqpTrigger implements INodeType {
|
||||||
// The "closeFunction" function gets called by n8n whenever
|
// The "closeFunction" function gets called by n8n whenever
|
||||||
// the workflow gets deactivated and can so clean up.
|
// the workflow gets deactivated and can so clean up.
|
||||||
async function closeFunction() {
|
async function closeFunction() {
|
||||||
|
container.removeAllListeners('receiver_open');
|
||||||
|
container.removeAllListeners('message');
|
||||||
connection.close();
|
connection.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,12 @@ import {
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import * as gm from 'gm';
|
import * as gm from 'gm';
|
||||||
|
import { file } from 'tmp-promise';
|
||||||
|
import {
|
||||||
|
writeFile as fsWriteFile,
|
||||||
|
} from 'fs';
|
||||||
|
import { promisify } from 'util';
|
||||||
|
const fsWriteFileAsync = promisify(fsWriteFile);
|
||||||
|
|
||||||
|
|
||||||
export class EditImage implements INodeType {
|
export class EditImage implements INodeType {
|
||||||
|
@ -61,6 +67,11 @@ export class EditImage implements INodeType {
|
||||||
value: 'resize',
|
value: 'resize',
|
||||||
description: 'Change the size of image',
|
description: 'Change the size of image',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Shear',
|
||||||
|
value: 'shear',
|
||||||
|
description: 'Shear image along the X or Y axis',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Text',
|
name: 'Text',
|
||||||
value: 'text',
|
value: 'text',
|
||||||
|
@ -385,6 +396,11 @@ export class EditImage implements INodeType {
|
||||||
value: 'onlyIfSmaller',
|
value: 'onlyIfSmaller',
|
||||||
description: 'Resize only if image is smaller than width or height',
|
description: 'Resize only if image is smaller than width or height',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Percent',
|
||||||
|
value: 'percent',
|
||||||
|
description: 'Width and height are specified in percents.',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
default: 'maximumArea',
|
default: 'maximumArea',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
|
@ -422,7 +438,10 @@ export class EditImage implements INodeType {
|
||||||
displayName: 'Background Color',
|
displayName: 'Background Color',
|
||||||
name: 'backgroundColor',
|
name: 'backgroundColor',
|
||||||
type: 'color',
|
type: 'color',
|
||||||
default: '#ffffff',
|
default: '#ffffffff',
|
||||||
|
typeOptions: {
|
||||||
|
showAlpha: true,
|
||||||
|
},
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
operation: [
|
operation: [
|
||||||
|
@ -433,6 +452,39 @@ export class EditImage implements INodeType {
|
||||||
description: 'The color to use for the background when image gets rotated by anything which is not a multiple of 90..',
|
description: 'The color to use for the background when image gets rotated by anything which is not a multiple of 90..',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// shear
|
||||||
|
// ----------------------------------
|
||||||
|
{
|
||||||
|
displayName: 'Degrees X',
|
||||||
|
name: 'degreesX',
|
||||||
|
type: 'number',
|
||||||
|
default: 0,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'shear',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'X (horizontal) shear degrees.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Degrees Y',
|
||||||
|
name: 'degreesY',
|
||||||
|
type: 'number',
|
||||||
|
default: 0,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'shear',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'Y (vertical) shear degrees.',
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
displayName: 'Options',
|
displayName: 'Options',
|
||||||
name: 'options',
|
name: 'options',
|
||||||
|
@ -503,7 +555,6 @@ export class EditImage implements INodeType {
|
||||||
},
|
},
|
||||||
description: 'Sets the jpeg|png|tiff compression level from 0 to 100 (best).',
|
description: 'Sets the jpeg|png|tiff compression level from 0 to 100 (best).',
|
||||||
},
|
},
|
||||||
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -529,6 +580,8 @@ export class EditImage implements INodeType {
|
||||||
|
|
||||||
let gmInstance = gm(Buffer.from(item.binary![dataPropertyName as string].data, BINARY_ENCODING));
|
let gmInstance = gm(Buffer.from(item.binary![dataPropertyName as string].data, BINARY_ENCODING));
|
||||||
|
|
||||||
|
gmInstance = gmInstance.background('transparent');
|
||||||
|
|
||||||
if (operation === 'blur') {
|
if (operation === 'blur') {
|
||||||
const blur = this.getNodeParameter('blur') as number;
|
const blur = this.getNodeParameter('blur') as number;
|
||||||
const sigma = this.getNodeParameter('sigma') as number;
|
const sigma = this.getNodeParameter('sigma') as number;
|
||||||
|
@ -574,6 +627,8 @@ export class EditImage implements INodeType {
|
||||||
option = '<';
|
option = '<';
|
||||||
} else if (resizeOption === 'onlyIfLarger') {
|
} else if (resizeOption === 'onlyIfLarger') {
|
||||||
option = '>';
|
option = '>';
|
||||||
|
} else if (resizeOption === 'percent') {
|
||||||
|
option = '%';
|
||||||
}
|
}
|
||||||
|
|
||||||
gmInstance = gmInstance.resize(width, height, option);
|
gmInstance = gmInstance.resize(width, height, option);
|
||||||
|
@ -581,6 +636,10 @@ export class EditImage implements INodeType {
|
||||||
const rotate = this.getNodeParameter('rotate') as number;
|
const rotate = this.getNodeParameter('rotate') as number;
|
||||||
const backgroundColor = this.getNodeParameter('backgroundColor') as string;
|
const backgroundColor = this.getNodeParameter('backgroundColor') as string;
|
||||||
gmInstance = gmInstance.rotate(backgroundColor, rotate);
|
gmInstance = gmInstance.rotate(backgroundColor, rotate);
|
||||||
|
} else if (operation === 'shear') {
|
||||||
|
const xDegrees = this.getNodeParameter('degreesX') as number;
|
||||||
|
const yDegress = this.getNodeParameter('degreesY') as number;
|
||||||
|
gmInstance = gmInstance.shear(xDegrees, yDegress);
|
||||||
} else if (operation === 'text') {
|
} else if (operation === 'text') {
|
||||||
const fontColor = this.getNodeParameter('fontColor') as string;
|
const fontColor = this.getNodeParameter('fontColor') as string;
|
||||||
const fontSize = this.getNodeParameter('fontSize') as number;
|
const fontSize = this.getNodeParameter('fontSize') as number;
|
||||||
|
@ -624,6 +683,8 @@ export class EditImage implements INodeType {
|
||||||
// data references which do not get changed still stay behind
|
// data references which do not get changed still stay behind
|
||||||
// but the incoming data does not get changed.
|
// but the incoming data does not get changed.
|
||||||
Object.assign(newItem.binary, item.binary);
|
Object.assign(newItem.binary, item.binary);
|
||||||
|
// Make a deep copy of the binary data we change
|
||||||
|
newItem.binary![dataPropertyName as string] = JSON.parse(JSON.stringify(newItem.binary![dataPropertyName as string]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.quality !== undefined) {
|
if (options.quality !== undefined) {
|
||||||
|
|
|
@ -288,6 +288,8 @@ export class Ftp implements INodeType {
|
||||||
port: credentials.port as number,
|
port: credentials.port as number,
|
||||||
username: credentials.username as string,
|
username: credentials.username as string,
|
||||||
password: credentials.password as string,
|
password: credentials.password as string,
|
||||||
|
privateKey: credentials.privateKey as string | undefined,
|
||||||
|
passphrase: credentials.passphrase as string | undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
646
packages/nodes-base/nodes/GetResponse/ContactDescription.ts
Normal file
646
packages/nodes-base/nodes/GetResponse/ContactDescription.ts
Normal file
|
@ -0,0 +1,646 @@
|
||||||
|
import {
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const contactOperations = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create a new contact',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete a contact',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Get a contact',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get All',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Get all contacts',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update contact properties',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'get',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
] as INodeProperties[];
|
||||||
|
|
||||||
|
export const contactFields = [
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* contact:create */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'Email',
|
||||||
|
name: 'email',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Campaign ID',
|
||||||
|
name: 'campaignId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getCampaigns',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Custom Fields',
|
||||||
|
name: 'customFieldsUi',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'Add Custom Field',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'customFieldValues',
|
||||||
|
displayName: 'Custom Field',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Field ID',
|
||||||
|
name: 'customFieldId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getCustomFields',
|
||||||
|
},
|
||||||
|
description: 'The end user specified key of the user defined data.',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The end user specified value of the user defined data.',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Day Of Cycle',
|
||||||
|
name: 'dayOfCycle',
|
||||||
|
type: 'string',
|
||||||
|
description: `The day on which the contact is in the Autoresponder cycle. null indicates the contacts is not in the cycle.`,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'IP Address',
|
||||||
|
name: 'ipAddress',
|
||||||
|
type: 'string',
|
||||||
|
description: `The contact's IP address. IPv4 and IPv6 formats are accepted.`,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Note',
|
||||||
|
name: 'note',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Scoring',
|
||||||
|
name: 'scoring',
|
||||||
|
type: 'number',
|
||||||
|
default: '',
|
||||||
|
description: 'Contact scoring, pass null to remove the score from a contact',
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Tag IDs',
|
||||||
|
name: 'tags',
|
||||||
|
type: 'multiOptions',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTags',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* contact:delete */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'Contact ID',
|
||||||
|
name: 'contactId',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'delete',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Id of contact to delete.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'delete',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'IP Address',
|
||||||
|
name: 'ipAddress',
|
||||||
|
type: 'string',
|
||||||
|
description: `This makes it possible to pass the IP from which the contact unsubscribed. Used only if the messageId was send.`,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Message ID',
|
||||||
|
name: 'messageId',
|
||||||
|
type: 'string',
|
||||||
|
description: `The ID of a message (such as a newsletter, an autoresponder, or an RSS-newsletter). When passed, this method will simulate the unsubscribe process, as if the contact clicked the unsubscribe link in a given message.`,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* contact:get */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'Contact ID',
|
||||||
|
name: 'contactId',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'get',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Unique identifier for a particular contact',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'get',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'string',
|
||||||
|
description: `List of fields that should be returned. Id is always returned. Fields should be separated by comma`,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* contact:getAll */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: false,
|
||||||
|
description: 'If all results should be returned or only up to a given limit.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
type: 'number',
|
||||||
|
default: 20,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
returnAll: [
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Campaign ID',
|
||||||
|
name: 'campaignId',
|
||||||
|
type: 'string',
|
||||||
|
description: `Search contacts by campaign ID`,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Change On From',
|
||||||
|
name: 'changeOnFrom',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
description: `Search contacts edited from this date`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Change On To',
|
||||||
|
name: 'changeOnTo',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
description: `Search contacts edited to this date`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Created On From',
|
||||||
|
name: 'createdOnFrom',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
description: `Count data from this date`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Created On To',
|
||||||
|
name: 'createdOnTo',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
description: `Count data from this date`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Exact Match',
|
||||||
|
name: 'exactMatch',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: `When set to true it will search for contacts with the exact value<br>
|
||||||
|
of the email and name provided in the query string. Without this flag, matching is done via a standard 'like' comparison,<br>
|
||||||
|
which may sometimes be slow.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'string',
|
||||||
|
description: `List of fields that should be returned. Id is always returned. Fields should be separated by comma`,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
description: `Search contacts by name`,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Origin',
|
||||||
|
name: 'origin',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'API',
|
||||||
|
value: 'api',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Copy',
|
||||||
|
value: 'copy',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Email',
|
||||||
|
value: 'email',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Forward',
|
||||||
|
value: 'forward',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'import',
|
||||||
|
value: 'import',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Iphone',
|
||||||
|
value: 'iphone',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Landing Page',
|
||||||
|
value: 'landing_page',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Leads',
|
||||||
|
value: 'leads',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Panel',
|
||||||
|
value: 'panel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Sale',
|
||||||
|
value: 'sale',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Survey',
|
||||||
|
value: 'survey',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Webinar',
|
||||||
|
value: 'webinar',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'WWW',
|
||||||
|
value: 'www',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
description: `Search contacts by origin`,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Sort By',
|
||||||
|
name: 'sortBy',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Campaign ID',
|
||||||
|
value: 'campaignId',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Changed On',
|
||||||
|
value: 'changedOn',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Created On',
|
||||||
|
value: 'createdOn',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Email',
|
||||||
|
value: 'email',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Sort Order',
|
||||||
|
name: 'sortOrder',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'ASC',
|
||||||
|
value: 'ASC',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'DESC',
|
||||||
|
value: 'DESC',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* contact:update */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'Contact ID',
|
||||||
|
name: 'contactId',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Unique identifier for a particular contact',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Update Fields',
|
||||||
|
name: 'updateFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Campaign ID',
|
||||||
|
name: 'campaignId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getCampaigns',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Custom Fields',
|
||||||
|
name: 'customFieldsUi',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'Add Custom Field',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'customFieldValues',
|
||||||
|
displayName: 'Custom Field',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Field ID',
|
||||||
|
name: 'customFieldId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getCustomFields',
|
||||||
|
},
|
||||||
|
description: 'The end user specified key of the user defined data.',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The end user specified value of the user defined data.',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Day Of Cycle',
|
||||||
|
name: 'dayOfCycle',
|
||||||
|
type: 'string',
|
||||||
|
description: `The day on which the contact is in the Autoresponder cycle. null indicates the contacts is not in the cycle.`,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Email',
|
||||||
|
name: 'email',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'IP Address',
|
||||||
|
name: 'ipAddress',
|
||||||
|
type: 'string',
|
||||||
|
description: `The contact's IP address. IPv4 and IPv6 formats are accepted.`,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Note',
|
||||||
|
name: 'note',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Scoring',
|
||||||
|
name: 'scoring',
|
||||||
|
type: 'number',
|
||||||
|
default: '',
|
||||||
|
description: 'Contact scoring, pass null to remove the score from a contact',
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Tag IDs',
|
||||||
|
name: 'tags',
|
||||||
|
type: 'multiOptions',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTags',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
] as INodeProperties[];
|
70
packages/nodes-base/nodes/GetResponse/GenericFunctions.ts
Normal file
70
packages/nodes-base/nodes/GetResponse/GenericFunctions.ts
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import {
|
||||||
|
OptionsWithUri,
|
||||||
|
} from 'request';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IHookFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
IWebhookFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export async function getresponseApiRequest(this: IWebhookFunctions | IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
|
||||||
|
const authentication = this.getNodeParameter('authentication', 0, 'apiKey') as string;
|
||||||
|
|
||||||
|
let options: OptionsWithUri = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
method,
|
||||||
|
body,
|
||||||
|
qs,
|
||||||
|
uri: uri || `https://api.getresponse.com/v3${resource}`,
|
||||||
|
json: true,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
options = Object.assign({}, options, option);
|
||||||
|
if (Object.keys(body).length === 0) {
|
||||||
|
delete options.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authentication === 'apiKey') {
|
||||||
|
const credentials = this.getCredentials('getResponseApi') as IDataObject;
|
||||||
|
options!.headers!['X-Auth-Token'] = `api-key ${credentials.apiKey}`;
|
||||||
|
//@ts-ignore
|
||||||
|
return await this.helpers.request.call(this, options);
|
||||||
|
} else {
|
||||||
|
//@ts-ignore
|
||||||
|
return await this.helpers.requestOAuth2.call(this, 'getResponseOAuth2Api', options);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response && error.response.body && error.response.body.message) {
|
||||||
|
// Try to return the error prettier
|
||||||
|
throw new Error(`GetResponse error response [${error.statusCode}]: ${error.response.body.message}`);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getResponseApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
|
||||||
|
let responseData;
|
||||||
|
query.page = 1;
|
||||||
|
|
||||||
|
do {
|
||||||
|
responseData = await getresponseApiRequest.call(this, method, endpoint, body, query, undefined, { resolveWithFullResponse: true });
|
||||||
|
query.page++;
|
||||||
|
returnData.push.apply(returnData, responseData.body);
|
||||||
|
} while (
|
||||||
|
responseData.headers.TotalPages !== responseData.headers.CurrentPage
|
||||||
|
);
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
320
packages/nodes-base/nodes/GetResponse/GetResponse.node.ts
Normal file
320
packages/nodes-base/nodes/GetResponse/GetResponse.node.ts
Normal file
|
@ -0,0 +1,320 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodePropertyOptions,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getresponseApiRequest,
|
||||||
|
getResponseApiRequestAllItems,
|
||||||
|
} from './GenericFunctions';
|
||||||
|
|
||||||
|
import {
|
||||||
|
contactFields,
|
||||||
|
contactOperations,
|
||||||
|
} from './ContactDescription';
|
||||||
|
|
||||||
|
import * as moment from 'moment-timezone';
|
||||||
|
|
||||||
|
export class GetResponse implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'GetResponse',
|
||||||
|
name: 'getResponse',
|
||||||
|
icon: 'file:getResponse.png',
|
||||||
|
group: ['input'],
|
||||||
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
|
description: 'Consume GetResponse API.',
|
||||||
|
defaults: {
|
||||||
|
name: 'GetResponse',
|
||||||
|
color: '#00afec',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'getResponseApi',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
authentication: [
|
||||||
|
'apiKey',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'getResponseOAuth2Api',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
authentication: [
|
||||||
|
'oAuth2',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Authentication',
|
||||||
|
name: 'authentication',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'API Key',
|
||||||
|
value: 'apiKey',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'OAuth2',
|
||||||
|
value: 'oAuth2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'apiKey',
|
||||||
|
description: 'The resource to operate on.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Resource',
|
||||||
|
name: 'resource',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Contact',
|
||||||
|
value: 'contact',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'contact',
|
||||||
|
description: 'The resource to operate on.',
|
||||||
|
},
|
||||||
|
...contactOperations,
|
||||||
|
...contactFields,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
methods = {
|
||||||
|
loadOptions: {
|
||||||
|
// Get all the campaigns to display them to user so that he can
|
||||||
|
// select them easily
|
||||||
|
async getCampaigns(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const campaigns = await getresponseApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/campaigns`,
|
||||||
|
);
|
||||||
|
for (const campaign of campaigns) {
|
||||||
|
returnData.push({
|
||||||
|
name: campaign.name as string,
|
||||||
|
value: campaign.campaignId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
},
|
||||||
|
// Get all the tagd to display them to user so that he can
|
||||||
|
// select them easily
|
||||||
|
async getTags(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const tags = await getresponseApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/tags`,
|
||||||
|
);
|
||||||
|
for (const tag of tags) {
|
||||||
|
returnData.push({
|
||||||
|
name: tag.name as string,
|
||||||
|
value: tag.tagId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
},
|
||||||
|
// Get all the custom fields to display them to user so that he can
|
||||||
|
// select them easily
|
||||||
|
async getCustomFields(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const customFields = await getresponseApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/custom-fields`,
|
||||||
|
);
|
||||||
|
for (const customField of customFields) {
|
||||||
|
returnData.push({
|
||||||
|
name: customField.name as string,
|
||||||
|
value: customField.customFieldId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
const items = this.getInputData();
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
const length = (items.length as unknown) as number;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
let responseData;
|
||||||
|
const resource = this.getNodeParameter('resource', 0) as string;
|
||||||
|
const operation = this.getNodeParameter('operation', 0) as string;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
|
||||||
|
if (resource === 'contact') {
|
||||||
|
//https://apireference.getresponse.com/#operation/createContact
|
||||||
|
if (operation === 'create') {
|
||||||
|
const email = this.getNodeParameter('email', i) as string;
|
||||||
|
|
||||||
|
const campaignId = this.getNodeParameter('campaignId', i) as string;
|
||||||
|
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
email,
|
||||||
|
campaign: {
|
||||||
|
campaignId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(body, additionalFields);
|
||||||
|
|
||||||
|
if (additionalFields.customFieldsUi) {
|
||||||
|
const customFieldValues = (additionalFields.customFieldsUi as IDataObject).customFieldValues as IDataObject[];
|
||||||
|
if (customFieldValues) {
|
||||||
|
body.customFieldValues = customFieldValues;
|
||||||
|
for (let i = 0; i < customFieldValues.length; i++) {
|
||||||
|
if (!Array.isArray(customFieldValues[i].value)) {
|
||||||
|
customFieldValues[i].value = [customFieldValues[i].value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete body.customFieldsUi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await getresponseApiRequest.call(this, 'POST', '/contacts', body);
|
||||||
|
|
||||||
|
responseData = { success: true };
|
||||||
|
}
|
||||||
|
//https://apireference.getresponse.com/?_ga=2.160836350.2102802044.1604719933-1897033509.1604598019#operation/deleteContact
|
||||||
|
if (operation === 'delete') {
|
||||||
|
const contactId = this.getNodeParameter('contactId', i) as string;
|
||||||
|
|
||||||
|
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||||
|
|
||||||
|
Object.assign(qs, options);
|
||||||
|
|
||||||
|
responseData = await getresponseApiRequest.call(this, 'DELETE', `/contacts/${contactId}`, {}, qs);
|
||||||
|
|
||||||
|
responseData = { success: true };
|
||||||
|
}
|
||||||
|
//https://apireference.getresponse.com/?_ga=2.160836350.2102802044.1604719933-1897033509.1604598019#operation/getContactById
|
||||||
|
if (operation === 'get') {
|
||||||
|
const contactId = this.getNodeParameter('contactId', i) as string;
|
||||||
|
|
||||||
|
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||||
|
|
||||||
|
Object.assign(qs, options);
|
||||||
|
|
||||||
|
responseData = await getresponseApiRequest.call(this, 'GET', `/contacts/${contactId}`, {}, qs);
|
||||||
|
}
|
||||||
|
//https://apireference.getresponse.com/?_ga=2.160836350.2102802044.1604719933-1897033509.1604598019#operation/getContactList
|
||||||
|
if (operation === 'getAll') {
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||||
|
|
||||||
|
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||||
|
|
||||||
|
const timezone = this.getTimezone();
|
||||||
|
|
||||||
|
Object.assign(qs, options);
|
||||||
|
|
||||||
|
const isNotQuery = [
|
||||||
|
'sortBy',
|
||||||
|
'sortOrder',
|
||||||
|
'additionalFlags',
|
||||||
|
'fields',
|
||||||
|
'exactMatch',
|
||||||
|
];
|
||||||
|
|
||||||
|
const isDate = [
|
||||||
|
'createdOnFrom',
|
||||||
|
'createdOnTo',
|
||||||
|
'changeOnFrom',
|
||||||
|
'changeOnTo',
|
||||||
|
];
|
||||||
|
|
||||||
|
const dateMapToKey: { [key: string]: string; } = {
|
||||||
|
'createdOnFrom': '[createdOn][from]',
|
||||||
|
'createdOnTo': '[createdOn][to]',
|
||||||
|
'changeOnFrom': '[changeOn][from]',
|
||||||
|
'changeOnTo': '[changeOn][to]',
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const key of Object.keys(qs)) {
|
||||||
|
if (!isNotQuery.includes(key)) {
|
||||||
|
if (isDate.includes(key)) {
|
||||||
|
qs[`query${dateMapToKey[key]}`] = moment.tz(qs[key], timezone).format('YYYY-MM-DDTHH:mm:ssZZ');
|
||||||
|
} else {
|
||||||
|
qs[`query[${key}]`] = qs[key];
|
||||||
|
}
|
||||||
|
delete qs[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qs.sortBy) {
|
||||||
|
qs[`sort[${qs.sortBy}]`] = qs.sortOrder || 'ASC';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qs.exactMatch === true) {
|
||||||
|
qs['additionalFlags'] = 'exactMatch';
|
||||||
|
delete qs.exactMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await getResponseApiRequestAllItems.call(this, 'GET', `/contacts`, {}, qs);
|
||||||
|
} else {
|
||||||
|
qs.perPage = this.getNodeParameter('limit', i) as number;
|
||||||
|
responseData = await getresponseApiRequest.call(this, 'GET', `/contacts`, {}, qs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//https://apireference.getresponse.com/?_ga=2.160836350.2102802044.1604719933-1897033509.1604598019#operation/updateContact
|
||||||
|
if (operation === 'update') {
|
||||||
|
|
||||||
|
const contactId = this.getNodeParameter('contactId', i) as string;
|
||||||
|
|
||||||
|
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||||
|
|
||||||
|
const body: IDataObject = {};
|
||||||
|
|
||||||
|
Object.assign(body, updateFields);
|
||||||
|
|
||||||
|
if (updateFields.customFieldsUi) {
|
||||||
|
const customFieldValues = (updateFields.customFieldsUi as IDataObject).customFieldValues as IDataObject[];
|
||||||
|
if (customFieldValues) {
|
||||||
|
body.customFieldValues = customFieldValues;
|
||||||
|
delete body.customFieldsUi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await getresponseApiRequest.call(this, 'POST', `/contacts/${contactId}`, body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Array.isArray(responseData)) {
|
||||||
|
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||||
|
|
||||||
|
} else if (responseData !== undefined) {
|
||||||
|
returnData.push(responseData as IDataObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [this.helpers.returnJsonArray(returnData)];
|
||||||
|
}
|
||||||
|
}
|
BIN
packages/nodes-base/nodes/GetResponse/getResponse.png
Normal file
BIN
packages/nodes-base/nodes/GetResponse/getResponse.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 750 B |
68
packages/nodes-base/nodes/Gotify/GenericFunctions.ts
Normal file
68
packages/nodes-base/nodes/Gotify/GenericFunctions.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import {
|
||||||
|
OptionsWithUri,
|
||||||
|
} from 'request';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IExecuteSingleFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export async function gotifyApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, uri?: string | undefined, option = {}): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
|
||||||
|
const credentials = this.getCredentials('gotifyApi') as IDataObject;
|
||||||
|
|
||||||
|
const options: OptionsWithUri = {
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
'X-Gotify-Key': (method === 'POST') ? credentials.appApiToken : credentials.clientApiToken,
|
||||||
|
accept: 'application/json',
|
||||||
|
},
|
||||||
|
body,
|
||||||
|
qs,
|
||||||
|
uri: uri || `${credentials.url}${path}`,
|
||||||
|
json: true,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
if (Object.keys(body).length === 0) {
|
||||||
|
delete options.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
return await this.helpers.request.call(this, options);
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
if (error.response && error.response.body && error.response.body.errorDescription) {
|
||||||
|
const message = error.response.body.errorDescription;
|
||||||
|
// Try to return the error prettier
|
||||||
|
throw new Error(
|
||||||
|
`Gotify error response [${error.statusCode}]: ${message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function gotifyApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
|
||||||
|
let responseData;
|
||||||
|
let uri: string | undefined;
|
||||||
|
query.limit = 100;
|
||||||
|
do {
|
||||||
|
responseData = await gotifyApiRequest.call(this, method, endpoint, body, query, uri);
|
||||||
|
if (responseData.paging.next) {
|
||||||
|
uri = responseData.paging.next;
|
||||||
|
}
|
||||||
|
returnData.push.apply(returnData, responseData[propertyName]);
|
||||||
|
} while (
|
||||||
|
responseData.paging.next
|
||||||
|
);
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
262
packages/nodes-base/nodes/Gotify/Gotify.node.ts
Normal file
262
packages/nodes-base/nodes/Gotify/Gotify.node.ts
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
gotifyApiRequest,
|
||||||
|
gotifyApiRequestAllItems,
|
||||||
|
} from './GenericFunctions';
|
||||||
|
|
||||||
|
export class Gotify implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'Gotify',
|
||||||
|
name: 'gotify',
|
||||||
|
icon: 'file:gotify.png',
|
||||||
|
group: ['input'],
|
||||||
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
|
description: 'Consume Gotify API.',
|
||||||
|
defaults: {
|
||||||
|
name: 'Gotify',
|
||||||
|
color: '#71c8ec',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'gotifyApi',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Resource',
|
||||||
|
name: 'resource',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Message',
|
||||||
|
value: 'message',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'message',
|
||||||
|
description: 'The resource to operate on.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get All',
|
||||||
|
value: 'getAll',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
description: 'The resource to operate on.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Message',
|
||||||
|
name: 'message',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: `The message. Markdown (excluding html) is allowed.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Priority',
|
||||||
|
name: 'priority',
|
||||||
|
type: 'number',
|
||||||
|
default: 1,
|
||||||
|
description: 'The priority of the message.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Title',
|
||||||
|
name: 'title',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: `The title of the message.`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Message ID',
|
||||||
|
name: 'messageId',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'delete',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: `The message id.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: false,
|
||||||
|
description: 'If all results should be returned or only up to a given limit.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
type: 'number',
|
||||||
|
default: 20,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
returnAll: [
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
const items = this.getInputData();
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
const length = (items.length as unknown) as number;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
let responseData;
|
||||||
|
const resource = this.getNodeParameter('resource', 0) as string;
|
||||||
|
const operation = this.getNodeParameter('operation', 0) as string;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
if (resource === 'message') {
|
||||||
|
if (operation === 'create') {
|
||||||
|
|
||||||
|
const message = this.getNodeParameter('message', i) as string;
|
||||||
|
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
message,
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(body, additionalFields);
|
||||||
|
|
||||||
|
responseData = await gotifyApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
`/message`,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (operation === 'delete') {
|
||||||
|
const messageId = this.getNodeParameter('messageId', i) as string;
|
||||||
|
|
||||||
|
responseData = await gotifyApiRequest.call(
|
||||||
|
this,
|
||||||
|
'DELETE',
|
||||||
|
`/message/${messageId}`,
|
||||||
|
);
|
||||||
|
responseData = { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'getAll') {
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await gotifyApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'messages',
|
||||||
|
'GET',
|
||||||
|
'/message',
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
qs.limit = this.getNodeParameter('limit', i) as number;
|
||||||
|
responseData = await gotifyApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/message`,
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
responseData = responseData.messages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Array.isArray(responseData)) {
|
||||||
|
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||||
|
} else if (responseData !== undefined) {
|
||||||
|
returnData.push(responseData as IDataObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [this.helpers.returnJsonArray(returnData)];
|
||||||
|
}
|
||||||
|
}
|
BIN
packages/nodes-base/nodes/Gotify/gotify.png
Normal file
BIN
packages/nodes-base/nodes/Gotify/gotify.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
|
@ -100,39 +100,12 @@ export const dealFields = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
{
|
|
||||||
displayName: 'Deal Name',
|
|
||||||
name: 'dealName',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Pipeline',
|
|
||||||
name: 'pipeline',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Close Date',
|
|
||||||
name: 'closeDate',
|
|
||||||
type: 'dateTime',
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Amount',
|
displayName: 'Amount',
|
||||||
name: 'amount',
|
name: 'amount',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
displayName: 'Deal Type',
|
|
||||||
name: 'dealType',
|
|
||||||
type: 'options',
|
|
||||||
typeOptions: {
|
|
||||||
loadOptionsMethod: 'getDealTypes',
|
|
||||||
},
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Associated Company',
|
displayName: 'Associated Company',
|
||||||
name: 'associatedCompany',
|
name: 'associatedCompany',
|
||||||
|
@ -151,6 +124,68 @@ export const dealFields = [
|
||||||
},
|
},
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Close Date',
|
||||||
|
name: 'closeDate',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Custom Properties',
|
||||||
|
name: 'customPropertiesUi',
|
||||||
|
placeholder: 'Add Custom Property',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'customPropertiesValues',
|
||||||
|
displayName: 'Custom Property',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Property',
|
||||||
|
name: 'property',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getDealCustomProperties',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Name of the property.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Value of the property',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Deal Name',
|
||||||
|
name: 'dealName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Deal Type',
|
||||||
|
name: 'dealType',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getDealTypes',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Pipeline',
|
||||||
|
name: 'pipeline',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
|
@ -191,6 +226,53 @@ export const dealFields = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Amount',
|
||||||
|
name: 'amount',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Close Date',
|
||||||
|
name: 'closeDate',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Custom Properties',
|
||||||
|
name: 'customPropertiesUi',
|
||||||
|
placeholder: 'Add Custom Property',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'customPropertiesValues',
|
||||||
|
displayName: 'Custom Property',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Property',
|
||||||
|
name: 'property',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getDealCustomProperties',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Name of the property.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Value of the property',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Deal Name',
|
displayName: 'Deal Name',
|
||||||
name: 'dealName',
|
name: 'dealName',
|
||||||
|
@ -208,24 +290,6 @@ export const dealFields = [
|
||||||
default: '',
|
default: '',
|
||||||
description: 'The dealstage is required when creating a deal. See the CRM Pipelines API for details on managing pipelines and stages.',
|
description: 'The dealstage is required when creating a deal. See the CRM Pipelines API for details on managing pipelines and stages.',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
displayName: 'Pipeline',
|
|
||||||
name: 'pipeline',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Close Date',
|
|
||||||
name: 'closeDate',
|
|
||||||
type: 'dateTime',
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Amount',
|
|
||||||
name: 'amount',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Deal Type',
|
displayName: 'Deal Type',
|
||||||
name: 'dealType',
|
name: 'dealType',
|
||||||
|
@ -235,6 +299,12 @@ export const dealFields = [
|
||||||
},
|
},
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Pipeline',
|
||||||
|
name: 'pipeline',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
|
@ -552,6 +552,25 @@ export class Hubspot implements INodeType {
|
||||||
}
|
}
|
||||||
return returnData;
|
return returnData;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Get all the deal properties to display them to user so that he can
|
||||||
|
// select them easily
|
||||||
|
async getDealCustomProperties(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const endpoint = '/properties/v2/deals/properties';
|
||||||
|
const properties = await hubspotApiRequest.call(this, 'GET', endpoint, {});
|
||||||
|
for (const property of properties) {
|
||||||
|
if (property.hubspotDefined === null) {
|
||||||
|
const propertyName = property.label;
|
||||||
|
const propertyId = property.name;
|
||||||
|
returnData.push({
|
||||||
|
name: propertyName,
|
||||||
|
value: propertyId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
},
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* FORM */
|
/* FORM */
|
||||||
|
@ -1801,6 +1820,17 @@ export class Hubspot implements INodeType {
|
||||||
value: additionalFields.pipeline as string,
|
value: additionalFields.pipeline as string,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (additionalFields.customPropertiesUi) {
|
||||||
|
const customProperties = (additionalFields.customPropertiesUi as IDataObject).customPropertiesValues as IDataObject[];
|
||||||
|
if (customProperties) {
|
||||||
|
for (const customProperty of customProperties) {
|
||||||
|
body.properties.push({
|
||||||
|
name: customProperty.property,
|
||||||
|
value: customProperty.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
body.associations = association;
|
body.associations = association;
|
||||||
const endpoint = '/deals/v1/deal';
|
const endpoint = '/deals/v1/deal';
|
||||||
responseData = await hubspotApiRequest.call(this, 'POST', endpoint, body);
|
responseData = await hubspotApiRequest.call(this, 'POST', endpoint, body);
|
||||||
|
@ -1846,6 +1876,17 @@ export class Hubspot implements INodeType {
|
||||||
value: updateFields.pipeline as string,
|
value: updateFields.pipeline as string,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (updateFields.customPropertiesUi) {
|
||||||
|
const customProperties = (updateFields.customPropertiesUi as IDataObject).customPropertiesValues as IDataObject[];
|
||||||
|
if (customProperties) {
|
||||||
|
for (const customProperty of customProperties) {
|
||||||
|
body.properties.push({
|
||||||
|
name: customProperty.property,
|
||||||
|
value: customProperty.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
const endpoint = `/deals/v1/deal/${dealId}`;
|
const endpoint = `/deals/v1/deal/${dealId}`;
|
||||||
responseData = await hubspotApiRequest.call(this, 'PUT', endpoint, body);
|
responseData = await hubspotApiRequest.call(this, 'PUT', endpoint, body);
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function jiraSoftwareCloudApiRequestAllItems(this: IHookFunctions | IExecuteFunctions, propertyName: string, endpoint: string, method: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
export async function jiraSoftwareCloudApiRequestAllItems(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, endpoint: string, method: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
|
||||||
const returnData: IDataObject[] = [];
|
const returnData: IDataObject[] = [];
|
||||||
|
|
||||||
|
|
|
@ -112,12 +112,16 @@ export class Jira implements INodeType {
|
||||||
async getProjects(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
async getProjects(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
const returnData: INodePropertyOptions[] = [];
|
const returnData: INodePropertyOptions[] = [];
|
||||||
const jiraVersion = this.getCurrentNodeParameter('jiraVersion') as string;
|
const jiraVersion = this.getCurrentNodeParameter('jiraVersion') as string;
|
||||||
|
let endpoint = '';
|
||||||
|
let projects;
|
||||||
|
|
||||||
let endpoint = '/api/2/project/search';
|
|
||||||
if (jiraVersion === 'server') {
|
if (jiraVersion === 'server') {
|
||||||
endpoint = '/api/2/project';
|
endpoint = '/api/2/project';
|
||||||
|
projects = await jiraSoftwareCloudApiRequest.call(this, endpoint, 'GET');
|
||||||
|
} else {
|
||||||
|
endpoint = '/api/2/project/search';
|
||||||
|
projects = await jiraSoftwareCloudApiRequestAllItems.call(this, 'values', endpoint, 'GET');
|
||||||
}
|
}
|
||||||
let projects = await jiraSoftwareCloudApiRequest.call(this, endpoint, 'GET');
|
|
||||||
|
|
||||||
if (projects.values && Array.isArray(projects.values)) {
|
if (projects.values && Array.isArray(projects.values)) {
|
||||||
projects = projects.values;
|
projects = projects.values;
|
||||||
|
@ -130,6 +134,13 @@ export class Jira implements INodeType {
|
||||||
value: projectId,
|
value: projectId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
returnData.sort((a, b) => {
|
||||||
|
if (a.name < b.name) { return -1; }
|
||||||
|
if (a.name > b.name) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -165,6 +176,12 @@ export class Jira implements INodeType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
returnData.sort((a, b) => {
|
||||||
|
if (a.name < b.name) { return -1; }
|
||||||
|
if (a.name > b.name) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -184,6 +201,13 @@ export class Jira implements INodeType {
|
||||||
value: labelId,
|
value: labelId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
returnData.sort((a, b) => {
|
||||||
|
if (a.name < b.name) { return -1; }
|
||||||
|
if (a.name > b.name) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -203,6 +227,13 @@ export class Jira implements INodeType {
|
||||||
value: priorityId,
|
value: priorityId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
returnData.sort((a, b) => {
|
||||||
|
if (a.name < b.name) { return -1; }
|
||||||
|
if (a.name > b.name) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -241,6 +272,12 @@ export class Jira implements INodeType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
returnData.sort((a, b) => {
|
||||||
|
if (a.name < b.name) { return -1; }
|
||||||
|
if (a.name > b.name) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -260,6 +297,13 @@ export class Jira implements INodeType {
|
||||||
value: groupId,
|
value: groupId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
returnData.sort((a, b) => {
|
||||||
|
if (a.name < b.name) { return -1; }
|
||||||
|
if (a.name > b.name) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -277,6 +321,13 @@ export class Jira implements INodeType {
|
||||||
value: transition.id,
|
value: transition.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
returnData.sort((a, b) => {
|
||||||
|
if (a.name < b.name) { return -1; }
|
||||||
|
if (a.name > b.name) { return 1; }
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
50
packages/nodes-base/nodes/Line/GenericFunctions.ts
Normal file
50
packages/nodes-base/nodes/Line/GenericFunctions.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import {
|
||||||
|
OptionsWithUri,
|
||||||
|
} from 'request';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IExecuteSingleFunctions,
|
||||||
|
IHookFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export async function lineApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
|
||||||
|
let options: OptionsWithUri = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
method,
|
||||||
|
body,
|
||||||
|
qs,
|
||||||
|
uri: uri || ``,
|
||||||
|
json: true,
|
||||||
|
};
|
||||||
|
options = Object.assign({}, options, option);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (Object.keys(body).length === 0) {
|
||||||
|
delete options.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
return await this.helpers.requestOAuth2.call(this, 'lineNotifyOAuth2Api', options, { tokenType: 'Bearer' });
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
let errorMessage;
|
||||||
|
|
||||||
|
if (error.response && error.response.body && error.response.body.message) {
|
||||||
|
|
||||||
|
errorMessage = error.response.body.message;
|
||||||
|
|
||||||
|
throw new Error(`Line error response [${error.statusCode}]: ${errorMessage}`);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
144
packages/nodes-base/nodes/Line/Line.node.ts
Normal file
144
packages/nodes-base/nodes/Line/Line.node.ts
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
import {
|
||||||
|
BINARY_ENCODING,
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IBinaryKeyData,
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
lineApiRequest,
|
||||||
|
} from './GenericFunctions';
|
||||||
|
|
||||||
|
import {
|
||||||
|
notificationFields,
|
||||||
|
notificationOperations,
|
||||||
|
} from './NotificationDescription';
|
||||||
|
|
||||||
|
export class Line implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'Line',
|
||||||
|
name: 'line',
|
||||||
|
icon: 'file:line.png',
|
||||||
|
group: ['input'],
|
||||||
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
|
description: 'Consume Line API.',
|
||||||
|
defaults: {
|
||||||
|
name: 'Line',
|
||||||
|
color: '#00b900',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'lineNotifyOAuth2Api',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'notification',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Resource',
|
||||||
|
name: 'resource',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Notification',
|
||||||
|
value: 'notification',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'notification',
|
||||||
|
description: 'The resource to operate on.',
|
||||||
|
},
|
||||||
|
...notificationOperations,
|
||||||
|
...notificationFields,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
const items = this.getInputData();
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
const length = (items.length as unknown) as number;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
let responseData;
|
||||||
|
const resource = this.getNodeParameter('resource', 0) as string;
|
||||||
|
const operation = this.getNodeParameter('operation', 0) as string;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
|
||||||
|
if (resource === 'notification') {
|
||||||
|
//https://notify-bot.line.me/doc/en/
|
||||||
|
if (operation === 'send') {
|
||||||
|
const message = this.getNodeParameter('message', i) as string;
|
||||||
|
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
message,
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(body, additionalFields);
|
||||||
|
|
||||||
|
if (body.hasOwnProperty('notificationDisabled')) {
|
||||||
|
body.notificationDisabled = (body.notificationDisabled) ? 'true' : 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.stickerUi) {
|
||||||
|
const sticker = (body.stickerUi as IDataObject).stickerValue as IDataObject;
|
||||||
|
if (sticker) {
|
||||||
|
body.stickerId = sticker.stickerId;
|
||||||
|
body.stickerPackageId = sticker.stickerPackageId;
|
||||||
|
}
|
||||||
|
delete body.stickerUi;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.imageUi) {
|
||||||
|
const image = (body.imageUi as IDataObject).imageValue as IDataObject;
|
||||||
|
|
||||||
|
if (image && image.binaryData === true) {
|
||||||
|
if (items[i].binary === undefined) {
|
||||||
|
throw new Error('No binary data exists on item!');
|
||||||
|
}
|
||||||
|
//@ts-ignore
|
||||||
|
if (items[i].binary[image.binaryProperty] === undefined) {
|
||||||
|
throw new Error(`No binary data property "${image.binaryProperty}" does not exists on item!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const binaryData = (items[i].binary as IBinaryKeyData)[image.binaryProperty as string];
|
||||||
|
|
||||||
|
body.imageFile = {
|
||||||
|
value: Buffer.from(binaryData.data, BINARY_ENCODING),
|
||||||
|
options: {
|
||||||
|
filename: binaryData.fileName,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
body.imageFullsize = image.imageFullsize;
|
||||||
|
body.imageThumbnail = image.imageThumbnail;
|
||||||
|
}
|
||||||
|
delete body.imageUi;
|
||||||
|
}
|
||||||
|
responseData = await lineApiRequest.call(this, 'POST', '', {}, {}, 'https://notify-api.line.me/api/notify', { formData: body });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Array.isArray(responseData)) {
|
||||||
|
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||||
|
|
||||||
|
} else if (responseData !== undefined) {
|
||||||
|
returnData.push(responseData as IDataObject);
|
||||||
|
}
|
||||||
|
return [this.helpers.returnJsonArray(returnData)];
|
||||||
|
}
|
||||||
|
}
|
176
packages/nodes-base/nodes/Line/NotificationDescription.ts
Normal file
176
packages/nodes-base/nodes/Line/NotificationDescription.ts
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
import {
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const notificationOperations = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'notification',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Send',
|
||||||
|
value: 'send',
|
||||||
|
description: 'Sends notifications to users or groups',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'send',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
] as INodeProperties[];
|
||||||
|
|
||||||
|
export const notificationFields = [
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* notification:send */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'Message',
|
||||||
|
name: 'message',
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'send',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'notification',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'send',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'notification',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Image',
|
||||||
|
name: 'imageUi',
|
||||||
|
placeholder: 'Add Image',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: false,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'imageValue',
|
||||||
|
displayName: 'image',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Binary Data',
|
||||||
|
name: 'binaryData',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Image Full Size',
|
||||||
|
name: 'imageFullsize',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
binaryData: [
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'HTTP/HTTPS URL. Maximum size of 2048×2048px JPEG',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Image Thumbnail',
|
||||||
|
name: 'imageThumbnail',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
binaryData: [
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'HTTP/HTTPS URL. Maximum size of 240×240px JPEG',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Binary Property',
|
||||||
|
name: 'binaryProperty',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
binaryData: [
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: 'data',
|
||||||
|
description: `Name of the property that holds the binary data.<br>`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Notification Disabled',
|
||||||
|
name: 'notificationDisabled',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: `true: The user doesn't receive a push notification when the message is sent.<br>
|
||||||
|
false: The user receives a push notification when the message is sent`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Sticker',
|
||||||
|
name: 'stickerUi',
|
||||||
|
placeholder: 'Add Sticker',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: false,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'stickerValue',
|
||||||
|
displayName: 'Sticker',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Sticker ID',
|
||||||
|
name: 'stickerId',
|
||||||
|
type: 'number',
|
||||||
|
default: '',
|
||||||
|
description: 'Sticker ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Sticker Package ID',
|
||||||
|
name: 'stickerPackageId',
|
||||||
|
type: 'number',
|
||||||
|
default: '',
|
||||||
|
description: 'Package ID',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as INodeProperties[];
|
BIN
packages/nodes-base/nodes/Line/line.png
Normal file
BIN
packages/nodes-base/nodes/Line/line.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -217,6 +217,9 @@ export class MicrosoftSql implements INodeType {
|
||||||
user: credentials.user as string,
|
user: credentials.user as string,
|
||||||
password: credentials.password as string,
|
password: credentials.password as string,
|
||||||
domain: credentials.domain ? (credentials.domain as string) : undefined,
|
domain: credentials.domain ? (credentials.domain as string) : undefined,
|
||||||
|
options: {
|
||||||
|
encrypt: credentials.tls as boolean,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const pool = new mssql.ConnectionPool(config);
|
const pool = new mssql.ConnectionPool(config);
|
||||||
|
|
|
@ -54,13 +54,31 @@ export class PipedriveTrigger implements INodeType {
|
||||||
{
|
{
|
||||||
name: 'pipedriveApi',
|
name: 'pipedriveApi',
|
||||||
required: true,
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
authentication: [
|
||||||
|
'apiToken',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pipedriveOAuth2Api',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
authentication: [
|
||||||
|
'oAuth2',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'httpBasicAuth',
|
name: 'httpBasicAuth',
|
||||||
required: true,
|
required: true,
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
authentication: [
|
incomingAuthentication: [
|
||||||
'basicAuth',
|
'basicAuth',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -80,6 +98,23 @@ export class PipedriveTrigger implements INodeType {
|
||||||
displayName: 'Authentication',
|
displayName: 'Authentication',
|
||||||
name: 'authentication',
|
name: 'authentication',
|
||||||
type: 'options',
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'API Token',
|
||||||
|
value: 'apiToken',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'OAuth2',
|
||||||
|
value: 'oAuth2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'apiToken',
|
||||||
|
description: 'Method of authentication.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Incoming Authentication',
|
||||||
|
name: 'incomingAuthentication',
|
||||||
|
type: 'options',
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: 'Basic Auth',
|
name: 'Basic Auth',
|
||||||
|
@ -91,7 +126,7 @@ export class PipedriveTrigger implements INodeType {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
default: 'none',
|
default: 'none',
|
||||||
description: 'If authentication should be activated for the webhook (makes it more scure).',
|
description: 'If authentication should be activated for the webhook (makes it more secure).',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Action',
|
displayName: 'Action',
|
||||||
|
@ -218,7 +253,7 @@ export class PipedriveTrigger implements INodeType {
|
||||||
},
|
},
|
||||||
async create(this: IHookFunctions): Promise<boolean> {
|
async create(this: IHookFunctions): Promise<boolean> {
|
||||||
const webhookUrl = this.getNodeWebhookUrl('default');
|
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||||
const authentication = this.getNodeParameter('authentication', 0) as string;
|
const incomingAuthentication = this.getNodeParameter('incomingAuthentication', 0) as string;
|
||||||
const eventAction = this.getNodeParameter('action') as string;
|
const eventAction = this.getNodeParameter('action') as string;
|
||||||
const eventObject = this.getNodeParameter('object') as string;
|
const eventObject = this.getNodeParameter('object') as string;
|
||||||
|
|
||||||
|
@ -232,7 +267,7 @@ export class PipedriveTrigger implements INodeType {
|
||||||
http_auth_password: undefined as string | undefined,
|
http_auth_password: undefined as string | undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (authentication === 'basicAuth') {
|
if (incomingAuthentication === 'basicAuth') {
|
||||||
const httpBasicAuth = this.getCredentials('httpBasicAuth');
|
const httpBasicAuth = this.getCredentials('httpBasicAuth');
|
||||||
|
|
||||||
if (httpBasicAuth === undefined || !httpBasicAuth.user || !httpBasicAuth.password) {
|
if (httpBasicAuth === undefined || !httpBasicAuth.user || !httpBasicAuth.password) {
|
||||||
|
@ -285,9 +320,9 @@ export class PipedriveTrigger implements INodeType {
|
||||||
const resp = this.getResponseObject();
|
const resp = this.getResponseObject();
|
||||||
const realm = 'Webhook';
|
const realm = 'Webhook';
|
||||||
|
|
||||||
const authentication = this.getNodeParameter('authentication', 0) as string;
|
const incomingAuthentication = this.getNodeParameter('incomingAuthentication', 0) as string;
|
||||||
|
|
||||||
if (authentication === 'basicAuth') {
|
if (incomingAuthentication === 'basicAuth') {
|
||||||
// Basic authorization is needed to call webhook
|
// Basic authorization is needed to call webhook
|
||||||
const httpBasicAuth = this.getCredentials('httpBasicAuth');
|
const httpBasicAuth = this.getCredentials('httpBasicAuth');
|
||||||
|
|
||||||
|
|
|
@ -262,6 +262,38 @@ export const identifyFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Custom Traits',
|
||||||
|
name: 'customTraitsUi',
|
||||||
|
placeholder: 'Add Custom Trait',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
default: '',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'customTraitValues',
|
||||||
|
displayName: 'Custom Traits',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Key',
|
||||||
|
name: 'key',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -38,6 +38,7 @@ import {
|
||||||
} from './TrackInterface';
|
} from './TrackInterface';
|
||||||
|
|
||||||
import * as uuid from 'uuid/v4';
|
import * as uuid from 'uuid/v4';
|
||||||
|
import { customerFields } from '../CustomerIo/CustomerDescription';
|
||||||
|
|
||||||
export class Segment implements INodeType {
|
export class Segment implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
|
@ -170,6 +171,7 @@ export class Segment implements INodeType {
|
||||||
if (traits.id) {
|
if (traits.id) {
|
||||||
body.traits!.id = traits.id as string;
|
body.traits!.id = traits.id as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (traits.company) {
|
if (traits.company) {
|
||||||
const company = (traits.company as IDataObject).companyUi as IDataObject;
|
const company = (traits.company as IDataObject).companyUi as IDataObject;
|
||||||
if (company) {
|
if (company) {
|
||||||
|
@ -384,6 +386,14 @@ export class Segment implements INodeType {
|
||||||
if (traits.id) {
|
if (traits.id) {
|
||||||
body.traits!.id = traits.id as string;
|
body.traits!.id = traits.id as string;
|
||||||
}
|
}
|
||||||
|
if (traits.customTraitsUi) {
|
||||||
|
const customTraits = (traits.customTraitsUi as IDataObject).customTraitValues as IDataObject[];
|
||||||
|
if (customTraits && customTraits.length !== 0) {
|
||||||
|
for (const customTrait of customTraits) {
|
||||||
|
body.traits![customTrait.key as string] = customTrait.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (traits.company) {
|
if (traits.company) {
|
||||||
const company = (traits.company as IDataObject).companyUi as IDataObject;
|
const company = (traits.company as IDataObject).companyUi as IDataObject;
|
||||||
if (company) {
|
if (company) {
|
||||||
|
@ -531,6 +541,17 @@ export class Segment implements INodeType {
|
||||||
body.integrations!.salesforce = integrations.salesforce as boolean;
|
body.integrations!.salesforce = integrations.salesforce as boolean;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.keys(traits.company as IDataObject).length === 0) {
|
||||||
|
//@ts-ignore
|
||||||
|
delete body.traits.company;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(traits.address as IDataObject).length === 0) {
|
||||||
|
//@ts-ignore
|
||||||
|
delete body.traits.address;
|
||||||
|
}
|
||||||
|
|
||||||
responseData = await segmentApiRequest.call(this, 'POST', '/identify', body);
|
responseData = await segmentApiRequest.call(this, 'POST', '/identify', body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -602,6 +623,14 @@ export class Segment implements INodeType {
|
||||||
if (traits.id) {
|
if (traits.id) {
|
||||||
body.traits!.id = traits.id as string;
|
body.traits!.id = traits.id as string;
|
||||||
}
|
}
|
||||||
|
if (traits.customTraitsUi) {
|
||||||
|
const customTraits = (traits.customTraitsUi as IDataObject).customTraitValues as IDataObject[];
|
||||||
|
if (customTraits && customTraits.length !== 0) {
|
||||||
|
for (const customTrait of customTraits) {
|
||||||
|
body.traits![customTrait.key as string] = customTrait.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (traits.company) {
|
if (traits.company) {
|
||||||
const company = (traits.company as IDataObject).companyUi as IDataObject;
|
const company = (traits.company as IDataObject).companyUi as IDataObject;
|
||||||
if (company) {
|
if (company) {
|
||||||
|
@ -760,6 +789,17 @@ export class Segment implements INodeType {
|
||||||
body.properties!.value = properties.value as string;
|
body.properties!.value = properties.value as string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.keys(traits.company as IDataObject).length === 0) {
|
||||||
|
//@ts-ignore
|
||||||
|
delete body.traits.company;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(traits.address as IDataObject).length === 0) {
|
||||||
|
//@ts-ignore
|
||||||
|
delete body.traits.address;
|
||||||
|
}
|
||||||
|
|
||||||
responseData = await segmentApiRequest.call(this, 'POST', '/track', body);
|
responseData = await segmentApiRequest.call(this, 'POST', '/track', body);
|
||||||
}
|
}
|
||||||
//https://segment.com/docs/connections/sources/catalog/libraries/server/http-api/#page
|
//https://segment.com/docs/connections/sources/catalog/libraries/server/http-api/#page
|
||||||
|
|
|
@ -285,6 +285,38 @@ export const trackFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Custom Traits',
|
||||||
|
name: 'customTraitsUi',
|
||||||
|
placeholder: 'Add Custom Trait',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
default: '',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'customTraitValues',
|
||||||
|
displayName: 'Custom Traits',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Key',
|
||||||
|
name: 'key',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -24,7 +24,7 @@ export async function shopifyApiRequest(this: IHookFunctions | IExecuteFunctions
|
||||||
throw new Error('No credentials got returned!');
|
throw new Error('No credentials got returned!');
|
||||||
}
|
}
|
||||||
const headerWithAuthentication = Object.assign({},
|
const headerWithAuthentication = Object.assign({},
|
||||||
{ Authorization: ` Basic ${Buffer.from(`${credentials.apiKey}:${credentials.password}`).toString(BINARY_ENCODING)}` });
|
{ Authorization: `Basic ${Buffer.from(`${credentials.apiKey}:${credentials.password}`).toString(BINARY_ENCODING)}` });
|
||||||
|
|
||||||
const options: OptionsWithUri = {
|
const options: OptionsWithUri = {
|
||||||
headers: headerWithAuthentication,
|
headers: headerWithAuthentication,
|
||||||
|
@ -47,6 +47,7 @@ export async function shopifyApiRequest(this: IHookFunctions | IExecuteFunctions
|
||||||
try {
|
try {
|
||||||
return await this.helpers.request!(options);
|
return await this.helpers.request!(options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error.response.body);
|
||||||
if (error.response.body && error.response.body.errors) {
|
if (error.response.body && error.response.body.errors) {
|
||||||
let message = '';
|
let message = '';
|
||||||
if (typeof error.response.body.errors === 'object') {
|
if (typeof error.response.body.errors === 'object') {
|
||||||
|
|
|
@ -25,6 +25,7 @@ export class ShopifyTrigger implements INodeType {
|
||||||
icon: 'file:shopify.png',
|
icon: 'file:shopify.png',
|
||||||
group: ['trigger'],
|
group: ['trigger'],
|
||||||
version: 1,
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["event"]}}',
|
||||||
description: 'Handle Shopify events via webhooks',
|
description: 'Handle Shopify events via webhooks',
|
||||||
defaults: {
|
defaults: {
|
||||||
name: 'Shopify Trigger',
|
name: 'Shopify Trigger',
|
||||||
|
@ -55,271 +56,268 @@ export class ShopifyTrigger implements INodeType {
|
||||||
options:
|
options:
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: 'App uninstalled',
|
name: 'App Uninstalled',
|
||||||
value: 'app/uninstalled',
|
value: 'app/uninstalled',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Carts create',
|
name: 'Cart Created',
|
||||||
value: 'carts/create',
|
value: 'carts/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Carts update',
|
name: 'Cart Updated',
|
||||||
value: 'carts/update',
|
value: 'carts/update',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Checkouts create',
|
name: 'Checkout Created',
|
||||||
value: 'checkouts/create',
|
value: 'checkouts/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Checkouts delete',
|
name: 'Checkout Delete',
|
||||||
value: 'checkouts/delete',
|
value: 'checkouts/delete',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Checkouts update',
|
name: 'Checkout Update',
|
||||||
value: 'checkouts/update',
|
value: 'checkouts/update',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Collection listings add',
|
name: 'Collection Listings Added',
|
||||||
value: 'collection_listings/add',
|
value: 'collection_listings/add',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Collection listings remove',
|
name: 'Collection Listings Removed',
|
||||||
value: 'collection_listings/remove',
|
value: 'collection_listings/remove',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Collection listings update',
|
name: 'Collection Listings Updated',
|
||||||
value: 'collection_listings/update',
|
value: 'collection_listings/update',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Collections create',
|
name: 'Collection Created',
|
||||||
value: 'collections/create',
|
value: 'collections/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Collections delete',
|
name: 'Collection Deleted',
|
||||||
value: 'collections/delete',
|
value: 'collections/delete',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Collections update',
|
name: 'Collection Updated',
|
||||||
value: 'collections/update',
|
value: 'collections/update',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Customer groups create',
|
name: 'Customer Groups Created',
|
||||||
value: 'customer_groups/create',
|
value: 'customer_groups/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Customer groups delete',
|
name: 'Customer Groups Deleted',
|
||||||
value: 'customer_groups/delete',
|
value: 'customer_groups/delete',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Customer groups update',
|
name: 'Customer Groups Updated',
|
||||||
value: 'customer_groups/update',
|
value: 'customer_groups/update',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Customers create',
|
name: 'Customer Created',
|
||||||
value: 'customers/create',
|
value: 'customers/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Customers delete',
|
name: 'Customer Deleted',
|
||||||
value: 'customers/delete',
|
value: 'customers/delete',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Customers disable',
|
name: 'Customer disabled',
|
||||||
value: 'customers/disable',
|
value: 'customers/disable',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Customers enable',
|
name: 'Customer Enabled',
|
||||||
value: 'customers/enable',
|
value: 'customers/enable',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Customers update',
|
name: 'Customer Updated',
|
||||||
value: 'customers/update',
|
value: 'customers/update',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Draft orders create',
|
name: 'Draft Orders Created',
|
||||||
value: 'draft_orders/create',
|
value: 'draft_orders/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Draft orders delete',
|
name: 'Draft Orders Deleted',
|
||||||
value: 'draft_orders/delete',
|
value: 'draft_orders/delete',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Draft orders update',
|
name: 'Draft orders Updated',
|
||||||
value: 'draft_orders/update',
|
value: 'draft_orders/update',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Fulfillment events create',
|
name: 'Fulfillment Events Created',
|
||||||
value: 'fulfillment_events/create',
|
value: 'fulfillment_events/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Fulfillment events delete',
|
name: 'Fulfillment Events Deleted',
|
||||||
value: 'fulfillment_events/delete',
|
value: 'fulfillment_events/delete',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Fulfillments create',
|
name: 'Fulfillment created',
|
||||||
value: 'fulfillments/create',
|
value: 'fulfillments/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Fulfillments update',
|
name: 'Fulfillment Updated',
|
||||||
value: 'fulfillments/update',
|
value: 'fulfillments/update',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Inventory_items create',
|
name: 'Inventory Items Created',
|
||||||
value: 'inventory_items/create',
|
value: 'inventory_items/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Inventory_items delete',
|
name: 'Inventory Items Deleted',
|
||||||
value: 'inventory_items/delete',
|
value: 'inventory_items/delete',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Inventory_items update',
|
name: 'Inventory Items Updated',
|
||||||
value: 'inventory_items/update',
|
value: 'inventory_items/update',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Inventory_levels connect',
|
name: 'Inventory Levels Connected',
|
||||||
value: 'inventory_levels/connect',
|
value: 'inventory_levels/connect',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Inventory_levels disconnect',
|
name: 'Inventory Levels Disconnected',
|
||||||
value: 'inventory_levels/disconnect',
|
value: 'inventory_levels/disconnect',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Inventory_levels update',
|
name: 'Inventory Levels Updated',
|
||||||
value: 'inventory_levels/update',
|
value: 'inventory_levels/update',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Locales create',
|
name: 'Locale Created',
|
||||||
value: 'locales/create',
|
value: 'locales/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Locales update',
|
name: 'Locale Updated',
|
||||||
value: 'locales/update',
|
value: 'locales/update',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Locations create',
|
name: 'Location Created',
|
||||||
value: 'locations/create',
|
value: 'locations/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Locations delete',
|
name: 'Location Deleted',
|
||||||
value: 'locations/delete',
|
value: 'locations/delete',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Locations update',
|
name: 'Location Updated',
|
||||||
value: 'locations/update',
|
value: 'locations/update',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Order transactions create',
|
name: 'Order transactions Created',
|
||||||
value: 'order_transactions/create',
|
value: 'order_transactions/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Orders cancelled',
|
name: 'Order cancelled',
|
||||||
value: 'orders/cancelled',
|
value: 'orders/cancelled',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Orders create',
|
name: 'Order Created',
|
||||||
value: 'orders/create',
|
value: 'orders/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Orders delete',
|
name: 'Orders Deleted',
|
||||||
value: 'orders/delete',
|
value: 'orders/delete',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Orders fulfilled',
|
name: 'Order Fulfilled',
|
||||||
value: 'orders/fulfilled',
|
value: 'orders/fulfilled',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Orders paid',
|
name: 'Order Paid',
|
||||||
value: 'orders/paid',
|
value: 'orders/paid',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Orders partially fulfilled',
|
name: 'Order Partially Fulfilled',
|
||||||
value: 'orders/partially_fulfilled',
|
value: 'orders/partially_fulfilled',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Orders updated',
|
name: 'Order Updated',
|
||||||
value: 'orders/updated',
|
value: 'orders/updated',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Product listings add',
|
name: 'Product Listings Added',
|
||||||
value: 'product_listings/add',
|
value: 'product_listings/add',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Product listings remove',
|
name: 'Product Listings Removed',
|
||||||
value: 'product_listings/remove',
|
value: 'product_listings/remove',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Product listings update',
|
name: 'Product Listings Updated',
|
||||||
value: 'product_listings/update',
|
value: 'product_listings/update',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Products create',
|
name: 'Product Created',
|
||||||
value: 'products/create',
|
value: 'products/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Products delete',
|
name: 'Product Deleted',
|
||||||
value: 'products/delete',
|
value: 'products/delete',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Products update',
|
name: 'Product Updated',
|
||||||
value: 'products/update',
|
value: 'products/update',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Refunds create',
|
name: 'Refund Created',
|
||||||
value: 'refunds/create',
|
value: 'refunds/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Shop update',
|
name: 'Shop Updated',
|
||||||
value: 'shop/update',
|
value: 'shop/update',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Tender transactions create',
|
name: 'Tender Transactions Created',
|
||||||
value: 'tender_transactions/create',
|
value: 'tender_transactions/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Themes create',
|
name: 'Theme Created',
|
||||||
value: 'themes/create',
|
value: 'themes/create',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Themes delete',
|
name: 'Theme Deleted',
|
||||||
value: 'themes/delete',
|
value: 'themes/delete',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Themes publish',
|
name: 'Theme Published',
|
||||||
value: 'themes/publish',
|
value: 'themes/publish',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Themes update',
|
name: 'Theme Updated',
|
||||||
value: 'themes/update',
|
value: 'themes/update',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
description: 'Event that triggers the webhook',
|
description: 'Event that triggers the webhook',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
};
|
};
|
||||||
// @ts-ignore (because of request)
|
// @ts-ignore (because of request)
|
||||||
webhookMethods = {
|
webhookMethods = {
|
||||||
default: {
|
default: {
|
||||||
async checkExists(this: IHookFunctions): Promise<boolean> {
|
async checkExists(this: IHookFunctions): Promise<boolean> {
|
||||||
|
const topic = this.getNodeParameter('topic') as string;
|
||||||
const webhookData = this.getWorkflowStaticData('node');
|
const webhookData = this.getWorkflowStaticData('node');
|
||||||
if (webhookData.webhookId === undefined) {
|
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||||
return false;
|
const endpoint = `/webhooks`;
|
||||||
}
|
|
||||||
const endpoint = `/webhooks/${webhookData.webhookId}.json`;
|
const { webhooks } = await shopifyApiRequest.call(this, 'GET', endpoint, {}, { topic });
|
||||||
try {
|
for (const webhook of webhooks) {
|
||||||
await shopifyApiRequest.call(this, 'GET', endpoint, {});
|
if (webhook.address === webhookUrl) {
|
||||||
} catch (e) {
|
webhookData.webhookId = webhook.id;
|
||||||
if (e.statusCode === 404) {
|
return true;
|
||||||
delete webhookData.webhookId;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
return true;
|
return false;
|
||||||
},
|
},
|
||||||
async create(this: IHookFunctions): Promise<boolean> {
|
async create(this: IHookFunctions): Promise<boolean> {
|
||||||
const credentials = this.getCredentials('shopifyApi');
|
|
||||||
const webhookUrl = this.getNodeWebhookUrl('default');
|
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||||
const topic = this.getNodeParameter('topic') as string;
|
const topic = this.getNodeParameter('topic') as string;
|
||||||
|
const webhookData = this.getWorkflowStaticData('node');
|
||||||
const endpoint = `/webhooks.json`;
|
const endpoint = `/webhooks.json`;
|
||||||
const body = {
|
const body = {
|
||||||
webhook: {
|
webhook: {
|
||||||
|
@ -330,21 +328,15 @@ export class ShopifyTrigger implements INodeType {
|
||||||
};
|
};
|
||||||
|
|
||||||
let responseData;
|
let responseData;
|
||||||
try {
|
|
||||||
responseData = await shopifyApiRequest.call(this, 'POST', endpoint, body);
|
responseData = await shopifyApiRequest.call(this, 'POST', endpoint, body);
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responseData.webhook === undefined || responseData.webhook.id === undefined) {
|
if (responseData.webhook === undefined || responseData.webhook.id === undefined) {
|
||||||
// Required data is missing so was not successful
|
// Required data is missing so was not successful
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const webhookData = this.getWorkflowStaticData('node');
|
|
||||||
webhookData.webhookId = responseData.webhook.id as string;
|
webhookData.webhookId = responseData.webhook.id as string;
|
||||||
webhookData.sharedSecret = credentials!.sharedSecret as string;
|
|
||||||
webhookData.topic = topic as string;
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
async delete(this: IHookFunctions): Promise<boolean> {
|
async delete(this: IHookFunctions): Promise<boolean> {
|
||||||
|
@ -357,8 +349,6 @@ export class ShopifyTrigger implements INodeType {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
delete webhookData.webhookId;
|
delete webhookData.webhookId;
|
||||||
delete webhookData.sharedSecret;
|
|
||||||
delete webhookData.topic;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
@ -368,17 +358,18 @@ export class ShopifyTrigger implements INodeType {
|
||||||
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
||||||
const headerData = this.getHeaderData() as IDataObject;
|
const headerData = this.getHeaderData() as IDataObject;
|
||||||
const req = this.getRequestObject();
|
const req = this.getRequestObject();
|
||||||
const webhookData = this.getWorkflowStaticData('node') as IDataObject;
|
const credentials = this.getCredentials('shopifyApi') as IDataObject;
|
||||||
|
const topic = this.getNodeParameter('topic') as string;
|
||||||
if (headerData['x-shopify-topic'] !== undefined
|
if (headerData['x-shopify-topic'] !== undefined
|
||||||
&& headerData['x-shopify-hmac-sha256'] !== undefined
|
&& headerData['x-shopify-hmac-sha256'] !== undefined
|
||||||
&& headerData['x-shopify-shop-domain'] !== undefined
|
&& headerData['x-shopify-shop-domain'] !== undefined
|
||||||
&& headerData['x-shopify-api-version'] !== undefined) {
|
&& headerData['x-shopify-api-version'] !== undefined) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const computedSignature = createHmac('sha256', webhookData.sharedSecret as string).update(req.rawBody).digest('base64');
|
const computedSignature = createHmac('sha256', credentials.sharedSecret as string).update(req.rawBody).digest('base64');
|
||||||
if (headerData['x-shopify-hmac-sha256'] !== computedSignature) {
|
if (headerData['x-shopify-hmac-sha256'] !== computedSignature) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
if (webhookData.topic !== headerData['x-shopify-topic']) {
|
if (topic !== headerData['x-shopify-topic']) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
350
packages/nodes-base/nodes/Strapi/EntryDescription.ts
Normal file
350
packages/nodes-base/nodes/Strapi/EntryDescription.ts
Normal file
|
@ -0,0 +1,350 @@
|
||||||
|
import {
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const entryOperations = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'entry',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create an entry',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete an entry',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Get an entry',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get All',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Get all entries',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update an entry',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'get',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
] as INodeProperties[];
|
||||||
|
|
||||||
|
export const entryFields = [
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* entry:create */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'Content Type',
|
||||||
|
name: 'contentType',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'entry',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'Name of the content type.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Columns',
|
||||||
|
name: 'columns',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'entry',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
placeholder: 'id,name,description',
|
||||||
|
description: 'Comma separated list of the properties which should used as columns for the new rows.',
|
||||||
|
},
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* entry:delete */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'Content Type',
|
||||||
|
name: 'contentType',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'entry',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'delete',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'Name of the content type.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Entry ID',
|
||||||
|
name: 'entryId',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'entry',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'delete',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The ID of the entry to delete.',
|
||||||
|
},
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* entry:get */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'Content Type',
|
||||||
|
name: 'contentType',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'entry',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'get',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'Name of the content type.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Entry ID',
|
||||||
|
name: 'entryId',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'entry',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'get',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The ID of the entry to get.',
|
||||||
|
},
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* entry:getAll */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'Content Type',
|
||||||
|
name: 'contentType',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'entry',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'Name of the content type',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'entry',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: false,
|
||||||
|
description: 'Returns a list of your user contacts.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
type: 'number',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'entry',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
returnAll: [
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 100,
|
||||||
|
},
|
||||||
|
default: 50,
|
||||||
|
description: 'How many results to return.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'entry',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Publication State',
|
||||||
|
name: 'publicationState',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Live',
|
||||||
|
value: 'live',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Preview',
|
||||||
|
value: 'preview',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: '',
|
||||||
|
description: 'Only select entries matching the publication state provided.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Sort Fields',
|
||||||
|
name: 'sort',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
multipleValueButtonText: 'Add Sort Field',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
placeholder: 'name:asc',
|
||||||
|
description: `Name of the fields to sort the data by. By default will be sorted ascendingly.<br>
|
||||||
|
To modify that behavior, you have to add the sort direction after the name of sort field preceded by a colon.
|
||||||
|
For example: name:asc`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Where (JSON)',
|
||||||
|
name: 'where',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
alwaysOpenEditWindow: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'JSON query to filter the data.<a href="https://strapi.io/documentation/v3.x/content-api/parameters.html#filters" target="_blank"> Info</a>',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* entry:update */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'Content Type',
|
||||||
|
name: 'contentType',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'entry',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'Name of the content type.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Update Key',
|
||||||
|
name: 'updateKey',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'entry',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: 'id',
|
||||||
|
required: true,
|
||||||
|
description: 'Name of the property which decides which rows in the database should be updated. Normally that would be "id".',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Columns',
|
||||||
|
name: 'columns',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'entry',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
placeholder: 'id,name,description',
|
||||||
|
description: 'Comma separated list of the properties which should used as columns for the new rows.',
|
||||||
|
},
|
||||||
|
] as INodeProperties[];
|
103
packages/nodes-base/nodes/Strapi/GenericFunctions.ts
Normal file
103
packages/nodes-base/nodes/Strapi/GenericFunctions.ts
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import {
|
||||||
|
OptionsWithUri,
|
||||||
|
} from 'request';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IHookFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
IWebhookFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export async function strapiApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
|
||||||
|
const credentials = this.getCredentials('strapiApi') as IDataObject;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options: OptionsWithUri = {
|
||||||
|
headers: {},
|
||||||
|
method,
|
||||||
|
body,
|
||||||
|
qs,
|
||||||
|
uri: uri || `${credentials.url}${resource}`,
|
||||||
|
json: true,
|
||||||
|
};
|
||||||
|
if (Object.keys(headers).length !== 0) {
|
||||||
|
options.headers = Object.assign({}, options.headers, headers);
|
||||||
|
}
|
||||||
|
if (Object.keys(body).length === 0) {
|
||||||
|
delete options.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
return await this.helpers?.request(options);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response && error.response.body && error.response.body.message) {
|
||||||
|
|
||||||
|
let messages = error.response.body.message;
|
||||||
|
|
||||||
|
if (Array.isArray(error.response.body.message)) {
|
||||||
|
messages = messages[0].messages.map((e: IDataObject) => e.message).join('|');
|
||||||
|
}
|
||||||
|
// Try to return the error prettier
|
||||||
|
throw new Error(
|
||||||
|
`Strapi error response [${error.statusCode}]: ${messages}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getToken(this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions | IWebhookFunctions): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
const credentials = this.getCredentials('strapiApi') as IDataObject;
|
||||||
|
|
||||||
|
const options: OptionsWithUri = {
|
||||||
|
headers: {
|
||||||
|
'content-type': `application/json`,
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
uri: `${credentials.url}/auth/local`,
|
||||||
|
body: {
|
||||||
|
identifier: credentials.email,
|
||||||
|
password: credentials.password,
|
||||||
|
},
|
||||||
|
json: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.helpers.request!(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function strapiApiRequestAllItems(this: IHookFunctions | ILoadOptionsFunctions | IExecuteFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, headers: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
|
||||||
|
let responseData;
|
||||||
|
|
||||||
|
query._limit = 20;
|
||||||
|
|
||||||
|
query._start = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
responseData = await strapiApiRequest.call(this, method, resource, body, query, undefined, headers);
|
||||||
|
query._start += query._limit;
|
||||||
|
returnData.push.apply(returnData, responseData);
|
||||||
|
} while (
|
||||||
|
responseData.length !== 0
|
||||||
|
);
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = JSON.parse(json!);
|
||||||
|
} catch (exception) {
|
||||||
|
result = undefined;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
192
packages/nodes-base/nodes/Strapi/Strapi.node.ts
Normal file
192
packages/nodes-base/nodes/Strapi/Strapi.node.ts
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getToken,
|
||||||
|
strapiApiRequest,
|
||||||
|
strapiApiRequestAllItems,
|
||||||
|
validateJSON,
|
||||||
|
} from './GenericFunctions';
|
||||||
|
|
||||||
|
import {
|
||||||
|
entryFields,
|
||||||
|
entryOperations,
|
||||||
|
} from './EntryDescription';
|
||||||
|
|
||||||
|
export class Strapi implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'Strapi',
|
||||||
|
name: 'strapi',
|
||||||
|
icon: 'file:strapi.svg',
|
||||||
|
group: ['input'],
|
||||||
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
|
description: 'Consume Strapi API.',
|
||||||
|
defaults: {
|
||||||
|
name: 'Strapi',
|
||||||
|
color: '#725ed8',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'strapiApi',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Resource',
|
||||||
|
name: 'resource',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Entry',
|
||||||
|
value: 'entry',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'entry',
|
||||||
|
description: 'The resource to operate on.',
|
||||||
|
},
|
||||||
|
...entryOperations,
|
||||||
|
...entryFields,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
const items = this.getInputData();
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
const length = (items.length as unknown) as number;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
const headers: IDataObject = {};
|
||||||
|
let responseData;
|
||||||
|
const resource = this.getNodeParameter('resource', 0) as string;
|
||||||
|
const operation = this.getNodeParameter('operation', 0) as string;
|
||||||
|
|
||||||
|
const { jwt } = await getToken.call(this);
|
||||||
|
|
||||||
|
headers.Authorization = `Bearer ${jwt}`;
|
||||||
|
|
||||||
|
if (resource === 'entry') {
|
||||||
|
if (operation === 'create') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
|
||||||
|
const body: IDataObject = {};
|
||||||
|
|
||||||
|
const contentType = this.getNodeParameter('contentType', i) as string;
|
||||||
|
|
||||||
|
const columns = this.getNodeParameter('columns', i) as string;
|
||||||
|
|
||||||
|
const columnList = columns.split(',').map(column => column.trim());
|
||||||
|
|
||||||
|
for (const key of Object.keys(items[i].json)) {
|
||||||
|
if (columnList.includes(key)) {
|
||||||
|
body[key] = items[i].json[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responseData = await strapiApiRequest.call(this, 'POST', `/${contentType}`, body, qs, undefined, headers);
|
||||||
|
|
||||||
|
returnData.push(responseData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'delete') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const contentType = this.getNodeParameter('contentType', i) as string;
|
||||||
|
|
||||||
|
const entryId = this.getNodeParameter('entryId', i) as string;
|
||||||
|
|
||||||
|
responseData = await strapiApiRequest.call(this, 'DELETE', `/${contentType}/${entryId}`, {}, qs, undefined, headers);
|
||||||
|
|
||||||
|
returnData.push(responseData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'getAll') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||||
|
|
||||||
|
const contentType = this.getNodeParameter('contentType', i) as string;
|
||||||
|
|
||||||
|
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||||
|
|
||||||
|
if (options.sort && (options.sort as string[]).length !== 0) {
|
||||||
|
const sortFields = options.sort as string[];
|
||||||
|
qs._sort = sortFields.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.where) {
|
||||||
|
const query = validateJSON(options.where as string);
|
||||||
|
if (query !== undefined) {
|
||||||
|
qs._where = query;
|
||||||
|
} else {
|
||||||
|
throw new Error('Query must be a valid JSON');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.publicationState) {
|
||||||
|
qs._publicationState = options.publicationState as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await strapiApiRequestAllItems.call(this, 'GET', `/${contentType}`, {}, qs, headers);
|
||||||
|
} else {
|
||||||
|
qs._limit = this.getNodeParameter('limit', i) as number;
|
||||||
|
|
||||||
|
responseData = await strapiApiRequest.call(this, 'GET', `/${contentType}`, {}, qs, undefined, headers);
|
||||||
|
}
|
||||||
|
returnData.push.apply(returnData, responseData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'get') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
|
||||||
|
const contentType = this.getNodeParameter('contentType', i) as string;
|
||||||
|
|
||||||
|
const entryId = this.getNodeParameter('entryId', i) as string;
|
||||||
|
|
||||||
|
responseData = await strapiApiRequest.call(this, 'GET', `/${contentType}/${entryId}`, {}, qs, undefined, headers);
|
||||||
|
|
||||||
|
returnData.push(responseData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'update') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
|
||||||
|
const body: IDataObject = {};
|
||||||
|
|
||||||
|
const contentType = this.getNodeParameter('contentType', i) as string;
|
||||||
|
|
||||||
|
const columns = this.getNodeParameter('columns', i) as string;
|
||||||
|
|
||||||
|
const updateKey = this.getNodeParameter('updateKey', i) as string;
|
||||||
|
|
||||||
|
const columnList = columns.split(',').map(column => column.trim());
|
||||||
|
|
||||||
|
const entryId = items[i].json[updateKey];
|
||||||
|
|
||||||
|
for (const key of Object.keys(items[i].json)) {
|
||||||
|
if (columnList.includes(key)) {
|
||||||
|
body[key] = items[i].json[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responseData = await strapiApiRequest.call(this, 'PUT', `/${contentType}/${entryId}`, body, qs, undefined, headers);
|
||||||
|
|
||||||
|
returnData.push(responseData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [this.helpers.returnJsonArray(returnData)];
|
||||||
|
}
|
||||||
|
}
|
72
packages/nodes-base/nodes/Strapi/strapi.svg
Normal file
72
packages/nodes-base/nodes/Strapi/strapi.svg
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<svg width="1025" height="1032" viewBox="0 0 1025 1032" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M341.328 0V344.061H682.661V688.121H1023.99V0H341.328Z" fill="url(#paint0_linear)"/>
|
||||||
|
<path d="M683 343.725H343V688.457H683V343.725Z" fill="url(#paint1_linear)"/>
|
||||||
|
<path d="M341.333 344.061H0L341.333 0V344.061Z" fill="url(#paint2_linear)"/>
|
||||||
|
<path d="M682.367 1031.18V687.457H1024.37L682.367 1031.18Z" fill="url(#paint3_linear)"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 365.162 314.663)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 374.502 314.663)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 383.843 314.663)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 393.184 314.663)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 402.525 314.663)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 411.866 314.663)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 421.206 314.663)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 430.546 314.663)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 439.887 314.663)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 449.228 314.663)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 458.569 314.663)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 365.162 306.041)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 374.502 306.041)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 383.843 306.041)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 393.184 306.041)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 402.525 306.041)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 411.866 306.041)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 421.206 306.041)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 430.546 306.041)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 365.162 297.419)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 374.502 297.419)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 383.843 297.419)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 393.184 297.419)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 402.525 297.419)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 365.162 288.796)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 374.502 288.797)" fill="#956FFF"/>
|
||||||
|
<circle r="1.7963" transform="matrix(1 8.74228e-08 8.74228e-08 -1 383.843 288.797)" fill="#956FFF"/>
|
||||||
|
<circle cx="716.162" cy="638.796" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="725.502" cy="638.796" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="734.843" cy="638.796" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="744.184" cy="638.796" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="753.525" cy="638.796" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="762.866" cy="638.796" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="772.206" cy="638.796" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="716.162" cy="647.419" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="725.502" cy="647.419" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="734.843" cy="647.419" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="744.184" cy="647.419" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="753.525" cy="647.419" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="762.866" cy="647.419" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="716.162" cy="656.041" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="725.502" cy="656.04" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="734.843" cy="656.04" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="744.184" cy="656.04" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="753.525" cy="656.04" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="716.162" cy="664.664" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="725.502" cy="664.663" r="1.7963" fill="#956FFF"/>
|
||||||
|
<circle cx="734.843" cy="664.663" r="1.7963" fill="#956FFF"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear" x1="1072.36" y1="-51.4075" x2="553.031" y2="446.945" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.0208333" stop-color="#956FFF"/>
|
||||||
|
<stop offset="1" stop-color="#1C1B7E"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint1_linear" x1="634.365" y1="396.5" x2="277.177" y2="712.901" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#956FFF"/>
|
||||||
|
<stop offset="1" stop-color="#1A2670"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint2_linear" x1="170.667" y1="0" x2="170.667" y2="344.061" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.0208333" stop-color="#956FFF"/>
|
||||||
|
<stop offset="1" stop-color="#1C1B7E"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint3_linear" x1="1085.18" y1="556.22" x2="695.654" y2="967.042" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#956FFF"/>
|
||||||
|
<stop offset="0.838542" stop-color="#1C1B7E"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5.2 KiB |
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "n8n-nodes-base",
|
"name": "n8n-nodes-base",
|
||||||
"version": "0.87.0",
|
"version": "0.88.0",
|
||||||
"description": "Base nodes of n8n",
|
"description": "Base nodes of n8n",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"homepage": "https://n8n.io",
|
"homepage": "https://n8n.io",
|
||||||
|
@ -71,6 +71,8 @@
|
||||||
"dist/credentials/FileMaker.credentials.js",
|
"dist/credentials/FileMaker.credentials.js",
|
||||||
"dist/credentials/FlowApi.credentials.js",
|
"dist/credentials/FlowApi.credentials.js",
|
||||||
"dist/credentials/Ftp.credentials.js",
|
"dist/credentials/Ftp.credentials.js",
|
||||||
|
"dist/credentials/GetResponseApi.credentials.js",
|
||||||
|
"dist/credentials/GetResponseOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GithubApi.credentials.js",
|
"dist/credentials/GithubApi.credentials.js",
|
||||||
"dist/credentials/GithubOAuth2Api.credentials.js",
|
"dist/credentials/GithubOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GitlabApi.credentials.js",
|
"dist/credentials/GitlabApi.credentials.js",
|
||||||
|
@ -86,6 +88,7 @@
|
||||||
"dist/credentials/GSuiteAdminOAuth2Api.credentials.js",
|
"dist/credentials/GSuiteAdminOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GoogleTasksOAuth2Api.credentials.js",
|
"dist/credentials/GoogleTasksOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GoogleTranslateOAuth2Api.credentials.js",
|
"dist/credentials/GoogleTranslateOAuth2Api.credentials.js",
|
||||||
|
"dist/credentials/GotifyApi.credentials.js",
|
||||||
"dist/credentials/YouTubeOAuth2Api.credentials.js",
|
"dist/credentials/YouTubeOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GumroadApi.credentials.js",
|
"dist/credentials/GumroadApi.credentials.js",
|
||||||
"dist/credentials/HarvestApi.credentials.js",
|
"dist/credentials/HarvestApi.credentials.js",
|
||||||
|
@ -105,6 +108,7 @@
|
||||||
"dist/credentials/JotFormApi.credentials.js",
|
"dist/credentials/JotFormApi.credentials.js",
|
||||||
"dist/credentials/Kafka.credentials.js",
|
"dist/credentials/Kafka.credentials.js",
|
||||||
"dist/credentials/KeapOAuth2Api.credentials.js",
|
"dist/credentials/KeapOAuth2Api.credentials.js",
|
||||||
|
"dist/credentials/LineNotifyOAuth2Api.credentials.js",
|
||||||
"dist/credentials/LinkedInOAuth2Api.credentials.js",
|
"dist/credentials/LinkedInOAuth2Api.credentials.js",
|
||||||
"dist/credentials/MailerLiteApi.credentials.js",
|
"dist/credentials/MailerLiteApi.credentials.js",
|
||||||
"dist/credentials/MailchimpApi.credentials.js",
|
"dist/credentials/MailchimpApi.credentials.js",
|
||||||
|
@ -176,6 +180,7 @@
|
||||||
"dist/credentials/SpotifyOAuth2Api.credentials.js",
|
"dist/credentials/SpotifyOAuth2Api.credentials.js",
|
||||||
"dist/credentials/StoryblokContentApi.credentials.js",
|
"dist/credentials/StoryblokContentApi.credentials.js",
|
||||||
"dist/credentials/StoryblokManagementApi.credentials.js",
|
"dist/credentials/StoryblokManagementApi.credentials.js",
|
||||||
|
"dist/credentials/StrapiApi.credentials.js",
|
||||||
"dist/credentials/SurveyMonkeyApi.credentials.js",
|
"dist/credentials/SurveyMonkeyApi.credentials.js",
|
||||||
"dist/credentials/SurveyMonkeyOAuth2Api.credentials.js",
|
"dist/credentials/SurveyMonkeyOAuth2Api.credentials.js",
|
||||||
"dist/credentials/TaigaCloudApi.credentials.js",
|
"dist/credentials/TaigaCloudApi.credentials.js",
|
||||||
|
@ -277,6 +282,7 @@
|
||||||
"dist/nodes/Flow/FlowTrigger.node.js",
|
"dist/nodes/Flow/FlowTrigger.node.js",
|
||||||
"dist/nodes/Function.node.js",
|
"dist/nodes/Function.node.js",
|
||||||
"dist/nodes/FunctionItem.node.js",
|
"dist/nodes/FunctionItem.node.js",
|
||||||
|
"dist/nodes/GetResponse/GetResponse.node.js",
|
||||||
"dist/nodes/Github/Github.node.js",
|
"dist/nodes/Github/Github.node.js",
|
||||||
"dist/nodes/Github/GithubTrigger.node.js",
|
"dist/nodes/Github/GithubTrigger.node.js",
|
||||||
"dist/nodes/Gitlab/Gitlab.node.js",
|
"dist/nodes/Gitlab/Gitlab.node.js",
|
||||||
|
@ -291,6 +297,7 @@
|
||||||
"dist/nodes/Google/Task/GoogleTasks.node.js",
|
"dist/nodes/Google/Task/GoogleTasks.node.js",
|
||||||
"dist/nodes/Google/Translate/GoogleTranslate.node.js",
|
"dist/nodes/Google/Translate/GoogleTranslate.node.js",
|
||||||
"dist/nodes/Google/YouTube/YouTube.node.js",
|
"dist/nodes/Google/YouTube/YouTube.node.js",
|
||||||
|
"dist/nodes/Gotify/Gotify.node.js",
|
||||||
"dist/nodes/GraphQL/GraphQL.node.js",
|
"dist/nodes/GraphQL/GraphQL.node.js",
|
||||||
"dist/nodes/Gumroad/GumroadTrigger.node.js",
|
"dist/nodes/Gumroad/GumroadTrigger.node.js",
|
||||||
"dist/nodes/HackerNews/HackerNews.node.js",
|
"dist/nodes/HackerNews/HackerNews.node.js",
|
||||||
|
@ -313,6 +320,7 @@
|
||||||
"dist/nodes/Kafka/Kafka.node.js",
|
"dist/nodes/Kafka/Kafka.node.js",
|
||||||
"dist/nodes/Keap/Keap.node.js",
|
"dist/nodes/Keap/Keap.node.js",
|
||||||
"dist/nodes/Keap/KeapTrigger.node.js",
|
"dist/nodes/Keap/KeapTrigger.node.js",
|
||||||
|
"dist/nodes/Line/Line.node.js",
|
||||||
"dist/nodes/LinkedIn/LinkedIn.node.js",
|
"dist/nodes/LinkedIn/LinkedIn.node.js",
|
||||||
"dist/nodes/MailerLite/MailerLite.node.js",
|
"dist/nodes/MailerLite/MailerLite.node.js",
|
||||||
"dist/nodes/MailerLite/MailerLiteTrigger.node.js",
|
"dist/nodes/MailerLite/MailerLiteTrigger.node.js",
|
||||||
|
@ -381,6 +389,7 @@
|
||||||
"dist/nodes/SseTrigger.node.js",
|
"dist/nodes/SseTrigger.node.js",
|
||||||
"dist/nodes/Start.node.js",
|
"dist/nodes/Start.node.js",
|
||||||
"dist/nodes/Storyblok/Storyblok.node.js",
|
"dist/nodes/Storyblok/Storyblok.node.js",
|
||||||
|
"dist/nodes/Strapi/Strapi.node.js",
|
||||||
"dist/nodes/Strava/Strava.node.js",
|
"dist/nodes/Strava/Strava.node.js",
|
||||||
"dist/nodes/Strava/StravaTrigger.node.js",
|
"dist/nodes/Strava/StravaTrigger.node.js",
|
||||||
"dist/nodes/Stripe/StripeTrigger.node.js",
|
"dist/nodes/Stripe/StripeTrigger.node.js",
|
||||||
|
@ -449,7 +458,7 @@
|
||||||
"@types/xml2js": "^0.4.3",
|
"@types/xml2js": "^0.4.3",
|
||||||
"gulp": "^4.0.0",
|
"gulp": "^4.0.0",
|
||||||
"jest": "^26.4.2",
|
"jest": "^26.4.2",
|
||||||
"n8n-workflow": "~0.43.0",
|
"n8n-workflow": "~0.44.0",
|
||||||
"ts-jest": "^26.3.0",
|
"ts-jest": "^26.3.0",
|
||||||
"tslint": "^6.1.2",
|
"tslint": "^6.1.2",
|
||||||
"typescript": "~3.9.7"
|
"typescript": "~3.9.7"
|
||||||
|
@ -479,7 +488,7 @@
|
||||||
"mqtt": "^4.2.0",
|
"mqtt": "^4.2.0",
|
||||||
"mssql": "^6.2.0",
|
"mssql": "^6.2.0",
|
||||||
"mysql2": "~2.1.0",
|
"mysql2": "~2.1.0",
|
||||||
"n8n-core": "~0.50.0",
|
"n8n-core": "~0.51.0",
|
||||||
"nodemailer": "^6.4.6",
|
"nodemailer": "^6.4.6",
|
||||||
"pdf-parse": "^1.1.1",
|
"pdf-parse": "^1.1.1",
|
||||||
"pg": "^8.3.0",
|
"pg": "^8.3.0",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "n8n-workflow",
|
"name": "n8n-workflow",
|
||||||
"version": "0.43.0",
|
"version": "0.44.0",
|
||||||
"description": "Workflow base code of n8n",
|
"description": "Workflow base code of n8n",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"homepage": "https://n8n.io",
|
"homepage": "https://n8n.io",
|
||||||
|
|
|
@ -408,6 +408,7 @@ export interface INodePropertyTypeOptions {
|
||||||
numberStepSize?: number; // Supported by: number
|
numberStepSize?: number; // Supported by: number
|
||||||
password?: boolean; // Supported by: string
|
password?: boolean; // Supported by: string
|
||||||
rows?: number; // Supported by: string
|
rows?: number; // Supported by: string
|
||||||
|
showAlpha?: boolean; // Supported by: color
|
||||||
[key: string]: boolean | number | string | EditorTypes | undefined | string[];
|
[key: string]: boolean | number | string | EditorTypes | undefined | string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -535,6 +536,7 @@ export interface INodeTypeDescription {
|
||||||
version: number;
|
version: number;
|
||||||
description: string;
|
description: string;
|
||||||
defaults: INodeParameters;
|
defaults: INodeParameters;
|
||||||
|
documentationUrl?: string;
|
||||||
inputs: string[];
|
inputs: string[];
|
||||||
inputNames?: string[];
|
inputNames?: string[];
|
||||||
outputs: string[];
|
outputs: string[];
|
||||||
|
@ -714,7 +716,7 @@ export interface IWorkflowExecuteHooks {
|
||||||
nodeExecuteAfter?: Array<((nodeName: string, data: ITaskData) => Promise<void>)>;
|
nodeExecuteAfter?: Array<((nodeName: string, data: ITaskData) => Promise<void>)>;
|
||||||
nodeExecuteBefore?: Array<((nodeName: string) => Promise<void>)>;
|
nodeExecuteBefore?: Array<((nodeName: string) => Promise<void>)>;
|
||||||
workflowExecuteAfter?: Array<((data: IRun, newStaticData: IDataObject) => Promise<void>)>;
|
workflowExecuteAfter?: Array<((data: IRun, newStaticData: IDataObject) => Promise<void>)>;
|
||||||
workflowExecuteBefore?: Array<(() => Promise<void>)>;
|
workflowExecuteBefore?: Array<((workflow: Workflow, data: IRunExecutionData) => Promise<void>)>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkflowExecuteAdditionalData {
|
export interface IWorkflowExecuteAdditionalData {
|
||||||
|
|
|
@ -28,19 +28,9 @@ export class WorkflowHooks {
|
||||||
async executeHookFunctions(hookName: string, parameters: any[]) { // tslint:disable-line:no-any
|
async executeHookFunctions(hookName: string, parameters: any[]) { // tslint:disable-line:no-any
|
||||||
if (this.hookFunctions[hookName] !== undefined && Array.isArray(this.hookFunctions[hookName])) {
|
if (this.hookFunctions[hookName] !== undefined && Array.isArray(this.hookFunctions[hookName])) {
|
||||||
for (const hookFunction of this.hookFunctions[hookName]!) {
|
for (const hookFunction of this.hookFunctions[hookName]!) {
|
||||||
await hookFunction.apply(this, parameters)
|
// TODO: As catch got removed we should make sure that we catch errors
|
||||||
.catch((error: Error) => {
|
// where hooks get called
|
||||||
// Catch all errors here because when "executeHook" gets called
|
await hookFunction.apply(this, parameters);
|
||||||
// we have the most time no "await" and so the errors would so
|
|
||||||
// not be uncaught by anything.
|
|
||||||
|
|
||||||
// TODO: Add proper logging
|
|
||||||
console.error(`There was a problem executing hook: "${hookName}"`);
|
|
||||||
console.error('Parameters:');
|
|
||||||
console.error(parameters);
|
|
||||||
console.error('Error:');
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue