mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-10 12:35:46 +03:00
🔀 Merge branch 'master' into oauth-support
This commit is contained in:
commit
147c50485c
@ -6,3 +6,10 @@ indent_style = tab
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[package.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
@ -11,7 +11,9 @@ Great that you are here and you want to contribute to n8n
|
||||
- [Development Cycle](#development-cycle)
|
||||
- [Create Custom Nodes](#create-custom-nodes)
|
||||
- [Create a new node to contribute to n8n](#create-a-new-node-to-contribute-to-n8n)
|
||||
- [Checklist before submitting a new node](#checklist-before-submitting-a-new-node)
|
||||
- [Extend Documentation](#extend-documentation)
|
||||
- [Contributor License Agreement](#contributor-license-agreement)
|
||||
|
||||
|
||||
## Code of Conduct
|
||||
@ -191,6 +193,24 @@ When developing n8n must get restarted and the browser reloaded every time param
|
||||
If only the code of the node changes (the execute method) than it is not needed as each workflow automatically starts a new process and so will always load the latest code.
|
||||
|
||||
|
||||
## Checklist before submitting a new node
|
||||
|
||||
If you'd like to submit a new node, please go through the following checklist. This will help us be quicker to review and merge your PR.
|
||||
|
||||
- [ ] Make failing requests to the API to ensure that the errors get displayed correctly (like malformed requests or requests with invalid credentials)
|
||||
- [ ] Ensure that the default values do not change and that the parameters do not get renamed, as it would break the existing workflows of the users
|
||||
- [ ] Ensure that all the top-level parameters use camelCase
|
||||
- [ ] Ensure that all the options are ordered alphabetically, unless a different order is needed for a specific reason
|
||||
- [ ] Ensure that the parameters have the correct type
|
||||
- [ ] Make sure that the file-name and the Class name are identical (case sensitive). The name under "description" in the node-code should also be identical (except that it starts with a lower-case letter and that it will never have a space)
|
||||
- [ ] Names of Trigger-Nodes always have to end with "Trigger"
|
||||
- [ ] Add credentials and nodes to the `package.json` file in alphanumerical order
|
||||
- [ ] Use tabs in all the files except in the `package.json` file, where 4-spaces have to get used
|
||||
- [ ] To make it as simple as possible for the users, check other similar nodes to ensure that they all behave similarly
|
||||
- [ ] Try to add as few parameters as possible on the main level to ensure that the node doesn't appear overwhelming. It should only contain the required parameters. All the other ones should be hidden on lower levels as "Additional Parameters" or "Options"
|
||||
- [ ] Create only one node per service which can do everything via "Resource" and "Options" and not a separate one for each possible operation.
|
||||
|
||||
|
||||
## Extend Documentation
|
||||
|
||||
All the files which get used in the n8n documentation on [https://docs.n8n.io](https://docs.n8n.io)
|
||||
|
@ -21,7 +21,7 @@ Software: n8n
|
||||
|
||||
License: Apache 2.0
|
||||
|
||||
Licensor: Jan Oberhauser
|
||||
Licensor: n8n GmbH
|
||||
|
||||
|
||||
---------------------------------------------------------------------
|
||||
|
@ -1,10 +1,10 @@
|
||||
# n8n Documentation
|
||||
|
||||
This is the documentation of n8n a free and open [fair-code](http://faircode.io) licensed node-based Workflow Automation Tool.
|
||||
This is the documentation of n8n, a free and open [fair-code](http://faircode.io) licensed node-based Workflow Automation Tool.
|
||||
|
||||
It covers everything from setup, usage to development. It is still work in progress and all contributions are welcome.
|
||||
It covers everything from setup to usage and development. It is still a work in progress and all contributions are welcome.
|
||||
|
||||
|
||||
## What is n8n?
|
||||
|
||||
n8n (pronounced nodemation) helps you to interconnect each and every app with an API in the world with each other to share and manipulate its data without a single line of code. It is an easy to use, user-friendly and highly customizable service, which uses an intuitive user interface for you to design your unique workflows very fast. Hosted on your server and not based in the cloud it keeps your sensible data very secure in your own trusted database.
|
||||
n8n (pronounced nodemation) helps you to interconnect every app with an API in the world with each other to share and manipulate its data without a single line of code. It is an easy to use, user-friendly and highly customizable service, which uses an intuitive user interface for you to design your unique workflows very fast. Hosted on your server and not based in the cloud, it keeps your sensible data very secure in your own trusted database.
|
||||
|
@ -34,6 +34,7 @@
|
||||
|
||||
- [FAQ](faq.md)
|
||||
- [License](license.md)
|
||||
- [Troubleshooting](troubleshooting.md)
|
||||
|
||||
|
||||
- Links
|
||||
|
@ -35,7 +35,7 @@ export VUE_APP_URL_BASE_API="https://n8n.example.com/"
|
||||
## Execution Data Manual Runs
|
||||
|
||||
n8n creates a random encryption key automatically on the first launch and saves
|
||||
it in the `~/.n8n` folder. That key gets used to encrypt the credentials before
|
||||
it in the `~/.n8n` folder. That key is used to encrypt the credentials before
|
||||
they get saved to the database. It is also possible to overwrite that key and
|
||||
set it via an environment variable.
|
||||
|
||||
@ -60,8 +60,8 @@ settings in the Editor UI.
|
||||
|
||||
## Execution Data Error/Success
|
||||
|
||||
When a workflow gets executed it will save the result in the database. That is
|
||||
the case for executions that did succeed and for the ones that failed. That
|
||||
When a workflow gets executed, it will save the result in the database. That's
|
||||
the case for executions that succeeded and for the ones that failed. The
|
||||
default behavior can be changed like this:
|
||||
|
||||
```bash
|
||||
@ -71,7 +71,7 @@ export EXECUTIONS_DATA_SAVE_ON_SUCCESS=none
|
||||
|
||||
Possible values are:
|
||||
- **all**: Saves all data
|
||||
- **none**: Do not save anything (recommended if a workflow runs a very often and/or processes a lot of data, set up "Error Workflow" instead)
|
||||
- **none**: Does not save anything (recommended if a workflow runs very often and/or processes a lot of data, set up "Error Workflow" instead)
|
||||
|
||||
These settings can also be overwritten on a per workflow basis in the workflow
|
||||
settings in the Editor UI.
|
||||
@ -80,10 +80,10 @@ settings in the Editor UI.
|
||||
## Execute In Same Process
|
||||
|
||||
All workflows get executed in their own separate process. This ensures that all CPU cores
|
||||
get used and that they do not block each other on CPU intensive tasks. Additionally does
|
||||
the crash of one execution not take down the whole application. The disadvantage is, however,
|
||||
that it slows down the start-time considerably and uses much more memory. So in case, the
|
||||
workflows are not CPU intensive and they have to start very fast it is possible to run them
|
||||
get used and that they do not block each other on CPU intensive tasks. Additionally, this makes sure that
|
||||
the crash of one execution does not take down the whole application. The disadvantage is, however,
|
||||
that it slows down the start-time considerably and uses much more memory. So in case the
|
||||
workflows are not CPU intensive and they have to start very fast, it is possible to run them
|
||||
all directly in the main-process with this setting.
|
||||
|
||||
```bash
|
||||
@ -93,9 +93,9 @@ export EXECUTIONS_PROCESS=main
|
||||
|
||||
## Exclude Nodes
|
||||
|
||||
It is possible to not allow users to use nodes of a specific node type. If you, for example,
|
||||
do not want that people can write data to disk with the "n8n-nodes-base.writeBinaryFile"
|
||||
node and can not execute commands with the "n8n-nodes-base.executeCommand" node you can
|
||||
It is possible to not allow users to use nodes of a specific node type. For example, if you
|
||||
do not want that people can write data to the disk with the "n8n-nodes-base.writeBinaryFile"
|
||||
node and that they cannot execute commands with the "n8n-nodes-base.executeCommand" node, you can
|
||||
set the following:
|
||||
|
||||
```bash
|
||||
@ -106,8 +106,8 @@ export NODES_EXCLUDE="[\"n8n-nodes-base.executeCommand\",\"n8n-nodes-base.writeB
|
||||
## Custom Nodes Location
|
||||
|
||||
Every user can add custom nodes that get loaded by n8n on startup. The default
|
||||
location is in the subfolder `.n8n/custom` of the user which started n8n.
|
||||
Additional folders can be defined via an environment variable.
|
||||
location is in the subfolder `.n8n/custom` of the user who started n8n.
|
||||
Additional folders can be defined with an environment variable.
|
||||
|
||||
```bash
|
||||
export N8N_CUSTOM_EXTENSIONS="/home/jim/n8n/custom-nodes;/data/n8n/nodes"
|
||||
@ -116,11 +116,11 @@ export N8N_CUSTOM_EXTENSIONS="/home/jim/n8n/custom-nodes;/data/n8n/nodes"
|
||||
|
||||
## Use built-in and external modules in Function-Nodes
|
||||
|
||||
For security reasons, importing modules is restricted by default in Function-Nodes.
|
||||
For security reasons, importing modules is restricted by default in the Function-Nodes.
|
||||
It is, however, possible to lift that restriction for built-in and external modules by
|
||||
setting the following environment variables:
|
||||
`NODE_FUNCTION_ALLOW_BUILTIN`: For builtin modules
|
||||
`NODE_FUNCTION_ALLOW_EXTERNAL`: For external modules sourced from n8n/node_modules directory. External module support is disabled when env variable is not set.
|
||||
- `NODE_FUNCTION_ALLOW_BUILTIN`: For builtin modules
|
||||
- `NODE_FUNCTION_ALLOW_EXTERNAL`: For external modules sourced from n8n/node_modules directory. External module support is disabled when env variable is not set.
|
||||
|
||||
```bash
|
||||
# Allows usage of all builtin modules
|
||||
@ -152,10 +152,10 @@ export N8N_SSL_CERT=/data/certs/server.pem
|
||||
|
||||
## Timezone
|
||||
|
||||
The timezone is set by default to "America/New_York". It gets for example used by the
|
||||
Cron-Node to know at what time the workflow should be started at. To set a different
|
||||
default timezone simply set `GENERIC_TIMEZONE` to the appropriate value like for example
|
||||
for Berlin (Germany):
|
||||
The timezone is set by default to "America/New_York". For instance, it is used by the
|
||||
Cron node to know at what time the workflow should be started. To set a different
|
||||
default timezone simply set `GENERIC_TIMEZONE` to the appropriate value. For example,
|
||||
if you want to set the timezone to Berlin (Germany):
|
||||
|
||||
```bash
|
||||
export GENERIC_TIMEZONE="Europe/Berlin"
|
||||
@ -168,8 +168,8 @@ You can find the name of your timezone here:
|
||||
## User Folder
|
||||
|
||||
User-specific data like the encryption key, SQLite database file, and
|
||||
the ID of the tunnel (if used) get by default saved in the subfolder
|
||||
`.n8n` of the user which started n8n. It is possible to overwrite the
|
||||
the ID of the tunnel (if used) gets saved by default in the subfolder
|
||||
`.n8n` of the user who started n8n. It is possible to overwrite the
|
||||
user-folder via an environment variable.
|
||||
|
||||
```bash
|
||||
@ -180,12 +180,12 @@ export N8N_USER_FOLDER="/home/jim/n8n"
|
||||
## Webhook URL
|
||||
|
||||
The webhook URL will normally be created automatically by combining
|
||||
`N8N_PROTOCOL`, `N8N_HOST` and `N8N_PORT`. If n8n runs, however, behind a
|
||||
reverse proxy that would not work. Because n8n does for example run internally
|
||||
`N8N_PROTOCOL`, `N8N_HOST` and `N8N_PORT`. However, if n8n runs behind a
|
||||
reverse proxy that would not work. That's because n8n runs internally
|
||||
on port 5678 but is exposed to the web via the reverse proxy on port 443. In
|
||||
that case, it is important to set the webhook URL manually that it can be
|
||||
displayed correctly in the Editor UI and even more important that the correct
|
||||
webhook URLs get registred with external services.
|
||||
that case, it is important to set the webhook URL manually so that it can be
|
||||
displayed correctly in the Editor UI and even more important is that the correct
|
||||
webhook URLs get registred with the external services.
|
||||
|
||||
```bash
|
||||
export WEBHOOK_TUNNEL_URL="https://n8n.example.com/"
|
||||
@ -194,15 +194,15 @@ export WEBHOOK_TUNNEL_URL="https://n8n.example.com/"
|
||||
|
||||
## Configuration via file
|
||||
|
||||
It is also possible to configure n8n via a configuration file.
|
||||
It is also possible to configure n8n using a configuration file.
|
||||
|
||||
It is not necessary to define all values. Only the ones which should be
|
||||
It is not necessary to define all values but only the ones that should be
|
||||
different from the defaults.
|
||||
|
||||
If needed also multiple files can be supplied to for example have generic
|
||||
If needed multiple files can also be supplied to. For example, have generic
|
||||
base settings and some specific ones depending on the environment.
|
||||
|
||||
The path to the JSON configuration file to use can be set via the environment
|
||||
The path to the JSON configuration file to use can be set using the environment
|
||||
variable `N8N_CONFIG_FILES`.
|
||||
|
||||
```bash
|
||||
|
@ -1,12 +1,12 @@
|
||||
# Create Node
|
||||
|
||||
It is quite easy to create own nodes in n8n. Mainly three things have to be defined:
|
||||
It is quite easy to create your own nodes in n8n. Mainly three things have to be defined:
|
||||
|
||||
1. Generic information like name, description, image/icon
|
||||
1. The parameters to display via which the user can interact with it
|
||||
1. The code to run once the node gets executed
|
||||
|
||||
To simplify the development process we created a very basic CLI which creates boilerplate code to get started and then also builds the node (as they are written in TypeScript) and copies it to the correct location.
|
||||
To simplify the development process, we created a very basic CLI which creates boilerplate code to get started, builds the node (as they are written in TypeScript), and copies it to the correct location.
|
||||
|
||||
|
||||
## Create the first basic node
|
||||
@ -14,13 +14,12 @@ To simplify the development process we created a very basic CLI which creates bo
|
||||
1. Install the n8n-node-dev CLI: `npm install -g n8n-node-dev`
|
||||
1. Create and go into the newly created folder in which you want to keep the code of the node
|
||||
1. Use CLI to create boilerplate node code: `n8n-node-dev new`
|
||||
1. Answer the questions (btw. Type “Execute” is the regular node you probably want to create).
|
||||
It will then at the end create the node in the current folder
|
||||
1. Answer the questions (the “Execute” node type is the regular node type that you probably want to create).
|
||||
It will then create the node in the current folder.
|
||||
1. Program… Add the functionality to the node
|
||||
1. Build the node and copy to correct location: `n8n-node-dev build`
|
||||
That command will build the JavaScript version of the node from the TypeScript code and copies it then
|
||||
the user folder where custom nodes get read from `~/.n8n/custom/`
|
||||
1. Restart n8n and refresh the window that the new node gets displayed
|
||||
That command will build the JavaScript version of the node from the TypeScript code and copy it to the user folder where custom nodes get read from `~/.n8n/custom/`
|
||||
1. Restart n8n and refresh the window so that the new node gets displayed
|
||||
|
||||
|
||||
## Create own custom n8n-nodes-module
|
||||
@ -43,14 +42,14 @@ can automatically find the nodes in the module:
|
||||
- The module has to be installed alongside n8n
|
||||
|
||||
An example starter module which contains one node and credentials and implements
|
||||
the above can be found here:
|
||||
the above can be found here:
|
||||
|
||||
[https://github.com/n8n-io/n8n-nodes-starter](https://github.com/n8n-io/n8n-nodes-starter)
|
||||
|
||||
|
||||
### Setup to use n8n-nodes-module
|
||||
|
||||
To use a custom `n8n-nodes-module` it simply has to be installed alongside n8n.
|
||||
To use a custom `n8n-nodes-module`, it simply has to be installed alongside n8n.
|
||||
For example like this:
|
||||
|
||||
```bash
|
||||
@ -71,7 +70,7 @@ n8n
|
||||
|
||||
### Development/Testing of custom n8n-nodes-module
|
||||
|
||||
Works actually the same as for any other npm module.
|
||||
This works in the same way as for any other npm module.
|
||||
|
||||
Execute in the folder which contains the code of the custom `n8n-nodes-module`
|
||||
which should be loaded with n8n:
|
||||
@ -99,25 +98,25 @@ n8n
|
||||
## Node Development Guidelines
|
||||
|
||||
|
||||
That everything works correctly, similarly and no unnecessary code gets added it is important to follow the following guidelines:
|
||||
Please make sure that everything works correctly and that no unnecessary code gets added. It is important to follow the following guidelines:
|
||||
|
||||
|
||||
### Do not change incoming data
|
||||
|
||||
Never change the incoming data a node receives (which can be queried with `this.getInputData()`) as it gets shared by all nodes. If data has to get added, changed or deleted it has to be cloned and the new data returned. If that is not done will sibling nodes, which execute after the current one, operate on the altered data and would so process different data then they were supposed to.
|
||||
It is however not needed to always clone all the data. If a node for, example only, changes only the binary data but not the JSON one simply a new item can be created which reuses the reference to the JSON item.
|
||||
Never change the incoming data a node receives (which can be queried with `this.getInputData()`) as it gets shared by all nodes. If data has to get added, changed or deleted it has to be cloned and the new data returned. If that is not done, sibling nodes which execute after the current one will operate on the altered data and would process different data than they were supposed to.
|
||||
It is however not needed to always clone all the data. If a node for, example only, changes only the binary data but not the JSON data, a new item can be created which reuses the reference to the JSON item.
|
||||
|
||||
An example can be seen in the code of the [ReadBinartFile-Node](https://github.com/n8n-io/n8n/blob/master/packages/nodes-base/nodes/ReadBinaryFile.node.ts#L69-L83).
|
||||
An example can be seen in the code of the [ReadBinaryFile-Node](https://github.com/n8n-io/n8n/blob/master/packages/nodes-base/nodes/ReadBinaryFile.node.ts#L69-L83).
|
||||
|
||||
|
||||
### Write nodes in TypeScript
|
||||
|
||||
All code of n8n is written in TypeScript so should also be the nodes. That makes development easier and faster and avoids at least some bugs.
|
||||
All code of n8n is written in TypeScript and hence, the nodes should also be written in TypeScript. That makes development easier, faster, and avoids at least some bugs.
|
||||
|
||||
|
||||
### Do use the built in request library
|
||||
### Use the built in request library
|
||||
|
||||
Some third-party services have their own libraries on npm which make it a little bit easier to create an integration. It can be quite tempting to use them. The problem with those is that you add another dependency and not just one you add also all the dependencies of the dependencies. That means that more and more code gets added, has to get loaded, can introduce security vulnerabilities and bugs and so on. So please use the built-in module which can be used like this:
|
||||
Some third-party services have their own libraries on npm which make it easier to create an integration. It can be quite tempting to use them. The problem with those is that you add another dependency and not just one you add but also all the dependencies of the dependencies. This means more and more code gets added, has to get loaded, can introduce security vulnerabilities, bugs and so on. So please use the built-in module which can be used like this:
|
||||
|
||||
```typescript
|
||||
const response = await this.helpers.request(options);
|
||||
@ -128,19 +127,19 @@ That is simply using the npm package [`request-promise-native`](https://github.c
|
||||
|
||||
### Reuse parameter names
|
||||
|
||||
When a node can perform multiple operations for example edit and delete some kind of entity. It would need for both operations an entity-id. Do not call them "editId" and "deleteId" simply call them "id". n8n can handle multiple parameters with the same name without a problem as long as only one is visible. To make sure that is the case the "displayOptions" can be used. By keeping the same name, the value can be kept if a user switches the operation from for example "edit" to "delete".
|
||||
When a node can perform multiple operations like edit and delete some kind of entity, for both operations, it would need an entity-id. Do not call them "editId" and "deleteId" simply call them "id". n8n can handle multiple parameters with the same name without a problem as long as only one is visible. To make sure that is the case, the "displayOptions" can be used. By keeping the same name, the value can be kept if a user switches the operation from "edit" to "delete".
|
||||
|
||||
|
||||
### Create an "Options" parameter
|
||||
|
||||
Some nodes may need a lot of options. Add only the very important ones to the top level and create for all the other ones an "Options" parameter where they can be added if needed. So the interface stays clean and does not unnecessarily confuse people. A good example of that would be the "XML Node".
|
||||
Some nodes may need a lot of options. Add only the very important ones to the top level and for all others, create an "Options" parameter where they can be added if needed. This ensures that the interface stays clean and does not unnecessarily confuse people. A good example of that would be the XML node.
|
||||
|
||||
|
||||
### Follow exiting parameter naming guideline
|
||||
|
||||
Ok, there is not much of a guideline yet but if your node can do multiple things call the parameter which sets the behavior either "mode" (like "Merge" and "XML" node) or "operation" like the most other ones. If this operations can be done on different resources (like "User" or "Order) create a "resource" parameter (like "Pipedrive" and "Trello" node)
|
||||
There is not much of a guideline yet but if your node can do multiple things, call the parameter which sets the behavior either "mode" (like "Merge" and "XML" node) or "operation" like the most other ones. If these operations can be done on different resources (like "User" or "Order) create a "resource" parameter (like "Pipedrive" and "Trello" node)
|
||||
|
||||
|
||||
### Node Icons
|
||||
|
||||
Check existing node icons as a reference when you create own ones. The resolution of an icon should be 60x60px and saved as png.
|
||||
Check existing node icons as a reference when you create own ones. The resolution of an icon should be 60x60px and saved as PNG.
|
||||
|
@ -1,15 +1,15 @@
|
||||
# Data Structure
|
||||
|
||||
For "basic usage" it is not necessarily needed to understand how the data is structured
|
||||
which gets passed from one node to another. It becomes however important if you want to:
|
||||
For "basic usage" it is not necessarily needed to understand how the data that
|
||||
gets passed from one node to another is structured. However, it becomes important if you want to:
|
||||
|
||||
- create an own node
|
||||
- create your own node
|
||||
- write custom expressions
|
||||
- use the Function or Function Item Node
|
||||
- you want to get the most out of n8n in general
|
||||
- use the Function or Function Item node
|
||||
- you want to get the most out of n8n
|
||||
|
||||
|
||||
In n8n all data passed between nodes is an array of objects. It has the structure below:
|
||||
In n8n, all the data that is passed between nodes is an array of objects. It has the following structure:
|
||||
|
||||
```json
|
||||
[
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Database
|
||||
|
||||
By default, n8n uses SQLite to save credentials, past executions, and workflows.
|
||||
n8n however also supports MongoDB and PostgresDB.
|
||||
By default, n8n uses SQLite to save credentials, past executions, and workflows. However,
|
||||
n8n also supports MongoDB and PostgresDB.
|
||||
|
||||
|
||||
## Shared Settings
|
||||
@ -13,12 +13,12 @@ The following environment variables get used by all databases:
|
||||
|
||||
## MongoDB
|
||||
|
||||
!> **WARNING**: Use Postgres if possible! Mongo has problems with saving large
|
||||
amounts of data in a document and causes also other problems. So support will
|
||||
!> **WARNING**: Use PostgresDB, if possible! MongoDB has problems saving large
|
||||
amounts of data in a document, among other issues. So, support
|
||||
may be dropped in the future.
|
||||
|
||||
To use MongoDB as database you can provide the following environment variables like
|
||||
in the example bellow:
|
||||
To use MongoDB as the database, you can provide the following environment variables like
|
||||
in the example below:
|
||||
- `DB_TYPE=mongodb`
|
||||
- `DB_MONGODB_CONNECTION_URL=<CONNECTION_URL>`
|
||||
|
||||
@ -38,7 +38,7 @@ n8n start
|
||||
|
||||
## PostgresDB
|
||||
|
||||
To use PostgresDB as database you can provide the following environment variables
|
||||
To use PostgresDB as the database, you can provide the following environment variables
|
||||
- `DB_TYPE=postgresdb`
|
||||
- `DB_POSTGRESDB_DATABASE` (default: 'n8n')
|
||||
- `DB_POSTGRESDB_HOST` (default: 'localhost')
|
||||
@ -62,7 +62,7 @@ n8n start
|
||||
|
||||
## MySQL / MariaDB
|
||||
|
||||
The compatibility with MySQL/MariaDB was tested, even so, it is advisable to observe the operation of the application with this DB, as it is a new option, recently added. If you spot any problems, feel free to submit a PR.
|
||||
The compatibility with MySQL/MariaDB has been tested. Even then, it is advisable to observe the operation of the application with this database as this option has been recently added. If you spot any problems, feel free to submit a burg report or a pull request.
|
||||
|
||||
To use MySQL as database you can provide the following environment variables:
|
||||
- `DB_TYPE=mysqldb` or `DB_TYPE=mariadb`
|
||||
@ -86,7 +86,7 @@ n8n start
|
||||
|
||||
## SQLite
|
||||
|
||||
The default database which gets used if no other one is defined.
|
||||
This is the default database that gets used if nothing is defined.
|
||||
|
||||
The database file is located at:
|
||||
`~/.n8n/database.sqlite`
|
||||
@ -94,17 +94,16 @@ The database file is located at:
|
||||
|
||||
## Other Databases
|
||||
|
||||
Currently, only the above databases are supported. n8n uses internally
|
||||
[TypeORM](https://typeorm.io) so adding support for the following databases
|
||||
Currently, only the databases mentioned above are supported. n8n internally uses
|
||||
[TypeORM](https://typeorm.io), so adding support for the following databases
|
||||
should not be too much work:
|
||||
|
||||
- CockroachDB
|
||||
- MariaDB
|
||||
- Microsoft SQL
|
||||
- Oracle
|
||||
|
||||
If you can not use any of the currently supported databases for some reason and
|
||||
you can program, you can simply add support by yourself. If not you can request
|
||||
that support should be added here:
|
||||
If you cannot use any of the currently supported databases for some reason and
|
||||
you can code, we'd appreciate your support in the form of a pull request. If not, you can request
|
||||
for support here:
|
||||
|
||||
[https://community.n8n.io/c/feature-requests/cli](https://community.n8n.io/c/feature-requests/cli)
|
||||
|
@ -3,5 +3,5 @@
|
||||
Detailed information about how to run n8n in Docker can be found in the README
|
||||
of the [Docker Image](https://github.com/n8n-io/n8n/blob/master/docker/images/n8n/README.md).
|
||||
|
||||
A basic step by step example setup of n8n with docker-compose and Lets Encrypt is available on the
|
||||
A basic step by step example setup of n8n with docker-compose and Let's Encrypt is available on the
|
||||
[Server Setup](server-setup.md) page.
|
||||
|
34
docs/faq.md
34
docs/faq.md
@ -7,53 +7,41 @@
|
||||
|
||||
You can request new integrations to be added to our forum. There is a special section for that where
|
||||
other users can also upvote it so that we know which integrations are important and should be
|
||||
created next. [Feature Request](https://community.n8n.io/c/feature-requests/nodes)
|
||||
created next. Request a new feature [here](https://community.n8n.io/c/feature-requests/nodes).
|
||||
|
||||
|
||||
### An integration exists already but a feature is missing. Can you add it?
|
||||
|
||||
Adding new functionality to an existing integration is normally not that complicated. So the chance is
|
||||
high that we can do that quite fast. Simply post your feature request in the forum and we will see
|
||||
what we can do. [Feature Request](https://community.n8n.io/c/feature-requests/nodes)
|
||||
high that we can do that quite fast. Post your feature request in the forum and we'll see
|
||||
what we can do. Request a new feature [here](https://community.n8n.io/c/feature-requests/nodes).
|
||||
|
||||
|
||||
### How can I create an integration myself?
|
||||
|
||||
Information about that can be found in the [CONTRIBUTING guide](https://github.com/n8n-io/n8n/blob/master/CONTRIBUTING.md)
|
||||
Information about that can be found in the [CONTRIBUTING guide](https://github.com/n8n-io/n8n/blob/master/CONTRIBUTING.md).
|
||||
|
||||
|
||||
## License
|
||||
|
||||
|
||||
### What license does n8n use?
|
||||
### Which license does n8n use?
|
||||
|
||||
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)
|
||||
|
||||
|
||||
### Is n8n open-source?
|
||||
|
||||
No, according to the definition of the [Open Source Initiative (OSI)](https://opensource.org/osd)
|
||||
is n8n not open-source. The reason is that [Commons Clause](https://commonsclause.com) which takes away some rights got attached to the Apache 2.0 license.
|
||||
The source code is however open and people and companies can use it totally free.
|
||||
What is however not allowed is to make money directly with n8n. So you can for example not charge
|
||||
other people to host or support n8n.
|
||||
No. The [Commons Clause](https://commonsclause.com) that is attached to the Apache 2.0 license takes away some rights. Hence, according to the definition of the [Open Source Initiative (OSI)](https://opensource.org/osd), n8n is not open-source. Nonetheless, the source code is open and everyone (individuals and companies) can use it for free. However, it is not allowed to make money directly with n8n.
|
||||
|
||||
The support part is mainly there because it was already in the license and I am not a lawyer. So to make it simpler do I hereby grant anybody the right to do consulting/support without prior permission as long as it is less than 30,000 USD ($30k) per year.
|
||||
|
||||
If you have bigger things planned simply write an email to [license@n8n.io](mailto:license@n8n.io).
|
||||
For instance, one cannot charge others to host or support n8n. However, to make things simpler, we grant everyone (individuals and companies) the right to offer consulting or support without prior permission as long as it is less than 30,000 USD ($30k) per annum.
|
||||
If your revenue from services based on n8n is greater than $30k per annum, we'd invite you to become a partner and apply for a license. If you have any questions about this, feel free to reach out to us at [license@n8n.io](mailto:license@n8n.io).
|
||||
|
||||
|
||||
### Why is n8n not open-source but [fair-code](http://faircode.io) licensed instead?
|
||||
|
||||
I love open-source and the idea that everybody can use and extend what I wrote for free. But as much
|
||||
as money can not buy you love, love can sadly literally not buy you anything. Especially does it not pay for rent, food, health insurance, and so on.
|
||||
And even though people can theoretically contribute to a project are the main drivers which push a project
|
||||
forward the most time very few and normally the creators or the company behind the project. So to make sure that the project improves and stays alive long term did the Commons Clause get attached. That makes sure that no other person/company can make money directly with n8n. Especially not in the way it is planned
|
||||
to finance further development. For 99.99% of the people it will not make any difference at all but at
|
||||
the same time does it protect the project.
|
||||
We love open-source and the idea that everybody can freely use and extend what we wrote. Our community is at the heart of everything that we do and we understand that people who contribute to a project are the main drivers that push a project forward. So to make sure that the project continues to evolve and stay alive in the longer run, we decided to attach the Commons Clause. This ensures that no other person or company can make money directly with n8n. Especially if it competes with how we plan to finance our further development. For the greater majority of the people, it will not make any difference at all. At the same time, it protects the project.
|
||||
|
||||
As n8n itself depends on and uses a lot of other open-source projects it is only fair and in our interest
|
||||
to also help them. So it is planed to contribute a certain percentage of revenue/profit every month to these
|
||||
projects. How much exactly is not decided yet.
|
||||
As n8n itself depends on and uses a lot of other open-source projects, it is only fair that we support them back. That is why we have planned to contribute a certain percentage of revenue/profit every month to these projects.
|
||||
|
||||
Started already with the first very small monthly contributions via [Open Collective](https://opencollective.com/n8n). It is not much yet as revenue is zero and profit in minus but it is at least a start. I hope to be able to ramp that up substantially over time.
|
||||
We have already started with the first monthly contributions via [Open Collective](https://opencollective.com/n8n). It is not much yet, but we hope to be able to ramp that up substantially over time.
|
||||
|
@ -8,19 +8,18 @@ A connection establishes a link between nodes to route data through the workflow
|
||||
|
||||
## Node
|
||||
|
||||
Example: Google Sheets Node. It can be used to retrieve or write data to a Google Sheet.
|
||||
A node is an entry point for retrieving data, a function to process data or an exit for sending data. The data process includes filtering, recomposing and changing data. There can be one or several nodes for your API, service or app. You can easily connect multiple nodes, which allows you to create simple and complex workflows with them intuitively.
|
||||
|
||||
A node is an entry for retrieving data, a function to process data or an exit for sending data. Data process includes filtering, recomposing and changing data. There is one or several nodes for your API, service or app. You can easily connect multiple nodes, which allows you to create simple and complex workflows with them very intuitively.
|
||||
For example, consider a Google Sheets node. It can be used to retrieve or write data to a Google Sheet.
|
||||
|
||||
|
||||
## Trigger
|
||||
## Trigger Node
|
||||
|
||||
Example: Trello Trigger Node. When a Trello Board gets updated, it will trigger a workflow to start using the data from the updated board.
|
||||
A trigger node is a node that starts a workflow and supplies the initial data. What triggers it, depends on the node. It could be the time, a webhook call or an event from an external service.
|
||||
|
||||
A trigger-node is a node which starts a workflow and supplies the initial data. What triggers it, depends on the node. It could be the time, a webhook call or an event from an external service.
|
||||
For example, consider a Trello trigger node. When a Trello Board gets updated, it will trigger a workflow to start using the data from the updated board.
|
||||
|
||||
|
||||
## Workflow
|
||||
|
||||
A workflow is a canvas, on which you can place and connect nodes. You define a trigger node(s), optionally function nodes which change the data in the flow or in external services. A workflow can be started manually
|
||||
or by trigger nodes. A workflow run ends when all active and connected nodes have processed their data.
|
||||
A workflow is a canvas on which you can place and connect nodes. A workflow can be started manually or by trigger nodes. A workflow run ends when all active and connected nodes have processed their data.
|
||||
|
@ -13,7 +13,7 @@ The following keyboard shortcuts can currently be used:
|
||||
- **Tab**: Open "Node Creator". Type to filter and navigate with arrow keys. To create press "enter"
|
||||
|
||||
|
||||
## With node/s selected
|
||||
## With node(s) selected
|
||||
|
||||
- **ArrowDown**: Select sibling node bellow the current one
|
||||
- **ArrowLeft**: Select node left of the current one
|
||||
|
@ -3,31 +3,31 @@
|
||||
|
||||
## Types
|
||||
|
||||
There are two main node types in n8n Trigger- and Regular-Nodes.
|
||||
There are two main node types in n8n: Trigger nodes and Regular nodes.
|
||||
|
||||
|
||||
### Trigger Nodes
|
||||
|
||||
The Trigger-Nodes start a workflow and supply the initial data. A workflow can contain multiple trigger nodes but with each execution, just one of them will execute as they do not have any input and they are the nodes from which the execution starts.
|
||||
The Trigger nodes start a workflow and supply the initial data. A workflow can contain multiple trigger nodes but with each execution, only one of them will execute. This is because the other trigger nodes would not have any input as they are the nodes from which the execution of the workflow starts.
|
||||
|
||||
|
||||
### Regular Nodes
|
||||
|
||||
These nodes do the actual “work”. They can add, remove and edit the data in the flow, request and send data to external APIs and can do everything possible with Node.js in general.
|
||||
These nodes do the actual work. They can add, remove, and edit the data in the flow as well as request and send data to external APIs. They can do everything possible with Node.js in general.
|
||||
|
||||
|
||||
## Credentials
|
||||
|
||||
External services need a way to identify and authenticate users. That data, which can range from API key over email/password combination to a very long multi-line private key, get saved in n8n as credentials.
|
||||
|
||||
To make sure that the data is secure, it gets saved to the database encrypted. As encryption key does a random personal encryption key gets used which gets automatically generated on the first start of n8n and then saved under `~/.n8n/config`.
|
||||
External services need a way to identify and authenticate users. This data can range from an API key over an email/password combination to a very long multi-line private key and can be saved in n8n as credentials.
|
||||
|
||||
Nodes in n8n can then request that credential information. As an additional layer of security credentials can only be accessed by node types which specifically have the right to do so.
|
||||
|
||||
To make sure that the data is secure, it gets saved to the database encrypted. A random personal encryption key is used which gets automatically generated on the first run of n8n and then saved under `~/.n8n/config`.
|
||||
|
||||
|
||||
## Expressions
|
||||
|
||||
With the help of expressions, it is possible to set node parameters dynamically by referencing other data. That can be data from the flow, nodes, the environment or self-generated data. They are normal text with placeholders (everything between {{...}}) that can execute JavaScript code which offers access to special variables to access data.
|
||||
With the help of expressions, it is possible to set node parameters dynamically by referencing other data. That can be data from the flow, nodes, the environment or self-generated data. Expressions are normal text with placeholders (everything between {{...}}) that can execute JavaScript code, which offers access to special variables to access data.
|
||||
|
||||
An expression could look like this:
|
||||
|
||||
@ -57,20 +57,20 @@ The following special variables are available:
|
||||
- **$runIndex**: The current run index (first time node gets executed it is 0, second time 1, ...)
|
||||
- **$workflow**: Returns workflow metadata like: active, id, name
|
||||
|
||||
Normally it is not needed to write the JavaScript variables manually as they can be simply selected with the help of the Expression Editor.
|
||||
Normally it is not needed to write the JavaScript variables manually as they can be selected with the help of the Expression Editor.
|
||||
|
||||
|
||||
## Parameters
|
||||
|
||||
Parameters can be set for most nodes in n8n. The values that get set define what exactly a node does.
|
||||
|
||||
Parameter values are static by default, and are always the same no matter what data the node processes. However, it is possible to set the values dynamically with the help of an Expression. Using Expressions, it is possible to make the parameter value dependent on other factors like the data of flow or parameters of other nodes.
|
||||
Parameter values are static by default and are always the same no matter what kind of data the node processes. However, it is possible to set the values dynamically with the help of an Expression. Using Expressions, it is possible to make the parameter value dependent on other factors like the data of flow or parameters of other nodes.
|
||||
|
||||
More information about it can be found under [Expressions](#expressions).
|
||||
|
||||
|
||||
## Pausing Node
|
||||
|
||||
Sometimes when creating and debugging a workflow it is helpful to not execute some specific nodes. To make that as simple as possible and not having to disconnect each node, it is possible to pause them. When a node gets paused the data simple passes through the node without being changed.
|
||||
Sometimes when creating and debugging a workflow, it is helpful to not execute specific nodes. To do that without disconnecting each node, you can pause them. When a node gets paused, the data passes through the node without being changed.
|
||||
|
||||
There are two ways to pause a node. Either pressing the pause button which gets displayed above the node when hovering over it. Or by selecting the node and pressing “d”.
|
||||
There are two ways to pause a node. You can either press the pause button which gets displayed above the node when hovering over it or select the node and press “d”.
|
||||
|
@ -1,22 +1,22 @@
|
||||
# Nodes
|
||||
|
||||
## Function and Function Item Node
|
||||
## Function and Function Item Nodes
|
||||
|
||||
These are the most powerful nodes in n8n. With these, almost everything can be done if you know how to
|
||||
write JavaScript code. Both nodes work very similarly. They simply give you access to the incoming data
|
||||
write JavaScript code. Both nodes work very similarly. They give you access to the incoming data
|
||||
and you can manipulate it.
|
||||
|
||||
|
||||
### Difference between both nodes
|
||||
|
||||
The difference is that the code of the Function-Node does get executed only once and it receives the
|
||||
full items (JSON and binary data) as an array and expects as return value again an array of items. The items
|
||||
returned can be totally different from the incoming ones. So is it not just possible to remove and edit
|
||||
existing items it is also possible to add or return totally new ones.
|
||||
The difference is that the code of the Function node gets executed only once. It receives the
|
||||
full items (JSON and binary data) as an array and expects an array of items as a return value. The items
|
||||
returned can be totally different from the incoming ones. So it is not only possible to remove and edit
|
||||
existing items, but also to add or return totally new ones.
|
||||
|
||||
The code of the Function Item-Node on the other does get executed once for every item. It receives
|
||||
as input one item at a time and also just the JSON data. As a return value, it again expects the JSON data
|
||||
of one single item. That makes it possible to very easily add, remove and edit JSON properties of items
|
||||
The code of the Function Item node on the other hand gets executed once for every item. It receives
|
||||
one item at a time as input and also just the JSON data. As a return value, it expects the JSON data
|
||||
of one single item. That makes it possible to add, remove and edit JSON properties of items
|
||||
but it is not possible to add new or remove existing items. Accessing and changing binary data is only
|
||||
possible via the methods `getBinaryData` and `setBinaryData`.
|
||||
|
||||
@ -28,9 +28,9 @@ return a promise which resolves accordingly.
|
||||
|
||||
#### Variable: items
|
||||
|
||||
It contains all the items the node received as input.
|
||||
It contains all the items that the node received as input.
|
||||
|
||||
Information about how the data is structured can be found on the page [Data Structure](data-structure.md)
|
||||
Information about how the data is structured can be found on the page [Data Structure](data-structure.md).
|
||||
|
||||
The data can be accessed and manipulated like this:
|
||||
|
||||
@ -64,18 +64,18 @@ return newItems;
|
||||
With `$item` it is possible to access the data of parent nodes. That can be the item data but also
|
||||
the parameters. It expects as input an index of the item the data should be returned for. This is
|
||||
needed because for each item the data returned can be different. This is probably obvious for the
|
||||
item data itself but maybe less for data like parameters. The reason why it is also needed there is
|
||||
item data itself but maybe less for data like parameters. The reason why it is also needed, is
|
||||
that they may contain an expression. Expressions get always executed of the context for an item.
|
||||
If that would not be the case, for example, the Email Send-Node not would be able to send multiple
|
||||
If that would not be the case, for example, the Email Send node not would be able to send multiple
|
||||
emails at once to different people. Instead, the same person would receive multiple emails.
|
||||
|
||||
The index is 0 based. So `$item(0)` will return the first item, `$item(1)` the second one, ...
|
||||
|
||||
By default will the item of the last run of the node be returned. So if the referenced node did run
|
||||
3x (its last runIndex is 2) and the current node runs the first time (its runIndex is 0) will the
|
||||
data of runIndex 2 of the referenced node be returned.
|
||||
By default the item of the last run of the node will be returned. So if the referenced node ran
|
||||
3x (its last runIndex is 2) and the current node runs the first time (its runIndex is 0) the
|
||||
data of runIndex 2 of the referenced node will be returned.
|
||||
|
||||
For more information about what data can be accessed via $node check [here](#variable-node).
|
||||
For more information about what data can be accessed via $node, check [here](#variable-node).
|
||||
|
||||
Example:
|
||||
|
||||
@ -96,9 +96,9 @@ const channel = $item(9).$node["Slack"].parameter["channel"];
|
||||
|
||||
#### Method: $items(nodeName?: string, outputIndex?: number, runIndex?: number)
|
||||
|
||||
Gives access to all the items of current or parent nodes. If no parameters get supplied
|
||||
Gives access to all the items of current or parent nodes. If no parameters get supplied,
|
||||
it returns all the items of the current node.
|
||||
If a node-name is given, it returns the items the node did output on it`s first output
|
||||
If a node-name is given, it returns the items the node output on its first output
|
||||
(index: 0, most nodes only have one output, exceptions are IF and Switch-Node) on
|
||||
its last run.
|
||||
|
||||
@ -184,13 +184,13 @@ return items;
|
||||
Gives access to the static workflow data.
|
||||
It is possible to save data directly with the workflow. This data should, however, be very small.
|
||||
A common use case is to for example to save a timestamp of the last item that got processed from
|
||||
an RSS-Feed or database. It will always return an object. Properties can then read, deleted or
|
||||
set on that object. When the workflow execution did succeed n8n will check automatically if data
|
||||
changed and will save it if necessary.
|
||||
an RSS-Feed or database. It will always return an object. Properties can then read, delete or
|
||||
set on that object. When the workflow execution succeeds, n8n will check automatically if the data
|
||||
has changed and will save it, if necessary.
|
||||
|
||||
There are two types of static data. The "global" and the "node" one. Global static data is the
|
||||
same in the whole workflow. And every node in the workflow can access it. The node static data
|
||||
, however, is different for every node and only the node which did set it can retrieve it again.
|
||||
, however, is different for every node and only the node which set it can retrieve it again.
|
||||
|
||||
Example:
|
||||
|
||||
@ -210,9 +210,9 @@ staticData.lastExecution = new Date().getTime();
|
||||
delete staticData.lastExecution;
|
||||
```
|
||||
|
||||
It is important to know that static data can not be read and written when testing via the UI.
|
||||
The data will there always be empty and changes will not be persisted. Only when a workflow
|
||||
is active and it gets called by a Trigger or Webhook will the static data be saved.
|
||||
It is important to know that the static data can not be read and written when testing via the UI.
|
||||
The data there will always be empty and the changes will not persist. Only when a workflow
|
||||
is active and it gets called by a Trigger or Webhook, the static data will be saved.
|
||||
|
||||
|
||||
|
||||
@ -244,4 +244,4 @@ Sets all the binary data (all keys) of the item which gets currently processed.
|
||||
|
||||
#### Method: getWorkflowStaticData(type)
|
||||
|
||||
As described above for Function-Node.
|
||||
As described above for Function node.
|
||||
|
@ -3,13 +3,13 @@
|
||||
|
||||
## Give n8n a spin
|
||||
|
||||
To spin up n8n to have a look you can run:
|
||||
To spin up n8n, you can run:
|
||||
|
||||
```bash
|
||||
npx n8n
|
||||
```
|
||||
|
||||
It will then download everything which is needed and start n8n.
|
||||
It will download everything that is needed to start n8n.
|
||||
|
||||
You can then access n8n by opening:
|
||||
[http://localhost:5678](http://localhost:5678)
|
||||
@ -17,7 +17,7 @@ You can then access n8n by opening:
|
||||
|
||||
## Start with docker
|
||||
|
||||
To just play around a little bit you can start n8n with docker.
|
||||
To play around with n8n, you can also start it using docker:
|
||||
|
||||
```bash
|
||||
docker run -it --rm \
|
||||
@ -26,7 +26,7 @@ docker run -it --rm \
|
||||
n8nio/n8n
|
||||
```
|
||||
|
||||
Be aware that all data will be lost once the docker container got removed. To
|
||||
Be aware that all the data will be lost once the docker container gets removed. To
|
||||
persist the data mount the `~/.n8n` folder:
|
||||
|
||||
```bash
|
||||
@ -37,5 +37,7 @@ docker run -it --rm \
|
||||
n8nio/n8n
|
||||
```
|
||||
|
||||
More information about the Docker setup can be found the README of the
|
||||
[Docker Image](https://github.com/n8n-io/n8n/blob/master/docker/images/n8n/README.md)
|
||||
More information about the Docker setup can be found in the README file of the
|
||||
[Docker Image](https://github.com/n8n-io/n8n/blob/master/docker/images/n8n/README.md).
|
||||
|
||||
In case you run into issues, check out the [troubleshooting](troubleshooting.md) page or ask for help in the community [forum](https://community.n8n.io/).
|
||||
|
@ -1,9 +1,9 @@
|
||||
# Security
|
||||
|
||||
By default, n8n can be accessed by everybody. This is OK if you have it only running
|
||||
locally but if you deploy it on a server which is accessible from the web you have
|
||||
to make sure that n8n is protected!
|
||||
Right now we have very basic protection via basic-auth in place. It can be activated
|
||||
By default, n8n can be accessed by everybody. This is okay if you only have it running
|
||||
locally but if you deploy it on a server which is accessible from the web, you have
|
||||
to make sure that n8n is protected.
|
||||
Right now we have very basic protection in place using basic-auth. It can be activated
|
||||
by setting the following environment variables:
|
||||
|
||||
```bash
|
||||
|
@ -1,9 +1,9 @@
|
||||
# Sensitive Data via File
|
||||
|
||||
To avoid passing sensitive information via environment variables "_FILE" may be
|
||||
To avoid passing sensitive information via environment variables, "_FILE" may be
|
||||
appended to some environment variables. It will then load the data from a file
|
||||
with the given name. That makes it possible to load data easily from
|
||||
Docker- and Kubernetes-Secrets.
|
||||
Docker and Kubernetes secrets.
|
||||
|
||||
The following environment variables support file input:
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
# Server Setup
|
||||
|
||||
!> ***Important***: Make sure that you secure your n8n instance like described under [Security](security.md)!
|
||||
!> ***Important***: Make sure that you secure your n8n instance as described under [Security](security.md).
|
||||
|
||||
|
||||
## Example setup with docker-compose
|
||||
|
||||
If you have already installed docker and docker-compose you can directly start with step 4.
|
||||
If you have already installed docker and docker-compose, then you can directly start with step 4.
|
||||
|
||||
|
||||
### 1. Install Docker
|
||||
@ -84,7 +84,7 @@ services:
|
||||
n8n:
|
||||
image: n8nio/n8n
|
||||
ports:
|
||||
- "5678:5678"
|
||||
- "127.0.0.1:5678:5678"
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.n8n.rule=Host(`${SUBDOMAIN}.${DOMAIN_NAME}`)
|
||||
@ -150,9 +150,9 @@ SSL_EMAIL=user@example.com
|
||||
### 7. Create data folder
|
||||
|
||||
Create the folder which is defined as `DATA_FOLDER`. In the example
|
||||
above it is `/root/n8n/`.
|
||||
above, it is `/root/n8n/`.
|
||||
|
||||
In that folder will the database file from SQLite be saved and also the encryption key.
|
||||
In that folder, the database file from SQLite as well as the encryption key will be saved.
|
||||
|
||||
The folder can be created like this:
|
||||
```
|
||||
@ -176,7 +176,7 @@ sudo docker-compose stop
|
||||
|
||||
### 9. Done
|
||||
|
||||
n8n will now be reachable via the above define subdomain + domain combination.
|
||||
n8n will now be reachable via the above defined subdomain + domain combination.
|
||||
The above example would result in: https://n8n.example.com
|
||||
|
||||
n8n will only be reachable via https not via http!
|
||||
n8n will only be reachable via https and not via http.
|
||||
|
@ -22,15 +22,14 @@ n8n start
|
||||
|
||||
## Start with tunnel
|
||||
|
||||
!> **WARNING**: This is only meant for local development and testing. Should not be used in production!
|
||||
!> **WARNING**: This is only meant for local development and testing. It should not be used in production!
|
||||
|
||||
To be able to use webhooks which all triggers of external services like Github
|
||||
rely on n8n has to be reachable from the web. To make that easy n8n has a
|
||||
special tunnel service (uses this code: [https://github.com/localtunnel/localtunnel](https://github.com/localtunnel/localtunnel)) which redirects requests from our servers to your local
|
||||
n8n instance.
|
||||
To be able to use webhooks for trigger nodes of external services like GitHub, n8n has to be reachable from the web. To make that easy, n8n has a special tunnel service, which redirects requests from our servers to your local n8n instance (uses this code: [https://github.com/localtunnel/localtunnel](https://github.com/localtunnel/localtunnel)).
|
||||
|
||||
To use it simply start n8n with `--tunnel`
|
||||
To use it, simply start n8n with `--tunnel`
|
||||
|
||||
```bash
|
||||
n8n start --tunnel
|
||||
```
|
||||
|
||||
In case you run into issues, check out the [troubleshooting](troubleshooting.md) page or ask for help in the community [forum](https://community.n8n.io/).
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Start Workflows via CLI
|
||||
|
||||
Workflows can not just be started by triggers, webhooks or manually via the
|
||||
Editor it is also possible to start them directly via the CLI.
|
||||
Workflows cannot be only started by triggers, webhooks or manually via the
|
||||
Editor. It is also possible to start them directly via the CLI.
|
||||
|
||||
Execute a saved workflow by its ID:
|
||||
|
||||
|
58
docs/troubleshooting.md
Normal file
58
docs/troubleshooting.md
Normal file
@ -0,0 +1,58 @@
|
||||
# Troubleshooting
|
||||
|
||||
## Windows
|
||||
|
||||
If you are experiencing issues running n8n with the typical flow of:
|
||||
|
||||
```powershell
|
||||
npx n8n
|
||||
```
|
||||
|
||||
### Requirements
|
||||
|
||||
Please ensure that you have the following requirements fulfilled:
|
||||
|
||||
- Install latest version of [NodeJS](https://nodejs.org/en/download/)
|
||||
- Install [Python 2.7](https://www.python.org/downloads/release/python-2717/) (It is okay to have multiple versions installed on the machine)
|
||||
- Windows SDK
|
||||
- C++ Desktop Development Tools
|
||||
- Windows Build Tools
|
||||
|
||||
#### Install build tools
|
||||
|
||||
If you haven't satisfied the above, follow this procedure through your PowerShell (run with administrative privileges).
|
||||
This command installs the build tools, windows SDK and the C++ development tools in one package.
|
||||
|
||||
```powershell
|
||||
npm install --global --production windows-build-tools
|
||||
```
|
||||
|
||||
#### Configure npm to use Python version 2.7
|
||||
|
||||
```powershell
|
||||
npm config set python python2.7
|
||||
```
|
||||
|
||||
#### Configure npm to use correct msvs version
|
||||
|
||||
```powershell
|
||||
npm config set msvs_version 2017 --global
|
||||
```
|
||||
|
||||
### Lesser known issues:
|
||||
|
||||
#### mmmagic npm package when using MSbuild tools with Visual Studio
|
||||
|
||||
While installing this package, `node-gyp` is run and it might fail to install it with an error appearing in the ballpark of:
|
||||
|
||||
```
|
||||
gyp ERR! stack Error: spawn C:\Program Files (x86)\Microsoft Visual Studio\2019\**Enterprise**\MSBuild\Current\Bin\MSBuild.exe ENOENT
|
||||
```
|
||||
|
||||
It is seeking the `MSBuild.exe` in a directory that does not exist. If you are using Visual Studio Community or vice versa, you can change the path of MSBuild with command:
|
||||
|
||||
```powershell
|
||||
npm config set msbuild_path "C:\Program Files (x86)\Microsoft Visual Studio\2019\**Community**\MSBuild\Current\Bin\MSBuild.exe"
|
||||
```
|
||||
|
||||
Attempt to install package again after running the command above.
|
@ -3,12 +3,12 @@
|
||||
|
||||
## Activate
|
||||
|
||||
Activating a workflow means that the Trigger & Webhook nodes get activated and can trigger a workflow to run. By default are all newly created workflows deactivated. That means that even if a Trigger-Node like the Cron-Node should start a workflow because a predefined time is reached it will not unless the workflow gets activated. It is only possible to activate a workflow which contains a Trigger or a Webhook node.
|
||||
Activating a workflow means that the Trigger and Webhook nodes get activated and can trigger a workflow to run. By default all the newly created workflows are deactivated. That means that even if a Trigger node like the Cron node should start a workflow because a predefined time is reached, it will not unless the workflow gets activated. It is only possible to activate a workflow which contains a Trigger or a Webhook node.
|
||||
|
||||
|
||||
## Data Flow
|
||||
|
||||
Nodes do not only process one "item" they process multiple ones. So if the Trello-Node is set to "Create-Card" and it has an expression set for "Name" to be set depending on "name"-property it will create a card for each item, always choosing the name-property-value of the current one.
|
||||
Nodes do not only process one "item", they process multiple ones. So if the Trello node is set to "Create-Card" and it has an expression set for "Name" to be set depending on "name" property, it will create a card for each item, always choosing the name-property-value of the current one.
|
||||
|
||||
This data would, for example, create two boards. One named "test1" the other one named "test2":
|
||||
|
||||
@ -26,7 +26,7 @@ This data would, for example, create two boards. One named "test1" the other one
|
||||
|
||||
## Error Workflows
|
||||
|
||||
For each workflow, an optional "Error Workflow" can be set. It gets executed in case the execution of the workflow fails. That makes it possible to for example inform the user via Email or Slack if something goes wrong. The same "Error Workflow" can be set on multiple workflows.
|
||||
For each workflow, an optional "Error Workflow" can be set. It gets executed in case the execution of the workflow fails. That makes it possible to, for instance, inform the user via Email or Slack if something goes wrong. The same "Error Workflow" can be set on multiple workflows.
|
||||
|
||||
The only difference between a regular workflow and an "Error Workflow" is that it contains an "Error Trigger" node. So it is important to make sure that this node gets created before setting a workflow as "Error Workflow".
|
||||
|
||||
@ -56,29 +56,29 @@ The "Error Trigger" node will trigger in case the execution fails and receives i
|
||||
```
|
||||
|
||||
All information is always present except:
|
||||
- **execution.id**: Only present when the execution gets saved in the Database
|
||||
- **execution.url**: Only present when the execution gets saved in the Database
|
||||
- **execution.retryOf**: Only present when the execution is a retry of a previously failed one
|
||||
- **execution.id**: Only present when the execution gets saved in the database
|
||||
- **execution.url**: Only present when the execution gets saved in the database
|
||||
- **execution.retryOf**: Only present when the execution is a retry of a previously failed execution
|
||||
|
||||
|
||||
### Setting Error Workflow
|
||||
|
||||
An "Error Workflow" can be set in the Workflow Settings which can be accessed by pressing the "Workflow" button in the menu on the menu on the left side. The last option is "Settings". In the then appearing window, the "Error Workflow" can be selected via the Dropdown "Error Workflow".
|
||||
An "Error Workflow" can be set in the Workflow Settings which can be accessed by pressing the "Workflow" button in the menu on the on the left side. The last option is "Settings". In the window that appears, the "Error Workflow" can be selected via the Dropdown "Error Workflow".
|
||||
|
||||
|
||||
## Share Workflows
|
||||
|
||||
All workflows are simple JSON and can so be shared very easily.
|
||||
All workflows are JSON and can be shared very easily.
|
||||
|
||||
There are multiple ways to download a workflow as JSON to then share it with other people via Email, Slack, Skype, Dropbox, …
|
||||
|
||||
1. Pressing "Download" under the Workflow button in the menu on the left. It then downloads the workflow as JSON file
|
||||
1. Selecting the nodes in the editor which should be exported and then copy them (Ctrl + c). The nodes get then saved as JSON in the clipboard and can be pasted wherever desired (Ctrl + v).
|
||||
1. Press the "Download" button under the Workflow menu in the sidebar on the left. It then downloads the workflow as a JSON file.
|
||||
1. Select the nodes in the editor which should be exported and then copy them (Ctrl + c). The nodes then get saved as JSON in the clipboard and can be pasted wherever desired (Ctrl + v).
|
||||
|
||||
Importing that JSON representation again into n8n is as easy and can also be done in different ways:
|
||||
|
||||
1. Pressing "Import from File" or "Import from URL" under the Workflow button in the menu on the left.
|
||||
1. Copying the JSON workflow to the clipboard (Ctrl + c) and then simply pasting it directly into the editor (Ctrl + v).
|
||||
1. Press "Import from File" or "Import from URL" under the Workflow menu in the sidebar on the left.
|
||||
1. Copy the JSON workflow to the clipboard (Ctrl + c) and then simply pasting it directly into the editor (Ctrl + v).
|
||||
|
||||
|
||||
## Workflow Settings
|
||||
@ -88,12 +88,12 @@ On each workflow, it is possible to set some custom settings and overwrite some
|
||||
|
||||
### Error Workflow
|
||||
|
||||
Workflow to run in case the execution of the current workflow fails. More information in section [Error Workflows](#error-workflows)
|
||||
Workflow to run in case the execution of the current workflow fails. More information in section [Error Workflows](#error-workflows).
|
||||
|
||||
|
||||
### Timezone
|
||||
|
||||
The timezone to use in the current workflow. If not set the global Timezone (by default "New York" gets used). This is for example important for the Cron Trigger node.
|
||||
The timezone to use in the current workflow. If not set, the global Timezone (by default "New York" gets used). For instance, this is important for the Cron Trigger node.
|
||||
|
||||
|
||||
### Save Data Error Execution
|
||||
|
@ -6,6 +6,7 @@
|
||||
"bootstrap": "lerna bootstrap --hoist --no-ci",
|
||||
"build": "lerna exec npm run build",
|
||||
"dev": "lerna exec npm run dev --parallel",
|
||||
"clean:dist": "lerna exec -- rimraf ./dist",
|
||||
"start": "run-script-os",
|
||||
"start:default": "cd packages/cli/bin && ./n8n",
|
||||
"start:windows": "cd packages/cli/bin && n8n",
|
||||
@ -14,6 +15,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"lerna": "^3.13.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"run-script-os": "^1.0.7"
|
||||
},
|
||||
"postcss": {}
|
||||
|
@ -21,7 +21,7 @@ Software: n8n
|
||||
|
||||
License: Apache 2.0
|
||||
|
||||
Licensor: Jan Oberhauser
|
||||
Licensor: n8n GmbH
|
||||
|
||||
|
||||
---------------------------------------------------------------------
|
||||
|
BIN
packages/cli/databases/sqlite/database.sqlite
Normal file
BIN
packages/cli/databases/sqlite/database.sqlite
Normal file
Binary file not shown.
108
packages/cli/migrations/ormconfig.ts
Normal file
108
packages/cli/migrations/ormconfig.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import {MongoDb, SQLite, MySQLDb, PostgresDb} from '../src/databases/index';
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
"name": "sqlite",
|
||||
"type": "sqlite",
|
||||
"logging": true,
|
||||
"entities": Object.values(SQLite),
|
||||
"database": "./packages/cli/database.sqlite",
|
||||
"migrations": [
|
||||
"./src/databases/sqlite/migrations/*.ts"
|
||||
],
|
||||
"subscribers": [
|
||||
"./src/databases/sqlite/subscribers/*.ts"
|
||||
],
|
||||
"cli": {
|
||||
"entitiesDir": "./src/databases/sqlite",
|
||||
"migrationsDir": "./src/databases/sqlite/migrations",
|
||||
"subscribersDir": "./src/databases/sqlite/subscribers"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mongodb",
|
||||
"type": "mongodb",
|
||||
"logging": false,
|
||||
"entities": Object.values(MongoDb),
|
||||
"url": "mongodb://root:example@localhost:27017/n8n",
|
||||
"authSource": 'admin',
|
||||
"migrations": [
|
||||
"./src/databases/mongodb/migrations/*.ts"
|
||||
],
|
||||
"subscribers": [
|
||||
"src/subscriber/**/*.ts"
|
||||
],
|
||||
"cli": {
|
||||
"entitiesDir": "./src/databases/mongodb",
|
||||
"migrationsDir": "./src/databases/mongodb/Migrations",
|
||||
"subscribersDir": "./src/databases/mongodb/Subscribers"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "postgres",
|
||||
"type": "postgres",
|
||||
"logging": false,
|
||||
"host": "localhost",
|
||||
"username": "postgres",
|
||||
"password": "docker",
|
||||
"port": 5432,
|
||||
"database": "postgres",
|
||||
"schema": "public",
|
||||
"entities": Object.values(PostgresDb),
|
||||
"migrations": [
|
||||
"./src/databases/postgresdb/migrations/*.ts"
|
||||
],
|
||||
"subscribers": [
|
||||
"src/subscriber/**/*.ts"
|
||||
],
|
||||
"cli": {
|
||||
"entitiesDir": "./src/databases/postgresdb",
|
||||
"migrationsDir": "./src/databases/postgresdb/migrations",
|
||||
"subscribersDir": "./src/databases/postgresdb/subscribers"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mysql",
|
||||
"type": "mysql",
|
||||
"database": "n8n",
|
||||
"username": "root",
|
||||
"password": "password",
|
||||
"host": "localhost",
|
||||
"port": "3308",
|
||||
"logging": false,
|
||||
"entities": Object.values(MySQLDb),
|
||||
"migrations": [
|
||||
"./src/databases/mysqldb/migrations/*.ts"
|
||||
],
|
||||
"subscribers": [
|
||||
"src/subscriber/**/*.ts"
|
||||
],
|
||||
"cli": {
|
||||
"entitiesDir": "./src/databases/mysqldb",
|
||||
"migrationsDir": "./src/databases/mysqldb/migrations",
|
||||
"subscribersDir": "./src/databases/mysqldb/Subscribers"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mariadb",
|
||||
"type": "mariadb",
|
||||
"database": "n8n",
|
||||
"username": "root",
|
||||
"password": "password",
|
||||
"host": "localhost",
|
||||
"port": "3308",
|
||||
"logging": false,
|
||||
"entities": Object.values(MySQLDb),
|
||||
"migrations": [
|
||||
"./src/databases/mysqldb/migrations/*.ts"
|
||||
],
|
||||
"subscribers": [
|
||||
"src/subscriber/**/*.ts"
|
||||
],
|
||||
"cli": {
|
||||
"entitiesDir": "./src/databases/mysqldb",
|
||||
"migrationsDir": "./src/databases/mysqldb/migrations",
|
||||
"subscribersDir": "./src/databases/mysqldb/Subscribers"
|
||||
}
|
||||
},
|
||||
];
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n",
|
||||
"version": "0.62.1",
|
||||
"version": "0.66.0",
|
||||
"description": "n8n Workflow Automation Tool",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
@ -28,7 +28,8 @@
|
||||
"start:windows": "cd bin && n8n",
|
||||
"test": "jest",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"watch": "tsc --watch"
|
||||
"watch": "tsc --watch",
|
||||
"typeorm": "ts-node ./node_modules/typeorm/cli.js"
|
||||
},
|
||||
"bin": {
|
||||
"n8n": "./bin/n8n"
|
||||
@ -70,8 +71,9 @@
|
||||
"p-cancelable": "^2.0.0",
|
||||
"run-script-os": "^1.0.7",
|
||||
"ts-jest": "^24.0.2",
|
||||
"tslint": "^5.17.0",
|
||||
"typescript": "~3.7.4"
|
||||
"tslint": "^6.1.2",
|
||||
"typescript": "~3.7.4",
|
||||
"ts-node": "^8.9.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oclif/command": "^1.5.18",
|
||||
@ -95,18 +97,18 @@
|
||||
"jwks-rsa": "^1.6.0",
|
||||
"localtunnel": "^2.0.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mongodb": "^3.2.3",
|
||||
"mongodb": "^3.5.5",
|
||||
"mysql2": "^2.0.1",
|
||||
"n8n-core": "~0.31.0",
|
||||
"n8n-editor-ui": "~0.42.0",
|
||||
"n8n-nodes-base": "~0.57.1",
|
||||
"n8n-workflow": "~0.28.0",
|
||||
"n8n-core": "~0.33.0",
|
||||
"n8n-editor-ui": "~0.44.0",
|
||||
"n8n-nodes-base": "~0.61.0",
|
||||
"n8n-workflow": "~0.30.0",
|
||||
"open": "^7.0.0",
|
||||
"pg": "^7.11.0",
|
||||
"request-promise-native": "^1.0.7",
|
||||
"sqlite3": "^4.0.6",
|
||||
"sqlite3": "^4.2.0",
|
||||
"sse-channel": "^3.1.1",
|
||||
"typeorm": "^0.2.16"
|
||||
"typeorm": "^0.2.24"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
|
0
packages/cli/packages/cli/database.sqlite
Normal file
0
packages/cli/packages/cli/database.sqlite
Normal file
@ -315,6 +315,7 @@ export class ActiveWorkflowRunner {
|
||||
return ((workflow: Workflow, node: INode) => {
|
||||
const returnFunctions = NodeExecuteFunctions.getExecuteTriggerFunctions(workflow, node, additionalData, mode);
|
||||
returnFunctions.emit = (data: INodeExecutionData[][]): void => {
|
||||
WorkflowHelpers.saveStaticData(workflow);
|
||||
this.runWorkflow(workflowData, node, data, additionalData, mode);
|
||||
};
|
||||
return returnFunctions;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
DatabaseType,
|
||||
GenericHelpers,
|
||||
IDatabaseCollections,
|
||||
DatabaseType,
|
||||
} from './';
|
||||
|
||||
import {
|
||||
@ -16,9 +16,9 @@ import {
|
||||
|
||||
import {
|
||||
MongoDb,
|
||||
MySQLDb,
|
||||
PostgresDb,
|
||||
SQLite,
|
||||
MySQLDb,
|
||||
} from './databases';
|
||||
|
||||
export let collections: IDatabaseCollections = {
|
||||
@ -27,16 +27,27 @@ export let collections: IDatabaseCollections = {
|
||||
Workflow: null,
|
||||
};
|
||||
|
||||
import {
|
||||
InitialMigration1587669153312
|
||||
} from './databases/postgresdb/migrations';
|
||||
|
||||
import {
|
||||
InitialMigration1588157391238
|
||||
} from './databases/mysqldb/migrations';
|
||||
|
||||
import {
|
||||
InitialMigration1588102412422
|
||||
} from './databases/sqlite/migrations';
|
||||
|
||||
import * as path from 'path';
|
||||
|
||||
export async function init(synchronize?: boolean): Promise<IDatabaseCollections> {
|
||||
export async function init(): Promise<IDatabaseCollections> {
|
||||
const dbType = await GenericHelpers.getConfigValue('database.type') as DatabaseType;
|
||||
const n8nFolder = UserSettings.getUserN8nFolderPath();
|
||||
|
||||
let entities;
|
||||
let connectionOptions: ConnectionOptions;
|
||||
|
||||
let dbNotExistError: string | undefined;
|
||||
switch (dbType) {
|
||||
case 'mongodb':
|
||||
entities = MongoDb;
|
||||
@ -49,7 +60,6 @@ export async function init(synchronize?: boolean): Promise<IDatabaseCollections>
|
||||
break;
|
||||
|
||||
case 'postgresdb':
|
||||
dbNotExistError = 'does not exist';
|
||||
entities = PostgresDb;
|
||||
connectionOptions = {
|
||||
type: 'postgres',
|
||||
@ -60,12 +70,13 @@ export async function init(synchronize?: boolean): Promise<IDatabaseCollections>
|
||||
port: await GenericHelpers.getConfigValue('database.postgresdb.port') as number,
|
||||
username: await GenericHelpers.getConfigValue('database.postgresdb.user') as string,
|
||||
schema: await GenericHelpers.getConfigValue('database.postgresdb.schema') as string,
|
||||
migrations: [InitialMigration1587669153312],
|
||||
migrationsRun: true
|
||||
};
|
||||
break;
|
||||
|
||||
case 'mariadb':
|
||||
case 'mysqldb':
|
||||
dbNotExistError = 'does not exist';
|
||||
entities = MySQLDb;
|
||||
connectionOptions = {
|
||||
type: dbType === 'mysqldb' ? 'mysql' : 'mariadb',
|
||||
@ -75,16 +86,19 @@ export async function init(synchronize?: boolean): Promise<IDatabaseCollections>
|
||||
password: await GenericHelpers.getConfigValue('database.mysqldb.password') as string,
|
||||
port: await GenericHelpers.getConfigValue('database.mysqldb.port') as number,
|
||||
username: await GenericHelpers.getConfigValue('database.mysqldb.user') as string,
|
||||
migrations: [InitialMigration1588157391238],
|
||||
migrationsRun: true
|
||||
};
|
||||
break;
|
||||
|
||||
case 'sqlite':
|
||||
dbNotExistError = 'no such table:';
|
||||
entities = SQLite;
|
||||
connectionOptions = {
|
||||
type: 'sqlite',
|
||||
database: path.join(n8nFolder, 'database.sqlite'),
|
||||
database: path.join(n8nFolder, 'database.sqlite'),
|
||||
entityPrefix: await GenericHelpers.getConfigValue('database.tablePrefix') as string,
|
||||
migrations: [InitialMigration1588102412422],
|
||||
migrationsRun: true,
|
||||
};
|
||||
break;
|
||||
|
||||
@ -94,38 +108,19 @@ export async function init(synchronize?: boolean): Promise<IDatabaseCollections>
|
||||
|
||||
Object.assign(connectionOptions, {
|
||||
entities: Object.values(entities),
|
||||
synchronize: synchronize === true || process.env['NODE_ENV'] !== 'production',
|
||||
logging: false
|
||||
synchronize: false,
|
||||
logging: false,
|
||||
});
|
||||
|
||||
const connection = await createConnection(connectionOptions);
|
||||
|
||||
// TODO: Fix that properly
|
||||
// @ts-ignore
|
||||
await connection.runMigrations({
|
||||
transaction: 'none',
|
||||
});
|
||||
|
||||
collections.Credentials = getRepository(entities.CredentialsEntity);
|
||||
// @ts-ignore
|
||||
collections.Execution = getRepository(entities.ExecutionEntity);
|
||||
// @ts-ignore
|
||||
collections.Workflow = getRepository(entities.WorkflowEntity);
|
||||
|
||||
// Make sure that database did already get initialized
|
||||
try {
|
||||
// Try a simple query, if it fails it is normally a sign that
|
||||
// database did not get initialized
|
||||
await collections.Workflow!.findOne({ id: 1 });
|
||||
} catch (error) {
|
||||
// If query errors and the problem is that the database does not exist
|
||||
// run the init again with "synchronize: true"
|
||||
if (dbNotExistError !== undefined && error.message.includes(dbNotExistError)) {
|
||||
// Disconnect before we try to connect again
|
||||
if (connection.isConnected) {
|
||||
await connection.close();
|
||||
}
|
||||
|
||||
return init(true);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return collections;
|
||||
}
|
||||
|
@ -517,11 +517,8 @@ class App {
|
||||
|
||||
const sessionId = GenericHelpers.getSessionId(req);
|
||||
|
||||
// Check if workflow is saved as webhooks can only be tested with saved workflows.
|
||||
// If that is the case check if any webhooks calls are present we have to wait for and
|
||||
// if that is the case wait till we receive it.
|
||||
if (WorkflowHelpers.isWorkflowIdValid(workflowData.id) === true && (runData === undefined || startNodes === undefined || startNodes.length === 0 || destinationNode === undefined)) {
|
||||
// Webhooks can only be tested with saved workflows
|
||||
// If webhooks nodes exist and are active we have to wait for till we receive a call
|
||||
if (runData === undefined || startNodes === undefined || startNodes.length === 0 || destinationNode === undefined) {
|
||||
const credentials = await WorkflowCredentials(workflowData.nodes);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials);
|
||||
const nodeTypes = NodeTypes();
|
||||
@ -563,7 +560,7 @@ class App {
|
||||
this.app.get('/rest/node-parameter-options', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<INodePropertyOptions[]> => {
|
||||
const nodeType = req.query.nodeType as string;
|
||||
let credentials: INodeCredentials | undefined = undefined;
|
||||
const currentNodeParameters = req.query.currentNodeParameters as INodeParameters[];
|
||||
const currentNodeParameters = JSON.parse('' + req.query.currentNodeParameters) as INodeParameters;
|
||||
if (req.query.credentials !== undefined) {
|
||||
credentials = JSON.parse(req.query.credentials as string);
|
||||
}
|
||||
@ -1248,7 +1245,6 @@ class App {
|
||||
return returnData;
|
||||
}));
|
||||
|
||||
|
||||
// Forces the execution to stop
|
||||
this.app.post('/rest/executions-current/:id/stop', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<IExecutionsStopData> => {
|
||||
const executionId = req.params.id;
|
||||
@ -1316,6 +1312,26 @@ class App {
|
||||
// Webhooks
|
||||
// ----------------------------------------
|
||||
|
||||
// HEAD webhook requests
|
||||
this.app.head(`/${this.endpointWebhook}/*`, async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook/" to get the registred part of the url
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(this.endpointWebhook.length + 2);
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await this.activeWorkflowRunner.executeWebhook('HEAD', requestUrl, req, res);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.noWebhookResponse === true) {
|
||||
// Nothing else to do as the response got already sent
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode);
|
||||
});
|
||||
|
||||
// GET webhook requests
|
||||
this.app.get(`/${this.endpointWebhook}/*`, async (req: express.Request, res: express.Response) => {
|
||||
@ -1338,7 +1354,6 @@ class App {
|
||||
ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode);
|
||||
});
|
||||
|
||||
|
||||
// POST webhook requests
|
||||
this.app.post(`/${this.endpointWebhook}/*`, async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook/" to get the registred part of the url
|
||||
@ -1360,6 +1375,26 @@ class App {
|
||||
ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode);
|
||||
});
|
||||
|
||||
// HEAD webhook requests (test for UI)
|
||||
this.app.head(`/${this.endpointWebhookTest}/*`, async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook-test/" to get the registred part of the url
|
||||
const requestUrl = (req as ICustomRequest).parsedUrl!.pathname!.slice(this.endpointWebhookTest.length + 2);
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await this.testWebhooks.callTestWebhook('HEAD', requestUrl, req, res);
|
||||
} catch (error) {
|
||||
ResponseHelper.sendErrorResponse(res, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.noWebhookResponse === true) {
|
||||
// Nothing else to do as the response got already sent
|
||||
return;
|
||||
}
|
||||
|
||||
ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode);
|
||||
});
|
||||
|
||||
// GET webhook requests (test for UI)
|
||||
this.app.get(`/${this.endpointWebhookTest}/*`, async (req: express.Request, res: express.Response) => {
|
||||
@ -1382,7 +1417,6 @@ class App {
|
||||
ResponseHelper.sendSuccessResponse(res, response.data, true, response.responseCode);
|
||||
});
|
||||
|
||||
|
||||
// POST webhook requests (test for UI)
|
||||
this.app.post(`/${this.endpointWebhookTest}/*`, async (req: express.Request, res: express.Response) => {
|
||||
// Cut away the "/webhook-test/" to get the registred part of the url
|
||||
|
@ -129,6 +129,10 @@ export class TestWebhooks {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (workflow.id === undefined) {
|
||||
throw new Error('Webhooks can only be added for saved workflows as an id is needed!');
|
||||
}
|
||||
|
||||
// Remove test-webhooks automatically if they do not get called (after 120 seconds)
|
||||
const timeout = setTimeout(() => {
|
||||
this.cancelTestWebhook(workflowData.id.toString());
|
||||
|
@ -149,6 +149,21 @@ export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflo
|
||||
};
|
||||
}
|
||||
|
||||
if (webhookData.webhookDescription['responseHeaders'] !== undefined) {
|
||||
const responseHeaders = workflow.getComplexParameterValue(workflowStartNode, webhookData.webhookDescription['responseHeaders'], undefined) as {
|
||||
entries?: Array<{
|
||||
name: string;
|
||||
value: string;
|
||||
}> | undefined;
|
||||
};
|
||||
|
||||
if (responseHeaders !== undefined && responseHeaders['entries'] !== undefined) {
|
||||
for (const item of responseHeaders['entries']) {
|
||||
res.setHeader(item['name'], item['value']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (webhookResultData.noWebhookResponse === true && didSendResponse === false) {
|
||||
// The response got already send
|
||||
responseCallback(null, {
|
||||
|
@ -388,10 +388,10 @@ export async function executeWorkflow(workflowInfo: IExecuteWorkflowInfo, additi
|
||||
*
|
||||
* @export
|
||||
* @param {IWorkflowCredentials} credentials
|
||||
* @param {INodeParameters[]} [currentNodeParameters=[]]
|
||||
* @param {INodeParameters} currentNodeParameters
|
||||
* @returns {Promise<IWorkflowExecuteAdditionalData>}
|
||||
*/
|
||||
export async function getBase(credentials: IWorkflowCredentials, currentNodeParameters: INodeParameters[] = []): Promise<IWorkflowExecuteAdditionalData> {
|
||||
export async function getBase(credentials: IWorkflowCredentials, currentNodeParameters?: INodeParameters): Promise<IWorkflowExecuteAdditionalData> {
|
||||
const urlBaseWebhook = WebhookHelpers.getWebhookBaseUrl();
|
||||
|
||||
const timezone = config.get('generic.timezone') as string;
|
||||
|
@ -145,6 +145,10 @@ export class WorkflowRunner {
|
||||
|
||||
this.activeExecutions.attachWorkflowExecution(executionId, workflowExecution);
|
||||
|
||||
workflowExecution.then((fullRunData) => {
|
||||
this.activeExecutions.remove(executionId, fullRunData);
|
||||
});
|
||||
|
||||
return executionId;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,11 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class InitialMigration1587563438936 implements MigrationInterface {
|
||||
|
||||
async up(queryRunner: QueryRunner): Promise<void> {
|
||||
}
|
||||
|
||||
async down(queryRunner: QueryRunner): Promise<void> {
|
||||
}
|
||||
|
||||
}
|
1
packages/cli/src/databases/mongodb/Migrations/index.ts
Normal file
1
packages/cli/src/databases/mongodb/Migrations/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './1587563438936-InitialMigration';
|
@ -0,0 +1,20 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class InitialMigration1588157391238 implements MigrationInterface {
|
||||
name = 'InitialMigration1588157391238';
|
||||
|
||||
async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('CREATE TABLE IF NOT EXISTS `credentials_entity` (`id` int NOT NULL AUTO_INCREMENT, `name` varchar(128) NOT NULL, `data` text NOT NULL, `type` varchar(32) NOT NULL, `nodesAccess` json NOT NULL, `createdAt` datetime NOT NULL, `updatedAt` datetime NOT NULL, INDEX `IDX_07fde106c0b471d8cc80a64fc8` (`type`), PRIMARY KEY (`id`)) ENGINE=InnoDB', undefined);
|
||||
await queryRunner.query('CREATE TABLE IF NOT EXISTS `execution_entity` (`id` int NOT NULL AUTO_INCREMENT, `data` text NOT NULL, `finished` tinyint NOT NULL, `mode` varchar(255) NOT NULL, `retryOf` varchar(255) NULL, `retrySuccessId` varchar(255) NULL, `startedAt` datetime NOT NULL, `stoppedAt` datetime NOT NULL, `workflowData` json NOT NULL, `workflowId` varchar(255) NULL, INDEX `IDX_c4d999a5e90784e8caccf5589d` (`workflowId`), PRIMARY KEY (`id`)) ENGINE=InnoDB', undefined);
|
||||
await queryRunner.query('CREATE TABLE IF NOT EXISTS`workflow_entity` (`id` int NOT NULL AUTO_INCREMENT, `name` varchar(128) NOT NULL, `active` tinyint NOT NULL, `nodes` json NOT NULL, `connections` json NOT NULL, `createdAt` datetime NOT NULL, `updatedAt` datetime NOT NULL, `settings` json NULL, `staticData` json NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB', undefined);
|
||||
}
|
||||
|
||||
async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP TABLE `workflow_entity`', undefined);
|
||||
await queryRunner.query('DROP INDEX `IDX_c4d999a5e90784e8caccf5589d` ON `execution_entity`', undefined);
|
||||
await queryRunner.query('DROP TABLE `execution_entity`', undefined);
|
||||
await queryRunner.query('DROP INDEX `IDX_07fde106c0b471d8cc80a64fc8` ON `credentials_entity`', undefined);
|
||||
await queryRunner.query('DROP TABLE `credentials_entity`', undefined);
|
||||
}
|
||||
|
||||
}
|
1
packages/cli/src/databases/mysqldb/migrations/index.ts
Normal file
1
packages/cli/src/databases/mysqldb/migrations/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './1588157391238-InitialMigration';
|
@ -41,4 +41,5 @@ export class CredentialsEntity implements ICredentialsDb {
|
||||
|
||||
@Column('timestamp')
|
||||
updatedAt: Date;
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class InitialMigration1587669153312 implements MigrationInterface {
|
||||
name = 'InitialMigration1587669153312';
|
||||
|
||||
async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS credentials_entity ("id" SERIAL NOT NULL, "name" character varying(128) NOT NULL, "data" text NOT NULL, "type" character varying(32) NOT NULL, "nodesAccess" json NOT NULL, "createdAt" TIMESTAMP NOT NULL, "updatedAt" TIMESTAMP NOT NULL, CONSTRAINT PK_814c3d3c36e8a27fa8edb761b0e PRIMARY KEY ("id"))`, undefined);
|
||||
await queryRunner.query(`CREATE INDEX IF NOT EXISTS IDX_07fde106c0b471d8cc80a64fc8 ON credentials_entity (type) `, undefined);
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS execution_entity ("id" SERIAL NOT NULL, "data" text NOT NULL, "finished" boolean NOT NULL, "mode" character varying NOT NULL, "retryOf" character varying, "retrySuccessId" character varying, "startedAt" TIMESTAMP NOT NULL, "stoppedAt" TIMESTAMP NOT NULL, "workflowData" json NOT NULL, "workflowId" character varying, CONSTRAINT PK_e3e63bbf986767844bbe1166d4e PRIMARY KEY ("id"))`, undefined);
|
||||
await queryRunner.query(`CREATE INDEX IF NOT EXISTS IDX_c4d999a5e90784e8caccf5589d ON execution_entity ("workflowId") `, undefined);
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS workflow_entity ("id" SERIAL NOT NULL, "name" character varying(128) NOT NULL, "active" boolean NOT NULL, "nodes" json NOT NULL, "connections" json NOT NULL, "createdAt" TIMESTAMP NOT NULL, "updatedAt" TIMESTAMP NOT NULL, "settings" json, "staticData" json, CONSTRAINT PK_eded7d72664448da7745d551207 PRIMARY KEY ("id"))`, undefined);
|
||||
}
|
||||
|
||||
async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE workflow_entity`, undefined);
|
||||
await queryRunner.query(`DROP INDEX IDX_c4d999a5e90784e8caccf5589d`, undefined);
|
||||
await queryRunner.query(`DROP TABLE execution_entity`, undefined);
|
||||
await queryRunner.query(`DROP INDEX IDX_07fde106c0b471d8cc80a64fc8`, undefined);
|
||||
await queryRunner.query(`DROP TABLE credentials_entity`, undefined);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from './1587669153312-InitialMigration';
|
@ -1,3 +1,4 @@
|
||||
export * from './CredentialsEntity';
|
||||
export * from './ExecutionEntity';
|
||||
export * from './WorkflowEntity';
|
||||
|
||||
|
@ -0,0 +1,22 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class InitialMigration1588102412422 implements MigrationInterface {
|
||||
name = 'InitialMigration1588102412422';
|
||||
|
||||
async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "credentials_entity" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(128) NOT NULL, "data" text NOT NULL, "type" varchar(32) NOT NULL, "nodesAccess" text NOT NULL, "createdAt" datetime NOT NULL, "updatedAt" datetime NOT NULL)`, undefined);
|
||||
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_07fde106c0b471d8cc80a64fc8" ON "credentials_entity" ("type") `, undefined);
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "execution_entity" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "data" text NOT NULL, "finished" boolean NOT NULL, "mode" varchar NOT NULL, "retryOf" varchar, "retrySuccessId" varchar, "startedAt" datetime NOT NULL, "stoppedAt" datetime NOT NULL, "workflowData" text NOT NULL, "workflowId" varchar)`, undefined);
|
||||
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_c4d999a5e90784e8caccf5589d" ON "execution_entity" ("workflowId") `, undefined);
|
||||
await queryRunner.query(`CREATE TABLE IF NOT EXISTS "workflow_entity" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(128) NOT NULL, "active" boolean NOT NULL, "nodes" text NOT NULL, "connections" text NOT NULL, "createdAt" datetime NOT NULL, "updatedAt" datetime NOT NULL, "settings" text, "staticData" text)`, undefined);
|
||||
}
|
||||
|
||||
async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE "workflow_entity"`, undefined);
|
||||
await queryRunner.query(`DROP INDEX "IDX_c4d999a5e90784e8caccf5589d"`, undefined);
|
||||
await queryRunner.query(`DROP TABLE "execution_entity"`, undefined);
|
||||
await queryRunner.query(`DROP INDEX "IDX_07fde106c0b471d8cc80a64fc8"`, undefined);
|
||||
await queryRunner.query(`DROP TABLE "credentials_entity"`, undefined);
|
||||
}
|
||||
|
||||
}
|
1
packages/cli/src/databases/sqlite/migrations/index.ts
Normal file
1
packages/cli/src/databases/sqlite/migrations/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './1588102412422-InitialMigration';
|
@ -21,7 +21,7 @@ Software: n8n
|
||||
|
||||
License: Apache 2.0
|
||||
|
||||
Licensor: Jan Oberhauser
|
||||
Licensor: n8n GmbH
|
||||
|
||||
|
||||
---------------------------------------------------------------------
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-core",
|
||||
"version": "0.31.0",
|
||||
"version": "0.33.0",
|
||||
"description": "Core functionality of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
@ -36,7 +36,7 @@
|
||||
"jest": "^24.9.0",
|
||||
"source-map-support": "^0.5.9",
|
||||
"ts-jest": "^24.0.2",
|
||||
"tslint": "^5.17.0",
|
||||
"tslint": "6.1.2",
|
||||
"typescript": "~3.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -45,8 +45,9 @@
|
||||
"crypto-js": "3.1.9-1",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mmmagic": "^0.5.2",
|
||||
"n8n-workflow": "~0.28.0",
|
||||
"n8n-workflow": "~0.30.0",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"request": "^2.88.2",
|
||||
"request-promise-native": "^1.0.7"
|
||||
},
|
||||
"jest": {
|
||||
|
@ -222,7 +222,7 @@ export function getCredentials(workflow: Workflow, node: INode, type: string, ad
|
||||
throw new Error(`Node type "${node.type}" does not have any credentials of type "${type}" defined!`);
|
||||
}
|
||||
|
||||
if (NodeHelpers.displayParameter(node.parameters, nodeCredentialDescription, node.parameters) === false) {
|
||||
if (NodeHelpers.displayParameter(additionalData.currentNodeParameters || node.parameters, nodeCredentialDescription, node.parameters) === false) {
|
||||
// Credentials should not be displayed so return undefined even if they would be defined
|
||||
return undefined;
|
||||
}
|
||||
@ -735,14 +735,14 @@ export function getLoadOptionsFunctions(workflow: Workflow, node: INode, additio
|
||||
return getCredentials(workflow, node, type, additionalData);
|
||||
},
|
||||
getCurrentNodeParameter: (parameterName: string): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object | undefined => {
|
||||
const nodeParameters = JSON.parse('' + additionalData.currentNodeParameters);
|
||||
const nodeParameters = additionalData.currentNodeParameters;
|
||||
if (nodeParameters && nodeParameters[parameterName]) {
|
||||
return nodeParameters[parameterName];
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
getCurrentNodeParameters: (): INodeParameters | undefined => {
|
||||
return JSON.parse('' + additionalData.currentNodeParameters);
|
||||
return additionalData.currentNodeParameters;
|
||||
},
|
||||
getNode: () => {
|
||||
return getNode(node);
|
||||
|
@ -21,7 +21,7 @@ Software: n8n
|
||||
|
||||
License: Apache 2.0
|
||||
|
||||
Licensor: Jan Oberhauser
|
||||
Licensor: n8n GmbH
|
||||
|
||||
|
||||
---------------------------------------------------------------------
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-editor-ui",
|
||||
"version": "0.42.0",
|
||||
"version": "0.44.0",
|
||||
"description": "Workflow Editor UI for n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
@ -30,8 +30,9 @@
|
||||
"@fortawesome/free-solid-svg-icons": "^5.9.0",
|
||||
"@fortawesome/vue-fontawesome": "^0.1.6",
|
||||
"@types/dateformat": "^3.0.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/file-saver": "^2.0.1",
|
||||
"@types/jest": "^24.0.18",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/lodash.set": "^4.3.6",
|
||||
"@types/node": "12.12.22",
|
||||
@ -39,7 +40,6 @@
|
||||
"@typescript-eslint/eslint-plugin": "^2.13.0",
|
||||
"@typescript-eslint/parser": "^2.13.0",
|
||||
"@vue/cli-plugin-babel": "^4.1.2",
|
||||
"@vue/cli-plugin-e2e-cypress": "^4.1.2",
|
||||
"@vue/cli-plugin-eslint": "^4.1.2",
|
||||
"@vue/cli-plugin-typescript": "~4.1.2",
|
||||
"@vue/cli-plugin-unit-jest": "^4.1.2",
|
||||
@ -64,15 +64,15 @@
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"n8n-workflow": "~0.28.0",
|
||||
"n8n-workflow": "~0.30.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"prismjs": "^1.17.1",
|
||||
"quill": "^2.0.0-dev.3",
|
||||
"quill-autoformat": "^0.1.1",
|
||||
"sass-loader": "^8.0.0",
|
||||
"string-template-parser": "^1.2.6",
|
||||
"ts-jest": "^24.0.2",
|
||||
"tslint": "^5.17.0",
|
||||
"ts-jest": "^25.4.0",
|
||||
"tslint": "^6.1.2",
|
||||
"typescript": "~3.7.4",
|
||||
"vue": "^2.6.9",
|
||||
"vue-cli-plugin-webpack-bundle-analyzer": "^2.0.0",
|
||||
|
@ -42,7 +42,7 @@
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div v-for="parameter in credentialProperties" :key="parameter.name">
|
||||
<el-row v-if="displayNodeParameter(parameter)" class="parameter-wrapper">
|
||||
<el-row v-if="displayCredentialParameter(parameter)" class="parameter-wrapper">
|
||||
<el-col :span="6" class="parameter-name">
|
||||
{{parameter.displayName}}:
|
||||
<el-tooltip placement="top" class="parameter-info" v-if="parameter.description" effect="light">
|
||||
@ -56,7 +56,6 @@
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
|
||||
<el-row class="nodes-access-wrapper">
|
||||
<el-col :span="6" class="headline">
|
||||
Nodes with access:
|
||||
@ -112,6 +111,7 @@ import {
|
||||
ICredentialType,
|
||||
ICredentialNodeAccess,
|
||||
INodeCredentialDescription,
|
||||
INodeParameters,
|
||||
INodeProperties,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
@ -232,6 +232,18 @@ export default mixins(
|
||||
tempValue[name] = parameterData.value;
|
||||
Vue.set(this, 'propertyValue', tempValue);
|
||||
},
|
||||
displayCredentialParameter (parameter: INodeProperties): boolean {
|
||||
if (parameter.type === 'hidden') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parameter.displayOptions === undefined) {
|
||||
// If it is not defined no need to do a proper check
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.displayParameter(this.propertyValue as INodeParameters, parameter, '');
|
||||
},
|
||||
async createCredentials (closeDialog: boolean): Promise<ICredentialsResponse | null> {
|
||||
const nodesAccess = this.nodesAccess.map((nodeType) => {
|
||||
return {
|
||||
@ -261,13 +273,6 @@ export default mixins(
|
||||
|
||||
return result;
|
||||
},
|
||||
displayNodeParameter (parameter: INodeProperties): boolean {
|
||||
if (parameter.type === 'hidden') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
async oAuth2CredentialAuthorize () {
|
||||
let url;
|
||||
|
||||
|
@ -504,11 +504,11 @@ export default mixins(
|
||||
} else if (entry.finished === true) {
|
||||
return 'The worklow execution was successful.';
|
||||
} else if (entry.retryOf !== undefined) {
|
||||
return `The workflow execution was a retry of "${entry.retryOf}" and did fail.<br />New retries have to be started from the original execution.`;
|
||||
return `The workflow execution was a retry of "${entry.retryOf}" and failed.<br />New retries have to be started from the original execution.`;
|
||||
} else if (entry.retrySuccessId !== undefined) {
|
||||
return `The workflow execution did fail but the retry "${entry.retrySuccessId}" was successful.`;
|
||||
return `The workflow execution failed but the retry "${entry.retrySuccessId}" was successful.`;
|
||||
} else {
|
||||
return 'The workflow execution did fail.';
|
||||
return 'The workflow execution failed.';
|
||||
}
|
||||
},
|
||||
async stopExecution (activeExecutionId: string) {
|
||||
|
@ -129,6 +129,10 @@ export default mixins(nodeBase, workflowHelpers).extend({
|
||||
}
|
||||
},
|
||||
nodeSubtitle (): string | undefined {
|
||||
if (this.data.notesInFlow) {
|
||||
return this.data.notes;
|
||||
}
|
||||
|
||||
if (this.nodeType !== null && this.nodeType.subtitle !== undefined) {
|
||||
return this.workflow.getSimpleParameterValue(this.data as INode, this.nodeType.subtitle) as string | undefined;
|
||||
}
|
||||
|
@ -142,6 +142,7 @@ export default mixins(
|
||||
nodeValues: {
|
||||
color: '#ff0000',
|
||||
alwaysOutputData: false,
|
||||
notesInFlow: false,
|
||||
continueOnFail: false,
|
||||
retryOnFail: false,
|
||||
maxTries: 3,
|
||||
@ -162,6 +163,14 @@ export default mixins(
|
||||
noDataExpression: true,
|
||||
description: 'Notes to save with the node.',
|
||||
},
|
||||
{
|
||||
displayName: 'Notes In Flow',
|
||||
name: 'notesInFlow',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
noDataExpression: true,
|
||||
description: 'If activated it will display the above notes in the flow as subtitle.',
|
||||
},
|
||||
{
|
||||
displayName: 'Node Color',
|
||||
name: 'color',
|
||||
@ -438,6 +447,11 @@ export default mixins(
|
||||
Vue.set(this.nodeValues, 'continueOnFail', this.node.continueOnFail);
|
||||
}
|
||||
|
||||
if (this.node.notesInFlow) {
|
||||
foundNodeSettings.push('notesInFlow');
|
||||
Vue.set(this.nodeValues, 'notesInFlow', this.node.notesInFlow);
|
||||
}
|
||||
|
||||
if (this.node.retryOnFail) {
|
||||
foundNodeSettings.push('retryOnFail');
|
||||
Vue.set(this.nodeValues, 'retryOnFail', this.node.retryOnFail);
|
||||
|
@ -22,6 +22,8 @@ export const pushConnection = mixins(
|
||||
return {
|
||||
eventSource: null as EventSource | null,
|
||||
reconnectTimeout: null as NodeJS.Timeout | null,
|
||||
retryTimeout: null as NodeJS.Timeout | null,
|
||||
pushMessageQueue: [] as Array<{ event: Event, retriesLeft: number }>,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -96,47 +98,84 @@ export const pushConnection = mixins(
|
||||
* @param {number} retryAttempts
|
||||
* @returns
|
||||
*/
|
||||
retryPushMessage (event: Event, retryAttempts: number) {
|
||||
retryAttempts = retryAttempts - 1;
|
||||
queuePushMessage (event: Event, retryAttempts: number) {
|
||||
this.pushMessageQueue.push({ event, retriesLeft: retryAttempts });
|
||||
|
||||
if (retryAttempts <= 0) {
|
||||
return;
|
||||
if (this.retryTimeout === null) {
|
||||
this.retryTimeout = setTimeout(this.processWaitingPushMessages, 20);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Process the push messages which are waiting in the queue
|
||||
*/
|
||||
processWaitingPushMessages () {
|
||||
if (this.retryTimeout !== null) {
|
||||
clearTimeout(this.retryTimeout);
|
||||
this.retryTimeout = null;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.pushMessageReceived(event, retryAttempts);
|
||||
}, 200);
|
||||
const queueLength = this.pushMessageQueue.length;
|
||||
for (let i = 0; i < queueLength; i++) {
|
||||
const messageData = this.pushMessageQueue.shift();
|
||||
|
||||
if (this.pushMessageReceived(messageData!.event, true) === false) {
|
||||
// Was not successful
|
||||
messageData!.retriesLeft -= 1;
|
||||
|
||||
if (messageData!.retriesLeft > 0) {
|
||||
// If still retries are left add it back and stop execution
|
||||
this.pushMessageQueue.unshift(messageData!);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.pushMessageQueue.length !== 0 && this.retryTimeout === null) {
|
||||
this.retryTimeout = setTimeout(this.processWaitingPushMessages, 25);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Process a newly received message
|
||||
*
|
||||
* @param {Event} event The event data with the message data
|
||||
* @returns {void}
|
||||
* @param {boolean} [isRetry] If it is a retry
|
||||
* @returns {boolean} If message could be processed
|
||||
*/
|
||||
pushMessageReceived (event: Event, retryAttempts?: number): void {
|
||||
retryAttempts = retryAttempts || 5;
|
||||
pushMessageReceived (event: Event, isRetry?: boolean): boolean {
|
||||
const retryAttempts = 5;
|
||||
|
||||
let receivedData: IPushData;
|
||||
try {
|
||||
// @ts-ignore
|
||||
receivedData = JSON.parse(event.data);
|
||||
} catch (error) {
|
||||
console.error('The received push data is not valid JSON.'); // eslint-disable-line no-console
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!['testWebhookReceived'].includes(receivedData.type) && isRetry !== true && this.pushMessageQueue.length) {
|
||||
// If there are already messages in the queue add the new one that all of them
|
||||
// get executed in order
|
||||
this.queuePushMessage(event, retryAttempts);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (['nodeExecuteAfter', 'nodeExecuteBefore'].includes(receivedData.type)) {
|
||||
if (this.$store.getters.isActionActive('workflowRunning') === false) {
|
||||
// No workflow is running so ignore the messages
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
const pushData = receivedData.data as IPushDataNodeExecuteBefore;
|
||||
if (this.$store.getters.activeExecutionId !== pushData.executionId) {
|
||||
// The data is not for the currently active execution or
|
||||
// we do not have the execution id yet.
|
||||
this.retryPushMessage(event, retryAttempts);
|
||||
return;
|
||||
if (isRetry !== true) {
|
||||
this.queuePushMessage(event, retryAttempts);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,14 +187,16 @@ export const pushConnection = mixins(
|
||||
|
||||
if (this.$store.getters.isActionActive('workflowRunning') === false) {
|
||||
// No workflow is running so ignore the messages
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.$store.getters.activeExecutionId !== pushData.executionIdActive) {
|
||||
// The workflow which did finish execution did either not get started
|
||||
// by this session or we do not have the execution id yet.
|
||||
this.retryPushMessage(event, retryAttempts);
|
||||
return;
|
||||
if (isRetry !== true) {
|
||||
this.queuePushMessage(event, retryAttempts);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const runDataExecuted = pushData.data;
|
||||
@ -231,7 +272,10 @@ export const pushConnection = mixins(
|
||||
this.$store.commit('setExecutionWaitingForWebhook', false);
|
||||
this.$store.commit('setActiveExecutionId', pushData.executionId);
|
||||
}
|
||||
|
||||
this.processWaitingPushMessages();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -181,7 +181,7 @@ if (process.env.NODE_ENV !== 'production') {
|
||||
// not do anything about it anyway
|
||||
return;
|
||||
}
|
||||
console.error('error cought in main.ts'); // eslint-disable-line no-console
|
||||
console.error('error caught in main.ts'); // eslint-disable-line no-console
|
||||
console.error(message); // eslint-disable-line no-console
|
||||
console.error(error); // eslint-disable-line no-console
|
||||
};
|
||||
|
@ -21,7 +21,7 @@ Software: n8n
|
||||
|
||||
License: Apache 2.0
|
||||
|
||||
Licensor: Jan Oberhauser
|
||||
Licensor: n8n GmbH
|
||||
|
||||
|
||||
---------------------------------------------------------------------
|
||||
|
@ -140,7 +140,7 @@ export class New extends Command {
|
||||
// in the correct way
|
||||
const replaceValues = {
|
||||
ClassNameReplace: changeCase.pascalCase(nodeName),
|
||||
DisplayNameReplace: changeCase.titleCase(nodeName),
|
||||
DisplayNameReplace: changeCase.capitalCase(nodeName),
|
||||
N8nNameReplace: changeCase.camelCase(nodeName),
|
||||
NodeDescriptionReplace: additionalAnswers.description,
|
||||
};
|
||||
|
@ -55,13 +55,13 @@
|
||||
"@oclif/errors": "^1.2.2",
|
||||
"@types/express": "^4.16.1",
|
||||
"@types/node": "^10.10.1",
|
||||
"change-case": "^3.1.0",
|
||||
"change-case": "^4.1.1",
|
||||
"copyfiles": "^2.1.1",
|
||||
"inquirer": "^7.0.0",
|
||||
"n8n-core": "^0.31.0",
|
||||
"n8n-workflow": "^0.28.0",
|
||||
"replace-in-file": "^4.1.0",
|
||||
"request": "^2.88.0",
|
||||
"replace-in-file": "^6.0.0",
|
||||
"request": "^2.88.2",
|
||||
"tmp-promise": "^2.0.2",
|
||||
"typescript": "~3.7.4"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
import * as fs from 'fs';
|
||||
import replaceInFile, { ReplaceInFileConfig } from 'replace-in-file';
|
||||
import {replaceInFile, ReplaceInFileConfig } from 'replace-in-file';
|
||||
|
||||
const { promisify } = require('util');
|
||||
const fsCopyFile = promisify(fs.copyFile);
|
||||
|
@ -21,7 +21,7 @@ Software: n8n
|
||||
|
||||
License: Apache 2.0
|
||||
|
||||
Licensor: Jan Oberhauser
|
||||
Licensor: n8n GmbH
|
||||
|
||||
|
||||
---------------------------------------------------------------------
|
||||
|
23
packages/nodes-base/credentials/AgileCrmApi.credentials.ts
Normal file
23
packages/nodes-base/credentials/AgileCrmApi.credentials.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class AgileCrmApi implements ICredentialType {
|
||||
name = 'agileCrmApi';
|
||||
displayName = 'AgileCRM API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
17
packages/nodes-base/credentials/BannerbearApi.credentials.ts
Normal file
17
packages/nodes-base/credentials/BannerbearApi.credentials.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class BannerbearApi implements ICredentialType {
|
||||
name = 'bannerbearApi';
|
||||
displayName = 'Bannerbear API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Project API Key',
|
||||
name: 'apiKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
export class FacebookGraphApi implements ICredentialType {
|
||||
name = 'facebookGraphApi';
|
||||
displayName = 'Facebook Graph API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Access Token',
|
||||
name: 'accessToken',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
@ -1,45 +1,106 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { ICredentialType, NodePropertyTypes } from 'n8n-workflow';
|
||||
|
||||
export class MongoDb implements ICredentialType {
|
||||
name = 'mongoDb';
|
||||
displayName = 'MongoDB';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Configuration Type',
|
||||
name: 'configurationType',
|
||||
type: 'options' as NodePropertyTypes,
|
||||
options: [
|
||||
{
|
||||
name: 'Connection String',
|
||||
value: 'connectionString',
|
||||
description: 'Provide connection data via string',
|
||||
},
|
||||
{
|
||||
name: 'Values',
|
||||
value: 'values',
|
||||
description: 'Provide connection data via values',
|
||||
},
|
||||
],
|
||||
default: 'values',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
{
|
||||
displayName: 'Connection String',
|
||||
name: 'connectionString',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
displayOptions: {
|
||||
show: {
|
||||
configurationType: [
|
||||
'connectionString',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
placeholder: 'mongodb://<USERNAME>:<PASSWORD>@localhost:27017/?authSource=admin&readPreference=primary&appname=n8n&ssl=false',
|
||||
required: false,
|
||||
description: `If provided, the value here will be used as a MongoDB connection string,<br />
|
||||
and the MongoDB credentials will be ignored`
|
||||
},
|
||||
{
|
||||
displayName: 'Host',
|
||||
name: 'host',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: 'localhost',
|
||||
displayOptions: {
|
||||
show: {
|
||||
configurationType: [
|
||||
'values',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 'localhost'
|
||||
},
|
||||
{
|
||||
displayName: 'Database',
|
||||
name: 'database',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
description: 'Note: the database should still be provided even if using an override connection string'
|
||||
},
|
||||
{
|
||||
displayName: 'User',
|
||||
name: 'user',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
configurationType: [
|
||||
'values',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: ''
|
||||
},
|
||||
{
|
||||
displayName: 'Password',
|
||||
name: 'password',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
typeOptions: {
|
||||
password: true,
|
||||
password: true
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
configurationType: [
|
||||
'values',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: ''
|
||||
},
|
||||
{
|
||||
displayName: 'Port',
|
||||
name: 'port',
|
||||
type: 'number' as NodePropertyTypes,
|
||||
default: 27017,
|
||||
displayOptions: {
|
||||
show: {
|
||||
configurationType: [
|
||||
'values',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 27017
|
||||
},
|
||||
];
|
||||
}
|
||||
|
17
packages/nodes-base/credentials/Sms77Api.credentials.ts
Normal file
17
packages/nodes-base/credentials/Sms77Api.credentials.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class Sms77Api implements ICredentialType {
|
||||
name = 'sms77Api';
|
||||
displayName = 'Sms77 API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class SurveyMonkeyApi implements ICredentialType {
|
||||
name = 'surveyMonkeyApi';
|
||||
displayName = 'SurveyMonkey API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Access Token',
|
||||
name: 'accessToken',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
description: `The access token must have the following scopes:</br />
|
||||
- Create/modify webhooks</br />
|
||||
- View webhooks</br />
|
||||
- View surveys</br />
|
||||
- View collectors</br />
|
||||
- View responses<br />
|
||||
- View response details`,
|
||||
},
|
||||
{
|
||||
displayName: 'Client ID',
|
||||
name: 'clientId',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Client Secret',
|
||||
name: 'clientSecret',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
575
packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts
Normal file
575
packages/nodes-base/nodes/AgileCrm/AgileCrm.node.ts
Normal file
@ -0,0 +1,575 @@
|
||||
import { IExecuteFunctions } from 'n8n-core';
|
||||
import {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
contactFields,
|
||||
contactOperations
|
||||
} from './ContactDescription';
|
||||
|
||||
import {
|
||||
companyFields,
|
||||
companyOperations
|
||||
} from './CompanyDescription';
|
||||
|
||||
import {
|
||||
dealFields,
|
||||
dealOperations
|
||||
} from './DealDescription';
|
||||
|
||||
import { IContact, IContactUpdate } from './ContactInterface';
|
||||
import { agileCrmApiRequest, agileCrmApiRequestUpdate, validateJSON } from './GenericFunctions';
|
||||
import { IDeal } from './DealInterface';
|
||||
|
||||
|
||||
export class AgileCrm implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'AgileCRM',
|
||||
name: 'agileCrm',
|
||||
icon: 'file:agilecrm.png',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
description: 'Consume AgileCRM API',
|
||||
defaults: {
|
||||
name: 'AgileCRM',
|
||||
color: '#772244',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'agileCrmApi',
|
||||
required: true,
|
||||
}
|
||||
],
|
||||
properties: [
|
||||
// Node properties which the user gets displayed and
|
||||
// can change on the node.
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Company',
|
||||
value: 'company'
|
||||
},
|
||||
{
|
||||
name: 'Contact',
|
||||
value: 'contact'
|
||||
},
|
||||
{
|
||||
name: 'Deal',
|
||||
value: 'deal'
|
||||
},
|
||||
],
|
||||
default: 'contact',
|
||||
description: 'Resource to consume.',
|
||||
},
|
||||
// CONTACT
|
||||
...contactOperations,
|
||||
...contactFields,
|
||||
|
||||
// COMPANY
|
||||
...companyOperations,
|
||||
...companyFields,
|
||||
|
||||
// DEAL
|
||||
...dealOperations,
|
||||
...dealFields
|
||||
],
|
||||
|
||||
};
|
||||
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
let responseData;
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
|
||||
if (resource === 'contact' || resource === 'company') {
|
||||
const idGetter = resource === 'contact' ? 'contactId' : 'companyId';
|
||||
|
||||
if (operation === 'get') {
|
||||
const contactId = this.getNodeParameter(idGetter, i) as string;
|
||||
|
||||
const endpoint = `api/contacts/${contactId}`;
|
||||
responseData = await agileCrmApiRequest.call(this, 'GET', endpoint, {});
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
const contactId = this.getNodeParameter(idGetter, i) as string;
|
||||
|
||||
const endpoint = `api/contacts/${contactId}`;
|
||||
responseData = await agileCrmApiRequest.call(this, 'DELETE', endpoint, {});
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
if (resource === 'contact') {
|
||||
if (returnAll) {
|
||||
const endpoint = 'api/contacts';
|
||||
responseData = await agileCrmApiRequest.call(this, 'GET', endpoint, {});
|
||||
} else {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
const endpoint = `api/contacts?page_size=${limit}`;
|
||||
responseData = await agileCrmApiRequest.call(this, 'GET', endpoint, {});
|
||||
}
|
||||
} else {
|
||||
if (returnAll) {
|
||||
const endpoint = 'api/contacts/companies/list';
|
||||
responseData = await agileCrmApiRequest.call(this, 'POST', endpoint, {});
|
||||
} else {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
const endpoint = `api/contacts/companies/list?page_size=${limit}`;
|
||||
responseData = await agileCrmApiRequest.call(this, 'POST', endpoint, {});
|
||||
}
|
||||
}
|
||||
|
||||
} else if (operation === 'create') {
|
||||
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
|
||||
const body: IContact = {};
|
||||
const properties: IDataObject[] = [];
|
||||
|
||||
if (jsonParameters) {
|
||||
const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string;
|
||||
|
||||
if (additionalFieldsJson !== '') {
|
||||
|
||||
if (validateJSON(additionalFieldsJson) !== undefined) {
|
||||
|
||||
Object.assign(body, JSON.parse(additionalFieldsJson));
|
||||
|
||||
} else {
|
||||
throw new Error('Additional fields must be a valid JSON');
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
// if company, add 'company' as type. default is person
|
||||
if (resource === 'company') {
|
||||
body.type = 'COMPANY';
|
||||
}
|
||||
if (additionalFields.starValue) {
|
||||
body.star_value = additionalFields.starValue as string;
|
||||
}
|
||||
if (additionalFields.tags) {
|
||||
body.tags = additionalFields.tags as string[];
|
||||
}
|
||||
|
||||
// Contact specific properties
|
||||
if (resource === 'contact') {
|
||||
if (additionalFields.firstName) {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
name: 'first_name',
|
||||
value: additionalFields.firstName as string
|
||||
} as IDataObject);
|
||||
}
|
||||
if (additionalFields.lastName) {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
name: 'last_name',
|
||||
value: additionalFields.lastName as string
|
||||
} as IDataObject);
|
||||
}
|
||||
if (additionalFields.company) {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
name: 'company',
|
||||
value: additionalFields.company as string
|
||||
} as IDataObject);
|
||||
}
|
||||
if (additionalFields.title) {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
name: 'title',
|
||||
value: additionalFields.title as string
|
||||
} as IDataObject);
|
||||
}
|
||||
if (additionalFields.emailOptions) {
|
||||
//@ts-ignore
|
||||
additionalFields.emailOptions.emailProperties.map(property => {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
subtype: property.subtype as string,
|
||||
name: 'email',
|
||||
value: property.email as string
|
||||
} as IDataObject);
|
||||
});
|
||||
}
|
||||
if (additionalFields.addressOptions) {
|
||||
//@ts-ignore
|
||||
additionalFields.addressOptions.addressProperties.map(property => {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
subtype: property.subtype as string,
|
||||
name: 'address',
|
||||
value: property.address as string
|
||||
} as IDataObject);
|
||||
});
|
||||
}
|
||||
|
||||
if (additionalFields.phoneOptions) {
|
||||
//@ts-ignore
|
||||
additionalFields.phoneOptions.phoneProperties.map(property => {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
subtype: property.subtype as string,
|
||||
name: 'phone',
|
||||
value: property.number as string
|
||||
} as IDataObject);
|
||||
});
|
||||
}
|
||||
} else if (resource === 'company') {
|
||||
if (additionalFields.email) {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
name: 'email',
|
||||
value: additionalFields.email as string
|
||||
} as IDataObject);
|
||||
}
|
||||
|
||||
if (additionalFields.address) {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
name: 'address',
|
||||
value: additionalFields.address as string
|
||||
} as IDataObject);
|
||||
}
|
||||
|
||||
if (additionalFields.phone) {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
name: 'phone',
|
||||
value: additionalFields.phone as string
|
||||
} as IDataObject);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (additionalFields.websiteOptions) {
|
||||
//@ts-ignore
|
||||
additionalFields.websiteOptions.websiteProperties.map(property => {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
subtype: property.subtype as string,
|
||||
name: 'webiste',
|
||||
value: property.url as string
|
||||
} as IDataObject);
|
||||
});
|
||||
}
|
||||
|
||||
if (additionalFields.customProperties) {
|
||||
//@ts-ignore
|
||||
additionalFields.customProperties.customProperty.map(property => {
|
||||
properties.push({
|
||||
type: 'CUSTOM',
|
||||
subtype: property.subtype as string,
|
||||
name: property.name,
|
||||
value: property.value as string
|
||||
} as IDataObject);
|
||||
});
|
||||
}
|
||||
body.properties = properties;
|
||||
|
||||
}
|
||||
const endpoint = 'api/contacts';
|
||||
responseData = await agileCrmApiRequest.call(this, 'POST', endpoint, body);
|
||||
|
||||
} else if (operation === 'update') {
|
||||
const contactId = this.getNodeParameter(idGetter, i) as string;
|
||||
const contactUpdatePayload: IContactUpdate = { id: contactId };
|
||||
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
|
||||
const body: IContact = {};
|
||||
const properties: IDataObject[] = [];
|
||||
|
||||
if (jsonParameters) {
|
||||
const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string;
|
||||
|
||||
if (additionalFieldsJson !== '') {
|
||||
|
||||
if (validateJSON(additionalFieldsJson) !== undefined) {
|
||||
|
||||
Object.assign(body, JSON.parse(additionalFieldsJson));
|
||||
|
||||
} else {
|
||||
throw new Error('Additional fields must be a valid JSON');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (additionalFields.starValue) {
|
||||
body.star_value = additionalFields.starValue as string;
|
||||
}
|
||||
if (additionalFields.tags) {
|
||||
body.tags = additionalFields.tags as string[];
|
||||
}
|
||||
|
||||
// Contact specific properties
|
||||
if (resource === 'contact') {
|
||||
|
||||
if (additionalFields.leadScore) {
|
||||
body.lead_score = additionalFields.leadScore as string;
|
||||
}
|
||||
|
||||
if (additionalFields.firstName) {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
name: 'first_name',
|
||||
value: additionalFields.firstName as string
|
||||
} as IDataObject);
|
||||
}
|
||||
if (additionalFields.lastName) {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
name: 'last_name',
|
||||
value: additionalFields.lastName as string
|
||||
} as IDataObject);
|
||||
}
|
||||
if (additionalFields.company) {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
name: 'company',
|
||||
value: additionalFields.company as string
|
||||
} as IDataObject);
|
||||
}
|
||||
if (additionalFields.title) {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
name: 'title',
|
||||
value: additionalFields.title as string
|
||||
} as IDataObject);
|
||||
}
|
||||
if (additionalFields.emailOptions) {
|
||||
//@ts-ignore
|
||||
additionalFields.emailOptions.emailProperties.map(property => {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
subtype: property.subtype as string,
|
||||
name: 'email',
|
||||
value: property.email as string
|
||||
} as IDataObject);
|
||||
});
|
||||
}
|
||||
if (additionalFields.addressOptions) {
|
||||
//@ts-ignore
|
||||
additionalFields.addressOptions.addressProperties.map(property => {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
subtype: property.subtype as string,
|
||||
name: 'address',
|
||||
value: property.address as string
|
||||
} as IDataObject);
|
||||
});
|
||||
}
|
||||
|
||||
if (additionalFields.phoneOptions) {
|
||||
//@ts-ignore
|
||||
additionalFields.phoneOptions.phoneProperties.map(property => {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
subtype: property.subtype as string,
|
||||
name: 'phone',
|
||||
value: property.number as string
|
||||
} as IDataObject);
|
||||
});
|
||||
}
|
||||
} else if (resource === 'company') {
|
||||
if (additionalFields.email) {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
name: 'email',
|
||||
value: additionalFields.email as string
|
||||
} as IDataObject);
|
||||
}
|
||||
|
||||
if (additionalFields.address) {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
name: 'address',
|
||||
value: additionalFields.address as string
|
||||
} as IDataObject);
|
||||
}
|
||||
|
||||
if (additionalFields.phone) {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
name: 'phone',
|
||||
value: additionalFields.phone as string
|
||||
} as IDataObject);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (additionalFields.websiteOptions) {
|
||||
//@ts-ignore
|
||||
additionalFields.websiteOptions.websiteProperties.map(property => {
|
||||
properties.push({
|
||||
type: 'SYSTEM',
|
||||
subtype: property.subtype as string,
|
||||
name: 'webiste',
|
||||
value: property.url as string
|
||||
} as IDataObject);
|
||||
});
|
||||
}
|
||||
if (additionalFields.customProperties) {
|
||||
//@ts-ignore
|
||||
additionalFields.customProperties.customProperty.map(property => {
|
||||
properties.push({
|
||||
type: 'CUSTOM',
|
||||
subtype: property.subtype as string,
|
||||
name: property.name,
|
||||
value: property.value as string
|
||||
} as IDataObject);
|
||||
});
|
||||
}
|
||||
body.properties = properties;
|
||||
}
|
||||
|
||||
Object.assign(contactUpdatePayload, body);
|
||||
|
||||
responseData = await agileCrmApiRequestUpdate.call(this, 'PUT', '', contactUpdatePayload);
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'deal') {
|
||||
|
||||
if (operation === 'get') {
|
||||
const dealId = this.getNodeParameter('dealId', i) as string;
|
||||
|
||||
const endpoint = `api/opportunity/${dealId}`;
|
||||
responseData = await agileCrmApiRequest.call(this, 'GET', endpoint, {});
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
const contactId = this.getNodeParameter('dealId', i) as string;
|
||||
|
||||
const endpoint = `api/opportunity/${contactId}`;
|
||||
responseData = await agileCrmApiRequest.call(this, 'DELETE', endpoint, {});
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
if (returnAll) {
|
||||
const endpoint = 'api/opportunity';
|
||||
responseData = await agileCrmApiRequest.call(this, 'GET', endpoint, {});
|
||||
} else {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
const endpoint = `api/opportunity?page_size=${limit}`;
|
||||
responseData = await agileCrmApiRequest.call(this, 'GET', endpoint, {});
|
||||
}
|
||||
|
||||
} else if (operation === 'create') {
|
||||
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
|
||||
|
||||
const body: IDeal = {};
|
||||
|
||||
if (jsonParameters) {
|
||||
const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string;
|
||||
|
||||
if (additionalFieldsJson !== '') {
|
||||
if (validateJSON(additionalFieldsJson) !== undefined) {
|
||||
Object.assign(body, JSON.parse(additionalFieldsJson));
|
||||
} else {
|
||||
throw new Error('Additional fields must be a valid JSON');
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
body.close_date = new Date(this.getNodeParameter('closeDate', i) as string).getTime();
|
||||
body.expected_value = this.getNodeParameter('expectedValue', i) as number;
|
||||
body.milestone = this.getNodeParameter('milestone', i) as string;
|
||||
body.probability = this.getNodeParameter('probability', i) as number;
|
||||
body.name = this.getNodeParameter('name', i) as string;
|
||||
|
||||
if (additionalFields.contactIds) {
|
||||
body.contactIds = additionalFields.contactIds as string[];
|
||||
}
|
||||
|
||||
if (additionalFields.customData) {
|
||||
// @ts-ignore
|
||||
body.customData = additionalFields.customData.customProperty as IDealCustomProperty[];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const endpoint = 'api/opportunity';
|
||||
responseData = await agileCrmApiRequest.call(this, 'POST', endpoint, body);
|
||||
|
||||
} else if (operation === 'update') {
|
||||
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
|
||||
|
||||
const body: IDeal = {};
|
||||
|
||||
if (jsonParameters) {
|
||||
const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string;
|
||||
|
||||
if (additionalFieldsJson !== '') {
|
||||
|
||||
if (validateJSON(additionalFieldsJson) !== undefined) {
|
||||
|
||||
Object.assign(body, JSON.parse(additionalFieldsJson));
|
||||
|
||||
} else {
|
||||
throw new Error('Additional fields must be valid JSON');
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
body.id = this.getNodeParameter('dealId', i) as number;
|
||||
|
||||
if (additionalFields.expectedValue) {
|
||||
body.expected_value = additionalFields.expectedValue as number;
|
||||
}
|
||||
|
||||
if (additionalFields.name) {
|
||||
body.name = additionalFields.name as string;
|
||||
}
|
||||
|
||||
if (additionalFields.probability) {
|
||||
body.probability = additionalFields.probability as number;
|
||||
}
|
||||
|
||||
if (additionalFields.contactIds) {
|
||||
body.contactIds = additionalFields.contactIds as string[];
|
||||
}
|
||||
|
||||
if (additionalFields.customData) {
|
||||
// @ts-ignore
|
||||
body.customData = additionalFields.customData.customProperty as IDealCustomProperty[];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const endpoint = 'api/opportunity/partial-update';
|
||||
responseData = await agileCrmApiRequest.call(this, 'PUT', endpoint, body);
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else {
|
||||
returnData.push(responseData as IDataObject);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
|
||||
}
|
660
packages/nodes-base/nodes/AgileCrm/CompanyDescription.ts
Normal file
660
packages/nodes-base/nodes/AgileCrm/CompanyDescription.ts
Normal file
@ -0,0 +1,660 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
export const companyOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'company',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a new company',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a company',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a company',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all companies',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update company properties',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
export const companyFields = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Company ID',
|
||||
name: 'companyId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'company',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Unique identifier for a particular company',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:get all */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'company',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 20,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'company',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'company',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: ' Additional Fields',
|
||||
name: 'additionalFieldsJson',
|
||||
type: 'json',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'company',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
jsonParameters: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Object of values to set as described <a href="https://github.com/agilecrm/rest-api#1-companys---companies-api" target="_blank">here</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'company',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Address',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Company address.',
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Company email.',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Company name.',
|
||||
},
|
||||
{
|
||||
displayName: 'Phone',
|
||||
name: 'phone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Company phone.',
|
||||
},
|
||||
{
|
||||
displayName: 'Star Value',
|
||||
name: 'starValue',
|
||||
type: 'options',
|
||||
default: '',
|
||||
description: 'Rating of company (Max value 5). This is not applicable for companies.',
|
||||
options: [
|
||||
{
|
||||
name: '0',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
name: '1',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
name: '2',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
name: '3',
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
name: '4',
|
||||
value: 4
|
||||
},
|
||||
{
|
||||
name: '5',
|
||||
value: 5
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
displayName: 'Tags',
|
||||
name: 'tags',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
multipleValueButtonText: 'Add Tag',
|
||||
},
|
||||
default: [],
|
||||
description: 'Unique identifiers added to company, for easy management of companys. This is not applicable for companies.',
|
||||
},
|
||||
{
|
||||
displayName: 'Website',
|
||||
name: 'websiteOptions',
|
||||
type: 'fixedCollection',
|
||||
description: 'Companies websites.',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Website properties.',
|
||||
name: 'websiteProperties',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'subtype',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Type of website.',
|
||||
options: [
|
||||
{
|
||||
name: 'Facebook',
|
||||
value: 'facebook',
|
||||
},
|
||||
{
|
||||
name: 'Feed',
|
||||
value: 'feed',
|
||||
},
|
||||
{
|
||||
name: 'Flickr',
|
||||
value: 'flickr',
|
||||
},
|
||||
{
|
||||
name: 'Github',
|
||||
value: 'github',
|
||||
},
|
||||
{
|
||||
name: 'Google Plus',
|
||||
value: 'googlePlus',
|
||||
},
|
||||
{
|
||||
name: 'LinkedIn',
|
||||
value: 'linkedin',
|
||||
},
|
||||
{
|
||||
name: 'Skype',
|
||||
value: 'skype',
|
||||
},
|
||||
{
|
||||
name: 'Twitter',
|
||||
value: 'twitter',
|
||||
},
|
||||
{
|
||||
name: 'URL',
|
||||
value: 'url',
|
||||
},
|
||||
{
|
||||
name: 'Xing',
|
||||
value: 'xing',
|
||||
},
|
||||
{
|
||||
name: 'YouTube',
|
||||
value: 'youtube',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Website URL',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Custom Properties',
|
||||
name: 'customProperties',
|
||||
type: 'fixedCollection',
|
||||
description: 'Custom Properties',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Property',
|
||||
name: 'customProperty',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Property name.'
|
||||
},
|
||||
{
|
||||
displayName: 'Sub Type',
|
||||
name: 'subtype',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Property sub type.',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Property value.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'company ID',
|
||||
name: 'companyId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'company',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'ID of company to delete',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* company:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Company ID',
|
||||
name: 'companyId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'company',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Unique identifier for a particular company',
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'company',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: ' Additional Fields',
|
||||
name: 'additionalFieldsJson',
|
||||
type: 'json',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'company',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
jsonParameters: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Object of values to set as described <a href="https://github.com/agilecrm/rest-api#1-companys---companies-api" target="_blank">here</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'company',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Address',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Company address.',
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Company email.',
|
||||
},
|
||||
{
|
||||
displayName: 'Star Value',
|
||||
name: 'starValue',
|
||||
type: 'options',
|
||||
default: '',
|
||||
description: 'Rating of company (Max value 5). This is not applicable for companies.',
|
||||
options: [
|
||||
{
|
||||
name: '0',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
name: '1',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
name: '2',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
name: '3',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
name: '4',
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
name: '5',
|
||||
value: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Tags',
|
||||
name: 'tags',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
multipleValueButtonText: 'Add Tag',
|
||||
},
|
||||
default: [],
|
||||
description: 'Unique identifiers added to company, for easy management of companys. This is not applicable for companies.',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Company name.',
|
||||
},
|
||||
{
|
||||
displayName: 'Phone',
|
||||
name: 'phone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Company phone.',
|
||||
},
|
||||
{
|
||||
displayName: 'Website',
|
||||
name: 'websiteOptions',
|
||||
type: 'fixedCollection',
|
||||
description: 'Companys websites.',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Website properties.',
|
||||
name: 'websiteProperties',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'subtype',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Type of website.',
|
||||
options: [
|
||||
{
|
||||
name: 'Facebook',
|
||||
value: 'facebook',
|
||||
},
|
||||
{
|
||||
name: 'Feed',
|
||||
value: 'feed',
|
||||
},
|
||||
{
|
||||
name: 'Flickr',
|
||||
value: 'flickr',
|
||||
},
|
||||
{
|
||||
name: 'Github',
|
||||
value: 'github',
|
||||
},
|
||||
{
|
||||
name: 'Google Plus',
|
||||
value: 'googlePlus',
|
||||
},
|
||||
{
|
||||
name: 'LinkedIn',
|
||||
value: 'linkedin',
|
||||
},
|
||||
{
|
||||
name: 'Skype',
|
||||
value: 'skype',
|
||||
},
|
||||
{
|
||||
name: 'Twitter',
|
||||
value: 'twitter',
|
||||
},
|
||||
{
|
||||
name: 'URL',
|
||||
value: 'url',
|
||||
},
|
||||
{
|
||||
name: 'Xing',
|
||||
value: 'xing',
|
||||
},
|
||||
{
|
||||
name: 'YouTube',
|
||||
value: 'youtube',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Website URL',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Custom Properties',
|
||||
name: 'customProperties',
|
||||
type: 'fixedCollection',
|
||||
description: 'Custom Properties',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Property',
|
||||
name: 'customProperty',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Property name.',
|
||||
},
|
||||
{
|
||||
displayName: 'Sub Type',
|
||||
name: 'subtype',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Property sub type.',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Property value.',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
991
packages/nodes-base/nodes/AgileCrm/ContactDescription.ts
Normal file
991
packages/nodes-base/nodes/AgileCrm/ContactDescription.ts
Normal file
@ -0,0 +1,991 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const contactOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a new contact',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a contact',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a contact',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all contacts',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update contact properties',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const contactFields = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
name: 'contactId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Unique identifier for a particular contact',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:get all */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 20,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: ' Additional Fields',
|
||||
name: 'additionalFieldsJson',
|
||||
type: 'json',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
jsonParameters: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
description: `Object of values to set as described <a href="https://github.com/agilecrm/rest-api#1-contacts---companies-api" target="_blank">here</a>.`,
|
||||
},
|
||||
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Address',
|
||||
name: 'addressOptions',
|
||||
type: 'fixedCollection',
|
||||
description: 'Contacts address.',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Address Properties',
|
||||
name: 'addressProperties',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'subtype',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Type of address.',
|
||||
options: [
|
||||
{
|
||||
name: 'Home',
|
||||
value: 'home',
|
||||
},
|
||||
{
|
||||
name: 'Postal',
|
||||
value: 'postal',
|
||||
},
|
||||
{
|
||||
name: 'Office',
|
||||
value: 'office'
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Address',
|
||||
name: 'address',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Full address.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Company',
|
||||
name: 'company',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Company Name.',
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'emailOptions',
|
||||
type: 'fixedCollection',
|
||||
description: 'Contact email.',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Email Properties',
|
||||
name: 'emailProperties',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'subtype',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Type of Email',
|
||||
options: [
|
||||
{
|
||||
name: 'Work',
|
||||
value: 'work',
|
||||
},
|
||||
{
|
||||
name: 'Personal',
|
||||
value: 'personal',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Email',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'First Name',
|
||||
name: 'firstName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Contact first name.',
|
||||
},
|
||||
{
|
||||
displayName: 'Last Name',
|
||||
name: 'lastName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Contact last name.',
|
||||
},
|
||||
{
|
||||
displayName: 'Lead Score',
|
||||
name: 'leadScore',
|
||||
type: 'number',
|
||||
default: '',
|
||||
description: 'Lead score of contact',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Star Value',
|
||||
name: 'starValue',
|
||||
type: 'options',
|
||||
default: '',
|
||||
description: 'Rating of contact (Max value 5). This is not applicable for companies.',
|
||||
options: [
|
||||
{
|
||||
name: '0',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
name: '1',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
name: '2',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
name: '3',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
name: '4',
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
name: '5',
|
||||
value: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Phone',
|
||||
name: 'phoneOptions',
|
||||
type: 'fixedCollection',
|
||||
description: 'Contacts phone.',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Phone properties',
|
||||
name: 'phoneProperties',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'subtype',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Type of phone number.',
|
||||
options: [
|
||||
{
|
||||
name: 'Home',
|
||||
value: 'home',
|
||||
},
|
||||
{
|
||||
name: 'Home Fax',
|
||||
value: 'homeFax',
|
||||
},
|
||||
{
|
||||
name: 'Main',
|
||||
value: 'main',
|
||||
},
|
||||
{
|
||||
name: 'Mobile',
|
||||
value: 'mobile',
|
||||
},
|
||||
{
|
||||
name: 'Other',
|
||||
value: 'other',
|
||||
},
|
||||
{
|
||||
name: 'Work Fax',
|
||||
value: 'workFax',
|
||||
},
|
||||
{
|
||||
name: 'Work',
|
||||
value: 'work',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Number',
|
||||
name: 'number',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Phone number.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Tags',
|
||||
name: 'tags',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
multipleValueButtonText: 'Add Tag',
|
||||
},
|
||||
default: [],
|
||||
description: 'Unique identifiers added to contact, for easy management of contacts. This is not applicable for companies.',
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Professional title.',
|
||||
},
|
||||
{
|
||||
displayName: 'Website',
|
||||
name: 'websiteOptions',
|
||||
type: 'fixedCollection',
|
||||
description: 'Contacts websites.',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Website properties.',
|
||||
name: 'websiteProperties',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'subtype',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Type of website.',
|
||||
options: [
|
||||
{
|
||||
name: 'Facebook',
|
||||
value: 'facebook',
|
||||
},
|
||||
{
|
||||
name: 'Feed',
|
||||
value: 'feed',
|
||||
},
|
||||
{
|
||||
name: 'Flickr',
|
||||
value: 'flickr',
|
||||
},
|
||||
{
|
||||
name: 'Github',
|
||||
value: 'github',
|
||||
},
|
||||
{
|
||||
name: 'Google Plus',
|
||||
value: 'googlePlus',
|
||||
},
|
||||
{
|
||||
name: 'LinkedIn',
|
||||
value: 'linkedin',
|
||||
},
|
||||
{
|
||||
name: 'Skype',
|
||||
value: 'skype',
|
||||
},
|
||||
{
|
||||
name: 'Twitter',
|
||||
value: 'twitter',
|
||||
},
|
||||
{
|
||||
name: 'URL',
|
||||
value: 'url',
|
||||
},
|
||||
{
|
||||
name: 'Xing',
|
||||
value: 'xing',
|
||||
},
|
||||
{
|
||||
name: 'YouTube',
|
||||
value: 'youtube',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Website URL',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Custom Properties',
|
||||
name: 'customProperties',
|
||||
type: 'fixedCollection',
|
||||
description: 'Custom Properties',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Property',
|
||||
name: 'customProperty',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Property name.',
|
||||
},
|
||||
{
|
||||
displayName: 'Sub Type',
|
||||
name: 'subtype',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Property sub type.',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Property value.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
name: 'contactId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Id of contact to delete.',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
name: 'contactId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Unique identifier for a particular contact',
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: ' Additional Fields',
|
||||
name: 'additionalFieldsJson',
|
||||
type: 'json',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
jsonParameters: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
description: `Object of values to set as described <a href="https://github.com/agilecrm/rest-api#1-contacts---companies-api" target="_blank">here</a>.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Address',
|
||||
name: 'addressOptions',
|
||||
type: 'fixedCollection',
|
||||
description: 'Contacts address.',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Address Properties',
|
||||
name: 'addressProperties',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'subtype',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Type of address.',
|
||||
options: [
|
||||
{
|
||||
name: 'Home',
|
||||
value: 'home'
|
||||
},
|
||||
{
|
||||
name: 'Office',
|
||||
value: 'office'
|
||||
},
|
||||
{
|
||||
name: 'Postal',
|
||||
value: 'postal'
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Address',
|
||||
name: 'address',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Full address.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Company',
|
||||
name: 'company',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Company Name.',
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'emailOptions',
|
||||
type: 'fixedCollection',
|
||||
description: 'Contact email.',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Email Properties',
|
||||
name: 'emailProperties',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'subtype',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Type of Email',
|
||||
options: [
|
||||
{
|
||||
name: 'Work',
|
||||
value: 'work',
|
||||
},
|
||||
{
|
||||
name: 'Personal',
|
||||
value: 'personal',
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Email',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'First Name',
|
||||
name: 'firstName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Contact first name.',
|
||||
},
|
||||
{
|
||||
displayName: 'Last Name',
|
||||
name: 'lastName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Contact last name.',
|
||||
},
|
||||
{
|
||||
displayName: 'Lead Score',
|
||||
name: 'leadScore',
|
||||
type: 'number',
|
||||
default: '',
|
||||
description: 'Lead score of contact',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: 'Star Value',
|
||||
name: 'starValue',
|
||||
type: 'options',
|
||||
default: '',
|
||||
description: 'Rating of contact (Max value 5). This is not applicable for companies.',
|
||||
options: [
|
||||
{
|
||||
name: '0',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
name: '1',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
name: '2',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
name: '3',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
name: '4',
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
name: '5',
|
||||
value: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Phone',
|
||||
name: 'phoneOptions',
|
||||
type: 'fixedCollection',
|
||||
description: 'Contacts phone.',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Phone properties',
|
||||
name: 'phoneProperties',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'subtype',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Type of phone number.',
|
||||
options: [
|
||||
{
|
||||
name: 'Home',
|
||||
value: 'home',
|
||||
},
|
||||
{
|
||||
name: 'Home Fax',
|
||||
value: 'homeFax',
|
||||
},
|
||||
{
|
||||
name: 'Main',
|
||||
value: 'main',
|
||||
},
|
||||
{
|
||||
name: 'Mobile',
|
||||
value: 'mobile',
|
||||
},
|
||||
{
|
||||
name: 'Other',
|
||||
value: 'other',
|
||||
},
|
||||
{
|
||||
name: 'Work Fax',
|
||||
value: 'workFax',
|
||||
},
|
||||
{
|
||||
name: 'Work',
|
||||
value: 'work',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Number',
|
||||
name: 'number',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Phone number.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Tags',
|
||||
name: 'tags',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
multipleValueButtonText: 'Add Tag',
|
||||
},
|
||||
default: [],
|
||||
description: 'Unique identifiers added to contact, for easy management of contacts. This is not applicable for companies.',
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Professional title.',
|
||||
},
|
||||
{
|
||||
displayName: 'Website',
|
||||
name: 'websiteOptions',
|
||||
type: 'fixedCollection',
|
||||
description: 'Contacts websites.',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Website properties.',
|
||||
name: 'websiteProperties',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'subtype',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Type of website.',
|
||||
options: [
|
||||
{
|
||||
name: 'Facebook',
|
||||
value: 'facebook',
|
||||
},
|
||||
{
|
||||
name: 'Feed',
|
||||
value: 'feed',
|
||||
},
|
||||
{
|
||||
name: 'Flickr',
|
||||
value: 'flickr',
|
||||
},
|
||||
{
|
||||
name: 'Github',
|
||||
value: 'github',
|
||||
},
|
||||
{
|
||||
name: 'Google Plus',
|
||||
value: 'googlePlus',
|
||||
},
|
||||
{
|
||||
name: 'LinkedIn',
|
||||
value: 'linkedin',
|
||||
},
|
||||
{
|
||||
name: 'Skype',
|
||||
value: 'skype',
|
||||
},
|
||||
{
|
||||
name: 'Twitter',
|
||||
value: 'twitter',
|
||||
},
|
||||
{
|
||||
name: 'URL',
|
||||
value: 'url',
|
||||
},
|
||||
{
|
||||
name: 'Xing',
|
||||
value: 'xing',
|
||||
},
|
||||
{
|
||||
name: 'YouTube',
|
||||
value: 'youtube',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Website URL',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Custom Properties',
|
||||
name: 'customProperties',
|
||||
type: 'fixedCollection',
|
||||
description: 'Custom Properties',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Property',
|
||||
name: 'customProperty',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Property name.'
|
||||
},
|
||||
{
|
||||
displayName: 'Sub Type',
|
||||
name: 'subtype',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Property sub type.',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Property value.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
26
packages/nodes-base/nodes/AgileCrm/ContactInterface.ts
Normal file
26
packages/nodes-base/nodes/AgileCrm/ContactInterface.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export interface IProperty {
|
||||
type: string;
|
||||
name: string;
|
||||
subtype?: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface IContact {
|
||||
type?: string;
|
||||
star_value?: string;
|
||||
lead_score?: string;
|
||||
tags?: string[];
|
||||
properties?: IDataObject[];
|
||||
}
|
||||
|
||||
export interface IContactUpdate {
|
||||
id: string;
|
||||
properties?: IDataObject[];
|
||||
star_value?: string;
|
||||
lead_score?: string;
|
||||
tags?: string[];
|
||||
}
|
515
packages/nodes-base/nodes/AgileCrm/DealDescription.ts
Normal file
515
packages/nodes-base/nodes/AgileCrm/DealDescription.ts
Normal file
@ -0,0 +1,515 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const dealOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a new deal',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a deal',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a deal',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all deals',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update deal properties',
|
||||
},
|
||||
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const dealFields = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* deal:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Deal ID',
|
||||
name: 'dealId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Unique identifier for a particular deal',
|
||||
},
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* deal:get all */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 20,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* deal:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Close Date',
|
||||
name: 'closeDate',
|
||||
type: 'dateTime',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Closing date of deal.',
|
||||
},
|
||||
{
|
||||
displayName: 'Expected Value',
|
||||
name: 'expectedValue',
|
||||
type: 'number',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
maxValue: 1000000000000
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 1,
|
||||
description: 'Expected Value of deal.',
|
||||
},
|
||||
{
|
||||
displayName: 'Milestone',
|
||||
name: 'milestone',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Milestone of deal.',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Name of deal.',
|
||||
},
|
||||
{
|
||||
displayName: 'Probability',
|
||||
name: 'probability',
|
||||
type: 'number',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
maxValue: 100
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 50,
|
||||
description: 'Expected probability.',
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: ' Additional Fields',
|
||||
name: 'additionalFieldsJson',
|
||||
type: 'json',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
jsonParameters: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
description: `Object of values to set as described <a href="https://github.com/agilecrm/rest-api#1-deals---companies-api" target="_blank">here</a>.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Contact Ids',
|
||||
name: 'contactIds',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
multipleValueButtonText: 'Add ID',
|
||||
},
|
||||
default: [],
|
||||
description: 'Unique contact identifiers.',
|
||||
},
|
||||
{
|
||||
displayName: 'Custom Data',
|
||||
name: 'customData',
|
||||
type: 'fixedCollection',
|
||||
description: 'Custom Data',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Property',
|
||||
name: 'customProperty',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Property name.'
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Property value.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* deal:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Deal ID',
|
||||
name: 'dealId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'ID of deal to delete',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* deal:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Deal ID',
|
||||
name: 'dealId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Id of deal to update',
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFieldsJson',
|
||||
type: 'json',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
jsonParameters: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
description: `Object of values to set as described <a href="https://github.com/agilecrm/rest-api#1-deals---companies-api" target="_blank">here</a>.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Expected Value',
|
||||
name: 'expectedValue',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
maxValue: 10000
|
||||
},
|
||||
default: '',
|
||||
description: 'Expected Value of deal.',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of deal.',
|
||||
},
|
||||
{
|
||||
displayName: 'Probability',
|
||||
name: 'probability',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
maxValue: 100
|
||||
},
|
||||
default: 50,
|
||||
description: 'Expected Value of deal.',
|
||||
},
|
||||
{
|
||||
displayName: 'Contact Ids',
|
||||
name: 'contactIds',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
multipleValueButtonText: 'Add ID',
|
||||
},
|
||||
default: [],
|
||||
description: 'Unique contact identifiers.',
|
||||
},
|
||||
{
|
||||
displayName: 'Custom Data',
|
||||
name: 'customData',
|
||||
type: 'fixedCollection',
|
||||
description: 'Custom Data',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Property',
|
||||
name: 'customProperty',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Property name.'
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Property value.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
] as INodeProperties[];
|
15
packages/nodes-base/nodes/AgileCrm/DealInterface.ts
Normal file
15
packages/nodes-base/nodes/AgileCrm/DealInterface.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export interface IDealCustomProperty {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface IDeal {
|
||||
id?: number;
|
||||
expected_value?: number;
|
||||
probability?: number;
|
||||
name?: string;
|
||||
close_date?: number;
|
||||
milestone?: string;
|
||||
contactIds?: string[];
|
||||
customData?: IDealCustomProperty[];
|
||||
}
|
132
packages/nodes-base/nodes/AgileCrm/GenericFunctions.ts
Normal file
132
packages/nodes-base/nodes/AgileCrm/GenericFunctions.ts
Normal file
@ -0,0 +1,132 @@
|
||||
import {
|
||||
OptionsWithUri
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
import { IContactUpdate } from './ContactInterface';
|
||||
|
||||
|
||||
export async function agileCrmApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const credentials = this.getCredentials('agileCrmApi');
|
||||
const options: OptionsWithUri = {
|
||||
method,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
auth: {
|
||||
username: credentials!.email as string,
|
||||
password: credentials!.apiKey as string
|
||||
},
|
||||
uri: uri || `https://n8nio.agilecrm.com/dev/${endpoint}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
// Only add Body property if method not GET or DELETE to avoid 400 response
|
||||
if (method !== 'GET' && method !== 'DELETE') {
|
||||
options.body = body;
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
throw new Error(`AgileCRM error response: ${error.message}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export async function agileCrmApiRequestUpdate(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method = 'PUT', endpoint?: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
const baseUri = 'https://n8nio.agilecrm.com/dev/';
|
||||
const credentials = this.getCredentials('agileCrmApi');
|
||||
const options: OptionsWithUri = {
|
||||
method,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: { id: body.id },
|
||||
auth: {
|
||||
username: credentials!.email as string,
|
||||
password: credentials!.apiKey as string,
|
||||
},
|
||||
uri: uri || baseUri,
|
||||
json: true,
|
||||
};
|
||||
|
||||
const successfulUpdates = [];
|
||||
let lastSuccesfulUpdateReturn: any; // tslint:disable-line:no-any
|
||||
const payload: IContactUpdate = body;
|
||||
|
||||
try {
|
||||
// Due to API, we must update each property separately. For user it looks like one seamless update
|
||||
if (payload.properties) {
|
||||
options.body.properties = payload.properties;
|
||||
options.uri = baseUri + 'api/contacts/edit-properties';
|
||||
lastSuccesfulUpdateReturn = await this.helpers.request!(options);
|
||||
|
||||
// Iterate trough properties and show them as individial updates instead of only vague "properties"
|
||||
payload.properties?.map((property: any) => { // tslint:disable-line:no-any
|
||||
successfulUpdates.push(`${property.name}`);
|
||||
});
|
||||
|
||||
delete options.body.properties;
|
||||
}
|
||||
if (payload.lead_score) {
|
||||
options.body.lead_score = payload.lead_score;
|
||||
options.uri = baseUri + 'api/contacts/edit/lead-score';
|
||||
lastSuccesfulUpdateReturn = await this.helpers.request!(options);
|
||||
|
||||
successfulUpdates.push('lead_score');
|
||||
|
||||
delete options.body.lead_score;
|
||||
}
|
||||
if (body.tags) {
|
||||
options.body.tags = payload.tags;
|
||||
options.uri = baseUri + 'api/contacts/edit/tags';
|
||||
lastSuccesfulUpdateReturn = await this.helpers.request!(options);
|
||||
|
||||
payload.tags?.map((tag: string) => {
|
||||
successfulUpdates.push(`(Tag) ${tag}`);
|
||||
});
|
||||
|
||||
delete options.body.tags;
|
||||
}
|
||||
if (body.star_value) {
|
||||
options.body.star_value = payload.star_value;
|
||||
options.uri = baseUri + 'api/contacts/edit/add-star';
|
||||
lastSuccesfulUpdateReturn = await this.helpers.request!(options);
|
||||
|
||||
successfulUpdates.push('star_value');
|
||||
|
||||
delete options.body.star_value;
|
||||
}
|
||||
|
||||
return lastSuccesfulUpdateReturn;
|
||||
|
||||
} catch (error) {
|
||||
if (successfulUpdates.length === 0) {
|
||||
throw new Error(`AgileCRM error response: ${error.message}`);
|
||||
} else {
|
||||
throw new Error(`Not all properties updated. Updated properties: ${successfulUpdates.join(', ')} \n \nAgileCRM error response: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any
|
||||
let result;
|
||||
try {
|
||||
result = JSON.parse(json!);
|
||||
} catch (exception) {
|
||||
result = undefined;
|
||||
}
|
||||
return result;
|
||||
}
|
BIN
packages/nodes-base/nodes/AgileCrm/agilecrm.png
Normal file
BIN
packages/nodes-base/nodes/AgileCrm/agilecrm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
@ -25,7 +25,7 @@ export class AsanaTrigger implements INodeType {
|
||||
version: 1,
|
||||
description: 'Starts the workflow when Asana events occure.',
|
||||
defaults: {
|
||||
name: 'Asana Trigger',
|
||||
name: 'Asana-Trigger',
|
||||
color: '#559922',
|
||||
},
|
||||
inputs: [],
|
||||
@ -97,7 +97,11 @@ export class AsanaTrigger implements INodeType {
|
||||
return true;
|
||||
},
|
||||
async create(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||
const webhookUrl = this.getNodeWebhookUrl('default') as string;
|
||||
|
||||
if (webhookUrl.includes('%20')) {
|
||||
throw new Error('The name of the Asana Trigger Node is not allowed to contain any spaces!');
|
||||
}
|
||||
|
||||
const resource = this.getNodeParameter('resource') as string;
|
||||
|
||||
|
@ -46,7 +46,6 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise<any> { // tslint:disable-line:no-any
|
||||
const response = await awsApiRequest.call(this, service, method, path, body, headers);
|
||||
try {
|
||||
@ -56,7 +55,6 @@ export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string, headers?: object): Promise<any> { // tslint:disable-line:no-any
|
||||
const response = await awsApiRequest.call(this, service, method, path, body, headers);
|
||||
try {
|
||||
|
632
packages/nodes-base/nodes/Aws/S3/AwsS3.node.ts
Normal file
632
packages/nodes-base/nodes/Aws/S3/AwsS3.node.ts
Normal file
@ -0,0 +1,632 @@
|
||||
|
||||
import {
|
||||
snakeCase,
|
||||
paramCase,
|
||||
} from 'change-case';
|
||||
|
||||
import {
|
||||
createHash,
|
||||
} from 'crypto';
|
||||
|
||||
import {
|
||||
Builder,
|
||||
} from 'xml2js';
|
||||
|
||||
import {
|
||||
BINARY_ENCODING,
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IBinaryKeyData,
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
bucketFields,
|
||||
bucketOperations,
|
||||
} from './BucketDescription';
|
||||
|
||||
import {
|
||||
folderFields,
|
||||
folderOperations,
|
||||
} from './FolderDescription';
|
||||
|
||||
import {
|
||||
fileFields,
|
||||
fileOperations,
|
||||
} from './FileDescription';
|
||||
|
||||
import {
|
||||
awsApiRequestREST,
|
||||
awsApiRequestSOAP,
|
||||
awsApiRequestSOAPAllItems,
|
||||
} from './GenericFunctions';
|
||||
|
||||
export class AwsS3 implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'AWS S3',
|
||||
name: 'awsS3',
|
||||
icon: 'file:s3.png',
|
||||
group: ['output'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Sends data to AWS S3',
|
||||
defaults: {
|
||||
name: 'AWS S3',
|
||||
color: '#d05b4b',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'aws',
|
||||
required: true,
|
||||
}
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Bucket',
|
||||
value: 'bucket',
|
||||
},
|
||||
{
|
||||
name: 'File',
|
||||
value: 'file',
|
||||
},
|
||||
{
|
||||
name: 'Folder',
|
||||
value: 'folder',
|
||||
},
|
||||
],
|
||||
default: 'file',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
// BUCKET
|
||||
...bucketOperations,
|
||||
...bucketFields,
|
||||
// FOLDER
|
||||
...folderOperations,
|
||||
...folderFields,
|
||||
// UPLOAD
|
||||
...fileOperations,
|
||||
...fileFields,
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
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++) {
|
||||
if (resource === 'bucket') {
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html
|
||||
if (operation === 'create') {
|
||||
const credentials = this.getCredentials('aws');
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
if (additionalFields.acl) {
|
||||
headers['x-amz-acl'] = paramCase(additionalFields.acl as string);
|
||||
}
|
||||
if (additionalFields.bucketObjectLockEnabled) {
|
||||
headers['x-amz-bucket-object-lock-enabled'] = additionalFields.bucketObjectLockEnabled as boolean;
|
||||
}
|
||||
if (additionalFields.grantFullControl) {
|
||||
headers['x-amz-grant-full-control'] = '';
|
||||
}
|
||||
if (additionalFields.grantRead) {
|
||||
headers['x-amz-grant-read'] = '';
|
||||
}
|
||||
if (additionalFields.grantReadAcp) {
|
||||
headers['x-amz-grant-read-acp'] = '';
|
||||
}
|
||||
if (additionalFields.grantWrite) {
|
||||
headers['x-amz-grant-write'] = '';
|
||||
}
|
||||
if (additionalFields.grantWriteAcp) {
|
||||
headers['x-amz-grant-write-acp'] = '';
|
||||
}
|
||||
let region = credentials!.region as string;
|
||||
|
||||
if (additionalFields.region) {
|
||||
region = additionalFields.region as string;
|
||||
}
|
||||
|
||||
const body: IDataObject = {
|
||||
CreateBucketConfiguration: {
|
||||
'$': {
|
||||
xmlns: 'http://s3.amazonaws.com/doc/2006-03-01/',
|
||||
},
|
||||
}
|
||||
};
|
||||
let data = '';
|
||||
// if credentials has the S3 defaul region (us-east-1) the body (XML) does not have to be sent.
|
||||
if (region !== 'us-east-1') {
|
||||
// @ts-ignore
|
||||
body.CreateBucketConfiguration.LocationConstraint = [region];
|
||||
const builder = new Builder();
|
||||
data = builder.buildObject(body);
|
||||
}
|
||||
responseData = await awsApiRequestSOAP.call(this, `${name}.s3`, 'PUT', '', data, qs, headers);
|
||||
|
||||
returnData.push({ success: true });
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListBuckets.html
|
||||
if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', 0) as boolean;
|
||||
if (returnAll) {
|
||||
responseData = await awsApiRequestSOAPAllItems.call(this, 'ListAllMyBucketsResult.Buckets.Bucket', 's3', 'GET', '');
|
||||
} else {
|
||||
qs.limit = this.getNodeParameter('limit', 0) as number;
|
||||
responseData = await awsApiRequestSOAPAllItems.call(this, 'ListAllMyBucketsResult.Buckets.Bucket', 's3', 'GET', '', '', qs);
|
||||
responseData = responseData.slice(0, qs.limit);
|
||||
}
|
||||
returnData.push.apply(returnData, responseData);
|
||||
}
|
||||
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html
|
||||
if (operation === 'search') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', 0) as boolean;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', 0) as IDataObject;
|
||||
|
||||
if (additionalFields.prefix) {
|
||||
qs['prefix'] = additionalFields.prefix as string;
|
||||
}
|
||||
|
||||
if (additionalFields.encodingType) {
|
||||
qs['encoding-type'] = additionalFields.encodingType as string;
|
||||
}
|
||||
|
||||
if (additionalFields.delmiter) {
|
||||
qs['delimiter'] = additionalFields.delmiter as string;
|
||||
}
|
||||
|
||||
if (additionalFields.fetchOwner) {
|
||||
qs['fetch-owner'] = additionalFields.fetchOwner as string;
|
||||
}
|
||||
|
||||
if (additionalFields.startAfter) {
|
||||
qs['start-after'] = additionalFields.startAfter as string;
|
||||
}
|
||||
|
||||
if (additionalFields.requesterPays) {
|
||||
qs['x-amz-request-payer'] = 'requester';
|
||||
}
|
||||
|
||||
qs['list-type'] = 2;
|
||||
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', { location: '' });
|
||||
|
||||
const region = responseData.LocationConstraint._ as string;
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await awsApiRequestSOAPAllItems.call(this, 'ListBucketResult.Contents', `${bucketName}.s3`, 'GET', '', '', qs, {}, {}, region);
|
||||
} else {
|
||||
qs['max-keys'] = this.getNodeParameter('limit', 0) as number;
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', qs, {}, {}, region);
|
||||
responseData = responseData.ListBucketResult.Contents;
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData);
|
||||
} else {
|
||||
returnData.push(responseData);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource === 'folder') {
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html
|
||||
if (operation === 'create') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const folderName = this.getNodeParameter('folderName', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
let path = `/${folderName}/`;
|
||||
|
||||
if (additionalFields.requesterPays) {
|
||||
headers['x-amz-request-payer'] = 'requester';
|
||||
}
|
||||
if (additionalFields.parentFolderKey) {
|
||||
path = `/${additionalFields.parentFolderKey}${folderName}/`;
|
||||
}
|
||||
if (additionalFields.storageClass) {
|
||||
headers['x-amz-storage-class'] = (snakeCase(additionalFields.storageClass as string)).toUpperCase();
|
||||
}
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', { location: '' });
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'PUT', path, '', qs, headers, {}, region);
|
||||
returnData.push({ success: true });
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html
|
||||
if (operation === 'delete') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const folderKey = this.getNodeParameter('folderKey', i) as string;
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', { location: '' });
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
responseData = await awsApiRequestSOAPAllItems.call(this, 'ListBucketResult.Contents', `${bucketName}.s3`, 'GET', '/', '', { 'list-type': 2, prefix: folderKey }, {}, {}, region);
|
||||
|
||||
// folder empty then just delete it
|
||||
if (responseData.length === 0) {
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'DELETE', `/${folderKey}`, '', qs, {}, {}, region);
|
||||
|
||||
responseData = { deleted: [ { 'Key': folderKey } ] };
|
||||
|
||||
} else {
|
||||
// delete everything inside the folder
|
||||
const body: IDataObject = {
|
||||
Delete: {
|
||||
'$': {
|
||||
xmlns: 'http://s3.amazonaws.com/doc/2006-03-01/',
|
||||
},
|
||||
Object: [],
|
||||
},
|
||||
};
|
||||
|
||||
for (const childObject of responseData) {
|
||||
//@ts-ignore
|
||||
(body.Delete.Object as IDataObject[]).push({
|
||||
Key: childObject.Key as string
|
||||
});
|
||||
}
|
||||
|
||||
const builder = new Builder();
|
||||
const data = builder.buildObject(body);
|
||||
|
||||
headers['Content-MD5'] = createHash('md5').update(data).digest('base64');
|
||||
|
||||
headers['Content-Type'] = 'application/xml';
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'POST', '/', data, { delete: '' } , headers, {}, region);
|
||||
|
||||
responseData = { deleted: responseData.DeleteResult.Deleted };
|
||||
}
|
||||
returnData.push(responseData);
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html
|
||||
if (operation === 'getAll') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', 0) as boolean;
|
||||
const options = this.getNodeParameter('options', 0) as IDataObject;
|
||||
|
||||
if (options.folderKey) {
|
||||
qs['prefix'] = options.folderKey as string;
|
||||
}
|
||||
|
||||
if (options.fetchOwner) {
|
||||
qs['fetch-owner'] = options.fetchOwner as string;
|
||||
}
|
||||
|
||||
qs['list-type'] = 2;
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', { location: '' });
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await awsApiRequestSOAPAllItems.call(this, 'ListBucketResult.Contents', `${bucketName}.s3`, 'GET', '', '', qs, {}, {}, region);
|
||||
} else {
|
||||
qs.limit = this.getNodeParameter('limit', 0) as number;
|
||||
responseData = await awsApiRequestSOAPAllItems.call(this, 'ListBucketResult.Contents', `${bucketName}.s3`, 'GET', '', '', qs, {}, {}, region);
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
responseData = responseData.filter((e: IDataObject) => (e.Key as string).endsWith('/') && e.Size === '0' && e.Key !== options.folderKey);
|
||||
if (qs.limit) {
|
||||
responseData = responseData.splice(0, qs.limit as number);
|
||||
}
|
||||
returnData.push.apply(returnData, responseData);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource === 'file') {
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html
|
||||
if (operation === 'copy') {
|
||||
const sourcePath = this.getNodeParameter('sourcePath', i) as string;
|
||||
const destinationPath = this.getNodeParameter('destinationPath', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
headers['x-amz-copy-source'] = sourcePath;
|
||||
|
||||
if (additionalFields.requesterPays) {
|
||||
headers['x-amz-request-payer'] = 'requester';
|
||||
}
|
||||
if (additionalFields.storageClass) {
|
||||
headers['x-amz-storage-class'] = (snakeCase(additionalFields.storageClass as string)).toUpperCase();
|
||||
}
|
||||
if (additionalFields.acl) {
|
||||
headers['x-amz-acl'] = paramCase(additionalFields.acl as string);
|
||||
}
|
||||
if (additionalFields.grantFullControl) {
|
||||
headers['x-amz-grant-full-control'] = '';
|
||||
}
|
||||
if (additionalFields.grantRead) {
|
||||
headers['x-amz-grant-read'] = '';
|
||||
}
|
||||
if (additionalFields.grantReadAcp) {
|
||||
headers['x-amz-grant-read-acp'] = '';
|
||||
}
|
||||
if (additionalFields.grantWriteAcp) {
|
||||
headers['x-amz-grant-write-acp'] = '';
|
||||
}
|
||||
if (additionalFields.lockLegalHold) {
|
||||
headers['x-amz-object-lock-legal-hold'] = (additionalFields.lockLegalHold as boolean) ? 'ON' : 'OFF';
|
||||
}
|
||||
if (additionalFields.lockMode) {
|
||||
headers['x-amz-object-lock-mode'] = (additionalFields.lockMode as string).toUpperCase();
|
||||
}
|
||||
if (additionalFields.lockRetainUntilDate) {
|
||||
headers['x-amz-object-lock-retain-until-date'] = additionalFields.lockRetainUntilDate as string;
|
||||
}
|
||||
if (additionalFields.serverSideEncryption) {
|
||||
headers['x-amz-server-side-encryption'] = additionalFields.serverSideEncryption as string;
|
||||
}
|
||||
if (additionalFields.encryptionAwsKmsKeyId) {
|
||||
headers['x-amz-server-side-encryption-aws-kms-key-id'] = additionalFields.encryptionAwsKmsKeyId as string;
|
||||
}
|
||||
if (additionalFields.serverSideEncryptionContext) {
|
||||
headers['x-amz-server-side-encryption-context'] = additionalFields.serverSideEncryptionContext as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerAlgorithm) {
|
||||
headers['x-amz-server-side-encryption-customer-algorithm'] = additionalFields.serversideEncryptionCustomerAlgorithm as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerKey) {
|
||||
headers['x-amz-server-side-encryption-customer-key'] = additionalFields.serversideEncryptionCustomerKey as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerKeyMD5) {
|
||||
headers['x-amz-server-side-encryption-customer-key-MD5'] = additionalFields.serversideEncryptionCustomerKeyMD5 as string;
|
||||
}
|
||||
if (additionalFields.taggingDirective) {
|
||||
headers['x-amz-tagging-directive'] = (additionalFields.taggingDirective as string).toUpperCase();
|
||||
}
|
||||
if (additionalFields.metadataDirective) {
|
||||
headers['x-amz-metadata-directive'] = (additionalFields.metadataDirective as string).toUpperCase();
|
||||
}
|
||||
|
||||
const destinationParts = destinationPath.split('/');
|
||||
|
||||
const bucketName = destinationParts[1];
|
||||
|
||||
const destination = `/${destinationParts.slice(2, destinationParts.length).join('/')}`;
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', { location: '' });
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'PUT', destination, '', qs, headers, {}, region);
|
||||
returnData.push(responseData.CopyObjectResult);
|
||||
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html
|
||||
if (operation === 'download') {
|
||||
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
|
||||
const fileKey = this.getNodeParameter('fileKey', i) as string;
|
||||
|
||||
const fileName = fileKey.split('/')[fileKey.split('/').length - 1];
|
||||
|
||||
if (fileKey.substring(fileKey.length - 1) === '/') {
|
||||
throw new Error('Downloding a whole directory is not yet supported, please provide a file key');
|
||||
}
|
||||
|
||||
let region = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', { location: '' });
|
||||
|
||||
region = region.LocationConstraint._;
|
||||
|
||||
const response = await awsApiRequestREST.call(this, `${bucketName}.s3`, 'GET', `/${fileKey}`, '', qs, {}, { encoding: null, resolveWithFullResponse: true }, region);
|
||||
|
||||
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 dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i) as string;
|
||||
|
||||
const data = Buffer.from(response.body as string, 'utf8');
|
||||
|
||||
items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(data as unknown as Buffer, fileName, mimeType);
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html
|
||||
if (operation === 'delete') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
|
||||
const fileKey = this.getNodeParameter('fileKey', i) as string;
|
||||
|
||||
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||
|
||||
if (options.versionId) {
|
||||
qs.versionId = options.versionId as string;
|
||||
}
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', { location: '' });
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'DELETE', `/${fileKey}`, '', qs, {}, {}, region);
|
||||
|
||||
returnData.push({ success: true });
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html
|
||||
if (operation === 'getAll') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', 0) as boolean;
|
||||
const options = this.getNodeParameter('options', 0) as IDataObject;
|
||||
|
||||
if (options.folderKey) {
|
||||
qs['prefix'] = options.folderKey as string;
|
||||
}
|
||||
|
||||
if (options.fetchOwner) {
|
||||
qs['fetch-owner'] = options.fetchOwner as string;
|
||||
}
|
||||
|
||||
qs['delimiter'] = '/';
|
||||
|
||||
qs['list-type'] = 2;
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', { location: '' });
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await awsApiRequestSOAPAllItems.call(this, 'ListBucketResult.Contents', `${bucketName}.s3`, 'GET', '', '', qs, {}, {}, region);
|
||||
} else {
|
||||
qs.limit = this.getNodeParameter('limit', 0) as number;
|
||||
responseData = await awsApiRequestSOAPAllItems.call(this, 'ListBucketResult.Contents', `${bucketName}.s3`, 'GET', '', '', qs, {}, {}, region);
|
||||
responseData = responseData.splice(0, qs.limit);
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
responseData = responseData.filter((e: IDataObject) => !(e.Key as string).endsWith('/') && e.Size !== '0');
|
||||
if (qs.limit) {
|
||||
responseData = responseData.splice(0, qs.limit as number);
|
||||
}
|
||||
returnData.push.apply(returnData, responseData);
|
||||
}
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html
|
||||
if (operation === 'upload') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const fileName = this.getNodeParameter('fileName', i) as string;
|
||||
const isBinaryData = this.getNodeParameter('binaryData', i) as boolean;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const tagsValues = (this.getNodeParameter('tagsUi', i) as IDataObject).tagsValues as IDataObject[];
|
||||
let path = '/';
|
||||
let body;
|
||||
|
||||
if (additionalFields.requesterPays) {
|
||||
headers['x-amz-request-payer'] = 'requester';
|
||||
}
|
||||
|
||||
if (additionalFields.parentFolderKey) {
|
||||
path = `/${additionalFields.parentFolderKey}/`;
|
||||
}
|
||||
if (additionalFields.storageClass) {
|
||||
headers['x-amz-storage-class'] = (snakeCase(additionalFields.storageClass as string)).toUpperCase();
|
||||
}
|
||||
if (additionalFields.acl) {
|
||||
headers['x-amz-acl'] = paramCase(additionalFields.acl as string);
|
||||
}
|
||||
if (additionalFields.grantFullControl) {
|
||||
headers['x-amz-grant-full-control'] = '';
|
||||
}
|
||||
if (additionalFields.grantRead) {
|
||||
headers['x-amz-grant-read'] = '';
|
||||
}
|
||||
if (additionalFields.grantReadAcp) {
|
||||
headers['x-amz-grant-read-acp'] = '';
|
||||
}
|
||||
if (additionalFields.grantWriteAcp) {
|
||||
headers['x-amz-grant-write-acp'] = '';
|
||||
}
|
||||
if (additionalFields.lockLegalHold) {
|
||||
headers['x-amz-object-lock-legal-hold'] = (additionalFields.lockLegalHold as boolean) ? 'ON' : 'OFF';
|
||||
}
|
||||
if (additionalFields.lockMode) {
|
||||
headers['x-amz-object-lock-mode'] = (additionalFields.lockMode as string).toUpperCase();
|
||||
}
|
||||
if (additionalFields.lockRetainUntilDate) {
|
||||
headers['x-amz-object-lock-retain-until-date'] = additionalFields.lockRetainUntilDate as string;
|
||||
}
|
||||
if (additionalFields.serverSideEncryption) {
|
||||
headers['x-amz-server-side-encryption'] = additionalFields.serverSideEncryption as string;
|
||||
}
|
||||
if (additionalFields.encryptionAwsKmsKeyId) {
|
||||
headers['x-amz-server-side-encryption-aws-kms-key-id'] = additionalFields.encryptionAwsKmsKeyId as string;
|
||||
}
|
||||
if (additionalFields.serverSideEncryptionContext) {
|
||||
headers['x-amz-server-side-encryption-context'] = additionalFields.serverSideEncryptionContext as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerAlgorithm) {
|
||||
headers['x-amz-server-side-encryption-customer-algorithm'] = additionalFields.serversideEncryptionCustomerAlgorithm as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerKey) {
|
||||
headers['x-amz-server-side-encryption-customer-key'] = additionalFields.serversideEncryptionCustomerKey as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerKeyMD5) {
|
||||
headers['x-amz-server-side-encryption-customer-key-MD5'] = additionalFields.serversideEncryptionCustomerKeyMD5 as string;
|
||||
}
|
||||
if (tagsValues) {
|
||||
const tags: string[] = [];
|
||||
tagsValues.forEach((o: IDataObject) => { tags.push(`${o.key}=${o.value}`); });
|
||||
headers['x-amz-tagging'] = tags.join('&');
|
||||
}
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'GET', '', '', { location: '' });
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
if (isBinaryData) {
|
||||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string;
|
||||
|
||||
if (items[i].binary === undefined) {
|
||||
throw new Error('No binary data exists on item!');
|
||||
}
|
||||
|
||||
if ((items[i].binary as IBinaryKeyData)[binaryPropertyName] === undefined) {
|
||||
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`);
|
||||
}
|
||||
|
||||
const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName];
|
||||
|
||||
body = Buffer.from(binaryData.data, BINARY_ENCODING) as Buffer;
|
||||
|
||||
headers['Content-Type'] = binaryData.mimeType;
|
||||
|
||||
headers['Content-MD5'] = createHash('md5').update(body).digest('base64');
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'PUT', `${path}${fileName || binaryData.fileName}`, body, qs, headers, {}, region);
|
||||
|
||||
} else {
|
||||
|
||||
const fileContent = this.getNodeParameter('fileContent', i) as string;
|
||||
|
||||
body = Buffer.from(fileContent, 'utf8');
|
||||
|
||||
headers['Content-Type'] = 'text/html';
|
||||
|
||||
headers['Content-MD5'] = createHash('md5').update(fileContent).digest('base64');
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${bucketName}.s3`, 'PUT', `${path}${fileName}`, body, qs, headers, {}, region);
|
||||
}
|
||||
returnData.push({ success: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource === 'file' && operation === 'download') {
|
||||
// For file downloads the files get attached to the existing items
|
||||
return this.prepareOutputData(items);
|
||||
} else {
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
||||
}
|
327
packages/nodes-base/nodes/Aws/S3/BucketDescription.ts
Normal file
327
packages/nodes-base/nodes/Aws/S3/BucketDescription.ts
Normal file
@ -0,0 +1,327 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const bucketOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'bucket',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create an bucket',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all buckets',
|
||||
},
|
||||
{
|
||||
name: 'Search',
|
||||
value: 'search',
|
||||
description: 'Search withim a bucket',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const bucketFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* bucket:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'bucket',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'A succinct description of the nature, symptoms, cause, or effect of the bucket.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'bucket',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'ACL',
|
||||
name: 'acl',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Authenticated Read',
|
||||
value: 'authenticatedRead',
|
||||
},
|
||||
{
|
||||
name: 'Private',
|
||||
value: 'Private',
|
||||
},
|
||||
{
|
||||
name: 'Public Read',
|
||||
value: 'publicRead',
|
||||
},
|
||||
{
|
||||
name: 'Public Read Write',
|
||||
value: 'publicReadWrite',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The canned ACL to apply to the bucket.',
|
||||
},
|
||||
{
|
||||
displayName: 'Bucket Object Lock Enabled',
|
||||
name: 'bucketObjectLockEnabled',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Specifies whether you want S3 Object Lock to be enabled for the new bucket.',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Full Control',
|
||||
name: 'grantFullControl',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Allows grantee the read, write, read ACP, and write ACP permissions on the bucket.',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read',
|
||||
name: 'grantRead',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Allows grantee to list the objects in the bucket.',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read ACP',
|
||||
name: 'grantReadAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Allows grantee to read the bucket ACL.',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Write',
|
||||
name: 'grantWrite',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Allows grantee to create, overwrite, and delete any object in the bucket.',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Write ACP',
|
||||
name: 'grantWriteAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Allows grantee to write the ACL for the applicable bucket.',
|
||||
},
|
||||
{
|
||||
displayName: 'Region',
|
||||
name: 'region',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Region you want to create the bucket in, by default the buckets are created on the region defined on the credentials.',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* bucket:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'bucket',
|
||||
],
|
||||
},
|
||||
},
|
||||
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: [
|
||||
'bucket',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* bucket:search */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'bucket',
|
||||
],
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
resource: [
|
||||
'bucket',
|
||||
],
|
||||
},
|
||||
},
|
||||
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: [
|
||||
'search',
|
||||
],
|
||||
resource: [
|
||||
'bucket',
|
||||
],
|
||||
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',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'bucket',
|
||||
],
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Delimiter',
|
||||
name: 'delimiter',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'A delimiter is a character you use to group keys.',
|
||||
},
|
||||
{
|
||||
displayName: 'Encoding Type',
|
||||
name: 'encodingType',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'URL',
|
||||
value: 'url',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'Encoding type used by Amazon S3 to encode object keys in the response.',
|
||||
},
|
||||
{
|
||||
displayName: 'Fetch Owner',
|
||||
name: 'fetchOwner',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'The owner field is not present in listV2 by default, if you want to return owner field with each key in the result then set the fetch owner field to true.',
|
||||
},
|
||||
{
|
||||
displayName: 'Prefix',
|
||||
name: 'prefix',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Limits the response to keys that begin with the specified prefix.',
|
||||
},
|
||||
{
|
||||
displayName: 'Requester Pays',
|
||||
name: 'requesterPays',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Weather the requester will pay for requests and data transfer. While Requester Pays is enabled, anonymous access to this bucket is disabled.',
|
||||
},
|
||||
{
|
||||
displayName: 'Start After',
|
||||
name: 'startAfter',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'StartAfter is where you want Amazon S3 to start listing from. Amazon S3 starts listing after this specified key',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
922
packages/nodes-base/nodes/Aws/S3/FileDescription.ts
Normal file
922
packages/nodes-base/nodes/Aws/S3/FileDescription.ts
Normal file
@ -0,0 +1,922 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const fileOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Copy',
|
||||
value: 'copy',
|
||||
description: 'Copy a file',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a file',
|
||||
},
|
||||
{
|
||||
name: 'Download',
|
||||
value: 'download',
|
||||
description: 'Download a file',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all files',
|
||||
},
|
||||
{
|
||||
name: 'Upload',
|
||||
value: 'upload',
|
||||
description: 'Upload a file',
|
||||
},
|
||||
],
|
||||
default: 'download',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const fileFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:copy */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Source Path',
|
||||
name: 'sourcePath',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
placeholder: '/bucket/my-image.jpg',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
operation: [
|
||||
'copy',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The name of the source bucket and key name of the source object, separated by a slash (/)',
|
||||
},
|
||||
{
|
||||
displayName: 'Destination Path',
|
||||
name: 'destinationPath',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
placeholder: '/bucket/my-second-image.jpg',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
operation: [
|
||||
'copy',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The name of the destination bucket and key name of the destination object, separated by a slash (/)',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
operation: [
|
||||
'copy',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'ACL',
|
||||
name: 'acl',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Authenticated Read',
|
||||
value: 'authenticatedRead'
|
||||
},
|
||||
{
|
||||
name: 'AWS Exec Read',
|
||||
value: 'awsExecRead'
|
||||
},
|
||||
{
|
||||
name: 'Bucket Owner Full Control',
|
||||
value: 'bucketOwnerFullControl'
|
||||
},
|
||||
{
|
||||
name: 'Bucket Owner Read',
|
||||
value: 'bucketOwnerRead'
|
||||
},
|
||||
{
|
||||
name: 'Private',
|
||||
value: 'private',
|
||||
},
|
||||
{
|
||||
name: 'Public Read',
|
||||
value: 'publicRead'
|
||||
},
|
||||
{
|
||||
name: 'Public Read Write',
|
||||
value: 'publicReadWrite'
|
||||
},
|
||||
],
|
||||
default: 'private',
|
||||
description: 'The canned ACL to apply to the object.'
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Full Control',
|
||||
name: 'grantFullControl',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Gives the grantee READ, READ_ACP, and WRITE_ACP permissions on the object.',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read',
|
||||
name: 'grantRead',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Allows grantee to read the object data and its metadata.',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read ACP',
|
||||
name: 'grantReadAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Allows grantee to read the object ACL.',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Write ACP',
|
||||
name: 'grantWriteAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Allows grantee to write the ACL for the applicable object.',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Legal Hold',
|
||||
name: 'lockLegalHold',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Specifies whether a legal hold will be applied to this object',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Mode',
|
||||
name: 'lockMode',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Governance',
|
||||
value: 'governance',
|
||||
},
|
||||
{
|
||||
name: 'Compliance',
|
||||
value: 'compliance',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The Object Lock mode that you want to apply to this object.',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Retain Until Date',
|
||||
name: 'lockRetainUntilDate',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: `The date and time when you want this object's Object Lock to expire.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Metadata Directive',
|
||||
name: 'metadataDirective',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Copy',
|
||||
value: 'copy',
|
||||
},
|
||||
{
|
||||
name: 'Replace',
|
||||
value: 'replace',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'Specifies whether the metadata is copied from the source object or replaced with metadata provided in the request.',
|
||||
},
|
||||
{
|
||||
displayName: 'Requester Pays',
|
||||
name: 'requesterPays',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Weather the requester will pay for requests and data transfer. While Requester Pays is enabled, anonymous access to this bucket is disabled.',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption',
|
||||
name: 'serverSideEncryption',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'AES256',
|
||||
value: 'AES256',
|
||||
},
|
||||
{
|
||||
name: 'AWS:KMS',
|
||||
value: 'aws:kms',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The server-side encryption algorithm used when storing this object in Amazon S3',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Context',
|
||||
name: 'serverSideEncryptionContext',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Specifies the AWS KMS Encryption Context to use for object encryption',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption AWS KMS Key ID',
|
||||
name: 'encryptionAwsKmsKeyId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'If x-amz-server-side-encryption is present and has the value of aws:kms',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Algorithm',
|
||||
name: 'serversideEncryptionCustomerAlgorithm',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Specifies the algorithm to use to when encrypting the object (for example, AES256).',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Key',
|
||||
name: 'serversideEncryptionCustomerKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Specifies the customer-provided encryption key for Amazon S3 to use in encrypting data',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Key MD5',
|
||||
name: 'serversideEncryptionCustomerKeyMD5',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Specifies the 128-bit MD5 digest of the encryption key according to RFC 1321.',
|
||||
},
|
||||
{
|
||||
displayName: 'Storage Class',
|
||||
name: 'storageClass',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Deep Archive',
|
||||
value: 'deepArchive',
|
||||
},
|
||||
{
|
||||
name: 'Intelligent Tiering',
|
||||
value: 'intelligentTiering',
|
||||
},
|
||||
{
|
||||
name: 'One Zone IA',
|
||||
value: 'onezoneIA',
|
||||
},
|
||||
{
|
||||
name: 'Glacier',
|
||||
value: 'glacier',
|
||||
},
|
||||
{
|
||||
name: 'Standard',
|
||||
value: 'standard',
|
||||
},
|
||||
{
|
||||
name: 'Standard IA',
|
||||
value: 'standardIA',
|
||||
},
|
||||
],
|
||||
default: 'standard',
|
||||
description: 'Amazon S3 storage classes.',
|
||||
},
|
||||
{
|
||||
displayName: 'Tagging Directive',
|
||||
name: 'taggingDirective',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Copy',
|
||||
value: 'copy',
|
||||
},
|
||||
{
|
||||
name: 'Replace',
|
||||
value: 'replace',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'Specifies whether the metadata is copied from the source object or replaced with metadata provided in the request.',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:upload */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
operation: [
|
||||
'upload',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'File Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'hello.txt',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
operation: [
|
||||
'upload',
|
||||
],
|
||||
binaryData: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'File Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
operation: [
|
||||
'upload',
|
||||
],
|
||||
binaryData: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'If not set the binary data filename will be used.',
|
||||
},
|
||||
{
|
||||
displayName: 'Binary Data',
|
||||
name: 'binaryData',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'upload'
|
||||
],
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'If the data to upload should be taken from binary field.',
|
||||
},
|
||||
{
|
||||
displayName: 'File Content',
|
||||
name: 'fileContent',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'upload'
|
||||
],
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
binaryData: [
|
||||
false
|
||||
],
|
||||
},
|
||||
},
|
||||
placeholder: '',
|
||||
description: 'The text content of the file to upload.',
|
||||
},
|
||||
{
|
||||
displayName: 'Binary Property',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'upload'
|
||||
],
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
binaryData: [
|
||||
true
|
||||
],
|
||||
},
|
||||
|
||||
},
|
||||
placeholder: '',
|
||||
description: 'Name of the binary property which contains<br />the data for the file to be uploaded.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
operation: [
|
||||
'upload',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'ACL',
|
||||
name: 'acl',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Authenticated Read',
|
||||
value: 'authenticatedRead'
|
||||
},
|
||||
{
|
||||
name: 'AWS Exec Read',
|
||||
value: 'awsExecRead'
|
||||
},
|
||||
{
|
||||
name: 'Bucket Owner Full Control',
|
||||
value: 'bucketOwnerFullControl'
|
||||
},
|
||||
{
|
||||
name: 'Bucket Owner Read',
|
||||
value: 'bucketOwnerRead'
|
||||
},
|
||||
{
|
||||
name: 'Private',
|
||||
value: 'private',
|
||||
},
|
||||
{
|
||||
name: 'Public Read',
|
||||
value: 'publicRead'
|
||||
},
|
||||
{
|
||||
name: 'Public Read Write',
|
||||
value: 'publicReadWrite'
|
||||
},
|
||||
],
|
||||
default: 'private',
|
||||
description: 'The canned ACL to apply to the object.'
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Full Control',
|
||||
name: 'grantFullControl',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Gives the grantee READ, READ_ACP, and WRITE_ACP permissions on the object.',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read',
|
||||
name: 'grantRead',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Allows grantee to read the object data and its metadata.',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Read ACP',
|
||||
name: 'grantReadAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Allows grantee to read the object ACL.',
|
||||
},
|
||||
{
|
||||
displayName: 'Grant Write ACP',
|
||||
name: 'grantWriteAcp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Allows grantee to write the ACL for the applicable object.',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Legal Hold',
|
||||
name: 'lockLegalHold',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Specifies whether a legal hold will be applied to this object',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Mode',
|
||||
name: 'lockMode',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Governance',
|
||||
value: 'governance',
|
||||
},
|
||||
{
|
||||
name: 'Compliance',
|
||||
value: 'compliance',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The Object Lock mode that you want to apply to this object.',
|
||||
},
|
||||
{
|
||||
displayName: 'Lock Retain Until Date',
|
||||
name: 'lockRetainUntilDate',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: `The date and time when you want this object's Object Lock to expire.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Parent Folder Key',
|
||||
name: 'parentFolderKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Parent file you want to create the file in',
|
||||
},
|
||||
{
|
||||
displayName: 'Requester Pays',
|
||||
name: 'requesterPays',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Weather the requester will pay for requests and data transfer. While Requester Pays is enabled, anonymous access to this bucket is disabled.',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption',
|
||||
name: 'serverSideEncryption',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'AES256',
|
||||
value: 'AES256',
|
||||
},
|
||||
{
|
||||
name: 'AWS:KMS',
|
||||
value: 'aws:kms',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The server-side encryption algorithm used when storing this object in Amazon S3',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Context',
|
||||
name: 'serverSideEncryptionContext',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Specifies the AWS KMS Encryption Context to use for object encryption',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption AWS KMS Key ID',
|
||||
name: 'encryptionAwsKmsKeyId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'If x-amz-server-side-encryption is present and has the value of aws:kms',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Algorithm',
|
||||
name: 'serversideEncryptionCustomerAlgorithm',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Specifies the algorithm to use to when encrypting the object (for example, AES256).',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Key',
|
||||
name: 'serversideEncryptionCustomerKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Specifies the customer-provided encryption key for Amazon S3 to use in encrypting data',
|
||||
},
|
||||
{
|
||||
displayName: 'Server Side Encryption Customer Key MD5',
|
||||
name: 'serversideEncryptionCustomerKeyMD5',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Specifies the 128-bit MD5 digest of the encryption key according to RFC 1321.',
|
||||
},
|
||||
{
|
||||
displayName: 'Storage Class',
|
||||
name: 'storageClass',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Deep Archive',
|
||||
value: 'deepArchive',
|
||||
},
|
||||
{
|
||||
name: 'Intelligent Tiering',
|
||||
value: 'intelligentTiering',
|
||||
},
|
||||
{
|
||||
name: 'One Zone IA',
|
||||
value: 'onezoneIA',
|
||||
},
|
||||
{
|
||||
name: 'Glacier',
|
||||
value: 'glacier',
|
||||
},
|
||||
{
|
||||
name: 'Standard',
|
||||
value: 'standard',
|
||||
},
|
||||
{
|
||||
name: 'Standard IA',
|
||||
value: 'standardIA',
|
||||
},
|
||||
],
|
||||
default: 'standard',
|
||||
description: 'Amazon S3 storage classes.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Tags',
|
||||
name: 'tagsUi',
|
||||
placeholder: 'Add Tag',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
operation: [
|
||||
'upload',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'tagsValues',
|
||||
displayName: 'Tag',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: '',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
description: 'Optional extra headers to add to the message (most headers are allowed).',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:download */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
operation: [
|
||||
'download',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'File Key',
|
||||
name: 'fileKey',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
operation: [
|
||||
'download',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Binary Property',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: 'data',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'download'
|
||||
],
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Name of the binary property to which to<br />write the data of the read file.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'File Key',
|
||||
name: 'fileKey',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Version ID',
|
||||
name: 'versionId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* file:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
},
|
||||
},
|
||||
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: [
|
||||
'file',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'file',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Fetch Owner',
|
||||
name: 'fetchOwner',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'The owner field is not present in listV2 by default, if you want to return owner field with each key in the result then set the fetch owner field to true.',
|
||||
},
|
||||
{
|
||||
displayName: 'Folder Key',
|
||||
name: 'folderKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
278
packages/nodes-base/nodes/Aws/S3/FolderDescription.ts
Normal file
278
packages/nodes-base/nodes/Aws/S3/FolderDescription.ts
Normal file
@ -0,0 +1,278 @@
|
||||
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 folder',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a folder',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all folders',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const folderFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Folder Name',
|
||||
name: 'folderName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Parent Folder Key',
|
||||
name: 'parentFolderKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Parent folder you want to create the folder in'
|
||||
},
|
||||
{
|
||||
displayName: 'Requester Pays',
|
||||
name: 'requesterPays',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Weather the requester will pay for requests and data transfer. While Requester Pays is enabled, anonymous access to this bucket is disabled.',
|
||||
},
|
||||
{
|
||||
displayName: 'Storage Class',
|
||||
name: 'storageClass',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Deep Archive',
|
||||
value: 'deepArchive',
|
||||
},
|
||||
{
|
||||
name: 'Intelligent Tiering',
|
||||
value: 'intelligentTiering',
|
||||
},
|
||||
{
|
||||
name: 'One Zone IA',
|
||||
value: 'onezoneIA',
|
||||
},
|
||||
{
|
||||
name: 'Glacier',
|
||||
value: 'glacier',
|
||||
},
|
||||
{
|
||||
name: 'Reduced Redundancy',
|
||||
value: 'RecudedRedundancy',
|
||||
},
|
||||
{
|
||||
name: 'Standard',
|
||||
value: 'standard',
|
||||
},
|
||||
{
|
||||
name: 'Standard IA',
|
||||
value: 'standardIA',
|
||||
},
|
||||
],
|
||||
default: 'standard',
|
||||
description: 'Amazon S3 storage classes.'
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Folder Key',
|
||||
name: 'folderKey',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Bucket Name',
|
||||
name: 'bucketName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
},
|
||||
},
|
||||
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: [
|
||||
'folder',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Fetch Owner',
|
||||
name: 'fetchOwner',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'The owner field is not present in listV2 by default, if you want to return owner field with each key in the result then set the fetch owner field to true.',
|
||||
},
|
||||
{
|
||||
displayName: 'Folder Key',
|
||||
name: 'folderKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
127
packages/nodes-base/nodes/Aws/S3/GenericFunctions.ts
Normal file
127
packages/nodes-base/nodes/Aws/S3/GenericFunctions.ts
Normal file
@ -0,0 +1,127 @@
|
||||
import {
|
||||
sign,
|
||||
} from 'aws4';
|
||||
|
||||
import {
|
||||
get,
|
||||
} from 'lodash';
|
||||
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
parseString,
|
||||
} from 'xml2js';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string | Buffer, query: IDataObject = {}, headers?: object, option: IDataObject = {}, region?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('aws');
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
}
|
||||
|
||||
const endpoint = `${service}.${region || credentials.region}.amazonaws.com`;
|
||||
|
||||
// Sign AWS API request with the user credentials
|
||||
const signOpts = {headers: headers || {}, host: endpoint, method, path: `${path}?${queryToString(query).replace(/\+/g, '%2B')}`, body};
|
||||
|
||||
sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`, secretAccessKey: `${credentials.secretAccessKey}`});
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: signOpts.headers,
|
||||
method,
|
||||
qs: query,
|
||||
uri: `https://${endpoint}${signOpts.path}`,
|
||||
body: signOpts.body,
|
||||
};
|
||||
|
||||
if (Object.keys(option).length !== 0) {
|
||||
Object.assign(options, option);
|
||||
}
|
||||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
const errorMessage = error.response.body.message || error.response.body.Message || error.message;
|
||||
|
||||
if (error.statusCode === 403) {
|
||||
if (errorMessage === 'The security token included in the request is invalid.') {
|
||||
throw new Error('The AWS credentials are not valid!');
|
||||
} else if (errorMessage.startsWith('The request signature we calculated does not match the signature you provided')) {
|
||||
throw new Error('The AWS credentials are not valid!');
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`AWS error response [${error.statusCode}]: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function awsApiRequestREST(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, service: string, method: string, path: string, body?: string, query: IDataObject = {}, headers?: object, options: IDataObject = {}, region?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
const response = await awsApiRequest.call(this, service, method, path, body, query, headers, options, region);
|
||||
try {
|
||||
return JSON.parse(response);
|
||||
} catch (e) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
export async function awsApiRequestSOAP(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string | Buffer, query: IDataObject = {}, headers?: object, option: IDataObject = {}, region?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
const response = await awsApiRequest.call(this, service, method, path, body, query, headers, option, region);
|
||||
try {
|
||||
return await new Promise((resolve, reject) => {
|
||||
parseString(response, { explicitArray: false }, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
export async function awsApiRequestSOAPAllItems(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, propertyName: string, service: string, method: string, path: string, body?: string, query: IDataObject = {}, headers: IDataObject = {}, option: IDataObject = {}, region?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
|
||||
do {
|
||||
responseData = await awsApiRequestSOAP.call(this, service, method, path, body, query, headers, option, region);
|
||||
|
||||
//https://forums.aws.amazon.com/thread.jspa?threadID=55746
|
||||
if (get(responseData, `${propertyName.split('.')[0]}.NextContinuationToken`)) {
|
||||
query['continuation-token'] = get(responseData, `${propertyName.split('.')[0]}.NextContinuationToken`);
|
||||
}
|
||||
if (get(responseData, propertyName)) {
|
||||
if (Array.isArray(get(responseData, propertyName))) {
|
||||
returnData.push.apply(returnData, get(responseData, propertyName));
|
||||
} else {
|
||||
returnData.push(get(responseData, propertyName));
|
||||
}
|
||||
}
|
||||
if (query.limit && query.limit <= returnData.length) {
|
||||
return returnData;
|
||||
}
|
||||
} while (
|
||||
get(responseData, `${propertyName.split('.')[0]}.IsTruncated`) !== undefined &&
|
||||
get(responseData, `${propertyName.split('.')[0]}.IsTruncated`) !== 'false'
|
||||
);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
function queryToString(params: IDataObject) {
|
||||
return Object.keys(params).map(key => key + '=' + params[key]).join('&');
|
||||
}
|
BIN
packages/nodes-base/nodes/Aws/S3/s3.png
Normal file
BIN
packages/nodes-base/nodes/Aws/S3/s3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
199
packages/nodes-base/nodes/Bannerbear/Bannerbear.node.ts
Normal file
199
packages/nodes-base/nodes/Bannerbear/Bannerbear.node.ts
Normal file
@ -0,0 +1,199 @@
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
bannerbearApiRequest,
|
||||
keysToSnakeCase,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
imageFields,
|
||||
imageOperations,
|
||||
} from './ImageDescription';
|
||||
|
||||
import {
|
||||
templateFields,
|
||||
templateOperations,
|
||||
} from './TemplateDescription';
|
||||
|
||||
export class Bannerbear implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Bannerbear',
|
||||
name: 'bannerbear',
|
||||
icon: 'file:bannerbear.png',
|
||||
group: ['output'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume Bannerbear API',
|
||||
defaults: {
|
||||
name: 'Bannerbear',
|
||||
color: '#f9d749',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'bannerbearApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Image',
|
||||
value: 'image',
|
||||
},
|
||||
{
|
||||
name: 'Template',
|
||||
value: 'template',
|
||||
},
|
||||
],
|
||||
default: 'image',
|
||||
description: 'Resource to consume.',
|
||||
},
|
||||
// IMAGE
|
||||
...imageOperations,
|
||||
...imageFields,
|
||||
// TEMPLATE
|
||||
...templateOperations,
|
||||
...templateFields,
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
// Get all the available templates to display them to user so that he can
|
||||
// select them easily
|
||||
async getTemplates(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const templates = await bannerbearApiRequest.call(this, 'GET', '/templates');
|
||||
for (const template of templates) {
|
||||
const templateName = template.name;
|
||||
const templateId = template.uid;
|
||||
returnData.push({
|
||||
name: templateName,
|
||||
value: templateId,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
|
||||
// Get all the available modifications to display them to user so that he can
|
||||
// select them easily
|
||||
async getModificationNames(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const templateId = this.getCurrentNodeParameter('templateId');
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const { available_modifications } = await bannerbearApiRequest.call(this, 'GET', `/templates/${templateId}`);
|
||||
for (const modification of available_modifications) {
|
||||
const modificationName = modification.name;
|
||||
const modificationId = modification.name;
|
||||
returnData.push({
|
||||
name: modificationName,
|
||||
value: modificationId,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const length = items.length as unknown as number;
|
||||
let responseData;
|
||||
const qs: IDataObject = {};
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (resource === 'image') {
|
||||
//https://developers.bannerbear.com/#create-an-image
|
||||
if (operation === 'create') {
|
||||
const templateId = this.getNodeParameter('templateId', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const modifications = (this.getNodeParameter('modificationsUi', i) as IDataObject).modificationsValues as IDataObject;
|
||||
const body: IDataObject = {
|
||||
template: templateId,
|
||||
};
|
||||
if (additionalFields.webhookUrl) {
|
||||
body.webhook_url = additionalFields.webhookUrl as string;
|
||||
}
|
||||
if (additionalFields.metadata) {
|
||||
body.metadata = additionalFields.metadata as string;
|
||||
}
|
||||
if (modifications) {
|
||||
body.modifications = keysToSnakeCase(modifications) as IDataObject[];
|
||||
// delete all fields set to empty
|
||||
for (const modification of body.modifications as IDataObject[]) {
|
||||
for (const key of Object.keys(modification)) {
|
||||
if (modification[key] === '') {
|
||||
delete modification[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
responseData = await bannerbearApiRequest.call(this, 'POST', '/images', body);
|
||||
if (additionalFields.waitForImage && responseData.status !== 'completed') {
|
||||
let maxTries = (additionalFields.waitForImageMaxTries as number) || 3;
|
||||
|
||||
const promise = (uid: string) => {
|
||||
let data: IDataObject = {};
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setInterval(async () => {
|
||||
data = await bannerbearApiRequest.call(this, 'GET', `/images/${uid}`);
|
||||
|
||||
if (data.status === 'completed') {
|
||||
clearInterval(timeout);
|
||||
resolve(data);
|
||||
}
|
||||
if (--maxTries === 0) {
|
||||
clearInterval(timeout);
|
||||
reject(new Error('Image did not finish processing after multiple tries.'));
|
||||
}
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
|
||||
responseData = await promise(responseData.uid);
|
||||
}
|
||||
}
|
||||
//https://developers.bannerbear.com/#get-a-specific-image
|
||||
if (operation === 'get') {
|
||||
const imageId = this.getNodeParameter('imageId', i) as string;
|
||||
responseData = await bannerbearApiRequest.call(this, 'GET', `/images/${imageId}`);
|
||||
}
|
||||
}
|
||||
if (resource === 'template') {
|
||||
//https://developers.bannerbear.com/#get-a-specific-template
|
||||
if (operation === 'get') {
|
||||
const templateId = this.getNodeParameter('templateId', i) as string;
|
||||
responseData = await bannerbearApiRequest.call(this, 'GET', `/templates/${templateId}`);
|
||||
}
|
||||
//https://developers.bannerbear.com/#list-templates
|
||||
if (operation === 'getAll') {
|
||||
responseData = await bannerbearApiRequest.call(this, 'GET', '/templates');
|
||||
}
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else {
|
||||
returnData.push(responseData);
|
||||
}
|
||||
}
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
71
packages/nodes-base/nodes/Bannerbear/GenericFunctions.ts
Normal file
71
packages/nodes-base/nodes/Bannerbear/GenericFunctions.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
IHookFunctions,
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
snakeCase,
|
||||
} from 'change-case';
|
||||
|
||||
export async function bannerbearApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const credentials = this.getCredentials('bannerbearApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
}
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${credentials.apiKey}`,
|
||||
},
|
||||
method,
|
||||
body,
|
||||
qs: query,
|
||||
uri: uri || `https://api.bannerbear.com/v2${resource}`,
|
||||
json: true,
|
||||
};
|
||||
if (!Object.keys(body).length) {
|
||||
delete options.form;
|
||||
}
|
||||
if (!Object.keys(query).length) {
|
||||
delete options.qs;
|
||||
}
|
||||
options.headers = Object.assign({}, options.headers, headers);
|
||||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
if (error.response && error.response.body && error.response.body.message) {
|
||||
// Try to return the error prettier
|
||||
//@ts-ignore
|
||||
throw new Error(`Bannerbear error response [${error.statusCode}]: ${error.response.body.message}`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function keysToSnakeCase(elements: IDataObject[] | IDataObject) : IDataObject[] {
|
||||
if (!Array.isArray(elements)) {
|
||||
elements = [elements];
|
||||
}
|
||||
for (const element of elements) {
|
||||
for (const key of Object.keys(element)) {
|
||||
if (key !== snakeCase(key)) {
|
||||
element[snakeCase(key)] = element[key];
|
||||
delete element[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return elements;
|
||||
}
|
210
packages/nodes-base/nodes/Bannerbear/ImageDescription.ts
Normal file
210
packages/nodes-base/nodes/Bannerbear/ImageDescription.ts
Normal file
@ -0,0 +1,210 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const imageOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'image',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create an image',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get an image',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const imageFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* image:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Template ID',
|
||||
name: 'templateId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTemplates',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'image',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The template id you want to use',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'image',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Metadata',
|
||||
name: 'metadata',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Metadata that you need to store e.g. ID of a record in your DB',
|
||||
},
|
||||
{
|
||||
displayName: 'Wait for Image',
|
||||
name: 'waitForImage',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Wait for the image to be proccesed before returning.<br />
|
||||
If after three tries the images is not ready, an error will be thrown.<br />
|
||||
Number of tries can be increased by setting "Wait Max Tries".`,
|
||||
},
|
||||
{
|
||||
displayName: 'Wait Max Tries',
|
||||
name: 'waitForImageMaxTries',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 10,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
waitForImage: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 3,
|
||||
description: `How often it should check if the image is available before it fails.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Webhook URL',
|
||||
name: 'webhookUrl',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'A url to POST the Image object to upon rendering completed',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Modifications',
|
||||
name: 'modificationsUi',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
placeholder: 'Add Modification',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'image',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Modification',
|
||||
name: 'modificationsValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getModificationNames',
|
||||
loadOptionsDependsOn: [
|
||||
'templateId',
|
||||
],
|
||||
},
|
||||
default: '',
|
||||
description: 'The name of the item you want to change',
|
||||
},
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Replacement text you want to use',
|
||||
},
|
||||
{
|
||||
displayName: 'Color',
|
||||
name: 'color',
|
||||
type: 'color',
|
||||
default: '',
|
||||
description: 'Color hex of object',
|
||||
},
|
||||
{
|
||||
displayName: 'Background',
|
||||
name: 'background',
|
||||
type: 'color',
|
||||
default: '',
|
||||
description: 'Color hex of text background',
|
||||
},
|
||||
{
|
||||
displayName: 'Image URL',
|
||||
name: 'imageUrl',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Replacement image url you want to use (must be publicly viewable)',
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* image:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Image ID',
|
||||
name: 'imageId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'image',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'Unique identifier for the image.',
|
||||
},
|
||||
] as INodeProperties[];
|
57
packages/nodes-base/nodes/Bannerbear/TemplateDescription.ts
Normal file
57
packages/nodes-base/nodes/Bannerbear/TemplateDescription.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const templateOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'template',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a template',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all templates',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const templateFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* template:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Template ID',
|
||||
name: 'templateId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'template',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'Unique identifier for the template.',
|
||||
},
|
||||
] as INodeProperties[];
|
BIN
packages/nodes-base/nodes/Bannerbear/bannerbear.png
Normal file
BIN
packages/nodes-base/nodes/Bannerbear/bannerbear.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
152
packages/nodes-base/nodes/ClickUp/ChecklistDescription.ts
Normal file
152
packages/nodes-base/nodes/ClickUp/ChecklistDescription.ts
Normal file
@ -0,0 +1,152 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const checklistOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'checklist',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a checklist',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a checklist',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a checklist',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const checklistFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklist:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Task ID',
|
||||
name: 'task',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'checklist',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'checklist',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklist:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Checklist ID',
|
||||
name: 'checklist',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'checklist',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklist:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Checklist ID',
|
||||
name: 'checklist',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'checklist',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'checklist',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Position',
|
||||
name: 'position',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
},
|
||||
default: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
221
packages/nodes-base/nodes/ClickUp/ChecklistItemDescription.ts
Normal file
221
packages/nodes-base/nodes/ClickUp/ChecklistItemDescription.ts
Normal file
@ -0,0 +1,221 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const checklistItemOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'checklistItem',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a checklist item',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a checklist item',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a checklist item',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const checklistItemFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklistItem:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Checklist ID',
|
||||
name: 'checklist',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'checklistItem',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'checklistItem',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'checklistItem',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Assignee ID',
|
||||
name: 'assignee',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklistItem:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Checklist ID',
|
||||
name: 'checklist',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'checklistItem',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Checklist Item ID',
|
||||
name: 'checklistItem',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'checklistItem',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* checklistItem:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Checklist ID',
|
||||
name: 'checklist',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'checklistItem',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Checklist Item ID',
|
||||
name: 'checklistItem',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'checklistItem',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'checklistItem',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Assignee ID',
|
||||
name: 'assignee',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Parent Checklist Item ID',
|
||||
name: 'parent',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Checklist item that you want to nest the target checklist item underneath.',
|
||||
},
|
||||
{
|
||||
displayName: 'Resolved',
|
||||
name: 'resolved',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
@ -1,6 +1,7 @@
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
@ -9,23 +10,76 @@ import {
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
clickupApiRequest,
|
||||
clickupApiRequestAllItems,
|
||||
validateJSON,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
checklistFields,
|
||||
checklistOperations,
|
||||
} from './ChecklistDescription';
|
||||
|
||||
import {
|
||||
checklistItemFields,
|
||||
checklistItemOperations,
|
||||
} from './ChecklistItemDescription';
|
||||
|
||||
import {
|
||||
commentFields,
|
||||
commentOperations,
|
||||
} from './CommentDescription';
|
||||
|
||||
import {
|
||||
folderFields,
|
||||
folderOperations,
|
||||
} from './FolderDescription';
|
||||
|
||||
import {
|
||||
goalFields,
|
||||
goalOperations,
|
||||
} from './GoalDescription';
|
||||
|
||||
import {
|
||||
goalKeyResultFields,
|
||||
goalKeyResultOperations,
|
||||
} from './GoalKeyResultDescription';
|
||||
|
||||
// import {
|
||||
// guestFields,
|
||||
// guestOperations,
|
||||
// } from './guestDescription';
|
||||
|
||||
import {
|
||||
taskFields,
|
||||
taskOperations,
|
||||
} from './TaskDescription';
|
||||
|
||||
import {
|
||||
taskDependencyFields,
|
||||
taskDependencyOperations,
|
||||
} from './TaskDependencyDescription';
|
||||
|
||||
import {
|
||||
timeTrackingFields,
|
||||
timeTrackingOperations,
|
||||
} from './TimeTrackingDescription';
|
||||
|
||||
import {
|
||||
listFields,
|
||||
listOperations,
|
||||
} from './ListDescription';
|
||||
|
||||
import {
|
||||
ITask,
|
||||
} from './TaskInterface';
|
||||
|
||||
import {
|
||||
IList,
|
||||
} from './ListInterface';
|
||||
|
||||
export class ClickUp implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'ClickUp',
|
||||
@ -53,6 +107,34 @@ export class ClickUp implements INodeType {
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Checklist',
|
||||
value: 'checklist',
|
||||
},
|
||||
{
|
||||
name: 'Checklist Item',
|
||||
value: 'checklistItem',
|
||||
},
|
||||
{
|
||||
name: 'Comment',
|
||||
value: 'comment',
|
||||
},
|
||||
{
|
||||
name: 'Folder',
|
||||
value: 'folder',
|
||||
},
|
||||
{
|
||||
name: 'Goal',
|
||||
value: 'goal',
|
||||
},
|
||||
{
|
||||
name: 'Goal Key Result',
|
||||
value: 'goalKeyResult',
|
||||
},
|
||||
// {
|
||||
// name: 'Guest',
|
||||
// value: 'guest',
|
||||
// },
|
||||
{
|
||||
name: 'List',
|
||||
value: 'list',
|
||||
@ -61,12 +143,49 @@ export class ClickUp implements INodeType {
|
||||
name: 'Task',
|
||||
value: 'task',
|
||||
},
|
||||
{
|
||||
name: 'Task Dependency',
|
||||
value: 'taskDependency',
|
||||
},
|
||||
{
|
||||
name: 'Time Traking',
|
||||
value: 'timeTracking',
|
||||
},
|
||||
],
|
||||
default: 'task',
|
||||
description: 'Resource to consume.',
|
||||
},
|
||||
// CHECKLIST
|
||||
...checklistOperations,
|
||||
...checklistFields,
|
||||
// CHECKLIST ITEM
|
||||
...checklistItemOperations,
|
||||
...checklistItemFields,
|
||||
// COMMENT
|
||||
...commentOperations,
|
||||
...commentFields,
|
||||
// FOLDER
|
||||
...folderOperations,
|
||||
...folderFields,
|
||||
// GOAL
|
||||
...goalOperations,
|
||||
...goalFields,
|
||||
// GOAL kEY RESULT
|
||||
...goalKeyResultOperations,
|
||||
...goalKeyResultFields,
|
||||
// GUEST
|
||||
// ...guestOperations,
|
||||
// ...guestFields,
|
||||
// TASK
|
||||
...taskOperations,
|
||||
...taskFields,
|
||||
// TASK DEPENDENCY
|
||||
...taskDependencyOperations,
|
||||
...taskDependencyFields,
|
||||
// TIME TRACKING
|
||||
...timeTrackingOperations,
|
||||
...timeTrackingFields,
|
||||
// LIST
|
||||
...listOperations,
|
||||
...listFields,
|
||||
],
|
||||
@ -215,6 +334,359 @@ export class ClickUp implements INodeType {
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (resource === 'checklist') {
|
||||
if (operation === 'create') {
|
||||
const taskId = this.getNodeParameter('task', i) as string;
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
const body: IDataObject = {
|
||||
name,
|
||||
};
|
||||
responseData = await clickupApiRequest.call(this, 'POST', `/task/${taskId}/checklist`, body);
|
||||
responseData = responseData.checklist;
|
||||
}
|
||||
if (operation === 'delete') {
|
||||
const checklistId = this.getNodeParameter('checklist', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'DELETE', `/checklist/${checklistId}`);
|
||||
responseData = { success: true };
|
||||
}
|
||||
if (operation === 'update') {
|
||||
const checklistId = this.getNodeParameter('checklist', i) as string;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
const body: IDataObject = {};
|
||||
if (updateFields.name) {
|
||||
body.name = updateFields.name as string;
|
||||
}
|
||||
if (updateFields.position) {
|
||||
body.position = updateFields.position as number;
|
||||
}
|
||||
responseData = await clickupApiRequest.call(this, 'PUT', `/checklist/${checklistId}`, body);
|
||||
responseData = responseData.checklist;
|
||||
}
|
||||
}
|
||||
if (resource === 'checklistItem') {
|
||||
if (operation === 'create') {
|
||||
const checklistId = this.getNodeParameter('checklist', i) as string;
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const body: IDataObject = {
|
||||
name,
|
||||
};
|
||||
if (additionalFields.assigneeId) {
|
||||
body.assignee = parseInt(additionalFields.assigneeId as string, 10);
|
||||
}
|
||||
responseData = await clickupApiRequest.call(this, 'POST', `/checklist/${checklistId}/checklist_item`, body);
|
||||
responseData = responseData.checklist;
|
||||
}
|
||||
if (operation === 'delete') {
|
||||
const checklistId = this.getNodeParameter('checklist', i) as string;
|
||||
const checklistItemId = this.getNodeParameter('checklistItem', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'DELETE', `/checklist/${checklistId}/checklist_item/${checklistItemId}`);
|
||||
responseData = { success: true };
|
||||
}
|
||||
if (operation === 'update') {
|
||||
const checklistId = this.getNodeParameter('checklist', i) as string;
|
||||
const checklistItemId = this.getNodeParameter('checklistItem', i) as string;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
const body: IDataObject = {};
|
||||
if (updateFields.name) {
|
||||
body.name = updateFields.name as string;
|
||||
}
|
||||
if (updateFields.parent) {
|
||||
body.parent = updateFields.parent as string;
|
||||
}
|
||||
if (updateFields.assignee) {
|
||||
body.assignee = parseInt(updateFields.assignee as string, 10);
|
||||
}
|
||||
if (updateFields.resolved) {
|
||||
body.resolved = updateFields.resolved as boolean;
|
||||
}
|
||||
responseData = await clickupApiRequest.call(this, 'PUT', `/checklist/${checklistId}/checklist_item/${checklistItemId}`, body);
|
||||
responseData = responseData.checklist;
|
||||
}
|
||||
}
|
||||
if (resource === 'comment') {
|
||||
if (operation === 'create') {
|
||||
const resource = this.getNodeParameter('commentOn', i) as string;
|
||||
const id = this.getNodeParameter('id', i) as string;
|
||||
const commentText = this.getNodeParameter('commentText', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const body: IDataObject = {
|
||||
comment_text: commentText,
|
||||
};
|
||||
if (additionalFields.assignee) {
|
||||
body.assigneeId = additionalFields.assignee as string;
|
||||
}
|
||||
if (additionalFields.notifyAll) {
|
||||
body.notify_all = additionalFields.notifyAll as boolean;
|
||||
}
|
||||
responseData = await clickupApiRequest.call(this, 'POST', `/${resource}/${id}/comment`, body);
|
||||
}
|
||||
if (operation === 'delete') {
|
||||
const commentId = this.getNodeParameter('comment', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'DELETE', `/comment/${commentId}`);
|
||||
responseData = { success: true };
|
||||
}
|
||||
if (operation === 'getAll') {
|
||||
const resource = this.getNodeParameter('commentsOn', i) as string;
|
||||
const id = this.getNodeParameter('id', i) as string;
|
||||
qs.limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await clickupApiRequest.call(this, 'GET', `/${resource}/${id}/comment`, {}, qs);
|
||||
responseData = responseData.comments;
|
||||
responseData = responseData.splice(0, qs.limit);
|
||||
}
|
||||
if (operation === 'update') {
|
||||
const commentId = this.getNodeParameter('comment', i) as string;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
const body: IDataObject = {};
|
||||
if (updateFields.commentText) {
|
||||
body.comment_text = updateFields.commentText as string;
|
||||
}
|
||||
if (updateFields.assignee) {
|
||||
body.assignee = parseInt(updateFields.assignee as string, 10);
|
||||
}
|
||||
if (updateFields.resolved) {
|
||||
body.resolved = updateFields.resolved as boolean;
|
||||
}
|
||||
responseData = await clickupApiRequest.call(this, 'PUT', `/comment/${commentId}`, body);
|
||||
responseData = { success: true };
|
||||
}
|
||||
}
|
||||
if (resource === 'folder') {
|
||||
if (operation === 'create') {
|
||||
const spaceId = this.getNodeParameter('space', i) as string;
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
const body: IDataObject = {
|
||||
name,
|
||||
};
|
||||
responseData = await clickupApiRequest.call(this, 'POST', `/space/${spaceId}/folder`, body);
|
||||
}
|
||||
if (operation === 'delete') {
|
||||
const folderId = this.getNodeParameter('folder', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'DELETE', `/folder/${folderId}`);
|
||||
responseData = { success: true };
|
||||
}
|
||||
if (operation === 'get') {
|
||||
const folderId = this.getNodeParameter('folder', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'GET', `/folder/${folderId}`);
|
||||
}
|
||||
if (operation === 'getAll') {
|
||||
const filters = this.getNodeParameter('filters', i) as IDataObject;
|
||||
const spaceId = this.getNodeParameter('space', i) as string;
|
||||
if (filters.archived) {
|
||||
qs.archived = filters.archived as boolean;
|
||||
}
|
||||
qs.limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await clickupApiRequest.call(this, 'GET', `/space/${spaceId}/folder`, {}, qs);
|
||||
responseData = responseData.folders;
|
||||
responseData = responseData.splice(0, qs.limit);
|
||||
}
|
||||
if (operation === 'update') {
|
||||
const folderId = this.getNodeParameter('folder', i) as string;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
const body: IDataObject = {};
|
||||
if (updateFields.name) {
|
||||
body.name = updateFields.name as string;
|
||||
}
|
||||
responseData = await clickupApiRequest.call(this, 'PUT', `/folder/${folderId}`, body);
|
||||
}
|
||||
}
|
||||
if (resource === 'goal') {
|
||||
if (operation === 'create') {
|
||||
const teamId = this.getNodeParameter('team', i) as string;
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const body: IDataObject = {
|
||||
name,
|
||||
};
|
||||
if (additionalFields.dueDate) {
|
||||
body.due_date = new Date(additionalFields.dueDate as string).getTime();
|
||||
}
|
||||
if (additionalFields.description) {
|
||||
body.description = additionalFields.description as string;
|
||||
}
|
||||
if (additionalFields.multipleOwners) {
|
||||
body.multiple_owners = additionalFields.multipleOwners as boolean;
|
||||
}
|
||||
if (additionalFields.color) {
|
||||
body.color = additionalFields.color as string;
|
||||
}
|
||||
if (additionalFields.owners) {
|
||||
body.owners = ((additionalFields.owners as string).split(',') as string[]).map((e: string) => parseInt(e, 10));
|
||||
}
|
||||
responseData = await clickupApiRequest.call(this, 'POST', `/team/${teamId}/goal`, body);
|
||||
responseData = responseData.goal;
|
||||
}
|
||||
if (operation === 'delete') {
|
||||
const goalId = this.getNodeParameter('goal', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'DELETE', `/goal/${goalId}`);
|
||||
responseData = { success: true };
|
||||
}
|
||||
if (operation === 'get') {
|
||||
const goalId = this.getNodeParameter('goal', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'GET', `/goal/${goalId}`);
|
||||
responseData = responseData.goal;
|
||||
}
|
||||
if (operation === 'getAll') {
|
||||
const teamId = this.getNodeParameter('team', i) as string;
|
||||
qs.limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await clickupApiRequest.call(this, 'GET', `/team/${teamId}/goal`, {}, qs);
|
||||
responseData = responseData.goals;
|
||||
responseData = responseData.splice(0, qs.limit);
|
||||
|
||||
}
|
||||
if (operation === 'update') {
|
||||
const goalId = this.getNodeParameter('goal', i) as string;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
const body: IDataObject = {};
|
||||
if (updateFields.name) {
|
||||
body.name = updateFields.name as string;
|
||||
}
|
||||
if (updateFields.dueDate) {
|
||||
body.due_date = new Date(updateFields.dueDate as string).getTime();
|
||||
}
|
||||
if (updateFields.description) {
|
||||
body.description = updateFields.description as string;
|
||||
}
|
||||
if (updateFields.color) {
|
||||
body.color = updateFields.color as string;
|
||||
}
|
||||
if (updateFields.addOwners) {
|
||||
body.add_owners = ((updateFields.addOwners as string).split(',') as string[]).map((e: string) => parseInt(e, 10)) as number[];
|
||||
}
|
||||
if (updateFields.removeOwners) {
|
||||
body.rem_owners = ((updateFields.removeOwners as string).split(',') as string[]).map((e: string) => parseInt(e, 10)) as number[];
|
||||
}
|
||||
responseData = await clickupApiRequest.call(this, 'PUT', `/goal/${goalId}`, body);
|
||||
responseData = responseData.goal;
|
||||
}
|
||||
}
|
||||
if (resource === 'goalKeyResult') {
|
||||
if (operation === 'create') {
|
||||
const goalId = this.getNodeParameter('goal', i) as string;
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
const type = this.getNodeParameter('type', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const body: IDataObject = {
|
||||
name,
|
||||
type,
|
||||
};
|
||||
if (type === 'number' || type === 'currency') {
|
||||
if (!additionalFields.unit) {
|
||||
throw new Error('Unit field must be set');
|
||||
}
|
||||
}
|
||||
if (type === 'number' || type === 'percentaje'
|
||||
|| type === 'automatic' || type === 'currency' ) {
|
||||
if (additionalFields.stepsStart === undefined
|
||||
|| !additionalFields.stepsEnd === undefined) {
|
||||
throw new Error('Steps start and steps end fields must be set');
|
||||
}
|
||||
}
|
||||
if (additionalFields.unit) {
|
||||
body.unit = additionalFields.unit as string;
|
||||
}
|
||||
if (additionalFields.stepsStart) {
|
||||
body.steps_start = additionalFields.stepsStart as number;
|
||||
}
|
||||
if (additionalFields.stepsEnd) {
|
||||
body.steps_end = additionalFields.stepsEnd as number;
|
||||
}
|
||||
if (additionalFields.taskIds) {
|
||||
body.task_ids = (additionalFields.taskIds as string).split(',');
|
||||
}
|
||||
if (additionalFields.listIds) {
|
||||
body.list_ids = (additionalFields.listIds as string).split(',');
|
||||
}
|
||||
if (additionalFields.owners) {
|
||||
body.owners = ((additionalFields.owners as string).split(',') as string[]).map((e: string) => parseInt(e, 10));
|
||||
}
|
||||
responseData = await clickupApiRequest.call(this, 'POST', `/goal/${goalId}/key_result`, body);
|
||||
responseData = responseData.key_result;
|
||||
}
|
||||
if (operation === 'delete') {
|
||||
const keyResultId = this.getNodeParameter('keyResult', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'DELETE', `/key_result/${keyResultId}`);
|
||||
responseData = { success: true };
|
||||
}
|
||||
if (operation === 'update') {
|
||||
const keyResultId = this.getNodeParameter('keyResult', i) as string;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
const body: IDataObject = {};
|
||||
if (updateFields.name) {
|
||||
body.name = updateFields.name as string;
|
||||
}
|
||||
if (updateFields.note) {
|
||||
body.note = updateFields.note as string;
|
||||
}
|
||||
if (updateFields.stepsCurrent) {
|
||||
body.steps_current = updateFields.stepsCurrent as number;
|
||||
}
|
||||
if (updateFields.stepsStart) {
|
||||
body.steps_start = updateFields.stepsStart as number;
|
||||
}
|
||||
if (updateFields.stepsEnd) {
|
||||
body.steps_end = updateFields.stepsEnd as number;
|
||||
}
|
||||
if (updateFields.unit) {
|
||||
body.unit = updateFields.unit as string;
|
||||
}
|
||||
responseData = await clickupApiRequest.call(this, 'PUT', `/key_result/${keyResultId}`, body);
|
||||
responseData = responseData.key_result;
|
||||
}
|
||||
}
|
||||
if (resource === 'guest') {
|
||||
if (operation === 'create') {
|
||||
const teamId = this.getNodeParameter('team', i) as string;
|
||||
const email = this.getNodeParameter('email', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const body: IDataObject = {
|
||||
email,
|
||||
};
|
||||
if (additionalFields.canEditTags) {
|
||||
body.can_edit_tags = additionalFields.canEditTags as boolean;
|
||||
}
|
||||
if (additionalFields.canSeeTimeSpend) {
|
||||
body.can_see_time_spend = additionalFields.canSeeTimeSpend as boolean;
|
||||
}
|
||||
if (additionalFields.canSeeTimeEstimated) {
|
||||
body.can_see_time_estimated = additionalFields.canSeeTimeEstimated as boolean;
|
||||
}
|
||||
responseData = await clickupApiRequest.call(this, 'POST', `/team/${teamId}/guest`, body);
|
||||
responseData = responseData.team;
|
||||
}
|
||||
if (operation === 'delete') {
|
||||
const teamId = this.getNodeParameter('team', i) as string;
|
||||
const guestId = this.getNodeParameter('guest', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'DELETE', `/team/${teamId}/guest/${guestId}`);
|
||||
responseData = { success: true };
|
||||
}
|
||||
if (operation === 'get') {
|
||||
const teamId = this.getNodeParameter('team', i) as string;
|
||||
const guestId = this.getNodeParameter('guest', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'GET', `/team/${teamId}/guest/${guestId}`);
|
||||
responseData = responseData.team;
|
||||
}
|
||||
if (operation === 'update') {
|
||||
const teamId = this.getNodeParameter('team', i) as string;
|
||||
const guestId = this.getNodeParameter('guest', i) as string;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
const body: IDataObject = {};
|
||||
if (updateFields.username) {
|
||||
body.username = updateFields.username as string;
|
||||
}
|
||||
if (updateFields.canEditTags) {
|
||||
body.can_edit_tags = updateFields.canEditTags as boolean;
|
||||
}
|
||||
if (updateFields.canSeeTimeSpend) {
|
||||
body.can_see_time_spend = updateFields.canSeeTimeSpend as boolean;
|
||||
}
|
||||
if (updateFields.canSeeTimeEstimated) {
|
||||
body.can_see_time_estimated = updateFields.canSeeTimeEstimated as boolean;
|
||||
}
|
||||
responseData = await clickupApiRequest.call(this, 'PUT', `/team/${teamId}/guest/${guestId}`, body);
|
||||
responseData = responseData.team;
|
||||
}
|
||||
}
|
||||
if (resource === 'task') {
|
||||
if (operation === 'create') {
|
||||
const listId = this.getNodeParameter('list', i) as string;
|
||||
@ -275,7 +747,12 @@ export class ClickUp implements INodeType {
|
||||
if (operation === 'update') {
|
||||
const taskId = this.getNodeParameter('id', i) as string;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
const body: ITask = {};
|
||||
const body: ITask = {
|
||||
assignees: {
|
||||
add: [],
|
||||
rem: [],
|
||||
},
|
||||
};
|
||||
if (updateFields.content) {
|
||||
body.content = updateFields.content as string;
|
||||
}
|
||||
@ -306,6 +783,17 @@ export class ClickUp implements INodeType {
|
||||
if (updateFields.parentId) {
|
||||
body.parent = updateFields.parentId as string;
|
||||
}
|
||||
if (updateFields.addAssignees) {
|
||||
//@ts-ignore
|
||||
body.assignees.add = ((updateFields.addAssignees as string).split(',') as string[]).map((e: string) => parseInt(e, 10));
|
||||
}
|
||||
if (updateFields.removeAssignees) {
|
||||
//@ts-ignore
|
||||
body.assignees.rem = ((updateFields.removeAssignees as string).split(',') as string[]).map((e: string) => parseInt(e, 10));
|
||||
}
|
||||
if (updateFields.status) {
|
||||
body.status = updateFields.status as string;
|
||||
}
|
||||
if (updateFields.markdownContent) {
|
||||
delete body.content;
|
||||
body.markdown_content = updateFields.content as string;
|
||||
@ -317,8 +805,7 @@ export class ClickUp implements INodeType {
|
||||
responseData = await clickupApiRequest.call(this, 'GET', `/task/${taskId}`);
|
||||
}
|
||||
if (operation === 'getAll') {
|
||||
const returnAll = true;
|
||||
//const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
const filters = this.getNodeParameter('filters', i) as IDataObject;
|
||||
if (filters.archived) {
|
||||
qs.archived = filters.archived as boolean;
|
||||
@ -363,9 +850,9 @@ export class ClickUp implements INodeType {
|
||||
if (returnAll === true) {
|
||||
responseData = await clickupApiRequestAllItems.call(this, 'tasks', 'GET', `/list/${listId}/task`, {}, qs);
|
||||
} else {
|
||||
// qs.limit = this.getNodeParameter('limit', i) as number;
|
||||
// responseData = await clickupApiRequest.call(this, 'GET', `/list/${listId}/task`, {}, qs);
|
||||
// responseData = responseData.tasks;
|
||||
qs.limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await clickupApiRequestAllItems.call(this, 'tasks', 'GET', `/list/${listId}/task`, {}, qs);
|
||||
responseData = responseData.splice(0, qs.limit);
|
||||
}
|
||||
}
|
||||
if (operation === 'setCustomField') {
|
||||
@ -392,14 +879,170 @@ export class ClickUp implements INodeType {
|
||||
if (operation === 'delete') {
|
||||
const taskId = this.getNodeParameter('id', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'DELETE', `/task/${taskId}`, {});
|
||||
responseData = { success: true };
|
||||
}
|
||||
}
|
||||
if (resource === 'taskDependency') {
|
||||
if (operation === 'create') {
|
||||
const taskId = this.getNodeParameter('task', i) as string;
|
||||
const dependsOnTaskId = this.getNodeParameter('dependsOnTask', i) as string;
|
||||
const body: IDataObject = {};
|
||||
|
||||
body.depends_on = dependsOnTaskId;
|
||||
|
||||
responseData = await clickupApiRequest.call(this, 'POST', `/task/${taskId}/dependency`, body);
|
||||
responseData = { success: true };
|
||||
}
|
||||
if (operation === 'delete') {
|
||||
const taskId = this.getNodeParameter('task', i) as string;
|
||||
const dependsOnTaskId = this.getNodeParameter('dependsOnTask', i) as string;
|
||||
|
||||
qs.depends_on = dependsOnTaskId;
|
||||
|
||||
responseData = await clickupApiRequest.call(this, 'DELETE', `/task/${taskId}/dependency`, {}, qs);
|
||||
responseData = { success: true };
|
||||
}
|
||||
}
|
||||
if (resource === 'timeTracking') {
|
||||
if (operation === 'log') {
|
||||
const taskId = this.getNodeParameter('task', i) as string;
|
||||
const type = this.getNodeParameter('type', i) as string;
|
||||
const body: IDataObject = {};
|
||||
if (type === 'fromTo') {
|
||||
const from = new Date(this.getNodeParameter('from', i) as string).getTime();
|
||||
const to = new Date(this.getNodeParameter('to', i) as string).getTime();
|
||||
body.from = from;
|
||||
body.to = to;
|
||||
} else {
|
||||
const minutes = this.getNodeParameter('minutes', i) as number;
|
||||
body.time = minutes * 60000;
|
||||
}
|
||||
responseData = await clickupApiRequest.call(this, 'POST', `/task/${taskId}/time`, body);
|
||||
}
|
||||
if (operation === 'delete') {
|
||||
const taskId = this.getNodeParameter('task', i) as string;
|
||||
const intervalId = this.getNodeParameter('interval', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'DELETE', `/task/${taskId}/time/${intervalId}`);
|
||||
responseData = { success: true };
|
||||
}
|
||||
if (operation === 'getAll') {
|
||||
const taskId = this.getNodeParameter('task', i) as string;
|
||||
qs.limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await clickupApiRequest.call(this, 'GET', `/task/${taskId}/time`, {}, qs);
|
||||
responseData = responseData.data;
|
||||
responseData = responseData.splice(0, qs.limit);
|
||||
}
|
||||
if (operation === 'update') {
|
||||
const taskId = this.getNodeParameter('task', i) as string;
|
||||
const intervalId = this.getNodeParameter('interval', i) as string;
|
||||
const type = this.getNodeParameter('type', i) as string;
|
||||
const body: IDataObject = {};
|
||||
if (type === 'fromTo') {
|
||||
const from = new Date(this.getNodeParameter('from', i) as string).getTime();
|
||||
const to = new Date(this.getNodeParameter('to', i) as string).getTime();
|
||||
body.from = from;
|
||||
body.to = to;
|
||||
} else {
|
||||
const minutes = this.getNodeParameter('minutes', i) as number;
|
||||
body.time = minutes * 60000;
|
||||
}
|
||||
responseData = await clickupApiRequest.call(this, 'PUT', `/task/${taskId}/time/${intervalId}`, body);
|
||||
responseData = { success: true };
|
||||
}
|
||||
}
|
||||
if (resource === 'list') {
|
||||
if (operation === 'create') {
|
||||
const spaceId = this.getNodeParameter('space', i) as string;
|
||||
const folderless = this.getNodeParameter('folderless', i) as string;
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const body: IList = {
|
||||
name,
|
||||
};
|
||||
if (additionalFields.content) {
|
||||
body.content = additionalFields.content as string;
|
||||
}
|
||||
if (additionalFields.dueDate) {
|
||||
body.due_date = new Date(additionalFields.dueDate as string).getTime();
|
||||
}
|
||||
if (additionalFields.dueDateTime) {
|
||||
body.due_date_time = additionalFields.dueDateTime as boolean;
|
||||
}
|
||||
if (additionalFields.priority) {
|
||||
body.priority = additionalFields.priority as number;
|
||||
}
|
||||
if (additionalFields.assignee) {
|
||||
body.assignee = parseInt(additionalFields.assignee as string, 10);
|
||||
}
|
||||
if (additionalFields.status) {
|
||||
body.status = additionalFields.status as string;
|
||||
}
|
||||
if (folderless) {
|
||||
responseData = await clickupApiRequest.call(this, 'POST', `/space/${spaceId}/list`, body);
|
||||
} else {
|
||||
const folderId = this.getNodeParameter('folder', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'POST', `/folder/${folderId}/list`, body);
|
||||
}
|
||||
}
|
||||
if (operation === 'customFields') {
|
||||
const listId = this.getNodeParameter('list', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'GET', `/list/${listId}/field`);
|
||||
responseData = responseData.fields;
|
||||
}
|
||||
if (operation === 'delete') {
|
||||
const listId = this.getNodeParameter('list', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'DELETE', `/list/${listId}`);
|
||||
responseData = { success: true };
|
||||
}
|
||||
if (operation === 'get') {
|
||||
const listId = this.getNodeParameter('list', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'GET', `/list/${listId}`);
|
||||
}
|
||||
if (operation === 'getAll') {
|
||||
const filters = this.getNodeParameter('filters', i) as IDataObject;
|
||||
const spaceId = this.getNodeParameter('space', i) as string;
|
||||
const folderless = this.getNodeParameter('folderless', i) as boolean;
|
||||
if (filters.archived) {
|
||||
qs.archived = filters.archived as boolean;
|
||||
}
|
||||
let endpoint = `/space/${spaceId}/list`;
|
||||
if (!folderless) {
|
||||
const folderId = this.getNodeParameter('folder', i) as string;
|
||||
endpoint = `/folder/${folderId}/list`;
|
||||
}
|
||||
|
||||
qs.limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await clickupApiRequest.call(this, 'GET', endpoint, {}, qs);
|
||||
responseData = responseData.lists;
|
||||
responseData = responseData.splice(0, qs.limit);
|
||||
}
|
||||
if (operation === 'update') {
|
||||
const listId = this.getNodeParameter('list', i) as string;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
const body: IList = {};
|
||||
if (updateFields.name) {
|
||||
body.name = updateFields.name as string;
|
||||
}
|
||||
if (updateFields.content) {
|
||||
body.content = updateFields.content as string;
|
||||
}
|
||||
if (updateFields.dueDate) {
|
||||
body.due_date = new Date(updateFields.dueDate as string).getTime();
|
||||
}
|
||||
if (updateFields.dueDateTime) {
|
||||
body.due_date_time = updateFields.dueDateTime as boolean;
|
||||
}
|
||||
if (updateFields.priority) {
|
||||
body.priority = updateFields.priority as number;
|
||||
}
|
||||
if (updateFields.assignee) {
|
||||
body.assignee = parseInt(updateFields.assignee as string, 10);
|
||||
}
|
||||
if (updateFields.unsetStatus) {
|
||||
body.unset_status = updateFields.unsetStatus as boolean;
|
||||
}
|
||||
responseData = await clickupApiRequest.call(this, 'PUT', `/list/${listId}`, body);
|
||||
}
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
|
292
packages/nodes-base/nodes/ClickUp/CommentDescription.ts
Normal file
292
packages/nodes-base/nodes/ClickUp/CommentDescription.ts
Normal file
@ -0,0 +1,292 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const commentOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'comment',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a comment',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a comment',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all comments',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a comment',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const commentFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* comment:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Comment On',
|
||||
name: 'commentOn',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'List',
|
||||
value: 'list',
|
||||
},
|
||||
{
|
||||
name: 'Task',
|
||||
value: 'task',
|
||||
},
|
||||
{
|
||||
name: 'View',
|
||||
value: 'view',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'comment',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'comment',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Comment Text',
|
||||
name: 'commentText',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'comment',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'comment',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Assignee ID',
|
||||
name: 'assignee',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Notify All',
|
||||
name: 'notifyAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'If true, creation notifications will be sent to everyone including the creator of the comment.',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* comment:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Comment ID',
|
||||
name: 'comment',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'comment',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* comment:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Comments On',
|
||||
name: 'commentsOn',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'List',
|
||||
value: 'list',
|
||||
},
|
||||
{
|
||||
name: 'Task',
|
||||
value: 'task',
|
||||
},
|
||||
{
|
||||
name: 'View',
|
||||
value: 'view',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'comment',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'comment',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'comment',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* comment:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Comment ID',
|
||||
name: 'comment',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'comment',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'comment',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Assignee ID',
|
||||
name: 'assignee',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Comment Text',
|
||||
name: 'commentText',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Resolved',
|
||||
name: 'resolved',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
438
packages/nodes-base/nodes/ClickUp/FolderDescription.ts
Normal file
438
packages/nodes-base/nodes/ClickUp/FolderDescription.ts
Normal file
@ -0,0 +1,438 @@
|
||||
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 folder',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a folder',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a folder',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all folders',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a folder',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const folderFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTeams',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Space ID',
|
||||
name: 'space',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSpaces',
|
||||
loadOptionsDependsOn: [
|
||||
'team',
|
||||
]
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTeams',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Space ID',
|
||||
name: 'space',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSpaces',
|
||||
loadOptionsDependsOn: [
|
||||
'team',
|
||||
]
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Folder ID',
|
||||
name: 'folder',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getFolders',
|
||||
loadOptionsDependsOn: [
|
||||
'space',
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTeams',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Space ID',
|
||||
name: 'space',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSpaces',
|
||||
loadOptionsDependsOn: [
|
||||
'team',
|
||||
]
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Folder ID',
|
||||
name: 'folder',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getFolders',
|
||||
loadOptionsDependsOn: [
|
||||
'space',
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTeams',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Space ID',
|
||||
name: 'space',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSpaces',
|
||||
loadOptionsDependsOn: [
|
||||
'team',
|
||||
]
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Filter',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Archived',
|
||||
name: 'archived',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* folder:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTeams',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Space ID',
|
||||
name: 'space',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSpaces',
|
||||
loadOptionsDependsOn: [
|
||||
'team',
|
||||
]
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Folder ID',
|
||||
name: 'folder',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getFolders',
|
||||
loadOptionsDependsOn: [
|
||||
'space',
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'folder',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
@ -1,4 +1,7 @@
|
||||
import { OptionsWithUri } from 'request';
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
@ -6,7 +9,10 @@ import {
|
||||
ILoadOptionsFunctions,
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-core';
|
||||
import { IDataObject } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function clickupApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('clickUpApi');
|
||||
@ -42,12 +48,14 @@ export async function clickupApiRequestAllItems(this: IHookFunctions | IExecuteF
|
||||
|
||||
let responseData;
|
||||
query.page = 0;
|
||||
query.limit = 100;
|
||||
|
||||
do {
|
||||
responseData = await clickupApiRequest.call(this, method, resource, body, query);
|
||||
returnData.push.apply(returnData, responseData[propertyName]);
|
||||
query.page++;
|
||||
if (query.limit && query.limit <= returnData.length) {
|
||||
return returnData;
|
||||
}
|
||||
} while (
|
||||
responseData[propertyName] &&
|
||||
responseData[propertyName].length !== 0
|
||||
|
305
packages/nodes-base/nodes/ClickUp/GoalDescription.ts
Normal file
305
packages/nodes-base/nodes/ClickUp/GoalDescription.ts
Normal file
@ -0,0 +1,305 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const goalOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goal',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a goal',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a goal',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a goal',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all goals',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a goal',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const goalFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goal:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goal',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTeams',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goal',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goal',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Color',
|
||||
name: 'color',
|
||||
type: 'color',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Description',
|
||||
name: 'description',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Due Date',
|
||||
name: 'dueDate',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Multiple Owners',
|
||||
name: 'multipleOwners',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Owners',
|
||||
name: 'owners',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goal:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Goal ID',
|
||||
name: 'goal',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goal',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goal:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Goal ID',
|
||||
name: 'goal',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goal',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goal:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goal',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTeams',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goal',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goal:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Goal ID',
|
||||
name: 'goal',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goal',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goal',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Add Owners',
|
||||
name: 'addOwners',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Color',
|
||||
name: 'color',
|
||||
type: 'color',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Description',
|
||||
name: 'description',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Due Date',
|
||||
name: 'dueDate',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Remove Owners',
|
||||
name: 'removeOwners',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
289
packages/nodes-base/nodes/ClickUp/GoalKeyResultDescription.ts
Normal file
289
packages/nodes-base/nodes/ClickUp/GoalKeyResultDescription.ts
Normal file
@ -0,0 +1,289 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const goalKeyResultOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goalKeyResult',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a key result',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a key result',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a key result',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const goalKeyResultFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goalKeyResult:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Goal ID',
|
||||
name: 'goal',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goalKeyResult',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goalKeyResult',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Automatic',
|
||||
value: 'automatic',
|
||||
},
|
||||
{
|
||||
name: 'Boolean',
|
||||
value: 'boolean',
|
||||
},
|
||||
{
|
||||
name: 'Currency',
|
||||
value: 'currency',
|
||||
},
|
||||
{
|
||||
name: 'Number',
|
||||
value: 'number',
|
||||
},
|
||||
{
|
||||
name: 'Percentage',
|
||||
value: 'percentage',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goalKeyResult',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goalKeyResult',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'List IDs',
|
||||
name: 'listIds',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Owners',
|
||||
name: 'owners',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Steps Start',
|
||||
name: 'stepsStart',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
},
|
||||
default: 0,
|
||||
description: 'Required for Percentage, Automatic, Number and Currency',
|
||||
},
|
||||
{
|
||||
displayName: 'Steps End',
|
||||
name: 'stepsEnd',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
},
|
||||
default: 0,
|
||||
description: 'Required for Percentage, Automatic, Number and Currency',
|
||||
},
|
||||
{
|
||||
displayName: 'Task IDs',
|
||||
name: 'taskIds',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Unit',
|
||||
name: 'unit',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `Only matters for type Number and Currency. For Currency the unit must be a valid currency code.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goalKeyResult:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Key Result ID',
|
||||
name: 'keyResult',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goalKeyResult',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* goalKeyResult:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Key Result ID',
|
||||
name: 'keyResult',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goalKeyResult',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'goalKeyResult',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Note',
|
||||
name: 'note',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Steps Current',
|
||||
name: 'stepsCurrent',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
},
|
||||
default: 1,
|
||||
},
|
||||
{
|
||||
displayName: 'Steps End',
|
||||
name: 'stepsEnd',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
},
|
||||
default: 0,
|
||||
},
|
||||
{
|
||||
displayName: 'Steps Start',
|
||||
name: 'stepsStart',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
},
|
||||
default: 0,
|
||||
},
|
||||
{
|
||||
displayName: 'Unit',
|
||||
name: 'unit',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `Only matters for type Number and Currency. For Currency the unit must be a valid currency code.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
285
packages/nodes-base/nodes/ClickUp/GuestDescription.ts
Normal file
285
packages/nodes-base/nodes/ClickUp/GuestDescription.ts
Normal file
@ -0,0 +1,285 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const guestOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'guest',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a guest',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a guest',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a guest',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a guest',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const guestFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* guest:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'guest',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTeams',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'guest',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'guest',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Can Edit Tags',
|
||||
name: 'can_edit_tags',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Can See Time Spend',
|
||||
name: 'can_see_time_spend',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Can See Time estimated',
|
||||
name: 'can_see_time_estimated',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* guest:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'guest',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTeams',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Guest ID',
|
||||
name: 'guest',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'guest',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* guest:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'guest',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTeams',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Guest ID',
|
||||
name: 'guest',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'guest',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* guest:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'guest',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTeams',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Guest ID',
|
||||
name: 'guest',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'guest',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'guest',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Can Edit Tags',
|
||||
name: 'can_edit_tags',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Can See Time Spend',
|
||||
name: 'can_see_time_spend',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Can See Time estimated',
|
||||
name: 'can_see_time_estimated',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Username',
|
||||
name: 'username',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
@ -1,4 +1,6 @@
|
||||
import { INodeProperties } from 'n8n-workflow';
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const listOperations = [
|
||||
{
|
||||
@ -13,11 +15,36 @@ export const listOperations = [
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a list',
|
||||
},
|
||||
{
|
||||
name: 'Custom Fields',
|
||||
value: 'customFields',
|
||||
description: `Retrieve list's custom fields`,
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a list',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a list',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all lists',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a list',
|
||||
},
|
||||
],
|
||||
default: 'customFields',
|
||||
description: 'The operation to perform.',
|
||||
@ -26,6 +53,181 @@ export const listOperations = [
|
||||
|
||||
export const listFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTeams',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Space ID',
|
||||
name: 'space',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSpaces',
|
||||
loadOptionsDependsOn: [
|
||||
'team',
|
||||
]
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Folderless List',
|
||||
name: 'folderless',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Folder ID',
|
||||
name: 'folder',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
folderless: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getFolders',
|
||||
loadOptionsDependsOn: [
|
||||
'space',
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Assignee',
|
||||
name: 'assignee',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Content',
|
||||
name: 'content',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Due Date',
|
||||
name: 'dueDate',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Due Date Time',
|
||||
name: 'dueDateTime',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Priority',
|
||||
name: 'priority',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 4,
|
||||
},
|
||||
description: 'Integer mapping as 1 : Urgent, 2 : High, 3 : Normal, 4 : Low',
|
||||
default: 3,
|
||||
},
|
||||
{
|
||||
displayName: 'Status',
|
||||
name: 'status',
|
||||
type: 'options',
|
||||
loadOptionsDependsOn: [
|
||||
'list',
|
||||
],
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getStatuses',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:customFields */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
@ -67,7 +269,7 @@ export const listFields = [
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSpaces',
|
||||
loadOptionsDependsOn: [
|
||||
'team',
|
||||
'teamId',
|
||||
]
|
||||
},
|
||||
required: true,
|
||||
@ -167,4 +369,533 @@ export const listFields = [
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTeams',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Space ID',
|
||||
name: 'space',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSpaces',
|
||||
loadOptionsDependsOn: [
|
||||
'team',
|
||||
]
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Folderless List',
|
||||
name: 'folderless',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Folder ID',
|
||||
name: 'folder',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
folderless: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getFolders',
|
||||
loadOptionsDependsOn: [
|
||||
'space',
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'List ID',
|
||||
name: 'list',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTeams',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Space ID',
|
||||
name: 'space',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSpaces',
|
||||
loadOptionsDependsOn: [
|
||||
'team',
|
||||
]
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Folderless List',
|
||||
name: 'folderless',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Folder ID',
|
||||
name: 'folder',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
folderless: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getFolders',
|
||||
loadOptionsDependsOn: [
|
||||
'space',
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'List ID',
|
||||
name: 'list',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTeams',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Space ID',
|
||||
name: 'space',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSpaces',
|
||||
loadOptionsDependsOn: [
|
||||
'team',
|
||||
]
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Folderless List',
|
||||
name: 'folderless',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Folder ID',
|
||||
name: 'folder',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
folderless: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getFolders',
|
||||
loadOptionsDependsOn: [
|
||||
'space',
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Filter',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Archived',
|
||||
name: 'archived',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Team ID',
|
||||
name: 'team',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTeams',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Space ID',
|
||||
name: 'space',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSpaces',
|
||||
loadOptionsDependsOn: [
|
||||
'team',
|
||||
]
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Folderless List',
|
||||
name: 'folderless',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Folder ID',
|
||||
name: 'folder',
|
||||
type: 'options',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
folderless: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getFolders',
|
||||
loadOptionsDependsOn: [
|
||||
'space',
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'List ID',
|
||||
name: 'list',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Assignee',
|
||||
name: 'assignee',
|
||||
type: 'options',
|
||||
loadOptionsDependsOn: [
|
||||
'list',
|
||||
],
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getAssignees',
|
||||
},
|
||||
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Content',
|
||||
name: 'content',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Due Date',
|
||||
name: 'dueDate',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Due Date Time',
|
||||
name: 'dueDateTime',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Priority',
|
||||
name: 'priority',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 4,
|
||||
},
|
||||
description: 'Integer mapping as 1 : Urgent, 2 : High, 3 : Normal, 4 : Low',
|
||||
default: 3,
|
||||
},
|
||||
{
|
||||
displayName: 'Unset Status',
|
||||
name: 'unsetStatus',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
11
packages/nodes-base/nodes/ClickUp/ListInterface.ts
Normal file
11
packages/nodes-base/nodes/ClickUp/ListInterface.ts
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
export interface IList {
|
||||
name?: string;
|
||||
content?: string;
|
||||
assignee?: number;
|
||||
status?: string;
|
||||
priority?: number;
|
||||
due_date?: number;
|
||||
due_date_time?: boolean;
|
||||
unset_status?: boolean;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user