mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
Merge branch 'master' of github.com:n8n-io/n8n into unify-execution-id
This commit is contained in:
commit
d433661ae9
38
.github/workflows/docker-images-nightly.yml
vendored
Normal file
38
.github/workflows/docker-images-nightly.yml
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
name: Docker Nightly Image CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 2 * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
-
|
||||||
|
name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
-
|
||||||
|
name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
-
|
||||||
|
name: Build and push
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./docker/images/n8n-custom/Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: n8nio/n8n:nightly
|
11
.github/workflows/docker-images-rpi.yml
vendored
11
.github/workflows/docker-images-rpi.yml
vendored
|
@ -26,8 +26,9 @@ jobs:
|
||||||
- name: Run Buildx (push image)
|
- name: Run Buildx (push image)
|
||||||
if: success()
|
if: success()
|
||||||
run: |
|
run: |
|
||||||
docker buildx build --platform linux/arm/v7 --build-arg N8N_VERSION=${{steps.vars.outputs.tag}} -t n8nio/n8n:${{steps.vars.outputs.tag}}-rpi --output type=image,push=true docker/images/n8n-rpi
|
docker buildx build \
|
||||||
- name: Tag Docker image with latest
|
--platform linux/arm/v7 \
|
||||||
run: docker tag n8nio/n8n:${{steps.vars.outputs.tag}}-rpi n8nio/n8n:latest-rpi
|
--build-arg N8N_VERSION=${{steps.vars.outputs.tag}} \
|
||||||
- name: Push docker images of latest
|
-t ${{ secrets.DOCKER_USERNAME }}/n8n:${{steps.vars.outputs.tag}}-rpi \
|
||||||
run: docker push n8nio/n8n:latest-rpi
|
-t ${{ secrets.DOCKER_USERNAME }}/n8n:latest-rpi \
|
||||||
|
--output type=image,push=true docker/images/n8n-rpi
|
||||||
|
|
8
.github/workflows/docker-images.yml
vendored
8
.github/workflows/docker-images.yml
vendored
|
@ -28,7 +28,7 @@ jobs:
|
||||||
- name: Push docker images of latest
|
- name: Push docker images of latest
|
||||||
run: docker push n8nio/n8n:latest
|
run: docker push n8nio/n8n:latest
|
||||||
|
|
||||||
- name: Build the Docker image of version (Ubuntu)
|
- name: Build the Docker image of version (Debian)
|
||||||
run: docker build --build-arg N8N_VERSION=${{steps.vars.outputs.tag}} -t n8nio/n8n:${{steps.vars.outputs.tag}}-ubuntu docker/images/n8n-ubuntu
|
run: docker build --build-arg N8N_VERSION=${{steps.vars.outputs.tag}} -t n8nio/n8n:${{steps.vars.outputs.tag}}-debian docker/images/n8n-debian
|
||||||
- name: Push Docker image of version (Ubuntu)
|
- name: Push Docker image of version (Debian)
|
||||||
run: docker push n8nio/n8n:${{steps.vars.outputs.tag}}-ubuntu
|
run: docker push n8nio/n8n:${{steps.vars.outputs.tag}}-debian
|
||||||
|
|
|
@ -16,7 +16,7 @@ received or lost a star.
|
||||||
|
|
||||||
## Available integrations
|
## Available integrations
|
||||||
|
|
||||||
n8n has 170+ different nodes to automate workflows. The list can be found on: [https://n8n.io/nodes](https://n8n.io/nodes)
|
n8n has 200+ different nodes to automate workflows. The list can be found on: [https://n8n.io/nodes](https://n8n.io/nodes)
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
@ -91,6 +91,6 @@ Have you found a bug :bug: ? Or maybe you have a nice feature :sparkles: to cont
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
n8n is [fair-code](http://faircode.io) licensed under [**Apache 2.0 with Commons Clause**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md).
|
n8n is [fair-code](http://faircode.io) distributed under [**Apache 2.0 with Commons Clause**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md) license.
|
||||||
|
|
||||||
Additional information about license can be found in the [FAQ](https://docs.n8n.io/#/faq?id=license).
|
Additional information about license can be found in the [FAQ](https://docs.n8n.io/#/faq?id=license).
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
## n8n - Ubuntu Docker Image
|
## n8n - Debian Docker Image
|
||||||
|
|
||||||
Dockerfile to build n8n with Ubuntu.
|
Dockerfile to build n8n with Debian.
|
||||||
|
|
||||||
For information about how to run n8n with Docker check the generic
|
For information about how to run n8n with Docker check the generic
|
||||||
[Docker-Readme](https://github.com/n8n-io/n8n/tree/master/docker/images/n8n/README.md)
|
[Docker-Readme](https://github.com/n8n-io/n8n/tree/master/docker/images/n8n/README.md)
|
||||||
|
@ -10,12 +10,12 @@ For information about how to run n8n with Docker check the generic
|
||||||
docker build --build-arg N8N_VERSION=<VERSION> -t n8nio/n8n:<VERSION> .
|
docker build --build-arg N8N_VERSION=<VERSION> -t n8nio/n8n:<VERSION> .
|
||||||
|
|
||||||
# For example:
|
# For example:
|
||||||
docker build --build-arg N8N_VERSION=0.43.0 -t n8nio/n8n:0.43.0-ubuntu .
|
docker build --build-arg N8N_VERSION=0.43.0 -t n8nio/n8n:0.43.0-debian .
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
docker run -it --rm \
|
docker run -it --rm \
|
||||||
--name n8n \
|
--name n8n \
|
||||||
-p 5678:5678 \
|
-p 5678:5678 \
|
||||||
n8nio/n8n:0.43.0-ubuntu
|
n8nio/n8n:0.43.0-debian
|
||||||
```
|
```
|
|
@ -17,5 +17,6 @@ docker build --build-arg N8N_VERSION=0.43.0 -t n8nio/n8n:0.43.0-rpi .
|
||||||
docker run -it --rm \
|
docker run -it --rm \
|
||||||
--name n8n \
|
--name n8n \
|
||||||
-p 5678:5678 \
|
-p 5678:5678 \
|
||||||
|
-v ~/.n8n:/home/node/.n8n \
|
||||||
n8nio/n8n:0.70.0-rpi
|
n8nio/n8n:0.70.0-rpi
|
||||||
```
|
```
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
n8n is a free and open [fair-code](http://faircode.io) licensed node based Workflow Automation Tool. It can be self-hosted, easily extended, and so also used with internal tools.
|
n8n is a free and open [fair-code](http://faircode.io) distributed node based Workflow Automation Tool. It can be self-hosted, easily extended, and so also used with internal tools.
|
||||||
|
|
||||||
<a href="https://raw.githubusercontent.com/n8n-io/n8n/master/assets/n8n-screenshot.png"><img src="https://raw.githubusercontent.com/n8n-io/n8n/master/assets/n8n-screenshot.png" width="550" alt="n8n.io - Screenshot"></a>
|
<a href="https://raw.githubusercontent.com/n8n-io/n8n/master/assets/n8n-screenshot.png"><img src="https://raw.githubusercontent.com/n8n-io/n8n/master/assets/n8n-screenshot.png" width="550" alt="n8n.io - Screenshot"></a>
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ Slack notification every time a Github repository received or lost a star.
|
||||||
|
|
||||||
## Available integrations
|
## Available integrations
|
||||||
|
|
||||||
n8n has 100+ different nodes to automate workflows. The list can be found on: [https://n8n.io/nodes](https://n8n.io/nodes)
|
n8n has 200+ different nodes to automate workflows. The list can be found on: [https://n8n.io/nodes](https://n8n.io/nodes)
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
@ -305,6 +305,6 @@ Before you upgrade to the latest version make sure to check here if there are an
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
n8n is [fair-code](http://faircode.io) licensed under [**Apache 2.0 with Commons Clause**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md)
|
n8n is [fair-code](http://faircode.io) distributed under [**Apache 2.0 with Commons Clause**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md) license
|
||||||
|
|
||||||
Additional information about license can be found in the [FAQ](https://docs.n8n.io/#/faq?id=license)
|
Additional information about license can be found in the [FAQ](https://docs.n8n.io/#/faq?id=license)
|
||||||
|
|
|
@ -6,6 +6,8 @@ if [ -d /root/.n8n ] ; then
|
||||||
ln -s /root/.n8n /home/node/
|
ln -s /root/.n8n /home/node/
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
chown -R node /home/node
|
||||||
|
|
||||||
if [ "$#" -gt 0 ]; then
|
if [ "$#" -gt 0 ]; then
|
||||||
# Got started with arguments
|
# Got started with arguments
|
||||||
exec su-exec node "$@"
|
exec su-exec node "$@"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
n8n is a free and open [fair-code](http://faircode.io) licensed node based Workflow Automation Tool. It can be self-hosted, easily extended, and so also used with internal tools.
|
n8n is a free and open [fair-code](http://faircode.io) distributed node based Workflow Automation Tool. It can be self-hosted, easily extended, and so also used with internal tools.
|
||||||
|
|
||||||
<a href="https://raw.githubusercontent.com/n8n-io/n8n/master/assets/n8n-screenshot.png"><img src="https://raw.githubusercontent.com/n8n-io/n8n/master/assets/n8n-screenshot.png" width="550" alt="n8n.io - Screenshot"></a>
|
<a href="https://raw.githubusercontent.com/n8n-io/n8n/master/assets/n8n-screenshot.png"><img src="https://raw.githubusercontent.com/n8n-io/n8n/master/assets/n8n-screenshot.png" width="550" alt="n8n.io - Screenshot"></a>
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ Slack notification every time a Github repository received or lost a star.
|
||||||
|
|
||||||
## Available integrations
|
## Available integrations
|
||||||
|
|
||||||
n8n has 100+ different nodes to automate workflows. The list can be found on: [https://n8n.io/nodes](https://n8n.io/nodes)
|
n8n has 200+ different nodes to automate workflows. The list can be found on: [https://n8n.io/nodes](https://n8n.io/nodes)
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
@ -100,7 +100,7 @@ Before you upgrade to the latest version make sure to check here if there are an
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
n8n is [fair-code](http://faircode.io) licensed under [**Apache 2.0 with Commons Clause**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md)
|
n8n is [fair-code](http://faircode.io) distributed under [**Apache 2.0 with Commons Clause**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md) license
|
||||||
|
|
||||||
Additional information about license can be found in the [FAQ](https://docs.n8n.io/#/faq?id=license)
|
Additional information about license can be found in the [FAQ](https://docs.n8n.io/#/faq?id=license)
|
||||||
|
|
||||||
|
|
|
@ -121,10 +121,16 @@ export class Start extends Command {
|
||||||
const { flags } = this.parse(Start);
|
const { flags } = this.parse(Start);
|
||||||
|
|
||||||
// Wrap that the process does not close but we can still use async
|
// Wrap that the process does not close but we can still use async
|
||||||
(async () => {
|
await (async () => {
|
||||||
try {
|
try {
|
||||||
// Start directly with the init of the database to improve startup time
|
// Start directly with the init of the database to improve startup time
|
||||||
const startDbInitPromise = Db.init();
|
const startDbInitPromise = Db.init().catch(error => {
|
||||||
|
console.error(`There was an error initializing DB: ${error.message}`);
|
||||||
|
|
||||||
|
processExistCode = 1;
|
||||||
|
// @ts-ignore
|
||||||
|
process.emit('SIGINT');
|
||||||
|
});
|
||||||
|
|
||||||
// Make sure the settings exist
|
// Make sure the settings exist
|
||||||
const userSettings = await UserSettings.prepareUserSettings();
|
const userSettings = await UserSettings.prepareUserSettings();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "n8n",
|
"name": "n8n",
|
||||||
"version": "0.96.0",
|
"version": "0.99.1",
|
||||||
"description": "n8n Workflow Automation Tool",
|
"description": "n8n Workflow Automation Tool",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"homepage": "https://n8n.io",
|
"homepage": "https://n8n.io",
|
||||||
|
@ -103,9 +103,9 @@
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"mongodb": "^3.5.5",
|
"mongodb": "^3.5.5",
|
||||||
"mysql2": "~2.1.0",
|
"mysql2": "~2.1.0",
|
||||||
"n8n-core": "~0.54.0",
|
"n8n-core": "~0.56.0",
|
||||||
"n8n-editor-ui": "~0.66.0",
|
"n8n-editor-ui": "~0.68.1",
|
||||||
"n8n-nodes-base": "~0.91.0",
|
"n8n-nodes-base": "~0.94.0",
|
||||||
"n8n-workflow": "~0.47.0",
|
"n8n-workflow": "~0.47.0",
|
||||||
"oauth-1.0a": "^2.2.6",
|
"oauth-1.0a": "^2.2.6",
|
||||||
"open": "^7.0.0",
|
"open": "^7.0.0",
|
||||||
|
|
|
@ -64,7 +64,11 @@ export function sendSuccessResponse(res: Response, data: any, raw?: boolean, res
|
||||||
}
|
}
|
||||||
|
|
||||||
if (raw === true) {
|
if (raw === true) {
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
res.send(data);
|
||||||
|
} else {
|
||||||
res.json(data);
|
res.json(data);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
res.json({
|
res.json({
|
||||||
data,
|
data,
|
||||||
|
|
|
@ -44,9 +44,11 @@ import * as config from '../config';
|
||||||
|
|
||||||
import { LessThanOrEqual } from "typeorm";
|
import { LessThanOrEqual } from "typeorm";
|
||||||
|
|
||||||
|
const ERROR_TRIGGER_TYPE = config.get('nodes.errorTriggerType') as string;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if there was an error and if errorWorkflow is defined. If so it collects
|
* Checks if there was an error and if errorWorkflow or a trigger is defined. If so it collects
|
||||||
* all the data and executes it
|
* all the data and executes it
|
||||||
*
|
*
|
||||||
* @param {IWorkflowBase} workflowData The workflow which got executed
|
* @param {IWorkflowBase} workflowData The workflow which got executed
|
||||||
|
@ -55,14 +57,14 @@ import { LessThanOrEqual } from "typeorm";
|
||||||
* @param {string} [executionId] The id the execution got saved as
|
* @param {string} [executionId] The id the execution got saved as
|
||||||
*/
|
*/
|
||||||
function executeErrorWorkflow(workflowData: IWorkflowBase, fullRunData: IRun, mode: WorkflowExecuteMode, executionId?: string, retryOf?: string): void {
|
function executeErrorWorkflow(workflowData: IWorkflowBase, fullRunData: IRun, mode: WorkflowExecuteMode, executionId?: string, retryOf?: string): void {
|
||||||
// Check if there was an error and if so if an errorWorkflow is set
|
// Check if there was an error and if so if an errorWorkflow or a trigger is set
|
||||||
|
|
||||||
let pastExecutionUrl: string | undefined = undefined;
|
let pastExecutionUrl: string | undefined = undefined;
|
||||||
if (executionId !== undefined) {
|
if (executionId !== undefined) {
|
||||||
pastExecutionUrl = `${WebhookHelpers.getWebhookBaseUrl()}execution/${executionId}`;
|
pastExecutionUrl = `${WebhookHelpers.getWebhookBaseUrl()}execution/${executionId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fullRunData.data.resultData.error !== undefined && workflowData.settings !== undefined && workflowData.settings.errorWorkflow) {
|
if (fullRunData.data.resultData.error !== undefined) {
|
||||||
const workflowErrorData = {
|
const workflowErrorData = {
|
||||||
execution: {
|
execution: {
|
||||||
id: executionId,
|
id: executionId,
|
||||||
|
@ -77,8 +79,16 @@ function executeErrorWorkflow(workflowData: IWorkflowBase, fullRunData: IRun, mo
|
||||||
name: workflowData.name,
|
name: workflowData.name,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Run the error workflow
|
// Run the error workflow
|
||||||
|
// To avoid an infinite loop do not run the error workflow again if the error-workflow itself failed and it is its own error-workflow.
|
||||||
|
if (workflowData.settings !== undefined && workflowData.settings.errorWorkflow && !(mode === 'error' && workflowData.id && workflowData.settings.errorWorkflow.toString() === workflowData.id.toString())) {
|
||||||
|
// If a specific error workflow is set run only that one
|
||||||
WorkflowHelpers.executeErrorWorkflow(workflowData.settings.errorWorkflow as string, workflowErrorData);
|
WorkflowHelpers.executeErrorWorkflow(workflowData.settings.errorWorkflow as string, workflowErrorData);
|
||||||
|
} else if (mode !== 'error' && workflowData.id !== undefined && workflowData.nodes.some((node) => node.type === ERROR_TRIGGER_TYPE)) {
|
||||||
|
// If the workflow contains
|
||||||
|
WorkflowHelpers.executeErrorWorkflow(workflowData.id.toString(), workflowErrorData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "n8n-core",
|
"name": "n8n-core",
|
||||||
"version": "0.54.0",
|
"version": "0.56.0",
|
||||||
"description": "Core functionality of n8n",
|
"description": "Core functionality of n8n",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"homepage": "https://n8n.io",
|
"homepage": "https://n8n.io",
|
||||||
|
|
|
@ -172,6 +172,10 @@ export function requestOAuth2(this: IAllExecuteFunctions, credentialsType: strin
|
||||||
client_secret: credentials.clientSecret as string,
|
client_secret: credentials.clientSecret as string,
|
||||||
};
|
};
|
||||||
tokenRefreshOptions.body = body;
|
tokenRefreshOptions.body = body;
|
||||||
|
// Override authorization property so the credentails are not included in it
|
||||||
|
tokenRefreshOptions.headers = {
|
||||||
|
Authorization: '',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const newToken = await token.refresh(tokenRefreshOptions);
|
const newToken = await token.refresh(tokenRefreshOptions);
|
||||||
|
@ -241,6 +245,15 @@ export function requestOAuth1(this: IAllExecuteFunctions, credentialsType: strin
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
requestOptions.data = { ...requestOptions.qs, ...requestOptions.form };
|
requestOptions.data = { ...requestOptions.qs, ...requestOptions.form };
|
||||||
|
|
||||||
|
// Fixes issue that OAuth1 library only works with "url" property and not with "uri"
|
||||||
|
// @ts-ignore
|
||||||
|
if (requestOptions.uri && !requestOptions.url) {
|
||||||
|
// @ts-ignore
|
||||||
|
requestOptions.url = requestOptions.uri;
|
||||||
|
// @ts-ignore
|
||||||
|
delete requestOptions.uri;
|
||||||
|
}
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
requestOptions.headers = oauth.toHeader(oauth.authorize(requestOptions, token));
|
requestOptions.headers = oauth.toHeader(oauth.authorize(requestOptions, token));
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "n8n-editor-ui",
|
"name": "n8n-editor-ui",
|
||||||
"version": "0.66.0",
|
"version": "0.68.1",
|
||||||
"description": "Workflow Editor UI for n8n",
|
"description": "Workflow Editor UI for n8n",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"homepage": "https://n8n.io",
|
"homepage": "https://n8n.io",
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
"flatted": "^2.0.0",
|
"flatted": "^2.0.0",
|
||||||
"jquery": "^3.4.1",
|
"jquery": "^3.4.1",
|
||||||
"jshint": "^2.9.7",
|
"jshint": "^2.9.7",
|
||||||
"jsplumb": "^2.10.0",
|
"jsplumb": "2.15.4",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.set": "^4.3.2",
|
"lodash.set": "^4.3.2",
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
"uuid": "^8.1.0",
|
"uuid": "^8.1.0",
|
||||||
"vue": "^2.6.9",
|
"vue": "^2.6.9",
|
||||||
"vue-cli-plugin-webpack-bundle-analyzer": "^2.0.0",
|
"vue-cli-plugin-webpack-bundle-analyzer": "^2.0.0",
|
||||||
"vue-json-tree": "^0.4.1",
|
"vue-json-pretty": "^1.7.1",
|
||||||
"vue-prism-editor": "^0.3.0",
|
"vue-prism-editor": "^0.3.0",
|
||||||
"vue-router": "^3.0.6",
|
"vue-router": "^3.0.6",
|
||||||
"vue-template-compiler": "^2.5.17",
|
"vue-template-compiler": "^2.5.17",
|
||||||
|
|
|
@ -11,10 +11,10 @@
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
<div class="binary-data-window-wrapper">
|
<div class="binary-data-window-wrapper">
|
||||||
<div v-if="binaryData === null">
|
<div v-if="!binaryData">
|
||||||
Data to display did not get found
|
Data to display did not get found
|
||||||
</div>
|
</div>
|
||||||
<embed :src="'data:' + binaryData.mimeType + ';base64,' + binaryData.data" class="binary-data" :class="embedClass"/>
|
<embed v-else :src="'data:' + binaryData.mimeType + ';base64,' + binaryData.data" class="binary-data" :class="embedClass"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,8 +10,12 @@
|
||||||
<div v-if="multipleValues === true">
|
<div v-if="multipleValues === true">
|
||||||
<div v-for="(value, index) in values[property.name]" :key="property.name + index" class="parameter-item">
|
<div v-for="(value, index) in values[property.name]" :key="property.name + index" class="parameter-item">
|
||||||
<div class="parameter-item-wrapper">
|
<div class="parameter-item-wrapper">
|
||||||
<div class="delete-option" title="Delete" v-if="!isReadOnly">
|
<div class="delete-option" v-if="!isReadOnly">
|
||||||
<font-awesome-icon icon="trash" class="reset-icon clickable" title="Delete Item" @click="deleteOption(property.name, index)" />
|
<font-awesome-icon icon="trash" class="reset-icon clickable" title="Delete Item" @click="deleteOption(property.name, index)" />
|
||||||
|
<div v-if="sortable">
|
||||||
|
<font-awesome-icon v-if="index !== 0" icon="angle-up" class="clickable" title="Move up" @click="moveOptionUp(property.name, index)" />
|
||||||
|
<font-awesome-icon v-if="index !== (values[property.name].length -1)" icon="angle-down" class="clickable" title="Move down" @click="moveOptionDown(property.name, index)" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<parameter-input-list :parameters="property.values" :nodeValues="nodeValues" :path="getPropertyPath(property.name, index)" :hideDelete="true" @valueChanged="valueChanged" />
|
<parameter-input-list :parameters="property.values" :nodeValues="nodeValues" :path="getPropertyPath(property.name, index)" :hideDelete="true" @valueChanged="valueChanged" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,7 +23,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="parameter-item">
|
<div v-else class="parameter-item">
|
||||||
<div class="parameter-item-wrapper">
|
<div class="parameter-item-wrapper">
|
||||||
<div class="delete-option" title="Delete" v-if="!isReadOnly">
|
<div class="delete-option" v-if="!isReadOnly">
|
||||||
<font-awesome-icon icon="trash" class="reset-icon clickable" title="Delete Item" @click="deleteOption(property.name)" />
|
<font-awesome-icon icon="trash" class="reset-icon clickable" title="Delete Item" @click="deleteOption(property.name)" />
|
||||||
</div>
|
</div>
|
||||||
<parameter-input-list :parameters="property.values" :nodeValues="nodeValues" :path="getPropertyPath(property.name)" class="parameter-item" @valueChanged="valueChanged" :hideDelete="true" />
|
<parameter-input-list :parameters="property.values" :nodeValues="nodeValues" :path="getPropertyPath(property.name)" class="parameter-item" @valueChanged="valueChanged" :hideDelete="true" />
|
||||||
|
@ -111,6 +115,9 @@ export default mixins(genericHelpers)
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
|
sortable (): string {
|
||||||
|
return this.parameter.typeOptions && this.parameter.typeOptions.sortable;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
deleteOption (optionName: string, index?: number) {
|
deleteOption (optionName: string, index?: number) {
|
||||||
|
@ -133,6 +140,26 @@ export default mixins(genericHelpers)
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
|
moveOptionDown (optionName: string, index: number) {
|
||||||
|
this.values[optionName].splice(index + 1, 0, this.values[optionName].splice(index, 1)[0]);
|
||||||
|
|
||||||
|
const parameterData = {
|
||||||
|
name: this.getPropertyPath(optionName),
|
||||||
|
value: this.values[optionName],
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$emit('valueChanged', parameterData);
|
||||||
|
},
|
||||||
|
moveOptionUp (optionName: string, index: number) {
|
||||||
|
this.values[optionName].splice(index - 1, 0, this.values[optionName].splice(index, 1)[0]);
|
||||||
|
|
||||||
|
const parameterData = {
|
||||||
|
name: this.getPropertyPath(optionName),
|
||||||
|
value: this.values[optionName],
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$emit('valueChanged', parameterData);
|
||||||
|
},
|
||||||
optionSelected (optionName: string) {
|
optionSelected (optionName: string) {
|
||||||
const option = this.getOptionProperties(optionName);
|
const option = this.getOptionProperties(optionName);
|
||||||
if (option === undefined) {
|
if (option === undefined) {
|
||||||
|
|
|
@ -10,8 +10,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-for="(value, index) in values" :key="index" class="duplicate-parameter-item" :class="parameter.type">
|
<div v-for="(value, index) in values" :key="index" class="duplicate-parameter-item" :class="parameter.type">
|
||||||
<div class="delete-item clickable" v-if="!isReadOnly" title="Delete Item" @click="deleteItem(index)">
|
<div class="delete-item clickable" v-if="!isReadOnly">
|
||||||
<font-awesome-icon icon="trash" />
|
<font-awesome-icon icon="trash" title="Delete Item" @click="deleteItem(index)" />
|
||||||
|
<div v-if="sortable">
|
||||||
|
<font-awesome-icon v-if="index !== 0" icon="angle-up" class="clickable" title="Move up" @click="moveOptionUp(index)" />
|
||||||
|
<font-awesome-icon v-if="index !== (values.length -1)" icon="angle-down" class="clickable" title="Move down" @click="moveOptionDown(index)" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="parameter.type === 'collection'">
|
<div v-if="parameter.type === 'collection'">
|
||||||
<collection-parameter :parameter="parameter" :values="value" :nodeValues="nodeValues" :path="getPath(index)" :hideDelete="hideDelete" @valueChanged="valueChanged" />
|
<collection-parameter :parameter="parameter" :values="value" :nodeValues="nodeValues" :path="getPath(index)" :hideDelete="hideDelete" @valueChanged="valueChanged" />
|
||||||
|
@ -67,6 +71,9 @@ export default mixins(genericHelpers)
|
||||||
hideDelete (): boolean {
|
hideDelete (): boolean {
|
||||||
return this.parameter.options.length === 1;
|
return this.parameter.options.length === 1;
|
||||||
},
|
},
|
||||||
|
sortable (): string {
|
||||||
|
return this.parameter.typeOptions && this.parameter.typeOptions.sortable;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addItem () {
|
addItem () {
|
||||||
|
@ -97,6 +104,26 @@ export default mixins(genericHelpers)
|
||||||
getPath (index?: number): string {
|
getPath (index?: number): string {
|
||||||
return this.path + (index !== undefined ? `[${index}]` : '');
|
return this.path + (index !== undefined ? `[${index}]` : '');
|
||||||
},
|
},
|
||||||
|
moveOptionDown (index: number) {
|
||||||
|
this.values.splice(index + 1, 0, this.values.splice(index, 1)[0]);
|
||||||
|
|
||||||
|
const parameterData = {
|
||||||
|
name: this.path,
|
||||||
|
value: this.values,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$emit('valueChanged', parameterData);
|
||||||
|
},
|
||||||
|
moveOptionUp (index: number) {
|
||||||
|
this.values.splice(index - 1, 0, this.values.splice(index, 1)[0]);
|
||||||
|
|
||||||
|
const parameterData = {
|
||||||
|
name: this.path,
|
||||||
|
value: this.values,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$emit('valueChanged', parameterData);
|
||||||
|
},
|
||||||
valueChanged (parameterData: IUpdateInformation) {
|
valueChanged (parameterData: IUpdateInformation) {
|
||||||
this.$emit('valueChanged', parameterData);
|
this.$emit('valueChanged', parameterData);
|
||||||
},
|
},
|
||||||
|
@ -125,6 +152,7 @@ export default mixins(genericHelpers)
|
||||||
top: .3em;
|
top: .3em;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
color: #f56c6c;
|
color: #f56c6c;
|
||||||
|
width: 15px;
|
||||||
|
|
||||||
:hover {
|
:hover {
|
||||||
color: #ff0000;
|
color: #ff0000;
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<div class="node-executing-info" title="Node is executing">
|
<div class="node-executing-info" title="Node is executing">
|
||||||
<font-awesome-icon icon="sync-alt" spin />
|
<font-awesome-icon icon="sync-alt" spin />
|
||||||
</div>
|
</div>
|
||||||
<div class="node-options" v-if="!isReadOnly">
|
<div class="node-options no-select-on-click" v-if="!isReadOnly">
|
||||||
<div v-touch:tap="deleteNode" class="option" title="Delete Node" >
|
<div v-touch:tap="deleteNode" class="option" title="Delete Node" >
|
||||||
<font-awesome-icon icon="trash" />
|
<font-awesome-icon icon="trash" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -62,6 +62,21 @@
|
||||||
<el-radio-button label="Binary" v-if="binaryData.length !== 0"></el-radio-button>
|
<el-radio-button label="Binary" v-if="binaryData.length !== 0"></el-radio-button>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="select-button" v-if="displayMode === 'JSON' && state.path !== deselectedPlaceholder">
|
||||||
|
<el-dropdown trigger="click" @command="handleCopyClick">
|
||||||
|
<span class="el-dropdown-link">
|
||||||
|
<el-button class="retry-button" circle type="text" size="small" title="Copy">
|
||||||
|
<font-awesome-icon icon="copy" />
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
<el-dropdown-menu slot="dropdown">
|
||||||
|
<el-dropdown-item :command="{command: 'itemPath'}">Copy Item Path</el-dropdown-item>
|
||||||
|
<el-dropdown-item :command="{command: 'parameterPath'}">Copy Parameter Path</el-dropdown-item>
|
||||||
|
<el-dropdown-item :command="{command: 'value'}">Copy Value</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</el-dropdown>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="data-display-content">
|
<div class="data-display-content">
|
||||||
<span v-if="node && workflowRunData !== null && workflowRunData.hasOwnProperty(node.name)">
|
<span v-if="node && workflowRunData !== null && workflowRunData.hasOwnProperty(node.name)">
|
||||||
|
@ -104,10 +119,18 @@
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<json-tree
|
<vue-json-pretty
|
||||||
v-else-if="displayMode === 'JSON'"
|
v-else-if="displayMode === 'JSON'"
|
||||||
:data="jsonData"
|
:data="jsonData"
|
||||||
:level="10"
|
:deep="10"
|
||||||
|
v-model="state.path"
|
||||||
|
:showLine="true"
|
||||||
|
:showLength="true"
|
||||||
|
selectableType="single"
|
||||||
|
path=""
|
||||||
|
:highlightSelectedNode="true"
|
||||||
|
:selectOnClickNode="true"
|
||||||
|
@click="dataItemClicked"
|
||||||
class="json-data"
|
class="json-data"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -171,8 +194,8 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
// @ts-ignore
|
//@ts-ignore
|
||||||
import JsonTree from 'vue-json-tree';
|
import VueJsonPretty from 'vue-json-pretty';
|
||||||
import {
|
import {
|
||||||
GenericValue,
|
GenericValue,
|
||||||
IBinaryData,
|
IBinaryData,
|
||||||
|
@ -200,13 +223,18 @@ import {
|
||||||
|
|
||||||
import BinaryDataDisplay from '@/components/BinaryDataDisplay.vue';
|
import BinaryDataDisplay from '@/components/BinaryDataDisplay.vue';
|
||||||
|
|
||||||
|
import { copyPaste } from '@/components/mixins/copyPaste';
|
||||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||||
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
||||||
import { workflowRun } from '@/components/mixins/workflowRun';
|
import { workflowRun } from '@/components/mixins/workflowRun';
|
||||||
|
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
|
|
||||||
|
// A path that does not exist so that nothing is selected by default
|
||||||
|
const deselectedPlaceholder = '_!^&*';
|
||||||
|
|
||||||
export default mixins(
|
export default mixins(
|
||||||
|
copyPaste,
|
||||||
genericHelpers,
|
genericHelpers,
|
||||||
nodeHelpers,
|
nodeHelpers,
|
||||||
workflowRun,
|
workflowRun,
|
||||||
|
@ -215,13 +243,18 @@ export default mixins(
|
||||||
name: 'RunData',
|
name: 'RunData',
|
||||||
components: {
|
components: {
|
||||||
BinaryDataDisplay,
|
BinaryDataDisplay,
|
||||||
JsonTree,
|
VueJsonPretty,
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
binaryDataPreviewActive: false,
|
binaryDataPreviewActive: false,
|
||||||
dataSize: 0,
|
dataSize: 0,
|
||||||
|
deselectedPlaceholder,
|
||||||
displayMode: 'Table',
|
displayMode: 'Table',
|
||||||
|
state: {
|
||||||
|
value: '' as object | number | string,
|
||||||
|
path: deselectedPlaceholder,
|
||||||
|
},
|
||||||
runIndex: 0,
|
runIndex: 0,
|
||||||
showData: false,
|
showData: false,
|
||||||
outputIndex: 0,
|
outputIndex: 0,
|
||||||
|
@ -380,18 +413,6 @@ export default mixins(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getOutputName (outputIndex: number) {
|
|
||||||
if (this.node === null) {
|
|
||||||
return outputIndex + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nodeType = this.$store.getters.nodeType(this.node.type);
|
|
||||||
if (!nodeType.hasOwnProperty('outputNames') || nodeType.outputNames.length <= outputIndex) {
|
|
||||||
return outputIndex + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodeType.outputNames[outputIndex];
|
|
||||||
},
|
|
||||||
closeBinaryDataDisplay () {
|
closeBinaryDataDisplay () {
|
||||||
this.binaryDataDisplayVisible = false;
|
this.binaryDataDisplayVisible = false;
|
||||||
this.binaryDataDisplayData = null;
|
this.binaryDataDisplayData = null;
|
||||||
|
@ -465,7 +486,9 @@ export default mixins(
|
||||||
this.$store.commit('setWorkflowExecutionData', null);
|
this.$store.commit('setWorkflowExecutionData', null);
|
||||||
this.updateNodesExecutionIssues();
|
this.updateNodesExecutionIssues();
|
||||||
},
|
},
|
||||||
// displayBinaryData (binaryData: IBinaryData) {
|
dataItemClicked (path: string, data: object | number | string) {
|
||||||
|
this.state.value = data;
|
||||||
|
},
|
||||||
displayBinaryData (index: number, key: string) {
|
displayBinaryData (index: number, key: string) {
|
||||||
this.binaryDataDisplayVisible = true;
|
this.binaryDataDisplayVisible = true;
|
||||||
|
|
||||||
|
@ -477,6 +500,85 @@ export default mixins(
|
||||||
key,
|
key,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
getOutputName (outputIndex: number) {
|
||||||
|
if (this.node === null) {
|
||||||
|
return outputIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeType = this.$store.getters.nodeType(this.node.type);
|
||||||
|
if (!nodeType.hasOwnProperty('outputNames') || nodeType.outputNames.length <= outputIndex) {
|
||||||
|
return outputIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeType.outputNames[outputIndex];
|
||||||
|
},
|
||||||
|
convertPath (path: string): string {
|
||||||
|
// TODO: That can for sure be done fancier but for now it works
|
||||||
|
const placeholder = '*___~#^#~___*';
|
||||||
|
let inBrackets = path.match(/\[(.*?)\]/g);
|
||||||
|
|
||||||
|
if (inBrackets === null) {
|
||||||
|
inBrackets = [];
|
||||||
|
} else {
|
||||||
|
inBrackets = inBrackets.map(item => item.slice(1, -1)).map(item => {
|
||||||
|
if (item.startsWith('"') && item.endsWith('"')) {
|
||||||
|
return item.slice(1, -1);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const withoutBrackets = path.replace(/\[(.*?)\]/g, placeholder);
|
||||||
|
const pathParts = withoutBrackets.split('.');
|
||||||
|
const allParts = [] as string[];
|
||||||
|
pathParts.forEach(part => {
|
||||||
|
let index = part.indexOf(placeholder);
|
||||||
|
while(index !== -1) {
|
||||||
|
if (index === 0) {
|
||||||
|
allParts.push(inBrackets!.shift() as string);
|
||||||
|
part = part.substr(placeholder.length);
|
||||||
|
} else {
|
||||||
|
allParts.push(part.substr(0, index));
|
||||||
|
part = part.substr(index);
|
||||||
|
}
|
||||||
|
index = part.indexOf(placeholder);
|
||||||
|
}
|
||||||
|
if (part !== '') {
|
||||||
|
allParts.push(part);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return '["' + allParts.join('"]["') + '"]';
|
||||||
|
},
|
||||||
|
handleCopyClick (commandData: { command: string }) {
|
||||||
|
const newPath = this.convertPath(this.state.path);
|
||||||
|
|
||||||
|
let value: string;
|
||||||
|
if (commandData.command === 'value') {
|
||||||
|
if (typeof this.state.value === 'object') {
|
||||||
|
value = JSON.stringify(this.state.value, null, 2);
|
||||||
|
} else {
|
||||||
|
value = this.state.value.toString();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let startPath = '';
|
||||||
|
let path = '';
|
||||||
|
if (commandData.command === 'itemPath') {
|
||||||
|
const pathParts = newPath.split(']');
|
||||||
|
const index = pathParts[0].slice(1);
|
||||||
|
path = pathParts.slice(1).join(']');
|
||||||
|
startPath = `$item(${index}).$node["${this.node!.name}"].json`;
|
||||||
|
} else if (commandData.command === 'parameterPath') {
|
||||||
|
path = newPath.split(']').slice(1).join(']');
|
||||||
|
startPath = `$node["${this.node!.name}"].json`;
|
||||||
|
}
|
||||||
|
if (!path.startsWith('[') && !path.startsWith('.') && path) {
|
||||||
|
path += '.';
|
||||||
|
}
|
||||||
|
value = `{{ ${startPath + path} }}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.copyToClipboard(value);
|
||||||
|
},
|
||||||
refreshDataSize () {
|
refreshDataSize () {
|
||||||
// Hide by default the data from being displayed
|
// Hide by default the data from being displayed
|
||||||
this.showData = false;
|
this.showData = false;
|
||||||
|
@ -492,10 +594,6 @@ export default mixins(
|
||||||
// Data is reasonable small (< 200kb) so display it directly
|
// Data is reasonable small (< 200kb) so display it directly
|
||||||
this.showData = true;
|
this.showData = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.displayMode === 'Binary' && this.binaryData.length === 0) {
|
|
||||||
this.displayMode = 'Table';
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -504,6 +602,12 @@ export default mixins(
|
||||||
this.outputIndex = 0;
|
this.outputIndex = 0;
|
||||||
this.maxDisplayItems = 25;
|
this.maxDisplayItems = 25;
|
||||||
this.refreshDataSize();
|
this.refreshDataSize();
|
||||||
|
if (this.displayMode === 'Binary') {
|
||||||
|
this.closeBinaryDataDisplay();
|
||||||
|
if (this.binaryData.length === 0) {
|
||||||
|
this.displayMode = 'Table';
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
jsonData () {
|
jsonData () {
|
||||||
this.refreshDataSize();
|
this.refreshDataSize();
|
||||||
|
@ -610,15 +714,8 @@ export default mixins(
|
||||||
}
|
}
|
||||||
|
|
||||||
.json-data {
|
.json-data {
|
||||||
.json-tree {
|
&.vjs-tree {
|
||||||
color: $--custom-input-font;
|
color: $--custom-input-font;
|
||||||
|
|
||||||
.json-tree-value-number {
|
|
||||||
color: #b03030;
|
|
||||||
}
|
|
||||||
.json-tree-value-string {
|
|
||||||
color: #8aab1a;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -694,6 +791,16 @@ export default mixins(
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
|
|
||||||
|
.select-button {
|
||||||
|
height: 30px;
|
||||||
|
top: 50px;
|
||||||
|
right: 30px;
|
||||||
|
position: absolute;
|
||||||
|
text-align: right;
|
||||||
|
width: 200px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
.title-text {
|
.title-text {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
|
|
|
@ -341,6 +341,14 @@ export const nodeBase = mixins(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mouseLeftClick (e: MouseEvent) {
|
mouseLeftClick (e: MouseEvent) {
|
||||||
|
// @ts-ignore
|
||||||
|
const path = e.path || (e.composedPath && e.composedPath());
|
||||||
|
for (let index = 0; index < path.length; index++) {
|
||||||
|
if (path[index].className && typeof path[index].className === 'string' && path[index].className.includes('no-select-on-click')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.isTouchDevice) {
|
if (!this.isTouchDevice) {
|
||||||
if (this.$store.getters.isActionActive('dragActive')) {
|
if (this.$store.getters.isActionActive('dragActive')) {
|
||||||
this.$store.commit('removeActiveAction', 'dragActive');
|
this.$store.commit('removeActiveAction', 'dragActive');
|
||||||
|
|
|
@ -5,6 +5,7 @@ import Vue from 'vue';
|
||||||
import 'prismjs';
|
import 'prismjs';
|
||||||
import 'prismjs/themes/prism.css';
|
import 'prismjs/themes/prism.css';
|
||||||
import 'vue-prism-editor/dist/VuePrismEditor.css';
|
import 'vue-prism-editor/dist/VuePrismEditor.css';
|
||||||
|
import 'vue-json-pretty/lib/styles.css';
|
||||||
import Vue2TouchEvents from 'vue2-touch-events';
|
import Vue2TouchEvents from 'vue2-touch-events';
|
||||||
|
|
||||||
import * as ElementUI from 'element-ui';
|
import * as ElementUI from 'element-ui';
|
||||||
|
|
|
@ -138,14 +138,13 @@ export const store = new Vuex.Store({
|
||||||
state.activeWorkflows = newActiveWorkflows;
|
state.activeWorkflows = newActiveWorkflows;
|
||||||
},
|
},
|
||||||
setWorkflowActive (state, workflowId: string) {
|
setWorkflowActive (state, workflowId: string) {
|
||||||
state.stateIsDirty = true;
|
state.stateIsDirty = false;
|
||||||
const index = state.activeWorkflows.indexOf(workflowId);
|
const index = state.activeWorkflows.indexOf(workflowId);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
state.activeWorkflows.push(workflowId);
|
state.activeWorkflows.push(workflowId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setWorkflowInactive (state, workflowId: string) {
|
setWorkflowInactive (state, workflowId: string) {
|
||||||
state.stateIsDirty = true;
|
|
||||||
const index = state.activeWorkflows.indexOf(workflowId);
|
const index = state.activeWorkflows.indexOf(workflowId);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
state.selectedNodes.splice(index, 1);
|
state.selectedNodes.splice(index, 1);
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
|
"skipLibCheck": true,
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|
18
packages/nodes-base/credentials/BrandfetchApi.credentials.ts
Normal file
18
packages/nodes-base/credentials/BrandfetchApi.credentials.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import {
|
||||||
|
ICredentialType,
|
||||||
|
NodePropertyTypes,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class BrandfetchApi implements ICredentialType {
|
||||||
|
name = 'brandfetchApi';
|
||||||
|
displayName = 'Brandfetch API';
|
||||||
|
documentationUrl = 'brandfetch';
|
||||||
|
properties = [
|
||||||
|
{
|
||||||
|
displayName: 'API Key',
|
||||||
|
name: 'apiKey',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import {
|
||||||
export class CortexApi implements ICredentialType {
|
export class CortexApi implements ICredentialType {
|
||||||
name = 'cortexApi';
|
name = 'cortexApi';
|
||||||
displayName = 'Cortex API';
|
displayName = 'Cortex API';
|
||||||
|
documentationUrl = 'cortex';
|
||||||
properties = [
|
properties = [
|
||||||
{
|
{
|
||||||
displayName: 'API Key',
|
displayName: 'API Key',
|
||||||
|
|
|
@ -3,6 +3,11 @@ import {
|
||||||
NodePropertyTypes,
|
NodePropertyTypes,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
const scopes = [
|
||||||
|
'files.content.write',
|
||||||
|
'files.content.read',
|
||||||
|
'sharing.read',
|
||||||
|
];
|
||||||
|
|
||||||
export class DropboxOAuth2Api implements ICredentialType {
|
export class DropboxOAuth2Api implements ICredentialType {
|
||||||
name = 'dropboxOAuth2Api';
|
name = 'dropboxOAuth2Api';
|
||||||
|
@ -30,13 +35,13 @@ export class DropboxOAuth2Api implements ICredentialType {
|
||||||
displayName: 'Scope',
|
displayName: 'Scope',
|
||||||
name: 'scope',
|
name: 'scope',
|
||||||
type: 'hidden' as NodePropertyTypes,
|
type: 'hidden' as NodePropertyTypes,
|
||||||
default: '',
|
default: scopes.join(' '),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Auth URI Query Parameters',
|
displayName: 'Auth URI Query Parameters',
|
||||||
name: 'authQueryParameters',
|
name: 'authQueryParameters',
|
||||||
type: 'hidden' as NodePropertyTypes,
|
type: 'hidden' as NodePropertyTypes,
|
||||||
default: '',
|
default: 'token_access_type=offline',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Authentication',
|
displayName: 'Authentication',
|
||||||
|
|
22
packages/nodes-base/credentials/EgoiApi.credentials.ts
Normal file
22
packages/nodes-base/credentials/EgoiApi.credentials.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import {
|
||||||
|
ICredentialType,
|
||||||
|
NodePropertyTypes,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
|
||||||
|
export class EgoiApi implements ICredentialType {
|
||||||
|
name = 'egoiApi';
|
||||||
|
displayName = 'e-goi API';
|
||||||
|
documentationUrl = 'egoi';
|
||||||
|
properties = [
|
||||||
|
// The credentials to get from user and save encrypted.
|
||||||
|
// Properties can be defined exactly in the same way
|
||||||
|
// as node properties.
|
||||||
|
{
|
||||||
|
displayName: 'API Key',
|
||||||
|
name: 'apiKey',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
|
@ -23,14 +23,14 @@ export class GithubOAuth2Api implements ICredentialType {
|
||||||
displayName: 'Authorization URL',
|
displayName: 'Authorization URL',
|
||||||
name: 'authUrl',
|
name: 'authUrl',
|
||||||
type: 'hidden' as NodePropertyTypes,
|
type: 'hidden' as NodePropertyTypes,
|
||||||
default: 'https://github.com/login/oauth/authorize',
|
default: '={{$parameter["server"] === "https://api.github.com" ? "https://github.com" : $parameter["server"]}}/login/oauth/authorize',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Access Token URL',
|
displayName: 'Access Token URL',
|
||||||
name: 'accessTokenUrl',
|
name: 'accessTokenUrl',
|
||||||
type: 'hidden' as NodePropertyTypes,
|
type: 'hidden' as NodePropertyTypes,
|
||||||
default: 'https://github.com/login/oauth/access_token',
|
default: '={{$parameter["server"] === "https://api.github.com" ? "https://github.com" : $parameter["server"]}}/login/oauth/access_token',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,14 +22,14 @@ export class GitlabOAuth2Api implements ICredentialType {
|
||||||
displayName: 'Authorization URL',
|
displayName: 'Authorization URL',
|
||||||
name: 'authUrl',
|
name: 'authUrl',
|
||||||
type: 'hidden' as NodePropertyTypes,
|
type: 'hidden' as NodePropertyTypes,
|
||||||
default: 'https://gitlab.com/oauth/authorize',
|
default: '={{$parameter["server"]}}/oauth/authorize',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Access Token URL',
|
displayName: 'Access Token URL',
|
||||||
name: 'accessTokenUrl',
|
name: 'accessTokenUrl',
|
||||||
type: 'hidden' as NodePropertyTypes,
|
type: 'hidden' as NodePropertyTypes,
|
||||||
default: 'https://gitlab.com/oauth/token',
|
default: '={{$parameter["server"]}}/oauth/token',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -50,7 +50,7 @@ export class MauticOAuth2Api implements ICredentialType {
|
||||||
displayName: 'Authentication',
|
displayName: 'Authentication',
|
||||||
name: 'authentication',
|
name: 'authentication',
|
||||||
type: 'hidden' as NodePropertyTypes,
|
type: 'hidden' as NodePropertyTypes,
|
||||||
default: 'header',
|
default: 'body',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import {
|
||||||
|
ICredentialType,
|
||||||
|
NodePropertyTypes,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class MicrosoftOutlookOAuth2Api implements ICredentialType {
|
||||||
|
name = 'microsoftOutlookOAuth2Api';
|
||||||
|
extends = [
|
||||||
|
'microsoftOAuth2Api',
|
||||||
|
];
|
||||||
|
displayName = 'Microsoft Outlook OAuth2 API';
|
||||||
|
documentationUrl = 'microsoft';
|
||||||
|
properties = [
|
||||||
|
//https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent
|
||||||
|
{
|
||||||
|
displayName: 'Scope',
|
||||||
|
name: 'scope',
|
||||||
|
type: 'hidden' as NodePropertyTypes,
|
||||||
|
default: 'openid offline_access Mail.ReadWrite Mail.Send MailboxSettings.Read',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
17
packages/nodes-base/credentials/NasaApi.credentials.ts
Normal file
17
packages/nodes-base/credentials/NasaApi.credentials.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import {
|
||||||
|
ICredentialType,
|
||||||
|
NodePropertyTypes,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class NasaApi implements ICredentialType {
|
||||||
|
name = 'nasaApi';
|
||||||
|
displayName = 'NASA API';
|
||||||
|
properties = [
|
||||||
|
{
|
||||||
|
displayName: 'API Key',
|
||||||
|
name: 'api_key',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
18
packages/nodes-base/credentials/PushcutApi.credentials.ts
Normal file
18
packages/nodes-base/credentials/PushcutApi.credentials.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import {
|
||||||
|
ICredentialType,
|
||||||
|
NodePropertyTypes,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class PushcutApi implements ICredentialType {
|
||||||
|
name = 'pushcutApi';
|
||||||
|
displayName = 'Pushcut API';
|
||||||
|
documentationUrl = 'pushcut';
|
||||||
|
properties = [
|
||||||
|
{
|
||||||
|
displayName: 'API Key',
|
||||||
|
name: 'apiKey',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
157
packages/nodes-base/credentials/RabbitMQ.credentials.ts
Normal file
157
packages/nodes-base/credentials/RabbitMQ.credentials.ts
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
import {
|
||||||
|
ICredentialType,
|
||||||
|
NodePropertyTypes,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class RabbitMQ implements ICredentialType {
|
||||||
|
name = 'rabbitmq';
|
||||||
|
displayName = 'RabbitMQ';
|
||||||
|
documentationUrl = 'rabbitmq';
|
||||||
|
properties = [
|
||||||
|
{
|
||||||
|
displayName: 'Hostname',
|
||||||
|
name: 'hostname',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
placeholder: 'localhost',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Port',
|
||||||
|
name: 'port',
|
||||||
|
type: 'number' as NodePropertyTypes,
|
||||||
|
default: 5672,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'User',
|
||||||
|
name: 'username',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
placeholder: 'guest',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Password',
|
||||||
|
name: 'password',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
typeOptions: {
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
placeholder: 'guest',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Vhost',
|
||||||
|
name: 'vhost',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'SSL',
|
||||||
|
name: 'ssl',
|
||||||
|
type: 'boolean' as NodePropertyTypes,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Client Certificate',
|
||||||
|
name: 'cert',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
typeOptions: {
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
ssl: [
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'SSL Client Certificate to use.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Client Key',
|
||||||
|
name: 'key',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
typeOptions: {
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
ssl: [
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'SSL Client Key to use.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Passphrase',
|
||||||
|
name: 'passphrase',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
typeOptions: {
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
ssl: [
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'SSL passphrase to use.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'CA Certificates',
|
||||||
|
name: 'ca',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
typeOptions: {
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
// typeOptions: {
|
||||||
|
// multipleValues: true,
|
||||||
|
// multipleValueButtonText: 'Add Certificate',
|
||||||
|
// },
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
ssl: [
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'SSL CA Certificates to use.',
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// displayName: 'Client ID',
|
||||||
|
// name: 'clientId',
|
||||||
|
// type: 'string' as NodePropertyTypes,
|
||||||
|
// default: '',
|
||||||
|
// placeholder: 'my-app',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// displayName: 'Brokers',
|
||||||
|
// name: 'brokers',
|
||||||
|
// type: 'string' as NodePropertyTypes,
|
||||||
|
// default: '',
|
||||||
|
// placeholder: 'kafka1:9092,kafka2:9092',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// displayName: 'Username',
|
||||||
|
// name: 'username',
|
||||||
|
// type: 'string' as NodePropertyTypes,
|
||||||
|
// default: '',
|
||||||
|
// description: 'Optional username if authenticated is required.',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// displayName: 'Password',
|
||||||
|
// name: 'password',
|
||||||
|
// type: 'string' as NodePropertyTypes,
|
||||||
|
// typeOptions: {
|
||||||
|
// password: true,
|
||||||
|
// },
|
||||||
|
// default: '',
|
||||||
|
// description: 'Optional password if authenticated is required.',
|
||||||
|
// },
|
||||||
|
];
|
||||||
|
}
|
|
@ -8,13 +8,15 @@ const userScopes = [
|
||||||
'chat:write',
|
'chat:write',
|
||||||
'files:read',
|
'files:read',
|
||||||
'files:write',
|
'files:write',
|
||||||
|
'groups:read',
|
||||||
|
'im:read',
|
||||||
|
'mpim:read',
|
||||||
|
'reactions:read',
|
||||||
|
'reactions:write',
|
||||||
'stars:read',
|
'stars:read',
|
||||||
'stars:write',
|
'stars:write',
|
||||||
'users.profile:read',
|
'users.profile:read',
|
||||||
'users.profile:write',
|
'users.profile:write',
|
||||||
'groups:read',
|
|
||||||
'im:read',
|
|
||||||
'mpim:read',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export class SlackOAuth2Api implements ICredentialType {
|
export class SlackOAuth2Api implements ICredentialType {
|
||||||
|
|
70
packages/nodes-base/credentials/Snowflake.credentials.ts
Normal file
70
packages/nodes-base/credentials/Snowflake.credentials.ts
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import {
|
||||||
|
ICredentialType,
|
||||||
|
NodePropertyTypes,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class Snowflake implements ICredentialType {
|
||||||
|
name = 'snowflake';
|
||||||
|
displayName = 'Snowflake';
|
||||||
|
documentationUrl = 'snowflake';
|
||||||
|
properties = [
|
||||||
|
{
|
||||||
|
displayName: 'Account',
|
||||||
|
name: 'account',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
description: 'Enter the name of your Snowflake account.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Database',
|
||||||
|
name: 'database',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
description: 'Specify the database you want to use after creating the connection.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Warehouse',
|
||||||
|
name: 'warehouse',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
description: 'The default virtual warehouse to use for the session after connecting. Used for performing queries, loading data, etc.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Username',
|
||||||
|
name: 'username',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Password',
|
||||||
|
name: 'password',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
typeOptions: {
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Schema',
|
||||||
|
name: 'schema',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
description: 'Enter the schema you want to use after creating the connection',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Role',
|
||||||
|
name: 'role',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
description: 'Enter the security role you want to use after creating the connection',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Client Session Keep Alive',
|
||||||
|
name: 'clientSessionKeepAlive',
|
||||||
|
type: 'boolean' as NodePropertyTypes,
|
||||||
|
default: false,
|
||||||
|
description: `By default, client connections typically time out approximately 3-4 hours after the most recent query was executed.<br>
|
||||||
|
If the parameter clientSessionKeepAlive is set to true, the client’s connection to the server will be kept alive indefinitely, even if no queries are executed.`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import {
|
||||||
export class TheHiveApi implements ICredentialType {
|
export class TheHiveApi implements ICredentialType {
|
||||||
name = 'theHiveApi';
|
name = 'theHiveApi';
|
||||||
displayName = 'The Hive API';
|
displayName = 'The Hive API';
|
||||||
|
documentationUrl = 'theHive';
|
||||||
properties = [
|
properties = [
|
||||||
{
|
{
|
||||||
displayName: 'API Key',
|
displayName: 'API Key',
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
import {
|
||||||
|
ICredentialType,
|
||||||
|
NodePropertyTypes,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
const scopes = [
|
||||||
|
'attachments:write',
|
||||||
|
'channels:remove',
|
||||||
|
'messages:remove',
|
||||||
|
'workspaces:read',
|
||||||
|
];
|
||||||
|
|
||||||
|
export class TwistOAuth2Api implements ICredentialType {
|
||||||
|
name = 'twistOAuth2Api';
|
||||||
|
extends = [
|
||||||
|
'oAuth2Api',
|
||||||
|
];
|
||||||
|
displayName = 'Twist OAuth2 API';
|
||||||
|
documentationUrl = 'twist';
|
||||||
|
properties = [
|
||||||
|
{
|
||||||
|
displayName: 'Authorization URL',
|
||||||
|
name: 'authUrl',
|
||||||
|
type: 'hidden' as NodePropertyTypes,
|
||||||
|
default: 'https://twist.com/oauth/authorize',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Access Token URL',
|
||||||
|
name: 'accessTokenUrl',
|
||||||
|
type: 'hidden' as NodePropertyTypes,
|
||||||
|
default: 'https://twist.com/oauth/access_token',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Scope',
|
||||||
|
name: 'scope',
|
||||||
|
type: 'hidden' as NodePropertyTypes,
|
||||||
|
default: scopes.join(','),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Auth URI Query Parameters',
|
||||||
|
name: 'authQueryParameters',
|
||||||
|
type: 'hidden' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Authentication',
|
||||||
|
name: 'authentication',
|
||||||
|
type: 'hidden' as NodePropertyTypes,
|
||||||
|
default: 'body',
|
||||||
|
description: 'Resource to consume.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
23
packages/nodes-base/credentials/UProcApi.credentials.ts
Normal file
23
packages/nodes-base/credentials/UProcApi.credentials.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import {
|
||||||
|
ICredentialType,
|
||||||
|
NodePropertyTypes,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class UProcApi implements ICredentialType {
|
||||||
|
name = 'uprocApi';
|
||||||
|
displayName = 'uProc API';
|
||||||
|
properties = [
|
||||||
|
{
|
||||||
|
displayName: 'Email',
|
||||||
|
name: 'email',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'API Key',
|
||||||
|
name: 'apiKey',
|
||||||
|
type: 'string' as NodePropertyTypes,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
|
@ -272,7 +272,7 @@ export class ActiveCampaign implements INodeType {
|
||||||
// select them easily
|
// select them easily
|
||||||
async getContactCustomFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
async getContactCustomFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
const returnData: INodePropertyOptions[] = [];
|
const returnData: INodePropertyOptions[] = [];
|
||||||
const { fields } = await activeCampaignApiRequest.call(this, 'GET', '/api/3/fields', {});
|
const { fields } = await activeCampaignApiRequest.call(this, 'GET', '/api/3/fields', {}, { limit: 100 });
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
const fieldName = field.title;
|
const fieldName = field.title;
|
||||||
const fieldId = field.id;
|
const fieldId = field.id;
|
||||||
|
@ -287,7 +287,7 @@ export class ActiveCampaign implements INodeType {
|
||||||
// select them easily
|
// select them easily
|
||||||
async getAccountCustomFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
async getAccountCustomFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
const returnData: INodePropertyOptions[] = [];
|
const returnData: INodePropertyOptions[] = [];
|
||||||
const { accountCustomFieldMeta: fields } = await activeCampaignApiRequest.call(this, 'GET', '/api/3/accountCustomFieldMeta', {});
|
const { accountCustomFieldMeta: fields } = await activeCampaignApiRequest.call(this, 'GET', '/api/3/accountCustomFieldMeta', {}, { limit: 100 });
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
const fieldName = field.fieldLabel;
|
const fieldName = field.fieldLabel;
|
||||||
const fieldId = field.id;
|
const fieldId = field.id;
|
||||||
|
@ -298,6 +298,19 @@ export class ActiveCampaign implements INodeType {
|
||||||
}
|
}
|
||||||
return returnData;
|
return returnData;
|
||||||
},
|
},
|
||||||
|
// Get all the available 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 activeCampaignApiRequest.call(this, 'GET', '/api/3/tags', {}, { limit: 100 });
|
||||||
|
for (const tag of tags) {
|
||||||
|
returnData.push({
|
||||||
|
name: tag.tag,
|
||||||
|
value: tag.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,10 @@ export const contactTagFields = [
|
||||||
{
|
{
|
||||||
displayName: 'Tag ID',
|
displayName: 'Tag ID',
|
||||||
name: 'tagId',
|
name: 'tagId',
|
||||||
type: 'number',
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTags',
|
||||||
|
},
|
||||||
default: '',
|
default: '',
|
||||||
required: true,
|
required: true,
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
|
|
|
@ -14,9 +14,16 @@ import {
|
||||||
import {
|
import {
|
||||||
asanaApiRequest,
|
asanaApiRequest,
|
||||||
asanaApiRequestAllItems,
|
asanaApiRequestAllItems,
|
||||||
|
getTaskFields,
|
||||||
getWorkspaces,
|
getWorkspaces,
|
||||||
} from './GenericFunctions';
|
} from './GenericFunctions';
|
||||||
|
|
||||||
|
import * as moment from 'moment-timezone';
|
||||||
|
|
||||||
|
import {
|
||||||
|
snakeCase,
|
||||||
|
} from 'change-case';
|
||||||
|
|
||||||
export class Asana implements INodeType {
|
export class Asana implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'Asana',
|
displayName: 'Asana',
|
||||||
|
@ -83,6 +90,10 @@ export class Asana implements INodeType {
|
||||||
name: 'Project',
|
name: 'Project',
|
||||||
value: 'project',
|
value: 'project',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Subtask',
|
||||||
|
value: 'subtask',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Task',
|
name: 'Task',
|
||||||
value: 'task',
|
value: 'task',
|
||||||
|
@ -103,7 +114,273 @@ export class Asana implements INodeType {
|
||||||
default: 'task',
|
default: 'task',
|
||||||
description: 'The resource to operate on.',
|
description: 'The resource to operate on.',
|
||||||
},
|
},
|
||||||
|
// ----------------------------------
|
||||||
|
// subtask
|
||||||
|
// ----------------------------------
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'subtask',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create a subtask',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get All',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Get all substasks',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// subtask:create
|
||||||
|
// ----------------------------------
|
||||||
|
{
|
||||||
|
displayName: 'Parent Task ID',
|
||||||
|
name: 'taskId',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'subtask',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The task to operate on.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'subtask',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The name of the subtask to create',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'otherProperties',
|
||||||
|
type: 'collection',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'subtask',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Assignee',
|
||||||
|
name: 'assignee',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getUsers',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Set Assignee on the subtask',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Assignee Status',
|
||||||
|
name: 'assignee_status',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Inbox',
|
||||||
|
value: 'inbox',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Today',
|
||||||
|
value: 'today',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Upcoming',
|
||||||
|
value: 'upcoming',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Later',
|
||||||
|
value: 'later',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'inbox',
|
||||||
|
description: 'Set Assignee status on the subtask (requires Assignee)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Completed',
|
||||||
|
name: 'completed',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'If the subtask should be marked completed.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Due On',
|
||||||
|
name: 'due_on',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
description: 'Date on which the time is due.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Liked',
|
||||||
|
name: 'liked',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'If the task is liked by the authorized user.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Notes',
|
||||||
|
name: 'notes',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
alwaysOpenEditWindow: true,
|
||||||
|
rows: 5,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The task notes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Workspace',
|
||||||
|
name: 'workspace',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getWorkspaces',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The workspace to create the subtask in',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// ----------------------------------
|
||||||
|
// subtask:getAll
|
||||||
|
// ----------------------------------
|
||||||
|
{
|
||||||
|
displayName: 'Parent Task ID',
|
||||||
|
name: 'taskId',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'subtask',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The task to operate on.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'subtask',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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: [
|
||||||
|
'subtask',
|
||||||
|
],
|
||||||
|
returnAll: [
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 500,
|
||||||
|
},
|
||||||
|
default: 100,
|
||||||
|
description: 'How many results to return.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'subtask',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'opt_fields',
|
||||||
|
type: 'multiOptions',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTaskFields',
|
||||||
|
},
|
||||||
|
default: [
|
||||||
|
'gid',
|
||||||
|
'name',
|
||||||
|
'resource_type',
|
||||||
|
],
|
||||||
|
description: 'Defines fields to return.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Pretty',
|
||||||
|
name: 'opt_pretty',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'Provides “pretty” output.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// task
|
// task
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
@ -134,6 +411,11 @@ export class Asana implements INodeType {
|
||||||
value: 'get',
|
value: 'get',
|
||||||
description: 'Get a task',
|
description: 'Get a task',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Get All',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Get all tasks',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Move',
|
name: 'Move',
|
||||||
value: 'move',
|
value: 'move',
|
||||||
|
@ -241,6 +523,145 @@ export class Asana implements INodeType {
|
||||||
},
|
},
|
||||||
description: 'The ID of the task to get the data of.',
|
description: 'The ID of the task to get the data of.',
|
||||||
},
|
},
|
||||||
|
// ----------------------------------
|
||||||
|
// task:getAll
|
||||||
|
// ----------------------------------
|
||||||
|
{
|
||||||
|
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',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'task',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
description: 'Properties to search for',
|
||||||
|
placeholder: 'Add Filter',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Assignee',
|
||||||
|
name: 'assignee',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getUsers',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The assignee to filter tasks on. Note: If you specify assignee, you must also specify the workspace to filter on.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'opt_fields',
|
||||||
|
type: 'multiOptions',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getTaskFields',
|
||||||
|
},
|
||||||
|
default: [
|
||||||
|
'gid',
|
||||||
|
'name',
|
||||||
|
'resource_type',
|
||||||
|
],
|
||||||
|
description: 'Defines fields to return.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Pretty',
|
||||||
|
name: 'opt_pretty',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'Provides “pretty” output.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Project',
|
||||||
|
name: 'project',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getProjects',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The project to filter tasks on.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Section',
|
||||||
|
name: 'section',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getSections',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The section to filter tasks on.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Workspace',
|
||||||
|
name: 'workspace',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getWorkspaces',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The workspace to filter tasks on. Note: If you specify workspace, you must also specify the assignee to filter on.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Completed Since',
|
||||||
|
name: 'completed_since',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
description: 'Only return tasks that are either incomplete or that have been completed since this time.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Modified Since',
|
||||||
|
name: 'modified_since',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
description: 'Only return tasks that have been modified since the given time.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// task:move
|
// task:move
|
||||||
|
@ -1219,12 +1640,25 @@ export class Asana implements INodeType {
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
},
|
},
|
||||||
|
async getTaskFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
for (const field of getTaskFields()) {
|
||||||
|
const value = snakeCase(field);
|
||||||
|
returnData.push({
|
||||||
|
name: field,
|
||||||
|
value: (value === '') ? '*' : value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
const items = this.getInputData();
|
const items = this.getInputData();
|
||||||
const returnData: IDataObject[] = [];
|
const returnData: IDataObject[] = [];
|
||||||
|
const timezone = this.getTimezone();
|
||||||
|
|
||||||
const resource = this.getNodeParameter('resource', 0) as string;
|
const resource = this.getNodeParameter('resource', 0) as string;
|
||||||
const operation = this.getNodeParameter('operation', 0) as string;
|
const operation = this.getNodeParameter('operation', 0) as string;
|
||||||
|
@ -1240,6 +1674,61 @@ export class Asana implements INodeType {
|
||||||
body = {};
|
body = {};
|
||||||
qs = {};
|
qs = {};
|
||||||
|
|
||||||
|
if (resource === 'subtask') {
|
||||||
|
if (operation === 'create') {
|
||||||
|
// ----------------------------------
|
||||||
|
// subtask:create
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
const taskId = this.getNodeParameter('taskId', i) as string;
|
||||||
|
|
||||||
|
requestMethod = 'POST';
|
||||||
|
endpoint = `/tasks/${taskId}/subtasks`;
|
||||||
|
|
||||||
|
body.name = this.getNodeParameter('name', i) as string;
|
||||||
|
|
||||||
|
const otherProperties = this.getNodeParameter('otherProperties', i) as IDataObject;
|
||||||
|
Object.assign(body, otherProperties);
|
||||||
|
|
||||||
|
responseData = await asanaApiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
responseData = responseData.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'getAll') {
|
||||||
|
// ----------------------------------
|
||||||
|
// subtask:getAll
|
||||||
|
// ----------------------------------
|
||||||
|
const taskId = this.getNodeParameter('taskId', i) as string;
|
||||||
|
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||||
|
|
||||||
|
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||||
|
|
||||||
|
requestMethod = 'GET';
|
||||||
|
endpoint = `/tasks/${taskId}/subtasks`;
|
||||||
|
|
||||||
|
Object.assign(qs, options);
|
||||||
|
|
||||||
|
if (qs.opt_fields) {
|
||||||
|
const fields = qs.opt_fields as string[];
|
||||||
|
if (fields.includes('*')) {
|
||||||
|
qs.opt_fields = getTaskFields().map((e) => snakeCase(e)).join(',');
|
||||||
|
} else {
|
||||||
|
qs.opt_fields = (qs.opt_fields as string[]).join(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await asanaApiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
responseData = responseData.data;
|
||||||
|
|
||||||
|
if (returnAll === false) {
|
||||||
|
const limit = this.getNodeParameter('limit', i) as boolean;
|
||||||
|
responseData = responseData.splice(0, limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (resource === 'task') {
|
if (resource === 'task') {
|
||||||
if (operation === 'create') {
|
if (operation === 'create') {
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
@ -1286,6 +1775,47 @@ export class Asana implements INodeType {
|
||||||
|
|
||||||
responseData = responseData.data;
|
responseData = responseData.data;
|
||||||
|
|
||||||
|
} else if (operation === 'getAll') {
|
||||||
|
// ----------------------------------
|
||||||
|
// task:getAll
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
const filters = this.getNodeParameter('filters', i) as IDataObject;
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||||
|
|
||||||
|
requestMethod = 'GET';
|
||||||
|
endpoint = `/tasks`;
|
||||||
|
|
||||||
|
Object.assign(qs, filters);
|
||||||
|
|
||||||
|
if (qs.opt_fields) {
|
||||||
|
const fields = qs.opt_fields as string[];
|
||||||
|
if (fields.includes('*')) {
|
||||||
|
qs.opt_fields = getTaskFields().map((e) => snakeCase(e)).join(',');
|
||||||
|
} else {
|
||||||
|
qs.opt_fields = (qs.opt_fields as string[]).join(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qs.modified_since) {
|
||||||
|
qs.modified_since = moment.tz(qs.modified_since as string, timezone).format();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qs.completed_since) {
|
||||||
|
qs.completed_since = moment.tz(qs.completed_since as string, timezone).format();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await asanaApiRequestAllItems.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
qs.limit = this.getNodeParameter('limit', i) as boolean;
|
||||||
|
|
||||||
|
responseData = await asanaApiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||||
|
|
||||||
|
responseData = responseData.data;
|
||||||
|
}
|
||||||
|
|
||||||
} else if (operation === 'move') {
|
} else if (operation === 'move') {
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// task:move
|
// task:move
|
||||||
|
|
|
@ -121,3 +121,40 @@ export async function getWorkspaces(this: ILoadOptionsFunctions): Promise < INod
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getTaskFields() {
|
||||||
|
return [
|
||||||
|
'*',
|
||||||
|
'GID',
|
||||||
|
'Resource Type',
|
||||||
|
'name',
|
||||||
|
'Approval Status',
|
||||||
|
'Assignee Status',
|
||||||
|
'Completed',
|
||||||
|
'Completed At',
|
||||||
|
'Completed By',
|
||||||
|
'Created At',
|
||||||
|
'Dependencies',
|
||||||
|
'Dependents',
|
||||||
|
'Due At',
|
||||||
|
'Due On',
|
||||||
|
'External',
|
||||||
|
'HTML Notes',
|
||||||
|
'Liked',
|
||||||
|
'Likes',
|
||||||
|
'Memberships',
|
||||||
|
'Modified At',
|
||||||
|
'Notes',
|
||||||
|
'Num Likes',
|
||||||
|
'Resource Subtype',
|
||||||
|
'Start On',
|
||||||
|
'Assignee',
|
||||||
|
'Custom Fields',
|
||||||
|
'Followers',
|
||||||
|
'Parent',
|
||||||
|
'Permalink URL',
|
||||||
|
'Projects',
|
||||||
|
'Tags',
|
||||||
|
'Workspace',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ export class BoxTrigger implements INodeType {
|
||||||
icon: 'file:box.png',
|
icon: 'file:box.png',
|
||||||
group: ['trigger'],
|
group: ['trigger'],
|
||||||
version: 1,
|
version: 1,
|
||||||
description: 'Starts the workflow when a Github events occurs.',
|
description: 'Starts the workflow when a Box events occurs.',
|
||||||
defaults: {
|
defaults: {
|
||||||
name: 'Box Trigger',
|
name: 'Box Trigger',
|
||||||
color: '#00aeef',
|
color: '#00aeef',
|
||||||
|
|
271
packages/nodes-base/nodes/Brandfetch/Brandfetch.node.ts
Normal file
271
packages/nodes-base/nodes/Brandfetch/Brandfetch.node.ts
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
brandfetchApiRequest,
|
||||||
|
} from './GenericFunctions';
|
||||||
|
|
||||||
|
export class Brandfetch implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'Brandfetch',
|
||||||
|
name: 'Brandfetch',
|
||||||
|
icon: 'file:brandfetch.png',
|
||||||
|
group: ['output'],
|
||||||
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["operation"]}}',
|
||||||
|
description: 'Consume Brandfetch API',
|
||||||
|
defaults: {
|
||||||
|
name: 'Brandfetch',
|
||||||
|
color: '#1f1f1f',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'brandfetchApi',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'Color',
|
||||||
|
value: 'color',
|
||||||
|
description: 'Return a company\'s colors',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Company',
|
||||||
|
value: 'company',
|
||||||
|
description: 'Return a company\'s data',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Font',
|
||||||
|
value: 'font',
|
||||||
|
description: 'Return a company\'s fonts',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Industry',
|
||||||
|
value: 'industry',
|
||||||
|
description: 'Return a company\'s industry',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Logo',
|
||||||
|
value: 'logo',
|
||||||
|
description: 'Return a company\'s logo & icon',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'logo',
|
||||||
|
description: 'The operation to perform',
|
||||||
|
},
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// All
|
||||||
|
// ----------------------------------
|
||||||
|
{
|
||||||
|
displayName: 'Domain',
|
||||||
|
name: 'domain',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The domain name of the company.',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Download',
|
||||||
|
name: 'download',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'logo',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'Name of the binary property to which to<br />write the data of the read file.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Image Type',
|
||||||
|
name: 'imageTypes',
|
||||||
|
type: 'multiOptions',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'logo',
|
||||||
|
],
|
||||||
|
download: [
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Icon',
|
||||||
|
value: 'icon',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Logo',
|
||||||
|
value: 'logo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: [
|
||||||
|
'logo',
|
||||||
|
'icon',
|
||||||
|
],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Image Format',
|
||||||
|
name: 'imageFormats',
|
||||||
|
type: 'multiOptions',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'logo',
|
||||||
|
],
|
||||||
|
download: [
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'PNG',
|
||||||
|
value: 'png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'SVG',
|
||||||
|
value: 'svg',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: [
|
||||||
|
'png',
|
||||||
|
],
|
||||||
|
description: 'The image format in which the logo should be returned as.',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
const items = this.getInputData();
|
||||||
|
const length = items.length as unknown as number;
|
||||||
|
|
||||||
|
const operation = this.getNodeParameter('operation', 0) as string;
|
||||||
|
const responseData = [];
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
if (operation === 'logo') {
|
||||||
|
const domain = this.getNodeParameter('domain', i) as string;
|
||||||
|
const download = this.getNodeParameter('download', i) as boolean;
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
domain,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await brandfetchApiRequest.call(this, 'POST', `/logo`, body);
|
||||||
|
|
||||||
|
if (download === true) {
|
||||||
|
|
||||||
|
const imageTypes = this.getNodeParameter('imageTypes', i) as string[];
|
||||||
|
|
||||||
|
const imageFormats = this.getNodeParameter('imageFormats', i) as string[];
|
||||||
|
|
||||||
|
const newItem: INodeExecutionData = {
|
||||||
|
json: {},
|
||||||
|
binary: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (items[i].binary !== undefined) {
|
||||||
|
// Create a shallow copy of the binary data so that the old
|
||||||
|
// data references which do not get changed still stay behind
|
||||||
|
// but the incoming data does not get changed.
|
||||||
|
Object.assign(newItem.binary, items[i].binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
newItem.json = response.response;
|
||||||
|
|
||||||
|
for (const imageType of imageTypes) {
|
||||||
|
for (const imageFormat of imageFormats) {
|
||||||
|
|
||||||
|
const url = response.response[imageType][(imageFormat === 'png') ? 'image' : imageFormat] as string;
|
||||||
|
|
||||||
|
if (url !== null) {
|
||||||
|
const data = await brandfetchApiRequest.call(this, 'GET', '', {}, {}, url, { json: false, encoding: null });
|
||||||
|
|
||||||
|
newItem.binary![`${imageType}_${imageFormat}`] = await this.helpers.prepareBinaryData(data, `${imageType}_${domain}.${imageFormat}`);
|
||||||
|
|
||||||
|
items[i] = newItem;
|
||||||
|
}
|
||||||
|
items[i] = newItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(items[i].binary!).length === 0) {
|
||||||
|
delete items[i].binary;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
responseData.push(response.response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (operation === 'color') {
|
||||||
|
const domain = this.getNodeParameter('domain', i) as string;
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
domain,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await brandfetchApiRequest.call(this, 'POST', `/color`, body);
|
||||||
|
responseData.push(response.response);
|
||||||
|
}
|
||||||
|
if (operation === 'font') {
|
||||||
|
const domain = this.getNodeParameter('domain', i) as string;
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
domain,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await brandfetchApiRequest.call(this, 'POST', `/font`, body);
|
||||||
|
responseData.push(response.response);
|
||||||
|
}
|
||||||
|
if (operation === 'company') {
|
||||||
|
const domain = this.getNodeParameter('domain', i) as string;
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
domain,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await brandfetchApiRequest.call(this, 'POST', `/company`, body);
|
||||||
|
responseData.push(response.response);
|
||||||
|
}
|
||||||
|
if (operation === 'industry') {
|
||||||
|
const domain = this.getNodeParameter('domain', i) as string;
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
domain,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await brandfetchApiRequest.call(this, 'POST', `/industry`, body);
|
||||||
|
responseData.push.apply(responseData, response.response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'logo' && this.getNodeParameter('download', 0) === true) {
|
||||||
|
// For file downloads the files get attached to the existing items
|
||||||
|
return this.prepareOutputData(items);
|
||||||
|
} else {
|
||||||
|
return [this.helpers.returnJsonArray(responseData)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
packages/nodes-base/nodes/Brandfetch/GenericFunctions.ts
Normal file
65
packages/nodes-base/nodes/Brandfetch/GenericFunctions.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import {
|
||||||
|
OptionsWithUri,
|
||||||
|
} from 'request';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IExecuteSingleFunctions,
|
||||||
|
IHookFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export async function brandfetchApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
try {
|
||||||
|
const credentials = this.getCredentials('brandfetchApi');
|
||||||
|
if (credentials === undefined) {
|
||||||
|
throw new Error('No credentials got returned!');
|
||||||
|
}
|
||||||
|
let options: OptionsWithUri = {
|
||||||
|
headers: {
|
||||||
|
'x-api-key': credentials.apiKey,
|
||||||
|
},
|
||||||
|
method,
|
||||||
|
qs,
|
||||||
|
body,
|
||||||
|
uri: uri || `https://api.brandfetch.io/v1${resource}`,
|
||||||
|
json: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
options = Object.assign({}, options, option);
|
||||||
|
|
||||||
|
if (this.getNodeParameter('operation', 0) === 'logo' && options.json === false) {
|
||||||
|
delete options.headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Object.keys(body).length) {
|
||||||
|
delete options.body;
|
||||||
|
}
|
||||||
|
if (!Object.keys(qs).length) {
|
||||||
|
delete options.qs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await this.helpers.request!(options);
|
||||||
|
|
||||||
|
if (response.statusCode && response.statusCode !== 200) {
|
||||||
|
throw new Error(`Brandfetch error response [${response.statusCode}]: ${response.response}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
if (error.response && error.response.body && error.response.body.message) {
|
||||||
|
// Try to return the error prettier
|
||||||
|
const errorBody = error.response.body;
|
||||||
|
throw new Error(`Brandfetch error response [${error.statusCode}]: ${errorBody.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expected error data did not get returned so throw the actual error
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
BIN
packages/nodes-base/nodes/Brandfetch/brandfetch.png
Normal file
BIN
packages/nodes-base/nodes/Brandfetch/brandfetch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 555 B |
|
@ -6,6 +6,7 @@ import {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
ILoadOptionsFunctions,
|
ILoadOptionsFunctions,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
|
INodeProperties,
|
||||||
INodePropertyOptions,
|
INodePropertyOptions,
|
||||||
INodeType,
|
INodeType,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
|
@ -23,26 +24,7 @@ const fsWriteFileAsync = promisify(fsWriteFile);
|
||||||
import * as getSystemFonts from 'get-system-fonts';
|
import * as getSystemFonts from 'get-system-fonts';
|
||||||
|
|
||||||
|
|
||||||
export class EditImage implements INodeType {
|
const nodeOperations: INodePropertyOptions[] = [
|
||||||
description: INodeTypeDescription = {
|
|
||||||
displayName: 'Edit Image',
|
|
||||||
name: 'editImage',
|
|
||||||
icon: 'fa:image',
|
|
||||||
group: ['transform'],
|
|
||||||
version: 1,
|
|
||||||
description: 'Edits an image like blur, resize or adding border and text',
|
|
||||||
defaults: {
|
|
||||||
name: 'Edit Image',
|
|
||||||
color: '#553399',
|
|
||||||
},
|
|
||||||
inputs: ['main'],
|
|
||||||
outputs: ['main'],
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
displayName: 'Operation',
|
|
||||||
name: 'operation',
|
|
||||||
type: 'options',
|
|
||||||
options: [
|
|
||||||
{
|
{
|
||||||
name: 'Blur',
|
name: 'Blur',
|
||||||
value: 'blur',
|
value: 'blur',
|
||||||
|
@ -53,6 +35,11 @@ export class EditImage implements INodeType {
|
||||||
value: 'border',
|
value: 'border',
|
||||||
description: 'Adds a border to the image',
|
description: 'Adds a border to the image',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Composite',
|
||||||
|
value: 'composite',
|
||||||
|
description: 'Composite image on top of another one',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Create',
|
name: 'Create',
|
||||||
value: 'create',
|
value: 'create',
|
||||||
|
@ -63,21 +50,11 @@ export class EditImage implements INodeType {
|
||||||
value: 'crop',
|
value: 'crop',
|
||||||
description: 'Crops the image',
|
description: 'Crops the image',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'Composite',
|
|
||||||
value: 'composite',
|
|
||||||
description: 'Composite image on top of another one',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'Draw',
|
name: 'Draw',
|
||||||
value: 'draw',
|
value: 'draw',
|
||||||
description: 'Draw on image',
|
description: 'Draw on image',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'Get Information',
|
|
||||||
value: 'information',
|
|
||||||
description: 'Returns image information like resolution',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'Rotate',
|
name: 'Rotate',
|
||||||
value: 'rotate',
|
value: 'rotate',
|
||||||
|
@ -98,19 +75,10 @@ export class EditImage implements INodeType {
|
||||||
value: 'text',
|
value: 'text',
|
||||||
description: 'Adds text to image',
|
description: 'Adds text to image',
|
||||||
},
|
},
|
||||||
],
|
];
|
||||||
default: 'border',
|
|
||||||
description: 'The operation to perform.',
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
displayName: 'Property Name',
|
|
||||||
name: 'dataPropertyName',
|
|
||||||
type: 'string',
|
|
||||||
default: 'data',
|
|
||||||
description: 'Name of the binary property in which the image data can be found.',
|
|
||||||
},
|
|
||||||
|
|
||||||
|
const nodeOperationOptions: INodeProperties[] = [
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// create
|
// create
|
||||||
|
@ -397,7 +365,6 @@ export class EditImage implements INodeType {
|
||||||
description: 'Max amount of characters in a line before a<br />line-break should get added.',
|
description: 'Max amount of characters in a line before a<br />line-break should get added.',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// blur
|
// blur
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
@ -742,7 +709,115 @@ export class EditImage implements INodeType {
|
||||||
},
|
},
|
||||||
description: 'Y (vertical) shear degrees.',
|
description: 'Y (vertical) shear degrees.',
|
||||||
},
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
export class EditImage implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'Edit Image',
|
||||||
|
name: 'editImage',
|
||||||
|
icon: 'fa:image',
|
||||||
|
group: ['transform'],
|
||||||
|
version: 1,
|
||||||
|
description: 'Edits an image like blur, resize or adding border and text',
|
||||||
|
defaults: {
|
||||||
|
name: 'Edit Image',
|
||||||
|
color: '#553399',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Get Information',
|
||||||
|
value: 'information',
|
||||||
|
description: 'Returns image information like resolution',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Multi Step',
|
||||||
|
value: 'multiStep',
|
||||||
|
description: 'Perform multiple operations',
|
||||||
|
},
|
||||||
|
...nodeOperations,
|
||||||
|
].sort((a, b) => {
|
||||||
|
if ((a as INodePropertyOptions).name.toLowerCase() < (b as INodePropertyOptions).name.toLowerCase()) { return -1; }
|
||||||
|
if ((a as INodePropertyOptions).name.toLowerCase() > (b as INodePropertyOptions).name.toLowerCase()) { return 1; }
|
||||||
|
return 0;
|
||||||
|
}) as INodePropertyOptions[],
|
||||||
|
default: 'border',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Property Name',
|
||||||
|
name: 'dataPropertyName',
|
||||||
|
type: 'string',
|
||||||
|
default: 'data',
|
||||||
|
description: 'Name of the binary property in which the image data can be found.',
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// multiStep
|
||||||
|
// ----------------------------------
|
||||||
|
{
|
||||||
|
displayName: 'Operations',
|
||||||
|
name: 'operations',
|
||||||
|
placeholder: 'Add Operation',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'multiStep',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The operations to perform.',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'operations',
|
||||||
|
displayName: 'Operations',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
options: nodeOperations,
|
||||||
|
default: '',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
...nodeOperationOptions,
|
||||||
|
{
|
||||||
|
displayName: 'Font',
|
||||||
|
name: 'font',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
'operation': [
|
||||||
|
'text',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getFonts',
|
||||||
|
},
|
||||||
|
default: 'default',
|
||||||
|
description: 'The font to use.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
...nodeOperationOptions,
|
||||||
{
|
{
|
||||||
displayName: 'Options',
|
displayName: 'Options',
|
||||||
name: 'options',
|
name: 'options',
|
||||||
|
@ -882,17 +957,91 @@ export class EditImage implements INodeType {
|
||||||
|
|
||||||
const options = this.getNodeParameter('options', {}) as IDataObject;
|
const options = this.getNodeParameter('options', {}) as IDataObject;
|
||||||
|
|
||||||
let gmInstance: gm.State;
|
const cleanupFunctions: Array<() => void> = [];
|
||||||
if (operation === 'create') {
|
|
||||||
const backgroundColor = this.getNodeParameter('backgroundColor') as string;
|
|
||||||
const width = this.getNodeParameter('width') as number;
|
|
||||||
const height = this.getNodeParameter('height') as number;
|
|
||||||
|
|
||||||
gmInstance = gm(width, height, backgroundColor);
|
let gmInstance: gm.State;
|
||||||
if (!options.format) {
|
|
||||||
options.format = 'png';
|
const requiredOperationParameters: {
|
||||||
}
|
[key: string]: string[],
|
||||||
|
} = {
|
||||||
|
blur: [
|
||||||
|
'blur',
|
||||||
|
'sigma',
|
||||||
|
],
|
||||||
|
create: [
|
||||||
|
'backgroundColor',
|
||||||
|
'height',
|
||||||
|
'width',
|
||||||
|
],
|
||||||
|
crop: [
|
||||||
|
'height',
|
||||||
|
'positionX',
|
||||||
|
'positionY',
|
||||||
|
'width',
|
||||||
|
],
|
||||||
|
composite: [
|
||||||
|
'dataPropertyNameComposite',
|
||||||
|
'positionX',
|
||||||
|
'positionY',
|
||||||
|
],
|
||||||
|
draw: [
|
||||||
|
'color',
|
||||||
|
'cornerRadius',
|
||||||
|
'endPositionX',
|
||||||
|
'endPositionY',
|
||||||
|
'primitive',
|
||||||
|
'startPositionX',
|
||||||
|
'startPositionY',
|
||||||
|
],
|
||||||
|
information: [],
|
||||||
|
resize: [
|
||||||
|
'height',
|
||||||
|
'resizeOption',
|
||||||
|
'width',
|
||||||
|
],
|
||||||
|
rotate: [
|
||||||
|
'backgroundColor',
|
||||||
|
'rotate',
|
||||||
|
],
|
||||||
|
shear: [
|
||||||
|
'degreesX',
|
||||||
|
'degreesY',
|
||||||
|
],
|
||||||
|
text: [
|
||||||
|
'font',
|
||||||
|
'fontColor',
|
||||||
|
'fontSize',
|
||||||
|
'lineLength',
|
||||||
|
'positionX',
|
||||||
|
'positionY',
|
||||||
|
'text',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let operations: IDataObject[] = [];
|
||||||
|
if (operation === 'multiStep') {
|
||||||
|
// Operation parameters are already in the correct format
|
||||||
|
const operationsData = this.getNodeParameter('operations', { operations: [] }) as IDataObject;
|
||||||
|
operations = operationsData.operations as IDataObject[];
|
||||||
} else {
|
} else {
|
||||||
|
// Operation parameters have to first get collected
|
||||||
|
const operationParameters: IDataObject = {};
|
||||||
|
requiredOperationParameters[operation].forEach(parameterName => {
|
||||||
|
try {
|
||||||
|
operationParameters[parameterName] = this.getNodeParameter(parameterName);
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
{
|
||||||
|
operation,
|
||||||
|
...operationParameters,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operations[0].operation !== 'create') {
|
||||||
|
// "create" generates a new image so does not require any incoming data.
|
||||||
if (item.binary === undefined) {
|
if (item.binary === undefined) {
|
||||||
throw new Error('Item does not contain any binary data.');
|
throw new Error('Item does not contain any binary data.');
|
||||||
}
|
}
|
||||||
|
@ -905,60 +1054,8 @@ export class EditImage implements INodeType {
|
||||||
gmInstance = gmInstance.background('transparent');
|
gmInstance = gmInstance.background('transparent');
|
||||||
}
|
}
|
||||||
|
|
||||||
const cleanupFunctions: Array<() => void> = [];
|
if (operation === 'information') {
|
||||||
|
// Just return the information
|
||||||
if (operation === 'blur') {
|
|
||||||
const blur = this.getNodeParameter('blur') as number;
|
|
||||||
const sigma = this.getNodeParameter('sigma') as number;
|
|
||||||
gmInstance = gmInstance.blur(blur, sigma);
|
|
||||||
} else if (operation === 'border') {
|
|
||||||
const borderWidth = this.getNodeParameter('borderWidth') as number;
|
|
||||||
const borderHeight = this.getNodeParameter('borderHeight') as number;
|
|
||||||
const borderColor = this.getNodeParameter('borderColor') as string;
|
|
||||||
|
|
||||||
gmInstance = gmInstance.borderColor(borderColor).border(borderWidth, borderHeight);
|
|
||||||
} else if (operation === 'composite') {
|
|
||||||
const dataPropertyNameComposite = this.getNodeParameter('dataPropertyNameComposite') as string;
|
|
||||||
const positionX = this.getNodeParameter('positionX') as number;
|
|
||||||
const positionY = this.getNodeParameter('positionY') as number;
|
|
||||||
|
|
||||||
const geometryString = (positionX >= 0 ? '+' : '') + positionX + (positionY >= 0 ? '+' : '') + positionY;
|
|
||||||
|
|
||||||
if (item.binary![dataPropertyNameComposite as string] === undefined) {
|
|
||||||
throw new Error(`Item does not contain any binary data with the name "${dataPropertyNameComposite}".`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { fd, path, cleanup } = await file();
|
|
||||||
cleanupFunctions.push(cleanup);
|
|
||||||
fsWriteFileAsync(fd, Buffer.from(item.binary![dataPropertyNameComposite as string].data, BINARY_ENCODING));
|
|
||||||
|
|
||||||
gmInstance = gmInstance.composite(path).geometry(geometryString);
|
|
||||||
} else if (operation === 'crop') {
|
|
||||||
const width = this.getNodeParameter('width') as number;
|
|
||||||
const height = this.getNodeParameter('height') as number;
|
|
||||||
|
|
||||||
const positionX = this.getNodeParameter('positionX') as number;
|
|
||||||
const positionY = this.getNodeParameter('positionY') as number;
|
|
||||||
|
|
||||||
gmInstance = gmInstance.crop(width, height, positionX, positionY);
|
|
||||||
} else if (operation === 'draw') {
|
|
||||||
const startPositionX = this.getNodeParameter('startPositionX') as number;
|
|
||||||
const startPositionY = this.getNodeParameter('startPositionY') as number;
|
|
||||||
const endPositionX = this.getNodeParameter('endPositionX') as number;
|
|
||||||
const endPositionY = this.getNodeParameter('endPositionY') as number;
|
|
||||||
const primitive = this.getNodeParameter('primitive') as string;
|
|
||||||
const color = this.getNodeParameter('color') as string;
|
|
||||||
|
|
||||||
gmInstance = gmInstance.fill(color);
|
|
||||||
|
|
||||||
if (primitive === 'line') {
|
|
||||||
gmInstance = gmInstance.drawLine(startPositionX, startPositionY, endPositionX, endPositionY);
|
|
||||||
} else if (primitive === 'rectangle') {
|
|
||||||
const cornerRadius = this.getNodeParameter('cornerRadius') as number;
|
|
||||||
gmInstance = gmInstance.drawRectangle(startPositionX, startPositionY, endPositionX, endPositionY, cornerRadius || undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (operation === 'information') {
|
|
||||||
const imageData = await new Promise<IDataObject>((resolve, reject) => {
|
const imageData = await new Promise<IDataObject>((resolve, reject) => {
|
||||||
gmInstance = gmInstance.identify((error, imageData) => {
|
gmInstance = gmInstance.identify((error, imageData) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -970,10 +1067,59 @@ export class EditImage implements INodeType {
|
||||||
});
|
});
|
||||||
|
|
||||||
item.json = imageData;
|
item.json = imageData;
|
||||||
} else if (operation === 'resize') {
|
return item;
|
||||||
const width = this.getNodeParameter('width') as number;
|
}
|
||||||
const height = this.getNodeParameter('height') as number;
|
|
||||||
const resizeOption = this.getNodeParameter('resizeOption') as string;
|
for (let i = 0; i < operations.length; i++) {
|
||||||
|
const operationData = operations[i];
|
||||||
|
if (operationData.operation === 'blur') {
|
||||||
|
gmInstance = gmInstance!.blur(operationData.blur as number, operationData.sigma as number);
|
||||||
|
} else if (operationData.operation === 'border') {
|
||||||
|
gmInstance = gmInstance!.borderColor(operationData.borderColor as string).border(operationData.borderWidth as number, operationData.borderHeight as number);
|
||||||
|
} else if (operationData.operation === 'composite') {
|
||||||
|
const positionX = operationData.positionX as number;
|
||||||
|
const positionY = operationData.positionY as number;
|
||||||
|
|
||||||
|
const geometryString = (positionX >= 0 ? '+' : '') + positionX + (positionY >= 0 ? '+' : '') + positionY;
|
||||||
|
|
||||||
|
if (item.binary![operationData.dataPropertyNameComposite as string] === undefined) {
|
||||||
|
throw new Error(`Item does not contain any binary data with the name "${operationData.dataPropertyNameComposite}".`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { fd, path, cleanup } = await file();
|
||||||
|
cleanupFunctions.push(cleanup);
|
||||||
|
await fsWriteFileAsync(fd, Buffer.from(item.binary![operationData.dataPropertyNameComposite as string].data, BINARY_ENCODING));
|
||||||
|
|
||||||
|
if (operations[0].operation === 'create') {
|
||||||
|
// It seems like if the image gets created newly we have to create a new gm instance
|
||||||
|
// else it fails for some reason
|
||||||
|
gmInstance = gm(gmInstance!.stream('png')).geometry(geometryString).composite(path);
|
||||||
|
} else {
|
||||||
|
gmInstance = gmInstance!.geometry(geometryString).composite(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operations.length !== i + 1) {
|
||||||
|
// If there are other operations after the current one create a new gm instance
|
||||||
|
// because else things do get messed up
|
||||||
|
gmInstance = gm(gmInstance.stream());
|
||||||
|
}
|
||||||
|
} else if (operationData.operation === 'create') {
|
||||||
|
gmInstance = gm(operationData.width as number, operationData.height as number, operationData.backgroundColor as string);
|
||||||
|
if (!options.format) {
|
||||||
|
options.format = 'png';
|
||||||
|
}
|
||||||
|
} else if (operationData.operation === 'crop') {
|
||||||
|
gmInstance = gmInstance!.crop(operationData.width as number, operationData.height as number, operationData.positionX as number, operationData.positionY as number);
|
||||||
|
} else if (operationData.operation === 'draw') {
|
||||||
|
gmInstance = gmInstance!.fill(operationData.color as string);
|
||||||
|
|
||||||
|
if (operationData.primitive === 'line') {
|
||||||
|
gmInstance = gmInstance.drawLine(operationData.startPositionX as number, operationData.startPositionY as number, operationData.endPositionX as number, operationData.endPositionY as number);
|
||||||
|
} else if (operationData.primitive === 'rectangle') {
|
||||||
|
gmInstance = gmInstance.drawRectangle(operationData.startPositionX as number, operationData.startPositionY as number, operationData.endPositionX as number, operationData.endPositionY as number, operationData.cornerRadius as number || undefined);
|
||||||
|
}
|
||||||
|
} else if (operationData.operation === 'resize') {
|
||||||
|
const resizeOption = operationData.resizeOption as string;
|
||||||
|
|
||||||
// By default use "maximumArea"
|
// By default use "maximumArea"
|
||||||
let option: gm.ResizeOption = '@';
|
let option: gm.ResizeOption = '@';
|
||||||
|
@ -989,29 +1135,18 @@ export class EditImage implements INodeType {
|
||||||
option = '%';
|
option = '%';
|
||||||
}
|
}
|
||||||
|
|
||||||
gmInstance = gmInstance.resize(width, height, option);
|
gmInstance = gmInstance!.resize(operationData.width as number, operationData.height as number, option);
|
||||||
} else if (operation === 'rotate') {
|
} else if (operationData.operation === 'rotate') {
|
||||||
const rotate = this.getNodeParameter('rotate') as number;
|
gmInstance = gmInstance!.rotate(operationData.backgroundColor as string, operationData.rotate as number);
|
||||||
const backgroundColor = this.getNodeParameter('backgroundColor') as string;
|
} else if (operationData.operation === 'shear') {
|
||||||
gmInstance = gmInstance.rotate(backgroundColor, rotate);
|
gmInstance = gmInstance!.shear(operationData.degreesX as number, operationData.degreesY as number);
|
||||||
} else if (operation === 'shear') {
|
} else if (operationData.operation === 'text') {
|
||||||
const xDegrees = this.getNodeParameter('degreesX') as number;
|
|
||||||
const yDegress = this.getNodeParameter('degreesY') as number;
|
|
||||||
gmInstance = gmInstance.shear(xDegrees, yDegress);
|
|
||||||
} else if (operation === 'text') {
|
|
||||||
const fontColor = this.getNodeParameter('fontColor') as string;
|
|
||||||
const fontSize = this.getNodeParameter('fontSize') as number;
|
|
||||||
const lineLength = this.getNodeParameter('lineLength') as number;
|
|
||||||
const positionX = this.getNodeParameter('positionX') as number;
|
|
||||||
const positionY = this.getNodeParameter('positionY') as number;
|
|
||||||
const text = this.getNodeParameter('text') as string || '';
|
|
||||||
|
|
||||||
// Split the text in multiple lines
|
// Split the text in multiple lines
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
let currentLine = '';
|
let currentLine = '';
|
||||||
(text as string).split('\n').forEach((textLine: string) => {
|
(operationData.text as string).split('\n').forEach((textLine: string) => {
|
||||||
textLine.split(' ').forEach((textPart: string) => {
|
textLine.split(' ').forEach((textPart: string) => {
|
||||||
if ((currentLine.length + textPart.length + 1) > lineLength) {
|
if ((currentLine.length + textPart.length + 1) > (operationData.lineLength as number)) {
|
||||||
lines.push(currentLine.trim());
|
lines.push(currentLine.trim());
|
||||||
currentLine = `${textPart} `;
|
currentLine = `${textPart} `;
|
||||||
return;
|
return;
|
||||||
|
@ -1026,16 +1161,17 @@ export class EditImage implements INodeType {
|
||||||
// Combine the lines to a single string
|
// Combine the lines to a single string
|
||||||
const renderText = lines.join('\n');
|
const renderText = lines.join('\n');
|
||||||
|
|
||||||
if (options.font && options.font !== 'default') {
|
const font = options.font || operationData.font;
|
||||||
gmInstance = gmInstance.font(options.font as string);
|
|
||||||
|
if (font && font !== 'default') {
|
||||||
|
gmInstance = gmInstance!.font(font as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
gmInstance = gmInstance
|
gmInstance = gmInstance!
|
||||||
.fill(fontColor)
|
.fill(operationData.fontColor as string)
|
||||||
.fontSize(fontSize)
|
.fontSize(operationData.fontSize as number)
|
||||||
.drawText(positionX, positionY, renderText);
|
.drawText(operationData.positionX as number, operationData.positionY as number, renderText);
|
||||||
} else if (operation !== 'create') {
|
}
|
||||||
throw new Error(`The operation "${operation}" is not supported!`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const newItem: INodeExecutionData = {
|
const newItem: INodeExecutionData = {
|
||||||
|
@ -1049,22 +1185,24 @@ export class EditImage implements INodeType {
|
||||||
// but the incoming data does not get changed.
|
// but the incoming data does not get changed.
|
||||||
Object.assign(newItem.binary, item.binary);
|
Object.assign(newItem.binary, item.binary);
|
||||||
// Make a deep copy of the binary data we change
|
// Make a deep copy of the binary data we change
|
||||||
|
if (newItem.binary![dataPropertyName as string]) {
|
||||||
newItem.binary![dataPropertyName as string] = JSON.parse(JSON.stringify(newItem.binary![dataPropertyName as string]));
|
newItem.binary![dataPropertyName as string] = JSON.parse(JSON.stringify(newItem.binary![dataPropertyName as string]));
|
||||||
} else {
|
}
|
||||||
newItem.binary = {
|
}
|
||||||
[dataPropertyName as string]: {
|
|
||||||
|
if (newItem.binary![dataPropertyName as string] === undefined) {
|
||||||
|
newItem.binary![dataPropertyName as string] = {
|
||||||
data: '',
|
data: '',
|
||||||
mimeType: '',
|
mimeType: '',
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.quality !== undefined) {
|
if (options.quality !== undefined) {
|
||||||
gmInstance = gmInstance.quality(options.quality as number);
|
gmInstance = gmInstance!.quality(options.quality as number);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.format !== undefined) {
|
if (options.format !== undefined) {
|
||||||
gmInstance = gmInstance.setFormat(options.format as string);
|
gmInstance = gmInstance!.setFormat(options.format as string);
|
||||||
newItem.binary![dataPropertyName as string].fileExtension = options.format as string;
|
newItem.binary![dataPropertyName as string].fileExtension = options.format as string;
|
||||||
newItem.binary![dataPropertyName as string].mimeType = `image/${options.format}`;
|
newItem.binary![dataPropertyName as string].mimeType = `image/${options.format}`;
|
||||||
const fileName = newItem.binary![dataPropertyName as string].fileName;
|
const fileName = newItem.binary![dataPropertyName as string].fileName;
|
||||||
|
|
767
packages/nodes-base/nodes/Egoi/Egoi.node.ts
Normal file
767
packages/nodes-base/nodes/Egoi/Egoi.node.ts
Normal file
|
@ -0,0 +1,767 @@
|
||||||
|
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodePropertyOptions,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
egoiApiRequest,
|
||||||
|
egoiApiRequestAllItems,
|
||||||
|
simplify,
|
||||||
|
} from './GenericFunctions';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ICreateMemberBody,
|
||||||
|
} from './Interfaces';
|
||||||
|
|
||||||
|
import * as moment from 'moment-timezone';
|
||||||
|
|
||||||
|
export class Egoi implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'e-goi',
|
||||||
|
name: 'egoi',
|
||||||
|
icon: 'file:egoi.png',
|
||||||
|
group: ['output'],
|
||||||
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
|
description: 'Consume e-goi API',
|
||||||
|
defaults: {
|
||||||
|
name: 'e-goi',
|
||||||
|
color: '#4cacd6',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'egoiApi',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Resource',
|
||||||
|
name: 'resource',
|
||||||
|
type: 'options',
|
||||||
|
required: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Contact',
|
||||||
|
value: 'contact',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'contact',
|
||||||
|
description: 'The resource to operate on.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
required: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create a member',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Get a member',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get All',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Get all members',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update a member',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'List ID',
|
||||||
|
name: 'list',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getLists',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
'create',
|
||||||
|
'update',
|
||||||
|
'get',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'ID of list to operate on.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Email',
|
||||||
|
name: 'email',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Email address for a subscriber.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Contact ID',
|
||||||
|
name: 'contactId',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Contact ID of the subscriber.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Resolve Data',
|
||||||
|
name: 'resolveData',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: true,
|
||||||
|
description: 'By default the response just includes the contact id. If this option gets activated it<br />will resolve the data automatically.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Birth Date',
|
||||||
|
name: 'birth_date',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
description: 'Birth date of a subscriber.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Cellphone',
|
||||||
|
name: 'cellphone',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Cellphone of a subscriber.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Extra Fields',
|
||||||
|
name: 'extraFieldsUi',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Extra Field',
|
||||||
|
name: 'extraFieldValues',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValueButtonText: 'Add Field',
|
||||||
|
},
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Field ID',
|
||||||
|
name: 'field_id',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getExtraFields',
|
||||||
|
loadOptionsDependsOn: [
|
||||||
|
'list',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'First Name',
|
||||||
|
name: 'first_name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of a subscriber.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Last Name',
|
||||||
|
name: 'last_name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of a subscriber.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Status',
|
||||||
|
name: 'status',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Unconfirmed',
|
||||||
|
value: 'unconfirmed',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Active',
|
||||||
|
value: 'active',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Inactive',
|
||||||
|
value: 'inactive',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Removed',
|
||||||
|
value: 'removed',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'active',
|
||||||
|
description: `Subscriber's current status.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Tags IDs',
|
||||||
|
name: 'tagIds',
|
||||||
|
type: 'multiOptions',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getListTags',
|
||||||
|
},
|
||||||
|
default: [],
|
||||||
|
description: 'List of tag ids to be added',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
//--------------------
|
||||||
|
//----UPDATE MEMBER---
|
||||||
|
//--------------------
|
||||||
|
{
|
||||||
|
displayName: 'Update Fields',
|
||||||
|
name: 'updateFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Birth Date',
|
||||||
|
name: 'birth_date',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
description: 'Birth date of subscriber.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Cellphone',
|
||||||
|
name: 'cellphone',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Cellphone of subscriber.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Email',
|
||||||
|
name: 'email',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Email address for subscriber.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Extra Fields',
|
||||||
|
name: 'extraFieldsUi',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Extra Field',
|
||||||
|
name: 'extraFieldValues',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValueButtonText: 'Add Field',
|
||||||
|
},
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Field ID',
|
||||||
|
name: 'field_id',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getExtraFields',
|
||||||
|
loadOptionsDependsOn: [
|
||||||
|
'list',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'First Name',
|
||||||
|
name: 'first_name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of subscriber.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Last Name',
|
||||||
|
name: 'last_name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of subscriber.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Status',
|
||||||
|
name: 'status',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Unconfirmed',
|
||||||
|
value: 'unconfirmed',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Active',
|
||||||
|
value: 'active',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Inactive',
|
||||||
|
value: 'inactive',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Removed',
|
||||||
|
value: 'removed',
|
||||||
|
},
|
||||||
|
|
||||||
|
],
|
||||||
|
default: 'active',
|
||||||
|
description: `Subscriber's current status.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Tags IDs',
|
||||||
|
name: 'tagIds',
|
||||||
|
type: 'multiOptions',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getListTags',
|
||||||
|
},
|
||||||
|
default: [],
|
||||||
|
description: 'List of tag ids to be added',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By',
|
||||||
|
name: 'by',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Contact ID',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Email',
|
||||||
|
value: 'email',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'get',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: 'id',
|
||||||
|
description: 'Search by',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Contact ID',
|
||||||
|
name: 'contactId',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'get',
|
||||||
|
],
|
||||||
|
by: [
|
||||||
|
'id',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Contact ID of the subscriber.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Email',
|
||||||
|
name: 'email',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'get',
|
||||||
|
],
|
||||||
|
by: [
|
||||||
|
'email',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Email address for subscriber.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: false,
|
||||||
|
description: 'If all results should be returned or only up to a given limit.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
type: 'number',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
returnAll: [
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 500,
|
||||||
|
},
|
||||||
|
default: 100,
|
||||||
|
description: 'How many results to return.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Simple',
|
||||||
|
name: 'simple',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'get',
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'contact',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: true,
|
||||||
|
description: 'When set to true a simple version of the response will be returned else the RAW data.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
methods = {
|
||||||
|
loadOptions: {
|
||||||
|
async getLists(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const lists = await egoiApiRequestAllItems.call(this, 'items', 'GET', '/lists');
|
||||||
|
for (const list of lists) {
|
||||||
|
const listName = list.internal_name;
|
||||||
|
const listId = list.list_id;
|
||||||
|
returnData.push({
|
||||||
|
name: listName,
|
||||||
|
value: listId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
},
|
||||||
|
|
||||||
|
async getExtraFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const listId = this.getCurrentNodeParameter('list');
|
||||||
|
const extraFields = await egoiApiRequest.call(this, 'GET', `/lists/${listId}/fields`);
|
||||||
|
for (const field of extraFields) {
|
||||||
|
if (field.type === 'extra') {
|
||||||
|
const fieldName = field.name;
|
||||||
|
const fieldId = field.field_id;
|
||||||
|
returnData.push({
|
||||||
|
name: fieldName,
|
||||||
|
value: fieldId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
},
|
||||||
|
|
||||||
|
async getListTags(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const tagList = await egoiApiRequestAllItems.call(this, 'items', 'GET', '/tags');
|
||||||
|
for (const tag of tagList) {
|
||||||
|
const tagName = tag.name;
|
||||||
|
const tagId = tag.tag_id;
|
||||||
|
returnData.push({
|
||||||
|
name: tagName,
|
||||||
|
value: tagId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
|
||||||
|
let responseData;
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
const items = this.getInputData();
|
||||||
|
const length = items.length as unknown as number;
|
||||||
|
const operation = this.getNodeParameter('operation', 0) as string;
|
||||||
|
const resource = this.getNodeParameter('resource', 0) as string;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
try {
|
||||||
|
if (resource === 'contact') {
|
||||||
|
if (operation === 'create') {
|
||||||
|
const listId = this.getNodeParameter('list', i) as string;
|
||||||
|
|
||||||
|
const email = this.getNodeParameter('email', i) as string;
|
||||||
|
|
||||||
|
const resolveData = this.getNodeParameter('resolveData', i) as boolean;
|
||||||
|
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||||
|
|
||||||
|
const body: ICreateMemberBody = {
|
||||||
|
base: {
|
||||||
|
email,
|
||||||
|
},
|
||||||
|
extra: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (additionalFields.birth_date) {
|
||||||
|
additionalFields.birth_date = moment(additionalFields.birth_date as string).format('YYYY-MM-DD');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.extraFieldsUi) {
|
||||||
|
const extraFields = (additionalFields.extraFieldsUi as IDataObject).extraFieldValues as IDataObject[];
|
||||||
|
if (extraFields) {
|
||||||
|
body.extra = extraFields as unknown as [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(body.base, additionalFields);
|
||||||
|
|
||||||
|
responseData = await egoiApiRequest.call(this, 'POST', `/lists/${listId}/contacts`, body);
|
||||||
|
|
||||||
|
const contactId = responseData.contact_id;
|
||||||
|
|
||||||
|
if (additionalFields.tagIds) {
|
||||||
|
const tags = additionalFields.tagIds as string[];
|
||||||
|
for (const tag of tags) {
|
||||||
|
await egoiApiRequest.call(this, 'POST', `/lists/${listId}/contacts/actions/attach-tag`, { tag_id: tag, contacts: [contactId] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolveData) {
|
||||||
|
responseData = await egoiApiRequest.call(this, 'GET', `/lists/${listId}/contacts/${contactId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'get') {
|
||||||
|
|
||||||
|
const listId = this.getNodeParameter('list', i) as string;
|
||||||
|
|
||||||
|
const simple = this.getNodeParameter('simple', i) as boolean;
|
||||||
|
|
||||||
|
const by = this.getNodeParameter('by', 0) as string;
|
||||||
|
|
||||||
|
let endpoint = '';
|
||||||
|
|
||||||
|
if (by === 'id') {
|
||||||
|
const contactId = this.getNodeParameter('contactId', i) as string;
|
||||||
|
endpoint = `/lists/${listId}/contacts/${contactId}`;
|
||||||
|
} else {
|
||||||
|
const email = this.getNodeParameter('email', i) as string;
|
||||||
|
endpoint = `/lists/${listId}/contacts?email=${email}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await egoiApiRequest.call(this, 'GET', endpoint, {});
|
||||||
|
|
||||||
|
if (responseData.items) {
|
||||||
|
responseData = responseData.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (simple === true) {
|
||||||
|
const data = (await simplify.call(this, [responseData], listId))[0];
|
||||||
|
|
||||||
|
responseData = {
|
||||||
|
...data,
|
||||||
|
email_stats: responseData.email_stats,
|
||||||
|
sms_stats: responseData.sms_stats,
|
||||||
|
push_stats: responseData.push_stats,
|
||||||
|
webpush_stats: responseData.webpush_stats,
|
||||||
|
voice_stats: responseData.voice_stats,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'getAll') {
|
||||||
|
|
||||||
|
const listId = this.getNodeParameter('list', i) as string;
|
||||||
|
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', 0) as boolean;
|
||||||
|
|
||||||
|
const simple = this.getNodeParameter('simple', i) as boolean;
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
|
||||||
|
responseData = await egoiApiRequestAllItems.call(this, 'items', 'GET', `/lists/${listId}/contacts`, {});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const limit = this.getNodeParameter('limit', i) as number;
|
||||||
|
|
||||||
|
responseData = await egoiApiRequest.call(this, 'GET', `/lists/${listId}/contacts`, {}, { limit });
|
||||||
|
|
||||||
|
responseData = responseData.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (simple === true) {
|
||||||
|
responseData = await simplify.call(this, responseData, listId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'update') {
|
||||||
|
const listId = this.getNodeParameter('list', i) as string;
|
||||||
|
const contactId = this.getNodeParameter('contactId', i) as string;
|
||||||
|
const resolveData = this.getNodeParameter('resolveData', i) as boolean;
|
||||||
|
|
||||||
|
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||||
|
const body: ICreateMemberBody = {
|
||||||
|
base: {
|
||||||
|
},
|
||||||
|
extra: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (updateFields.birth_date) {
|
||||||
|
updateFields.birth_date = moment(updateFields.birth_date as string).format('YYYY-MM-DD');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateFields.extraFieldsUi) {
|
||||||
|
const extraFields = (updateFields.extraFieldsUi as IDataObject).extraFieldValues as IDataObject[];
|
||||||
|
if (extraFields) {
|
||||||
|
body.extra = extraFields as unknown as [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(body.base, updateFields);
|
||||||
|
|
||||||
|
responseData = await egoiApiRequest.call(this, 'PATCH', `/lists/${listId}/contacts/${contactId}`, body);
|
||||||
|
|
||||||
|
if (updateFields.tagIds) {
|
||||||
|
const tags = updateFields.tagIds as string[];
|
||||||
|
for (const tag of tags) {
|
||||||
|
await egoiApiRequest.call(this, 'POST', `/lists/${listId}/contacts/actions/attach-tag`, { tag_id: tag, contacts: [contactId] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolveData) {
|
||||||
|
responseData = await egoiApiRequest.call(this, 'GET', `/lists/${listId}/contacts/${contactId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (this.continueOnFail() !== true) {
|
||||||
|
throw error;
|
||||||
|
} else {
|
||||||
|
// Return the actual reason as error
|
||||||
|
returnData.push(
|
||||||
|
{
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(responseData)) {
|
||||||
|
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||||
|
} else {
|
||||||
|
returnData.push(responseData as IDataObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [this.helpers.returnJsonArray(returnData)];
|
||||||
|
}
|
||||||
|
}
|
126
packages/nodes-base/nodes/Egoi/GenericFunctions.ts
Normal file
126
packages/nodes-base/nodes/Egoi/GenericFunctions.ts
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
import {
|
||||||
|
OptionsWithUrl,
|
||||||
|
} from 'request';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IExecuteSingleFunctions,
|
||||||
|
IHookFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
interface IContact {
|
||||||
|
tags: [];
|
||||||
|
base: IDataObject;
|
||||||
|
extra: IDataObject[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldCache: {
|
||||||
|
[key: string]: IDataObject[];
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
|
||||||
|
export async function getFields(this: IExecuteFunctions, listId: string) {
|
||||||
|
if (fieldCache[listId]) {
|
||||||
|
return fieldCache[listId];
|
||||||
|
}
|
||||||
|
fieldCache[listId] = await egoiApiRequest.call(this, 'GET', `/lists/${listId}/fields`);
|
||||||
|
return fieldCache[listId];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function egoiApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, qs: IDataObject = {}, headers?: object): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
|
||||||
|
const credentials = this.getCredentials('egoiApi') as IDataObject;
|
||||||
|
|
||||||
|
const options: OptionsWithUrl = {
|
||||||
|
headers: {
|
||||||
|
'accept': 'application/json',
|
||||||
|
'Apikey': `${credentials.apiKey}`,
|
||||||
|
},
|
||||||
|
method,
|
||||||
|
qs,
|
||||||
|
body,
|
||||||
|
url: `https://api.egoiapp.com${endpoint}`,
|
||||||
|
json: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Object.keys(body).length === 0) {
|
||||||
|
delete options.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
return await this.helpers.request!(options);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
let errorMessage;
|
||||||
|
|
||||||
|
if (error.response && error.response.body) {
|
||||||
|
|
||||||
|
if (Array.isArray(error.response.body.errors)) {
|
||||||
|
const errors = error.response.body.errors;
|
||||||
|
errorMessage = errors.map((e: IDataObject) => e.detail);
|
||||||
|
} else {
|
||||||
|
errorMessage = error.response.body.detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`e-goi Error response [${error.statusCode}]: ${errorMessage}`);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function egoiApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
|
||||||
|
let responseData;
|
||||||
|
|
||||||
|
query.offset = 0;
|
||||||
|
query.count = 500;
|
||||||
|
|
||||||
|
do {
|
||||||
|
responseData = await egoiApiRequest.call(this, method, endpoint, body, query);
|
||||||
|
returnData.push.apply(returnData, responseData[propertyName]);
|
||||||
|
query.offset += query.count;
|
||||||
|
} while (
|
||||||
|
responseData[propertyName] && responseData[propertyName].length !== 0
|
||||||
|
);
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function simplify(this: IExecuteFunctions, contacts: IContact[], listId: string) {
|
||||||
|
let fields = await getFields.call(this, listId);
|
||||||
|
|
||||||
|
fields = fields.filter((element: IDataObject) => element.type === 'extra');
|
||||||
|
const fieldsKeyValue: IDataObject = {};
|
||||||
|
for (const field of fields) {
|
||||||
|
fieldsKeyValue[field.field_id as string] = field.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: IDataObject[] = [];
|
||||||
|
|
||||||
|
for (const contact of contacts) {
|
||||||
|
const extras = contact.extra.reduce(
|
||||||
|
(acumulator: IDataObject, currentValue: IDataObject): any => { // tslint:disable-line:no-any
|
||||||
|
const key = fieldsKeyValue[currentValue.field_id as string] as string;
|
||||||
|
return { [key]: currentValue.value, ...acumulator };
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
data.push({
|
||||||
|
...contact.base,
|
||||||
|
...extras,
|
||||||
|
tags: contact.tags,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
12
packages/nodes-base/nodes/Egoi/Interfaces.ts
Normal file
12
packages/nodes-base/nodes/Egoi/Interfaces.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
export interface ICreateMemberBody {
|
||||||
|
base: {
|
||||||
|
email?: string;
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
cellphone?: string;
|
||||||
|
birth_date?: string;
|
||||||
|
subscription_status?: string;
|
||||||
|
};
|
||||||
|
extra: [];
|
||||||
|
}
|
BIN
packages/nodes-base/nodes/Egoi/egoi.png
Normal file
BIN
packages/nodes-base/nodes/Egoi/egoi.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1,011 B |
|
@ -1,68 +0,0 @@
|
||||||
import { IExecuteFunctions } from 'n8n-core';
|
|
||||||
import {
|
|
||||||
INodeExecutionData,
|
|
||||||
INodeType,
|
|
||||||
INodeTypeDescription,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
|
|
||||||
export class Example implements INodeType {
|
|
||||||
description: INodeTypeDescription = {
|
|
||||||
displayName: 'Example',
|
|
||||||
name: 'example',
|
|
||||||
group: ['input'],
|
|
||||||
version: 1,
|
|
||||||
description: 'Example',
|
|
||||||
defaults: {
|
|
||||||
name: 'Example',
|
|
||||||
color: '#0000FF',
|
|
||||||
},
|
|
||||||
inputs: ['main'],
|
|
||||||
outputs: ['main'],
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
displayName: 'Age',
|
|
||||||
name: 'age',
|
|
||||||
type: 'number',
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Brand Name',
|
|
||||||
name: 'brandName',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
|
||||||
|
|
||||||
const items = this.getInputData();
|
|
||||||
|
|
||||||
if (items.length === 0) {
|
|
||||||
items.push({json: {}});
|
|
||||||
}
|
|
||||||
|
|
||||||
const returnData: INodeExecutionData[] = [];
|
|
||||||
let item: INodeExecutionData;
|
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
const age = this.getNodeParameter('age', i) as number | undefined;
|
|
||||||
const brandName = this.getNodeParameter('brandName', i) as string | undefined;
|
|
||||||
|
|
||||||
item = items[i];
|
|
||||||
|
|
||||||
const newItem: INodeExecutionData = {
|
|
||||||
json: {
|
|
||||||
age: age || item.json.age,
|
|
||||||
brandName: brandName || item.json.brandName,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
returnData.push(newItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.prepareOutputData(returnData);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -97,6 +97,11 @@ export class Ftp implements INodeType {
|
||||||
name: 'operation',
|
name: 'operation',
|
||||||
type: 'options',
|
type: 'options',
|
||||||
options: [
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete a file.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Download',
|
name: 'Download',
|
||||||
value: 'download',
|
value: 'download',
|
||||||
|
@ -107,6 +112,11 @@ export class Ftp implements INodeType {
|
||||||
value: 'list',
|
value: 'list',
|
||||||
description: 'List folder content.',
|
description: 'List folder content.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Rename',
|
||||||
|
value: 'rename',
|
||||||
|
description: 'Rename/move oldPath to newPath.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Upload',
|
name: 'Upload',
|
||||||
value: 'upload',
|
value: 'upload',
|
||||||
|
@ -117,6 +127,25 @@ export class Ftp implements INodeType {
|
||||||
description: 'Operation to perform.',
|
description: 'Operation to perform.',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// delete
|
||||||
|
// ----------------------------------
|
||||||
|
{
|
||||||
|
displayName: 'Path',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'delete',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: 'path',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The file path of the file to delete. Has to contain the full path.',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// download
|
// download
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
@ -152,6 +181,40 @@ export class Ftp implements INodeType {
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// rename
|
||||||
|
// ----------------------------------
|
||||||
|
{
|
||||||
|
displayName: 'Old Path',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'rename',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: 'oldPath',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The old path',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'New Path',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'rename',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: 'newPath',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The new path',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// upload
|
// upload
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
@ -318,9 +381,10 @@ export class Ftp implements INodeType {
|
||||||
items[i] = newItem;
|
items[i] = newItem;
|
||||||
|
|
||||||
if (protocol === 'sftp') {
|
if (protocol === 'sftp') {
|
||||||
const path = this.getNodeParameter('path', i) as string;
|
|
||||||
|
|
||||||
if (operation === 'list') {
|
if (operation === 'list') {
|
||||||
|
const path = this.getNodeParameter('path', i) as string;
|
||||||
|
|
||||||
const recursive = this.getNodeParameter('recursive', i) as boolean;
|
const recursive = this.getNodeParameter('recursive', i) as boolean;
|
||||||
|
|
||||||
if (recursive) {
|
if (recursive) {
|
||||||
|
@ -333,7 +397,27 @@ export class Ftp implements INodeType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (operation === 'delete') {
|
||||||
|
const path = this.getNodeParameter('path', i) as string;
|
||||||
|
|
||||||
|
responseData = await sftp!.delete(path);
|
||||||
|
|
||||||
|
returnItems.push({ json: { success: true } });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'rename') {
|
||||||
|
const oldPath = this.getNodeParameter('oldPath', i) as string;
|
||||||
|
|
||||||
|
const newPath = this.getNodeParameter('newPath', i) as string;
|
||||||
|
|
||||||
|
responseData = await sftp!.rename(oldPath, newPath);
|
||||||
|
|
||||||
|
returnItems.push({ json: { success: true } });
|
||||||
|
}
|
||||||
|
|
||||||
if (operation === 'download') {
|
if (operation === 'download') {
|
||||||
|
const path = this.getNodeParameter('path', i) as string;
|
||||||
|
|
||||||
responseData = await sftp!.get(path);
|
responseData = await sftp!.get(path);
|
||||||
|
|
||||||
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i) as string;
|
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i) as string;
|
||||||
|
@ -385,9 +469,9 @@ export class Ftp implements INodeType {
|
||||||
|
|
||||||
if (protocol === 'ftp') {
|
if (protocol === 'ftp') {
|
||||||
|
|
||||||
|
if (operation === 'list') {
|
||||||
const path = this.getNodeParameter('path', i) as string;
|
const path = this.getNodeParameter('path', i) as string;
|
||||||
|
|
||||||
if (operation === 'list') {
|
|
||||||
const recursive = this.getNodeParameter('recursive', i) as boolean;
|
const recursive = this.getNodeParameter('recursive', i) as boolean;
|
||||||
|
|
||||||
if (recursive) {
|
if (recursive) {
|
||||||
|
@ -400,7 +484,17 @@ export class Ftp implements INodeType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (operation === 'delete') {
|
||||||
|
const path = this.getNodeParameter('path', i) as string;
|
||||||
|
|
||||||
|
responseData = await ftp!.delete(path);
|
||||||
|
|
||||||
|
returnItems.push({ json: { success: true } });
|
||||||
|
}
|
||||||
|
|
||||||
if (operation === 'download') {
|
if (operation === 'download') {
|
||||||
|
const path = this.getNodeParameter('path', i) as string;
|
||||||
|
|
||||||
responseData = await ftp!.get(path);
|
responseData = await ftp!.get(path);
|
||||||
|
|
||||||
// Convert readable stream to buffer so that can be displayed properly
|
// Convert readable stream to buffer so that can be displayed properly
|
||||||
|
@ -420,6 +514,17 @@ export class Ftp implements INodeType {
|
||||||
returnItems.push(items[i]);
|
returnItems.push(items[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (operation === 'rename') {
|
||||||
|
|
||||||
|
const oldPath = this.getNodeParameter('oldPath', i) as string;
|
||||||
|
|
||||||
|
const newPath = this.getNodeParameter('newPath', i) as string;
|
||||||
|
|
||||||
|
responseData = await ftp!.rename(oldPath, newPath);
|
||||||
|
|
||||||
|
returnItems.push({ json: { success: true } });
|
||||||
|
}
|
||||||
|
|
||||||
if (operation === 'upload') {
|
if (operation === 'upload') {
|
||||||
const remotePath = this.getNodeParameter('path', i) as string;
|
const remotePath = this.getNodeParameter('path', i) as string;
|
||||||
const fileName = basename(remotePath);
|
const fileName = basename(remotePath);
|
||||||
|
|
|
@ -121,6 +121,13 @@ export class CloudFirestore implements INodeType {
|
||||||
{ documents: documentList },
|
{ documents: documentList },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
responseData = responseData.map((element: { found: { id: string, name: string } }) => {
|
||||||
|
if (element.found) {
|
||||||
|
element.found.id = (element.found.name as string).split('/').pop() as string;
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
});
|
||||||
|
|
||||||
if (simple === false) {
|
if (simple === false) {
|
||||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||||
} else {
|
} else {
|
||||||
|
@ -148,6 +155,9 @@ export class CloudFirestore implements INodeType {
|
||||||
`/${projectId}/databases/${database}/documents/${collection}`,
|
`/${projectId}/databases/${database}/documents/${collection}`,
|
||||||
document,
|
document,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
responseData.id = (responseData.name as string).split('/').pop();
|
||||||
|
|
||||||
if (simple === false) {
|
if (simple === false) {
|
||||||
returnData.push(responseData);
|
returnData.push(responseData);
|
||||||
} else {
|
} else {
|
||||||
|
@ -179,11 +189,16 @@ export class CloudFirestore implements INodeType {
|
||||||
) as IDataObject;
|
) as IDataObject;
|
||||||
responseData = getAllResponse.documents;
|
responseData = getAllResponse.documents;
|
||||||
}
|
}
|
||||||
|
responseData = responseData.map((element: IDataObject) => {
|
||||||
|
element.id = (element.name as string).split('/').pop();
|
||||||
|
return element;
|
||||||
|
});
|
||||||
if (simple === false) {
|
if (simple === false) {
|
||||||
returnData.push.apply(returnData, responseData);
|
returnData.push.apply(returnData, responseData);
|
||||||
} else {
|
} else {
|
||||||
returnData.push.apply(returnData, responseData.map((element: IDataObject) => fullDocumentToJson(element as IDataObject)));
|
returnData.push.apply(returnData, responseData.map((element: IDataObject) => fullDocumentToJson(element as IDataObject)));
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (operation === 'delete') {
|
} else if (operation === 'delete') {
|
||||||
const responseData: IDataObject[] = [];
|
const responseData: IDataObject[] = [];
|
||||||
|
|
||||||
|
@ -295,6 +310,14 @@ export class CloudFirestore implements INodeType {
|
||||||
`/${projectId}/databases/${database}/documents:runQuery`,
|
`/${projectId}/databases/${database}/documents:runQuery`,
|
||||||
JSON.parse(query),
|
JSON.parse(query),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
responseData = responseData.map((element: { document: { id: string, name: string } }) => {
|
||||||
|
if (element.document) {
|
||||||
|
element.document.id = (element.document.name as string).split('/').pop() as string;
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
});
|
||||||
|
|
||||||
if (simple === false) {
|
if (simple === false) {
|
||||||
returnData.push.apply(returnData, responseData);
|
returnData.push.apply(returnData, responseData);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -117,6 +117,7 @@ export function fullDocumentToJson(data: IDataObject): IDataObject {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_name: data.name,
|
_name: data.name,
|
||||||
|
_id: data.id,
|
||||||
_createTime: data.createTime,
|
_createTime: data.createTime,
|
||||||
_updateTime: data.updateTime,
|
_updateTime: data.updateTime,
|
||||||
...documentToJson(data.fields as IDataObject),
|
...documentToJson(data.fields as IDataObject),
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {
|
||||||
|
|
||||||
export class RealtimeDatabase implements INodeType {
|
export class RealtimeDatabase implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'Google Firebase Realtime Database',
|
displayName: 'Google Cloud Realtime Database',
|
||||||
name: 'googleFirebaseRealtimeDatabase',
|
name: 'googleFirebaseRealtimeDatabase',
|
||||||
icon: 'file:googleFirebaseRealtimeDatabase.png',
|
icon: 'file:googleFirebaseRealtimeDatabase.png',
|
||||||
group: ['input'],
|
group: ['input'],
|
||||||
|
|
|
@ -21,6 +21,10 @@ import {
|
||||||
ValueRenderOption,
|
ValueRenderOption,
|
||||||
} from './GoogleSheet';
|
} from './GoogleSheet';
|
||||||
|
|
||||||
|
import {
|
||||||
|
googleApiRequest,
|
||||||
|
} from './GenericFunctions';
|
||||||
|
|
||||||
export class GoogleSheets implements INodeType {
|
export class GoogleSheets implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'Google Sheets ',
|
displayName: 'Google Sheets ',
|
||||||
|
@ -28,6 +32,7 @@ export class GoogleSheets implements INodeType {
|
||||||
icon: 'file:googlesheets.png',
|
icon: 'file:googlesheets.png',
|
||||||
group: ['input', 'output'],
|
group: ['input', 'output'],
|
||||||
version: 1,
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
description: 'Read, update and write data to Google Sheets',
|
description: 'Read, update and write data to Google Sheets',
|
||||||
defaults: {
|
defaults: {
|
||||||
name: 'Google Sheets',
|
name: 'Google Sheets',
|
||||||
|
@ -76,10 +81,35 @@ export class GoogleSheets implements INodeType {
|
||||||
],
|
],
|
||||||
default: 'serviceAccount',
|
default: 'serviceAccount',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Resource',
|
||||||
|
name: 'resource',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Spreadsheet',
|
||||||
|
value: 'spreadsheet',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Sheet',
|
||||||
|
value: 'sheet',
|
||||||
|
},
|
||||||
|
|
||||||
|
],
|
||||||
|
default: 'sheet',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Operation',
|
displayName: 'Operation',
|
||||||
name: 'operation',
|
name: 'operation',
|
||||||
type: 'options',
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'sheet',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: 'Append',
|
name: 'Append',
|
||||||
|
@ -123,6 +153,13 @@ export class GoogleSheets implements INodeType {
|
||||||
displayName: 'Sheet ID',
|
displayName: 'Sheet ID',
|
||||||
name: 'sheetId',
|
name: 'sheetId',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'sheet',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
default: '',
|
default: '',
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The ID of the Google Sheet.<br />Found as part of the sheet URL https://docs.google.com/spreadsheets/d/{ID}/',
|
description: 'The ID of the Google Sheet.<br />Found as part of the sheet URL https://docs.google.com/spreadsheets/d/{ID}/',
|
||||||
|
@ -132,6 +169,11 @@ export class GoogleSheets implements INodeType {
|
||||||
name: 'range',
|
name: 'range',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'sheet',
|
||||||
|
],
|
||||||
|
},
|
||||||
hide: {
|
hide: {
|
||||||
operation: [
|
operation: [
|
||||||
'delete',
|
'delete',
|
||||||
|
@ -158,6 +200,9 @@ export class GoogleSheets implements INodeType {
|
||||||
},
|
},
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
|
resource: [
|
||||||
|
'sheet',
|
||||||
|
],
|
||||||
operation: [
|
operation: [
|
||||||
'delete',
|
'delete',
|
||||||
],
|
],
|
||||||
|
@ -254,6 +299,9 @@ export class GoogleSheets implements INodeType {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
|
resource: [
|
||||||
|
'sheet',
|
||||||
|
],
|
||||||
operation: [
|
operation: [
|
||||||
'read',
|
'read',
|
||||||
],
|
],
|
||||||
|
@ -269,6 +317,9 @@ export class GoogleSheets implements INodeType {
|
||||||
default: 'data',
|
default: 'data',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
|
resource: [
|
||||||
|
'sheet',
|
||||||
|
],
|
||||||
operation: [
|
operation: [
|
||||||
'read',
|
'read',
|
||||||
],
|
],
|
||||||
|
@ -289,6 +340,9 @@ export class GoogleSheets implements INodeType {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
|
resource: [
|
||||||
|
'sheet',
|
||||||
|
],
|
||||||
operation: [
|
operation: [
|
||||||
'update',
|
'update',
|
||||||
],
|
],
|
||||||
|
@ -304,6 +358,9 @@ export class GoogleSheets implements INodeType {
|
||||||
default: 'data',
|
default: 'data',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
|
resource: [
|
||||||
|
'sheet',
|
||||||
|
],
|
||||||
operation: [
|
operation: [
|
||||||
'update',
|
'update',
|
||||||
],
|
],
|
||||||
|
@ -327,6 +384,11 @@ export class GoogleSheets implements INodeType {
|
||||||
},
|
},
|
||||||
default: 1,
|
default: 1,
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'sheet',
|
||||||
|
],
|
||||||
|
},
|
||||||
hide: {
|
hide: {
|
||||||
operation: [
|
operation: [
|
||||||
'append',
|
'append',
|
||||||
|
@ -352,6 +414,11 @@ export class GoogleSheets implements INodeType {
|
||||||
minValue: 0,
|
minValue: 0,
|
||||||
},
|
},
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'sheet',
|
||||||
|
],
|
||||||
|
},
|
||||||
hide: {
|
hide: {
|
||||||
operation: [
|
operation: [
|
||||||
'clear',
|
'clear',
|
||||||
|
@ -379,6 +446,9 @@ export class GoogleSheets implements INodeType {
|
||||||
required: true,
|
required: true,
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
|
resource: [
|
||||||
|
'sheet',
|
||||||
|
],
|
||||||
operation: [
|
operation: [
|
||||||
'lookup',
|
'lookup',
|
||||||
],
|
],
|
||||||
|
@ -394,6 +464,9 @@ export class GoogleSheets implements INodeType {
|
||||||
placeholder: 'frank@example.com',
|
placeholder: 'frank@example.com',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
|
resource: [
|
||||||
|
'sheet',
|
||||||
|
],
|
||||||
operation: [
|
operation: [
|
||||||
'lookup',
|
'lookup',
|
||||||
],
|
],
|
||||||
|
@ -412,6 +485,9 @@ export class GoogleSheets implements INodeType {
|
||||||
default: 'id',
|
default: 'id',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
|
resource: [
|
||||||
|
'sheet',
|
||||||
|
],
|
||||||
operation: [
|
operation: [
|
||||||
'update',
|
'update',
|
||||||
],
|
],
|
||||||
|
@ -431,6 +507,9 @@ export class GoogleSheets implements INodeType {
|
||||||
default: {},
|
default: {},
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
|
resource: [
|
||||||
|
'sheet',
|
||||||
|
],
|
||||||
operation: [
|
operation: [
|
||||||
'append',
|
'append',
|
||||||
'lookup',
|
'lookup',
|
||||||
|
@ -566,6 +645,154 @@ export class GoogleSheets implements INodeType {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'spreadsheet',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create a spreadsheet',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
// ----------------------------------
|
||||||
|
// spreadsheet:create
|
||||||
|
// ----------------------------------
|
||||||
|
{
|
||||||
|
displayName: 'Title',
|
||||||
|
name: 'title',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'spreadsheet',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The title of the spreadsheet.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Sheets',
|
||||||
|
name: 'sheetsUi',
|
||||||
|
placeholder: 'Add Sheet',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'spreadsheet',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'sheetValues',
|
||||||
|
displayName: 'Sheet',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Sheet Properties',
|
||||||
|
name: 'propertiesUi',
|
||||||
|
placeholder: 'Add Property',
|
||||||
|
type: 'collection',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Hidden',
|
||||||
|
name: 'hidden',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'If the Sheet should be hidden in the UI',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Title',
|
||||||
|
name: 'title',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Title of the property to create',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'spreadsheet',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Locale',
|
||||||
|
name: 'locale',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'en_US',
|
||||||
|
description: 'The locale of the spreadsheet in one of the following formats:<br /><ul><li>en (639-1)</li><li>fil (639-2 if no 639-1 format exists)</li><li>en_US (combination of ISO language an country)</li><ul>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Recalculation Interval',
|
||||||
|
name: 'autoRecalc',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Default',
|
||||||
|
value: '',
|
||||||
|
description: 'Default value',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'On Change',
|
||||||
|
value: 'ON_CHANGE',
|
||||||
|
description: 'Volatile functions are updated on every change.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Minute',
|
||||||
|
value: 'MINUTE',
|
||||||
|
description: 'Volatile functions are updated on every change and every minute.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Hour',
|
||||||
|
value: 'HOUR',
|
||||||
|
description: ' Volatile functions are updated on every change and hourly.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: '',
|
||||||
|
description: 'Cell recalculation interval options.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -602,12 +829,16 @@ export class GoogleSheets implements INodeType {
|
||||||
|
|
||||||
|
|
||||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
|
||||||
|
const operation = this.getNodeParameter('operation', 0) as string;
|
||||||
|
const resource = this.getNodeParameter('resource', 0) as string;
|
||||||
|
|
||||||
|
if (resource === 'sheet') {
|
||||||
|
|
||||||
const spreadsheetId = this.getNodeParameter('sheetId', 0) as string;
|
const spreadsheetId = this.getNodeParameter('sheetId', 0) as string;
|
||||||
|
|
||||||
const sheet = new GoogleSheet(spreadsheetId, this);
|
const sheet = new GoogleSheet(spreadsheetId, this);
|
||||||
|
|
||||||
const operation = this.getNodeParameter('operation', 0) as string;
|
|
||||||
|
|
||||||
let range = '';
|
let range = '';
|
||||||
if (operation !== 'delete') {
|
if (operation !== 'delete') {
|
||||||
range = this.getNodeParameter('range', 0) as string;
|
range = this.getNodeParameter('range', 0) as string;
|
||||||
|
@ -786,6 +1017,61 @@ export class GoogleSheets implements INodeType {
|
||||||
return this.prepareOutputData(items);
|
return this.prepareOutputData(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource === 'spreadsheet') {
|
||||||
|
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
|
||||||
|
let responseData;
|
||||||
|
|
||||||
|
if (operation === 'create') {
|
||||||
|
// ----------------------------------
|
||||||
|
// create
|
||||||
|
// ----------------------------------
|
||||||
|
// https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/create
|
||||||
|
|
||||||
|
for (let i = 0; i < this.getInputData().length; i++) {
|
||||||
|
|
||||||
|
const title = this.getNodeParameter('title', i) as string;
|
||||||
|
const sheetsUi = this.getNodeParameter('sheetsUi', i, {}) as IDataObject;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
properties: {
|
||||||
|
title,
|
||||||
|
autoRecalc: undefined as undefined | string,
|
||||||
|
locale: undefined as undefined | string,
|
||||||
|
},
|
||||||
|
sheets: [] as IDataObject[],
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = this.getNodeParameter('options', i, {}) as IDataObject;
|
||||||
|
|
||||||
|
if (Object.keys(sheetsUi).length) {
|
||||||
|
const data = [];
|
||||||
|
const sheets = sheetsUi.sheetValues as IDataObject[];
|
||||||
|
for (const sheet of sheets) {
|
||||||
|
const properties = sheet.propertiesUi as IDataObject;
|
||||||
|
if (properties) {
|
||||||
|
data.push({ properties });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body.sheets = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.properties!.autoRecalc = options.autoRecalc ? (options.autoRecalc as string) : undefined;
|
||||||
|
body.properties!.locale = options.locale ? (options.locale as string) : undefined;
|
||||||
|
|
||||||
|
responseData = await googleApiRequest.call(this, 'POST', `/v4/spreadsheets`, body);
|
||||||
|
|
||||||
|
returnData.push(responseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return [this.helpers.returnJsonArray(returnData)];
|
||||||
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -355,6 +355,13 @@ export class HttpRequest implements INodeType {
|
||||||
default: 10000,
|
default: 10000,
|
||||||
description: 'Time in ms to wait for the server to send response headers (and start the response body) before aborting the request.',
|
description: 'Time in ms to wait for the server to send response headers (and start the response body) before aborting the request.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Use Querystring',
|
||||||
|
name: 'useQueryString',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'Set this option to true if you need arrays to be serialized as foo=bar&foo=baz instead of the default foo[0]=bar&foo[1]=baz.',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -683,6 +690,10 @@ export class HttpRequest implements INodeType {
|
||||||
requestOptions.timeout = options.timeout as number;
|
requestOptions.timeout = options.timeout as number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.useQueryString === true) {
|
||||||
|
requestOptions.useQuerystring = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (parametersAreJson === true) {
|
if (parametersAreJson === true) {
|
||||||
// Parameters are defined as JSON
|
// Parameters are defined as JSON
|
||||||
let optionData: OptionData;
|
let optionData: OptionData;
|
||||||
|
@ -927,7 +938,7 @@ export class HttpRequest implements INodeType {
|
||||||
newItem.binary![dataPropertyName] = await this.helpers.prepareBinaryData(response!, fileName);
|
newItem.binary![dataPropertyName] = await this.helpers.prepareBinaryData(response!, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
items[itemIndex] = newItem;
|
returnItems.push(newItem);
|
||||||
} else if (responseFormat === 'string') {
|
} else if (responseFormat === 'string') {
|
||||||
const dataPropertyName = this.getNodeParameter('dataPropertyName', 0) as string;
|
const dataPropertyName = this.getNodeParameter('dataPropertyName', 0) as string;
|
||||||
|
|
||||||
|
@ -980,12 +991,6 @@ export class HttpRequest implements INodeType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseFormat === 'file') {
|
|
||||||
// For file downloads the files get attached to the existing items
|
|
||||||
return this.prepareOutputData(items);
|
|
||||||
} else {
|
|
||||||
// For all other ones does the output items get replaced
|
|
||||||
return this.prepareOutputData(returnItems);
|
return this.prepareOutputData(returnItems);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,10 @@ export class If implements INodeType {
|
||||||
name: 'Larger Equal',
|
name: 'Larger Equal',
|
||||||
value: 'largerEqual',
|
value: 'largerEqual',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Is Empty',
|
||||||
|
value: 'isEmpty',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
default: 'smaller',
|
default: 'smaller',
|
||||||
description: 'Operation to decide where the the data should be mapped to.',
|
description: 'Operation to decide where the the data should be mapped to.',
|
||||||
|
@ -120,6 +124,13 @@ export class If implements INodeType {
|
||||||
displayName: 'Value 2',
|
displayName: 'Value 2',
|
||||||
name: 'value2',
|
name: 'value2',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
displayOptions: {
|
||||||
|
hide: {
|
||||||
|
operation: [
|
||||||
|
'isEmpty',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
default: 0,
|
default: 0,
|
||||||
description: 'The value to compare with the first one.',
|
description: 'The value to compare with the first one.',
|
||||||
},
|
},
|
||||||
|
@ -145,6 +156,10 @@ export class If implements INodeType {
|
||||||
name: 'Contains',
|
name: 'Contains',
|
||||||
value: 'contains',
|
value: 'contains',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Ends With',
|
||||||
|
value: 'endsWith',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Equal',
|
name: 'Equal',
|
||||||
value: 'equal',
|
value: 'equal',
|
||||||
|
@ -161,6 +176,14 @@ export class If implements INodeType {
|
||||||
name: 'Regex',
|
name: 'Regex',
|
||||||
value: 'regex',
|
value: 'regex',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Starts With',
|
||||||
|
value: 'startsWith',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Is Empty',
|
||||||
|
value: 'isEmpty',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
default: 'equal',
|
default: 'equal',
|
||||||
description: 'Operation to decide where the the data should be mapped to.',
|
description: 'Operation to decide where the the data should be mapped to.',
|
||||||
|
@ -172,6 +195,7 @@ export class If implements INodeType {
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
hide: {
|
hide: {
|
||||||
operation: [
|
operation: [
|
||||||
|
'isEmpty',
|
||||||
'regex',
|
'regex',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -236,12 +260,15 @@ export class If implements INodeType {
|
||||||
} = {
|
} = {
|
||||||
contains: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || '').toString().includes((value2 || '').toString()),
|
contains: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || '').toString().includes((value2 || '').toString()),
|
||||||
notContains: (value1: NodeParameterValue, value2: NodeParameterValue) => !(value1 || '').toString().includes((value2 || '').toString()),
|
notContains: (value1: NodeParameterValue, value2: NodeParameterValue) => !(value1 || '').toString().includes((value2 || '').toString()),
|
||||||
|
endsWith: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 as string).endsWith(value2 as string),
|
||||||
equal: (value1: NodeParameterValue, value2: NodeParameterValue) => value1 === value2,
|
equal: (value1: NodeParameterValue, value2: NodeParameterValue) => value1 === value2,
|
||||||
notEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => value1 !== value2,
|
notEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => value1 !== value2,
|
||||||
larger: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || 0) > (value2 || 0),
|
larger: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || 0) > (value2 || 0),
|
||||||
largerEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || 0) >= (value2 || 0),
|
largerEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || 0) >= (value2 || 0),
|
||||||
smaller: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || 0) < (value2 || 0),
|
smaller: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || 0) < (value2 || 0),
|
||||||
smallerEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || 0) <= (value2 || 0),
|
smallerEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 || 0) <= (value2 || 0),
|
||||||
|
startsWith: (value1: NodeParameterValue, value2: NodeParameterValue) => (value1 as string).startsWith(value2 as string),
|
||||||
|
isEmpty: (value1: NodeParameterValue) => [undefined, null, ''].includes(value1 as string),
|
||||||
regex: (value1: NodeParameterValue, value2: NodeParameterValue) => {
|
regex: (value1: NodeParameterValue, value2: NodeParameterValue) => {
|
||||||
const regexMatch = (value2 || '').toString().match(new RegExp('^/(.*?)/([gimy]*)$'));
|
const regexMatch = (value2 || '').toString().match(new RegExp('^/(.*?)/([gimy]*)$'));
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,9 @@ import {
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
|
INodePropertyOptions,
|
||||||
INodeType,
|
INodeType,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
@ -23,6 +25,11 @@ import {
|
||||||
userOperations,
|
userOperations,
|
||||||
} from './UserDescription';
|
} from './UserDescription';
|
||||||
|
|
||||||
|
import {
|
||||||
|
userListFields,
|
||||||
|
userListOperations,
|
||||||
|
} from './UserListDescription';
|
||||||
|
|
||||||
import * as moment from 'moment-timezone';
|
import * as moment from 'moment-timezone';
|
||||||
|
|
||||||
export class Iterable implements INodeType {
|
export class Iterable implements INodeType {
|
||||||
|
@ -60,6 +67,10 @@ export class Iterable implements INodeType {
|
||||||
name: 'User',
|
name: 'User',
|
||||||
value: 'user',
|
value: 'user',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'User List',
|
||||||
|
value: 'userList',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
default: 'user',
|
default: 'user',
|
||||||
description: 'The resource to operate on.',
|
description: 'The resource to operate on.',
|
||||||
|
@ -68,9 +79,28 @@ export class Iterable implements INodeType {
|
||||||
...eventFields,
|
...eventFields,
|
||||||
...userOperations,
|
...userOperations,
|
||||||
...userFields,
|
...userFields,
|
||||||
|
...userListOperations,
|
||||||
|
...userListFields,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
methods = {
|
||||||
|
loadOptions: {
|
||||||
|
// Get all the lists available channels
|
||||||
|
async getLists(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const { lists } = await iterableApiRequest.call(this, 'GET', '/lists');
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
for (const list of lists) {
|
||||||
|
returnData.push({
|
||||||
|
name: list.name,
|
||||||
|
value: list.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
const items = this.getInputData();
|
const items = this.getInputData();
|
||||||
const returnData: IDataObject[] = [];
|
const returnData: IDataObject[] = [];
|
||||||
|
@ -234,6 +264,74 @@ export class Iterable implements INodeType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (resource === 'userList') {
|
||||||
|
if (operation === 'add') {
|
||||||
|
//https://api.iterable.com/api/docs#lists_subscribe
|
||||||
|
const listId = this.getNodeParameter('listId', 0) as string;
|
||||||
|
|
||||||
|
const identifier = this.getNodeParameter('identifier', 0) as string;
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
listId: parseInt(listId, 10),
|
||||||
|
subscribers: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscribers: IDataObject[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
|
||||||
|
const value = this.getNodeParameter('value', i) as string;
|
||||||
|
|
||||||
|
if (identifier === 'email') {
|
||||||
|
subscribers.push({ email: value });
|
||||||
|
} else {
|
||||||
|
subscribers.push({ userId: value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.subscribers = subscribers;
|
||||||
|
|
||||||
|
responseData = await iterableApiRequest.call(this, 'POST', '/lists/subscribe', body);
|
||||||
|
|
||||||
|
returnData.push(responseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'remove') {
|
||||||
|
//https://api.iterable.com/api/docs#lists_unsubscribe
|
||||||
|
const listId = this.getNodeParameter('listId', 0) as string;
|
||||||
|
|
||||||
|
const identifier = this.getNodeParameter('identifier', 0) as string;
|
||||||
|
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', 0) as IDataObject;
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
listId: parseInt(listId, 10),
|
||||||
|
subscribers: [],
|
||||||
|
campaignId: additionalFields.campaignId as number,
|
||||||
|
channelUnsubscribe: additionalFields.channelUnsubscribe as boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscribers: IDataObject[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
|
||||||
|
const value = this.getNodeParameter('value', i) as string;
|
||||||
|
|
||||||
|
if (identifier === 'email') {
|
||||||
|
subscribers.push({ email: value });
|
||||||
|
} else {
|
||||||
|
subscribers.push({ userId: value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.subscribers = subscribers;
|
||||||
|
|
||||||
|
responseData = await iterableApiRequest.call(this, 'POST', '/lists/unsubscribe', body);
|
||||||
|
|
||||||
|
returnData.push(responseData);
|
||||||
|
}
|
||||||
|
}
|
||||||
return [this.helpers.returnJsonArray(returnData)];
|
return [this.helpers.returnJsonArray(returnData)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export const userOperations = [
|
export const userOperations = [
|
||||||
{
|
{
|
||||||
|
@ -38,9 +38,9 @@ export const userOperations = [
|
||||||
|
|
||||||
export const userFields = [
|
export const userFields = [
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* user:upsert */
|
/* user:upsert */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Identifier',
|
displayName: 'Identifier',
|
||||||
name: 'identifier',
|
name: 'identifier',
|
||||||
|
@ -167,9 +167,10 @@ export const userFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* user:delete */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* user:delete */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'By',
|
displayName: 'By',
|
||||||
name: 'by',
|
name: 'by',
|
||||||
|
@ -240,9 +241,10 @@ export const userFields = [
|
||||||
default: '',
|
default: '',
|
||||||
description: 'Email for a particular user',
|
description: 'Email for a particular user',
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* user:get */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* user:get */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'By',
|
displayName: 'By',
|
||||||
name: 'by',
|
name: 'by',
|
||||||
|
|
208
packages/nodes-base/nodes/Iterable/UserListDescription.ts
Normal file
208
packages/nodes-base/nodes/Iterable/UserListDescription.ts
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
import {
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const userListOperations = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'userList',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Add',
|
||||||
|
value: 'add',
|
||||||
|
description: 'Add user to list',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Remove',
|
||||||
|
value: 'remove',
|
||||||
|
description: 'Remove a user from a list',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'add',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
] as INodeProperties[];
|
||||||
|
|
||||||
|
export const userListFields = [
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* userList:add */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'List ID',
|
||||||
|
name: 'listId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getLists',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'userList',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'add',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Identifier to be used',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Identifier',
|
||||||
|
name: 'identifier',
|
||||||
|
type: 'options',
|
||||||
|
required: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Email',
|
||||||
|
value: 'email',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'User ID',
|
||||||
|
value: 'userId',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'userList',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'add',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Identifier to be used',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'userList',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'add',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* userList:remove */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'List ID',
|
||||||
|
name: 'listId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getLists',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'userList',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'remove',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Identifier to be used',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Identifier',
|
||||||
|
name: 'identifier',
|
||||||
|
type: 'options',
|
||||||
|
required: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Email',
|
||||||
|
value: 'email',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'User ID',
|
||||||
|
value: 'userId',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'userList',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'remove',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Identifier to be used',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'userList',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'remove',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'userList',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'remove',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Campaign ID',
|
||||||
|
name: 'campaignId',
|
||||||
|
type: 'number',
|
||||||
|
default: 0,
|
||||||
|
description: 'Attribute unsubscribe to a campaign',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Channel Unsubscribe',
|
||||||
|
name: 'channelUnsubscribe',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: `Unsubscribe email from list's associated channel - essentially a global unsubscribe`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as INodeProperties[];
|
488
packages/nodes-base/nodes/Jira/IssueCommentDescription.ts
Normal file
488
packages/nodes-base/nodes/Jira/IssueCommentDescription.ts
Normal file
|
@ -0,0 +1,488 @@
|
||||||
|
import {
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const issueCommentOperations = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Add',
|
||||||
|
value: 'add',
|
||||||
|
description: 'Add comment to issue',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Get a comment',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get All',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Get all comments',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Remove',
|
||||||
|
value: 'remove',
|
||||||
|
description: 'Remove a comment',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update a comment',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'add',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
] as INodeProperties[];
|
||||||
|
|
||||||
|
export const issueCommentFields = [
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* issueComment:add */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'Issue Key',
|
||||||
|
name: 'issueKey',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'add',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'issueComment Key',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'JSON Parameters',
|
||||||
|
name: 'jsonParameters',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'add',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Comment',
|
||||||
|
name: 'comment',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'add',
|
||||||
|
],
|
||||||
|
jsonParameters: [
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: `Comment's text`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Document Format (JSON)',
|
||||||
|
name: 'commentJson',
|
||||||
|
type: 'json',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'add',
|
||||||
|
],
|
||||||
|
jsonParameters: [
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: `The Atlassian Document Format (ADF). Online builder can be found <a href="https://developer.atlassian.com/cloud/jira/platform/apis/document/playground/" target="_blank">here</a>.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'add',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Expand',
|
||||||
|
name: 'expand',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Rendered Body',
|
||||||
|
value: 'renderedBody',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: '',
|
||||||
|
description: 'Use expand to include additional information about comments<br />in the response. This parameter accepts Rendered Body, which<br />returns the comment body rendered in HTML.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* issueComment:get */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'Issue Key',
|
||||||
|
name: 'issueKey',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'get',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The ID or key of the issue.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Comment ID',
|
||||||
|
name: 'commentId',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'get',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The ID of the comment',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'get',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Expand',
|
||||||
|
name: 'expand',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Rendered Body',
|
||||||
|
value: 'renderedBody',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: '',
|
||||||
|
description: 'Use expand to include additional information about comments in<br />the response. This parameter accepts Rendered Body, which<br />returns the comment body rendered in HTML.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* issueComment:getAll */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'Issue Key',
|
||||||
|
name: 'issueKey',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The ID or key of the issue.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
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: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
returnAll: [
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 100,
|
||||||
|
},
|
||||||
|
default: 50,
|
||||||
|
description: 'How many results to return.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Expand',
|
||||||
|
name: 'expand',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Rendered Body',
|
||||||
|
value: 'renderedBody',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: '',
|
||||||
|
description: 'Use expand to include additional information about comments in the<br />response. This parameter accepts Rendered Body, which returns the comment<br />body rendered in HTML.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* issueComment:remove */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'Issue Key',
|
||||||
|
name: 'issueKey',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'remove',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The ID or key of the issue.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Comment ID',
|
||||||
|
name: 'commentId',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'remove',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The ID of the comment.',
|
||||||
|
},
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* issueComment:update */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'Issue Key',
|
||||||
|
name: 'issueKey',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The Issue Comment key.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Comment ID',
|
||||||
|
name: 'commentId',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'The ID of the comment.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'JSON Parameters',
|
||||||
|
name: 'jsonParameters',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Comment',
|
||||||
|
name: 'comment',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
jsonParameters: [
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: `Comment's text.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Document Format (JSON)',
|
||||||
|
name: 'commentJson',
|
||||||
|
type: 'json',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
jsonParameters: [
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: `The Atlassian Document Format (ADF). Online builder can be found <a href="https://developer.atlassian.com/cloud/jira/platform/apis/document/playground/" target="_blank">here</a>.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'issueComment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Expand',
|
||||||
|
name: 'expand',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Rendered Body',
|
||||||
|
value: 'renderedBody',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: '',
|
||||||
|
description: 'Use expand to include additional information about comments in the response. This parameter accepts Rendered Body, which returns the comment body rendered in HTML.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as INodeProperties[];
|
|
@ -1,4 +1,6 @@
|
||||||
import { INodeProperties } from "n8n-workflow";
|
import {
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export const issueOperations = [
|
export const issueOperations = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,6 +17,11 @@ import {
|
||||||
validateJSON,
|
validateJSON,
|
||||||
} from './GenericFunctions';
|
} from './GenericFunctions';
|
||||||
|
|
||||||
|
import {
|
||||||
|
issueCommentFields,
|
||||||
|
issueCommentOperations,
|
||||||
|
} from './IssueCommentDescription';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
issueFields,
|
issueFields,
|
||||||
issueOperations,
|
issueOperations,
|
||||||
|
@ -96,12 +101,19 @@ export class Jira implements INodeType {
|
||||||
value: 'issue',
|
value: 'issue',
|
||||||
description: 'Creates an issue or, where the option to create subtasks is enabled in Jira, a subtask',
|
description: 'Creates an issue or, where the option to create subtasks is enabled in Jira, a subtask',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Issue Comment',
|
||||||
|
value: 'issueComment',
|
||||||
|
description: 'Get, create, update, and delete a comment from an issue.',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
default: 'issue',
|
default: 'issue',
|
||||||
description: 'Resource to consume.',
|
description: 'Resource to consume.',
|
||||||
},
|
},
|
||||||
...issueOperations,
|
...issueOperations,
|
||||||
...issueFields,
|
...issueFields,
|
||||||
|
...issueCommentOperations,
|
||||||
|
...issueCommentFields,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -642,6 +654,125 @@ export class Jira implements INodeType {
|
||||||
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}`, 'DELETE', {}, qs);
|
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}`, 'DELETE', {}, qs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (resource === 'issueComment') {
|
||||||
|
//https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-comments/#api-rest-api-3-issue-issueidorkey-comment-post
|
||||||
|
if (operation === 'add') {
|
||||||
|
const jsonParameters = this.getNodeParameter('jsonParameters', 0) as boolean;
|
||||||
|
const issueKey = this.getNodeParameter('issueKey', i) as string;
|
||||||
|
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||||
|
const body: IDataObject = {};
|
||||||
|
if (options.expand) {
|
||||||
|
qs.expand = options.expand as string;
|
||||||
|
delete options.expand;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(body, options);
|
||||||
|
if (jsonParameters === false) {
|
||||||
|
const comment = this.getNodeParameter('comment', i) as string;
|
||||||
|
Object.assign(body, {
|
||||||
|
body: {
|
||||||
|
type: 'doc',
|
||||||
|
version: 1,
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "paragraph",
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: comment,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const commentJson = this.getNodeParameter('commentJson', i) as string;
|
||||||
|
const json = validateJSON(commentJson);
|
||||||
|
if (json === '') {
|
||||||
|
throw new Error('Document Format must be a valid JSON');
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(body, { body: json });
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/3/issue/${issueKey}/comment`, 'POST', body, qs);
|
||||||
|
}
|
||||||
|
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-get
|
||||||
|
if (operation === 'get') {
|
||||||
|
const issueKey = this.getNodeParameter('issueKey', i) as string;
|
||||||
|
const commentId = this.getNodeParameter('commentId', i) as string;
|
||||||
|
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||||
|
Object.assign(qs, options);
|
||||||
|
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/3/issue/${issueKey}/comment/${commentId}`, 'GET', {}, qs);
|
||||||
|
|
||||||
|
}
|
||||||
|
//https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-comments/#api-rest-api-3-issue-issueidorkey-comment-get
|
||||||
|
if (operation === 'getAll') {
|
||||||
|
const issueKey = this.getNodeParameter('issueKey', i) as string;
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||||
|
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||||
|
const body: IDataObject = {};
|
||||||
|
Object.assign(qs, options);
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await jiraSoftwareCloudApiRequestAllItems.call(this, 'comments', `/api/3/issue/${issueKey}/comment`, 'GET', body, qs);
|
||||||
|
} else {
|
||||||
|
const limit = this.getNodeParameter('limit', i) as number;
|
||||||
|
body.maxResults = limit;
|
||||||
|
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/3/issue/${issueKey}/comment`, 'GET', body, qs);
|
||||||
|
responseData = responseData.comments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-comments/#api-rest-api-3-issue-issueidorkey-comment-id-delete
|
||||||
|
if (operation === 'remove') {
|
||||||
|
const issueKey = this.getNodeParameter('issueKey', i) as string;
|
||||||
|
const commentId = this.getNodeParameter('commentId', i) as string;
|
||||||
|
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/3/issue/${issueKey}/comment/${commentId}`, 'DELETE', {}, qs);
|
||||||
|
responseData = { success: true };
|
||||||
|
}
|
||||||
|
//https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-comments/#api-rest-api-3-issue-issueidorkey-comment-id-put
|
||||||
|
if (operation === 'update') {
|
||||||
|
const issueKey = this.getNodeParameter('issueKey', i) as string;
|
||||||
|
const commentId = this.getNodeParameter('commentId', i) as string;
|
||||||
|
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||||
|
const jsonParameters = this.getNodeParameter('jsonParameters', 0) as boolean;
|
||||||
|
const body: IDataObject = {};
|
||||||
|
if (options.expand) {
|
||||||
|
qs.expand = options.expand as string;
|
||||||
|
delete options.expand;
|
||||||
|
}
|
||||||
|
Object.assign(qs, options);
|
||||||
|
if (jsonParameters === false) {
|
||||||
|
const comment = this.getNodeParameter('comment', i) as string;
|
||||||
|
Object.assign(body, {
|
||||||
|
body: {
|
||||||
|
type: 'doc',
|
||||||
|
version: 1,
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "paragraph",
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: comment,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const commentJson = this.getNodeParameter('commentJson', i) as string;
|
||||||
|
const json = validateJSON(commentJson);
|
||||||
|
if (json === '') {
|
||||||
|
throw new Error('Document Format must be a valid JSON');
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(body, { body: json });
|
||||||
|
}
|
||||||
|
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/3/issue/${issueKey}/comment/${commentId}`, 'PUT', body, qs);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (Array.isArray(responseData)) {
|
if (Array.isArray(responseData)) {
|
||||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -63,7 +63,7 @@ export async function mauticApiRequest(this: IHookFunctions | IExecuteFunctions
|
||||||
|
|
||||||
options.uri = `${credentials.url}${options.uri}`;
|
options.uri = `${credentials.url}${options.uri}`;
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
returnData = await this.helpers.requestOAuth2.call(this, 'mauticOAuth2Api', options);
|
returnData = await this.helpers.requestOAuth2.call(this, 'mauticOAuth2Api', options, { includeCredentialsOnRefreshOnBody: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (returnData.errors) {
|
if (returnData.errors) {
|
||||||
|
|
|
@ -43,8 +43,10 @@ export async function messageBirdApiRequest(
|
||||||
uri: `https://rest.messagebird.com${resource}`,
|
uri: `https://rest.messagebird.com${resource}`,
|
||||||
json: true,
|
json: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (Object.keys(body).length === 0) {
|
||||||
|
delete options.body;
|
||||||
|
}
|
||||||
return await this.helpers.request(options);
|
return await this.helpers.request(options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.statusCode === 401) {
|
if (error.statusCode === 401) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
IExecuteFunctions,
|
IExecuteFunctions,
|
||||||
} from 'n8n-core';
|
} from 'n8n-core';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
|
@ -44,6 +44,10 @@ export class MessageBird implements INodeType {
|
||||||
name: 'SMS',
|
name: 'SMS',
|
||||||
value: 'sms',
|
value: 'sms',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Balance',
|
||||||
|
value: 'balance',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
default: 'sms',
|
default: 'sms',
|
||||||
description: 'The resource to operate on.',
|
description: 'The resource to operate on.',
|
||||||
|
@ -69,6 +73,27 @@ export class MessageBird implements INodeType {
|
||||||
default: 'send',
|
default: 'send',
|
||||||
description: 'The operation to perform.',
|
description: 'The operation to perform.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'balance',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Get the balance',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'get',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// sms:send
|
// sms:send
|
||||||
|
@ -133,6 +158,16 @@ export class MessageBird implements INodeType {
|
||||||
displayName: 'Additional Fields',
|
displayName: 'Additional Fields',
|
||||||
name: 'additionalFields',
|
name: 'additionalFields',
|
||||||
type: 'collection',
|
type: 'collection',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'send',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'sms',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
placeholder: 'Add Fields',
|
placeholder: 'Add Fields',
|
||||||
default: {},
|
default: {},
|
||||||
options: [
|
options: [
|
||||||
|
@ -269,11 +304,12 @@ export class MessageBird implements INodeType {
|
||||||
let resource: string;
|
let resource: string;
|
||||||
|
|
||||||
// For POST
|
// For POST
|
||||||
let bodyRequest: IDataObject;
|
let bodyRequest: IDataObject = {};
|
||||||
// For Query string
|
// For Query string
|
||||||
let qs: IDataObject;
|
let qs: IDataObject;
|
||||||
|
|
||||||
let requestMethod;
|
let requestMethod;
|
||||||
|
let requestPath;
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
qs = {};
|
qs = {};
|
||||||
|
@ -289,6 +325,7 @@ export class MessageBird implements INodeType {
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
requestMethod = 'POST';
|
requestMethod = 'POST';
|
||||||
|
requestPath = '/messages';
|
||||||
const originator = this.getNodeParameter('originator', i) as string;
|
const originator = this.getNodeParameter('originator', i) as string;
|
||||||
const body = this.getNodeParameter('message', i) as string;
|
const body = this.getNodeParameter('message', i) as string;
|
||||||
|
|
||||||
|
@ -337,21 +374,27 @@ export class MessageBird implements INodeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
const receivers = this.getNodeParameter('recipients', i) as string;
|
const receivers = this.getNodeParameter('recipients', i) as string;
|
||||||
|
|
||||||
bodyRequest.recipients = receivers.split(',').map(item => {
|
bodyRequest.recipients = receivers.split(',').map(item => {
|
||||||
|
|
||||||
return parseInt(item, 10);
|
return parseInt(item, 10);
|
||||||
});
|
});
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
throw new Error(`The operation "${operation}" is not known!`);
|
throw new Error(`The operation "${operation}" is not known!`);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
} else if (resource === 'balance') {
|
||||||
|
requestMethod = 'GET';
|
||||||
|
requestPath = '/balance';
|
||||||
|
}
|
||||||
|
else {
|
||||||
throw new Error(`The resource "${resource}" is not known!`);
|
throw new Error(`The resource "${resource}" is not known!`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const responseData = await messageBirdApiRequest.call(
|
const responseData = await messageBirdApiRequest.call(
|
||||||
this,
|
this,
|
||||||
requestMethod,
|
requestMethod,
|
||||||
'/messages',
|
requestPath,
|
||||||
bodyRequest,
|
bodyRequest,
|
||||||
qs,
|
qs,
|
||||||
);
|
);
|
||||||
|
|
304
packages/nodes-base/nodes/Microsoft/Outlook/DraftDescription.ts
Normal file
304
packages/nodes-base/nodes/Microsoft/Outlook/DraftDescription.ts
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
import {
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const draftOperations = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'draft',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create a new email draft',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete a draft',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Get a single draft',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Send',
|
||||||
|
value: 'send',
|
||||||
|
description: 'Send an existing draft message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update a draft',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
] as INodeProperties[];
|
||||||
|
|
||||||
|
export const draftFields = [
|
||||||
|
{
|
||||||
|
displayName: 'Message ID',
|
||||||
|
name: 'messageId',
|
||||||
|
description: 'Message ID',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'draft',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'delete',
|
||||||
|
'get',
|
||||||
|
'send',
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// draft:create
|
||||||
|
{
|
||||||
|
displayName: 'Subject',
|
||||||
|
name: 'subject',
|
||||||
|
description: 'The subject of the message.',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'draft',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Body Content',
|
||||||
|
name: 'bodyContent',
|
||||||
|
description: 'Message body content.',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'draft',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'draft',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Attachments',
|
||||||
|
name: 'attachments',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
placeholder: 'Add Attachment',
|
||||||
|
default: {},
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'attachments',
|
||||||
|
displayName: 'Attachment',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Binary Property Name',
|
||||||
|
name: 'binaryPropertyName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of the binary property containing the data to be added to the email as an attachment',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'BCC Recipients',
|
||||||
|
name: 'bccRecipients',
|
||||||
|
description: 'Email addresses of BCC recipients.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Body Content Type',
|
||||||
|
name: 'bodyContentType',
|
||||||
|
description: 'Message body content type.',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'HTML',
|
||||||
|
value: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Text',
|
||||||
|
value: 'Text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Categories',
|
||||||
|
name: 'categories',
|
||||||
|
type: 'multiOptions',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getCategories',
|
||||||
|
},
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'CC Recipients',
|
||||||
|
name: 'ccRecipients',
|
||||||
|
description: 'Email addresses of CC recipients.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Custom Headers',
|
||||||
|
name: 'internetMessageHeaders',
|
||||||
|
placeholder: 'Add Header',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'headers',
|
||||||
|
displayName: 'Header',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of the header.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Value to set for the header.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'From',
|
||||||
|
name: 'from',
|
||||||
|
description: 'The owner of the mailbox which the message is sent.<br>Must correspond to the actual mailbox used.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Importance',
|
||||||
|
name: 'importance',
|
||||||
|
description: 'The importance of the message.',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Low',
|
||||||
|
value: 'Low',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Normal',
|
||||||
|
value: 'Normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'High',
|
||||||
|
value: 'High',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'Low',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Read Receipt Requested',
|
||||||
|
name: 'isReadReceiptRequested',
|
||||||
|
description: 'Indicates whether a read receipt is requested for the message.',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Recipients',
|
||||||
|
name: 'toRecipients',
|
||||||
|
description: 'Email addresses of recipients. Multiple can be added separated by comma.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Reply To',
|
||||||
|
name: 'replyTo',
|
||||||
|
description: 'Email addresses to use when replying.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// draft:send
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'draft',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'send',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Recipients',
|
||||||
|
name: 'recipients',
|
||||||
|
description: 'Email addresses of recipients. Mutiple can be set separated by comma.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
] as INodeProperties[];
|
|
@ -0,0 +1,209 @@
|
||||||
|
import {
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const draftMessageSharedFields = [
|
||||||
|
|
||||||
|
// Get & Get All operations
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'draft',
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'get',
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Attachments Prefix',
|
||||||
|
name: 'dataPropertyAttachmentsPrefixName',
|
||||||
|
type: 'string',
|
||||||
|
default: 'attachment_',
|
||||||
|
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: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Fields the response will contain. Multiple can be added separated by comma.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Filter',
|
||||||
|
name: 'filter',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'isRead eq false',
|
||||||
|
description: 'Microsoft Graph API OData $filter query. Information about the syntax can be found <a href="https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter" target="_blank">here</a>.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update operation
|
||||||
|
{
|
||||||
|
displayName: 'Update Fields',
|
||||||
|
name: 'updateFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'draft',
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'BCC Recipients',
|
||||||
|
name: 'bccRecipients',
|
||||||
|
description: 'Email addresses of BCC recipients.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Body Content',
|
||||||
|
name: 'bodyContent',
|
||||||
|
description: 'Message body content.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Body Content Type',
|
||||||
|
name: 'bodyContentType',
|
||||||
|
description: 'Message body content type.',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'HTML',
|
||||||
|
value: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Text',
|
||||||
|
value: 'Text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Categories',
|
||||||
|
name: 'categories',
|
||||||
|
type: 'multiOptions',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getCategories',
|
||||||
|
},
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'CC Recipients',
|
||||||
|
name: 'ccRecipients',
|
||||||
|
description: 'Email addresses of CC recipients.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Custom Headers',
|
||||||
|
name: 'internetMessageHeaders',
|
||||||
|
placeholder: 'Add Header',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'headers',
|
||||||
|
displayName: 'Header',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of the header.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Value to set for the header.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'From',
|
||||||
|
name: 'from',
|
||||||
|
description: 'The owner of the mailbox which the message is sent.<br>Must correspond to the actual mailbox used.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Importance',
|
||||||
|
name: 'importance',
|
||||||
|
description: 'The importance of the message.',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Low',
|
||||||
|
value: 'Low',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Normal',
|
||||||
|
value: 'Normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'High',
|
||||||
|
value: 'High',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'Low',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Read Receipt Requested',
|
||||||
|
name: 'isReadReceiptRequested',
|
||||||
|
description: 'Indicates whether a read receipt is requested for the message.',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Recipients',
|
||||||
|
name: 'toRecipients',
|
||||||
|
description: 'Email addresses of recipients. Multiple can be added separated by comma.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Reply To',
|
||||||
|
name: 'replyTo',
|
||||||
|
description: 'Email addresses to use when replying.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Subject',
|
||||||
|
name: 'subject',
|
||||||
|
description: 'The subject of the message.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
] as INodeProperties[];
|
311
packages/nodes-base/nodes/Microsoft/Outlook/FolderDescription.ts
Normal file
311
packages/nodes-base/nodes/Microsoft/Outlook/FolderDescription.ts
Normal file
|
@ -0,0 +1,311 @@
|
||||||
|
import {
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const folderOperations = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'folder',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create a new mail folder in the root folder of the user\'s mailbox',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete a folder',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Get a single folder details',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get All',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Get all folders under the root folder of the signed-in user',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Children',
|
||||||
|
value: 'getChildren',
|
||||||
|
description: 'Lists all child folders under the folder',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
] as INodeProperties[];
|
||||||
|
|
||||||
|
export const folderFields = [
|
||||||
|
{
|
||||||
|
displayName: 'Folder ID',
|
||||||
|
name: 'folderId',
|
||||||
|
description: 'Folder ID',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'folder',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'delete',
|
||||||
|
'get',
|
||||||
|
'getChildren',
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// folder:list, getChildren, listMessages
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'folder',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
'getChildren',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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: [
|
||||||
|
'folder',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
'getChildren',
|
||||||
|
],
|
||||||
|
returnAll: [false],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 500,
|
||||||
|
},
|
||||||
|
default: 100,
|
||||||
|
description: 'How many results to return.',
|
||||||
|
},
|
||||||
|
// folder:create
|
||||||
|
{
|
||||||
|
displayName: 'Type',
|
||||||
|
name: 'folderType',
|
||||||
|
description: 'Folder Type',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Folder',
|
||||||
|
value: 'folder',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Search Folder',
|
||||||
|
value: 'searchFolder',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'folder',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: 'folder',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Display Name',
|
||||||
|
name: 'displayName',
|
||||||
|
description: 'Name of the folder.',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'folder',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Include Nested Folders',
|
||||||
|
name: 'includeNestedFolders',
|
||||||
|
description: 'Include child folders in the search.',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'folder',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
folderType: [
|
||||||
|
'searchFolder',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Source Folder IDs',
|
||||||
|
name: 'sourceFolderIds',
|
||||||
|
description: 'The mailbox folders that should be mined.',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: [],
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'folder',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
folderType: [
|
||||||
|
'searchFolder',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Filter Query',
|
||||||
|
name: 'filterQuery',
|
||||||
|
description: 'The OData query to filter the messages.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'folder',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
],
|
||||||
|
folderType: [
|
||||||
|
'searchFolder',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'folder',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'get',
|
||||||
|
'getAll',
|
||||||
|
'getChildren',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Fields the response will contain. Multiple can be added separated by ,.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Filter',
|
||||||
|
name: 'filter',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Microsoft Graph API OData $filter query.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// folder:update
|
||||||
|
{
|
||||||
|
displayName: 'Update Fields',
|
||||||
|
name: 'updateFields',
|
||||||
|
description: 'Fields to update.',
|
||||||
|
type: 'collection',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'folder',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'update',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Display Name',
|
||||||
|
name: 'displayName',
|
||||||
|
description: 'Name of the folder.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Filter Query',
|
||||||
|
name: 'filterQuery',
|
||||||
|
description: 'The OData query to filter the messages. Only for search folders.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Include Nested Folders',
|
||||||
|
name: 'includeNestedFolders',
|
||||||
|
description: 'Include child folders in the search. Only for search folders.',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Source Folder IDs',
|
||||||
|
name: 'sourceFolderIds',
|
||||||
|
description: 'The mailbox folders that should be mined. Only for search folders.',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
] as INodeProperties[];
|
|
@ -0,0 +1,122 @@
|
||||||
|
import {
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const folderMessageOperations = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'folderMessage',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Get All',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Get all the messages in a folder',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
] as INodeProperties[];
|
||||||
|
|
||||||
|
export const folderMessageFields = [
|
||||||
|
{
|
||||||
|
displayName: 'Folder ID',
|
||||||
|
name: 'folderId',
|
||||||
|
description: 'Folder ID',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'folderMessage',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'folderMessage',
|
||||||
|
],
|
||||||
|
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: [
|
||||||
|
'folderMessage',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
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: [
|
||||||
|
'folderMessage',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Fields the response will contain. Multiple can be added separated by ,.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Filter',
|
||||||
|
name: 'filter',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Microsoft Graph API OData $filter query.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as INodeProperties[];
|
175
packages/nodes-base/nodes/Microsoft/Outlook/GenericFunctions.ts
Normal file
175
packages/nodes-base/nodes/Microsoft/Outlook/GenericFunctions.ts
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
import {
|
||||||
|
OptionsWithUri,
|
||||||
|
} from 'request';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IExecuteSingleFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}, option: IDataObject = { json: true }): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
const options: OptionsWithUri = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
method,
|
||||||
|
body,
|
||||||
|
qs,
|
||||||
|
uri: uri || `https://graph.microsoft.com/v1.0/me${resource}`,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
Object.assign(options, option);
|
||||||
|
|
||||||
|
if (Object.keys(headers).length !== 0) {
|
||||||
|
options.headers = Object.assign({}, options.headers, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(body).length === 0) {
|
||||||
|
delete options.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
return await this.helpers.requestOAuth2.call(this, 'microsoftOutlookOAuth2Api', options);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response && error.response.body && error.response.body.error && error.response.body.error.message) {
|
||||||
|
// Try to return the error prettier
|
||||||
|
throw new Error(`Microsoft error response [${error.statusCode}]: ${error.response.body.error.message}`);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function microsoftApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, headers: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
|
||||||
|
let responseData;
|
||||||
|
let uri: string | undefined;
|
||||||
|
query['$top'] = 100;
|
||||||
|
|
||||||
|
do {
|
||||||
|
responseData = await microsoftApiRequest.call(this, method, endpoint, body, query, uri, headers);
|
||||||
|
uri = responseData['@odata.nextLink'];
|
||||||
|
returnData.push.apply(returnData, responseData[propertyName]);
|
||||||
|
} while (
|
||||||
|
responseData['@odata.nextLink'] !== undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function microsoftApiRequestAllItemsSkip(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, headers: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
|
||||||
|
let responseData;
|
||||||
|
query['$top'] = 100;
|
||||||
|
query['$skip'] = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
responseData = await microsoftApiRequest.call(this, method, endpoint, body, query, undefined, headers);
|
||||||
|
query['$skip'] += query['$top'];
|
||||||
|
returnData.push.apply(returnData, responseData[propertyName]);
|
||||||
|
} while (
|
||||||
|
responseData['value'].length !== 0
|
||||||
|
);
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeRecipient(email: string) {
|
||||||
|
return {
|
||||||
|
emailAddress: {
|
||||||
|
address: email,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createMessage(fields: IDataObject) {
|
||||||
|
const message: IDataObject = {};
|
||||||
|
|
||||||
|
// Create body object
|
||||||
|
if (fields.bodyContent || fields.bodyContentType) {
|
||||||
|
const bodyObject = {
|
||||||
|
content: fields.bodyContent,
|
||||||
|
contentType: fields.bodyContentType,
|
||||||
|
};
|
||||||
|
|
||||||
|
message['body'] = bodyObject;
|
||||||
|
delete fields['bodyContent'];
|
||||||
|
delete fields['bodyContentType'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle custom headers
|
||||||
|
if ('internetMessageHeaders' in fields && 'headers' in (fields.internetMessageHeaders as IDataObject)) {
|
||||||
|
fields.internetMessageHeaders = (fields.internetMessageHeaders as IDataObject).headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle recipient fields
|
||||||
|
['bccRecipients', 'ccRecipients', 'replyTo', 'sender', 'toRecipients'].forEach(key => {
|
||||||
|
if (Array.isArray(fields[key])) {
|
||||||
|
fields[key] = (fields[key] as string[]).map(email => makeRecipient(email));
|
||||||
|
} else if (fields[key] !== undefined) {
|
||||||
|
fields[key] = (fields[key] as string).split(',').map((recipient: string) => makeRecipient(recipient));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
['from', 'sender'].forEach(key => {
|
||||||
|
if (fields[key] !== undefined) {
|
||||||
|
fields[key] = makeRecipient(fields[key] as string);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Object.assign(message, fields);
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downloadAttachments(this: IExecuteFunctions, messages: IDataObject[] | IDataObject, prefix: string) {
|
||||||
|
const elements: INodeExecutionData[] = [];
|
||||||
|
if (!Array.isArray(messages)) {
|
||||||
|
messages = [messages];
|
||||||
|
}
|
||||||
|
for (const message of messages) {
|
||||||
|
const element: INodeExecutionData = {
|
||||||
|
json: message,
|
||||||
|
binary: {},
|
||||||
|
};
|
||||||
|
if (message.hasAttachments === true) {
|
||||||
|
const attachments = await microsoftApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'value',
|
||||||
|
'GET',
|
||||||
|
`/messages/${message.id}/attachments`,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
for (const [index, attachment] of attachments.entries()) {
|
||||||
|
const response = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/messages/${message.id}/attachments/${attachment.id}/$value`,
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
{ encoding: null, resolveWithFullResponse: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = Buffer.from(response.body as string, 'utf8');
|
||||||
|
element.binary![`${prefix}${index}`] = await this.helpers.prepareBinaryData(data as unknown as Buffer, attachment.name, attachment.contentType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(element.binary!).length === 0) {
|
||||||
|
delete element.binary;
|
||||||
|
}
|
||||||
|
elements.push(element);
|
||||||
|
}
|
||||||
|
return elements;
|
||||||
|
}
|
|
@ -0,0 +1,216 @@
|
||||||
|
import {
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const messageAttachmentOperations = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'messageAttachment',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Add',
|
||||||
|
value: 'add',
|
||||||
|
description: 'Add an attachment to a message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Download',
|
||||||
|
value: 'download',
|
||||||
|
description: 'Download attachment content',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Get an attachment from a message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get All',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Get all the message\'s attachments',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'add',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
] as INodeProperties[];
|
||||||
|
|
||||||
|
export const messageAttachmentFields = [
|
||||||
|
{
|
||||||
|
displayName: 'Message ID',
|
||||||
|
name: 'messageId',
|
||||||
|
description: 'Message ID',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'messageAttachment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'add',
|
||||||
|
'download',
|
||||||
|
'get',
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Attachment ID',
|
||||||
|
name: 'attachmentId',
|
||||||
|
description: 'Attachment ID',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'messageAttachment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'download',
|
||||||
|
'get',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// messageAttachment:getAll, messageAttachment:listAttachments
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'messageAttachment',
|
||||||
|
],
|
||||||
|
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: [
|
||||||
|
'messageAttachment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
returnAll: [
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 500,
|
||||||
|
},
|
||||||
|
default: 100,
|
||||||
|
description: 'How many results to return.',
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// messageAttachment:create, messageAttachment:update, messageAttachment:send
|
||||||
|
|
||||||
|
// File operations
|
||||||
|
{
|
||||||
|
displayName: 'Binary Property',
|
||||||
|
name: 'binaryPropertyName',
|
||||||
|
description: 'Name of the binary property to which to<br />write the data of the read file.',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
default: 'data',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'messageAttachment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'add',
|
||||||
|
'download',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// messageAttachment:add
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'messageAttachment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'add',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'File Name',
|
||||||
|
name: 'fileName',
|
||||||
|
description: 'Filename of the attachment. If not set will the file-name of the binary property be used, if it exists.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get & Get All operations
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'messageAttachment',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'get',
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Fields the response will contain. Multiple can be added separated by ,.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Filter',
|
||||||
|
name: 'filter',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Microsoft Graph API OData $filter query.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as INodeProperties[];
|
|
@ -0,0 +1,633 @@
|
||||||
|
import {
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const messageOperations = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete a message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Get a single message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get All',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Get all messages in the signed-in user\'s mailbox',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get MIME Content',
|
||||||
|
value: 'getMime',
|
||||||
|
description: 'Get MIME content of a message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Reply',
|
||||||
|
value: 'reply',
|
||||||
|
description: 'Create reply to a message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Send',
|
||||||
|
value: 'send',
|
||||||
|
description: 'Send a message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update a message',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'send',
|
||||||
|
description: 'The operation to perform.',
|
||||||
|
},
|
||||||
|
] as INodeProperties[];
|
||||||
|
|
||||||
|
export const messageFields = [
|
||||||
|
{
|
||||||
|
displayName: 'Message ID',
|
||||||
|
name: 'messageId',
|
||||||
|
description: 'Message ID',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'addAttachment',
|
||||||
|
'delete',
|
||||||
|
'get',
|
||||||
|
'getAttachment',
|
||||||
|
'getMime',
|
||||||
|
'update',
|
||||||
|
'reply',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// message:reply
|
||||||
|
{
|
||||||
|
displayName: 'Reply Type',
|
||||||
|
name: 'replyType',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Reply',
|
||||||
|
value: 'reply',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Reply All',
|
||||||
|
value: 'replyAll',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'reply',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'reply',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Comment',
|
||||||
|
name: 'comment',
|
||||||
|
description: 'A comment to include. Can be an empty string.',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'reply',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Send',
|
||||||
|
name: 'send',
|
||||||
|
description: 'Send the reply message directly. If not set, it will be saved as draft.',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'reply',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'reply',
|
||||||
|
],
|
||||||
|
replyType: [
|
||||||
|
'reply',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Attachments',
|
||||||
|
name: 'attachments',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
placeholder: 'Add Attachment',
|
||||||
|
default: {},
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'attachments',
|
||||||
|
displayName: 'Attachment',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Binary Property Name',
|
||||||
|
name: 'binaryPropertyName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of the binary property containing the data to be added to the email as an attachment',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'BCC Recipients',
|
||||||
|
name: 'bccRecipients',
|
||||||
|
description: 'Email addresses of BCC recipients.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Body Content',
|
||||||
|
name: 'bodyContent',
|
||||||
|
description: 'Message body content.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Body Content Type',
|
||||||
|
name: 'bodyContentType',
|
||||||
|
description: 'Message body content type.',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'HTML',
|
||||||
|
value: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Text',
|
||||||
|
value: 'Text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'CC Recipients',
|
||||||
|
name: 'ccRecipients',
|
||||||
|
description: 'Email addresses of CC recipients.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Custom Headers',
|
||||||
|
name: 'internetMessageHeaders',
|
||||||
|
placeholder: 'Add Header',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'headers',
|
||||||
|
displayName: 'Header',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of the header.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Value to set for the header.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'From',
|
||||||
|
name: 'from',
|
||||||
|
description: 'The owner of the mailbox which the message is sent.<br>Must correspond to the actual mailbox used.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Importance',
|
||||||
|
name: 'importance',
|
||||||
|
description: 'The importance of the message.',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Low',
|
||||||
|
value: 'Low',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Normal',
|
||||||
|
value: 'Normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'High',
|
||||||
|
value: 'High',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'Low',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Read Receipt Requested',
|
||||||
|
name: 'isReadReceiptRequested',
|
||||||
|
description: 'Indicates whether a read receipt is requested for the message.',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Recipients',
|
||||||
|
name: 'toRecipients',
|
||||||
|
description: 'Email addresses of recipients. Multiple can be added separated by comma.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Reply To',
|
||||||
|
name: 'replyTo',
|
||||||
|
description: 'Email addresses to use when replying.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Subject',
|
||||||
|
name: 'subject',
|
||||||
|
description: 'The subject of the message.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// message:getAll
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: false,
|
||||||
|
description: 'If all results should be returned or only up to a given limit.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
type: 'number',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getAll',
|
||||||
|
],
|
||||||
|
returnAll: [
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
maxValue: 500,
|
||||||
|
},
|
||||||
|
default: 100,
|
||||||
|
description: 'How many results to return.',
|
||||||
|
},
|
||||||
|
|
||||||
|
// message:create, message:update, message:send
|
||||||
|
{
|
||||||
|
displayName: 'Subject',
|
||||||
|
name: 'subject',
|
||||||
|
description: 'The subject of the message.',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
'send',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Body Content',
|
||||||
|
name: 'bodyContent',
|
||||||
|
description: 'Message body content.',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'create',
|
||||||
|
'send',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Recipients',
|
||||||
|
name: 'toRecipients',
|
||||||
|
description: 'Email addresses of recipients. Multiple can be added separated by comma.',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'send',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'send',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Attachments',
|
||||||
|
name: 'attachments',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
placeholder: 'Add Attachment',
|
||||||
|
default: {},
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'attachments',
|
||||||
|
displayName: 'Attachment',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Binary Property Name',
|
||||||
|
name: 'binaryPropertyName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of the binary property containing the data to be added to the email as an attachment',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'BCC Recipients',
|
||||||
|
name: 'bccRecipients',
|
||||||
|
description: 'Email addresses of BCC recipients.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Body Content Type',
|
||||||
|
name: 'bodyContentType',
|
||||||
|
description: 'Message body content type.',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'HTML',
|
||||||
|
value: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Text',
|
||||||
|
value: 'Text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Categories',
|
||||||
|
name: 'categories',
|
||||||
|
type: 'multiOptions',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getCategories',
|
||||||
|
},
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'CC Recipients',
|
||||||
|
name: 'ccRecipients',
|
||||||
|
description: 'Email addresses of CC recipients.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Custom Headers',
|
||||||
|
name: 'internetMessageHeaders',
|
||||||
|
placeholder: 'Add Header',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'headers',
|
||||||
|
displayName: 'Header',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of the header.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Value to set for the header.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'From',
|
||||||
|
name: 'from',
|
||||||
|
description: 'The owner of the mailbox which the message is sent.<br>Must correspond to the actual mailbox used.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Importance',
|
||||||
|
name: 'importance',
|
||||||
|
description: 'The importance of the message.',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Low',
|
||||||
|
value: 'Low',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Normal',
|
||||||
|
value: 'Normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'High',
|
||||||
|
value: 'High',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'Low',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Read Receipt Requested',
|
||||||
|
name: 'isReadReceiptRequested',
|
||||||
|
description: 'Indicates whether a read receipt is requested for the message.',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Recipients',
|
||||||
|
name: 'toRecipients',
|
||||||
|
description: 'Email addresses of recipients. Multiple can be added separated by comma.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Reply To',
|
||||||
|
name: 'replyTo',
|
||||||
|
description: 'Email addresses to use when replying.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Save To Sent Items',
|
||||||
|
name: 'saveToSentItems',
|
||||||
|
description: 'Indicates whether to save the message in Sent Items.',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// File operations
|
||||||
|
{
|
||||||
|
displayName: 'Binary Property',
|
||||||
|
name: 'binaryPropertyName',
|
||||||
|
description: 'Name of the binary property to which to<br />write the data of the read file.',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
default: 'data',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'getMime',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// message:move
|
||||||
|
{
|
||||||
|
displayName: 'Folder ID',
|
||||||
|
name: 'folderId',
|
||||||
|
description: 'Folder ID',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'move',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as INodeProperties[];
|
|
@ -0,0 +1,961 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IBinaryKeyData,
|
||||||
|
IDataObject,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodePropertyOptions,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createMessage,
|
||||||
|
downloadAttachments,
|
||||||
|
makeRecipient,
|
||||||
|
microsoftApiRequest,
|
||||||
|
microsoftApiRequestAllItems
|
||||||
|
} from './GenericFunctions';
|
||||||
|
|
||||||
|
import {
|
||||||
|
draftFields,
|
||||||
|
draftOperations,
|
||||||
|
} from './DraftDescription';
|
||||||
|
|
||||||
|
import {
|
||||||
|
draftMessageSharedFields,
|
||||||
|
} from './DraftMessageSharedDescription';
|
||||||
|
|
||||||
|
import {
|
||||||
|
messageFields,
|
||||||
|
messageOperations,
|
||||||
|
} from './MessageDescription';
|
||||||
|
|
||||||
|
import {
|
||||||
|
messageAttachmentFields,
|
||||||
|
messageAttachmentOperations,
|
||||||
|
} from './MessageAttachmentDescription';
|
||||||
|
|
||||||
|
import {
|
||||||
|
folderFields,
|
||||||
|
folderOperations,
|
||||||
|
} from './FolderDescription';
|
||||||
|
|
||||||
|
import {
|
||||||
|
folderMessageFields,
|
||||||
|
folderMessageOperations,
|
||||||
|
} from './FolderMessageDecription';
|
||||||
|
|
||||||
|
export class MicrosoftOutlook implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'Microsoft Outlook',
|
||||||
|
name: 'microsoftOutlook',
|
||||||
|
group: ['transform'],
|
||||||
|
icon: 'file:outlook.svg',
|
||||||
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
|
description: 'Consume Microsoft Outlook API',
|
||||||
|
defaults: {
|
||||||
|
name: 'Microsoft Outlook',
|
||||||
|
color: '#3a71b5',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'microsoftOutlookOAuth2Api',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Resource',
|
||||||
|
name: 'resource',
|
||||||
|
type: 'options',
|
||||||
|
default: 'message',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Draft',
|
||||||
|
value: 'draft',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Folder',
|
||||||
|
value: 'folder',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Folder Message',
|
||||||
|
value: 'folderMessage',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Message',
|
||||||
|
value: 'message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Message Attachment',
|
||||||
|
value: 'messageAttachment',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// Draft
|
||||||
|
...draftOperations,
|
||||||
|
...draftFields,
|
||||||
|
// Message
|
||||||
|
...messageOperations,
|
||||||
|
...messageFields,
|
||||||
|
// Message Attachment
|
||||||
|
...messageAttachmentOperations,
|
||||||
|
...messageAttachmentFields,
|
||||||
|
// Folder
|
||||||
|
...folderOperations,
|
||||||
|
...folderFields,
|
||||||
|
// Folder Message
|
||||||
|
...folderMessageOperations,
|
||||||
|
...folderMessageFields,
|
||||||
|
|
||||||
|
// Draft & Message
|
||||||
|
...draftMessageSharedFields,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
methods = {
|
||||||
|
loadOptions: {
|
||||||
|
// Get all the categories to display them to user so that he can
|
||||||
|
// select them easily
|
||||||
|
async getCategories(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const categories = await microsoftApiRequestAllItems.call(this, 'value', 'GET', '/outlook/masterCategories');
|
||||||
|
for (const category of categories) {
|
||||||
|
returnData.push({
|
||||||
|
name: category.displayName as string,
|
||||||
|
value: category.id as string,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
const items = this.getInputData();
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
const length = items.length as unknown as number;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
let responseData;
|
||||||
|
|
||||||
|
const resource = this.getNodeParameter('resource', 0) as string;
|
||||||
|
const operation = this.getNodeParameter('operation', 0) as string;
|
||||||
|
|
||||||
|
if (['draft', 'message'].includes(resource)) {
|
||||||
|
if (operation === 'delete') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const messageId = this.getNodeParameter('messageId', i) as string;
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'DELETE',
|
||||||
|
`/messages/${messageId}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
returnData.push({ success: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'get') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const messageId = this.getNodeParameter('messageId', i) as string;
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||||
|
|
||||||
|
if (additionalFields.fields) {
|
||||||
|
qs['$select'] = additionalFields.fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.filter) {
|
||||||
|
qs['$filter'] = additionalFields.filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/messages/${messageId}`,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (additionalFields.dataPropertyAttachmentsPrefixName) {
|
||||||
|
const prefix = additionalFields.dataPropertyAttachmentsPrefixName as string;
|
||||||
|
const data = await downloadAttachments.call(this, responseData, prefix);
|
||||||
|
returnData.push.apply(returnData, data as unknown as IDataObject[]);
|
||||||
|
} else {
|
||||||
|
returnData.push(responseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.dataPropertyAttachmentsPrefixName) {
|
||||||
|
return [returnData as INodeExecutionData[]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'update') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const messageId = this.getNodeParameter('messageId', i) as string;
|
||||||
|
|
||||||
|
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||||
|
|
||||||
|
// Create message from optional fields
|
||||||
|
const body: IDataObject = createMessage(updateFields);
|
||||||
|
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'PATCH',
|
||||||
|
`/messages/${messageId}`,
|
||||||
|
body,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
returnData.push(responseData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource === 'draft') {
|
||||||
|
|
||||||
|
if (operation === 'create') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||||
|
|
||||||
|
const subject = this.getNodeParameter('subject', i) as string;
|
||||||
|
|
||||||
|
const bodyContent = this.getNodeParameter('bodyContent', i, '') as string;
|
||||||
|
|
||||||
|
additionalFields.subject = subject;
|
||||||
|
|
||||||
|
additionalFields.bodyContent = bodyContent || ' ';
|
||||||
|
|
||||||
|
// Create message object from optional fields
|
||||||
|
const body: IDataObject = createMessage(additionalFields);
|
||||||
|
|
||||||
|
if (additionalFields.attachments) {
|
||||||
|
const attachments = (additionalFields.attachments as IDataObject).attachments as IDataObject[];
|
||||||
|
|
||||||
|
// // Handle attachments
|
||||||
|
body['attachments'] = attachments.map(attachment => {
|
||||||
|
const binaryPropertyName = attachment.binaryPropertyName as string;
|
||||||
|
|
||||||
|
if (items[i].binary === undefined) {
|
||||||
|
throw new Error('No binary data exists on item!');
|
||||||
|
}
|
||||||
|
//@ts-ignore
|
||||||
|
if (items[i].binary[binaryPropertyName] === undefined) {
|
||||||
|
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName];
|
||||||
|
return {
|
||||||
|
'@odata.type': '#microsoft.graph.fileAttachment',
|
||||||
|
name: binaryData.fileName,
|
||||||
|
contentBytes: binaryData.data,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
`/messages`,
|
||||||
|
body,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
returnData.push(responseData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'send') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const messageId = this.getNodeParameter('messageId', i);
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', i, {}) as IDataObject;
|
||||||
|
|
||||||
|
if (additionalFields && additionalFields.recipients) {
|
||||||
|
const recipients = ((additionalFields.recipients as string).split(',') as string[]).filter(email => !!email);
|
||||||
|
if (recipients.length !== 0) {
|
||||||
|
await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'PATCH',
|
||||||
|
`/messages/${messageId}`,
|
||||||
|
{ toRecipients: recipients.map((recipient: string) => makeRecipient(recipient)) },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
`/messages/${messageId}/send`,
|
||||||
|
);
|
||||||
|
|
||||||
|
returnData.push({ success: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource === 'message') {
|
||||||
|
|
||||||
|
if (operation === 'reply') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const messageId = this.getNodeParameter('messageId', i) as string;
|
||||||
|
const replyType = this.getNodeParameter('replyType', i) as string;
|
||||||
|
const comment = this.getNodeParameter('comment', i) as string;
|
||||||
|
const send = this.getNodeParameter('send', i, false) as boolean;
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', i, {}) as IDataObject;
|
||||||
|
|
||||||
|
const body: IDataObject = {};
|
||||||
|
|
||||||
|
let action = 'createReply';
|
||||||
|
if (replyType === 'replyAll') {
|
||||||
|
body.comment = comment;
|
||||||
|
action = 'createReplyAll';
|
||||||
|
} else {
|
||||||
|
body.comment = comment;
|
||||||
|
body.message = {};
|
||||||
|
Object.assign(body.message, createMessage(additionalFields));
|
||||||
|
//@ts-ignore
|
||||||
|
delete body.message.attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
`/messages/${messageId}/${action}`,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (additionalFields.attachments) {
|
||||||
|
const attachments = (additionalFields.attachments as IDataObject).attachments as IDataObject[];
|
||||||
|
// // Handle attachments
|
||||||
|
const data = attachments.map(attachment => {
|
||||||
|
const binaryPropertyName = attachment.binaryPropertyName as string;
|
||||||
|
|
||||||
|
if (items[i].binary === undefined) {
|
||||||
|
throw new Error('No binary data exists on item!');
|
||||||
|
}
|
||||||
|
//@ts-ignore
|
||||||
|
if (items[i].binary[binaryPropertyName] === undefined) {
|
||||||
|
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName];
|
||||||
|
return {
|
||||||
|
'@odata.type': '#microsoft.graph.fileAttachment',
|
||||||
|
name: binaryData.fileName,
|
||||||
|
contentBytes: binaryData.data,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const attachment of data) {
|
||||||
|
await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
`/messages/${responseData.id}/attachments`,
|
||||||
|
attachment,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (send === true) {
|
||||||
|
await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
`/messages/${responseData.id}/send`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
returnData.push(responseData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'getMime') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const messageId = this.getNodeParameter('messageId', i) as string;
|
||||||
|
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i) as string;
|
||||||
|
const response = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/messages/${messageId}/$value`,
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
{ encoding: null, resolveWithFullResponse: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
let mimeType: string | undefined;
|
||||||
|
if (response.headers['content-type']) {
|
||||||
|
mimeType = response.headers['content-type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const newItem: INodeExecutionData = {
|
||||||
|
json: items[i].json,
|
||||||
|
binary: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (items[i].binary !== undefined) {
|
||||||
|
// Create a shallow copy of the binary data so that the old
|
||||||
|
// data references which do not get changed still stay behind
|
||||||
|
// but the incoming data does not get changed.
|
||||||
|
Object.assign(newItem.binary, items[i].binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
items[i] = newItem;
|
||||||
|
|
||||||
|
|
||||||
|
const fileName = `${messageId}.eml`;
|
||||||
|
const data = Buffer.from(response.body as string, 'utf8');
|
||||||
|
items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(data as unknown as Buffer, fileName, mimeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'getAll') {
|
||||||
|
let additionalFields: IDataObject = {};
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||||
|
additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||||
|
|
||||||
|
if (additionalFields.fields) {
|
||||||
|
qs['$select'] = additionalFields.fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.filter) {
|
||||||
|
qs['$filter'] = additionalFields.filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = '/messages';
|
||||||
|
|
||||||
|
if (returnAll === true) {
|
||||||
|
responseData = await microsoftApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'value',
|
||||||
|
'GET',
|
||||||
|
endpoint,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
qs['$top'] = this.getNodeParameter('limit', i) as number;
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
endpoint,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
responseData = responseData.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.dataPropertyAttachmentsPrefixName) {
|
||||||
|
const prefix = additionalFields.dataPropertyAttachmentsPrefixName as string;
|
||||||
|
const data = await downloadAttachments.call(this, responseData, prefix);
|
||||||
|
returnData.push.apply(returnData, data as unknown as IDataObject[]);
|
||||||
|
} else {
|
||||||
|
returnData.push.apply(returnData, responseData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.dataPropertyAttachmentsPrefixName) {
|
||||||
|
return [returnData as INodeExecutionData[]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'move') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const messageId = this.getNodeParameter('messageId', i) as string;
|
||||||
|
const destinationId = this.getNodeParameter('folderId', i) as string;
|
||||||
|
const body: IDataObject = {
|
||||||
|
destinationId,
|
||||||
|
};
|
||||||
|
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
`/messages/${messageId}/move`,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
returnData.push({ success: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'send') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||||
|
|
||||||
|
const toRecipients = this.getNodeParameter('toRecipients', i) as string;
|
||||||
|
|
||||||
|
const subject = this.getNodeParameter('subject', i) as string;
|
||||||
|
|
||||||
|
const bodyContent = this.getNodeParameter('bodyContent', i, '') as string;
|
||||||
|
|
||||||
|
additionalFields.subject = subject;
|
||||||
|
|
||||||
|
additionalFields.bodyContent = bodyContent || ' ';
|
||||||
|
|
||||||
|
additionalFields.toRecipients = toRecipients;
|
||||||
|
|
||||||
|
const saveToSentItems = additionalFields.saveToSentItems === undefined ? true : additionalFields.saveToSentItems;
|
||||||
|
delete additionalFields.saveToSentItems;
|
||||||
|
|
||||||
|
// Create message object from optional fields
|
||||||
|
const message: IDataObject = createMessage(additionalFields);
|
||||||
|
|
||||||
|
if (additionalFields.attachments) {
|
||||||
|
const attachments = (additionalFields.attachments as IDataObject).attachments as IDataObject[];
|
||||||
|
|
||||||
|
// // Handle attachments
|
||||||
|
message['attachments'] = attachments.map(attachment => {
|
||||||
|
const binaryPropertyName = attachment.binaryPropertyName as string;
|
||||||
|
|
||||||
|
if (items[i].binary === undefined) {
|
||||||
|
throw new Error('No binary data exists on item!');
|
||||||
|
}
|
||||||
|
//@ts-ignore
|
||||||
|
if (items[i].binary[binaryPropertyName] === undefined) {
|
||||||
|
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName];
|
||||||
|
return {
|
||||||
|
'@odata.type': '#microsoft.graph.fileAttachment',
|
||||||
|
name: binaryData.fileName,
|
||||||
|
contentBytes: binaryData.data,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
message,
|
||||||
|
saveToSentItems,
|
||||||
|
};
|
||||||
|
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
`/sendMail`,
|
||||||
|
body,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
returnData.push({ success: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource === 'messageAttachment') {
|
||||||
|
if (operation === 'add') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const messageId = this.getNodeParameter('messageId', i) as string;
|
||||||
|
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string;
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||||
|
|
||||||
|
if (items[i].binary === undefined) {
|
||||||
|
throw new Error('No binary data exists on item!');
|
||||||
|
}
|
||||||
|
//@ts-ignore
|
||||||
|
if (items[i].binary[binaryPropertyName] === undefined) {
|
||||||
|
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName];
|
||||||
|
const dataBuffer = Buffer.from(binaryData.data, 'base64');
|
||||||
|
|
||||||
|
const fileName = additionalFields.fileName === undefined ? binaryData.fileName : additionalFields.fileName;
|
||||||
|
|
||||||
|
if (!fileName) {
|
||||||
|
throw new Error('File name is not set. It has either to be set via "Additional Fields" or has to be set on the binary property!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the file is over 3MB big
|
||||||
|
if (dataBuffer.length > 3e6) {
|
||||||
|
// Maximum chunk size is 4MB
|
||||||
|
const chunkSize = 4e6;
|
||||||
|
const body: IDataObject = {
|
||||||
|
AttachmentItem: {
|
||||||
|
attachmentType: 'file',
|
||||||
|
name: fileName,
|
||||||
|
size: dataBuffer.length,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create upload session
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
`/messages/${messageId}/attachments/createUploadSession`,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
const uploadUrl = responseData.uploadUrl;
|
||||||
|
|
||||||
|
if (uploadUrl === undefined) {
|
||||||
|
throw new Error('Failed to get upload session');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let bytesUploaded = 0; bytesUploaded < dataBuffer.length; bytesUploaded += chunkSize) {
|
||||||
|
// Upload the file chunk by chunk
|
||||||
|
const nextChunk = Math.min(bytesUploaded + chunkSize, dataBuffer.length);
|
||||||
|
const contentRange = `bytes ${bytesUploaded}-${nextChunk - 1}/${dataBuffer.length}`;
|
||||||
|
|
||||||
|
const data = dataBuffer.subarray(bytesUploaded, nextChunk);
|
||||||
|
|
||||||
|
responseData = await this.helpers.request(
|
||||||
|
uploadUrl,
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/octet-stream',
|
||||||
|
'Content-Length': data.length,
|
||||||
|
'Content-Range': contentRange,
|
||||||
|
},
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const body: IDataObject = {
|
||||||
|
'@odata.type': '#microsoft.graph.fileAttachment',
|
||||||
|
name: fileName,
|
||||||
|
contentBytes: binaryData.data,
|
||||||
|
};
|
||||||
|
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
`/messages/${messageId}/attachments`,
|
||||||
|
body,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
returnData.push({ success: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'download') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const messageId = this.getNodeParameter('messageId', i) as string;
|
||||||
|
const attachmentId = this.getNodeParameter('attachmentId', i) as string;
|
||||||
|
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i) as string;
|
||||||
|
|
||||||
|
// Get attachment details first
|
||||||
|
const attachmentDetails = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/messages/${messageId}/attachments/${attachmentId}`,
|
||||||
|
undefined,
|
||||||
|
{ '$select': 'id,name,contentType' },
|
||||||
|
);
|
||||||
|
|
||||||
|
let mimeType: string | undefined;
|
||||||
|
if (attachmentDetails.contentType) {
|
||||||
|
mimeType = attachmentDetails.contentType;
|
||||||
|
}
|
||||||
|
const fileName = attachmentDetails.name;
|
||||||
|
|
||||||
|
const response = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/messages/${messageId}/attachments/${attachmentId}/$value`,
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
{ encoding: null, resolveWithFullResponse: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const newItem: INodeExecutionData = {
|
||||||
|
json: items[i].json,
|
||||||
|
binary: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (items[i].binary !== undefined) {
|
||||||
|
// Create a shallow copy of the binary data so that the old
|
||||||
|
// data references which do not get changed still stay behind
|
||||||
|
// but the incoming data does not get changed.
|
||||||
|
Object.assign(newItem.binary, items[i].binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
items[i] = newItem;
|
||||||
|
const data = Buffer.from(response.body as string, 'utf8');
|
||||||
|
items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(data as unknown as Buffer, fileName, mimeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'get') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const messageId = this.getNodeParameter('messageId', i) as string;
|
||||||
|
const attachmentId = this.getNodeParameter('attachmentId', i) as string;
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||||
|
|
||||||
|
// Have sane defaults so we don't fetch attachment data in this operation
|
||||||
|
qs['$select'] = 'id,lastModifiedDateTime,name,contentType,size,isInline';
|
||||||
|
if (additionalFields.fields) {
|
||||||
|
qs['$select'] = additionalFields.fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/messages/${messageId}/attachments/${attachmentId}`,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
returnData.push(responseData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'getAll') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const messageId = this.getNodeParameter('messageId', i) as string;
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||||
|
|
||||||
|
// Have sane defaults so we don't fetch attachment data in this operation
|
||||||
|
qs['$select'] = 'id,lastModifiedDateTime,name,contentType,size,isInline';
|
||||||
|
if (additionalFields.fields) {
|
||||||
|
qs['$select'] = additionalFields.fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.filter) {
|
||||||
|
qs['$filter'] = additionalFields.filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = `/messages/${messageId}/attachments`;
|
||||||
|
if (returnAll === true) {
|
||||||
|
responseData = await microsoftApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'value',
|
||||||
|
'GET',
|
||||||
|
endpoint,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
qs['$top'] = this.getNodeParameter('limit', i) as number;
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
endpoint,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
responseData = responseData.value;
|
||||||
|
}
|
||||||
|
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource === 'folder') {
|
||||||
|
if (operation === 'create') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const displayName = this.getNodeParameter('displayName', i) as string;
|
||||||
|
const folderType = this.getNodeParameter('folderType', i) as string;
|
||||||
|
const body: IDataObject = {
|
||||||
|
displayName,
|
||||||
|
};
|
||||||
|
|
||||||
|
let endpoint = '/mailFolders';
|
||||||
|
|
||||||
|
if (folderType === 'searchFolder') {
|
||||||
|
endpoint = '/mailFolders/searchfolders/childFolders';
|
||||||
|
const includeNestedFolders = this.getNodeParameter('includeNestedFolders', i);
|
||||||
|
const sourceFolderIds = this.getNodeParameter('sourceFolderIds', i);
|
||||||
|
const filterQuery = this.getNodeParameter('filterQuery', i);
|
||||||
|
Object.assign(body, {
|
||||||
|
'@odata.type': 'microsoft.graph.mailSearchFolder',
|
||||||
|
includeNestedFolders,
|
||||||
|
sourceFolderIds,
|
||||||
|
filterQuery,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
endpoint,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
returnData.push(responseData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'delete') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const folderId = this.getNodeParameter('folderId', i) as string;
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'DELETE',
|
||||||
|
`/mailFolders/${folderId}`,
|
||||||
|
);
|
||||||
|
returnData.push({ success: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'get') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const folderId = this.getNodeParameter('folderId', i) as string;
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||||
|
|
||||||
|
if (additionalFields.fields) {
|
||||||
|
qs['$select'] = additionalFields.fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.filter) {
|
||||||
|
qs['$filter'] = additionalFields.filter;
|
||||||
|
}
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/mailFolders/${folderId}`,
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
returnData.push(responseData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'getAll') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||||
|
|
||||||
|
if (additionalFields.fields) {
|
||||||
|
qs['$select'] = additionalFields.fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.filter) {
|
||||||
|
qs['$filter'] = additionalFields.filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnAll === true) {
|
||||||
|
responseData = await microsoftApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'value',
|
||||||
|
'GET',
|
||||||
|
'/mailFolders',
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
qs['$top'] = this.getNodeParameter('limit', i) as number;
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
'/mailFolders',
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
responseData = responseData.value;
|
||||||
|
}
|
||||||
|
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'getChildren') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const folderId = this.getNodeParameter('folderId', i) as string;
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||||
|
|
||||||
|
if (additionalFields.fields) {
|
||||||
|
qs['$select'] = additionalFields.fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.filter) {
|
||||||
|
qs['$filter'] = additionalFields.filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await microsoftApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'value',
|
||||||
|
'GET',
|
||||||
|
`/mailFolders/${folderId}/childFolders`,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
qs['$top'] = this.getNodeParameter('limit', i) as number;
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/mailFolders/${folderId}/childFolders`,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
responseData = responseData.value;
|
||||||
|
}
|
||||||
|
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation === 'update') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const folderId = this.getNodeParameter('folderId', i) as string;
|
||||||
|
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
...updateFields,
|
||||||
|
};
|
||||||
|
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'PATCH',
|
||||||
|
`/mailFolders/${folderId}`,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
returnData.push(responseData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource === 'folderMessage') {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
if (operation === 'getAll') {
|
||||||
|
const folderId = this.getNodeParameter('folderId', i) as string;
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||||
|
|
||||||
|
if (additionalFields.fields) {
|
||||||
|
qs['$select'] = additionalFields.fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = `/mailFolders/${folderId}/messages`;
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await microsoftApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'value',
|
||||||
|
'GET',
|
||||||
|
endpoint,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qs['$top'] = this.getNodeParameter('limit', i) as number;
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
endpoint,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
responseData = responseData.value;
|
||||||
|
}
|
||||||
|
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((resource === 'message' && operation === 'getMime') || (resource === 'messageAttachment' && operation === 'download')) {
|
||||||
|
return this.prepareOutputData(items);
|
||||||
|
} else {
|
||||||
|
return [this.helpers.returnJsonArray(returnData)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
packages/nodes-base/nodes/Microsoft/Outlook/outlook.svg
Normal file
1
packages/nodes-base/nodes/Microsoft/Outlook/outlook.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 81 81" fill="#fff" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><use xlink:href="#A" x=".5" y=".5"/><symbol id="A" overflow="visible"><path d="M77.9 19.2H46.86V0L0 8.232V71.98L46.86 80V61.847H77.9c1.266 0 2.1-1.055 2.1-2.1V21.32c0-1.266-.844-2.1-2.1-2.1zM21.952 54.46c-15.4-1.055-14.776-29.13.422-29.974 16.675-.844 16.253 31.03-.422 29.974zm55.515 4.644H46.86V35.04l9.5 9.077c.422.422.844.633 1.477.633s1.055-.2 1.478-.633L77.467 27.23v31.873zm0-35.462l-19.63 18.153L46.86 31.45v-9.7h30.396v1.9h.2zm-55.092 6.543c-8.232.422-8.443 18.364-.2 18.575 8.654.422 8.654-18.997.2-18.575z" fill="#2372ba" stroke="none"/></symbol></svg>
|
After Width: | Height: | Size: 759 B |
71
packages/nodes-base/nodes/Nasa/GenericFunctions.ts
Normal file
71
packages/nodes-base/nodes/Nasa/GenericFunctions.ts
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import {
|
||||||
|
OptionsWithUri,
|
||||||
|
} from 'request';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IHookFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export async function nasaApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, qs: IDataObject, option: IDataObject = {}, uri?: string | undefined): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
|
||||||
|
const credentials = this.getCredentials('nasaApi') as IDataObject;
|
||||||
|
|
||||||
|
qs.api_key = credentials['api_key'] as string;
|
||||||
|
|
||||||
|
const options: OptionsWithUri = {
|
||||||
|
method,
|
||||||
|
qs,
|
||||||
|
uri: uri || `https://api.nasa.gov${endpoint}`,
|
||||||
|
json: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Object.keys(option)) {
|
||||||
|
Object.assign(options, option);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await this.helpers.request(options);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
if (error.statusCode === 401) {
|
||||||
|
// Return a clear error
|
||||||
|
throw new Error('The NASA credentials are not valid!');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.response && error.response.body && error.response.body.msg) {
|
||||||
|
// Try to return the error prettier
|
||||||
|
throw new Error(`NASA error response [${error.statusCode}]: ${error.response.body.msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If that data does not exist for some reason return the actual error
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function nasaApiRequestAllItems(this: IHookFunctions | IExecuteFunctions, propertyName: string, method: string, resource: string, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
|
||||||
|
let responseData;
|
||||||
|
|
||||||
|
query.size = 20;
|
||||||
|
|
||||||
|
let uri: string | undefined = undefined;
|
||||||
|
|
||||||
|
do {
|
||||||
|
responseData = await nasaApiRequest.call(this, method, resource, query, {}, uri);
|
||||||
|
uri = responseData.links.next;
|
||||||
|
returnData.push.apply(returnData, responseData[propertyName]);
|
||||||
|
} while (
|
||||||
|
responseData.links.next !== undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
1235
packages/nodes-base/nodes/Nasa/Nasa.node.ts
Normal file
1235
packages/nodes-base/nodes/Nasa/Nasa.node.ts
Normal file
File diff suppressed because it is too large
Load diff
BIN
packages/nodes-base/nodes/Nasa/nasa.png
Normal file
BIN
packages/nodes-base/nodes/Nasa/nasa.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
50
packages/nodes-base/nodes/Pushcut/GenericFunctions.ts
Normal file
50
packages/nodes-base/nodes/Pushcut/GenericFunctions.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import {
|
||||||
|
OptionsWithUri,
|
||||||
|
} from 'request';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
IHookFunctions,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export async function pushcutApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions, method: string, path: string, body: any = {}, qs: IDataObject = {}, uri?: string | undefined, option = {}): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
|
||||||
|
const credentials = this.getCredentials('pushcutApi') as IDataObject;
|
||||||
|
|
||||||
|
const options: OptionsWithUri = {
|
||||||
|
headers: {
|
||||||
|
'API-Key': credentials.apiKey,
|
||||||
|
},
|
||||||
|
method,
|
||||||
|
body,
|
||||||
|
qs,
|
||||||
|
uri: uri || `https://api.pushcut.io/v1${path}`,
|
||||||
|
json: true,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
if (Object.keys(body).length === 0) {
|
||||||
|
delete options.body;
|
||||||
|
}
|
||||||
|
if (Object.keys(option).length !== 0) {
|
||||||
|
Object.assign(options, option);
|
||||||
|
}
|
||||||
|
//@ts-ignore
|
||||||
|
return await this.helpers.request.call(this, options);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response && error.response.body && error.response.body.error) {
|
||||||
|
|
||||||
|
const message = error.response.body.error;
|
||||||
|
|
||||||
|
// Try to return the error prettier
|
||||||
|
throw new Error(
|
||||||
|
`Pushcut error response [${error.statusCode}]: ${message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
215
packages/nodes-base/nodes/Pushcut/Pushcut.node.ts
Normal file
215
packages/nodes-base/nodes/Pushcut/Pushcut.node.ts
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodePropertyOptions,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
pushcutApiRequest,
|
||||||
|
} from './GenericFunctions';
|
||||||
|
|
||||||
|
export class Pushcut implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'Pushcut',
|
||||||
|
name: 'pushcut',
|
||||||
|
icon: 'file:pushcut.png',
|
||||||
|
group: ['input'],
|
||||||
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
|
description: 'Consume Pushcut API.',
|
||||||
|
defaults: {
|
||||||
|
name: 'Pushcut',
|
||||||
|
color: '#1f2957',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'pushcutApi',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Resource',
|
||||||
|
name: 'resource',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Notification',
|
||||||
|
value: 'notification',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'notification',
|
||||||
|
description: 'The resource to operate on.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'notification',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Send',
|
||||||
|
value: 'send',
|
||||||
|
description: 'Send a notification',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'send',
|
||||||
|
description: 'The resource to operate on.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Notification Name',
|
||||||
|
name: 'notificationName',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getNotifications',
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'notification',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'send',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'send',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'notification',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Devices',
|
||||||
|
name: 'devices',
|
||||||
|
type: 'multiOptions',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getDevices',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'List of devices this notification is sent to. (default is all devices)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Input',
|
||||||
|
name: 'input',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Value that is passed as input to the notification action.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Text',
|
||||||
|
name: 'text',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Text that is used instead of the one defined in the app.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Title',
|
||||||
|
name: 'title',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Title that is used instead of the one defined in the app.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
methods = {
|
||||||
|
loadOptions: {
|
||||||
|
// Get all the available devices to display them to user so that he can
|
||||||
|
// select them easily
|
||||||
|
async getDevices(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const devices = await pushcutApiRequest.call(this, 'GET', '/devices');
|
||||||
|
for (const device of devices) {
|
||||||
|
returnData.push({
|
||||||
|
name: device.id,
|
||||||
|
value: device.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
},
|
||||||
|
// Get all the available notifications to display them to user so that he can
|
||||||
|
// select them easily
|
||||||
|
async getNotifications(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const notifications = await pushcutApiRequest.call(this, 'GET', '/notifications');
|
||||||
|
for (const notification of notifications) {
|
||||||
|
returnData.push({
|
||||||
|
name: notification.title,
|
||||||
|
value: notification.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
const items = this.getInputData();
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
const length = (items.length as unknown) as number;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
let responseData;
|
||||||
|
const resource = this.getNodeParameter('resource', 0) as string;
|
||||||
|
const operation = this.getNodeParameter('operation', 0) as string;
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
|
||||||
|
if (resource === 'notification') {
|
||||||
|
if (operation === 'send') {
|
||||||
|
const notificationName = this.getNodeParameter('notificationName', i) as string;
|
||||||
|
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||||
|
|
||||||
|
const body: IDataObject = {};
|
||||||
|
|
||||||
|
Object.assign(body, additionalFields);
|
||||||
|
|
||||||
|
responseData = await pushcutApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
`/notifications/${encodeURI(notificationName)}`,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Array.isArray(responseData)) {
|
||||||
|
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||||
|
|
||||||
|
} else if (responseData !== undefined) {
|
||||||
|
returnData.push(responseData as IDataObject);
|
||||||
|
|
||||||
|
}
|
||||||
|
return [this.helpers.returnJsonArray(returnData)];
|
||||||
|
}
|
||||||
|
}
|
130
packages/nodes-base/nodes/Pushcut/PushcutTrigger.node.ts
Normal file
130
packages/nodes-base/nodes/Pushcut/PushcutTrigger.node.ts
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
import {
|
||||||
|
IHookFunctions,
|
||||||
|
IWebhookFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
IWebhookResponseData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
pushcutApiRequest,
|
||||||
|
} from './GenericFunctions';
|
||||||
|
|
||||||
|
export class PushcutTrigger implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'Pushcut Trigger',
|
||||||
|
name: 'pushcutTrigger',
|
||||||
|
icon: 'file:pushcut.png',
|
||||||
|
group: ['trigger'],
|
||||||
|
version: 1,
|
||||||
|
description: 'Starts the workflow when a Pushcut events occurs.',
|
||||||
|
defaults: {
|
||||||
|
name: 'Pushcut Trigger',
|
||||||
|
color: '#1f2957',
|
||||||
|
},
|
||||||
|
inputs: [],
|
||||||
|
outputs: ['main'],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'pushcutApi',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
webhooks: [
|
||||||
|
{
|
||||||
|
name: 'default',
|
||||||
|
httpMethod: 'POST',
|
||||||
|
responseMode: 'onReceived',
|
||||||
|
path: 'webhook',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Action Name',
|
||||||
|
name: 'actionName',
|
||||||
|
type: 'string',
|
||||||
|
description: 'Choose any name you would like. It will show up as a server action in the app',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-ignore (because of request)
|
||||||
|
webhookMethods = {
|
||||||
|
default: {
|
||||||
|
async checkExists(this: IHookFunctions): Promise<boolean> {
|
||||||
|
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||||
|
const webhookData = this.getWorkflowStaticData('node');
|
||||||
|
const actionName = this.getNodeParameter('actionName');
|
||||||
|
// Check all the webhooks which exist already if it is identical to the
|
||||||
|
// one that is supposed to get created.
|
||||||
|
const endpoint = '/subscriptions';
|
||||||
|
const webhooks = await pushcutApiRequest.call(this, 'GET', endpoint, {});
|
||||||
|
|
||||||
|
for (const webhook of webhooks) {
|
||||||
|
if (webhook.url === webhookUrl &&
|
||||||
|
webhook.actionName === actionName) {
|
||||||
|
webhookData.webhookId = webhook.id;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
async create(this: IHookFunctions): Promise<boolean> {
|
||||||
|
const webhookData = this.getWorkflowStaticData('node');
|
||||||
|
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||||
|
const actionName = this.getNodeParameter('actionName');
|
||||||
|
|
||||||
|
const endpoint = '/subscriptions';
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
actionName,
|
||||||
|
url: webhookUrl,
|
||||||
|
};
|
||||||
|
|
||||||
|
const responseData = await pushcutApiRequest.call(this, 'POST', endpoint, body);
|
||||||
|
|
||||||
|
if (responseData.id === undefined) {
|
||||||
|
// Required data is missing so was not successful
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookData.webhookId = responseData.id as string;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
async delete(this: IHookFunctions): Promise<boolean> {
|
||||||
|
const webhookData = this.getWorkflowStaticData('node');
|
||||||
|
if (webhookData.webhookId !== undefined) {
|
||||||
|
|
||||||
|
const endpoint = `/subscriptions/${webhookData.webhookId}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await pushcutApiRequest.call(this, 'DELETE', endpoint);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from the static workflow data so that it is clear
|
||||||
|
// that no webhooks are registred anymore
|
||||||
|
delete webhookData.webhookId;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
||||||
|
const body = this.getBodyData() as IDataObject;
|
||||||
|
|
||||||
|
return {
|
||||||
|
workflowData: [
|
||||||
|
this.helpers.returnJsonArray(body),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
BIN
packages/nodes-base/nodes/Pushcut/pushcut.png
Normal file
BIN
packages/nodes-base/nodes/Pushcut/pushcut.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
60
packages/nodes-base/nodes/RabbitMQ/DefaultOptions.ts
Normal file
60
packages/nodes-base/nodes/RabbitMQ/DefaultOptions.ts
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import {
|
||||||
|
INodeProperties,
|
||||||
|
INodePropertyCollection,
|
||||||
|
INodePropertyOptions,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const rabbitDefaultOptions: Array<INodePropertyOptions | INodeProperties | INodePropertyCollection> = [
|
||||||
|
{
|
||||||
|
displayName: 'Arguments',
|
||||||
|
name: 'arguments',
|
||||||
|
placeholder: 'Add Argument',
|
||||||
|
description: 'Arguments to add.',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'argument',
|
||||||
|
displayName: 'Argument',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Key',
|
||||||
|
name: 'key',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Auto Delete',
|
||||||
|
name: 'autoDelete',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'The queue will be deleted when the number of consumers drops to zero .',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Durable',
|
||||||
|
name: 'durable',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
description: 'The queue will survive broker restarts.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Exclusive',
|
||||||
|
name: 'exclusive',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'Scopes the queue to the connection.',
|
||||||
|
},
|
||||||
|
];
|
62
packages/nodes-base/nodes/RabbitMQ/GenericFunctions.ts
Normal file
62
packages/nodes-base/nodes/RabbitMQ/GenericFunctions.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
IExecuteFunctions,
|
||||||
|
ITriggerFunctions,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
const amqplib = require('amqplib');
|
||||||
|
|
||||||
|
export async function rabbitmqConnect(this: IExecuteFunctions | ITriggerFunctions, queue: string, options: IDataObject): Promise<any> { // tslint:disable-line:no-any
|
||||||
|
const credentials = this.getCredentials('rabbitmq') as IDataObject;
|
||||||
|
|
||||||
|
const credentialKeys = [
|
||||||
|
'hostname',
|
||||||
|
'port',
|
||||||
|
'username',
|
||||||
|
'password',
|
||||||
|
'vhost',
|
||||||
|
];
|
||||||
|
const credentialData: IDataObject = {};
|
||||||
|
credentialKeys.forEach(key => {
|
||||||
|
credentialData[key] = credentials[key] === '' ? undefined : credentials[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
const optsData: IDataObject = {};
|
||||||
|
if (credentials.ssl === true) {
|
||||||
|
credentialData.protocol = 'amqps';
|
||||||
|
|
||||||
|
optsData.cert = credentials.cert === '' ? undefined : Buffer.from(credentials.cert as string);
|
||||||
|
optsData.key = credentials.key === '' ? undefined : Buffer.from(credentials.key as string);
|
||||||
|
optsData.passphrase = credentials.passphrase === '' ? undefined : credentials.passphrase;
|
||||||
|
optsData.ca = credentials.ca === '' ? undefined : [Buffer.from(credentials.ca as string)];
|
||||||
|
optsData.credentials = amqplib.credentials.external();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const connection = await amqplib.connect(credentialData, optsData);
|
||||||
|
|
||||||
|
connection.on('error', (error: Error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
const channel = await connection.createChannel().catch(console.warn);
|
||||||
|
|
||||||
|
if (options.arguments && ((options.arguments as IDataObject).argument! as IDataObject[]).length) {
|
||||||
|
const additionalArguments: IDataObject = {};
|
||||||
|
((options.arguments as IDataObject).argument as IDataObject[]).forEach((argument: IDataObject) => {
|
||||||
|
additionalArguments[argument.key as string] = argument.value;
|
||||||
|
});
|
||||||
|
options.arguments = additionalArguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
await channel.assertQueue(queue, options);
|
||||||
|
|
||||||
|
resolve(channel);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
147
packages/nodes-base/nodes/RabbitMQ/RabbitMQ.node.ts
Normal file
147
packages/nodes-base/nodes/RabbitMQ/RabbitMQ.node.ts
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
import {
|
||||||
|
IExecuteFunctions,
|
||||||
|
} from 'n8n-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
rabbitDefaultOptions,
|
||||||
|
} from './DefaultOptions';
|
||||||
|
|
||||||
|
import {
|
||||||
|
rabbitmqConnect,
|
||||||
|
} from './GenericFunctions';
|
||||||
|
|
||||||
|
export class RabbitMQ implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'RabbitMQ',
|
||||||
|
name: 'rabbitmq',
|
||||||
|
icon: 'file:rabbitmq.png',
|
||||||
|
group: ['transform'],
|
||||||
|
version: 1,
|
||||||
|
description: 'Sends messages to a RabbitMQ topic',
|
||||||
|
defaults: {
|
||||||
|
name: 'RabbitMQ',
|
||||||
|
color: '#ff6600',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'rabbitmq',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Queue / Topic',
|
||||||
|
name: 'queue',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'queue-name',
|
||||||
|
description: 'Name of the queue to publish to.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Send Input Data',
|
||||||
|
name: 'sendInputData',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
description: 'Send the the data the node receives as JSON to Kafka.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Message',
|
||||||
|
name: 'message',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
sendInputData: [
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'The message to be sent.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
default: {},
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
options: rabbitDefaultOptions,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
let channel;
|
||||||
|
try {
|
||||||
|
const items = this.getInputData();
|
||||||
|
|
||||||
|
const queue = this.getNodeParameter('queue', 0) as string;
|
||||||
|
|
||||||
|
const options = this.getNodeParameter('options', 0, {}) as IDataObject;
|
||||||
|
|
||||||
|
channel = await rabbitmqConnect.call(this, queue, options);
|
||||||
|
|
||||||
|
const sendInputData = this.getNodeParameter('sendInputData', 0) as boolean;
|
||||||
|
|
||||||
|
let message: string;
|
||||||
|
|
||||||
|
const queuePromises = [];
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
if (sendInputData === true) {
|
||||||
|
message = JSON.stringify(items[i].json);
|
||||||
|
} else {
|
||||||
|
message = this.getNodeParameter('message', i) as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
queuePromises.push(channel.sendToQueue(queue, Buffer.from(message)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const promisesResponses = await Promise.allSettled(queuePromises);
|
||||||
|
|
||||||
|
const returnItems: INodeExecutionData[] = [];
|
||||||
|
|
||||||
|
promisesResponses.forEach((response: IDataObject) => {
|
||||||
|
if (response!.status !== 'fulfilled') {
|
||||||
|
|
||||||
|
if (this.continueOnFail() !== true) {
|
||||||
|
throw new Error(response!.reason as string);
|
||||||
|
} else {
|
||||||
|
// Return the actual reason as error
|
||||||
|
returnItems.push(
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
error: response.reason,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
returnItems.push({
|
||||||
|
json: {
|
||||||
|
success: response.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await channel.close();
|
||||||
|
|
||||||
|
return this.prepareOutputData(returnItems);
|
||||||
|
} catch (error) {
|
||||||
|
if (channel) {
|
||||||
|
await channel.close();
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
172
packages/nodes-base/nodes/RabbitMQ/RabbitMQTrigger.node.ts
Normal file
172
packages/nodes-base/nodes/RabbitMQ/RabbitMQTrigger.node.ts
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
import {
|
||||||
|
IDataObject,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeProperties,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
ITriggerFunctions,
|
||||||
|
ITriggerResponse,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import {
|
||||||
|
rabbitDefaultOptions,
|
||||||
|
} from './DefaultOptions';
|
||||||
|
|
||||||
|
import {
|
||||||
|
rabbitmqConnect,
|
||||||
|
} from './GenericFunctions';
|
||||||
|
|
||||||
|
export class RabbitMQTrigger implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'RabbitMQ Trigger',
|
||||||
|
name: 'rabbitmqTrigger',
|
||||||
|
icon: 'file:rabbitmq.png',
|
||||||
|
group: ['trigger'],
|
||||||
|
version: 1,
|
||||||
|
description: 'Listens to RabbitMQ messages',
|
||||||
|
defaults: {
|
||||||
|
name: 'RabbitMQ',
|
||||||
|
color: '#ff6600',
|
||||||
|
},
|
||||||
|
inputs: [],
|
||||||
|
outputs: ['main'],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'rabbitmq',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Queue / Topic',
|
||||||
|
name: 'queue',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'queue-name',
|
||||||
|
description: 'Name of the queue to publish to.',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
default: {},
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Content is Binary',
|
||||||
|
name: 'contentIsBinary',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'Saves the content as binary.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'JSON Parse Body',
|
||||||
|
name: 'jsonParseBody',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
hide: {
|
||||||
|
contentIsBinary: [
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: false,
|
||||||
|
description: 'Parse the body to an object.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Only Content',
|
||||||
|
name: 'onlyContent',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
hide: {
|
||||||
|
contentIsBinary: [
|
||||||
|
true,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: false,
|
||||||
|
description: 'Returns only the content property.',
|
||||||
|
},
|
||||||
|
...rabbitDefaultOptions,
|
||||||
|
].sort((a, b) => {
|
||||||
|
if ((a as INodeProperties).displayName.toLowerCase() < (b as INodeProperties).displayName.toLowerCase()) { return -1; }
|
||||||
|
if ((a as INodeProperties).displayName.toLowerCase() > (b as INodeProperties).displayName.toLowerCase()) { return 1; }
|
||||||
|
return 0;
|
||||||
|
}) as INodeProperties[],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> {
|
||||||
|
const queue = this.getNodeParameter('queue') as string;
|
||||||
|
const options = this.getNodeParameter('options', {}) as IDataObject;
|
||||||
|
|
||||||
|
const channel = await rabbitmqConnect.call(this, queue, options);
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
const item: INodeExecutionData = {
|
||||||
|
json: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const startConsumer = async () => {
|
||||||
|
await channel.consume(queue, async (message: IDataObject) => {
|
||||||
|
if (message !== null) {
|
||||||
|
let content: IDataObject | string = message!.content!.toString();
|
||||||
|
|
||||||
|
if (options.contentIsBinary === true) {
|
||||||
|
item.binary = {
|
||||||
|
data: await this.helpers.prepareBinaryData(message.content),
|
||||||
|
};
|
||||||
|
|
||||||
|
item.json = message;
|
||||||
|
message.content = undefined;
|
||||||
|
} else {
|
||||||
|
if (options.jsonParseBody === true) {
|
||||||
|
content = JSON.parse(content as string);
|
||||||
|
}
|
||||||
|
if (options.onlyContent === true) {
|
||||||
|
item.json = content as IDataObject;
|
||||||
|
} else {
|
||||||
|
message.content = content;
|
||||||
|
item.json = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.emit([
|
||||||
|
[
|
||||||
|
item,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
channel.ack(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
startConsumer();
|
||||||
|
|
||||||
|
// The "closeFunction" function gets called by n8n whenever
|
||||||
|
// the workflow gets deactivated and can so clean up.
|
||||||
|
async function closeFunction() {
|
||||||
|
await channel.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The "manualTriggerFunction" function gets called by n8n
|
||||||
|
// when a user is in the workflow editor and starts the
|
||||||
|
// workflow manually. So the function has to make sure that
|
||||||
|
// the emit() gets called with similar data like when it
|
||||||
|
// would trigger by itself so that the user knows what data
|
||||||
|
// to expect.
|
||||||
|
async function manualTriggerFunction() {
|
||||||
|
startConsumer();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
closeFunction,
|
||||||
|
manualTriggerFunction,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
BIN
packages/nodes-base/nodes/RabbitMQ/rabbitmq.png
Normal file
BIN
packages/nodes-base/nodes/RabbitMQ/rabbitmq.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 527 B |
|
@ -46,7 +46,7 @@ export class RssFeedRead implements INodeType {
|
||||||
|
|
||||||
const parser = new Parser();
|
const parser = new Parser();
|
||||||
|
|
||||||
let feed: Parser.Output;
|
let feed: Parser.Output<IDataObject>;
|
||||||
try {
|
try {
|
||||||
feed = await parser.parseURL(url);
|
feed = await parser.parseURL(url);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -105,11 +105,11 @@ export class S3 implements INodeType {
|
||||||
const items = this.getInputData();
|
const items = this.getInputData();
|
||||||
const returnData: IDataObject[] = [];
|
const returnData: IDataObject[] = [];
|
||||||
const qs: IDataObject = {};
|
const qs: IDataObject = {};
|
||||||
const headers: IDataObject = {};
|
|
||||||
let responseData;
|
let responseData;
|
||||||
const resource = this.getNodeParameter('resource', 0) as string;
|
const resource = this.getNodeParameter('resource', 0) as string;
|
||||||
const operation = this.getNodeParameter('operation', 0) as string;
|
const operation = this.getNodeParameter('operation', 0) as string;
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const headers: IDataObject = {};
|
||||||
if (resource === 'bucket') {
|
if (resource === 'bucket') {
|
||||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html
|
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html
|
||||||
if (operation === 'create') {
|
if (operation === 'create') {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { INodeProperties } from 'n8n-workflow';
|
import {
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export const channelOperations = [
|
export const channelOperations = [
|
||||||
{
|
{
|
||||||
|
@ -63,6 +65,11 @@ export const channelOperations = [
|
||||||
value: 'leave',
|
value: 'leave',
|
||||||
description: 'Leaves a conversation.',
|
description: 'Leaves a conversation.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Member',
|
||||||
|
value: 'member',
|
||||||
|
description: 'List members of a conversation.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Open',
|
name: 'Open',
|
||||||
value: 'open',
|
value: 'open',
|
||||||
|
@ -101,9 +108,9 @@ export const channelOperations = [
|
||||||
|
|
||||||
export const channelFields = [
|
export const channelFields = [
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* channel:archive */
|
/* channel:archive */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Channel',
|
displayName: 'Channel',
|
||||||
name: 'channelId',
|
name: 'channelId',
|
||||||
|
@ -125,9 +132,10 @@ export const channelFields = [
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The name of the channel to archive.',
|
description: 'The name of the channel to archive.',
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* channel:close */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* channel:close */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Channel',
|
displayName: 'Channel',
|
||||||
name: 'channelId',
|
name: 'channelId',
|
||||||
|
@ -149,9 +157,10 @@ export const channelFields = [
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The name of the channel to close.',
|
description: 'The name of the channel to close.',
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* channel:create */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* channel:create */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Channel',
|
displayName: 'Channel',
|
||||||
name: 'channelId',
|
name: 'channelId',
|
||||||
|
@ -197,9 +206,10 @@ export const channelFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* channel:invite */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* channel:invite */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Channel',
|
displayName: 'Channel',
|
||||||
name: 'channelId',
|
name: 'channelId',
|
||||||
|
@ -242,9 +252,10 @@ export const channelFields = [
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The ID of the user to invite into channel.',
|
description: 'The ID of the user to invite into channel.',
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* channel:get */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* channel:get */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Channel',
|
displayName: 'Channel',
|
||||||
name: 'channelId',
|
name: 'channelId',
|
||||||
|
@ -288,9 +299,10 @@ export const channelFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* channel:kick */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* channel:kick */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Channel',
|
displayName: 'Channel',
|
||||||
name: 'channelId',
|
name: 'channelId',
|
||||||
|
@ -332,9 +344,10 @@ export const channelFields = [
|
||||||
},
|
},
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* channel:join */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* channel:join */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Channel',
|
displayName: 'Channel',
|
||||||
name: 'channelId',
|
name: 'channelId',
|
||||||
|
@ -356,9 +369,10 @@ export const channelFields = [
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* channel:getAll */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* channel:getAll */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Return All',
|
displayName: 'Return All',
|
||||||
name: 'returnAll',
|
name: 'returnAll',
|
||||||
|
@ -451,9 +465,10 @@ export const channelFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* channel:history */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* channel:history */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Channel',
|
displayName: 'Channel',
|
||||||
name: 'channelId',
|
name: 'channelId',
|
||||||
|
@ -557,9 +572,10 @@ export const channelFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* channel:leave */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* channel:leave */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Channel',
|
displayName: 'Channel',
|
||||||
name: 'channelId',
|
name: 'channelId',
|
||||||
|
@ -581,9 +597,89 @@ export const channelFields = [
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The name of the channel to leave.',
|
description: 'The name of the channel to leave.',
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* channel:open */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* channel:member */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
{
|
||||||
|
displayName: 'Channel',
|
||||||
|
name: 'channelId',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getChannels',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'member',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'member',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: false,
|
||||||
|
description: 'If all results should be returned or only up to a given limit.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
type: 'number',
|
||||||
|
default: 100,
|
||||||
|
placeholder: 'Limit',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: [
|
||||||
|
'member',
|
||||||
|
],
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
returnAll: [
|
||||||
|
false,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Resolve Data',
|
||||||
|
name: 'resolveData',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: [
|
||||||
|
'channel',
|
||||||
|
],
|
||||||
|
operation: [
|
||||||
|
'member',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'By default the response only contain the ID to resource. If this<br />option gets activated it will resolve the data automatically.',
|
||||||
|
},
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* channel:open */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Options',
|
displayName: 'Options',
|
||||||
name: 'options',
|
name: 'options',
|
||||||
|
@ -627,9 +723,10 @@ export const channelFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* channel:rename */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* channel:rename */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Channel',
|
displayName: 'Channel',
|
||||||
name: 'channelId',
|
name: 'channelId',
|
||||||
|
@ -669,9 +766,10 @@ export const channelFields = [
|
||||||
required: true,
|
required: true,
|
||||||
description: 'New name for conversation.',
|
description: 'New name for conversation.',
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* channel:replies */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* channel:replies */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Channel',
|
displayName: 'Channel',
|
||||||
name: 'channelId',
|
name: 'channelId',
|
||||||
|
@ -793,9 +891,10 @@ export const channelFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* channel:setPurpose */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* channel:setPurpose */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Channel',
|
displayName: 'Channel',
|
||||||
name: 'channelId',
|
name: 'channelId',
|
||||||
|
@ -835,9 +934,10 @@ export const channelFields = [
|
||||||
required: true,
|
required: true,
|
||||||
description: 'A new, specialer purpose',
|
description: 'A new, specialer purpose',
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* channel:setTopic */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* channel:setTopic */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Channel',
|
displayName: 'Channel',
|
||||||
name: 'channelId',
|
name: 'channelId',
|
||||||
|
@ -877,9 +977,10 @@ export const channelFields = [
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The new topic string. Does not support formatting or linkification.',
|
description: 'The new topic string. Does not support formatting or linkification.',
|
||||||
},
|
},
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
/* channel:unarchive */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* channel:unarchive */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Channel',
|
displayName: 'Channel',
|
||||||
name: 'channelId',
|
name: 'channelId',
|
||||||
|
|
|
@ -36,9 +36,9 @@ export const fileOperations = [
|
||||||
|
|
||||||
export const fileFields = [
|
export const fileFields = [
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* file:upload */
|
/* file:upload */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Binary Data',
|
displayName: 'Binary Data',
|
||||||
name: 'binaryData',
|
name: 'binaryData',
|
||||||
|
@ -159,9 +159,10 @@ export const fileFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* file:getAll */
|
/* ----------------------------------------------------------------------- */
|
||||||
/* ----------------------------------------------------------------------- */
|
/* file:getAll */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Return All',
|
displayName: 'Return All',
|
||||||
name: 'returnAll',
|
name: 'returnAll',
|
||||||
|
@ -261,29 +262,29 @@ export const fileFields = [
|
||||||
value: 'all',
|
value: 'all',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Spaces',
|
name: 'Google Docs',
|
||||||
value: 'spaces',
|
value: 'gdocs',
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Snippets',
|
|
||||||
value: 'snippets',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Images',
|
name: 'Images',
|
||||||
value: 'images',
|
value: 'images',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Google Docs',
|
name: 'Snippets',
|
||||||
value: 'gdocs',
|
value: 'snippets',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Zips',
|
name: 'Spaces',
|
||||||
value: 'zips',
|
value: 'spaces',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'pdfs',
|
name: 'pdfs',
|
||||||
value: 'pdfs',
|
value: 'pdfs',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Zips',
|
||||||
|
value: 'zips',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
default: ['all'],
|
default: ['all'],
|
||||||
description: 'Filter files by type',
|
description: 'Filter files by type',
|
||||||
|
@ -300,9 +301,10 @@ export const fileFields = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* file:get */
|
/* ----------------------------------------------------------------------- */
|
||||||
/* ----------------------------------------------------------------------- */
|
/* file:get */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'File ID',
|
displayName: 'File ID',
|
||||||
name: 'fileId',
|
name: 'fileId',
|
||||||
|
|
|
@ -76,7 +76,7 @@ export async function slackApiRequest(this: IExecuteFunctions | IExecuteSingleFu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function slackApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
export async function slackApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||||
const returnData: IDataObject[] = [];
|
const returnData: IDataObject[] = [];
|
||||||
let responseData;
|
let responseData;
|
||||||
query.page = 1;
|
query.page = 1;
|
||||||
|
|
|
@ -31,9 +31,9 @@ export const messageOperations = [
|
||||||
|
|
||||||
export const messageFields = [
|
export const messageFields = [
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* message:post */
|
/* message:post */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Channel',
|
displayName: 'Channel',
|
||||||
name: 'channel',
|
name: 'channel',
|
||||||
|
@ -382,6 +382,13 @@ export const messageFields = [
|
||||||
default: '',
|
default: '',
|
||||||
description: 'URL to an image to use as the icon for this message.',
|
description: 'URL to an image to use as the icon for this message.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Link Names',
|
||||||
|
name: 'link_names',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'Find and link channel names and usernames.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Make Reply',
|
displayName: 'Make Reply',
|
||||||
name: 'thread_ts',
|
name: 'thread_ts',
|
||||||
|
@ -389,20 +396,6 @@ export const messageFields = [
|
||||||
default: '',
|
default: '',
|
||||||
description: 'Provide another message\'s ts value to make this message a reply.',
|
description: 'Provide another message\'s ts value to make this message a reply.',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
displayName: 'Unfurl Links',
|
|
||||||
name: 'unfurl_links',
|
|
||||||
type: 'boolean',
|
|
||||||
default: false,
|
|
||||||
description: 'Pass true to enable unfurling of primarily text-based content.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Unfurl Media',
|
|
||||||
name: 'unfurl_media',
|
|
||||||
type: 'boolean',
|
|
||||||
default: true,
|
|
||||||
description: 'Pass false to disable unfurling of media content.',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
displayName: 'Markdown',
|
displayName: 'Markdown',
|
||||||
name: 'mrkdwn',
|
name: 'mrkdwn',
|
||||||
|
@ -418,17 +411,25 @@ export const messageFields = [
|
||||||
description: 'Used in conjunction with thread_ts and indicates whether reply should be made visible to everyone in the channel or conversation.',
|
description: 'Used in conjunction with thread_ts and indicates whether reply should be made visible to everyone in the channel or conversation.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Link Names',
|
displayName: 'Unfurl Links',
|
||||||
name: 'link_names',
|
name: 'unfurl_links',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
description: 'Find and link channel names and usernames.',
|
description: 'Pass true to enable unfurling of primarily text-based content.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Unfurl Media',
|
||||||
|
name: 'unfurl_media',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
description: 'Pass false to disable unfurling of media content.',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
/* ----------------------------------------------------------------------- */
|
|
||||||
/* message:update */
|
/* ----------------------------------------------------------------------- */
|
||||||
/* ----------------------------------------------------------------------- */
|
/* message:update */
|
||||||
|
/* ----------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Channel',
|
displayName: 'Channel',
|
||||||
name: 'channelId',
|
name: 'channelId',
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue