mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
🔀 Merge branch 'master' into Doc-Link-to-Node-Credentials-Modal
This commit is contained in:
commit
d2110f677e
|
@ -63,9 +63,9 @@ check out our job posts:
|
|||
|
||||
|
||||
|
||||
## What does n8n mean and how do you pronounce it
|
||||
## What does n8n mean and how do you pronounce it?
|
||||
|
||||
**Short answer:** It means "nodemation"
|
||||
**Short answer:** It means "nodemation" and it is pronounced as n-eight-n.
|
||||
|
||||
**Long answer:** I get that question quite often (more often than I expected)
|
||||
so I decided it is probably best to answer it here. While looking for a
|
||||
|
|
|
@ -260,9 +260,9 @@ docker build --build-arg N8N_VERSION=0.18.1 -t n8nio/n8n:0.18.1 .
|
|||
```
|
||||
|
||||
|
||||
## What does n8n mean and how do you pronounce it
|
||||
## What does n8n mean and how do you pronounce it?
|
||||
|
||||
**Short answer:** It means "nodemation"
|
||||
**Short answer:** It means "nodemation" and it is pronounced as n-eight-n.
|
||||
|
||||
**Long answer:** I get that question quite often (more often than I expected)
|
||||
so I decided it is probably best to answer it here. While looking for a
|
||||
|
|
|
@ -2,6 +2,26 @@
|
|||
|
||||
This list shows all the versions which include breaking changes and how to upgrade.
|
||||
|
||||
## 0.79.0
|
||||
|
||||
### What changed?
|
||||
|
||||
We have renamed the operations in the Todoist Node for consistency with the codebase. We also deleted the `close_match` and `delete_match` operations as these can be accomplished using the following operations: `getAll`, `close`, and `delete`.
|
||||
|
||||
### When is action necessary?
|
||||
|
||||
When one of the following operations is used:
|
||||
|
||||
- close_by
|
||||
- close_match
|
||||
- delete_id
|
||||
- delete_match
|
||||
|
||||
### How to upgrade:
|
||||
|
||||
After upgrading, open all workflows which contain the Todoist Node. Set the corresponding operation, and then save the workflow.
|
||||
|
||||
If the operations `close_match` or `delete_match` are used, recreate them using the operations: `getAll`, `delete`, and `close`.
|
||||
|
||||
## 0.69.0
|
||||
|
||||
|
|
|
@ -60,9 +60,9 @@ If you are interested in a hosted version of n8n on our infrastructure please co
|
|||
|
||||
|
||||
|
||||
## What does n8n mean and how do you pronounce it
|
||||
## What does n8n mean and how do you pronounce it?
|
||||
|
||||
**Short answer:** It means "nodemation"
|
||||
**Short answer:** It means "nodemation" and it is pronounced as n-eight-n.
|
||||
|
||||
**Long answer:** I get that question quite often (more often than I expected)
|
||||
so I decided it is probably best to answer it here. While looking for a
|
||||
|
|
|
@ -417,3 +417,5 @@ export interface ITimeoutHMS {
|
|||
minutes: number;
|
||||
seconds: number;
|
||||
}
|
||||
|
||||
export type WorkflowTitleStatus = 'EXECUTING' | 'IDLE' | 'ERROR';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div v-if="dialogVisible" @keydown.stop>
|
||||
<el-dialog :visible="dialogVisible" append-to-body width="55%" :title="title" :nodeType="nodeType" :before-close="closeDialog">
|
||||
<el-dialog :visible="dialogVisible" append-to-body width="75%" :title="title" :nodeType="nodeType" :before-close="closeDialog">
|
||||
<div name="title" class="titleContainer" slot="title">
|
||||
<div id="left">{{title}}</div>
|
||||
<div id="right">
|
||||
|
@ -271,7 +271,7 @@ export default mixins(
|
|||
|
||||
this.$showMessage({
|
||||
title: 'Credentials created',
|
||||
message: `The credential "${eventData.data.name}" got created!`,
|
||||
message: `"${eventData.data.name}" credentials were successfully created!`,
|
||||
type: 'success',
|
||||
});
|
||||
|
||||
|
@ -284,7 +284,7 @@ export default mixins(
|
|||
|
||||
this.$showMessage({
|
||||
title: 'Credentials updated',
|
||||
message: `The credential "${eventData.data.name}" got updated!`,
|
||||
message: `"${eventData.data.name}" credentials were successfully updated!`,
|
||||
type: 'success',
|
||||
});
|
||||
|
||||
|
|
|
@ -44,19 +44,19 @@
|
|||
<el-button title="Connect OAuth Credentials" circle :disabled="true">
|
||||
<font-awesome-icon icon="redo" />
|
||||
</el-button>
|
||||
Not all required credential properties are filled
|
||||
Enter all required properties
|
||||
</span>
|
||||
<span v-else-if="isOAuthConnected === true">
|
||||
<el-button title="Reconnect OAuth Credentials" @click.stop="oAuthCredentialAuthorize()" circle>
|
||||
<font-awesome-icon icon="redo" />
|
||||
</el-button>
|
||||
Is connected
|
||||
Connected
|
||||
</span>
|
||||
<span v-else>
|
||||
<el-button title="Connect OAuth Credentials" @click.stop="oAuthCredentialAuthorize()" circle>
|
||||
<font-awesome-icon icon="sign-in-alt" />
|
||||
</el-button>
|
||||
Is NOT connected
|
||||
Not connected
|
||||
</span>
|
||||
|
||||
<div v-if="credentialProperties.length">
|
||||
|
@ -91,7 +91,7 @@
|
|||
|
||||
<div v-if="nodesAccess.length === 0" class="no-nodes-access">
|
||||
<strong>
|
||||
Important!
|
||||
Important
|
||||
</strong><br />
|
||||
Add at least one node which has access to the credentials!
|
||||
</div>
|
||||
|
@ -163,8 +163,8 @@ export default mixins(
|
|||
isMinimized: true,
|
||||
helpTexts: {
|
||||
credentialsData: 'The credentials to set.',
|
||||
credentialsName: 'The name the credentials should be saved as. Use a name<br />which makes it clear to what exactly they give access to.<br />For credentials of an Email account that could be the Email address itself.',
|
||||
nodesWithAccess: 'The nodes which allowed to use this credentials.',
|
||||
credentialsName: 'A recognizable label for the credentials. Descriptive names work <br />best here, so you can easily select it from a list later.',
|
||||
nodesWithAccess: 'Nodes with access to these credentials.',
|
||||
},
|
||||
credentialDataTemp: null as ICredentialsDecryptedResponse | null,
|
||||
nodesAccess: [] as string[],
|
||||
|
@ -256,7 +256,7 @@ export default mixins(
|
|||
|
||||
this.$showMessage({
|
||||
title: 'Copied',
|
||||
message: `The callback URL got copied!`,
|
||||
message: `Callback URL was successfully copied!`,
|
||||
type: 'success',
|
||||
});
|
||||
},
|
||||
|
@ -401,7 +401,7 @@ export default mixins(
|
|||
|
||||
this.$showMessage({
|
||||
title: 'Connected',
|
||||
message: 'Got connected!',
|
||||
message: 'Connected successfully!',
|
||||
type: 'success',
|
||||
});
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ export default mixins(
|
|||
try {
|
||||
this.credentials = JSON.parse(JSON.stringify(this.$store.getters.allCredentials));
|
||||
} catch (error) {
|
||||
this.$showError(error, 'Proble loading credentials', 'There was a problem loading the credentials:');
|
||||
this.$showError(error, 'Problem loading credentials', 'There was a problem loading the credentials:');
|
||||
this.isDataLoading = false;
|
||||
return;
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ export default mixins(
|
|||
},
|
||||
|
||||
async deleteCredential (credential: ICredentialsResponse) {
|
||||
const deleteConfirmed = await this.confirmMessage(`Are you sure that you want to delete the credentials "${credential.name}"?`, 'Delete Credentials?', 'warning', 'Yes, delete!');
|
||||
const deleteConfirmed = await this.confirmMessage(`Are you sure you want to delete "${credential.name}" credentials?`, 'Delete Credentials?', 'warning', 'Yes, delete!');
|
||||
|
||||
if (deleteConfirmed === false) {
|
||||
return;
|
||||
|
|
|
@ -12,14 +12,15 @@
|
|||
<font-awesome-icon icon="check" class="execution-icon success" v-if="executionFinished" title="Execution was successful" />
|
||||
<font-awesome-icon icon="times" class="execution-icon error" v-else title="Execution did fail" />
|
||||
</span>
|
||||
of Workflow
|
||||
of
|
||||
<span class="workflow-name clickable" title="Open Workflow">
|
||||
<span @click="openWorkflow(workflowExecution.workflowId)">"{{workflowName}}"</span>
|
||||
</span>
|
||||
workflow
|
||||
</span>
|
||||
<span index="workflow-name" class="current-workflow" v-if="!isReadOnly">
|
||||
<span v-if="currentWorkflow">Workflow: <span class="workflow-name">{{workflowName}}</span></span>
|
||||
<span v-else class="workflow-not-saved">Workflow not saved!</span>
|
||||
<span v-else class="workflow-not-saved">Workflow was not saved!</span>
|
||||
</span>
|
||||
|
||||
<span class="saving-workflow" v-if="isWorkflowSaving">
|
||||
|
@ -32,9 +33,9 @@
|
|||
<div class="push-connection-lost" v-if="!isPushConnectionActive">
|
||||
<el-tooltip placement="bottom-end" effect="light">
|
||||
<div slot="content">
|
||||
Server connection could not be established.<br />
|
||||
The server is down or there is a connection problem.<br />
|
||||
It will reconnect automatically as soon as the backend can be reached.
|
||||
Cannot connect to server.<br />
|
||||
It is either down or you have a connection issue. <br />
|
||||
It should reconnect automatically once the issue is resolved.
|
||||
</div>
|
||||
<span>
|
||||
<font-awesome-icon icon="exclamation-triangle" />
|
||||
|
@ -50,9 +51,8 @@
|
|||
<div class="read-only" v-if="isReadOnly">
|
||||
<el-tooltip placement="bottom-end" effect="light">
|
||||
<div slot="content">
|
||||
A past execution gets displayed. For that reason no data<br />
|
||||
can be changed. To make changes or to execute it again open<br />
|
||||
the workflow by clicking on it`s name on the left.
|
||||
You're viewing the log of a previous execution. You cannot<br />
|
||||
make changes since this execution already occured. Make changes<br /> to this workflow by clicking on it`s name on the left.
|
||||
</div>
|
||||
<span>
|
||||
<font-awesome-icon icon="exclamation-triangle" />
|
||||
|
@ -84,6 +84,7 @@ import { genericHelpers } from '@/components/mixins/genericHelpers';
|
|||
import { pushConnection } from '@/components/mixins/pushConnection';
|
||||
import { restApi } from '@/components/mixins/restApi';
|
||||
import { showMessage } from '@/components/mixins/showMessage';
|
||||
import { titleChange } from '@/components/mixins/titleChange';
|
||||
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
||||
|
||||
import { saveAs } from 'file-saver';
|
||||
|
@ -95,6 +96,7 @@ export default mixins(
|
|||
pushConnection,
|
||||
restApi,
|
||||
showMessage,
|
||||
titleChange,
|
||||
workflowHelpers,
|
||||
)
|
||||
.extend({
|
||||
|
@ -155,6 +157,7 @@ export default mixins(
|
|||
},
|
||||
methods: {
|
||||
async openWorkflow (workflowId: string) {
|
||||
this.$titleSet(this.workflowName, 'IDLE');
|
||||
// Change to other workflow
|
||||
this.$router.push({
|
||||
name: 'NodeViewExisting',
|
||||
|
@ -162,7 +165,6 @@ export default mixins(
|
|||
});
|
||||
},
|
||||
},
|
||||
|
||||
async mounted () {
|
||||
// Initialize the push connection
|
||||
this.pushConnect();
|
||||
|
|
|
@ -179,6 +179,7 @@ import WorkflowSettings from '@/components/WorkflowSettings.vue';
|
|||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
import { restApi } from '@/components/mixins/restApi';
|
||||
import { showMessage } from '@/components/mixins/showMessage';
|
||||
import { titleChange } from '@/components/mixins/titleChange';
|
||||
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
||||
import { workflowSave } from '@/components/mixins/workflowSave';
|
||||
import { workflowRun } from '@/components/mixins/workflowRun';
|
||||
|
@ -191,6 +192,7 @@ export default mixins(
|
|||
genericHelpers,
|
||||
restApi,
|
||||
showMessage,
|
||||
titleChange,
|
||||
workflowHelpers,
|
||||
workflowRun,
|
||||
workflowSave,
|
||||
|
@ -417,7 +419,8 @@ export default mixins(
|
|||
this.$showError(error, 'Problem deleting the workflow', 'There was a problem deleting the workflow:');
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset tab title since workflow is deleted.
|
||||
this.$titleReset();
|
||||
this.$showMessage({
|
||||
title: 'Workflow got deleted',
|
||||
message: `The workflow "${this.workflowName}" got deleted!`,
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<div class="node-create-list-wrapper">
|
||||
<div class="node-create-list">
|
||||
<div v-if="filteredNodeTypes.length === 0" class="no-results">
|
||||
No node found which matches active filter!
|
||||
🙃 no nodes matching your search criteria
|
||||
</div>
|
||||
<node-create-item :active="index === activeNodeTypeIndex" :nodeType="nodeType" v-for="(nodeType, index) in filteredNodeTypes" v-bind:key="nodeType.name" @nodeTypeSelected="nodeTypeSelected"></node-create-item>
|
||||
</div>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<display-with-change :key-name="'name'" @valueChanged="valueChanged"></display-with-change>
|
||||
<a v-if="nodeType" :href="'http://n8n.io/nodes/' + nodeType.name" target="_blank" class="node-info">
|
||||
<el-tooltip class="clickable" placement="top" effect="light">
|
||||
<div slot="content" v-html="'<strong>Node Description:</strong><br />' + nodeTypeDescription + '<br /><br /><strong>For more information and usage examples click!</strong>'"></div>
|
||||
<div slot="content" v-html="'<strong>Node Description:</strong><br />' + nodeTypeDescription + '<br /><br /><strong>Click the \'?\' icon to open this node on n8n.io </strong>'"></div>
|
||||
<font-awesome-icon icon="question-circle" />
|
||||
</el-tooltip>
|
||||
</a>
|
||||
|
@ -22,7 +22,7 @@
|
|||
<node-webhooks :node="node" :nodeType="nodeType" />
|
||||
<parameter-input-list :parameters="parametersNoneSetting" :hideDelete="true" :nodeValues="nodeValues" path="parameters" @valueChanged="valueChanged" />
|
||||
<div v-if="parametersNoneSetting.length === 0">
|
||||
The node does not have any parameters.
|
||||
This node does not have any parameters.
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Settings">
|
||||
|
@ -162,15 +162,15 @@ export default mixins(
|
|||
},
|
||||
default: '',
|
||||
noDataExpression: true,
|
||||
description: 'Notes to save with the node.',
|
||||
description: 'Optional note to save with the node.',
|
||||
},
|
||||
{
|
||||
displayName: 'Notes In Flow',
|
||||
displayName: 'Display note in flow?',
|
||||
name: 'notesInFlow',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
noDataExpression: true,
|
||||
description: 'If activated it will display the above notes in the flow as subtitle.',
|
||||
description: 'If active, the note above will display in the flow as a subtitle.',
|
||||
},
|
||||
{
|
||||
displayName: 'Node Color',
|
||||
|
@ -186,7 +186,7 @@ export default mixins(
|
|||
type: 'boolean',
|
||||
default: false,
|
||||
noDataExpression: true,
|
||||
description: 'If activated and the node does not have any data for the first output,<br />it returns an empty item anyway. Be careful setting this on<br />IF-Nodes as it could easily cause an infinite loop.',
|
||||
description: 'If active, the node will return an empty item even if the <br />node returns no data during an initial execution. Be careful setting <br />this on IF-Nodes as it could cause an infinite loop.',
|
||||
},
|
||||
{
|
||||
displayName: 'Execute Once',
|
||||
|
@ -194,7 +194,7 @@ export default mixins(
|
|||
type: 'boolean',
|
||||
default: false,
|
||||
noDataExpression: true,
|
||||
description: 'Instead of executing once per item does it only execute once with the data of the first item.',
|
||||
description: 'If active, the node executes only once, with data<br /> from the first item it recieves. ',
|
||||
},
|
||||
{
|
||||
displayName: 'Retry On Fail',
|
||||
|
@ -202,7 +202,7 @@ export default mixins(
|
|||
type: 'boolean',
|
||||
default: false,
|
||||
noDataExpression: true,
|
||||
description: 'If activated it will automatically retry the node again multiple times.',
|
||||
description: 'If active, the node tries to execute a failed attempt <br /> multiple times until it succeeds.',
|
||||
},
|
||||
{
|
||||
displayName: 'Max. Tries',
|
||||
|
@ -221,7 +221,7 @@ export default mixins(
|
|||
},
|
||||
},
|
||||
noDataExpression: true,
|
||||
description: 'How often it should try to execute the node before it should fail.',
|
||||
description: 'Number of times Retry On Fail should attempt to execute the node <br />before stopping and returning the execution as failed.',
|
||||
},
|
||||
{
|
||||
displayName: 'Wait Between Tries',
|
||||
|
@ -240,7 +240,7 @@ export default mixins(
|
|||
},
|
||||
},
|
||||
noDataExpression: true,
|
||||
description: 'How long to wait between ties. Value in ms.',
|
||||
description: 'How long to wait between each attempt. Value in ms.',
|
||||
},
|
||||
{
|
||||
displayName: 'Continue On Fail',
|
||||
|
@ -248,7 +248,7 @@ export default mixins(
|
|||
type: 'boolean',
|
||||
default: false,
|
||||
noDataExpression: true,
|
||||
description: 'If activated and the node fails the workflow will simply continue running.<br />It will then simply pass through the input data so the workflow has<br />to be set up to handle the case that different data gets returned.',
|
||||
description: 'If active, the workflow continues even if this node\'s <br /execution fails. When this occurs, the node passes along input data from<br />previous nodes - so your workflow should account for unexpected output data.',
|
||||
},
|
||||
] as INodeProperties[],
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ export default mixins(
|
|||
|
||||
this.$showMessage({
|
||||
title: 'Copied',
|
||||
message: `The webhook URL got copied!`,
|
||||
message: `The webhook URL was successfully copied!`,
|
||||
type: 'success',
|
||||
});
|
||||
},
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
:disabled="workflowRunning"
|
||||
@click.stop="runWorkflow(node.name)"
|
||||
class="execute-node-button"
|
||||
:title="`Executes node ${node.name} and all not already executed nodes before it.`"
|
||||
:title="`Executes this ${node.name} node after executing any previous nodes that have not yet returned data`"
|
||||
>
|
||||
<div class="run-icon-button">
|
||||
<font-awesome-icon v-if="!workflowRunning" icon="play-circle"/>
|
||||
|
@ -72,14 +72,14 @@
|
|||
<span v-else>
|
||||
<div v-if="showData === false" class="to-much-data">
|
||||
<h3>
|
||||
Node contains large amount of data
|
||||
Node returned a large amount of data
|
||||
</h3>
|
||||
|
||||
<div class="text">
|
||||
The node contains {{parseInt(dataSize/1024).toLocaleString()}} KB of data.<br />
|
||||
Displaying it could cause problems!<br />
|
||||
<br />
|
||||
If you decide to display it anyway avoid the JSON view!
|
||||
If you do decide to display it, avoid the JSON view!
|
||||
</div>
|
||||
|
||||
<el-button size="small" @click="displayMode = 'Table';showData = true;">
|
||||
|
@ -162,7 +162,7 @@
|
|||
<div>
|
||||
<strong>No data</strong><br />
|
||||
<br />
|
||||
To display data execute the node first by pressing the execute button above.
|
||||
Data returned by this node will display here<br />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -34,6 +34,7 @@ import WorkflowActivator from '@/components/WorkflowActivator.vue';
|
|||
import { restApi } from '@/components/mixins/restApi';
|
||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
import { showMessage } from '@/components/mixins/showMessage';
|
||||
import { titleChange } from '@/components/mixins/titleChange';
|
||||
import { IWorkflowShortResponse } from '@/Interface';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
@ -42,6 +43,7 @@ export default mixins(
|
|||
genericHelpers,
|
||||
restApi,
|
||||
showMessage,
|
||||
titleChange,
|
||||
).extend({
|
||||
name: 'WorkflowOpen',
|
||||
props: [
|
||||
|
@ -89,6 +91,7 @@ export default mixins(
|
|||
},
|
||||
openWorkflow (data: IWorkflowShortResponse, column: any) { // tslint:disable-line:no-any
|
||||
if (column.label !== 'Active') {
|
||||
this.$titleSet(data.name, 'IDLE');
|
||||
this.$emit('openWorkflow', data.id);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -10,12 +10,14 @@ import {
|
|||
|
||||
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
||||
import { showMessage } from '@/components/mixins/showMessage';
|
||||
import { titleChange } from '@/components/mixins/titleChange';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
export const pushConnection = mixins(
|
||||
nodeHelpers,
|
||||
showMessage,
|
||||
titleChange,
|
||||
)
|
||||
.extend({
|
||||
data () {
|
||||
|
@ -147,7 +149,6 @@ export const pushConnection = mixins(
|
|||
*/
|
||||
pushMessageReceived (event: Event, isRetry?: boolean): boolean {
|
||||
const retryAttempts = 5;
|
||||
|
||||
let receivedData: IPushData;
|
||||
try {
|
||||
// @ts-ignore
|
||||
|
@ -201,13 +202,15 @@ export const pushConnection = mixins(
|
|||
|
||||
const runDataExecuted = pushData.data;
|
||||
|
||||
// @ts-ignore
|
||||
const workflow = this.getWorkflow();
|
||||
if (runDataExecuted.finished !== true) {
|
||||
// There was a problem with executing the workflow
|
||||
let errorMessage = 'There was a problem executing the workflow!';
|
||||
if (runDataExecuted.data.resultData.error && runDataExecuted.data.resultData.error.message) {
|
||||
errorMessage = `There was a problem executing the workflow:<br /><strong>"${runDataExecuted.data.resultData.error.message}"</strong>`;
|
||||
}
|
||||
|
||||
this.$titleSet(workflow.name, 'ERROR');
|
||||
this.$showMessage({
|
||||
title: 'Problem executing workflow',
|
||||
message: errorMessage,
|
||||
|
@ -215,6 +218,7 @@ export const pushConnection = mixins(
|
|||
});
|
||||
} else {
|
||||
// Workflow did execute without a problem
|
||||
this.$titleSet(workflow.name, 'IDLE');
|
||||
this.$showMessage({
|
||||
title: 'Workflow got executed',
|
||||
message: 'Workflow did get executed successfully!',
|
||||
|
|
31
packages/editor-ui/src/components/mixins/titleChange.ts
Normal file
31
packages/editor-ui/src/components/mixins/titleChange.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
import {
|
||||
WorkflowTitleStatus,
|
||||
} from '../../Interface';
|
||||
|
||||
export const titleChange = Vue.extend({
|
||||
methods: {
|
||||
/**
|
||||
* Change title of n8n tab
|
||||
*
|
||||
* @param {string} workflow Name of workflow
|
||||
* @param {WorkflowTitleStatus} status Status of workflow
|
||||
*/
|
||||
$titleSet(workflow: string, status: WorkflowTitleStatus) {
|
||||
let icon = '⚠️';
|
||||
if (status === 'EXECUTING') {
|
||||
icon = '🔄';
|
||||
} else if (status === 'IDLE') {
|
||||
icon = '▶️';
|
||||
}
|
||||
|
||||
window.document.title = `n8n - ${icon} ${workflow}`;
|
||||
},
|
||||
|
||||
$titleReset() {
|
||||
document.title = `n8n - Workflow Automation`;
|
||||
},
|
||||
|
||||
},
|
||||
});
|
|
@ -14,10 +14,12 @@ import { restApi } from '@/components/mixins/restApi';
|
|||
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { titleChange } from './titleChange';
|
||||
|
||||
export const workflowRun = mixins(
|
||||
restApi,
|
||||
workflowHelpers,
|
||||
titleChange,
|
||||
).extend({
|
||||
methods: {
|
||||
// Starts to executes a workflow on server.
|
||||
|
@ -27,6 +29,7 @@ export const workflowRun = mixins(
|
|||
// because then it can not receive the data as it executes.
|
||||
throw new Error('No active connection to server. It is maybe down.');
|
||||
}
|
||||
const workflow = this.getWorkflow();
|
||||
|
||||
this.$store.commit('addActiveAction', 'workflowRunning');
|
||||
|
||||
|
@ -55,6 +58,7 @@ export const workflowRun = mixins(
|
|||
}
|
||||
|
||||
const workflow = this.getWorkflow();
|
||||
this.$titleSet(workflow.name as string, 'EXECUTING');
|
||||
|
||||
try {
|
||||
// Check first if the workflow has any issues before execute it
|
||||
|
@ -78,6 +82,7 @@ export const workflowRun = mixins(
|
|||
type: 'error',
|
||||
duration: 0,
|
||||
});
|
||||
this.$titleSet(workflow.name as string, 'ERROR');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -165,8 +170,9 @@ export const workflowRun = mixins(
|
|||
};
|
||||
this.$store.commit('setWorkflowExecutionData', executionData);
|
||||
|
||||
return await this.runWorkflowApi(startRunData);
|
||||
return await this.runWorkflowApi(startRunData);
|
||||
} catch (error) {
|
||||
this.$titleSet(workflow.name as string, 'ERROR');
|
||||
this.$showError(error, 'Problem running workflow', 'There was a problem running the workflow:');
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -115,6 +115,8 @@ import { mouseSelect } from '@/components/mixins/mouseSelect';
|
|||
import { moveNodeWorkflow } from '@/components/mixins/moveNodeWorkflow';
|
||||
import { restApi } from '@/components/mixins/restApi';
|
||||
import { showMessage } from '@/components/mixins/showMessage';
|
||||
import { titleChange } from '@/components/mixins/titleChange';
|
||||
|
||||
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
||||
import { workflowRun } from '@/components/mixins/workflowRun';
|
||||
|
||||
|
@ -165,6 +167,7 @@ export default mixins(
|
|||
moveNodeWorkflow,
|
||||
restApi,
|
||||
showMessage,
|
||||
titleChange,
|
||||
workflowHelpers,
|
||||
workflowRun,
|
||||
)
|
||||
|
@ -1324,6 +1327,8 @@ export default mixins(
|
|||
}
|
||||
|
||||
if (workflowId !== null) {
|
||||
const workflow = await this.restApi().getWorkflow(workflowId);
|
||||
this.$titleSet(workflow.name, 'IDLE');
|
||||
// Open existing workflow
|
||||
await this.openWorkflow(workflowId);
|
||||
} else {
|
||||
|
|
34
packages/nodes-base/credentials/ContentfulApi.credentials.ts
Normal file
34
packages/nodes-base/credentials/ContentfulApi.credentials.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
//https://www.contentful.com/developers/docs/references/authentication/
|
||||
export class ContentfulApi implements ICredentialType {
|
||||
name = 'contentfulApi';
|
||||
displayName = 'Contenful API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Space ID',
|
||||
name: 'spaceId',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'The id for the Contentful space.',
|
||||
},
|
||||
{
|
||||
displayName: 'Content Delivery API Access token',
|
||||
name: 'ContentDeliveryaccessToken',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
description: 'Access token that has access to the space. Can be left empty if only Delivery API should be used.',
|
||||
},
|
||||
{
|
||||
displayName: 'Content Preview API Access token',
|
||||
name: 'ContentPreviewaccessToken',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
description: 'Access token that has access to the space. Can be left empty if only Preview API should be used.',
|
||||
},
|
||||
];
|
||||
}
|
21
packages/nodes-base/credentials/ConvertKitApi.credentials.ts
Normal file
21
packages/nodes-base/credentials/ConvertKitApi.credentials.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
export class ConvertKitApi implements ICredentialType {
|
||||
name = 'convertKitApi';
|
||||
displayName = 'ConvertKit API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'API Secret',
|
||||
name: 'apiSecret',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
typeOptions: {
|
||||
password: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
23
packages/nodes-base/credentials/PaddleApi.credentials.ts
Normal file
23
packages/nodes-base/credentials/PaddleApi.credentials.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class PaddleApi implements ICredentialType {
|
||||
name = 'paddleApi';
|
||||
displayName = 'Paddle API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Vendor Auth Code',
|
||||
name: 'vendorAuthCode',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Vendor ID',
|
||||
name: 'vendorId',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -36,10 +36,24 @@ export class Postgres implements ICredentialType {
|
|||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Ignore SSL Issues',
|
||||
name: 'allowUnauthorizedCerts',
|
||||
type: 'boolean' as NodePropertyTypes,
|
||||
default: false,
|
||||
description: 'Connect even if SSL certificate validation is not possible.',
|
||||
},
|
||||
{
|
||||
displayName: 'SSL',
|
||||
name: 'ssl',
|
||||
type: 'options' as NodePropertyTypes,
|
||||
displayOptions: {
|
||||
show: {
|
||||
allowUnauthorizedCerts: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'disable',
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class TodoistOAuth2Api implements ICredentialType {
|
||||
name = 'todoistOAuth2Api';
|
||||
extends = [
|
||||
'oAuth2Api',
|
||||
];
|
||||
displayName = 'Todoist OAuth2 API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Authorization URL',
|
||||
name: 'authUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://todoist.com/oauth/authorize',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Access Token URL',
|
||||
name: 'accessTokenUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://todoist.com/oauth/access_token',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'data:read_write,data:delete',
|
||||
},
|
||||
{
|
||||
displayName: 'Auth URI Query Parameters',
|
||||
name: 'authQueryParameters',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'body',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -13,7 +13,8 @@ export class ZendeskApi implements ICredentialType {
|
|||
name: 'subdomain',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
description: 'The subdomain of your Zendesk work environment.',
|
||||
default: 'n8n',
|
||||
placeholder: 'company',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
|
|
|
@ -526,4 +526,4 @@ export const dealFields = [
|
|||
description: 'The content of the deal note',
|
||||
},
|
||||
|
||||
] as INodeProperties[];
|
||||
] as INodeProperties[];
|
||||
|
|
|
@ -367,7 +367,7 @@ export class Airtable implements INodeType {
|
|||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
|
||||
const application = this.getNodeParameter('application', 0) as string;
|
||||
const table = this.getNodeParameter('table', 0) as string;
|
||||
const table = encodeURI(this.getNodeParameter('table', 0) as string);
|
||||
|
||||
let returnAll = false;
|
||||
let endpoint = '';
|
||||
|
|
|
@ -237,10 +237,6 @@ export class Coda implements INodeType {
|
|||
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||
const endpoint = `/docs/${docId}/tables/${tableId}/rows`;
|
||||
|
||||
if (options.keyColumns) {
|
||||
// @ts-ignore
|
||||
items[i].json['keyColumns'] = options.keyColumns.split(',') as string[];
|
||||
}
|
||||
if (options.disableParsing) {
|
||||
qs.disableParsing = options.disableParsing as boolean;
|
||||
}
|
||||
|
@ -264,6 +260,11 @@ export class Coda implements INodeType {
|
|||
};
|
||||
}
|
||||
((sendData[endpoint]! as IDataObject).rows! as IDataObject[]).push({ cells });
|
||||
|
||||
if (options.keyColumns) {
|
||||
// @ts-ignore
|
||||
(sendData[endpoint]! as IDataObject).keyColumns! = options.keyColumns.split(',') as string[];
|
||||
}
|
||||
}
|
||||
|
||||
// Now that all data got collected make all the requests
|
||||
|
|
197
packages/nodes-base/nodes/Contentful/AssetDescription.ts
Normal file
197
packages/nodes-base/nodes/Contentful/AssetDescription.ts
Normal file
|
@ -0,0 +1,197 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
INodePropertyOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const resource = {
|
||||
name: 'Asset',
|
||||
value: 'asset',
|
||||
} as INodePropertyOptions;
|
||||
|
||||
export const operations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
},
|
||||
],
|
||||
default: 'getAll',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const fields = [
|
||||
{
|
||||
displayName: 'Environment ID',
|
||||
name: 'environmentId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 'master',
|
||||
description: 'The id for the Contentful environment (e.g. master, staging, etc.). Depending on your plan, you might not have environments. In that case use "master".'
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Equal',
|
||||
name: 'equal',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'fields.title=n8n',
|
||||
description: 'Search for all data that matches the condition: {attribute}={value}. Attribute can use dot notation.',
|
||||
},
|
||||
{
|
||||
displayName: 'Exclude',
|
||||
name: 'exclude',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'fields.tags[nin]=accessories,flowers',
|
||||
description: 'Search for all data that matches the condition: {attribute}[nin]={value}. Attribute can use dot notation.',
|
||||
},
|
||||
{
|
||||
displayName: 'Exist',
|
||||
name: 'exist',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'fields.tags[exists]=true',
|
||||
description: 'Search for all data that matches the condition: {attribute}[exists]={value}. Attribute can use dot notation.',
|
||||
},
|
||||
{
|
||||
displayName: 'Fields',
|
||||
name: 'select',
|
||||
type: 'string',
|
||||
placeholder: 'fields.title',
|
||||
default: '',
|
||||
description: 'The select operator allows you to choose what fields to return from an entity. You can choose multiple values by combining comma separated operators.',
|
||||
},
|
||||
{
|
||||
displayName: 'Include',
|
||||
name: 'include',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'fields.tags[in]=accessories,flowers',
|
||||
description: 'Search for all data that matches the condition: {attribute}[in]={value}. Attribute can use dot notation.',
|
||||
},
|
||||
{
|
||||
displayName: 'Not Equal',
|
||||
name: 'notEqual',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'fields.title[ne]=n8n',
|
||||
description: 'Search for all data that matches the condition: {attribute}[ne]={value}. Attribute can use dot notation.',
|
||||
},
|
||||
{
|
||||
displayName: 'Order',
|
||||
name: 'order',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'sys.createdAt',
|
||||
description: 'You can order items in the response by specifying the order search parameter. You can use sys properties (such as sys.createdAt) or field values (such as fields.myCustomDateField) for ordering.',
|
||||
},
|
||||
{
|
||||
displayName: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: ' Full-text search is case insensitive and might return more results than expected. A query will only take values with more than 1 character.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Asset ID',
|
||||
name: 'assetId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
resource.value
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
] as INodeProperties[];
|
|
@ -0,0 +1,69 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
INodePropertyOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const resource = {
|
||||
name: 'Content Type',
|
||||
value: 'contentType',
|
||||
} as INodePropertyOptions;
|
||||
|
||||
export const operations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const fields = [
|
||||
{
|
||||
displayName: 'Environment ID',
|
||||
name: 'environmentId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 'master',
|
||||
description: 'The id for the Contentful environment (e.g. master, staging, etc.). Depending on your plan, you might not have environments. In that case use "master".'
|
||||
},
|
||||
{
|
||||
displayName: 'Content Type ID',
|
||||
name: 'contentTypeId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
] as INodeProperties[];
|
266
packages/nodes-base/nodes/Contentful/Contentful.node.ts
Normal file
266
packages/nodes-base/nodes/Contentful/Contentful.node.ts
Normal file
|
@ -0,0 +1,266 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
contentfulApiRequest,
|
||||
contenfulApiRequestAllItems,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import * as SpaceDescription from './SpaceDescription';
|
||||
import * as ContentTypeDescription from './ContentTypeDescription';
|
||||
import * as EntryDescription from './EntryDescription';
|
||||
import * as AssetDescription from './AssetDescription';
|
||||
import * as LocaleDescription from './LocaleDescription';
|
||||
|
||||
export class Contentful implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Contentful',
|
||||
name: 'contentful',
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
icon: 'file:contentful.png',
|
||||
group: ['input'],
|
||||
version: 1,
|
||||
description: 'Consume Contenful API',
|
||||
defaults: {
|
||||
name: 'Contentful',
|
||||
color: '#2E75D4'
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'contentfulApi',
|
||||
required: true
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Source',
|
||||
name: 'source',
|
||||
type: 'options',
|
||||
default: 'deliveryApi',
|
||||
description: 'Pick where your data comes from, delivery or preview API',
|
||||
options: [
|
||||
{
|
||||
name: 'Delivery API',
|
||||
value: 'deliveryApi'
|
||||
},
|
||||
{
|
||||
name: 'Preview API',
|
||||
value: 'previewApi'
|
||||
},
|
||||
],
|
||||
},
|
||||
// Resources:
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
AssetDescription.resource,
|
||||
ContentTypeDescription.resource,
|
||||
EntryDescription.resource,
|
||||
LocaleDescription.resource,
|
||||
SpaceDescription.resource,
|
||||
],
|
||||
default: 'entry',
|
||||
description: 'The resource to operate on.'
|
||||
},
|
||||
|
||||
// Operations:
|
||||
...SpaceDescription.operations,
|
||||
...ContentTypeDescription.operations,
|
||||
...EntryDescription.operations,
|
||||
...AssetDescription.operations,
|
||||
...LocaleDescription.operations,
|
||||
|
||||
// Resource specific fields:
|
||||
...SpaceDescription.fields,
|
||||
...ContentTypeDescription.fields,
|
||||
...EntryDescription.fields,
|
||||
...AssetDescription.fields,
|
||||
...LocaleDescription.fields,
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
let responseData;
|
||||
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const qs: Record<string, string | number> = {};
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (resource === 'space') {
|
||||
if (operation === 'get') {
|
||||
|
||||
const credentials = this.getCredentials('contentfulApi');
|
||||
|
||||
responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}`);
|
||||
}
|
||||
}
|
||||
if (resource === 'contentType') {
|
||||
if (operation === 'get') {
|
||||
|
||||
const credentials = this.getCredentials('contentfulApi');
|
||||
|
||||
const env = this.getNodeParameter('environmentId', 0) as string;
|
||||
|
||||
const id = this.getNodeParameter('contentTypeId', 0) as string;
|
||||
|
||||
responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/content_types/${id}`);
|
||||
}
|
||||
}
|
||||
if (resource === 'entry') {
|
||||
|
||||
if (operation === 'get') {
|
||||
|
||||
const credentials = this.getCredentials('contentfulApi');
|
||||
|
||||
const env = this.getNodeParameter('environmentId', 0) as string;
|
||||
|
||||
const id = this.getNodeParameter('entryId', 0) as string;
|
||||
|
||||
responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/entries/${id}`, {}, qs);
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
const credentials = this.getCredentials('contentfulApi');
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', 0) as boolean;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
const env = this.getNodeParameter('environmentId', i) as string;
|
||||
|
||||
Object.assign(qs, additionalFields);
|
||||
|
||||
if (qs.equal) {
|
||||
const [atribute, value] = (qs.equal as string).split('=');
|
||||
qs[atribute] = value;
|
||||
delete qs.equal;
|
||||
}
|
||||
|
||||
if (qs.notEqual) {
|
||||
const [atribute, value] = (qs.notEqual as string).split('=');
|
||||
qs[atribute] = value;
|
||||
delete qs.notEqual;
|
||||
}
|
||||
|
||||
if (qs.include) {
|
||||
const [atribute, value] = (qs.include as string).split('=');
|
||||
qs[atribute] = value;
|
||||
delete qs.include;
|
||||
}
|
||||
|
||||
if (qs.exclude) {
|
||||
const [atribute, value] = (qs.exclude as string).split('=');
|
||||
qs[atribute] = value;
|
||||
delete qs.exclude;
|
||||
}
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await contenfulApiRequestAllItems.call(this, 'items', 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/entries`, {}, qs);
|
||||
} else {
|
||||
const limit = this.getNodeParameter('limit', 0) as number;
|
||||
qs.limit = limit;
|
||||
responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/entries`, {}, qs);
|
||||
responseData = responseData.items;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource === 'asset') {
|
||||
if (operation === 'get') {
|
||||
|
||||
const credentials = this.getCredentials('contentfulApi');
|
||||
|
||||
const env = this.getNodeParameter('environmentId', 0) as string;
|
||||
|
||||
const id = this.getNodeParameter('assetId', 0) as string;
|
||||
|
||||
responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/assets/${id}`, {}, qs);
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
const credentials = this.getCredentials('contentfulApi');
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', 0) as boolean;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
const env = this.getNodeParameter('environmentId', i) as string;
|
||||
|
||||
Object.assign(qs, additionalFields);
|
||||
|
||||
if (qs.equal) {
|
||||
const [atribute, value] = (qs.equal as string).split('=');
|
||||
qs[atribute] = value;
|
||||
delete qs.equal;
|
||||
}
|
||||
|
||||
if (qs.notEqual) {
|
||||
const [atribute, value] = (qs.notEqual as string).split('=');
|
||||
qs[atribute] = value;
|
||||
delete qs.notEqual;
|
||||
}
|
||||
|
||||
if (qs.include) {
|
||||
const [atribute, value] = (qs.include as string).split('=');
|
||||
qs[atribute] = value;
|
||||
delete qs.include;
|
||||
}
|
||||
|
||||
if (qs.exclude) {
|
||||
const [atribute, value] = (qs.exclude as string).split('=');
|
||||
qs[atribute] = value;
|
||||
delete qs.exclude;
|
||||
}
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await contenfulApiRequestAllItems.call(this, 'items', 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/assets`, {}, qs);
|
||||
} else {
|
||||
const limit = this.getNodeParameter('limit', 0) as number;
|
||||
qs.limit = limit;
|
||||
responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/assets`, {}, qs);
|
||||
responseData = responseData.items;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource === 'locale') {
|
||||
|
||||
if (operation === 'getAll') {
|
||||
|
||||
const credentials = this.getCredentials('contentfulApi');
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', 0) as boolean;
|
||||
|
||||
const env = this.getNodeParameter('environmentId', i) as string;
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await contenfulApiRequestAllItems.call(this, 'items', 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/locales`, {}, qs);
|
||||
} else {
|
||||
const limit = this.getNodeParameter('limit', 0) as number;
|
||||
qs.limit = limit;
|
||||
responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/locales`, {}, qs);
|
||||
responseData = responseData.items;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else {
|
||||
returnData.push(responseData as IDataObject);
|
||||
}
|
||||
}
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
204
packages/nodes-base/nodes/Contentful/EntryDescription.ts
Normal file
204
packages/nodes-base/nodes/Contentful/EntryDescription.ts
Normal file
|
@ -0,0 +1,204 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
INodePropertyOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const resource = {
|
||||
name: 'Entry',
|
||||
value: 'entry',
|
||||
} as INodePropertyOptions;
|
||||
|
||||
export const operations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform.'
|
||||
}
|
||||
] as INodeProperties[];
|
||||
|
||||
export const fields = [
|
||||
{
|
||||
displayName: 'Environment ID',
|
||||
name: 'environmentId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 'master',
|
||||
description: 'The id for the Contentful environment (e.g. master, staging, etc.). Depending on your plan, you might not have environments. In that case use "master".',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Content Type ID',
|
||||
name: 'content_type',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'To search for entries with a specific content type',
|
||||
},
|
||||
{
|
||||
displayName: 'Equal',
|
||||
name: 'equal',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'fields.title=n8n',
|
||||
description: 'Search for all data that matches the condition: {attribute}={value}. Attribute can use dot notation.',
|
||||
},
|
||||
{
|
||||
displayName: 'Exclude',
|
||||
name: 'exclude',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'fields.tags[nin]=accessories,flowers',
|
||||
description: 'Search for all data that matches the condition: {attribute}[nin]={value}. Attribute can use dot notation.',
|
||||
},
|
||||
{
|
||||
displayName: 'Exist',
|
||||
name: 'exist',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'fields.tags[exists]=true',
|
||||
description: 'Search for all data that matches the condition: {attribute}[exists]={value}. Attribute can use dot notation.',
|
||||
},
|
||||
{
|
||||
displayName: 'Fields',
|
||||
name: 'select',
|
||||
type: 'string',
|
||||
placeholder: 'fields.title',
|
||||
default: '',
|
||||
description: 'The select operator allows you to choose what fields to return from an entity. You can choose multiple values by combining comma separated operators.',
|
||||
},
|
||||
{
|
||||
displayName: 'Include',
|
||||
name: 'include',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'fields.tags[in]=accessories,flowers',
|
||||
description: 'Search for all data that matches the condition: {attribute}[in]={value}. Attribute can use dot notation.',
|
||||
},
|
||||
{
|
||||
displayName: 'Not Equal',
|
||||
name: 'notEqual',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'fields.title[ne]=n8n',
|
||||
description: 'Search for all data that matches the condition: {attribute}[ne]={value}. Attribute can use dot notation.',
|
||||
},
|
||||
{
|
||||
displayName: 'Order',
|
||||
name: 'order',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'sys.createdAt',
|
||||
description: 'You can order items in the response by specifying the order search parameter. You can use sys properties (such as sys.createdAt) or field values (such as fields.myCustomDateField) for ordering.',
|
||||
},
|
||||
{
|
||||
displayName: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: ' Full-text search is case insensitive and might return more results than expected. A query will only take values with more than 1 character.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Entry ID',
|
||||
name: 'entryId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
] as INodeProperties[];
|
75
packages/nodes-base/nodes/Contentful/GenericFunctions.ts
Normal file
75
packages/nodes-base/nodes/Contentful/GenericFunctions.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function contentfulApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const credentials = this.getCredentials('contentfulApi');
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
}
|
||||
|
||||
const source = this.getNodeParameter('source', 0) as string;
|
||||
const isPreview = source === 'previewApi';
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
method,
|
||||
qs,
|
||||
body,
|
||||
uri: uri ||`https://${isPreview ? 'preview' : 'cdn'}.contentful.com${resource}`,
|
||||
json: true
|
||||
};
|
||||
|
||||
if (isPreview) {
|
||||
qs.access_token = credentials.ContentPreviewaccessToken as string;
|
||||
} else {
|
||||
qs.access_token = credentials.ContentDeliveryaccessToken as string;
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
|
||||
let errorMessage = error;
|
||||
|
||||
if (error.response && error.response.body && error.response.body.details) {
|
||||
const details = error.response.body.details;
|
||||
errorMessage = details.errors.map((e: IDataObject) => e.details).join('|');
|
||||
} else if (error.response && error.response.body && error.response.body.message) {
|
||||
errorMessage = error.response.body.message;
|
||||
}
|
||||
|
||||
throw new Error(`Contentful error response [${error.statusCode}]: ${errorMessage}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export async function contenfulApiRequestAllItems(this: ILoadOptionsFunctions | IExecuteFunctions, propertyName: string, method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
|
||||
query.limit = 100;
|
||||
query.skip = 0;
|
||||
|
||||
do {
|
||||
responseData = await contentfulApiRequest.call(this, method, resource, body, query);
|
||||
query.skip = (query.skip + 1) * query.limit;
|
||||
returnData.push.apply(returnData, responseData[propertyName]);
|
||||
} while (
|
||||
returnData.length < responseData.total
|
||||
);
|
||||
|
||||
return returnData;
|
||||
}
|
94
packages/nodes-base/nodes/Contentful/LocaleDescription.ts
Normal file
94
packages/nodes-base/nodes/Contentful/LocaleDescription.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
INodePropertyOptions
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const resource = {
|
||||
name: 'Locale',
|
||||
value: 'locale',
|
||||
} as INodePropertyOptions;
|
||||
|
||||
export const operations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
},
|
||||
],
|
||||
default: 'getAll',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const fields = [
|
||||
{
|
||||
displayName: 'Environment ID',
|
||||
name: 'environmentId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 'master',
|
||||
description: 'The id for the Contentful environment (e.g. master, staging, etc.). Depending on your plan, you might not have environments. In that case use "master".'
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
] as INodeProperties[];
|
|
@ -0,0 +1,37 @@
|
|||
import { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const fields = [
|
||||
{
|
||||
displayName: 'Search Parameters',
|
||||
name: 'search_parameters',
|
||||
description: 'You can use a variety of query parameters to search and filter items.',
|
||||
placeholder: 'Add parameter',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Parameters',
|
||||
name: 'parameters',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Parameter Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of the search parameter to set.'
|
||||
},
|
||||
{
|
||||
displayName: 'Parameter Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Value of the search parameter to set.'
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
] as INodeProperties[];
|
33
packages/nodes-base/nodes/Contentful/SpaceDescription.ts
Normal file
33
packages/nodes-base/nodes/Contentful/SpaceDescription.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const resource = {
|
||||
name: 'Space',
|
||||
value: 'space',
|
||||
};
|
||||
|
||||
export const operations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
resource.value,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const fields = [] as INodeProperties[];
|
BIN
packages/nodes-base/nodes/Contentful/contentful.png
Normal file
BIN
packages/nodes-base/nodes/Contentful/contentful.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
486
packages/nodes-base/nodes/ConvertKit/ConvertKit.node.ts
Normal file
486
packages/nodes-base/nodes/ConvertKit/ConvertKit.node.ts
Normal file
|
@ -0,0 +1,486 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeTypeDescription,
|
||||
INodeType,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
convertKitApiRequest,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
customFieldFields,
|
||||
customFieldOperations,
|
||||
} from './CustomFieldDescription';
|
||||
|
||||
import {
|
||||
formFields,
|
||||
formOperations,
|
||||
} from './FormDescription';
|
||||
|
||||
import {
|
||||
sequenceFields,
|
||||
sequenceOperations,
|
||||
} from './SequenceDescription';
|
||||
|
||||
import {
|
||||
tagFields,
|
||||
tagOperations,
|
||||
} from './TagDescription';
|
||||
|
||||
import {
|
||||
tagSubscriberFields,
|
||||
tagSubscriberOperations,
|
||||
} from './TagSubscriberDescription';
|
||||
|
||||
export class ConvertKit implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'ConvertKit',
|
||||
name: 'convertKit',
|
||||
icon: 'file:convertKit.png',
|
||||
group: ['input'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume ConvertKit API.',
|
||||
defaults: {
|
||||
name: 'ConvertKit',
|
||||
color: '#fb6970',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'convertKitApi',
|
||||
required: true,
|
||||
}
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Custom Field',
|
||||
value: 'customField',
|
||||
},
|
||||
{
|
||||
name: 'Form',
|
||||
value: 'form',
|
||||
},
|
||||
{
|
||||
name: 'Sequence',
|
||||
value: 'sequence',
|
||||
},
|
||||
{
|
||||
name: 'Tag',
|
||||
value: 'tag',
|
||||
},
|
||||
{
|
||||
name: 'Tag Subscriber',
|
||||
value: 'tagSubscriber',
|
||||
},
|
||||
],
|
||||
default: 'form',
|
||||
description: 'The resource to operate on.'
|
||||
},
|
||||
//--------------------
|
||||
// Field Description
|
||||
//--------------------
|
||||
...customFieldOperations,
|
||||
...customFieldFields,
|
||||
//--------------------
|
||||
// FormDescription
|
||||
//--------------------
|
||||
...formOperations,
|
||||
...formFields,
|
||||
//--------------------
|
||||
// Sequence Description
|
||||
//--------------------
|
||||
...sequenceOperations,
|
||||
...sequenceFields,
|
||||
//--------------------
|
||||
// Tag Description
|
||||
//--------------------
|
||||
...tagOperations,
|
||||
...tagFields,
|
||||
//--------------------
|
||||
// Tag Subscriber Description
|
||||
//--------------------
|
||||
...tagSubscriberOperations,
|
||||
...tagSubscriberFields,
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
// Get all the tags to display them to user so that he can
|
||||
// select them easily
|
||||
async getTags(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const { tags } = await convertKitApiRequest.call(this, 'GET', '/tags');
|
||||
for (const tag of tags) {
|
||||
const tagName = tag.name;
|
||||
const tagId = tag.id;
|
||||
returnData.push({
|
||||
name: tagName,
|
||||
value: tagId,
|
||||
});
|
||||
}
|
||||
|
||||
return returnData;
|
||||
},
|
||||
// Get all the forms to display them to user so that he can
|
||||
// select them easily
|
||||
async getForms(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const { forms } = await convertKitApiRequest.call(this, 'GET', '/forms');
|
||||
for (const form of forms) {
|
||||
const formName = form.name;
|
||||
const formId = form.id;
|
||||
returnData.push({
|
||||
name: formName,
|
||||
value: formId,
|
||||
});
|
||||
}
|
||||
|
||||
return returnData;
|
||||
},
|
||||
|
||||
// Get all the sequences to display them to user so that he can
|
||||
// select them easily
|
||||
async getSequences(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const { courses } = await convertKitApiRequest.call(this, 'GET', '/sequences');
|
||||
for (const course of courses) {
|
||||
const courseName = course.name;
|
||||
const courseId = course.id;
|
||||
returnData.push({
|
||||
name: courseName,
|
||||
value: courseId,
|
||||
});
|
||||
}
|
||||
|
||||
return returnData;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
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 < items.length; i++) {
|
||||
|
||||
if (resource === 'customField') {
|
||||
if (operation === 'create') {
|
||||
|
||||
const label = this.getNodeParameter('label', i) as string;
|
||||
|
||||
responseData = await convertKitApiRequest.call(this, 'POST', '/custom_fields', { label }, qs);
|
||||
}
|
||||
if (operation === 'delete') {
|
||||
|
||||
const id = this.getNodeParameter('id', i) as string;
|
||||
|
||||
responseData = await convertKitApiRequest.call(this, 'DELETE', `/custom_fields/${id}`);
|
||||
}
|
||||
if (operation === 'get') {
|
||||
|
||||
const id = this.getNodeParameter('id', i) as string;
|
||||
|
||||
responseData = await convertKitApiRequest.call(this, 'GET', `/custom_fields/${id}`);
|
||||
}
|
||||
if (operation === 'getAll') {
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
responseData = await convertKitApiRequest.call(this, 'GET', `/custom_fields`);
|
||||
|
||||
responseData = responseData.custom_fields;
|
||||
|
||||
if (!returnAll) {
|
||||
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
|
||||
responseData = responseData.slice(0, limit);
|
||||
}
|
||||
}
|
||||
if (operation === 'update') {
|
||||
|
||||
const id = this.getNodeParameter('id', i) as string;
|
||||
|
||||
const label = this.getNodeParameter('label', i) as string;
|
||||
|
||||
responseData = await convertKitApiRequest.call(this, 'PUT', `/custom_fields/${id}`, { label });
|
||||
|
||||
responseData = { success: true };
|
||||
}
|
||||
}
|
||||
|
||||
if (resource === 'form') {
|
||||
if (operation === 'addSubscriber') {
|
||||
|
||||
const email = this.getNodeParameter('email', i) as string;
|
||||
|
||||
const formId = this.getNodeParameter('id', i) as string;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
const body: IDataObject = {
|
||||
email,
|
||||
};
|
||||
|
||||
if (additionalFields.firstName) {
|
||||
body.first_name = additionalFields.firstName as string;
|
||||
}
|
||||
|
||||
if (additionalFields.tags) {
|
||||
body.tags = additionalFields.tags as string[];
|
||||
}
|
||||
|
||||
if (additionalFields.fieldsUi) {
|
||||
const fieldValues = (additionalFields.fieldsUi as IDataObject).fieldsValues as IDataObject[];
|
||||
if (fieldValues) {
|
||||
body.fields = {};
|
||||
for (const fieldValue of fieldValues) {
|
||||
//@ts-ignore
|
||||
body.fields[fieldValue.key] = fieldValue.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { subscription } = await convertKitApiRequest.call(this, 'POST', `/forms/${formId}/subscribe`, body);
|
||||
|
||||
responseData = subscription;
|
||||
}
|
||||
if (operation === 'getAll') {
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
responseData = await convertKitApiRequest.call(this, 'GET', `/forms`);
|
||||
|
||||
responseData = responseData.forms;
|
||||
|
||||
if (!returnAll) {
|
||||
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
|
||||
responseData = responseData.slice(0, limit);
|
||||
}
|
||||
}
|
||||
if (operation === 'getSubscriptions') {
|
||||
|
||||
const formId = this.getNodeParameter('id', i) as string;
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (additionalFields.subscriberState) {
|
||||
qs.subscriber_state = additionalFields.subscriberState as string;
|
||||
}
|
||||
|
||||
responseData = await convertKitApiRequest.call(this, 'GET', `/forms/${formId}/subscriptions`, {}, qs);
|
||||
|
||||
responseData = responseData.subscriptions;
|
||||
|
||||
if (!returnAll) {
|
||||
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
|
||||
responseData = responseData.slice(0, limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resource === 'sequence') {
|
||||
if (operation === 'addSubscriber') {
|
||||
|
||||
const email = this.getNodeParameter('email', i) as string;
|
||||
|
||||
const sequenceId = this.getNodeParameter('id', i) as string;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
const body: IDataObject = {
|
||||
email,
|
||||
};
|
||||
|
||||
if (additionalFields.firstName) {
|
||||
body.first_name = additionalFields.firstName as string;
|
||||
}
|
||||
|
||||
if (additionalFields.tags) {
|
||||
body.tags = additionalFields.tags as string[];
|
||||
}
|
||||
|
||||
if (additionalFields.fieldsUi) {
|
||||
const fieldValues = (additionalFields.fieldsUi as IDataObject).fieldsValues as IDataObject[];
|
||||
if (fieldValues) {
|
||||
body.fields = {};
|
||||
for (const fieldValue of fieldValues) {
|
||||
//@ts-ignore
|
||||
body.fields[fieldValue.key] = fieldValue.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { subscription } = await convertKitApiRequest.call(this, 'POST', `/sequences/${sequenceId}/subscribe`, body);
|
||||
|
||||
responseData = subscription;
|
||||
}
|
||||
if (operation === 'getAll') {
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
responseData = await convertKitApiRequest.call(this, 'GET', `/sequences`);
|
||||
|
||||
responseData = responseData.courses;
|
||||
|
||||
if (!returnAll) {
|
||||
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
|
||||
responseData = responseData.slice(0, limit);
|
||||
}
|
||||
}
|
||||
if (operation === 'getSubscriptions') {
|
||||
|
||||
const sequenceId = this.getNodeParameter('id', i) as string;
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (additionalFields.subscriberState) {
|
||||
qs.subscriber_state = additionalFields.subscriberState as string;
|
||||
}
|
||||
|
||||
responseData = await convertKitApiRequest.call(this, 'GET', `/sequences/${sequenceId}/subscriptions`, {}, qs);
|
||||
|
||||
responseData = responseData.subscriptions;
|
||||
|
||||
if (!returnAll) {
|
||||
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
|
||||
responseData = responseData.slice(0, limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resource === 'tag') {
|
||||
if (operation === 'create') {
|
||||
|
||||
const names = ((this.getNodeParameter('name', i) as string).split(',') as string[]).map((e) => ({ name: e }));
|
||||
|
||||
const body: IDataObject = {
|
||||
tag: names
|
||||
};
|
||||
|
||||
responseData = await convertKitApiRequest.call(this, 'POST', '/tags', body);
|
||||
}
|
||||
|
||||
if (operation === 'getAll') {
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
responseData = await convertKitApiRequest.call(this, 'GET', `/tags`);
|
||||
|
||||
responseData = responseData.tags;
|
||||
|
||||
if (!returnAll) {
|
||||
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
|
||||
responseData = responseData.slice(0, limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resource === 'tagSubscriber') {
|
||||
|
||||
if (operation === 'add') {
|
||||
|
||||
const tagId = this.getNodeParameter('tagId', i) as string;
|
||||
|
||||
const email = this.getNodeParameter('email', i) as string;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
const body: IDataObject = {
|
||||
email,
|
||||
};
|
||||
|
||||
if (additionalFields.firstName) {
|
||||
body.first_name = additionalFields.firstName as string;
|
||||
}
|
||||
|
||||
if (additionalFields.fieldsUi) {
|
||||
const fieldValues = (additionalFields.fieldsUi as IDataObject).fieldsValues as IDataObject[];
|
||||
if (fieldValues) {
|
||||
body.fields = {};
|
||||
for (const fieldValue of fieldValues) {
|
||||
//@ts-ignore
|
||||
body.fields[fieldValue.key] = fieldValue.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { subscription } = await convertKitApiRequest.call(this, 'POST', `/tags/${tagId}/subscribe`, body);
|
||||
|
||||
responseData = subscription;
|
||||
}
|
||||
|
||||
if (operation === 'getAll') {
|
||||
|
||||
const tagId = this.getNodeParameter('tagId', i) as string;
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
responseData = await convertKitApiRequest.call(this, 'GET', `/tags/${tagId}/subscriptions`);
|
||||
|
||||
responseData = responseData.subscriptions;
|
||||
|
||||
if (!returnAll) {
|
||||
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
|
||||
responseData = responseData.slice(0, limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'delete') {
|
||||
|
||||
const tagId = this.getNodeParameter('tagId', i) as string;
|
||||
|
||||
const email = this.getNodeParameter('email', i) as string;
|
||||
|
||||
responseData = await convertKitApiRequest.call(this, 'POST', `/tags/${tagId}>/unsubscribe`, { email });
|
||||
}
|
||||
}
|
||||
|
||||
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)];
|
||||
}
|
||||
}
|
367
packages/nodes-base/nodes/ConvertKit/ConvertKitTrigger.node.ts
Normal file
367
packages/nodes-base/nodes/ConvertKit/ConvertKitTrigger.node.ts
Normal file
|
@ -0,0 +1,367 @@
|
|||
import {
|
||||
IHookFunctions,
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodePropertyOptions,
|
||||
INodeTypeDescription,
|
||||
INodeType,
|
||||
IWebhookResponseData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
convertKitApiRequest,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
snakeCase,
|
||||
} from 'change-case';
|
||||
|
||||
export class ConvertKitTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'ConvertKit Trigger',
|
||||
name: 'convertKitTrigger',
|
||||
icon: 'file:convertKit.png',
|
||||
subtitle: '={{$parameter["event"]}}',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
description: 'Handle ConvertKit events via webhooks',
|
||||
defaults: {
|
||||
name: 'ConvertKit Trigger',
|
||||
color: '#fb6970',
|
||||
},
|
||||
inputs: [],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'convertKitApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
webhooks: [
|
||||
{
|
||||
name: 'default',
|
||||
httpMethod: 'POST',
|
||||
responseMode: 'onReceived',
|
||||
path: 'webhook',
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Event',
|
||||
name: 'event',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'The events that can trigger the webhook and whether they are enabled.',
|
||||
options: [
|
||||
{
|
||||
name: 'Form Subscribe',
|
||||
value: 'formSubscribe',
|
||||
},
|
||||
{
|
||||
name: 'Link Click',
|
||||
value: 'linkClick',
|
||||
},
|
||||
{
|
||||
name: 'Product Purchase',
|
||||
value: 'productPurchase',
|
||||
},
|
||||
{
|
||||
name: 'Purchase Created',
|
||||
value: 'purchaseCreate',
|
||||
},
|
||||
{
|
||||
name: 'Sequence Complete',
|
||||
value: 'courseComplete',
|
||||
},
|
||||
{
|
||||
name: 'Sequence Subscribe',
|
||||
value: 'courseSubscribe',
|
||||
},
|
||||
{
|
||||
name: 'Subscriber Activated',
|
||||
value: 'subscriberActivate',
|
||||
},
|
||||
{
|
||||
name: 'Subscriber Unsubscribe',
|
||||
value: 'subscriberUnsubscribe',
|
||||
},
|
||||
{
|
||||
name: 'Tag Add',
|
||||
value: 'tagAdd',
|
||||
},
|
||||
{
|
||||
name: 'Tag Remove',
|
||||
value: 'tagRemove',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Form ID',
|
||||
name: 'formId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getForms',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
event: [
|
||||
'formSubscribe',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Sequence ID',
|
||||
name: 'courseId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSequences',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
event: [
|
||||
'courseSubscribe',
|
||||
'courseComplete',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Initiating Link',
|
||||
name: 'link',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'The URL of the initiating link',
|
||||
displayOptions: {
|
||||
show: {
|
||||
event: [
|
||||
'linkClick',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Product ID',
|
||||
name: 'productId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
event: [
|
||||
'productPurchase',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Tag ID',
|
||||
name: 'tagId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTags',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
event: [
|
||||
'tagAdd',
|
||||
'tagRemove',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
// Get all the tags to display them to user so that he can
|
||||
// select them easily
|
||||
async getTags(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
|
||||
const { tags } = await convertKitApiRequest.call(this, 'GET', '/tags');
|
||||
|
||||
for (const tag of tags) {
|
||||
|
||||
const tagName = tag.name;
|
||||
|
||||
const tagId = tag.id;
|
||||
|
||||
returnData.push({
|
||||
name: tagName,
|
||||
value: tagId,
|
||||
});
|
||||
}
|
||||
|
||||
return returnData;
|
||||
},
|
||||
// Get all the forms to display them to user so that he can
|
||||
// select them easily
|
||||
async getForms(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
|
||||
const { forms } = await convertKitApiRequest.call(this, 'GET', '/forms');
|
||||
|
||||
for (const form of forms) {
|
||||
|
||||
const formName = form.name;
|
||||
|
||||
const formId = form.id;
|
||||
|
||||
returnData.push({
|
||||
name: formName,
|
||||
value: formId,
|
||||
});
|
||||
}
|
||||
|
||||
return returnData;
|
||||
},
|
||||
|
||||
// Get all the sequences to display them to user so that he can
|
||||
// select them easily
|
||||
async getSequences(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
|
||||
const { courses } = await convertKitApiRequest.call(this, 'GET', '/sequences');
|
||||
|
||||
for (const course of courses) {
|
||||
|
||||
const courseName = course.name;
|
||||
|
||||
const courseId = course.id;
|
||||
|
||||
returnData.push({
|
||||
name: courseName,
|
||||
value: courseId,
|
||||
});
|
||||
}
|
||||
|
||||
return returnData;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// @ts-ignore (because of request)
|
||||
webhookMethods = {
|
||||
default: {
|
||||
async checkExists(this: IHookFunctions): Promise<boolean> {
|
||||
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
|
||||
// THe API does not have an endpoint to list all webhooks
|
||||
|
||||
if (webhookData.webhookId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
async create(this: IHookFunctions): Promise<boolean> {
|
||||
|
||||
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||
|
||||
let event = this.getNodeParameter('event', 0) as string;
|
||||
|
||||
const endpoint = '/automations/hooks';
|
||||
|
||||
if (event === 'purchaseCreate') {
|
||||
event = `purchase.${snakeCase(event)}`;
|
||||
} else {
|
||||
event = `subscriber.${snakeCase(event)}`;
|
||||
}
|
||||
|
||||
const body: IDataObject = {
|
||||
target_url: webhookUrl as string,
|
||||
event: {
|
||||
name: event
|
||||
},
|
||||
};
|
||||
|
||||
if (event === 'subscriber.form_subscribe') {
|
||||
//@ts-ignore
|
||||
body.event['form_id'] = this.getNodeParameter('formId', 0);
|
||||
}
|
||||
|
||||
if (event === 'subscriber.course_subscribe' || event === 'subscriber.course_complete') {
|
||||
//@ts-ignore
|
||||
body.event['sequence_id'] = this.getNodeParameter('courseId', 0);
|
||||
}
|
||||
|
||||
if (event === 'subscriber.link_click') {
|
||||
//@ts-ignore
|
||||
body.event['initiator_value'] = this.getNodeParameter('link', 0);
|
||||
}
|
||||
|
||||
if (event === 'subscriber.product_purchase') {
|
||||
//@ts-ignore
|
||||
body.event['product_id'] = this.getNodeParameter('productId', 0);
|
||||
}
|
||||
|
||||
if (event === 'subscriber.tag_add' || event === 'subscriber.tag_remove') {
|
||||
//@ts-ignore
|
||||
body.event['tag_id'] = this.getNodeParameter('tagId', 0);
|
||||
}
|
||||
|
||||
const webhook = await convertKitApiRequest.call(this, 'POST', endpoint, body);
|
||||
|
||||
if (webhook.rule.id === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
|
||||
webhookData.webhookId = webhook.rule.id as string;
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
async delete(this: IHookFunctions): Promise<boolean> {
|
||||
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
|
||||
if (webhookData.webhookId !== undefined) {
|
||||
|
||||
const endpoint = `/automations/hooks/${webhookData.webhookId}`;
|
||||
|
||||
try {
|
||||
await convertKitApiRequest.call(this, 'DELETE', endpoint);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
delete webhookData.webhookId;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
||||
const returnData: IDataObject[] = [];
|
||||
returnData.push(this.getBodyData());
|
||||
|
||||
return {
|
||||
workflowData: [
|
||||
this.helpers.returnJsonArray(returnData),
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
124
packages/nodes-base/nodes/ConvertKit/CustomFieldDescription.ts
Normal file
124
packages/nodes-base/nodes/ConvertKit/CustomFieldDescription.ts
Normal file
|
@ -0,0 +1,124 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const customFieldOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'customField',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a field',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a field',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all fields',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a field',
|
||||
},
|
||||
],
|
||||
default: 'update',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const customFieldFields = [
|
||||
{
|
||||
displayName: 'Field ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'customField',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The ID of your custom field.',
|
||||
},
|
||||
{
|
||||
displayName: 'Label',
|
||||
name: 'label',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'customField',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The label of the custom field.',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'customField',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'customField',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
] as INodeProperties[];
|
220
packages/nodes-base/nodes/ConvertKit/FormDescription.ts
Normal file
220
packages/nodes-base/nodes/ConvertKit/FormDescription.ts
Normal file
|
@ -0,0 +1,220 @@
|
|||
import {
|
||||
INodeProperties
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const formOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Add Subscriber',
|
||||
value: 'addSubscriber',
|
||||
description: 'Add a subscriber',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all forms',
|
||||
},
|
||||
{
|
||||
name: 'Get Subscriptions',
|
||||
value: 'getSubscriptions',
|
||||
description: 'List subscriptions to a form including subscriber data',
|
||||
},
|
||||
],
|
||||
default: 'addSubscriber',
|
||||
description: 'The operations to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const formFields = [
|
||||
{
|
||||
displayName: 'Form ID',
|
||||
name: 'id',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getForms',
|
||||
},
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
operation: [
|
||||
'addSubscriber',
|
||||
'getSubscriptions',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Form ID.',
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
operation: [
|
||||
'addSubscriber',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: `The subscriber's email address.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
operation: [
|
||||
'addSubscriber',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Custom Fields',
|
||||
name: 'fieldsUi',
|
||||
placeholder: 'Add Custom Field',
|
||||
description: 'Object of key/value pairs for custom fields (the custom field must exist before you can use it here).',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'fieldsValues',
|
||||
displayName: 'Custom Field',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Field Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'last_name',
|
||||
description: `The field's key.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Field Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'Doe',
|
||||
description: 'Value of the field.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'First Name',
|
||||
name: 'firstName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `The subscriber's first name.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
'getSubscriptions',
|
||||
],
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
'getSubscriptions',
|
||||
],
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
operation: [
|
||||
'getSubscriptions',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Subscriber State',
|
||||
name: 'subscriberState',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Active',
|
||||
value: 'active',
|
||||
},
|
||||
{
|
||||
name: 'Cancelled',
|
||||
value: 'cancelled',
|
||||
},
|
||||
],
|
||||
default: 'active',
|
||||
},
|
||||
],
|
||||
description: 'Receive only active subscribers or cancelled subscribers.',
|
||||
},
|
||||
] as INodeProperties[];
|
67
packages/nodes-base/nodes/ConvertKit/GenericFunctions.ts
Normal file
67
packages/nodes-base/nodes/ConvertKit/GenericFunctions.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
import {
|
||||
OptionsWithUri
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
IHookFunctions
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function convertKitApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions,
|
||||
method: string, endpoint: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const credentials = this.getCredentials('convertKitApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
}
|
||||
|
||||
let options: OptionsWithUri = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method,
|
||||
qs,
|
||||
body,
|
||||
uri: uri || `https://api.convertkit.com/v3${endpoint}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
options = Object.assign({}, options, option);
|
||||
|
||||
if (Object.keys(options.body).length === 0) {
|
||||
delete options.body;
|
||||
}
|
||||
|
||||
// it's a webhook so include the api secret on the body
|
||||
if ((options.uri as string).includes('/automations/hooks')) {
|
||||
options.body['api_secret'] = credentials.apiSecret;
|
||||
} else {
|
||||
qs.api_secret = credentials.apiSecret;
|
||||
}
|
||||
|
||||
if (Object.keys(options.qs).length === 0) {
|
||||
delete options.qs;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
return await this.helpers.request!(options);
|
||||
|
||||
} catch (error) {
|
||||
|
||||
let errorMessage = error;
|
||||
|
||||
if (error.response && error.response.body && error.response.body.message) {
|
||||
errorMessage = error.response.body.message;
|
||||
}
|
||||
|
||||
throw new Error(`ConvertKit error response: ${errorMessage}`);
|
||||
}
|
||||
}
|
230
packages/nodes-base/nodes/ConvertKit/SequenceDescription.ts
Normal file
230
packages/nodes-base/nodes/ConvertKit/SequenceDescription.ts
Normal file
|
@ -0,0 +1,230 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const sequenceOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sequence',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Add Subscriber',
|
||||
value: 'addSubscriber',
|
||||
description: 'Add a subscriber',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all sequences',
|
||||
},
|
||||
{
|
||||
name: 'Get Subscriptions',
|
||||
value: 'getSubscriptions',
|
||||
description: 'Get all subscriptions to a sequence including subscriber data',
|
||||
},
|
||||
],
|
||||
default: 'addSubscriber',
|
||||
description: 'The operations to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const sequenceFields = [
|
||||
{
|
||||
displayName: 'Sequence ID',
|
||||
name: 'id',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSequences',
|
||||
},
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sequence',
|
||||
],
|
||||
operation: [
|
||||
'addSubscriber',
|
||||
'getSubscriptions',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Sequence ID.',
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sequence',
|
||||
],
|
||||
operation: [
|
||||
'addSubscriber',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: `The subscriber's email address.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
'getSubscriptions',
|
||||
],
|
||||
resource: [
|
||||
'sequence',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
'getSubscriptions',
|
||||
],
|
||||
resource: [
|
||||
'sequence',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sequence',
|
||||
],
|
||||
operation: [
|
||||
'addSubscriber',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Custom Fields',
|
||||
name: 'fieldsUi',
|
||||
placeholder: 'Add Custom Field',
|
||||
description: 'Object of key/value pairs for custom fields (the custom field must exist before you can use it here).',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'fieldsValues',
|
||||
displayName: 'Custom Field',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Field Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'last_name',
|
||||
description: `The field's key.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Field Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'Doe',
|
||||
description: 'Value of the field.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'First Name',
|
||||
name: 'firstName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `The subscriber's first name.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Tag IDs',
|
||||
name: 'tags',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTags',
|
||||
},
|
||||
default: [],
|
||||
description: 'Tags',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sequence',
|
||||
],
|
||||
operation: [
|
||||
'getSubscriptions',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Subscriber State',
|
||||
name: 'subscriberState',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Active',
|
||||
value: 'active',
|
||||
},
|
||||
{
|
||||
name: 'Cancelled',
|
||||
value: 'cancelled',
|
||||
},
|
||||
],
|
||||
default: 'active',
|
||||
},
|
||||
],
|
||||
description: 'Receive only active subscribers or cancelled subscribers.',
|
||||
},
|
||||
] as INodeProperties[];
|
94
packages/nodes-base/nodes/ConvertKit/TagDescription.ts
Normal file
94
packages/nodes-base/nodes/ConvertKit/TagDescription.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const tagOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'tag',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a tag',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all tags',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const tagFields = [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'tag',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Tag name, multiple can be added separated by comma',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'tag',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'tag',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
] as INodeProperties[];
|
219
packages/nodes-base/nodes/ConvertKit/TagSubscriberDescription.ts
Normal file
219
packages/nodes-base/nodes/ConvertKit/TagSubscriberDescription.ts
Normal file
|
@ -0,0 +1,219 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const tagSubscriberOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'tagSubscriber',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Add',
|
||||
value: 'add',
|
||||
description: 'Add a tag to a subscriber',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'List subscriptions to a tag including subscriber data',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a tag from a subscriber',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const tagSubscriberFields = [
|
||||
{
|
||||
displayName: 'Tag ID',
|
||||
name: 'tagId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTags',
|
||||
},
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'tagSubscriber',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
'getAll',
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'tagSubscriber',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Subscriber email address.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'tagSubscriber',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Custom Fields',
|
||||
name: 'fields',
|
||||
placeholder: 'Add Custom Field',
|
||||
description: 'Object of key/value pairs for custom fields (the custom field must exist before you can use it here).',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'field',
|
||||
displayName: 'Custom Field',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Field Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'last_name',
|
||||
description: `The field's key.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Field Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'Doe',
|
||||
description: 'Value of the field.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'First Name',
|
||||
name: 'firstName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Subscriber first name.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'tagSubscriber',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'tagSubscriber',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'tagSubscriber',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Subscriber State',
|
||||
name: 'subscriberState',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Active',
|
||||
value: 'active',
|
||||
},
|
||||
{
|
||||
name: 'Cancelled',
|
||||
value: 'cancelled',
|
||||
},
|
||||
],
|
||||
default: 'active',
|
||||
},
|
||||
],
|
||||
description: 'Receive only active subscribers or cancelled subscribers.',
|
||||
},
|
||||
] as INodeProperties[];
|
BIN
packages/nodes-base/nodes/ConvertKit/convertKit.png
Normal file
BIN
packages/nodes-base/nodes/ConvertKit/convertKit.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
|
@ -1,6 +1,7 @@
|
|||
import { ITriggerFunctions } from 'n8n-core';
|
||||
import {
|
||||
IBinaryData,
|
||||
IBinaryKeyData,
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
|
@ -8,7 +9,13 @@ import {
|
|||
ITriggerResponse,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { connect as imapConnect, ImapSimple, ImapSimpleOptions, getParts, Message } from 'imap-simple';
|
||||
import { connect as imapConnect, ImapSimple, ImapSimpleOptions, getParts, Message } from 'imap-simple';
|
||||
import {
|
||||
simpleParser,
|
||||
Source as ParserSource,
|
||||
} from 'mailparser';
|
||||
|
||||
import * as lodash from 'lodash';
|
||||
|
||||
export class EmailReadImap implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
|
@ -44,11 +51,11 @@ export class EmailReadImap implements INodeType {
|
|||
options: [
|
||||
{
|
||||
name: 'Mark as read',
|
||||
value: 'read'
|
||||
value: 'read',
|
||||
},
|
||||
{
|
||||
name: 'Nothing',
|
||||
value: 'nothing'
|
||||
value: 'nothing',
|
||||
},
|
||||
],
|
||||
default: 'read',
|
||||
|
@ -59,8 +66,39 @@ export class EmailReadImap implements INodeType {
|
|||
name: 'downloadAttachments',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
format: [
|
||||
'simple',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'If attachments of emails should be downloaded.<br />Only set if needed as it increases processing.',
|
||||
},
|
||||
{
|
||||
displayName: 'Format',
|
||||
name: 'format',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'RAW',
|
||||
value: 'raw',
|
||||
description: 'Returns the full email message data with body content in the raw field as a base64url encoded string; the payload field is not used.'
|
||||
},
|
||||
{
|
||||
name: 'Resolved',
|
||||
value: 'resolved',
|
||||
description: 'Returns the full email with all data resolved and attachments saved as binary data.',
|
||||
},
|
||||
{
|
||||
name: 'Simple',
|
||||
value: 'simple',
|
||||
description: 'Returns the full email; do not use if you wish to gather inline attachments.',
|
||||
},
|
||||
],
|
||||
default: 'simple',
|
||||
description: 'The format to return the message in',
|
||||
},
|
||||
{
|
||||
displayName: 'Property Prefix Name',
|
||||
name: 'dataPropertyAttachmentsPrefixName',
|
||||
|
@ -68,8 +106,25 @@ export class EmailReadImap implements INodeType {
|
|||
default: 'attachment_',
|
||||
displayOptions: {
|
||||
show: {
|
||||
format: [
|
||||
'resolved',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Prefix for name of the binary property to which to<br />write the attachments. An index starting with 0 will be added.<br />So if name is "attachment_" the first attachment is saved to "attachment_0"',
|
||||
},
|
||||
{
|
||||
displayName: 'Property Prefix Name',
|
||||
name: 'dataPropertyAttachmentsPrefixName',
|
||||
type: 'string',
|
||||
default: 'attachment_',
|
||||
displayOptions: {
|
||||
show: {
|
||||
format: [
|
||||
'simple',
|
||||
],
|
||||
downloadAttachments: [
|
||||
true
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -105,7 +160,6 @@ export class EmailReadImap implements INodeType {
|
|||
|
||||
const mailbox = this.getNodeParameter('mailbox') as string;
|
||||
const postProcessAction = this.getNodeParameter('postProcessAction') as string;
|
||||
const downloadAttachments = this.getNodeParameter('downloadAttachments') as boolean;
|
||||
const options = this.getNodeParameter('options', {}) as IDataObject;
|
||||
|
||||
|
||||
|
@ -156,16 +210,26 @@ export class EmailReadImap implements INodeType {
|
|||
|
||||
// Returns all the new unseen messages
|
||||
const getNewEmails = async (connection: ImapSimple): Promise<INodeExecutionData[]> => {
|
||||
|
||||
const format = this.getNodeParameter('format', 0) as string;
|
||||
const searchCriteria = [
|
||||
'UNSEEN'
|
||||
];
|
||||
|
||||
const fetchOptions = {
|
||||
bodies: ['HEADER', 'TEXT'],
|
||||
markSeen: postProcessAction === 'read',
|
||||
struct: true,
|
||||
};
|
||||
let fetchOptions = {};
|
||||
|
||||
if (format === 'simple' || format === 'raw') {
|
||||
fetchOptions = {
|
||||
bodies: ['TEXT', 'HEADER'],
|
||||
markSeen: postProcessAction === 'read',
|
||||
struct: true,
|
||||
};
|
||||
} else if (format === 'resolved') {
|
||||
fetchOptions = {
|
||||
bodies: [''],
|
||||
markSeen: postProcessAction === 'read',
|
||||
struct: true,
|
||||
};
|
||||
}
|
||||
|
||||
const results = await connection.search(searchCriteria, fetchOptions);
|
||||
|
||||
|
@ -174,10 +238,7 @@ export class EmailReadImap implements INodeType {
|
|||
let attachments: IBinaryData[];
|
||||
let propertyName: string;
|
||||
|
||||
let dataPropertyAttachmentsPrefixName = '';
|
||||
if (downloadAttachments === true) {
|
||||
dataPropertyAttachmentsPrefixName = this.getNodeParameter('dataPropertyAttachmentsPrefixName') as string;
|
||||
}
|
||||
|
||||
|
||||
// All properties get by default moved to metadata except the ones
|
||||
// which are defined here which get set on the top level.
|
||||
|
@ -188,45 +249,83 @@ export class EmailReadImap implements INodeType {
|
|||
'subject',
|
||||
'to',
|
||||
];
|
||||
for (const message of results) {
|
||||
|
||||
const parts = getParts(message.attributes.struct!);
|
||||
if (format === 'resolved') {
|
||||
const dataPropertyAttachmentsPrefixName = this.getNodeParameter('dataPropertyAttachmentsPrefixName') as string;
|
||||
|
||||
newEmail = {
|
||||
json: {
|
||||
textHtml: await getText(parts, message, 'html'),
|
||||
textPlain: await getText(parts, message, 'plain'),
|
||||
metadata: {} as IDataObject,
|
||||
for (const message of results) {
|
||||
const part = lodash.find(message.parts, { which: '' });
|
||||
|
||||
if (part === undefined) {
|
||||
throw new Error('Email part could not be parsed.');
|
||||
}
|
||||
};
|
||||
const parsedEmail = await parseRawEmail.call(this, part.body, dataPropertyAttachmentsPrefixName);
|
||||
|
||||
messageHeader = message.parts.filter((part) => {
|
||||
return part.which === 'HEADER';
|
||||
});
|
||||
|
||||
messageBody = messageHeader[0].body;
|
||||
for (propertyName of Object.keys(messageBody)) {
|
||||
if (messageBody[propertyName].length) {
|
||||
if (topLevelProperties.includes(propertyName)) {
|
||||
newEmail.json[propertyName] = messageBody[propertyName][0];
|
||||
} else {
|
||||
(newEmail.json.metadata as IDataObject)[propertyName] = messageBody[propertyName][0];
|
||||
}
|
||||
}
|
||||
newEmails.push(parsedEmail);
|
||||
}
|
||||
} else if (format === 'simple') {
|
||||
const downloadAttachments = this.getNodeParameter('downloadAttachments') as boolean;
|
||||
|
||||
let dataPropertyAttachmentsPrefixName = '';
|
||||
if (downloadAttachments === true) {
|
||||
// Get attachments and add them if any get found
|
||||
attachments = await getAttachment(connection, parts, message);
|
||||
if (attachments.length) {
|
||||
newEmail.binary = {};
|
||||
for (let i = 0; i < attachments.length; i++) {
|
||||
newEmail.binary[`${dataPropertyAttachmentsPrefixName}${i}`] = attachments[i];
|
||||
}
|
||||
}
|
||||
dataPropertyAttachmentsPrefixName = this.getNodeParameter('dataPropertyAttachmentsPrefixName') as string;
|
||||
}
|
||||
|
||||
newEmails.push(newEmail);
|
||||
for (const message of results) {
|
||||
const parts = getParts(message.attributes.struct!);
|
||||
|
||||
newEmail = {
|
||||
json: {
|
||||
textHtml: await getText(parts, message, 'html'),
|
||||
textPlain: await getText(parts, message, 'plain'),
|
||||
metadata: {} as IDataObject,
|
||||
}
|
||||
};
|
||||
|
||||
messageHeader = message.parts.filter((part) => {
|
||||
return part.which === 'HEADER';
|
||||
});
|
||||
|
||||
messageBody = messageHeader[0].body;
|
||||
for (propertyName of Object.keys(messageBody)) {
|
||||
if (messageBody[propertyName].length) {
|
||||
if (topLevelProperties.includes(propertyName)) {
|
||||
newEmail.json[propertyName] = messageBody[propertyName][0];
|
||||
} else {
|
||||
(newEmail.json.metadata as IDataObject)[propertyName] = messageBody[propertyName][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (downloadAttachments === true) {
|
||||
// Get attachments and add them if any get found
|
||||
attachments = await getAttachment(connection, parts, message);
|
||||
if (attachments.length) {
|
||||
newEmail.binary = {};
|
||||
for (let i = 0; i < attachments.length; i++) {
|
||||
newEmail.binary[`${dataPropertyAttachmentsPrefixName}${i}`] = attachments[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newEmails.push(newEmail);
|
||||
}
|
||||
} else if (format === 'raw') {
|
||||
for (const message of results) {
|
||||
const part = lodash.find(message.parts, { which: 'TEXT' });
|
||||
|
||||
if (part === undefined) {
|
||||
throw new Error('Email part could not be parsed.');
|
||||
}
|
||||
// Return base64 string
|
||||
newEmail = {
|
||||
json: {
|
||||
raw: part.body
|
||||
}
|
||||
};
|
||||
|
||||
newEmails.push(newEmail);
|
||||
}
|
||||
}
|
||||
|
||||
return newEmails;
|
||||
|
@ -277,3 +376,32 @@ export class EmailReadImap implements INodeType {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
export async function parseRawEmail(this: ITriggerFunctions, messageEncoded: ParserSource, dataPropertyNameDownload: string): Promise<INodeExecutionData> {
|
||||
const responseData = await simpleParser(messageEncoded);
|
||||
const headers: IDataObject = {};
|
||||
for (const header of responseData.headerLines) {
|
||||
headers[header.key] = header.line;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
responseData.headers = headers;
|
||||
// @ts-ignore
|
||||
responseData.headerLines = undefined;
|
||||
|
||||
const binaryData: IBinaryKeyData = {};
|
||||
if (responseData.attachments) {
|
||||
|
||||
for (let i = 0; i < responseData.attachments.length; i++) {
|
||||
const attachment = responseData.attachments[i];
|
||||
binaryData[`${dataPropertyNameDownload}${i}`] = await this.helpers.prepareBinaryData(attachment.content, attachment.filename, attachment.contentType);
|
||||
}
|
||||
// @ts-ignore
|
||||
responseData.attachments = undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
json: responseData as unknown as IDataObject,
|
||||
binary: Object.keys(binaryData).length ? binaryData : undefined,
|
||||
} as INodeExecutionData;
|
||||
}
|
||||
|
|
|
@ -821,7 +821,7 @@ export class HttpRequest implements INodeType {
|
|||
}
|
||||
else if (oAuth2Api !== undefined) {
|
||||
//@ts-ignore
|
||||
response = await this.helpers.requestOAuth2.call(this, 'oAuth2Api', requestOptions, 'Bearer');
|
||||
response = await this.helpers.requestOAuth2.call(this, 'oAuth2Api', requestOptions, { tokenType: 'Bearer' });
|
||||
} else {
|
||||
response = await this.helpers.request(requestOptions);
|
||||
}
|
||||
|
|
|
@ -40,6 +40,11 @@ export const contactOperations = [
|
|||
value: 'getRecentlyCreatedUpdated',
|
||||
description: 'Get recently created/updated contacts',
|
||||
},
|
||||
{
|
||||
name: 'Search',
|
||||
value: 'search',
|
||||
description: 'Search contacts',
|
||||
},
|
||||
],
|
||||
default: 'upsert',
|
||||
description: 'The operation to perform.',
|
||||
|
@ -166,6 +171,41 @@ export const contactFields = [
|
|||
type: 'string',
|
||||
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: 'getContactCustomProperties',
|
||||
},
|
||||
default: '',
|
||||
description: 'Name of the property.',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Value of the property',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Date of Birth',
|
||||
name: 'dateOfBirth',
|
||||
|
@ -834,4 +874,242 @@ export const contactFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
|
||||
//*-------------------------------------------------------------------------- */
|
||||
/* contact:search */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 250,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Filter Groups',
|
||||
name: 'filterGroupsUi',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
placeholder: 'Add Filter Group',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
required: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'filterGroupsValues',
|
||||
displayName: 'Filter Group',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filtersUi',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
placeholder: 'Add Filter',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
required: false,
|
||||
options: [
|
||||
{
|
||||
name: 'filterValues',
|
||||
displayName: 'Filter',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Property Name',
|
||||
name: 'propertyName',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getContactProperties',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Operator',
|
||||
name: 'operator',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Equal',
|
||||
value: 'EQ',
|
||||
},
|
||||
{
|
||||
name: 'Not Equal',
|
||||
value: 'NEQ',
|
||||
},
|
||||
{
|
||||
name: 'Less Than',
|
||||
value: 'LT',
|
||||
},
|
||||
{
|
||||
name: 'Less Than Or Equal',
|
||||
value: 'LTE',
|
||||
},
|
||||
{
|
||||
name: 'Greater Than',
|
||||
value: 'GT',
|
||||
},
|
||||
{
|
||||
name: 'Greater Than Or Equal',
|
||||
value: 'GTE',
|
||||
},
|
||||
{
|
||||
name: 'Is Known',
|
||||
value: 'HAS_PROPERTY',
|
||||
},
|
||||
{
|
||||
name: 'Is Unknown',
|
||||
value: 'NOT_HAS_PROPERTY',
|
||||
},
|
||||
{
|
||||
name: 'Contains Exactly',
|
||||
value: 'CONSTAIN_TOKEN',
|
||||
},
|
||||
{
|
||||
name: `Doesn't Contain Exactly`,
|
||||
value: 'NOT_CONSTAIN_TOKEN',
|
||||
},
|
||||
],
|
||||
default: 'EQ',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
operator: [
|
||||
'HAS_PROPERTY',
|
||||
'NOT_HAS_PROPERTY',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
description: 'Use filters to limit the results to only CRM objects with matching property values. More info <a href="https://developers.hubspot.com/docs/api/crm/search">here</a>',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
description: `When multiple filters are provided within a filterGroup, they will be combined using a logical AND operator.<br>
|
||||
When multiple filterGroups are provided, they will be combined using a logical OR operator.<br>
|
||||
The system supports a maximum of three filterGroups with up to three filters each.<br>
|
||||
More info <a href="https://developers.hubspot.com/docs/api/crm/search">here</a>`
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Direction',
|
||||
name: 'direction',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'ASC',
|
||||
value: 'ASCENDING',
|
||||
},
|
||||
{
|
||||
name: 'DESC',
|
||||
value: 'DESCENDING',
|
||||
},
|
||||
],
|
||||
default: 'DESCENDING',
|
||||
description: 'Defines the direction in which search results are ordered. Default value is DESC.',
|
||||
},
|
||||
{
|
||||
displayName: 'Fields',
|
||||
name: 'properties',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getContactProperties',
|
||||
},
|
||||
default: [
|
||||
'firstname',
|
||||
'lastname',
|
||||
'email',
|
||||
],
|
||||
description: `Used to include specific company properties in the results.<br/>
|
||||
By default, the results will only include company ID and will not include the values for any properties for your companys.<br/>
|
||||
Including this parameter will include the data for the specified property in the results.<br/>
|
||||
You can include this parameter multiple times to request multiple properties separed by ,.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Perform a text search against all property values for an object type',
|
||||
},
|
||||
{
|
||||
displayName: 'Sort By',
|
||||
name: 'sortBy',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getContactProperties',
|
||||
},
|
||||
default: 'createdate',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
|
|
@ -307,6 +307,25 @@ export class Hubspot implements INodeType {
|
|||
return returnData;
|
||||
},
|
||||
|
||||
// Get all the contact properties to display them to user so that he can
|
||||
// select them easily
|
||||
async getContactCustomProperties(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const endpoint = '/properties/v2/contacts/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;
|
||||
},
|
||||
|
||||
// Get all the contact number of employees options to display them to user so that he can
|
||||
// select them easily
|
||||
async getContactNumberOfEmployees(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
|
@ -1064,6 +1083,20 @@ export class Hubspot implements INodeType {
|
|||
value: additionalFields.workEmail,
|
||||
});
|
||||
}
|
||||
|
||||
if (additionalFields.customPropertiesUi) {
|
||||
const customProperties = (additionalFields.customPropertiesUi as IDataObject).customPropertiesValues as IDataObject[];
|
||||
|
||||
if (customProperties) {
|
||||
for (const customProperty of customProperties) {
|
||||
body.push({
|
||||
property: customProperty.property,
|
||||
value: customProperty.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const endpoint = `/contacts/v1/contact/createOrUpdate/email/${email}`;
|
||||
responseData = await hubspotApiRequest.call(this, 'POST', endpoint, { properties: body });
|
||||
|
||||
|
@ -1166,6 +1199,53 @@ export class Hubspot implements INodeType {
|
|||
const endpoint = `/contacts/v1/contact/vid/${contactId}`;
|
||||
responseData = await hubspotApiRequest.call(this, 'DELETE', endpoint);
|
||||
}
|
||||
//https://developers.hubspot.com/docs/api/crm/search
|
||||
if (operation === 'search') {
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const returnAll = this.getNodeParameter('returnAll', 0) as boolean;
|
||||
const filtersGroupsUi = this.getNodeParameter('filterGroupsUi', i) as IDataObject;
|
||||
const sortBy = additionalFields.sortBy || 'createdate';
|
||||
const direction = additionalFields.direction || 'DESCENDING';
|
||||
|
||||
const body: IDataObject = {
|
||||
sorts: [
|
||||
{
|
||||
propertyName: sortBy,
|
||||
direction,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (filtersGroupsUi) {
|
||||
const filterGroupValues = (filtersGroupsUi as IDataObject).filterGroupsValues as IDataObject[];
|
||||
if (filterGroupValues) {
|
||||
body.filterGroups = [];
|
||||
for (const filterGroupValue of filterGroupValues) {
|
||||
if (filterGroupValue.filtersUi) {
|
||||
const filterValues = (filterGroupValue.filtersUi as IDataObject).filterValues as IDataObject[];
|
||||
if (filterValues) {
|
||||
//@ts-ignore
|
||||
body.filterGroups.push({ filters: filterValues });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(body, additionalFields);
|
||||
|
||||
const endpoint = '/crm/v3/objects/contacts/search';
|
||||
|
||||
if (returnAll) {
|
||||
|
||||
responseData = await hubspotApiRequestAllItems.call(this, 'results', 'POST', endpoint, body, qs);
|
||||
|
||||
} else {
|
||||
qs.count = this.getNodeParameter('limit', 0) as number;
|
||||
responseData = await hubspotApiRequest.call(this, 'POST', endpoint, body, qs);
|
||||
responseData = responseData.results;
|
||||
}
|
||||
}
|
||||
}
|
||||
//https://developers.hubspot.com/docs/methods/companies/companies-overview
|
||||
if (resource === 'company') {
|
||||
|
|
870
packages/nodes-base/nodes/Paddle/CouponDescription.ts
Normal file
870
packages/nodes-base/nodes/Paddle/CouponDescription.ts
Normal file
|
@ -0,0 +1,870 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const couponOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a coupon.',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all coupons.',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a coupon.',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const couponFields = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* coupon:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Coupon Type',
|
||||
name: 'couponType',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
operation: [
|
||||
`create`
|
||||
],
|
||||
jsonParameters: [
|
||||
false
|
||||
]
|
||||
},
|
||||
},
|
||||
default: 'checkout',
|
||||
description: 'Either product (valid for specified products or subscription plans) or checkout (valid for any checkout).',
|
||||
options: [
|
||||
{
|
||||
name: 'Checkout',
|
||||
value: 'checkout'
|
||||
},
|
||||
{
|
||||
name: 'Product',
|
||||
value: 'product'
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
displayName: 'Product IDs',
|
||||
name: 'productIds',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getProducts',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
operation: [
|
||||
`create`
|
||||
],
|
||||
couponType: [
|
||||
'product',
|
||||
],
|
||||
jsonParameters: [
|
||||
false
|
||||
]
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Comma-separated list of product IDs. Required if coupon_type is product.',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Discount Type',
|
||||
name: 'discountType',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
operation: [
|
||||
`create`
|
||||
],
|
||||
jsonParameters: [
|
||||
false
|
||||
]
|
||||
},
|
||||
},
|
||||
default: 'flat',
|
||||
description: 'Either flat or percentage.',
|
||||
options: [
|
||||
{
|
||||
name: 'Flat',
|
||||
value: 'flat'
|
||||
},
|
||||
{
|
||||
name: 'Percentage',
|
||||
value: 'percentage'
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
displayName: 'Discount Amount Currency',
|
||||
name: 'discountAmount',
|
||||
type: 'number',
|
||||
default: '',
|
||||
description: 'Discount amount in currency.',
|
||||
typeOptions: {
|
||||
minValue: 1
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
operation: [
|
||||
`create`
|
||||
],
|
||||
discountType: [
|
||||
'flat',
|
||||
],
|
||||
jsonParameters: [
|
||||
false
|
||||
]
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Discount Amount %',
|
||||
name: 'discountAmount',
|
||||
type: 'number',
|
||||
default: '',
|
||||
description: 'Discount amount in percentage.',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
operation: [
|
||||
`create`
|
||||
],
|
||||
discountType: [
|
||||
'percentage',
|
||||
],
|
||||
jsonParameters: [
|
||||
false
|
||||
]
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Currency',
|
||||
name: 'currency',
|
||||
type: 'options',
|
||||
default: 'EUR',
|
||||
description: 'The currency must match the balance currency specified in your account.',
|
||||
options: [
|
||||
{
|
||||
name: 'ARS',
|
||||
value: 'ARS'
|
||||
},
|
||||
{
|
||||
name: 'AUD',
|
||||
value: 'AUD'
|
||||
},
|
||||
{
|
||||
name: 'BRL',
|
||||
value: 'BRL'
|
||||
},
|
||||
{
|
||||
name: 'CAD',
|
||||
value: 'CAD'
|
||||
},
|
||||
{
|
||||
name: 'CHF',
|
||||
value: 'CHF'
|
||||
},
|
||||
{
|
||||
name: 'CNY',
|
||||
value: 'CNY'
|
||||
},
|
||||
{
|
||||
name: 'CZK',
|
||||
value: 'CZK'
|
||||
},
|
||||
{
|
||||
name: 'DKK',
|
||||
value: 'DKK'
|
||||
},
|
||||
{
|
||||
name: 'EUR',
|
||||
value: 'EUR'
|
||||
},
|
||||
{
|
||||
name: 'GBP',
|
||||
value: 'GBP'
|
||||
},
|
||||
{
|
||||
name: 'HKD',
|
||||
value: 'HKD'
|
||||
},
|
||||
{
|
||||
name: 'HUF',
|
||||
value: 'HUF'
|
||||
},
|
||||
{
|
||||
name: 'INR',
|
||||
value: 'INR'
|
||||
},
|
||||
{
|
||||
name: 'JPY',
|
||||
value: 'JPY'
|
||||
},
|
||||
{
|
||||
name: 'KRW',
|
||||
value: 'KRW'
|
||||
},
|
||||
{
|
||||
name: 'MXN',
|
||||
value: 'MXN'
|
||||
},
|
||||
{
|
||||
name: 'NOK',
|
||||
value: 'NOK'
|
||||
},
|
||||
{
|
||||
name: 'NZD',
|
||||
value: 'NZD'
|
||||
},
|
||||
{
|
||||
name: 'PLN',
|
||||
value: 'PLN'
|
||||
},
|
||||
{
|
||||
name: 'RUB',
|
||||
value: 'RUB'
|
||||
},
|
||||
{
|
||||
name: 'SEK',
|
||||
value: 'SEK'
|
||||
},
|
||||
{
|
||||
name: 'SGD',
|
||||
value: 'SGD'
|
||||
},
|
||||
{
|
||||
name: 'THB',
|
||||
value: 'THB'
|
||||
},
|
||||
{
|
||||
name: 'TWD',
|
||||
value: 'TWD'
|
||||
},
|
||||
{
|
||||
name: 'USD',
|
||||
value: 'USD'
|
||||
},
|
||||
{
|
||||
name: 'ZAR',
|
||||
value: 'ZAR'
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
operation: [
|
||||
`create`
|
||||
],
|
||||
discountType: [
|
||||
'flat',
|
||||
],
|
||||
jsonParameters: [
|
||||
false
|
||||
]
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: ' Additional Fields',
|
||||
name: 'additionalFieldsJson',
|
||||
type: 'json',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
jsonParameters: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
description: `Attributes in JSON form.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
jsonParameters: [
|
||||
false
|
||||
]
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Allowed Uses',
|
||||
name: 'allowedUses',
|
||||
type: 'number',
|
||||
default: 1,
|
||||
description: 'Number of times a coupon can be used in a checkout. This will be set to 999,999 by default, if not specified.',
|
||||
},
|
||||
{
|
||||
displayName: 'Coupon Code',
|
||||
name: 'couponCode',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Will be randomly generated if not specified.',
|
||||
},
|
||||
{
|
||||
displayName: 'Coupon Prefix',
|
||||
name: 'couponPrefix',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Prefix for generated codes. Not valid if coupon_code is specified.',
|
||||
},
|
||||
{
|
||||
displayName: 'Description',
|
||||
name: 'description',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Description of the coupon. This will be displayed in the Seller Dashboard.',
|
||||
},
|
||||
{
|
||||
displayName: 'Expires',
|
||||
name: 'expires',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'The coupon will expire on the date at 00:00:00 UTC.',
|
||||
},
|
||||
{
|
||||
displayName: 'Group',
|
||||
name: 'group',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 50
|
||||
},
|
||||
default: '',
|
||||
description: 'The name of the coupon group this coupon should be assigned to.',
|
||||
},
|
||||
{
|
||||
displayName: 'Number of Coupons',
|
||||
name: 'numberOfCoupons',
|
||||
type: 'number',
|
||||
default: 1,
|
||||
description: 'Number of coupons to generate. Not valid if coupon_code is specified.',
|
||||
},
|
||||
{
|
||||
displayName: 'Recurring',
|
||||
name: 'recurring',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'If the coupon is used on subscription products, this indicates whether the discount should apply to recurring payments after the initial purchase.',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* coupon:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Product ID',
|
||||
name: 'productId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
operation: [
|
||||
`getAll`
|
||||
]
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'The specific product/subscription ID.',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* coupon:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Update by',
|
||||
name: 'updateBy',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
operation: [
|
||||
`update`
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 'couponCode',
|
||||
description: 'Either flat or percentage.',
|
||||
options: [
|
||||
{
|
||||
name: 'Coupon Code',
|
||||
value: 'couponCode'
|
||||
},
|
||||
{
|
||||
name: 'Group',
|
||||
value: 'group'
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
displayName: 'Coupon Code',
|
||||
name: 'couponCode',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
operation: [
|
||||
'update'
|
||||
],
|
||||
updateBy: [
|
||||
'couponCode'
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Identify the coupon to update',
|
||||
},
|
||||
{
|
||||
displayName: 'Group',
|
||||
name: 'group',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
operation: [
|
||||
'update'
|
||||
],
|
||||
updateBy: [
|
||||
'group'
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The name of the group of coupons you want to update.',
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFieldsJson',
|
||||
type: 'json',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
jsonParameters: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
description: `Attributes in JSON form.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'coupon',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
jsonParameters: [
|
||||
false
|
||||
]
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Allowed Uses',
|
||||
name: 'allowedUses',
|
||||
type: 'number',
|
||||
default: 1,
|
||||
description: 'Number of times a coupon can be used in a checkout. This will be set to 999,999 by default, if not specified.',
|
||||
},
|
||||
{
|
||||
displayName: 'Discount',
|
||||
name: 'discount',
|
||||
type: 'fixedCollection',
|
||||
default: 'discountProperties',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Discount Properties',
|
||||
name: 'discountProperties',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Currency',
|
||||
name: 'currency',
|
||||
type: 'options',
|
||||
default: 'EUR',
|
||||
description: 'The currency must match the balance currency specified in your account.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
discountType: [
|
||||
'flat',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'ARS',
|
||||
value: 'ARS'
|
||||
},
|
||||
{
|
||||
name: 'AUD',
|
||||
value: 'AUD'
|
||||
},
|
||||
{
|
||||
name: 'BRL',
|
||||
value: 'BRL'
|
||||
},
|
||||
{
|
||||
name: 'CAD',
|
||||
value: 'CAD'
|
||||
},
|
||||
{
|
||||
name: 'CHF',
|
||||
value: 'CHF'
|
||||
},
|
||||
{
|
||||
name: 'CNY',
|
||||
value: 'CNY'
|
||||
},
|
||||
{
|
||||
name: 'CZK',
|
||||
value: 'CZK'
|
||||
},
|
||||
{
|
||||
name: 'DKK',
|
||||
value: 'DKK'
|
||||
},
|
||||
{
|
||||
name: 'EUR',
|
||||
value: 'EUR'
|
||||
},
|
||||
{
|
||||
name: 'GBP',
|
||||
value: 'GBP'
|
||||
},
|
||||
{
|
||||
name: 'HKD',
|
||||
value: 'HKD'
|
||||
},
|
||||
{
|
||||
name: 'HUF',
|
||||
value: 'HUF'
|
||||
},
|
||||
{
|
||||
name: 'INR',
|
||||
value: 'INR'
|
||||
},
|
||||
{
|
||||
name: 'JPY',
|
||||
value: 'JPY'
|
||||
},
|
||||
{
|
||||
name: 'KRW',
|
||||
value: 'KRW'
|
||||
},
|
||||
{
|
||||
name: 'MXN',
|
||||
value: 'MXN'
|
||||
},
|
||||
{
|
||||
name: 'NOK',
|
||||
value: 'NOK'
|
||||
},
|
||||
{
|
||||
name: 'NZD',
|
||||
value: 'NZD'
|
||||
},
|
||||
{
|
||||
name: 'PLN',
|
||||
value: 'PLN'
|
||||
},
|
||||
{
|
||||
name: 'RUB',
|
||||
value: 'RUB'
|
||||
},
|
||||
{
|
||||
name: 'SEK',
|
||||
value: 'SEK'
|
||||
},
|
||||
{
|
||||
name: 'SGD',
|
||||
value: 'SGD'
|
||||
},
|
||||
{
|
||||
name: 'THB',
|
||||
value: 'THB'
|
||||
},
|
||||
{
|
||||
name: 'TWD',
|
||||
value: 'TWD'
|
||||
},
|
||||
{
|
||||
name: 'USD',
|
||||
value: 'USD'
|
||||
},
|
||||
{
|
||||
name: 'ZAR',
|
||||
value: 'ZAR'
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Discount Amount Currency',
|
||||
name: 'discountAmount',
|
||||
type: 'number',
|
||||
default: '',
|
||||
description: 'Discount amount.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
discountType: [
|
||||
'flat',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 0
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Discount Amount Percentage',
|
||||
name: 'discountAmount',
|
||||
type: 'number',
|
||||
default: '',
|
||||
description: 'Discount amount.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
discountType: [
|
||||
'percentage',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
maxValue: 100
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Discount Type',
|
||||
name: 'discountType',
|
||||
type: 'options',
|
||||
default: 'flat',
|
||||
description: 'Either flat or percentage.',
|
||||
options: [
|
||||
{
|
||||
name: 'Flat',
|
||||
value: 'flat'
|
||||
},
|
||||
{
|
||||
name: 'Percentage',
|
||||
value: 'percentage'
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Expires',
|
||||
name: 'expires',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'The coupon will expire on the date at 00:00:00 UTC.',
|
||||
},
|
||||
{
|
||||
displayName: 'New Coupon Code',
|
||||
name: 'newCouponCode',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'New code to rename the coupon to.',
|
||||
},
|
||||
{
|
||||
displayName: 'New Group Name',
|
||||
name: 'newGroup',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 50
|
||||
},
|
||||
default: '',
|
||||
description: 'New group name to move coupon to.',
|
||||
},
|
||||
{
|
||||
displayName: 'Product IDs',
|
||||
name: 'productIds',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Comma-separated list of products e.g. 499531,1234,123546. If blank then remove associated products.',
|
||||
},
|
||||
{
|
||||
displayName: 'Recurring',
|
||||
name: 'recurring',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'If the coupon is used on subscription products, this indicates whether the discount should apply to recurring payments after the initial purchase.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
] as INodeProperties[];
|
76
packages/nodes-base/nodes/Paddle/GenericFunctions.ts
Normal file
76
packages/nodes-base/nodes/Paddle/GenericFunctions.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function paddleApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, endpoint: string, method: string, body: any = {}, query?: IDataObject, uri?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('paddleApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('Could not retrieve credentials!');
|
||||
}
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
method,
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
uri: `https://vendors.paddle.com/api${endpoint}`,
|
||||
body,
|
||||
json: true
|
||||
};
|
||||
|
||||
body['vendor_id'] = credentials.vendorId;
|
||||
body['vendor_auth_code'] = credentials.vendorAuthCode;
|
||||
try {
|
||||
const response = await this.helpers.request!(options);
|
||||
|
||||
if (!response.success) {
|
||||
throw new Error(`Code: ${response.error.code}. Message: ${response.error.message}`);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
throw new Error(`ERROR: Code: ${error.code}. Message: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function paddleApiRequestAllItems(this: IHookFunctions | IExecuteFunctions, propertyName: string, endpoint: string, method: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
|
||||
body.results_per_page = 200;
|
||||
body.page = 1;
|
||||
|
||||
do {
|
||||
responseData = await paddleApiRequest.call(this, endpoint, method, body, query);
|
||||
returnData.push.apply(returnData, responseData[propertyName]);
|
||||
} while (
|
||||
responseData[propertyName].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;
|
||||
}
|
52
packages/nodes-base/nodes/Paddle/OrderDescription.ts
Normal file
52
packages/nodes-base/nodes/Paddle/OrderDescription.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const orderOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'order',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get an order',
|
||||
}
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const orderFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* order:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Checkout ID',
|
||||
name: 'checkoutId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'order',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The identifier of the buyer’s checkout.',
|
||||
},
|
||||
] as INodeProperties[];
|
517
packages/nodes-base/nodes/Paddle/Paddle.node.ts
Normal file
517
packages/nodes-base/nodes/Paddle/Paddle.node.ts
Normal file
|
@ -0,0 +1,517 @@
|
|||
import {
|
||||
IExecuteFunctions
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
couponFields,
|
||||
couponOperations,
|
||||
} from './CouponDescription';
|
||||
|
||||
import {
|
||||
paddleApiRequest,
|
||||
paddleApiRequestAllItems,
|
||||
validateJSON
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
paymentFields,
|
||||
paymentOperations,
|
||||
} from './PaymentDescription';
|
||||
|
||||
import {
|
||||
planFields,
|
||||
planOperations,
|
||||
} from './PlanDescription';
|
||||
|
||||
import {
|
||||
productFields,
|
||||
productOperations,
|
||||
} from './ProductDescription';
|
||||
|
||||
import {
|
||||
userFields,
|
||||
userOperations,
|
||||
} from './UserDescription';
|
||||
|
||||
// import {
|
||||
// orderOperations,
|
||||
// orderFields,
|
||||
// } from './OrderDescription';
|
||||
|
||||
import * as moment from 'moment';
|
||||
|
||||
export class Paddle implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Paddle',
|
||||
name: 'paddle',
|
||||
icon: 'file:paddle.png',
|
||||
group: ['output'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume Paddle API',
|
||||
defaults: {
|
||||
name: 'Paddle',
|
||||
color: '#45567c',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'paddleApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Coupon',
|
||||
value: 'coupon',
|
||||
},
|
||||
{
|
||||
name: 'Payment',
|
||||
value: 'payment',
|
||||
},
|
||||
{
|
||||
name: 'Plan',
|
||||
value: 'plan',
|
||||
},
|
||||
{
|
||||
name: 'Product',
|
||||
value: 'product',
|
||||
},
|
||||
// {
|
||||
// name: 'Order',
|
||||
// value: 'order',
|
||||
// },
|
||||
{
|
||||
name: 'User',
|
||||
value: 'user',
|
||||
},
|
||||
],
|
||||
default: 'coupon',
|
||||
description: 'Resource to consume.',
|
||||
},
|
||||
// COUPON
|
||||
...couponOperations,
|
||||
...couponFields,
|
||||
// PAYMENT
|
||||
...paymentOperations,
|
||||
...paymentFields,
|
||||
// PLAN
|
||||
...planOperations,
|
||||
...planFields,
|
||||
// PRODUCT
|
||||
...productOperations,
|
||||
...productFields,
|
||||
// ORDER
|
||||
// ...orderOperations,
|
||||
// ...orderFields,
|
||||
// USER
|
||||
...userOperations,
|
||||
...userFields
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* PAYMENT */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
// Get all payment so they can be selected in payment rescheduling
|
||||
async getPayments(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const endpoint = '/2.0/subscription/payments';
|
||||
const paymentResponse = await paddleApiRequest.call(this, endpoint, 'POST', {});
|
||||
|
||||
// Alert user if there's no payments present to be loaded into payments property
|
||||
if (paymentResponse.response === undefined || paymentResponse.response.length === 0) {
|
||||
throw Error('No payments on account.');
|
||||
}
|
||||
|
||||
for (const payment of paymentResponse.response) {
|
||||
const id = payment.id;
|
||||
returnData.push({
|
||||
name: id,
|
||||
value: id,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* PRODUCTS */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
// Get all Products so they can be selected in coupon creation when assigning products
|
||||
async getProducts(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const endpoint = '/2.0/product/get_products';
|
||||
const products = await paddleApiRequest.call(this, endpoint, 'POST', {});
|
||||
|
||||
// Alert user if there's no products present to be loaded into payments property
|
||||
if (products.length === 0) {
|
||||
throw Error('No products on account.');
|
||||
}
|
||||
|
||||
for (const product of products) {
|
||||
const name = product.name;
|
||||
const id = product.id;
|
||||
returnData.push({
|
||||
name,
|
||||
value: id,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const length = items.length as unknown as number;
|
||||
let responseData;
|
||||
const body: IDataObject = {};
|
||||
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 === 'coupon') {
|
||||
if (operation === 'create') {
|
||||
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
|
||||
|
||||
if (jsonParameters) {
|
||||
const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string;
|
||||
|
||||
if (additionalFieldsJson !== '') {
|
||||
if (validateJSON(additionalFieldsJson) !== undefined) {
|
||||
Object.assign(body, JSON.parse(additionalFieldsJson));
|
||||
} else {
|
||||
throw new Error('Additional fields must be a valid JSON');
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
const discountType = this.getNodeParameter('discountType', i) as string;
|
||||
const couponType = this.getNodeParameter('couponType', i) as string;
|
||||
const discountAmount = this.getNodeParameter('discountAmount', i) as number;
|
||||
|
||||
if (couponType === 'product') {
|
||||
body.product_ids = (this.getNodeParameter('productIds', i) as string[]).join();
|
||||
}
|
||||
|
||||
if (discountType === 'flat') {
|
||||
body.currency = this.getNodeParameter('currency', i) as string;
|
||||
}
|
||||
|
||||
body.coupon_type = couponType;
|
||||
body.discount_type = discountType;
|
||||
body.discount_amount = discountAmount;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (additionalFields.allowedUses) {
|
||||
body.allowed_uses = additionalFields.allowedUses as number;
|
||||
}
|
||||
if (additionalFields.couponCode) {
|
||||
body.coupon_code = additionalFields.couponCode as string;
|
||||
}
|
||||
if (additionalFields.couponPrefix) {
|
||||
body.coupon_prefix = additionalFields.couponPrefix as string;
|
||||
}
|
||||
if (additionalFields.expires) {
|
||||
body.expires = moment(additionalFields.expires as Date).format('YYYY-MM-DD') as string;
|
||||
}
|
||||
if (additionalFields.group) {
|
||||
body.group = additionalFields.group as string;
|
||||
}
|
||||
if (additionalFields.recurring) {
|
||||
body.recurring = 1;
|
||||
} else {
|
||||
body.recurring = 0;
|
||||
}
|
||||
if (additionalFields.numberOfCoupons) {
|
||||
body.num_coupons = additionalFields.numberOfCoupons as number;
|
||||
}
|
||||
if (additionalFields.description) {
|
||||
body.description = additionalFields.description as string;
|
||||
}
|
||||
|
||||
const endpoint = '/2.1/product/create_coupon';
|
||||
|
||||
responseData = await paddleApiRequest.call(this, endpoint, 'POST', body);
|
||||
responseData = responseData.response.coupon_codes;
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'getAll') {
|
||||
const productId = this.getNodeParameter('productId', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
const endpoint = '/2.0/product/list_coupons';
|
||||
|
||||
body.product_id = productId as string;
|
||||
|
||||
responseData = await paddleApiRequest.call(this, endpoint, 'POST', body);
|
||||
|
||||
if (returnAll) {
|
||||
responseData = responseData.response;
|
||||
} else {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = responseData.response.splice(0, limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'update') {
|
||||
|
||||
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
|
||||
|
||||
if (jsonParameters) {
|
||||
const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string;
|
||||
|
||||
if (additionalFieldsJson !== '') {
|
||||
if (validateJSON(additionalFieldsJson) !== undefined) {
|
||||
Object.assign(body, JSON.parse(additionalFieldsJson));
|
||||
} else {
|
||||
throw new Error('Additional fields must be a valid JSON');
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
const updateBy = this.getNodeParameter('updateBy', i) as string;
|
||||
|
||||
if (updateBy === 'group') {
|
||||
body.group = this.getNodeParameter('group', i) as string;
|
||||
} else {
|
||||
body.coupon_code = this.getNodeParameter('couponCode', i) as string;
|
||||
}
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (additionalFields.allowedUses) {
|
||||
body.allowed_uses = additionalFields.allowedUses as number;
|
||||
}
|
||||
if (additionalFields.currency) {
|
||||
body.currency = additionalFields.currency as string;
|
||||
}
|
||||
if (additionalFields.newCouponCode) {
|
||||
body.new_coupon_code = additionalFields.newCouponCode as string;
|
||||
}
|
||||
if (additionalFields.expires) {
|
||||
body.expires = moment(additionalFields.expires as Date).format('YYYY-MM-DD') as string;
|
||||
}
|
||||
if (additionalFields.newGroup) {
|
||||
body.new_group = additionalFields.newGroup as string;
|
||||
}
|
||||
if (additionalFields.recurring === true) {
|
||||
body.recurring = 1;
|
||||
} else if (additionalFields.recurring === false) {
|
||||
body.recurring = 0;
|
||||
}
|
||||
if (additionalFields.productIds) {
|
||||
body.product_ids = additionalFields.productIds as number;
|
||||
}
|
||||
if (additionalFields.discountAmount) {
|
||||
body.discount_amount = additionalFields.discountAmount as number;
|
||||
}
|
||||
if (additionalFields.discount) {
|
||||
//@ts-ignore
|
||||
if (additionalFields.discount.discountProperties.discountType === 'percentage') {
|
||||
// @ts-ignore
|
||||
body.discount_amount = additionalFields.discount.discountProperties.discountAmount as number;
|
||||
} else {
|
||||
//@ts-ignore
|
||||
body.currency = additionalFields.discount.discountProperties.currency as string;
|
||||
//@ts-ignore
|
||||
body.discount_amount = additionalFields.discount.discountProperties.discountAmount as number;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const endpoint = '/2.1/product/update_coupon';
|
||||
|
||||
responseData = await paddleApiRequest.call(this, endpoint, 'POST', body);
|
||||
responseData = responseData.response;
|
||||
}
|
||||
}
|
||||
if (resource === 'payment') {
|
||||
if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
|
||||
|
||||
if (jsonParameters) {
|
||||
const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string;
|
||||
|
||||
if (additionalFieldsJson !== '') {
|
||||
if (validateJSON(additionalFieldsJson) !== undefined) {
|
||||
Object.assign(body, JSON.parse(additionalFieldsJson));
|
||||
} else {
|
||||
throw new Error('Additional fields must be a valid JSON');
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (additionalFields.subscriptionId) {
|
||||
body.subscription_id = additionalFields.subscriptionId as number;
|
||||
}
|
||||
if (additionalFields.plan) {
|
||||
body.plan = additionalFields.plan as string;
|
||||
}
|
||||
if (additionalFields.state) {
|
||||
body.state = additionalFields.state as string;
|
||||
}
|
||||
if (additionalFields.isPaid) {
|
||||
body.is_paid = 1;
|
||||
} else {
|
||||
body.is_paid = 0;
|
||||
}
|
||||
if (additionalFields.from) {
|
||||
body.from = moment(additionalFields.from as Date).format('YYYY-MM-DD') as string;
|
||||
}
|
||||
if (additionalFields.to) {
|
||||
body.to = moment(additionalFields.to as Date).format('YYYY-MM-DD') as string;
|
||||
}
|
||||
if (additionalFields.isOneOffCharge) {
|
||||
body.is_one_off_charge = additionalFields.isOneOffCharge as boolean;
|
||||
}
|
||||
}
|
||||
const endpoint = '/2.0/subscription/payments';
|
||||
|
||||
responseData = await paddleApiRequest.call(this, endpoint, 'POST', body);
|
||||
|
||||
if (returnAll) {
|
||||
responseData = responseData.response;
|
||||
} else {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = responseData.response.splice(0, limit);
|
||||
}
|
||||
}
|
||||
if (operation === 'reschedule') {
|
||||
const paymentId = this.getNodeParameter('paymentId', i) as number;
|
||||
const date = this.getNodeParameter('date', i) as Date;
|
||||
|
||||
body.payment_id = paymentId;
|
||||
body.date = body.to = moment(date as Date).format('YYYY-MM-DD') as string;
|
||||
|
||||
const endpoint = '/2.0/subscription/payments_reschedule';
|
||||
|
||||
responseData = await paddleApiRequest.call(this, endpoint, 'POST', body);
|
||||
}
|
||||
}
|
||||
if (resource === 'plan') {
|
||||
if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
const endpoint = '/2.0/subscription/plans';
|
||||
|
||||
responseData = await paddleApiRequest.call(this, endpoint, 'POST', body);
|
||||
|
||||
if (returnAll) {
|
||||
responseData = responseData.response;
|
||||
} else {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = responseData.response.splice(0, limit);
|
||||
}
|
||||
}
|
||||
if (operation === 'get') {
|
||||
const planId = this.getNodeParameter('planId', i) as string;
|
||||
|
||||
body.plan = planId;
|
||||
|
||||
const endpoint = '/2.0/subscription/plans';
|
||||
|
||||
responseData = await paddleApiRequest.call(this, endpoint, 'POST', body);
|
||||
responseData = responseData.response;
|
||||
}
|
||||
}
|
||||
if (resource === 'product') {
|
||||
if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
const endpoint = '/2.0/product/get_products';
|
||||
|
||||
responseData = await paddleApiRequest.call(this, endpoint, 'POST', body);
|
||||
|
||||
if (returnAll) {
|
||||
responseData = responseData.response.products;
|
||||
} else {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = responseData.response.products.splice(0, limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource === 'order') {
|
||||
if (operation === 'get') {
|
||||
const endpoint = '/1.0/order';
|
||||
const checkoutId = this.getNodeParameter('checkoutId', i) as string;
|
||||
|
||||
body.checkout_id = checkoutId;
|
||||
|
||||
responseData = await paddleApiRequest.call(this, endpoint, 'GET', body);
|
||||
}
|
||||
}
|
||||
if (resource === 'user') {
|
||||
if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
|
||||
|
||||
if (jsonParameters) {
|
||||
const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string;
|
||||
|
||||
if (additionalFieldsJson !== '') {
|
||||
if (validateJSON(additionalFieldsJson) !== undefined) {
|
||||
Object.assign(body, JSON.parse(additionalFieldsJson));
|
||||
} else {
|
||||
throw new Error('Additional fields must be a valid JSON');
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (additionalFields.state) {
|
||||
body.state = additionalFields.state as string;
|
||||
}
|
||||
if (additionalFields.planId) {
|
||||
body.plan_id = additionalFields.planId as string;
|
||||
}
|
||||
if (additionalFields.subscriptionId) {
|
||||
body.subscription_id = additionalFields.subscriptionId as string;
|
||||
}
|
||||
}
|
||||
|
||||
const endpoint = '/2.0/subscription/users';
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await paddleApiRequestAllItems.call(this, 'response', endpoint, 'POST', body);
|
||||
} else {
|
||||
body.results_per_page = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await paddleApiRequest.call(this, endpoint, 'POST', body);
|
||||
responseData = responseData.response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else {
|
||||
returnData.push(responseData as unknown as IDataObject);
|
||||
}
|
||||
}
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
248
packages/nodes-base/nodes/Paddle/PaymentDescription.ts
Normal file
248
packages/nodes-base/nodes/Paddle/PaymentDescription.ts
Normal file
|
@ -0,0 +1,248 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const paymentOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'payment',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all payment.',
|
||||
},
|
||||
{
|
||||
name: 'Reschedule',
|
||||
value: 'reschedule',
|
||||
description: 'Reschedule payment.',
|
||||
}
|
||||
],
|
||||
default: 'getAll',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const paymentFields = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* payment:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'payment',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'payment',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'payment',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFieldsJson',
|
||||
type: 'json',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'payment',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
jsonParameters: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
description: `Attributes in JSON form.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'payment',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
jsonParameters: [
|
||||
false
|
||||
]
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Date From',
|
||||
name: 'from',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'payment starting from date.',
|
||||
},
|
||||
{
|
||||
displayName: 'Date To',
|
||||
name: 'to',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'payment up until date.',
|
||||
},
|
||||
{
|
||||
displayName: 'Is Paid',
|
||||
name: 'isPaid',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'payment is paid.',
|
||||
},
|
||||
{
|
||||
displayName: 'Plan ID',
|
||||
name: 'plan',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Filter: The product/plan ID (single or comma-separated values).',
|
||||
},
|
||||
{
|
||||
displayName: 'Subscription ID',
|
||||
name: 'subscriptionId',
|
||||
type: 'number',
|
||||
default: '',
|
||||
description: 'A specific user subscription ID.',
|
||||
},
|
||||
{
|
||||
displayName: 'State',
|
||||
name: 'state',
|
||||
type: 'options',
|
||||
default: 'active',
|
||||
description: 'Filter: The user subscription status. Returns all active, past_due, trialing and paused subscription plans if not specified.',
|
||||
options: [
|
||||
{
|
||||
name: 'Active',
|
||||
value: 'active'
|
||||
},
|
||||
{
|
||||
name: 'Past Due',
|
||||
value: 'past_due'
|
||||
},
|
||||
{
|
||||
name: 'Paused',
|
||||
value: 'paused'
|
||||
},
|
||||
{
|
||||
name: 'Trialing',
|
||||
value: 'trialing'
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
displayName: 'One off charge',
|
||||
name: 'isOneOffCharge',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* payment:reschedule */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Payment ID',
|
||||
name: 'paymentId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getpayment',
|
||||
},
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'payment',
|
||||
],
|
||||
operation: [
|
||||
'reschedule',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The upcoming subscription payment ID.',
|
||||
},
|
||||
{
|
||||
displayName: 'Date',
|
||||
name: 'date',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'payment',
|
||||
],
|
||||
operation: [
|
||||
'reschedule',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Date you want to move the payment to.',
|
||||
},
|
||||
] as INodeProperties[];
|
98
packages/nodes-base/nodes/Paddle/PlanDescription.ts
Normal file
98
packages/nodes-base/nodes/Paddle/PlanDescription.ts
Normal file
|
@ -0,0 +1,98 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const planOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'plan',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a plan.',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all plans.',
|
||||
}
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const planFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* plan:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Plan ID',
|
||||
name: 'planId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'plan',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Filter: The subscription plan ID.',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'plan',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'plan',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
] as INodeProperties[];
|
71
packages/nodes-base/nodes/Paddle/ProductDescription.ts
Normal file
71
packages/nodes-base/nodes/Paddle/ProductDescription.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const productOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'product',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all products.',
|
||||
}
|
||||
],
|
||||
default: 'getAll',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const productFields = [
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'product',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'product',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
] as INodeProperties[];
|
176
packages/nodes-base/nodes/Paddle/UserDescription.ts
Normal file
176
packages/nodes-base/nodes/Paddle/UserDescription.ts
Normal file
|
@ -0,0 +1,176 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const userOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all users',
|
||||
}
|
||||
],
|
||||
default: 'getAll',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const userFields = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
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: 100,
|
||||
required: true,
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 200
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'Number of subscription records to return per page.',
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFieldsJson',
|
||||
type: 'json',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
jsonParameters: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
description: `Attributes in JSON form.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
jsonParameters: [
|
||||
false
|
||||
]
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Plan ID',
|
||||
name: 'planId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Filter: The subscription plan ID.',
|
||||
},
|
||||
{
|
||||
displayName: 'Subscription ID',
|
||||
name: 'subscriptionId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'A specific user subscription ID.',
|
||||
},
|
||||
{
|
||||
displayName: 'State',
|
||||
name: 'state',
|
||||
type: 'options',
|
||||
default: 'active',
|
||||
description: 'Filter: The user subscription status. Returns all active, past_due, trialing and paused subscription plans if not specified.',
|
||||
options: [
|
||||
{
|
||||
name: 'Active',
|
||||
value: 'active'
|
||||
},
|
||||
{
|
||||
name: 'Past Due',
|
||||
value: 'past_due'
|
||||
},
|
||||
{
|
||||
name: 'Paused',
|
||||
value: 'paused'
|
||||
},
|
||||
{
|
||||
name: 'Trialing',
|
||||
value: 'trialing'
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
BIN
packages/nodes-base/nodes/Paddle/paddle.png
Normal file
BIN
packages/nodes-base/nodes/Paddle/paddle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3 KiB |
|
@ -189,16 +189,23 @@ export class Postgres implements INodeType {
|
|||
|
||||
const pgp = pgPromise();
|
||||
|
||||
const config = {
|
||||
const config: IDataObject = {
|
||||
host: credentials.host as string,
|
||||
port: credentials.port as number,
|
||||
database: credentials.database as string,
|
||||
user: credentials.user as string,
|
||||
password: credentials.password as string,
|
||||
ssl: !['disable', undefined].includes(credentials.ssl as string | undefined),
|
||||
sslmode: (credentials.ssl as string) || 'disable',
|
||||
};
|
||||
|
||||
if (credentials.allowUnauthorizedCerts === true) {
|
||||
config.ssl = {
|
||||
rejectUnauthorized: false,
|
||||
};
|
||||
} else {
|
||||
config.ssl = !['disable', undefined].includes(credentials.ssl as string | undefined);
|
||||
config.sslmode = (credentials.ssl as string) || 'disable';
|
||||
}
|
||||
|
||||
const db = pgp(config);
|
||||
|
||||
let returnItems = [];
|
||||
|
|
|
@ -10,10 +10,10 @@ import {
|
|||
|
||||
export async function salesforceApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('salesforceOAuth2Api');
|
||||
const subdomain = (credentials!.accessTokenUrl as string).split('.')[0].split('/')[2];
|
||||
const subdomain = ((credentials!.accessTokenUrl as string).match(/https:\/\/(.+).salesforce\.com/) || [])[1]
|
||||
const options: OptionsWithUri = {
|
||||
method,
|
||||
body,
|
||||
body: method === "GET" ? undefined : body,
|
||||
qs,
|
||||
uri: uri || `https://${subdomain}.salesforce.com/services/data/v39.0${resource}`,
|
||||
json: true
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,66 +1,37 @@
|
|||
import { OptionsWithUri } from 'request';
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
IExecuteSingleFunctions
|
||||
} from 'n8n-core';
|
||||
|
||||
import * as _ from 'lodash';
|
||||
|
||||
export const filterAndExecuteForEachTask = async function(
|
||||
this: IExecuteSingleFunctions,
|
||||
taskCallback: (t: any) => any
|
||||
) {
|
||||
const expression = this.getNodeParameter('expression') as string;
|
||||
const projectId = this.getNodeParameter('project') as number;
|
||||
// Enable regular expressions
|
||||
const reg = new RegExp(expression);
|
||||
const tasks = await todoistApiRequest.call(this, '/tasks', 'GET');
|
||||
const filteredTasks = tasks.filter(
|
||||
// Make sure that project will match no matter what the type is. If project was not selected match all projects
|
||||
(el: any) => (!projectId || el.project_id) && el.content.match(reg)
|
||||
);
|
||||
return {
|
||||
affectedTasks: (
|
||||
await Promise.all(filteredTasks.map((t: any) => taskCallback(t)))
|
||||
)
|
||||
// This makes it more clear and informative. We pass the ID as a convention and content to give the user confirmation that his/her expression works as expected
|
||||
.map(
|
||||
(el, i) =>
|
||||
el || { id: filteredTasks[i].id, content: filteredTasks[i].content }
|
||||
)
|
||||
};
|
||||
};
|
||||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function todoistApiRequest(
|
||||
this:
|
||||
| IHookFunctions
|
||||
| IExecuteFunctions
|
||||
| IExecuteSingleFunctions
|
||||
| ILoadOptionsFunctions,
|
||||
resource: string,
|
||||
method: string,
|
||||
body: any = {},
|
||||
headers?: object
|
||||
): Promise<any> {
|
||||
// tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('todoistApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
}
|
||||
|
||||
const headerWithAuthentication = Object.assign({}, headers, { Authorization: `Bearer ${credentials.apiKey}` });
|
||||
resource: string,
|
||||
body: any = {}, // tslint:disable-line:no-any
|
||||
qs: IDataObject = {},
|
||||
): Promise<any> { // tslint:disable-line:no-any
|
||||
const authentication = this.getNodeParameter('authentication', 0, 'apiKey');
|
||||
|
||||
const endpoint = 'api.todoist.com/rest/v1';
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: headerWithAuthentication,
|
||||
headers: {},
|
||||
method,
|
||||
qs,
|
||||
uri: `https://${endpoint}${resource}`,
|
||||
json: true
|
||||
json: true,
|
||||
};
|
||||
|
||||
if (Object.keys(body).length !== 0) {
|
||||
|
@ -68,13 +39,25 @@ export async function todoistApiRequest(
|
|||
}
|
||||
|
||||
try {
|
||||
return this.helpers.request!(options);
|
||||
if (authentication === 'apiKey') {
|
||||
const credentials = this.getCredentials('todoistApi') as IDataObject;
|
||||
|
||||
//@ts-ignore
|
||||
options.headers['Authorization'] = `Bearer ${credentials.apiKey}`;
|
||||
|
||||
return this.helpers.request!(options);
|
||||
} else {
|
||||
//@ts-ignore
|
||||
return await this.helpers.requestOAuth2.call(this, 'todoistOAuth2Api', options);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
const errorMessage = error.response.body.message || error.response.body.Message;
|
||||
const errorMessage = error.response.body;
|
||||
|
||||
if (errorMessage !== undefined) {
|
||||
throw errorMessage;
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
throw error.response.body;
|
||||
|
||||
throw errorMessage;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
IExecuteSingleFunctions,
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeTypeDescription,
|
||||
|
@ -9,12 +10,11 @@ import {
|
|||
ILoadOptionsFunctions,
|
||||
INodePropertyOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
todoistApiRequest,
|
||||
filterAndExecuteForEachTask,
|
||||
} from './GenericFunctions';
|
||||
|
||||
|
||||
interface IBodyCreateTask {
|
||||
content: string;
|
||||
project_id?: number;
|
||||
|
@ -48,9 +48,44 @@ export class Todoist implements INodeType {
|
|||
{
|
||||
name: 'todoistApi',
|
||||
required: true,
|
||||
}
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: [
|
||||
'apiKey',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'todoistOAuth2Api',
|
||||
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',
|
||||
|
@ -85,24 +120,29 @@ export class Todoist implements INodeType {
|
|||
description: 'Create a new task',
|
||||
},
|
||||
{
|
||||
name: 'Close by ID',
|
||||
value: 'close_id',
|
||||
description: 'Close a task by passing an ID',
|
||||
name: 'Close',
|
||||
value: 'close',
|
||||
description: 'Close a task',
|
||||
},
|
||||
{
|
||||
name: 'Close matching',
|
||||
value: 'close_match',
|
||||
description: 'Close a task by passing a regular expression',
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a task',
|
||||
},
|
||||
{
|
||||
name: 'Delete by ID',
|
||||
value: 'delete_id',
|
||||
description: 'Delete a task by passing an ID',
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a task',
|
||||
},
|
||||
{
|
||||
name: 'Delete matching',
|
||||
value: 'delete_match',
|
||||
description: 'Delete a task by passing a regular expression',
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all tasks',
|
||||
},
|
||||
{
|
||||
name: 'Reopen',
|
||||
value: 'reopen',
|
||||
description: 'Reopen a task',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
|
@ -122,9 +162,7 @@ export class Todoist implements INodeType {
|
|||
],
|
||||
operation: [
|
||||
'create',
|
||||
'close_match',
|
||||
'delete_match',
|
||||
]
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
|
@ -144,7 +182,7 @@ export class Todoist implements INodeType {
|
|||
],
|
||||
operation: [
|
||||
'create',
|
||||
]
|
||||
],
|
||||
},
|
||||
},
|
||||
default: [],
|
||||
|
@ -165,7 +203,7 @@ export class Todoist implements INodeType {
|
|||
],
|
||||
operation: [
|
||||
'create',
|
||||
]
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
|
@ -173,32 +211,27 @@ export class Todoist implements INodeType {
|
|||
description: 'Task content',
|
||||
},
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
displayName: 'Task ID',
|
||||
name: 'taskId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
typeOptions: { rows: 1 },
|
||||
displayOptions: {
|
||||
show: { resource: ['task'], operation: ['close_id', 'delete_id'] }
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
'close',
|
||||
'get',
|
||||
'reopen',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Expression to match',
|
||||
name: 'expression',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
typeOptions: { rows: 1 },
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['task'],
|
||||
operation: ['close_match', 'delete_match']
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
displayName: 'Additional Fields',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Option',
|
||||
|
@ -210,22 +243,10 @@ export class Todoist implements INodeType {
|
|||
],
|
||||
operation: [
|
||||
'create',
|
||||
]
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Priority',
|
||||
name: 'priority',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
numberStepSize: 1,
|
||||
maxValue: 4,
|
||||
minValue: 1,
|
||||
},
|
||||
default: 1,
|
||||
description: 'Task priority from 1 (normal) to 4 (urgent).',
|
||||
},
|
||||
{
|
||||
displayName: 'Due Date Time',
|
||||
name: 'dueDateTime',
|
||||
|
@ -240,24 +261,131 @@ export class Todoist implements INodeType {
|
|||
default: '',
|
||||
description: 'Human defined task due date (ex.: “next Monday”, “Tomorrow”). Value is set using local (not UTC) time.',
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
{
|
||||
displayName: 'Priority',
|
||||
name: 'priority',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
numberStepSize: 1,
|
||||
maxValue: 4,
|
||||
minValue: 1,
|
||||
},
|
||||
default: 1,
|
||||
description: 'Task priority from 1 (normal) to 4 (urgent).',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Option',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Filter',
|
||||
name: 'filter',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Filter by any <a href="https://get.todoist.help/hc/en-us/articles/205248842">supported filter.</a>',
|
||||
},
|
||||
{
|
||||
displayName: 'IDs',
|
||||
name: 'ids',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'A list of the task IDs to retrieve, this should be a comma separated list.',
|
||||
},
|
||||
{
|
||||
displayName: 'Label ID',
|
||||
name: 'labelId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getLabels',
|
||||
},
|
||||
default: {},
|
||||
description: 'Filter tasks by label.',
|
||||
},
|
||||
{
|
||||
displayName: 'Lang',
|
||||
name: 'lang',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'IETF language tag defining what language filter is written in, if differs from default English',
|
||||
},
|
||||
{
|
||||
displayName: 'Project ID',
|
||||
name: 'projectId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getProjects',
|
||||
},
|
||||
default: '',
|
||||
description: 'Filter tasks by project id.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
// Get all the available projects to display them to user so that he can
|
||||
// select them easily
|
||||
async getProjects(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
let projects;
|
||||
try {
|
||||
projects = await todoistApiRequest.call(this, '/projects', 'GET');
|
||||
} catch (err) {
|
||||
throw new Error(`Todoist Error: ${err}`);
|
||||
}
|
||||
const projects = await todoistApiRequest.call(this, 'GET', '/projects');
|
||||
for (const project of projects) {
|
||||
const projectName = project.name;
|
||||
const projectId = project.id;
|
||||
|
@ -275,12 +403,8 @@ export class Todoist implements INodeType {
|
|||
// select them easily
|
||||
async getLabels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
let labels;
|
||||
try {
|
||||
labels = await todoistApiRequest.call(this, '/labels', 'GET');
|
||||
} catch (err) {
|
||||
throw new Error(`Todoist Error: ${err}`);
|
||||
}
|
||||
const labels = await todoistApiRequest.call(this, 'GET', '/labels');
|
||||
|
||||
for (const label of labels) {
|
||||
const labelName = label.name;
|
||||
const labelId = label.id;
|
||||
|
@ -296,67 +420,113 @@ export class Todoist implements INodeType {
|
|||
}
|
||||
};
|
||||
|
||||
async executeSingle(this: IExecuteSingleFunctions): Promise<INodeExecutionData> {
|
||||
const resource = this.getNodeParameter('resource') as string;
|
||||
const operation = this.getNodeParameter('operation') as string;
|
||||
try {
|
||||
return {
|
||||
json: { result: await OPERATIONS[resource]?.[operation]?.bind(this)() }
|
||||
};
|
||||
} catch (err) {
|
||||
return { json: { error: `Todoist Error: ${err.message}` } };
|
||||
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 === 'task') {
|
||||
if (operation === 'create') {
|
||||
//https://developer.todoist.com/rest/v1/#create-a-new-task
|
||||
const content = this.getNodeParameter('content', i) as string;
|
||||
const projectId = this.getNodeParameter('project', i) as number;
|
||||
const labels = this.getNodeParameter('labels', i) as number[];
|
||||
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||
|
||||
const body: IBodyCreateTask = {
|
||||
content,
|
||||
project_id: projectId,
|
||||
priority: (options.priority!) ? parseInt(options.priority as string, 10) : 1,
|
||||
};
|
||||
|
||||
if (options.dueDateTime) {
|
||||
body.due_datetime = options.dueDateTime as string;
|
||||
}
|
||||
|
||||
if (options.dueString) {
|
||||
body.due_string = options.dueString as string;
|
||||
}
|
||||
|
||||
if (labels !== undefined && labels.length !== 0) {
|
||||
body.label_ids = labels;
|
||||
}
|
||||
|
||||
responseData = await todoistApiRequest.call(this, 'POST', '/tasks', body);
|
||||
}
|
||||
if (operation === 'close') {
|
||||
//https://developer.todoist.com/rest/v1/#close-a-task
|
||||
const id = this.getNodeParameter('taskId', i) as string;
|
||||
|
||||
responseData = await todoistApiRequest.call(this, 'POST', `/tasks/${id}/close`);
|
||||
|
||||
responseData = { success: true };
|
||||
|
||||
}
|
||||
if (operation === 'delete') {
|
||||
//https://developer.todoist.com/rest/v1/#delete-a-task
|
||||
const id = this.getNodeParameter('taskId', i) as string;
|
||||
|
||||
responseData = await todoistApiRequest.call(this, 'DELETE', `/tasks/${id}`);
|
||||
|
||||
responseData = { success: true };
|
||||
|
||||
}
|
||||
if (operation === 'get') {
|
||||
//https://developer.todoist.com/rest/v1/#get-an-active-task
|
||||
const id = this.getNodeParameter('taskId', i) as string;
|
||||
|
||||
responseData = await todoistApiRequest.call(this, 'GET', `/tasks/${id}`);
|
||||
}
|
||||
if (operation === 'getAll') {
|
||||
//https://developer.todoist.com/rest/v1/#get-active-tasks
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
const filters = this.getNodeParameter('filters', i) as IDataObject;
|
||||
if (filters.projectId) {
|
||||
qs.project_id = filters.projectId as string;
|
||||
}
|
||||
if (filters.labelId) {
|
||||
qs.label_id = filters.labelId as string;
|
||||
}
|
||||
if (filters.filter) {
|
||||
qs.filter = filters.filter as string;
|
||||
}
|
||||
if (filters.lang) {
|
||||
qs.lang = filters.lang as string;
|
||||
}
|
||||
if (filters.ids) {
|
||||
qs.ids = filters.ids as string;
|
||||
}
|
||||
|
||||
responseData = await todoistApiRequest.call(this, 'GET', '/tasks', {}, qs);
|
||||
|
||||
if (!returnAll) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = responseData.splice(0, limit);
|
||||
}
|
||||
}
|
||||
if (operation === 'reopen') {
|
||||
//https://developer.todoist.com/rest/v1/#get-an-active-task
|
||||
const id = this.getNodeParameter('taskId', i) as string;
|
||||
|
||||
responseData = await todoistApiRequest.call(this, 'POST', `/tasks/${id}/reopen`);
|
||||
|
||||
responseData = { success: true };
|
||||
}
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else {
|
||||
returnData.push(responseData as IDataObject);
|
||||
}
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
||||
|
||||
const OPERATIONS: {
|
||||
[key: string]: { [key: string]: (this: IExecuteSingleFunctions) => any };
|
||||
} = {
|
||||
task: {
|
||||
create(this: IExecuteSingleFunctions) {
|
||||
//https://developer.todoist.com/rest/v1/#create-a-new-task
|
||||
const content = this.getNodeParameter('content') as string;
|
||||
const projectId = this.getNodeParameter('project') as number;
|
||||
const labels = this.getNodeParameter('labels') as number[];
|
||||
const options = this.getNodeParameter('options') as IDataObject;
|
||||
|
||||
const body: IBodyCreateTask = {
|
||||
content,
|
||||
project_id: projectId,
|
||||
priority: (options.priority!) ? parseInt(options.priority as string, 10) : 1,
|
||||
};
|
||||
|
||||
if (options.dueDateTime) {
|
||||
body.due_datetime = options.dueDateTime as string;
|
||||
}
|
||||
|
||||
if (options.dueString) {
|
||||
body.due_string = options.dueString as string;
|
||||
}
|
||||
|
||||
if (labels !== undefined && labels.length !== 0) {
|
||||
body.label_ids = labels;
|
||||
}
|
||||
|
||||
return todoistApiRequest.call(this, '/tasks', 'POST', body);
|
||||
},
|
||||
close_id(this: IExecuteSingleFunctions) {
|
||||
const id = this.getNodeParameter('id') as string;
|
||||
return todoistApiRequest.call(this, `/tasks/${id}/close`, 'POST');
|
||||
},
|
||||
delete_id(this: IExecuteSingleFunctions) {
|
||||
const id = this.getNodeParameter('id') as string;
|
||||
return todoistApiRequest.call(this, `/tasks/${id}`, 'DELETE');
|
||||
},
|
||||
close_match(this) {
|
||||
return filterAndExecuteForEachTask.call(this, t =>
|
||||
todoistApiRequest.call(this, `/tasks/${t.id}/close`, 'POST')
|
||||
);
|
||||
},
|
||||
delete_match(this) {
|
||||
return filterAndExecuteForEachTask.call(this, t =>
|
||||
todoistApiRequest.call(this, `/tasks/${t.id}`, 'DELETE')
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 5.5 KiB |
|
@ -1,4 +1,6 @@
|
|||
import { INodeProperties } from "n8n-workflow";
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const attachmentOperations = [
|
||||
// ----------------------------------
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { INodeProperties } from "n8n-workflow";
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const boardOperations = [
|
||||
// ----------------------------------
|
||||
|
|
177
packages/nodes-base/nodes/Trello/CardCommentDescription.ts
Normal file
177
packages/nodes-base/nodes/Trello/CardCommentDescription.ts
Normal file
|
@ -0,0 +1,177 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const cardCommentOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'cardComment',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a comment on a card',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a comment from a card',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a comment on a card',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const cardCommentFields = [
|
||||
// ----------------------------------
|
||||
// cardComment:create
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Card ID',
|
||||
name: 'cardId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
resource: [
|
||||
'cardComment',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The id of the card',
|
||||
},
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
resource: [
|
||||
'cardComment',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Text of the comment',
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// cardComment:remove
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Card ID',
|
||||
name: 'cardId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
resource: [
|
||||
'cardComment',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The ID of the card.',
|
||||
},
|
||||
{
|
||||
displayName: 'Comment ID',
|
||||
name: 'commentId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
resource: [
|
||||
'cardComment',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The ID of the comment to delete.',
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// cardComment:update
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Card ID',
|
||||
name: 'cardId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
resource: [
|
||||
'cardComment',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The ID of the card to update.',
|
||||
},
|
||||
{
|
||||
displayName: 'Comment ID',
|
||||
name: 'commentId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
resource: [
|
||||
'cardComment',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The ID of the comment to delete.',
|
||||
},
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
resource: [
|
||||
'cardComment',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Text of the comment',
|
||||
},
|
||||
] as INodeProperties[];
|
|
@ -1,4 +1,6 @@
|
|||
import { INodeProperties } from "n8n-workflow";
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const cardOperations = [
|
||||
// ----------------------------------
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { INodeProperties } from "n8n-workflow";
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const checklistOperations = [
|
||||
// ----------------------------------
|
||||
|
|
|
@ -4,9 +4,13 @@ import {
|
|||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import { OptionsWithUri } from 'request';
|
||||
import { IDataObject } from 'n8n-workflow';
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
/**
|
||||
* Make an API request to Trello
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { INodeProperties } from "n8n-workflow";
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const labelOperations = [
|
||||
// ----------------------------------
|
||||
|
|
|
@ -1,42 +1,46 @@
|
|||
import { INodeProperties } from "n8n-workflow";
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const listOperations = [
|
||||
// ----------------------------------
|
||||
// list
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: "Operation",
|
||||
name: "operation",
|
||||
type: "options",
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ["list"]
|
||||
}
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: "Archive",
|
||||
value: "archive",
|
||||
description: "Archive/Unarchive a list"
|
||||
name: 'Archive',
|
||||
value: 'archive',
|
||||
description: 'Archive/Unarchive a list'
|
||||
},
|
||||
{
|
||||
name: "Create",
|
||||
value: "create",
|
||||
description: "Create a new list"
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a new list'
|
||||
},
|
||||
{
|
||||
name: "Get",
|
||||
value: "get",
|
||||
description: "Get the data of a list"
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get the data of a list'
|
||||
},
|
||||
{
|
||||
name: "Update",
|
||||
value: "update",
|
||||
description: "Update a list"
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a list'
|
||||
}
|
||||
],
|
||||
default: "create",
|
||||
description: "The operation to perform."
|
||||
default: 'create',
|
||||
description: 'The operation to perform.'
|
||||
}
|
||||
] as INodeProperties[];
|
||||
|
||||
|
@ -45,92 +49,112 @@ export const listFields = [
|
|||
// list:archive
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: "List ID",
|
||||
name: "id",
|
||||
type: "string",
|
||||
default: "",
|
||||
displayName: 'List ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ["archive"],
|
||||
resource: ["list"]
|
||||
}
|
||||
operation: [
|
||||
'archive',
|
||||
],
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: "The ID of the list to archive or unarchive."
|
||||
description: 'The ID of the list to archive or unarchive.'
|
||||
},
|
||||
{
|
||||
displayName: "Archive",
|
||||
name: "archive",
|
||||
type: "boolean",
|
||||
displayName: 'Archive',
|
||||
name: 'archive',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ["archive"],
|
||||
resource: ["list"]
|
||||
}
|
||||
operation: [
|
||||
'archive',
|
||||
],
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: "If the list should be archived or unarchived."
|
||||
description: 'If the list should be archived or unarchived.'
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// list:create
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: "Board ID",
|
||||
name: "idBoard",
|
||||
type: "string",
|
||||
default: "",
|
||||
displayName: 'Board ID',
|
||||
name: 'idBoard',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ["create"],
|
||||
resource: ["list"]
|
||||
}
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: "The ID of the board the list should be created in"
|
||||
description: 'The ID of the board the list should be created in'
|
||||
},
|
||||
{
|
||||
displayName: "Name",
|
||||
name: "name",
|
||||
type: "string",
|
||||
default: "",
|
||||
placeholder: "My list",
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'My list',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ["create"],
|
||||
resource: ["list"]
|
||||
}
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: "The name of the list"
|
||||
description: 'The name of the list'
|
||||
},
|
||||
{
|
||||
displayName: "Additional Fields",
|
||||
name: "additionalFields",
|
||||
type: "collection",
|
||||
placeholder: "Add Field",
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ["create"],
|
||||
resource: ["list"]
|
||||
}
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: "List Source",
|
||||
name: "idListSource",
|
||||
type: "string",
|
||||
default: "",
|
||||
description: "ID of the list to copy into the new list."
|
||||
displayName: 'List Source',
|
||||
name: 'idListSource',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'ID of the list to copy into the new list.'
|
||||
},
|
||||
{
|
||||
displayName: "Position",
|
||||
name: "pos",
|
||||
type: "string",
|
||||
default: "bottom",
|
||||
displayName: 'Position',
|
||||
name: 'pos',
|
||||
type: 'string',
|
||||
default: 'bottom',
|
||||
description:
|
||||
"The position of the new list. top, bottom, or a positive float."
|
||||
'The position of the new list. top, bottom, or a positive float.'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -139,39 +163,46 @@ export const listFields = [
|
|||
// list:get
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: "List ID",
|
||||
name: "id",
|
||||
type: "string",
|
||||
default: "",
|
||||
displayName: 'List ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ["get"],
|
||||
resource: ["list"]
|
||||
}
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: "The ID of the list to get."
|
||||
description: 'The ID of the list to get.'
|
||||
},
|
||||
{
|
||||
displayName: "Additional Fields",
|
||||
name: "additionalFields",
|
||||
type: "collection",
|
||||
placeholder: "Add Field",
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ["get"],
|
||||
resource: ["list"]
|
||||
}
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: "Fields",
|
||||
name: "fields",
|
||||
type: "string",
|
||||
default: "all",
|
||||
description:
|
||||
'Fields to return. Either "all" or a comma-separated list of fields.'
|
||||
displayName: 'Fields',
|
||||
name: 'fields',
|
||||
type: 'string',
|
||||
default: 'all',
|
||||
description: 'Fields to return. Either "all" or a comma-separated list of fields.'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -180,67 +211,75 @@ export const listFields = [
|
|||
// list:update
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: "List ID",
|
||||
name: "id",
|
||||
type: "string",
|
||||
default: "",
|
||||
displayName: 'List ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ["update"],
|
||||
resource: ["list"]
|
||||
}
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: "The ID of the list to update."
|
||||
description: 'The ID of the list to update.'
|
||||
},
|
||||
{
|
||||
displayName: "Update Fields",
|
||||
name: "updateFields",
|
||||
type: "collection",
|
||||
placeholder: "Add Field",
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ["update"],
|
||||
resource: ["list"]
|
||||
}
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: "Board ID",
|
||||
name: "idBoard",
|
||||
type: "string",
|
||||
default: "",
|
||||
description: "ID of a board the list should be moved to."
|
||||
displayName: 'Board ID',
|
||||
name: 'idBoard',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'ID of a board the list should be moved to.'
|
||||
},
|
||||
{
|
||||
displayName: "Closed",
|
||||
name: "closed",
|
||||
type: "boolean",
|
||||
displayName: 'Closed',
|
||||
name: 'closed',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: "Whether the list is closed."
|
||||
description: 'Whether the list is closed.'
|
||||
},
|
||||
{
|
||||
displayName: "Name",
|
||||
name: "name",
|
||||
type: "string",
|
||||
default: "",
|
||||
description: "New name of the list"
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'New name of the list'
|
||||
},
|
||||
{
|
||||
displayName: "Position",
|
||||
name: "pos",
|
||||
type: "string",
|
||||
default: "bottom",
|
||||
displayName: 'Position',
|
||||
name: 'pos',
|
||||
type: 'string',
|
||||
default: 'bottom',
|
||||
description:
|
||||
"The position of the list. top, bottom, or a positive float."
|
||||
'The position of the list. top, bottom, or a positive float.'
|
||||
},
|
||||
{
|
||||
displayName: "Subscribed",
|
||||
name: "subscribed",
|
||||
type: "boolean",
|
||||
displayName: 'Subscribed',
|
||||
name: 'subscribed',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: "Whether the acting user is subscribed to the list."
|
||||
description: 'Whether the acting user is subscribed to the list.'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { IExecuteFunctions } from "n8n-core";
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeTypeDescription,
|
||||
|
@ -6,13 +9,44 @@ import {
|
|||
INodeType,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { apiRequest } from "./GenericFunctions";
|
||||
import { attachmentOperations, attachmentFields } from './AttachmentDescription';
|
||||
import { boardOperations, boardFields } from './BoardDescription';
|
||||
import { cardOperations, cardFields } from './CardDescription';
|
||||
import { checklistOperations, checklistFields } from './ChecklistDescription';
|
||||
import { labelOperations, labelFields } from './LabelDescription';
|
||||
import { listOperations, listFields } from './ListDescription';
|
||||
import {
|
||||
apiRequest,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
attachmentOperations,
|
||||
attachmentFields,
|
||||
} from './AttachmentDescription';
|
||||
|
||||
import {
|
||||
boardOperations,
|
||||
boardFields,
|
||||
} from './BoardDescription';
|
||||
|
||||
import {
|
||||
cardOperations,
|
||||
cardFields,
|
||||
} from './CardDescription';
|
||||
|
||||
import {
|
||||
cardCommentOperations,
|
||||
cardCommentFields,
|
||||
} from './CardCommentDescription';
|
||||
|
||||
import {
|
||||
checklistOperations,
|
||||
checklistFields,
|
||||
} from './ChecklistDescription';
|
||||
|
||||
import {
|
||||
labelOperations,
|
||||
labelFields,
|
||||
} from './LabelDescription';
|
||||
|
||||
import {
|
||||
listOperations,
|
||||
listFields,
|
||||
} from './ListDescription';
|
||||
|
||||
export class Trello implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
|
@ -33,7 +67,7 @@ export class Trello implements INodeType {
|
|||
{
|
||||
name: 'trelloApi',
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
|
@ -53,6 +87,10 @@ export class Trello implements INodeType {
|
|||
name: 'Card',
|
||||
value: 'card',
|
||||
},
|
||||
{
|
||||
name: 'Card Comment',
|
||||
value: 'cardComment',
|
||||
},
|
||||
{
|
||||
name: 'Checklist',
|
||||
value: 'checklist',
|
||||
|
@ -76,6 +114,7 @@ export class Trello implements INodeType {
|
|||
...attachmentOperations,
|
||||
...boardOperations,
|
||||
...cardOperations,
|
||||
...cardCommentOperations,
|
||||
...checklistOperations,
|
||||
...labelOperations,
|
||||
...listOperations,
|
||||
|
@ -86,15 +125,14 @@ export class Trello implements INodeType {
|
|||
...attachmentFields,
|
||||
...boardFields,
|
||||
...cardFields,
|
||||
...cardCommentFields,
|
||||
...checklistFields,
|
||||
...labelFields,
|
||||
...listFields
|
||||
|
||||
],
|
||||
|
||||
};
|
||||
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
|
@ -236,6 +274,54 @@ export class Trello implements INodeType {
|
|||
throw new Error(`The operation "${operation}" is not known!`);
|
||||
}
|
||||
|
||||
} else if (resource === 'cardComment') {
|
||||
|
||||
if (operation === 'create') {
|
||||
// ----------------------------------
|
||||
// create
|
||||
// ----------------------------------
|
||||
|
||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
||||
|
||||
qs.text = this.getNodeParameter('text', i) as string;
|
||||
|
||||
requestMethod = 'POST';
|
||||
|
||||
endpoint = `cards/${cardId}/actions/comments`;
|
||||
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
// ----------------------------------
|
||||
// delete
|
||||
// ----------------------------------
|
||||
|
||||
requestMethod = 'DELETE';
|
||||
|
||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
||||
|
||||
const commentId = this.getNodeParameter('commentId', i) as string;
|
||||
|
||||
endpoint = `/cards/${cardId}/actions/${commentId}/comments`;
|
||||
|
||||
} else if (operation === 'update') {
|
||||
// ----------------------------------
|
||||
// update
|
||||
// ----------------------------------
|
||||
|
||||
requestMethod = 'PUT';
|
||||
|
||||
const cardId = this.getNodeParameter('cardId', i) as string;
|
||||
|
||||
const commentId = this.getNodeParameter('commentId', i) as string;
|
||||
|
||||
qs.text = this.getNodeParameter('text', i) as string;
|
||||
|
||||
endpoint = `cards/${cardId}/actions/${commentId}/comments`;
|
||||
|
||||
} else {
|
||||
throw new Error(`The operation "${operation}" is not known!`);
|
||||
}
|
||||
|
||||
} else if (resource === 'list') {
|
||||
|
||||
if (operation === 'archive') {
|
||||
|
|
|
@ -23,7 +23,10 @@ export async function zendeskApiRequest(this: IHookFunctions | IExecuteFunctions
|
|||
body,
|
||||
//@ts-ignore
|
||||
uri,
|
||||
json: true
|
||||
json: true,
|
||||
qsStringifyOptions: {
|
||||
arrayFormat: 'brackets',
|
||||
},
|
||||
};
|
||||
|
||||
options = Object.assign({}, options, option);
|
||||
|
@ -56,6 +59,7 @@ export async function zendeskApiRequest(this: IHookFunctions | IExecuteFunctions
|
|||
return await this.helpers.requestOAuth2!.call(this, 'zendeskOAuth2Api', options);
|
||||
}
|
||||
} catch(err) {
|
||||
|
||||
let errorMessage = err.message;
|
||||
if (err.response && err.response.body && err.response.body.error) {
|
||||
errorMessage = err.response.body.error;
|
||||
|
|
692
packages/nodes-base/nodes/Zendesk/UserDescription.ts
Normal file
692
packages/nodes-base/nodes/Zendesk/UserDescription.ts
Normal file
|
@ -0,0 +1,692 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const userOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a user',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a user',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a user',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all users',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a user',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const userFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: `The user's name`,
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Alias',
|
||||
name: 'alis',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `An alias displayed to end users`,
|
||||
},
|
||||
{
|
||||
displayName: 'Custom Role ID',
|
||||
name: 'custom_role_id',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: `A custom role if the user is an agent on the Enterprise plan`,
|
||||
},
|
||||
{
|
||||
displayName: 'Details',
|
||||
name: 'details',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Any details you want to store about the user, such as an address',
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `The user's primary email address`,
|
||||
},
|
||||
{
|
||||
displayName: 'External ID',
|
||||
name: 'externalId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'A unique identifier from another system',
|
||||
},
|
||||
{
|
||||
displayName: 'Locale ID',
|
||||
name: 'locale',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getLocales',
|
||||
},
|
||||
default: '',
|
||||
description: `The user's locale.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Moderator',
|
||||
name: 'moderator',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Designates whether the user has forum moderation capabilities',
|
||||
},
|
||||
{
|
||||
displayName: 'Notes',
|
||||
name: 'notes',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Any notes you want to store about the user',
|
||||
},
|
||||
{
|
||||
displayName: 'Only Private Comments',
|
||||
name: 'only_private_comments',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `true if the user can only create private comments`,
|
||||
},
|
||||
{
|
||||
displayName: 'Organization ID',
|
||||
name: 'organizationId',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: `The id of the user's organization. If the user has more than one organization memberships, the id of the user's default organization`,
|
||||
},
|
||||
{
|
||||
displayName: 'Phone',
|
||||
name: 'phone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `The user's primary phone number.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Report CSV',
|
||||
name: 'report_csv',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not the user can access the CSV report on the Search tab of the Reporting page in the Support admin interface.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Restricted Agent',
|
||||
name: 'restricted_agent',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `If the agent has any restrictions; false for admins and unrestricted agents, true for other agents`,
|
||||
},
|
||||
{
|
||||
displayName: 'Role',
|
||||
name: 'role',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'End User',
|
||||
value: 'end-user',
|
||||
},
|
||||
{
|
||||
name: 'Agent',
|
||||
value: 'agent',
|
||||
},
|
||||
{
|
||||
name: 'Admin',
|
||||
value: 'admin',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: `The user's role`,
|
||||
},
|
||||
{
|
||||
displayName: 'Signature',
|
||||
name: 'signature',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `The user's signature. Only agents and admins can have signatures`,
|
||||
},
|
||||
{
|
||||
displayName: 'Suspended',
|
||||
name: 'suspended',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `If the agent is suspended. Tickets from suspended users are also suspended, and these users cannot sign in to the end user portal`,
|
||||
},
|
||||
{
|
||||
displayName: 'Tags',
|
||||
name: 'tags',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTags',
|
||||
},
|
||||
default: [],
|
||||
description: 'The array of tags applied to this user',
|
||||
},
|
||||
{
|
||||
displayName: 'Ticket Restriction',
|
||||
name: 'ticket_restriction',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Organization',
|
||||
value: 'organization',
|
||||
},
|
||||
{
|
||||
name: 'Groups',
|
||||
value: 'groups',
|
||||
},
|
||||
{
|
||||
name: 'Assigned',
|
||||
value: 'assigned',
|
||||
},
|
||||
{
|
||||
name: 'Requested',
|
||||
value: 'requested',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: `Specifies which tickets the user has access to`,
|
||||
},
|
||||
{
|
||||
displayName: 'Timezone',
|
||||
name: 'time_zone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `The user's time zone.`,
|
||||
},
|
||||
{
|
||||
displayName: 'User Fields',
|
||||
name: 'userFieldsUi',
|
||||
placeholder: 'Add User Field',
|
||||
description: `Values of custom fields in the user's profile.`,
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'userFieldValues',
|
||||
displayName: 'Field',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Field',
|
||||
name: 'field',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUserFields',
|
||||
},
|
||||
default: '',
|
||||
description: 'Name of the field to sort on.',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Value of the field.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Verified',
|
||||
name: 'verified',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `The user's primary identity is verified or not`,
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'User ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'User ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Alias',
|
||||
name: 'alis',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `An alias displayed to end users`,
|
||||
},
|
||||
{
|
||||
displayName: 'Custom Role ID',
|
||||
name: 'custom_role_id',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: `A custom role if the user is an agent on the Enterprise plan`,
|
||||
},
|
||||
{
|
||||
displayName: 'Details',
|
||||
name: 'details',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Any details you want to store about the user, such as an address',
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `The user's primary email address`,
|
||||
},
|
||||
{
|
||||
displayName: 'External ID',
|
||||
name: 'externalId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'A unique identifier from another system',
|
||||
},
|
||||
{
|
||||
displayName: 'Locale ID',
|
||||
name: 'locale',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getLocales',
|
||||
},
|
||||
default: '',
|
||||
description: `The user's locale.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Moderator',
|
||||
name: 'moderator',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Designates whether the user has forum moderation capabilities',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `The user's name`,
|
||||
},
|
||||
{
|
||||
displayName: 'Notes',
|
||||
name: 'notes',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Any notes you want to store about the user',
|
||||
},
|
||||
{
|
||||
displayName: 'Only Private Comments',
|
||||
name: 'only_private_comments',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `true if the user can only create private comments`,
|
||||
},
|
||||
{
|
||||
displayName: 'Organization ID',
|
||||
name: 'organizationId',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: `The id of the user's organization. If the user has more than one organization memberships, the id of the user's default organization`,
|
||||
},
|
||||
{
|
||||
displayName: 'Phone',
|
||||
name: 'phone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `The user's primary phone number.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Report CSV',
|
||||
name: 'report_csv',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not the user can access the CSV report on the Search tab of the Reporting page in the Support admin interface.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Restricted Agent',
|
||||
name: 'restricted_agent',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `If the agent has any restrictions; false for admins and unrestricted agents, true for other agents`,
|
||||
},
|
||||
{
|
||||
displayName: 'Role',
|
||||
name: 'role',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'End User',
|
||||
value: 'end-user',
|
||||
},
|
||||
{
|
||||
name: 'Agent',
|
||||
value: 'agent',
|
||||
},
|
||||
{
|
||||
name: 'Admin',
|
||||
value: 'admin',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: `The user's role`,
|
||||
},
|
||||
{
|
||||
displayName: 'Signature',
|
||||
name: 'signature',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `The user's signature. Only agents and admins can have signatures`,
|
||||
},
|
||||
{
|
||||
displayName: 'Suspended',
|
||||
name: 'suspended',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `If the agent is suspended. Tickets from suspended users are also suspended, and these users cannot sign in to the end user portal`,
|
||||
},
|
||||
{
|
||||
displayName: 'Tags',
|
||||
name: 'tags',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTags',
|
||||
},
|
||||
default: [],
|
||||
description: 'The array of tags applied to this user',
|
||||
},
|
||||
{
|
||||
displayName: 'Ticket Restriction',
|
||||
name: 'ticket_restriction',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Organization',
|
||||
value: 'organization',
|
||||
},
|
||||
{
|
||||
name: 'Groups',
|
||||
value: 'groups',
|
||||
},
|
||||
{
|
||||
name: 'Assigned',
|
||||
value: 'assigned',
|
||||
},
|
||||
{
|
||||
name: 'Requested',
|
||||
value: 'requested',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: `Specifies which tickets the user has access to`,
|
||||
},
|
||||
{
|
||||
displayName: 'Timezone',
|
||||
name: 'time_zone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `The user's time zone.`,
|
||||
},
|
||||
{
|
||||
displayName: 'User Fields',
|
||||
name: 'userFieldsUi',
|
||||
placeholder: 'Add User Field',
|
||||
description: `Values of custom fields in the user's profile.`,
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'userFieldValues',
|
||||
displayName: 'Field',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Field',
|
||||
name: 'field',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUserFields',
|
||||
},
|
||||
default: '',
|
||||
description: 'Name of the field to sort on.',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Value of the field.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Verified',
|
||||
name: 'verified',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `The user's primary identity is verified or not`,
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'User ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'User ID',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Filter',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Roles',
|
||||
name: 'role',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
name: 'End User',
|
||||
value: 'end-user',
|
||||
},
|
||||
{
|
||||
name: 'Agent',
|
||||
value: 'agent',
|
||||
},
|
||||
{
|
||||
name: 'Admin',
|
||||
value: 'admin',
|
||||
},
|
||||
],
|
||||
default: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'User ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'User ID',
|
||||
},
|
||||
] as INodeProperties[];
|
|
@ -27,11 +27,15 @@ import {
|
|||
ticketFieldOperations
|
||||
} from './TicketFieldDescription';
|
||||
|
||||
import {
|
||||
userFields,
|
||||
userOperations
|
||||
} from './UserDescription';
|
||||
|
||||
import {
|
||||
ITicket,
|
||||
IComment,
|
||||
} from './TicketInterface';
|
||||
import { response } from 'express';
|
||||
|
||||
export class Zendesk implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
|
@ -105,6 +109,11 @@ export class Zendesk implements INodeType {
|
|||
value: 'ticketField',
|
||||
description: 'Manage system and custom ticket fields',
|
||||
},
|
||||
{
|
||||
name: 'User',
|
||||
value: 'user',
|
||||
description: 'Manage users',
|
||||
},
|
||||
],
|
||||
default: 'ticket',
|
||||
description: 'Resource to consume.',
|
||||
|
@ -112,9 +121,12 @@ export class Zendesk implements INodeType {
|
|||
// TICKET
|
||||
...ticketOperations,
|
||||
...ticketFields,
|
||||
// TICKET FIELDS
|
||||
// TICKET FIELD
|
||||
...ticketFieldOperations,
|
||||
...ticketFieldFields,
|
||||
// USER
|
||||
...userOperations,
|
||||
...userFields,
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -177,6 +189,38 @@ export class Zendesk implements INodeType {
|
|||
}
|
||||
return returnData;
|
||||
},
|
||||
|
||||
// Get all the locales to display them to user so that he can
|
||||
// select them easily
|
||||
async getLocales(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const locales = await zendeskApiRequestAllItems.call(this, 'locales', 'GET', '/locales');
|
||||
for (const locale of locales) {
|
||||
const localeName = `${locale.locale} - ${locale.name}`;
|
||||
const localeId = locale.locale;
|
||||
returnData.push({
|
||||
name: localeName,
|
||||
value: localeId,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
|
||||
// Get all the user fields to display them to user so that he can
|
||||
// select them easily
|
||||
async getUserFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const fields = await zendeskApiRequestAllItems.call(this, 'user_fields', 'GET', '/user_fields');
|
||||
for (const field of fields) {
|
||||
const fieldName = field.title;
|
||||
const fieldId = field.key;
|
||||
returnData.push({
|
||||
name: fieldName,
|
||||
value: fieldId,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -359,6 +403,87 @@ export class Zendesk implements INodeType {
|
|||
}
|
||||
}
|
||||
}
|
||||
//https://developer.zendesk.com/rest_api/docs/support/users
|
||||
if (resource === 'user') {
|
||||
//https://developer.zendesk.com/rest_api/docs/support/users#create-user
|
||||
if (operation === 'create') {
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
const body: IDataObject = {
|
||||
name,
|
||||
};
|
||||
|
||||
Object.assign(body, additionalFields);
|
||||
|
||||
if (body.userFieldsUi) {
|
||||
const userFields = (body.userFieldsUi as IDataObject).userFieldValues as IDataObject[];
|
||||
if (userFields) {
|
||||
body.user_fields = {};
|
||||
for (const userField of userFields) {
|
||||
//@ts-ignore
|
||||
body.user_fields[userField.field] = userField.value;
|
||||
}
|
||||
delete body.userFieldsUi;
|
||||
}
|
||||
}
|
||||
|
||||
responseData = await zendeskApiRequest.call(this, 'POST', '/users', { user: body });
|
||||
responseData = responseData.user;
|
||||
}
|
||||
//https://developer.zendesk.com/rest_api/docs/support/tickets#update-ticket
|
||||
if (operation === 'update') {
|
||||
const userId = this.getNodeParameter('id', i) as string;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
|
||||
const body: IDataObject = {};
|
||||
|
||||
Object.assign(body, updateFields);
|
||||
|
||||
if (body.userFieldsUi) {
|
||||
const userFields = (body.userFieldsUi as IDataObject).userFieldValues as IDataObject[];
|
||||
if (userFields) {
|
||||
body.user_fields = {};
|
||||
for (const userField of userFields) {
|
||||
//@ts-ignore
|
||||
body.user_fields[userField.field] = userField.value;
|
||||
}
|
||||
delete body.userFieldsUi;
|
||||
}
|
||||
}
|
||||
|
||||
responseData = await zendeskApiRequest.call(this, 'PUT', `/users/${userId}`, { user: body });
|
||||
responseData = responseData.user;
|
||||
}
|
||||
//https://developer.zendesk.com/rest_api/docs/support/users#show-user
|
||||
if (operation === 'get') {
|
||||
const userId = this.getNodeParameter('id', i) as string;
|
||||
responseData = await zendeskApiRequest.call(this, 'GET', `/users/${userId}`, {});
|
||||
responseData = responseData.user;
|
||||
}
|
||||
//https://developer.zendesk.com/rest_api/docs/support/users#list-users
|
||||
if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
const options = this.getNodeParameter('filters', i) as IDataObject;
|
||||
|
||||
Object.assign(qs, options);
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await zendeskApiRequestAllItems.call(this, 'users', 'GET', `/users`, {}, qs);
|
||||
} else {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
qs.per_page = limit;
|
||||
responseData = await zendeskApiRequest.call(this, 'GET', `/users`, {}, qs);
|
||||
responseData = responseData.users;
|
||||
}
|
||||
}
|
||||
//https://developer.zendesk.com/rest_api/docs/support/users#delete-user
|
||||
if (operation === 'delete') {
|
||||
const userId = this.getNodeParameter('id', i) as string;
|
||||
responseData = await zendeskApiRequest.call(this, 'DELETE', `/users/${userId}`, {});
|
||||
responseData = responseData.user;
|
||||
}
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else {
|
||||
|
|
|
@ -375,14 +375,14 @@ export class ZendeskTrigger implements INodeType {
|
|||
displayName: 'All',
|
||||
values: [
|
||||
...conditionFields,
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'any',
|
||||
displayName: 'Any',
|
||||
values: [
|
||||
...conditionFields,
|
||||
]
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -435,17 +435,74 @@ export class ZendeskTrigger implements INodeType {
|
|||
webhookMethods = {
|
||||
default: {
|
||||
async checkExists(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookUrl = this.getNodeWebhookUrl('default') as string;
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
if (webhookData.webhookId === undefined) {
|
||||
const conditions = this.getNodeParameter('conditions') as IDataObject;
|
||||
const conditionsAll = conditions.all as [IDataObject];
|
||||
|
||||
let endpoint = '';
|
||||
const aux: IDataObject = {};
|
||||
const resultAll = [], resultAny = [];
|
||||
|
||||
if (conditionsAll) {
|
||||
for (const conditionAll of conditionsAll) {
|
||||
aux.field = conditionAll.field;
|
||||
aux.operator = conditionAll.operation;
|
||||
if (conditionAll.operation !== 'changed'
|
||||
&& conditionAll.operation !== 'not_changed') {
|
||||
aux.value = conditionAll.value;
|
||||
} else {
|
||||
aux.value = null;
|
||||
}
|
||||
resultAll.push(aux);
|
||||
}
|
||||
}
|
||||
|
||||
const conditionsAny = conditions.any as [IDataObject];
|
||||
if (conditionsAny) {
|
||||
for (const conditionAny of conditionsAny) {
|
||||
aux.field = conditionAny.field;
|
||||
aux.operator = conditionAny.operation;
|
||||
if (conditionAny.operation !== 'changed'
|
||||
&& conditionAny.operation !== 'not_changed') {
|
||||
aux.value = conditionAny.value;
|
||||
} else {
|
||||
aux.value = null;
|
||||
}
|
||||
resultAny.push(aux);
|
||||
}
|
||||
}
|
||||
|
||||
// check if there is a target already created
|
||||
endpoint = `/targets`;
|
||||
const targets = await zendeskApiRequestAllItems.call(this, 'targets', 'GET', endpoint);
|
||||
for (const target of targets) {
|
||||
if (target.target_url === webhookUrl) {
|
||||
webhookData.targetId = target.id.toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// no target was found
|
||||
if (webhookData.targetId === undefined) {
|
||||
return false;
|
||||
}
|
||||
const endpoint = `/triggers/${webhookData.webhookId}`;
|
||||
try {
|
||||
await zendeskApiRequest.call(this, 'GET', endpoint);
|
||||
} catch (e) {
|
||||
return false;
|
||||
|
||||
endpoint = `/triggers/active`;
|
||||
const triggers = await zendeskApiRequestAllItems.call(this, 'triggers', 'GET', endpoint);
|
||||
for (const trigger of triggers) {
|
||||
const toDeleteTriggers = [];
|
||||
// this trigger belong to the current target
|
||||
if (trigger.actions[0].value[0].toString() === webhookData.targetId?.toString()) {
|
||||
toDeleteTriggers.push(trigger.id);
|
||||
}
|
||||
// delete all trigger attach to this target;
|
||||
if (toDeleteTriggers.length !== 0) {
|
||||
await zendeskApiRequest.call(this, 'DELETE', '/triggers/destroy_many', {}, { ids: toDeleteTriggers.join(',') } );
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
async create(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookUrl = this.getNodeWebhookUrl('default') as string;
|
||||
|
@ -509,8 +566,8 @@ export class ZendeskTrigger implements INodeType {
|
|||
{
|
||||
field: 'notification_target',
|
||||
value: [],
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const bodyTarget: IDataObject = {
|
||||
|
@ -523,9 +580,21 @@ export class ZendeskTrigger implements INodeType {
|
|||
content_type: 'application/json',
|
||||
},
|
||||
};
|
||||
const { target } = await zendeskApiRequest.call(this, 'POST', '/targets', bodyTarget);
|
||||
let target: IDataObject = {};
|
||||
|
||||
// if target id exists but trigger does not then reuse the target
|
||||
// and create the trigger else create both
|
||||
if (webhookData.targetId !== undefined) {
|
||||
target.id = webhookData.targetId;
|
||||
} else {
|
||||
target = await zendeskApiRequest.call(this, 'POST', '/targets', bodyTarget);
|
||||
target = target.target as IDataObject;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
bodyTrigger.trigger.actions[0].value = [target.id, JSON.stringify(message)];
|
||||
|
||||
//@ts-ignore
|
||||
const { trigger } = await zendeskApiRequest.call(this, 'POST', '/triggers', bodyTrigger);
|
||||
webhookData.webhookId = trigger.id;
|
||||
webhookData.targetId = target.id;
|
||||
|
|
|
@ -47,6 +47,8 @@
|
|||
"dist/credentials/ClockifyApi.credentials.js",
|
||||
"dist/credentials/CockpitApi.credentials.js",
|
||||
"dist/credentials/CodaApi.credentials.js",
|
||||
"dist/credentials/ContentfulApi.credentials.js",
|
||||
"dist/credentials/ConvertKitApi.credentials.js",
|
||||
"dist/credentials/CopperApi.credentials.js",
|
||||
"dist/credentials/CalendlyApi.credentials.js",
|
||||
"dist/credentials/CustomerIoApi.credentials.js",
|
||||
|
@ -120,6 +122,7 @@
|
|||
"dist/credentials/OAuth1Api.credentials.js",
|
||||
"dist/credentials/OAuth2Api.credentials.js",
|
||||
"dist/credentials/OpenWeatherMapApi.credentials.js",
|
||||
"dist/credentials/PaddleApi.credentials.js",
|
||||
"dist/credentials/PagerDutyApi.credentials.js",
|
||||
"dist/credentials/PagerDutyOAuth2Api.credentials.js",
|
||||
"dist/credentials/PayPalApi.credentials.js",
|
||||
|
@ -148,6 +151,7 @@
|
|||
"dist/credentials/SurveyMonkeyOAuth2Api.credentials.js",
|
||||
"dist/credentials/TelegramApi.credentials.js",
|
||||
"dist/credentials/TodoistApi.credentials.js",
|
||||
"dist/credentials/TodoistOAuth2Api.credentials.js",
|
||||
"dist/credentials/TravisCiApi.credentials.js",
|
||||
"dist/credentials/TrelloApi.credentials.js",
|
||||
"dist/credentials/TwilioApi.credentials.js",
|
||||
|
@ -204,6 +208,9 @@
|
|||
"dist/nodes/Clockify/ClockifyTrigger.node.js",
|
||||
"dist/nodes/Cockpit/Cockpit.node.js",
|
||||
"dist/nodes/Coda/Coda.node.js",
|
||||
"dist/nodes/Contentful/Contentful.node.js",
|
||||
"dist/nodes/ConvertKit/ConvertKit.node.js",
|
||||
"dist/nodes/ConvertKit/ConvertKitTrigger.node.js",
|
||||
"dist/nodes/Copper/CopperTrigger.node.js",
|
||||
"dist/nodes/CrateDb/CrateDb.node.js",
|
||||
"dist/nodes/Cron.node.js",
|
||||
|
@ -287,6 +294,7 @@
|
|||
"dist/nodes/NextCloud/NextCloud.node.js",
|
||||
"dist/nodes/NoOp.node.js",
|
||||
"dist/nodes/OpenWeatherMap.node.js",
|
||||
"dist/nodes/Paddle/Paddle.node.js",
|
||||
"dist/nodes/PagerDuty/PagerDuty.node.js",
|
||||
"dist/nodes/PayPal/PayPal.node.js",
|
||||
"dist/nodes/PayPal/PayPalTrigger.node.js",
|
||||
|
|
Loading…
Reference in a new issue