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)
|
||||
if: success()
|
||||
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
|
||||
- name: Tag Docker image with latest
|
||||
run: docker tag n8nio/n8n:${{steps.vars.outputs.tag}}-rpi n8nio/n8n:latest-rpi
|
||||
- name: Push docker images of latest
|
||||
run: docker push n8nio/n8n:latest-rpi
|
||||
docker buildx build \
|
||||
--platform linux/arm/v7 \
|
||||
--build-arg N8N_VERSION=${{steps.vars.outputs.tag}} \
|
||||
-t ${{ secrets.DOCKER_USERNAME }}/n8n:${{steps.vars.outputs.tag}}-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
|
||||
run: docker push n8nio/n8n:latest
|
||||
|
||||
- name: Build the Docker image of version (Ubuntu)
|
||||
run: docker build --build-arg N8N_VERSION=${{steps.vars.outputs.tag}} -t n8nio/n8n:${{steps.vars.outputs.tag}}-ubuntu docker/images/n8n-ubuntu
|
||||
- name: Push Docker image of version (Ubuntu)
|
||||
run: docker push n8nio/n8n:${{steps.vars.outputs.tag}}-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}}-debian docker/images/n8n-debian
|
||||
- name: Push Docker image of version (Debian)
|
||||
run: docker push n8nio/n8n:${{steps.vars.outputs.tag}}-debian
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||

|
||||
|
||||
n8n is an extendable workflow automation tool. With a [fair-code](http://faircode.io) distribution model, n8n will always have visible source code, be available to self-host, and allow you to add your own custom functions, logic and apps. n8n's node-based approach makes it highly versatile, enabling you to connect anything to everything.
|
||||
n8n is an extendable workflow automation tool. With a [fair-code](http://faircode.io) distribution model, n8n will always have visible source code, be available to self-host, and allow you to add your own custom functions, logic and apps. n8n's node-based approach makes it highly versatile, enabling you to connect anything to everything.
|
||||
|
||||
<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>
|
||||
|
||||
|
@ -16,7 +16,7 @@ received or lost a star.
|
|||
|
||||
## 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
|
||||
|
@ -91,6 +91,6 @@ Have you found a bug :bug: ? Or maybe you have a nice feature :sparkles: to cont
|
|||
|
||||
## 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).
|
||||
|
|
|
@ -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
|
||||
[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> .
|
||||
|
||||
# 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 \
|
||||
--name n8n \
|
||||
-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 \
|
||||
--name n8n \
|
||||
-p 5678:5678 \
|
||||
-v ~/.n8n:/home/node/.n8n \
|
||||
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>
|
||||
|
||||
|
@ -33,7 +33,7 @@ Slack notification every time a Github repository received or lost a star.
|
|||
|
||||
## 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
|
||||
|
@ -305,6 +305,6 @@ Before you upgrade to the latest version make sure to check here if there are an
|
|||
|
||||
## 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)
|
||||
|
|
|
@ -6,6 +6,8 @@ if [ -d /root/.n8n ] ; then
|
|||
ln -s /root/.n8n /home/node/
|
||||
fi
|
||||
|
||||
chown -R node /home/node
|
||||
|
||||
if [ "$#" -gt 0 ]; then
|
||||
# Got started with arguments
|
||||
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>
|
||||
|
||||
|
@ -32,7 +32,7 @@ Slack notification every time a Github repository received or lost a star.
|
|||
|
||||
## 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
|
||||
|
@ -100,7 +100,7 @@ Before you upgrade to the latest version make sure to check here if there are an
|
|||
|
||||
## 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)
|
||||
|
||||
|
|
|
@ -121,10 +121,16 @@ export class Start extends Command {
|
|||
const { flags } = this.parse(Start);
|
||||
|
||||
// Wrap that the process does not close but we can still use async
|
||||
(async () => {
|
||||
await (async () => {
|
||||
try {
|
||||
// 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
|
||||
const userSettings = await UserSettings.prepareUserSettings();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n",
|
||||
"version": "0.96.0",
|
||||
"version": "0.99.1",
|
||||
"description": "n8n Workflow Automation Tool",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -103,9 +103,9 @@
|
|||
"lodash.get": "^4.4.2",
|
||||
"mongodb": "^3.5.5",
|
||||
"mysql2": "~2.1.0",
|
||||
"n8n-core": "~0.54.0",
|
||||
"n8n-editor-ui": "~0.66.0",
|
||||
"n8n-nodes-base": "~0.91.0",
|
||||
"n8n-core": "~0.56.0",
|
||||
"n8n-editor-ui": "~0.68.1",
|
||||
"n8n-nodes-base": "~0.94.0",
|
||||
"n8n-workflow": "~0.47.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"open": "^7.0.0",
|
||||
|
|
|
@ -64,7 +64,11 @@ export function sendSuccessResponse(res: Response, data: any, raw?: boolean, res
|
|||
}
|
||||
|
||||
if (raw === true) {
|
||||
res.json(data);
|
||||
if (typeof data === 'string') {
|
||||
res.send(data);
|
||||
} else {
|
||||
res.json(data);
|
||||
}
|
||||
} else {
|
||||
res.json({
|
||||
data,
|
||||
|
|
|
@ -44,9 +44,11 @@ import * as config from '../config';
|
|||
|
||||
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
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
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;
|
||||
if (executionId !== undefined) {
|
||||
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 = {
|
||||
execution: {
|
||||
id: executionId,
|
||||
|
@ -77,8 +79,16 @@ function executeErrorWorkflow(workflowData: IWorkflowBase, fullRunData: IRun, mo
|
|||
name: workflowData.name,
|
||||
},
|
||||
};
|
||||
|
||||
// Run the error workflow
|
||||
WorkflowHelpers.executeErrorWorkflow(workflowData.settings.errorWorkflow as string, workflowErrorData);
|
||||
// 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);
|
||||
} 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",
|
||||
"version": "0.54.0",
|
||||
"version": "0.56.0",
|
||||
"description": "Core functionality of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
|
|
@ -172,6 +172,10 @@ export function requestOAuth2(this: IAllExecuteFunctions, credentialsType: strin
|
|||
client_secret: credentials.clientSecret as string,
|
||||
};
|
||||
tokenRefreshOptions.body = body;
|
||||
// Override authorization property so the credentails are not included in it
|
||||
tokenRefreshOptions.headers = {
|
||||
Authorization: '',
|
||||
};
|
||||
}
|
||||
|
||||
const newToken = await token.refresh(tokenRefreshOptions);
|
||||
|
@ -241,6 +245,15 @@ export function requestOAuth1(this: IAllExecuteFunctions, credentialsType: strin
|
|||
//@ts-ignore
|
||||
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
|
||||
requestOptions.headers = oauth.toHeader(oauth.authorize(requestOptions, token));
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-editor-ui",
|
||||
"version": "0.66.0",
|
||||
"version": "0.68.1",
|
||||
"description": "Workflow Editor UI for n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -61,7 +61,7 @@
|
|||
"flatted": "^2.0.0",
|
||||
"jquery": "^3.4.1",
|
||||
"jshint": "^2.9.7",
|
||||
"jsplumb": "^2.10.0",
|
||||
"jsplumb": "2.15.4",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
|
@ -79,7 +79,7 @@
|
|||
"uuid": "^8.1.0",
|
||||
"vue": "^2.6.9",
|
||||
"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-router": "^3.0.6",
|
||||
"vue-template-compiler": "^2.5.17",
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
</el-button>
|
||||
|
||||
<div class="binary-data-window-wrapper">
|
||||
<div v-if="binaryData === null">
|
||||
<div v-if="!binaryData">
|
||||
Data to display did not get found
|
||||
</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>
|
||||
|
|
|
@ -10,8 +10,12 @@
|
|||
<div v-if="multipleValues === true">
|
||||
<div v-for="(value, index) in values[property.name]" :key="property.name + index" class="parameter-item">
|
||||
<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)" />
|
||||
<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>
|
||||
<parameter-input-list :parameters="property.values" :nodeValues="nodeValues" :path="getPropertyPath(property.name, index)" :hideDelete="true" @valueChanged="valueChanged" />
|
||||
</div>
|
||||
|
@ -19,7 +23,7 @@
|
|||
</div>
|
||||
<div v-else class="parameter-item">
|
||||
<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)" />
|
||||
</div>
|
||||
<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 [];
|
||||
},
|
||||
sortable (): string {
|
||||
return this.parameter.typeOptions && this.parameter.typeOptions.sortable;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
deleteOption (optionName: string, index?: number) {
|
||||
|
@ -133,6 +140,26 @@ export default mixins(genericHelpers)
|
|||
|
||||
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) {
|
||||
const option = this.getOptionProperties(optionName);
|
||||
if (option === undefined) {
|
||||
|
|
|
@ -10,8 +10,12 @@
|
|||
</div>
|
||||
|
||||
<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)">
|
||||
<font-awesome-icon icon="trash" />
|
||||
<div class="delete-item clickable" v-if="!isReadOnly">
|
||||
<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 v-if="parameter.type === 'collection'">
|
||||
<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 {
|
||||
return this.parameter.options.length === 1;
|
||||
},
|
||||
sortable (): string {
|
||||
return this.parameter.typeOptions && this.parameter.typeOptions.sortable;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addItem () {
|
||||
|
@ -97,6 +104,26 @@ export default mixins(genericHelpers)
|
|||
getPath (index?: number): string {
|
||||
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) {
|
||||
this.$emit('valueChanged', parameterData);
|
||||
},
|
||||
|
@ -125,6 +152,7 @@ export default mixins(genericHelpers)
|
|||
top: .3em;
|
||||
z-index: 999;
|
||||
color: #f56c6c;
|
||||
width: 15px;
|
||||
|
||||
:hover {
|
||||
color: #ff0000;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<div class="node-executing-info" title="Node is executing">
|
||||
<font-awesome-icon icon="sync-alt" spin />
|
||||
</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" >
|
||||
<font-awesome-icon icon="trash" />
|
||||
</div>
|
||||
|
|
|
@ -62,6 +62,21 @@
|
|||
<el-radio-button label="Binary" v-if="binaryData.length !== 0"></el-radio-button>
|
||||
</el-radio-group>
|
||||
</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 class="data-display-content">
|
||||
<span v-if="node && workflowRunData !== null && workflowRunData.hasOwnProperty(node.name)">
|
||||
|
@ -104,10 +119,18 @@
|
|||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<json-tree
|
||||
<vue-json-pretty
|
||||
v-else-if="displayMode === 'JSON'"
|
||||
: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"
|
||||
/>
|
||||
</div>
|
||||
|
@ -171,8 +194,8 @@
|
|||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
// @ts-ignore
|
||||
import JsonTree from 'vue-json-tree';
|
||||
//@ts-ignore
|
||||
import VueJsonPretty from 'vue-json-pretty';
|
||||
import {
|
||||
GenericValue,
|
||||
IBinaryData,
|
||||
|
@ -200,13 +223,18 @@ import {
|
|||
|
||||
import BinaryDataDisplay from '@/components/BinaryDataDisplay.vue';
|
||||
|
||||
import { copyPaste } from '@/components/mixins/copyPaste';
|
||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
||||
import { workflowRun } from '@/components/mixins/workflowRun';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
// A path that does not exist so that nothing is selected by default
|
||||
const deselectedPlaceholder = '_!^&*';
|
||||
|
||||
export default mixins(
|
||||
copyPaste,
|
||||
genericHelpers,
|
||||
nodeHelpers,
|
||||
workflowRun,
|
||||
|
@ -215,13 +243,18 @@ export default mixins(
|
|||
name: 'RunData',
|
||||
components: {
|
||||
BinaryDataDisplay,
|
||||
JsonTree,
|
||||
VueJsonPretty,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
binaryDataPreviewActive: false,
|
||||
dataSize: 0,
|
||||
deselectedPlaceholder,
|
||||
displayMode: 'Table',
|
||||
state: {
|
||||
value: '' as object | number | string,
|
||||
path: deselectedPlaceholder,
|
||||
},
|
||||
runIndex: 0,
|
||||
showData: false,
|
||||
outputIndex: 0,
|
||||
|
@ -380,18 +413,6 @@ export default mixins(
|
|||
},
|
||||
},
|
||||
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 () {
|
||||
this.binaryDataDisplayVisible = false;
|
||||
this.binaryDataDisplayData = null;
|
||||
|
@ -465,7 +486,9 @@ export default mixins(
|
|||
this.$store.commit('setWorkflowExecutionData', null);
|
||||
this.updateNodesExecutionIssues();
|
||||
},
|
||||
// displayBinaryData (binaryData: IBinaryData) {
|
||||
dataItemClicked (path: string, data: object | number | string) {
|
||||
this.state.value = data;
|
||||
},
|
||||
displayBinaryData (index: number, key: string) {
|
||||
this.binaryDataDisplayVisible = true;
|
||||
|
||||
|
@ -477,6 +500,85 @@ export default mixins(
|
|||
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 () {
|
||||
// Hide by default the data from being displayed
|
||||
this.showData = false;
|
||||
|
@ -492,10 +594,6 @@ export default mixins(
|
|||
// Data is reasonable small (< 200kb) so display it directly
|
||||
this.showData = true;
|
||||
}
|
||||
|
||||
if (this.displayMode === 'Binary' && this.binaryData.length === 0) {
|
||||
this.displayMode = 'Table';
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
@ -504,6 +602,12 @@ export default mixins(
|
|||
this.outputIndex = 0;
|
||||
this.maxDisplayItems = 25;
|
||||
this.refreshDataSize();
|
||||
if (this.displayMode === 'Binary') {
|
||||
this.closeBinaryDataDisplay();
|
||||
if (this.binaryData.length === 0) {
|
||||
this.displayMode = 'Table';
|
||||
}
|
||||
}
|
||||
},
|
||||
jsonData () {
|
||||
this.refreshDataSize();
|
||||
|
@ -610,15 +714,8 @@ export default mixins(
|
|||
}
|
||||
|
||||
.json-data {
|
||||
.json-tree {
|
||||
&.vjs-tree {
|
||||
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-left: 10px;
|
||||
|
||||
.select-button {
|
||||
height: 30px;
|
||||
top: 50px;
|
||||
right: 30px;
|
||||
position: absolute;
|
||||
text-align: right;
|
||||
width: 200px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
display: inline-block;
|
||||
line-height: 30px;
|
||||
|
|
|
@ -341,6 +341,14 @@ export const nodeBase = mixins(
|
|||
}
|
||||
},
|
||||
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.$store.getters.isActionActive('dragActive')) {
|
||||
this.$store.commit('removeActiveAction', 'dragActive');
|
||||
|
|
|
@ -5,6 +5,7 @@ import Vue from 'vue';
|
|||
import 'prismjs';
|
||||
import 'prismjs/themes/prism.css';
|
||||
import 'vue-prism-editor/dist/VuePrismEditor.css';
|
||||
import 'vue-json-pretty/lib/styles.css';
|
||||
import Vue2TouchEvents from 'vue2-touch-events';
|
||||
|
||||
import * as ElementUI from 'element-ui';
|
||||
|
|
|
@ -138,14 +138,13 @@ export const store = new Vuex.Store({
|
|||
state.activeWorkflows = newActiveWorkflows;
|
||||
},
|
||||
setWorkflowActive (state, workflowId: string) {
|
||||
state.stateIsDirty = true;
|
||||
state.stateIsDirty = false;
|
||||
const index = state.activeWorkflows.indexOf(workflowId);
|
||||
if (index === -1) {
|
||||
state.activeWorkflows.push(workflowId);
|
||||
}
|
||||
},
|
||||
setWorkflowInactive (state, workflowId: string) {
|
||||
state.stateIsDirty = true;
|
||||
const index = state.activeWorkflows.indexOf(workflowId);
|
||||
if (index !== -1) {
|
||||
state.selectedNodes.splice(index, 1);
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"noImplicitReturns": true,
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"skipLibCheck": true,
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "node",
|
||||
"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 {
|
||||
name = 'cortexApi';
|
||||
displayName = 'Cortex API';
|
||||
documentationUrl = 'cortex';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'API Key',
|
||||
|
|
|
@ -3,6 +3,11 @@ import {
|
|||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
const scopes = [
|
||||
'files.content.write',
|
||||
'files.content.read',
|
||||
'sharing.read',
|
||||
];
|
||||
|
||||
export class DropboxOAuth2Api implements ICredentialType {
|
||||
name = 'dropboxOAuth2Api';
|
||||
|
@ -30,13 +35,13 @@ export class DropboxOAuth2Api implements ICredentialType {
|
|||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: '',
|
||||
default: scopes.join(' '),
|
||||
},
|
||||
{
|
||||
displayName: 'Auth URI Query Parameters',
|
||||
name: 'authQueryParameters',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: '',
|
||||
default: 'token_access_type=offline',
|
||||
},
|
||||
{
|
||||
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',
|
||||
name: 'authUrl',
|
||||
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,
|
||||
},
|
||||
{
|
||||
displayName: 'Access Token URL',
|
||||
name: 'accessTokenUrl',
|
||||
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,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -22,14 +22,14 @@ export class GitlabOAuth2Api implements ICredentialType {
|
|||
displayName: 'Authorization URL',
|
||||
name: 'authUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://gitlab.com/oauth/authorize',
|
||||
default: '={{$parameter["server"]}}/oauth/authorize',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Access Token URL',
|
||||
name: 'accessTokenUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://gitlab.com/oauth/token',
|
||||
default: '={{$parameter["server"]}}/oauth/token',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -50,7 +50,7 @@ export class MauticOAuth2Api implements ICredentialType {
|
|||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
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',
|
||||
'files:read',
|
||||
'files:write',
|
||||
'groups:read',
|
||||
'im:read',
|
||||
'mpim:read',
|
||||
'reactions:read',
|
||||
'reactions:write',
|
||||
'stars:read',
|
||||
'stars:write',
|
||||
'users.profile:read',
|
||||
'users.profile:write',
|
||||
'groups:read',
|
||||
'im:read',
|
||||
'mpim:read',
|
||||
];
|
||||
|
||||
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 {
|
||||
name = 'theHiveApi';
|
||||
displayName = 'The Hive API';
|
||||
documentationUrl = 'theHive';
|
||||
properties = [
|
||||
{
|
||||
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
|
||||
async getContactCustomFields(this: ILoadOptionsFunctions): Promise<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) {
|
||||
const fieldName = field.title;
|
||||
const fieldId = field.id;
|
||||
|
@ -287,7 +287,7 @@ export class ActiveCampaign implements INodeType {
|
|||
// select them easily
|
||||
async getAccountCustomFields(this: ILoadOptionsFunctions): Promise<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) {
|
||||
const fieldName = field.fieldLabel;
|
||||
const fieldId = field.id;
|
||||
|
@ -298,6 +298,19 @@ export class ActiveCampaign implements INodeType {
|
|||
}
|
||||
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',
|
||||
name: 'tagId',
|
||||
type: 'number',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTags',
|
||||
},
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
|
|
|
@ -14,9 +14,16 @@ import {
|
|||
import {
|
||||
asanaApiRequest,
|
||||
asanaApiRequestAllItems,
|
||||
getTaskFields,
|
||||
getWorkspaces,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import * as moment from 'moment-timezone';
|
||||
|
||||
import {
|
||||
snakeCase,
|
||||
} from 'change-case';
|
||||
|
||||
export class Asana implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Asana',
|
||||
|
@ -83,6 +90,10 @@ export class Asana implements INodeType {
|
|||
name: 'Project',
|
||||
value: 'project',
|
||||
},
|
||||
{
|
||||
name: 'Subtask',
|
||||
value: 'subtask',
|
||||
},
|
||||
{
|
||||
name: 'Task',
|
||||
value: 'task',
|
||||
|
@ -103,7 +114,273 @@ export class Asana implements INodeType {
|
|||
default: 'task',
|
||||
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
|
||||
// ----------------------------------
|
||||
|
@ -134,6 +411,11 @@ export class Asana implements INodeType {
|
|||
value: 'get',
|
||||
description: 'Get a task',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all tasks',
|
||||
},
|
||||
{
|
||||
name: 'Move',
|
||||
value: 'move',
|
||||
|
@ -241,6 +523,145 @@ export class Asana implements INodeType {
|
|||
},
|
||||
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
|
||||
|
@ -1219,12 +1640,25 @@ export class Asana implements INodeType {
|
|||
|
||||
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[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const timezone = this.getTimezone();
|
||||
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
|
@ -1240,6 +1674,61 @@ export class Asana implements INodeType {
|
|||
body = {};
|
||||
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 (operation === 'create') {
|
||||
// ----------------------------------
|
||||
|
@ -1286,6 +1775,47 @@ export class Asana implements INodeType {
|
|||
|
||||
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') {
|
||||
// ----------------------------------
|
||||
// task:move
|
||||
|
|
|
@ -121,3 +121,40 @@ export async function getWorkspaces(this: ILoadOptionsFunctions): Promise < INod
|
|||
|
||||
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',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
description: 'Starts the workflow when a Github events occurs.',
|
||||
description: 'Starts the workflow when a Box events occurs.',
|
||||
defaults: {
|
||||
name: 'Box Trigger',
|
||||
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 |
File diff suppressed because it is too large
Load diff
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',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a file.',
|
||||
},
|
||||
{
|
||||
name: 'Download',
|
||||
value: 'download',
|
||||
|
@ -107,6 +112,11 @@ export class Ftp implements INodeType {
|
|||
value: 'list',
|
||||
description: 'List folder content.',
|
||||
},
|
||||
{
|
||||
name: 'Rename',
|
||||
value: 'rename',
|
||||
description: 'Rename/move oldPath to newPath.',
|
||||
},
|
||||
{
|
||||
name: 'Upload',
|
||||
value: 'upload',
|
||||
|
@ -117,6 +127,25 @@ export class Ftp implements INodeType {
|
|||
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
|
||||
// ----------------------------------
|
||||
|
@ -152,6 +181,40 @@ export class Ftp implements INodeType {
|
|||
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
|
||||
// ----------------------------------
|
||||
|
@ -318,9 +381,10 @@ export class Ftp implements INodeType {
|
|||
items[i] = newItem;
|
||||
|
||||
if (protocol === 'sftp') {
|
||||
const path = this.getNodeParameter('path', i) as string;
|
||||
|
||||
if (operation === 'list') {
|
||||
const path = this.getNodeParameter('path', i) as string;
|
||||
|
||||
const recursive = this.getNodeParameter('recursive', i) as boolean;
|
||||
|
||||
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') {
|
||||
const path = this.getNodeParameter('path', i) as string;
|
||||
|
||||
responseData = await sftp!.get(path);
|
||||
|
||||
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i) as string;
|
||||
|
@ -385,9 +469,9 @@ export class Ftp implements INodeType {
|
|||
|
||||
if (protocol === 'ftp') {
|
||||
|
||||
const path = this.getNodeParameter('path', i) as string;
|
||||
|
||||
if (operation === 'list') {
|
||||
const path = this.getNodeParameter('path', i) as string;
|
||||
|
||||
const recursive = this.getNodeParameter('recursive', i) as boolean;
|
||||
|
||||
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') {
|
||||
const path = this.getNodeParameter('path', i) as string;
|
||||
|
||||
responseData = await ftp!.get(path);
|
||||
|
||||
// Convert readable stream to buffer so that can be displayed properly
|
||||
|
@ -420,6 +514,17 @@ export class Ftp implements INodeType {
|
|||
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') {
|
||||
const remotePath = this.getNodeParameter('path', i) as string;
|
||||
const fileName = basename(remotePath);
|
||||
|
|
|
@ -121,6 +121,13 @@ export class CloudFirestore implements INodeType {
|
|||
{ 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) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else {
|
||||
|
@ -148,6 +155,9 @@ export class CloudFirestore implements INodeType {
|
|||
`/${projectId}/databases/${database}/documents/${collection}`,
|
||||
document,
|
||||
);
|
||||
|
||||
responseData.id = (responseData.name as string).split('/').pop();
|
||||
|
||||
if (simple === false) {
|
||||
returnData.push(responseData);
|
||||
} else {
|
||||
|
@ -179,11 +189,16 @@ export class CloudFirestore implements INodeType {
|
|||
) as IDataObject;
|
||||
responseData = getAllResponse.documents;
|
||||
}
|
||||
responseData = responseData.map((element: IDataObject) => {
|
||||
element.id = (element.name as string).split('/').pop();
|
||||
return element;
|
||||
});
|
||||
if (simple === false) {
|
||||
returnData.push.apply(returnData, responseData);
|
||||
} else {
|
||||
returnData.push.apply(returnData, responseData.map((element: IDataObject) => fullDocumentToJson(element as IDataObject)));
|
||||
}
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
const responseData: IDataObject[] = [];
|
||||
|
||||
|
@ -295,6 +310,14 @@ export class CloudFirestore implements INodeType {
|
|||
`/${projectId}/databases/${database}/documents:runQuery`,
|
||||
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) {
|
||||
returnData.push.apply(returnData, responseData);
|
||||
} else {
|
||||
|
|
|
@ -117,6 +117,7 @@ export function fullDocumentToJson(data: IDataObject): IDataObject {
|
|||
|
||||
return {
|
||||
_name: data.name,
|
||||
_id: data.id,
|
||||
_createTime: data.createTime,
|
||||
_updateTime: data.updateTime,
|
||||
...documentToJson(data.fields as IDataObject),
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
|
||||
export class RealtimeDatabase implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Google Firebase Realtime Database',
|
||||
displayName: 'Google Cloud Realtime Database',
|
||||
name: 'googleFirebaseRealtimeDatabase',
|
||||
icon: 'file:googleFirebaseRealtimeDatabase.png',
|
||||
group: ['input'],
|
||||
|
|
|
@ -21,6 +21,10 @@ import {
|
|||
ValueRenderOption,
|
||||
} from './GoogleSheet';
|
||||
|
||||
import {
|
||||
googleApiRequest,
|
||||
} from './GenericFunctions';
|
||||
|
||||
export class GoogleSheets implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Google Sheets ',
|
||||
|
@ -28,6 +32,7 @@ export class GoogleSheets implements INodeType {
|
|||
icon: 'file:googlesheets.png',
|
||||
group: ['input', 'output'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Read, update and write data to Google Sheets',
|
||||
defaults: {
|
||||
name: 'Google Sheets',
|
||||
|
@ -76,10 +81,35 @@ export class GoogleSheets implements INodeType {
|
|||
],
|
||||
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',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sheet',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Append',
|
||||
|
@ -123,6 +153,13 @@ export class GoogleSheets implements INodeType {
|
|||
displayName: 'Sheet ID',
|
||||
name: 'sheetId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sheet',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
required: true,
|
||||
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',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sheet',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
operation: [
|
||||
'delete',
|
||||
|
@ -158,6 +200,9 @@ export class GoogleSheets implements INodeType {
|
|||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sheet',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
|
@ -254,6 +299,9 @@ export class GoogleSheets implements INodeType {
|
|||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sheet',
|
||||
],
|
||||
operation: [
|
||||
'read',
|
||||
],
|
||||
|
@ -269,6 +317,9 @@ export class GoogleSheets implements INodeType {
|
|||
default: 'data',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sheet',
|
||||
],
|
||||
operation: [
|
||||
'read',
|
||||
],
|
||||
|
@ -289,6 +340,9 @@ export class GoogleSheets implements INodeType {
|
|||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sheet',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
|
@ -304,6 +358,9 @@ export class GoogleSheets implements INodeType {
|
|||
default: 'data',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sheet',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
|
@ -327,6 +384,11 @@ export class GoogleSheets implements INodeType {
|
|||
},
|
||||
default: 1,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sheet',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
operation: [
|
||||
'append',
|
||||
|
@ -352,6 +414,11 @@ export class GoogleSheets implements INodeType {
|
|||
minValue: 0,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sheet',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
operation: [
|
||||
'clear',
|
||||
|
@ -379,6 +446,9 @@ export class GoogleSheets implements INodeType {
|
|||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sheet',
|
||||
],
|
||||
operation: [
|
||||
'lookup',
|
||||
],
|
||||
|
@ -394,6 +464,9 @@ export class GoogleSheets implements INodeType {
|
|||
placeholder: 'frank@example.com',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sheet',
|
||||
],
|
||||
operation: [
|
||||
'lookup',
|
||||
],
|
||||
|
@ -412,6 +485,9 @@ export class GoogleSheets implements INodeType {
|
|||
default: 'id',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sheet',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
|
@ -431,6 +507,9 @@ export class GoogleSheets implements INodeType {
|
|||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'sheet',
|
||||
],
|
||||
operation: [
|
||||
'append',
|
||||
'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,188 +829,247 @@ export class GoogleSheets implements INodeType {
|
|||
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const spreadsheetId = this.getNodeParameter('sheetId', 0) as string;
|
||||
|
||||
const sheet = new GoogleSheet(spreadsheetId, this);
|
||||
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
|
||||
let range = '';
|
||||
if (operation !== 'delete') {
|
||||
range = this.getNodeParameter('range', 0) as string;
|
||||
}
|
||||
if (resource === 'sheet') {
|
||||
|
||||
const options = this.getNodeParameter('options', 0, {}) as IDataObject;
|
||||
const spreadsheetId = this.getNodeParameter('sheetId', 0) as string;
|
||||
|
||||
const valueInputMode = (options.valueInputMode || 'RAW') as ValueInputOption;
|
||||
const valueRenderMode = (options.valueRenderMode || 'UNFORMATTED_VALUE') as ValueRenderOption;
|
||||
const sheet = new GoogleSheet(spreadsheetId, this);
|
||||
|
||||
if (operation === 'append') {
|
||||
// ----------------------------------
|
||||
// append
|
||||
// ----------------------------------
|
||||
const keyRow = parseInt(this.getNodeParameter('keyRow', 0) as string, 10);
|
||||
|
||||
const items = this.getInputData();
|
||||
|
||||
const setData: IDataObject[] = [];
|
||||
items.forEach((item) => {
|
||||
setData.push(item.json);
|
||||
});
|
||||
|
||||
// Convert data into array format
|
||||
const data = await sheet.appendSheetData(setData, sheet.encodeRange(range), keyRow, valueInputMode);
|
||||
|
||||
// TODO: Should add this data somewhere
|
||||
// TODO: Should have something like add metadata which does not get passed through
|
||||
|
||||
return this.prepareOutputData(items);
|
||||
} else if (operation === 'clear') {
|
||||
// ----------------------------------
|
||||
// clear
|
||||
// ----------------------------------
|
||||
|
||||
await sheet.clearData(sheet.encodeRange(range));
|
||||
|
||||
const items = this.getInputData();
|
||||
return this.prepareOutputData(items);
|
||||
} else if (operation === 'delete') {
|
||||
// ----------------------------------
|
||||
// delete
|
||||
// ----------------------------------
|
||||
|
||||
const requests: IDataObject[] = [];
|
||||
|
||||
const toDelete = this.getNodeParameter('toDelete', 0) as IToDelete;
|
||||
|
||||
const deletePropertyToDimensions: IDataObject = {
|
||||
'columns': 'COLUMNS',
|
||||
'rows': 'ROWS',
|
||||
};
|
||||
|
||||
for (const propertyName of Object.keys(deletePropertyToDimensions)) {
|
||||
if (toDelete[propertyName] !== undefined) {
|
||||
toDelete[propertyName]!.forEach(range => {
|
||||
requests.push({
|
||||
deleteDimension: {
|
||||
range: {
|
||||
sheetId: range.sheetId,
|
||||
dimension: deletePropertyToDimensions[propertyName] as string,
|
||||
startIndex: range.startIndex,
|
||||
endIndex: parseInt(range.startIndex.toString(), 10) + parseInt(range.amount.toString(), 10),
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
let range = '';
|
||||
if (operation !== 'delete') {
|
||||
range = this.getNodeParameter('range', 0) as string;
|
||||
}
|
||||
|
||||
const data = await sheet.spreadsheetBatchUpdate(requests);
|
||||
const options = this.getNodeParameter('options', 0, {}) as IDataObject;
|
||||
|
||||
const items = this.getInputData();
|
||||
return this.prepareOutputData(items);
|
||||
} else if (operation === 'lookup') {
|
||||
// ----------------------------------
|
||||
// lookup
|
||||
// ----------------------------------
|
||||
const valueInputMode = (options.valueInputMode || 'RAW') as ValueInputOption;
|
||||
const valueRenderMode = (options.valueRenderMode || 'UNFORMATTED_VALUE') as ValueRenderOption;
|
||||
|
||||
const sheetData = await sheet.getData(sheet.encodeRange(range), valueRenderMode);
|
||||
|
||||
if (sheetData === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const dataStartRow = parseInt(this.getNodeParameter('dataStartRow', 0) as string, 10);
|
||||
const keyRow = parseInt(this.getNodeParameter('keyRow', 0) as string, 10);
|
||||
|
||||
const items = this.getInputData();
|
||||
|
||||
const lookupValues: ILookupValues[] = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
lookupValues.push({
|
||||
lookupColumn: this.getNodeParameter('lookupColumn', i) as string,
|
||||
lookupValue: this.getNodeParameter('lookupValue', i) as string,
|
||||
});
|
||||
}
|
||||
|
||||
let returnData = await sheet.lookupValues(sheetData, keyRow, dataStartRow, lookupValues, options.returnAllMatches as boolean | undefined);
|
||||
|
||||
if (returnData.length === 0 && options.continue && options.returnAllMatches) {
|
||||
returnData = [{}];
|
||||
} else if (returnData.length === 1 && Object.keys(returnData[0]).length === 0 && !options.continue && !options.returnAllMatches) {
|
||||
returnData = [];
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
} else if (operation === 'read') {
|
||||
// ----------------------------------
|
||||
// read
|
||||
// ----------------------------------
|
||||
|
||||
const rawData = this.getNodeParameter('rawData', 0) as boolean;
|
||||
|
||||
const sheetData = await sheet.getData(sheet.encodeRange(range), valueRenderMode);
|
||||
|
||||
let returnData: IDataObject[];
|
||||
if (!sheetData) {
|
||||
returnData = [];
|
||||
} else if (rawData === true) {
|
||||
const dataProperty = this.getNodeParameter('dataProperty', 0) as string;
|
||||
returnData = [
|
||||
{
|
||||
[dataProperty]: sheetData,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
const dataStartRow = parseInt(this.getNodeParameter('dataStartRow', 0) as string, 10);
|
||||
if (operation === 'append') {
|
||||
// ----------------------------------
|
||||
// append
|
||||
// ----------------------------------
|
||||
const keyRow = parseInt(this.getNodeParameter('keyRow', 0) as string, 10);
|
||||
|
||||
returnData = sheet.structureArrayDataByColumn(sheetData, keyRow, dataStartRow);
|
||||
}
|
||||
|
||||
if (returnData.length === 0 && options.continue) {
|
||||
returnData = [{}];
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
} else if (operation === 'update') {
|
||||
// ----------------------------------
|
||||
// update
|
||||
// ----------------------------------
|
||||
|
||||
const rawData = this.getNodeParameter('rawData', 0) as boolean;
|
||||
|
||||
const items = this.getInputData();
|
||||
|
||||
if (rawData === true) {
|
||||
const dataProperty = this.getNodeParameter('dataProperty', 0) as string;
|
||||
|
||||
const updateData: ISheetUpdateData[] = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
updateData.push({
|
||||
range,
|
||||
values: items[i].json[dataProperty] as string[][],
|
||||
});
|
||||
}
|
||||
|
||||
const data = await sheet.batchUpdate(updateData, valueInputMode);
|
||||
} else {
|
||||
const keyName = this.getNodeParameter('key', 0) as string;
|
||||
const keyRow = parseInt(this.getNodeParameter('keyRow', 0) as string, 10);
|
||||
const dataStartRow = parseInt(this.getNodeParameter('dataStartRow', 0) as string, 10);
|
||||
const items = this.getInputData();
|
||||
|
||||
const setData: IDataObject[] = [];
|
||||
items.forEach((item) => {
|
||||
setData.push(item.json);
|
||||
});
|
||||
|
||||
const data = await sheet.updateSheetData(setData, keyName, range, keyRow, dataStartRow, valueInputMode, valueRenderMode);
|
||||
// Convert data into array format
|
||||
const data = await sheet.appendSheetData(setData, sheet.encodeRange(range), keyRow, valueInputMode);
|
||||
|
||||
// TODO: Should add this data somewhere
|
||||
// TODO: Should have something like add metadata which does not get passed through
|
||||
|
||||
return this.prepareOutputData(items);
|
||||
} else if (operation === 'clear') {
|
||||
// ----------------------------------
|
||||
// clear
|
||||
// ----------------------------------
|
||||
|
||||
await sheet.clearData(sheet.encodeRange(range));
|
||||
|
||||
const items = this.getInputData();
|
||||
return this.prepareOutputData(items);
|
||||
} else if (operation === 'delete') {
|
||||
// ----------------------------------
|
||||
// delete
|
||||
// ----------------------------------
|
||||
|
||||
const requests: IDataObject[] = [];
|
||||
|
||||
const toDelete = this.getNodeParameter('toDelete', 0) as IToDelete;
|
||||
|
||||
const deletePropertyToDimensions: IDataObject = {
|
||||
'columns': 'COLUMNS',
|
||||
'rows': 'ROWS',
|
||||
};
|
||||
|
||||
for (const propertyName of Object.keys(deletePropertyToDimensions)) {
|
||||
if (toDelete[propertyName] !== undefined) {
|
||||
toDelete[propertyName]!.forEach(range => {
|
||||
requests.push({
|
||||
deleteDimension: {
|
||||
range: {
|
||||
sheetId: range.sheetId,
|
||||
dimension: deletePropertyToDimensions[propertyName] as string,
|
||||
startIndex: range.startIndex,
|
||||
endIndex: parseInt(range.startIndex.toString(), 10) + parseInt(range.amount.toString(), 10),
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const data = await sheet.spreadsheetBatchUpdate(requests);
|
||||
|
||||
const items = this.getInputData();
|
||||
return this.prepareOutputData(items);
|
||||
} else if (operation === 'lookup') {
|
||||
// ----------------------------------
|
||||
// lookup
|
||||
// ----------------------------------
|
||||
|
||||
const sheetData = await sheet.getData(sheet.encodeRange(range), valueRenderMode);
|
||||
|
||||
if (sheetData === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const dataStartRow = parseInt(this.getNodeParameter('dataStartRow', 0) as string, 10);
|
||||
const keyRow = parseInt(this.getNodeParameter('keyRow', 0) as string, 10);
|
||||
|
||||
const items = this.getInputData();
|
||||
|
||||
const lookupValues: ILookupValues[] = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
lookupValues.push({
|
||||
lookupColumn: this.getNodeParameter('lookupColumn', i) as string,
|
||||
lookupValue: this.getNodeParameter('lookupValue', i) as string,
|
||||
});
|
||||
}
|
||||
|
||||
let returnData = await sheet.lookupValues(sheetData, keyRow, dataStartRow, lookupValues, options.returnAllMatches as boolean | undefined);
|
||||
|
||||
if (returnData.length === 0 && options.continue && options.returnAllMatches) {
|
||||
returnData = [{}];
|
||||
} else if (returnData.length === 1 && Object.keys(returnData[0]).length === 0 && !options.continue && !options.returnAllMatches) {
|
||||
returnData = [];
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
} else if (operation === 'read') {
|
||||
// ----------------------------------
|
||||
// read
|
||||
// ----------------------------------
|
||||
|
||||
const rawData = this.getNodeParameter('rawData', 0) as boolean;
|
||||
|
||||
const sheetData = await sheet.getData(sheet.encodeRange(range), valueRenderMode);
|
||||
|
||||
let returnData: IDataObject[];
|
||||
if (!sheetData) {
|
||||
returnData = [];
|
||||
} else if (rawData === true) {
|
||||
const dataProperty = this.getNodeParameter('dataProperty', 0) as string;
|
||||
returnData = [
|
||||
{
|
||||
[dataProperty]: sheetData,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
const dataStartRow = parseInt(this.getNodeParameter('dataStartRow', 0) as string, 10);
|
||||
const keyRow = parseInt(this.getNodeParameter('keyRow', 0) as string, 10);
|
||||
|
||||
returnData = sheet.structureArrayDataByColumn(sheetData, keyRow, dataStartRow);
|
||||
}
|
||||
|
||||
if (returnData.length === 0 && options.continue) {
|
||||
returnData = [{}];
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
} else if (operation === 'update') {
|
||||
// ----------------------------------
|
||||
// update
|
||||
// ----------------------------------
|
||||
|
||||
const rawData = this.getNodeParameter('rawData', 0) as boolean;
|
||||
|
||||
const items = this.getInputData();
|
||||
|
||||
if (rawData === true) {
|
||||
const dataProperty = this.getNodeParameter('dataProperty', 0) as string;
|
||||
|
||||
const updateData: ISheetUpdateData[] = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
updateData.push({
|
||||
range,
|
||||
values: items[i].json[dataProperty] as string[][],
|
||||
});
|
||||
}
|
||||
|
||||
const data = await sheet.batchUpdate(updateData, valueInputMode);
|
||||
} else {
|
||||
const keyName = this.getNodeParameter('key', 0) as string;
|
||||
const keyRow = parseInt(this.getNodeParameter('keyRow', 0) as string, 10);
|
||||
const dataStartRow = parseInt(this.getNodeParameter('dataStartRow', 0) as string, 10);
|
||||
|
||||
const setData: IDataObject[] = [];
|
||||
items.forEach((item) => {
|
||||
setData.push(item.json);
|
||||
});
|
||||
|
||||
const data = await sheet.updateSheetData(setData, keyName, range, keyRow, dataStartRow, valueInputMode, valueRenderMode);
|
||||
}
|
||||
// TODO: Should add this data somewhere
|
||||
// TODO: Should have something like add metadata which does not get passed through
|
||||
|
||||
|
||||
return this.prepareOutputData(items);
|
||||
}
|
||||
// TODO: Should add this data somewhere
|
||||
// TODO: Should have something like add metadata which does not get passed through
|
||||
|
||||
}
|
||||
|
||||
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 [];
|
||||
|
|
|
@ -355,6 +355,13 @@ export class HttpRequest implements INodeType {
|
|||
default: 10000,
|
||||
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;
|
||||
}
|
||||
|
||||
if (options.useQueryString === true) {
|
||||
requestOptions.useQuerystring = true;
|
||||
}
|
||||
|
||||
if (parametersAreJson === true) {
|
||||
// Parameters are defined as JSON
|
||||
let optionData: OptionData;
|
||||
|
@ -927,7 +938,7 @@ export class HttpRequest implements INodeType {
|
|||
newItem.binary![dataPropertyName] = await this.helpers.prepareBinaryData(response!, fileName);
|
||||
}
|
||||
|
||||
items[itemIndex] = newItem;
|
||||
returnItems.push(newItem);
|
||||
} else if (responseFormat === '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',
|
||||
value: 'largerEqual',
|
||||
},
|
||||
{
|
||||
name: 'Is Empty',
|
||||
value: 'isEmpty',
|
||||
},
|
||||
],
|
||||
default: 'smaller',
|
||||
description: 'Operation to decide where the the data should be mapped to.',
|
||||
|
@ -120,6 +124,13 @@ export class If implements INodeType {
|
|||
displayName: 'Value 2',
|
||||
name: 'value2',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
operation: [
|
||||
'isEmpty',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 0,
|
||||
description: 'The value to compare with the first one.',
|
||||
},
|
||||
|
@ -145,6 +156,10 @@ export class If implements INodeType {
|
|||
name: 'Contains',
|
||||
value: 'contains',
|
||||
},
|
||||
{
|
||||
name: 'Ends With',
|
||||
value: 'endsWith',
|
||||
},
|
||||
{
|
||||
name: 'Equal',
|
||||
value: 'equal',
|
||||
|
@ -161,6 +176,14 @@ export class If implements INodeType {
|
|||
name: 'Regex',
|
||||
value: 'regex',
|
||||
},
|
||||
{
|
||||
name: 'Starts With',
|
||||
value: 'startsWith',
|
||||
},
|
||||
{
|
||||
name: 'Is Empty',
|
||||
value: 'isEmpty',
|
||||
},
|
||||
],
|
||||
default: 'equal',
|
||||
description: 'Operation to decide where the the data should be mapped to.',
|
||||
|
@ -172,6 +195,7 @@ export class If implements INodeType {
|
|||
displayOptions: {
|
||||
hide: {
|
||||
operation: [
|
||||
'isEmpty',
|
||||
'regex',
|
||||
],
|
||||
},
|
||||
|
@ -236,12 +260,15 @@ export class If implements INodeType {
|
|||
} = {
|
||||
contains: (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,
|
||||
notEqual: (value1: NodeParameterValue, value2: NodeParameterValue) => value1 !== value2,
|
||||
larger: (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),
|
||||
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) => {
|
||||
const regexMatch = (value2 || '').toString().match(new RegExp('^/(.*?)/([gimy]*)$'));
|
||||
|
||||
|
|
|
@ -4,7 +4,9 @@ import {
|
|||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
@ -23,6 +25,11 @@ import {
|
|||
userOperations,
|
||||
} from './UserDescription';
|
||||
|
||||
import {
|
||||
userListFields,
|
||||
userListOperations,
|
||||
} from './UserListDescription';
|
||||
|
||||
import * as moment from 'moment-timezone';
|
||||
|
||||
export class Iterable implements INodeType {
|
||||
|
@ -60,6 +67,10 @@ export class Iterable implements INodeType {
|
|||
name: 'User',
|
||||
value: 'user',
|
||||
},
|
||||
{
|
||||
name: 'User List',
|
||||
value: 'userList',
|
||||
},
|
||||
],
|
||||
default: 'user',
|
||||
description: 'The resource to operate on.',
|
||||
|
@ -68,9 +79,28 @@ export class Iterable implements INodeType {
|
|||
...eventFields,
|
||||
...userOperations,
|
||||
...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[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
|
@ -181,7 +211,7 @@ export class Iterable implements INodeType {
|
|||
|
||||
if (by === 'email') {
|
||||
const email = this.getNodeParameter('email', i) as string;
|
||||
endpoint = `/users/${email}`;
|
||||
endpoint = `/users/${email}`;
|
||||
} else {
|
||||
const userId = this.getNodeParameter('userId', i) as string;
|
||||
endpoint = `/users/byUserId/${userId}`;
|
||||
|
@ -212,7 +242,7 @@ export class Iterable implements INodeType {
|
|||
|
||||
if (by === 'email') {
|
||||
const email = this.getNodeParameter('email', i) as string;
|
||||
endpoint = `/users/getByEmail`;
|
||||
endpoint = `/users/getByEmail`;
|
||||
qs.email = email;
|
||||
} else {
|
||||
const userId = this.getNodeParameter('userId', i) as string;
|
||||
|
@ -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)];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const userOperations = [
|
||||
{
|
||||
|
@ -38,9 +38,9 @@ export const userOperations = [
|
|||
|
||||
export const userFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:upsert */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:upsert */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Identifier',
|
||||
name: 'identifier',
|
||||
|
@ -167,9 +167,10 @@ export const userFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'By',
|
||||
name: 'by',
|
||||
|
@ -240,9 +241,10 @@ export const userFields = [
|
|||
default: '',
|
||||
description: 'Email for a particular user',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: '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 = [
|
||||
{
|
||||
|
|
|
@ -17,6 +17,11 @@ import {
|
|||
validateJSON,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
issueCommentFields,
|
||||
issueCommentOperations,
|
||||
} from './IssueCommentDescription';
|
||||
|
||||
import {
|
||||
issueFields,
|
||||
issueOperations,
|
||||
|
@ -96,12 +101,19 @@ export class Jira implements INodeType {
|
|||
value: 'issue',
|
||||
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',
|
||||
description: 'Resource to consume.',
|
||||
},
|
||||
...issueOperations,
|
||||
...issueFields,
|
||||
...issueCommentOperations,
|
||||
...issueCommentFields,
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -642,6 +654,125 @@ export class Jira implements INodeType {
|
|||
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)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else {
|
||||
|
|
|
@ -63,7 +63,7 @@ export async function mauticApiRequest(this: IHookFunctions | IExecuteFunctions
|
|||
|
||||
options.uri = `${credentials.url}${options.uri}`;
|
||||
//@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) {
|
||||
|
|
|
@ -43,8 +43,10 @@ export async function messageBirdApiRequest(
|
|||
uri: `https://rest.messagebird.com${resource}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
try {
|
||||
if (Object.keys(body).length === 0) {
|
||||
delete options.body;
|
||||
}
|
||||
return await this.helpers.request(options);
|
||||
} catch (error) {
|
||||
if (error.statusCode === 401) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
|
@ -44,6 +44,10 @@ export class MessageBird implements INodeType {
|
|||
name: 'SMS',
|
||||
value: 'sms',
|
||||
},
|
||||
{
|
||||
name: 'Balance',
|
||||
value: 'balance',
|
||||
},
|
||||
],
|
||||
default: 'sms',
|
||||
description: 'The resource to operate on.',
|
||||
|
@ -69,6 +73,27 @@ export class MessageBird implements INodeType {
|
|||
default: 'send',
|
||||
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
|
||||
|
@ -133,6 +158,16 @@ export class MessageBird implements INodeType {
|
|||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'send',
|
||||
],
|
||||
resource: [
|
||||
'sms',
|
||||
],
|
||||
},
|
||||
},
|
||||
placeholder: 'Add Fields',
|
||||
default: {},
|
||||
options: [
|
||||
|
@ -269,11 +304,12 @@ export class MessageBird implements INodeType {
|
|||
let resource: string;
|
||||
|
||||
// For POST
|
||||
let bodyRequest: IDataObject;
|
||||
let bodyRequest: IDataObject = {};
|
||||
// For Query string
|
||||
let qs: IDataObject;
|
||||
|
||||
let requestMethod;
|
||||
let requestPath;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
qs = {};
|
||||
|
@ -289,6 +325,7 @@ export class MessageBird implements INodeType {
|
|||
// ----------------------------------
|
||||
|
||||
requestMethod = 'POST';
|
||||
requestPath = '/messages';
|
||||
const originator = this.getNodeParameter('originator', 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;
|
||||
|
||||
bodyRequest.recipients = receivers.split(',').map(item => {
|
||||
|
||||
return parseInt(item, 10);
|
||||
});
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
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!`);
|
||||
}
|
||||
|
||||
const responseData = await messageBirdApiRequest.call(
|
||||
this,
|
||||
requestMethod,
|
||||
'/messages',
|
||||
requestPath,
|
||||
bodyRequest,
|
||||
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();
|
||||
|
||||
let feed: Parser.Output;
|
||||
let feed: Parser.Output<IDataObject>;
|
||||
try {
|
||||
feed = await parser.parseURL(url);
|
||||
} catch (e) {
|
||||
|
|
|
@ -105,11 +105,11 @@ export class S3 implements INodeType {
|
|||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const qs: IDataObject = {};
|
||||
const headers: IDataObject = {};
|
||||
let responseData;
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const headers: IDataObject = {};
|
||||
if (resource === 'bucket') {
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html
|
||||
if (operation === 'create') {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { INodeProperties } from 'n8n-workflow';
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const channelOperations = [
|
||||
{
|
||||
|
@ -63,6 +65,11 @@ export const channelOperations = [
|
|||
value: 'leave',
|
||||
description: 'Leaves a conversation.',
|
||||
},
|
||||
{
|
||||
name: 'Member',
|
||||
value: 'member',
|
||||
description: 'List members of a conversation.',
|
||||
},
|
||||
{
|
||||
name: 'Open',
|
||||
value: 'open',
|
||||
|
@ -101,9 +108,9 @@ export const channelOperations = [
|
|||
|
||||
export const channelFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:archive */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:archive */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Channel',
|
||||
name: 'channelId',
|
||||
|
@ -125,9 +132,10 @@ export const channelFields = [
|
|||
required: true,
|
||||
description: 'The name of the channel to archive.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:close */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:close */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Channel',
|
||||
name: 'channelId',
|
||||
|
@ -149,9 +157,10 @@ export const channelFields = [
|
|||
required: true,
|
||||
description: 'The name of the channel to close.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Channel',
|
||||
name: 'channelId',
|
||||
|
@ -197,9 +206,10 @@ export const channelFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:invite */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:invite */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Channel',
|
||||
name: 'channelId',
|
||||
|
@ -242,9 +252,10 @@ export const channelFields = [
|
|||
required: true,
|
||||
description: 'The ID of the user to invite into channel.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Channel',
|
||||
name: 'channelId',
|
||||
|
@ -288,9 +299,10 @@ export const channelFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:kick */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:kick */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Channel',
|
||||
name: 'channelId',
|
||||
|
@ -332,9 +344,10 @@ export const channelFields = [
|
|||
},
|
||||
default: '',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:join */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:join */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Channel',
|
||||
name: 'channelId',
|
||||
|
@ -356,9 +369,10 @@ export const channelFields = [
|
|||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
|
@ -451,9 +465,10 @@ export const channelFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:history */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:history */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Channel',
|
||||
name: 'channelId',
|
||||
|
@ -557,9 +572,10 @@ export const channelFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:leave */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:leave */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Channel',
|
||||
name: 'channelId',
|
||||
|
@ -581,9 +597,89 @@ export const channelFields = [
|
|||
required: true,
|
||||
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',
|
||||
name: 'options',
|
||||
|
@ -627,9 +723,10 @@ export const channelFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:rename */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:rename */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Channel',
|
||||
name: 'channelId',
|
||||
|
@ -669,9 +766,10 @@ export const channelFields = [
|
|||
required: true,
|
||||
description: 'New name for conversation.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:replies */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:replies */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Channel',
|
||||
name: 'channelId',
|
||||
|
@ -793,9 +891,10 @@ export const channelFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:setPurpose */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:setPurpose */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Channel',
|
||||
name: 'channelId',
|
||||
|
@ -835,9 +934,10 @@ export const channelFields = [
|
|||
required: true,
|
||||
description: 'A new, specialer purpose',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:setTopic */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:setTopic */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Channel',
|
||||
name: 'channelId',
|
||||
|
@ -877,9 +977,10 @@ export const channelFields = [
|
|||
required: true,
|
||||
description: 'The new topic string. Does not support formatting or linkification.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:unarchive */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* channel:unarchive */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Channel',
|
||||
name: 'channelId',
|
||||
|
|
|
@ -36,9 +36,9 @@ export const fileOperations = [
|
|||
|
||||
export const fileFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:upload */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:upload */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Binary Data',
|
||||
name: 'binaryData',
|
||||
|
@ -159,9 +159,10 @@ export const fileFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* ----------------------------------------------------------------------- */
|
||||
/* file:getAll */
|
||||
/* ----------------------------------------------------------------------- */
|
||||
|
||||
/* ----------------------------------------------------------------------- */
|
||||
/* file:getAll */
|
||||
/* ----------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
|
@ -261,29 +262,29 @@ export const fileFields = [
|
|||
value: 'all',
|
||||
},
|
||||
{
|
||||
name: 'Spaces',
|
||||
value: 'spaces',
|
||||
},
|
||||
{
|
||||
name: 'Snippets',
|
||||
value: 'snippets',
|
||||
name: 'Google Docs',
|
||||
value: 'gdocs',
|
||||
},
|
||||
{
|
||||
name: 'Images',
|
||||
value: 'images',
|
||||
},
|
||||
{
|
||||
name: 'Google Docs',
|
||||
value: 'gdocs',
|
||||
name: 'Snippets',
|
||||
value: 'snippets',
|
||||
},
|
||||
{
|
||||
name: 'Zips',
|
||||
value: 'zips',
|
||||
name: 'Spaces',
|
||||
value: 'spaces',
|
||||
},
|
||||
{
|
||||
name: 'pdfs',
|
||||
value: 'pdfs',
|
||||
},
|
||||
{
|
||||
name: 'Zips',
|
||||
value: 'zips',
|
||||
},
|
||||
],
|
||||
default: ['all'],
|
||||
description: 'Filter files by type',
|
||||
|
@ -300,9 +301,10 @@ export const fileFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* ----------------------------------------------------------------------- */
|
||||
/* file:get */
|
||||
/* ----------------------------------------------------------------------- */
|
||||
|
||||
/* ----------------------------------------------------------------------- */
|
||||
/* file:get */
|
||||
/* ----------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'File ID',
|
||||
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[] = [];
|
||||
let responseData;
|
||||
query.page = 1;
|
||||
|
@ -88,13 +88,13 @@ export async function slackApiRequestAllItems(this: IExecuteFunctions | ILoadOpt
|
|||
returnData.push.apply(returnData, responseData[propertyName]);
|
||||
} while (
|
||||
(responseData.response_metadata !== undefined &&
|
||||
responseData.response_metadata.mext_cursor !== undefined &&
|
||||
responseData.response_metadata.next_cursor !== '' &&
|
||||
responseData.response_metadata.next_cursor !== null) ||
|
||||
responseData.response_metadata.mext_cursor !== undefined &&
|
||||
responseData.response_metadata.next_cursor !== '' &&
|
||||
responseData.response_metadata.next_cursor !== null) ||
|
||||
(responseData.paging !== undefined &&
|
||||
responseData.paging.pages !== undefined &&
|
||||
responseData.paging.page !== undefined &&
|
||||
responseData.paging.page < responseData.paging.pages
|
||||
responseData.paging.pages !== undefined &&
|
||||
responseData.paging.page !== undefined &&
|
||||
responseData.paging.page < responseData.paging.pages
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -31,9 +31,9 @@ export const messageOperations = [
|
|||
|
||||
export const messageFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* message:post */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* message:post */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Channel',
|
||||
name: 'channel',
|
||||
|
@ -382,6 +382,13 @@ export const messageFields = [
|
|||
default: '',
|
||||
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',
|
||||
name: 'thread_ts',
|
||||
|
@ -389,20 +396,6 @@ export const messageFields = [
|
|||
default: '',
|
||||
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',
|
||||
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.',
|
||||
},
|
||||
{
|
||||
displayName: 'Link Names',
|
||||
name: 'link_names',
|
||||
displayName: 'Unfurl Links',
|
||||
name: 'unfurl_links',
|
||||
type: 'boolean',
|
||||
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',
|
||||
name: 'channelId',
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue