fix conflict

This commit is contained in:
Mutasem 2022-04-06 06:35:06 +02:00
commit 40c951fab9
103 changed files with 45686 additions and 27153 deletions

View file

@ -1,3 +1,47 @@
# [0.171.0](https://github.com/n8n-io/n8n/compare/n8n@0.170.0...n8n@0.171.0) (2022-04-03)
### Bug Fixes
* **core:** Fix crash on webhook when last node did not return data ([c50d04a](https://github.com/n8n-io/n8n/commit/c50d04af9eb033d82860c336fc7350b5c3f22242))
* **EmailReadImap Node:** Fix issue that crashed process if node was configured wrong ([#3079](https://github.com/n8n-io/n8n/issues/3079)) ([85f15d4](https://github.com/n8n-io/n8n/commit/85f15d49896d876fa3ab84e9fa1846f856851274))
* **Google Tasks Node:** Fix "Show Completed" option and hide title field where not needed ([#2741](https://github.com/n8n-io/n8n/issues/2741)) ([9d703e3](https://github.com/n8n-io/n8n/commit/9d703e366b8e191e0f588469892ebb7b6d03c1d3))
* **NocoDB Node:** Fix pagination ([#3081](https://github.com/n8n-io/n8n/issues/3081)) ([5f44b0d](https://github.com/n8n-io/n8n/commit/5f44b0dad5254fe9f985b314db8f7d43ab48c712))
* **Salesforce Node:** Fix issue that "status" did not get used for Case => Create & Update ([#2212](https://github.com/n8n-io/n8n/issues/2212)) ([1018146](https://github.com/n8n-io/n8n/commit/1018146f21c47eda9f888bd19e92d1106c49267a))
### Features
* **editor:** Add download button for binary data ([#2992](https://github.com/n8n-io/n8n/issues/2992)) ([13a9db7](https://github.com/n8n-io/n8n/commit/13a9db774576a00d4e3ce1988557654d00067073))
* **Emelia Node:** Add Campaign > Duplicate functionality ([#3000](https://github.com/n8n-io/n8n/issues/3000)) ([0b08be1](https://github.com/n8n-io/n8n/commit/0b08be1c0b2961f235fc2446a36afe3995b4d847)), closes [#3065](https://github.com/n8n-io/n8n/issues/3065) [#2741](https://github.com/n8n-io/n8n/issues/2741) [#3075](https://github.com/n8n-io/n8n/issues/3075)
* **FTP Node:** Add option to recursively create directories on rename ([#3001](https://github.com/n8n-io/n8n/issues/3001)) ([39a6f41](https://github.com/n8n-io/n8n/commit/39a6f417203b76cfa2c68816c49e86dc7236aba4))
* **Mautic Node:** Add credential test and allow trailing slash in host ([#3080](https://github.com/n8n-io/n8n/issues/3080)) ([0a75539](https://github.com/n8n-io/n8n/commit/0a75539cc3d696a8946d7db5ff5842ff54835134))
* **Microsoft Teams Node:** Add chat message support ([#2635](https://github.com/n8n-io/n8n/issues/2635)) ([984f62d](https://github.com/n8n-io/n8n/commit/984f62df9ed92cdf297b3b56300c9f23bf128d2d))
* **Mocean Node:** Add "Delivery Report URL" option and credential tests ([#3075](https://github.com/n8n-io/n8n/issues/3075)) ([c89d2b1](https://github.com/n8n-io/n8n/commit/c89d2b10f2461ff8e90209b8f29c222f9430dba5))
* **ServiceNow Node:** Add basicAuth support and fix getColumns loadOptions ([#2712](https://github.com/n8n-io/n8n/issues/2712)) ([2c72584](https://github.com/n8n-io/n8n/commit/2c72584b55521b437baa20ddad7c919807fd9f8f)), closes [#2741](https://github.com/n8n-io/n8n/issues/2741) [#3075](https://github.com/n8n-io/n8n/issues/3075) [#3000](https://github.com/n8n-io/n8n/issues/3000) [#3065](https://github.com/n8n-io/n8n/issues/3065) [#2741](https://github.com/n8n-io/n8n/issues/2741) [#3075](https://github.com/n8n-io/n8n/issues/3075) [#3071](https://github.com/n8n-io/n8n/issues/3071) [#3001](https://github.com/n8n-io/n8n/issues/3001) [#2635](https://github.com/n8n-io/n8n/issues/2635) [#3080](https://github.com/n8n-io/n8n/issues/3080) [#3061](https://github.com/n8n-io/n8n/issues/3061) [#3081](https://github.com/n8n-io/n8n/issues/3081) [#2582](https://github.com/n8n-io/n8n/issues/2582) [#2212](https://github.com/n8n-io/n8n/issues/2212)
* **Strava Node:** Add "Get Streams" operation ([#2582](https://github.com/n8n-io/n8n/issues/2582)) ([6bbb4df](https://github.com/n8n-io/n8n/commit/6bbb4df05925362404f844a23a695f186d27b72e))
# [0.170.0](https://github.com/n8n-io/n8n/compare/n8n@0.169.0...n8n@0.170.0) (2022-03-27)
### Bug Fixes
- **core:** Add logs and error catches for possible failures in queue mode ([#3032](https://github.com/n8n-io/n8n/issues/3032)) ([3b4a97d](https://github.com/n8n-io/n8n/commit/3b4a97dd576bd3c2f53f958266964d3e02f01c96))
- **AWS Lambda Node:** Fix "Invocation Type" > "Continue Workflow" ([#3010](https://github.com/n8n-io/n8n/issues/3010)) ([9547a08](https://github.com/n8n-io/n8n/commit/9547a08f0344825e42f5580da035bb1f21c03368))
- **Supabase Node:** Fix Row > Get operation ([#3045](https://github.com/n8n-io/n8n/issues/3045)) ([b9aa440](https://github.com/n8n-io/n8n/commit/b9aa440be3d52bf412990b93cfc3758353fb4943))
- **Supabase Node:** Send token also via Authorization Bearer ([#2814](https://github.com/n8n-io/n8n/issues/2814)) ([5774dd8](https://github.com/n8n-io/n8n/commit/5774dd8885a87a1ebe70f4ef4a06a42013112afe))
- **Xero Node:** Fix some operations and add support for setting address and phone number ([#3048](https://github.com/n8n-io/n8n/issues/3048)) ([ab08c0d](https://github.com/n8n-io/n8n/commit/ab08c0df1599d44326b45c37f80918e5c107cc6a))
- **Wise Node:** Fix issue when executing a transfer ([#3039](https://github.com/n8n-io/n8n/issues/3039)) ([b90bf45](https://github.com/n8n-io/n8n/commit/b90bf4576c6e3f86000d61606f412ea0544b59ef))
### Features
- **Crypto Node:** Add Generate operation to generate random values ([#2541](https://github.com/n8n-io/n8n/issues/2541)) ([b5ecccb](https://github.com/n8n-io/n8n/commit/b5ecccb84080362880a307e3f9d76d429bd1d537))
- **HTTP Request Node:** Add support for OPTIONS method ([#3030](https://github.com/n8n-io/n8n/issues/3030)) ([bd9064c](https://github.com/n8n-io/n8n/commit/bd9064cd0ea8833b49a7e3860f12bfa37c286947))
- **Jira Node:** Add Simplify Output option to Issue > Get ([#2408](https://github.com/n8n-io/n8n/issues/2408)) ([016aeaa](https://github.com/n8n-io/n8n/commit/016aeaaa791205c5ee3d16eef25f856603cf0085))
- **Reddit Node:** Add possibility to query saved posts ([#3034](https://github.com/n8n-io/n8n/issues/3034)) ([5ba4c27](https://github.com/n8n-io/n8n/commit/5ba4c27d8c417964187af89a15d5dd4ce9f3271a))
- **Zendesk Node:** Add ticket status "On-hold" ([2b20a46](https://github.com/n8n-io/n8n/commit/2b20a460915655791647d62b48dde97dad3b2fd3))
# [0.169.0](https://github.com/n8n-io/n8n/compare/n8n@0.168.2...n8n@0.169.0) (2022-03-20) # [0.169.0](https://github.com/n8n-io/n8n/compare/n8n@0.168.2...n8n@0.169.0) (2022-03-20)
### License change ### License change
@ -55,3 +99,5 @@ From [Apache 2.0 with Commons Clause](https://github.com/n8n-io/n8n/blob/181ba3c
- **MongoDb Node:** Add Aggregate Operation ([2c9a06e](https://github.com/n8n-io/n8n/commit/2c9a06e86346a9e21f877cb508d13a1401c700a9)) - **MongoDb Node:** Add Aggregate Operation ([2c9a06e](https://github.com/n8n-io/n8n/commit/2c9a06e86346a9e21f877cb508d13a1401c700a9))
- **Redis Node:** Add Redis Trigger node and publish operation to regular node ([5c2deb4](https://github.com/n8n-io/n8n/commit/5c2deb468867ec77a05d09ef324d4855210e17d4)) - **Redis Node:** Add Redis Trigger node and publish operation to regular node ([5c2deb4](https://github.com/n8n-io/n8n/commit/5c2deb468867ec77a05d09ef324d4855210e17d4))
- **Wordpress Node:** Add Status option to Get All operation of Posts resource ([4d4db7f](https://github.com/n8n-io/n8n/commit/4d4db7f805673758dfb379c9e86e98815f265db2)) - **Wordpress Node:** Add Status option to Get All operation of Posts resource ([4d4db7f](https://github.com/n8n-io/n8n/commit/4d4db7f805673758dfb379c9e86e98815f265db2))
> **Note:** for changelogs before 0.167.0, refer to the [Release notes](https://docs.n8n.io/reference/release-notes.html) in the documentation.

View file

@ -12,7 +12,7 @@ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensa
## Limitations ## Limitations
You may use or modify the software only for your own internal business purposes or for non-commercial or personal use You may use or modify the software only for your own internal business purposes or for non-commercial or personal use.
You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes. You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes.
You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law. You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law.

69911
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,13 +2,27 @@
This list shows all the versions which include breaking changes and how to upgrade. This list shows all the versions which include breaking changes and how to upgrade.
## 0.171.0
### What changed?
The GraphQL node now errors when the response includes an error.
### When is action necessary?
If you are using the GraphQL node.
### How to upgrade:
Go to the workflows that use the GraphQL node and adjust them to the new behavior. If you want to continue even on error, you can set "Continue on Fail" to true.
## 0.165.0 ## 0.165.0
### What changed? ### What changed?
The Hive node now correctly rejects invalid SSL certificates when the "Ignore SSL Issues" option is set to False. The Hive node now correctly rejects invalid SSL certificates when the "Ignore SSL Issues" option is set to False.
### When is action necassary? ### When is action necessary?
If you are using a self signed certificate with The Hive. If you are using a self signed certificate with The Hive.
@ -28,7 +42,7 @@ If you are using the Hubspot Trigger.
### How to upgrade: ### How to upgrade:
Create an app in HubSpot, use the Client ID, Client Secret, App ID, and the Developer Key, and complete the OAuth2 flow. Create an app in HubSpot, use the Client ID, Client Secret, App ID, and the Developer Key, and complete the OAuth2 flow.
## 0.135.0 ## 0.135.0
@ -59,15 +73,14 @@ const credentials = await this.getCredentials(myNodeCredentials);
Example: Example:
```typescript ```typescript
const items = this.getInputData(); const items = this.getInputData();
for (const i = 0; i < items.length; i++) { for (const i = 0; i < items.length; i++) {
const item = items[i].binary as IBinaryKeyData; const item = items[i].binary as IBinaryKeyData;
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string; const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string;
const binaryData = item[binaryPropertyName] as IBinaryData; const binaryData = item[binaryPropertyName] as IBinaryData;
// Before 0.135.0: // Before 0.135.0:
const binaryDataBuffer = Buffer.from(binaryData.data, BINARY_ENCODING); const binaryDataBuffer = Buffer.from(binaryData.data, BINARY_ENCODING);
// From 0.135.0: // From 0.135.0:
const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName); const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
} }
@ -106,17 +119,21 @@ If you are using `lead:create` with "Company" or "Address", reset the parameters
## 0.118.0 ## 0.118.0
### What changed? ### What changed?
The minimum Node.js version required for n8n is now v14. The minimum Node.js version required for n8n is now v14.
### When is action necessary? ### When is action necessary?
If you're using n8n via npm or PM2 or if you're contributing to n8n. If you're using n8n via npm or PM2 or if you're contributing to n8n.
### How to upgrade: ### How to upgrade:
Update the Node.js version to v14 or above. Update the Node.js version to v14 or above.
---------------------------- ---
### What changed? ### What changed?
In the Postgres, CrateDB, QuestDB and TimescaleDB nodes the `Execute Query` operation returns the result from all queries executed instead of just one of the results. In the Postgres, CrateDB, QuestDB and TimescaleDB nodes the `Execute Query` operation returns the result from all queries executed instead of just one of the results.
### When is action necessary? ### When is action necessary?
@ -126,6 +143,7 @@ If you use any of the above mentioned nodes with the `Execute Query` operation a
## 0.117.0 ## 0.117.0
### What changed? ### What changed?
Removed the "Activation Trigger" node. This node was replaced by two other nodes. Removed the "Activation Trigger" node. This node was replaced by two other nodes.
The "Activation Trigger" node was added on version 0.113.0 but was not fully compliant to UX, so we decided to refactor and change it ASAP so it affects the least possible users. The "Activation Trigger" node was added on version 0.113.0 but was not fully compliant to UX, so we decided to refactor and change it ASAP so it affects the least possible users.
@ -140,7 +158,7 @@ If you use the "Activation Trigger" in any of your workflows, please replace it
Remove the previous node and add the new ones according to your workflows. Remove the previous node and add the new ones according to your workflows.
---------------------------- ---
Changed the behavior for nodes that use Postgres Wire Protocol: Postgres, QuestDB, CrateDB and TimescaleDB. Changed the behavior for nodes that use Postgres Wire Protocol: Postgres, QuestDB, CrateDB and TimescaleDB.
@ -158,10 +176,10 @@ By default, all `insert` operations will have `Return fields: *` as the default,
Previously, the node would return all information it received, without taking into account what actually happened in the database. Previously, the node would return all information it received, without taking into account what actually happened in the database.
## 0.113.0 ## 0.113.0
### What changed? ### What changed?
In the Dropbox node, both credential types (Access Token & OAuth2) have a new parameter called "APP Access Type". In the Dropbox node, both credential types (Access Token & OAuth2) have a new parameter called "APP Access Type".
### When is action necessary? ### When is action necessary?
@ -175,6 +193,7 @@ Open your Dropbox node's credentials and set the "APP Access Type" parameter to
## 0.111.0 ## 0.111.0
### What changed? ### What changed?
In the Dropbox node, now all operations are performed relative to the user's root directory. In the Dropbox node, now all operations are performed relative to the user's root directory.
### When is action necessary? ### When is action necessary?
@ -192,24 +211,29 @@ Also, if you are using the `folder:list` operation, make sure your logic is taki
## 0.105.0 ## 0.105.0
### What changed? ### What changed?
In the Hubspot Trigger, now multiple events can be provided and the field `App ID` was so moved to the credentials. In the Hubspot Trigger, now multiple events can be provided and the field `App ID` was so moved to the credentials.
### When is action necessary? ### When is action necessary?
If you are using the Hubspot Trigger node. If you are using the Hubspot Trigger node.
### How to upgrade: ### How to upgrade:
Open the Hubspot Trigger and set the events again. Also open the credentials `Hubspot Developer API` and set your APP ID.
Open the Hubspot Trigger and set the events again. Also open the credentials `Hubspot Developer API` and set your APP ID.
## 0.104.0 ## 0.104.0
### What changed? ### What changed?
Support for MongoDB as a database for n8n has been dropped as MongoDB had problems saving large amounts of data in a document, among other issues. Support for MongoDB as a database for n8n has been dropped as MongoDB had problems saving large amounts of data in a document, among other issues.
### When is action necessary? ### When is action necessary?
If you have been using MongoDB as a database for n8n. Please note that this is not related to the MongoDB node. If you have been using MongoDB as a database for n8n. Please note that this is not related to the MongoDB node.
### How to upgrade: ### How to upgrade:
Before upgrading, you can [export](https://docs.n8n.io/reference/start-workflows-via-cli.html#export-workflows-and-credentials) all your credentials and workflows using the CLI. Before upgrading, you can [export](https://docs.n8n.io/reference/start-workflows-via-cli.html#export-workflows-and-credentials) all your credentials and workflows using the CLI.
``` ```
@ -227,21 +251,26 @@ n8n import:credentials --separate --input=backups/latest/
## 0.102.0 ## 0.102.0
### What changed? ### What changed?
- The `As User` property and the `User Name` field got combined and renamed to `Send as User`. It also got moved under “Add Options”.
- The `As User` property and the `User Name` field got combined and renamed to `Send as User`. It also got moved under “Add Options”.
- The `Ephemeral` property got removed. To send an ephemeral message, you have to select the "Post (Ephemeral)" operation. - The `Ephemeral` property got removed. To send an ephemeral message, you have to select the "Post (Ephemeral)" operation.
### When is action necessary? ### When is action necessary?
If you are using the following fields or properties in the Slack node: If you are using the following fields or properties in the Slack node:
- As User - As User
- Ephemeral - Ephemeral
- User Name - User Name
### How to upgrade: ### How to upgrade:
Open the Slack node and set them again to the appropriate values. Open the Slack node and set them again to the appropriate values.
---------------------------- ---
### What changed? ### What changed?
If you have a question in Typeform that uses a previously answered question as part of its text, the question text would look like this in the Typeform Trigger node: If you have a question in Typeform that uses a previously answered question as part of its text, the question text would look like this in the Typeform Trigger node:
`You have chosen {{field:23234242}} as your answer. Is this correct?` `You have chosen {{field:23234242}} as your answer. Is this correct?`
@ -251,9 +280,11 @@ Those curly braces broke the expression editor. The change makes it now display
`You have chosen [field:23234242] as your answer. Is this correct?` `You have chosen [field:23234242] as your answer. Is this correct?`
### When is action necessary? ### When is action necessary?
If you are using the Typeform Trigger node with questions using the [Recall information](https://help.typeform.com/hc/en-us/articles/360050447072-What-is-Recall-information-) feature. If you are using the Typeform Trigger node with questions using the [Recall information](https://help.typeform.com/hc/en-us/articles/360050447072-What-is-Recall-information-) feature.
### How to upgrade: ### How to upgrade:
In workflows using the Typeform Trigger node, nodes that reference such key names (questions that use a previously answered question as part of its text) will need to be updated. In workflows using the Typeform Trigger node, nodes that reference such key names (questions that use a previously answered question as part of its text) will need to be updated.
## 0.95.0 ## 0.95.0
@ -281,11 +312,11 @@ In the Segment Node, we have changed how the properties 'traits' and 'properties
When the properties 'traits' or 'properties' are set, and one of the following resources/operations is used: When the properties 'traits' or 'properties' are set, and one of the following resources/operations is used:
| Resource | Operation | | Resource | Operation |
|--|--| | -------- | --------- |
| Identify | Create | | Identify | Create |
| Track | Event | | Track | Event |
| Track | Page | | Track | Page |
| Group | Add | | Group | Add |
### How to upgrade: ### How to upgrade:
@ -305,7 +336,6 @@ If you had set "Basic Auth" for the "Authentication" field in the node.
The "Authentication" field has been renamed to "Incoming Authentication". Please set the parameter “Incoming Authentication” to “Basic Auth” to activate it again. The "Authentication" field has been renamed to "Incoming Authentication". Please set the parameter “Incoming Authentication” to “Basic Auth” to activate it again.
## 0.90.0 ## 0.90.0
### What changed? ### What changed?
@ -320,7 +350,6 @@ If you are running Node.js version older than 12.9.
You can find download and install the latest version of Node.js from [here](https://nodejs.org/en/download/). You can find download and install the latest version of Node.js from [here](https://nodejs.org/en/download/).
## 0.87.0 ## 0.87.0
### What changed? ### What changed?
@ -335,7 +364,6 @@ If you are are actively using the link.fish node.
Unfortunately, that's not possible. We'd recommend you to look for an alternative service. Unfortunately, that's not possible. We'd recommend you to look for an alternative service.
## 0.83.0 ## 0.83.0
### What changed? ### What changed?
@ -346,13 +374,13 @@ In the Active Campaign Node, we have changed how the `getAll` operation works wi
When one of the following resources/operations is used: When one of the following resources/operations is used:
| Resource | Operation | | Resource | Operation |
|--|--| | ------------------------- | --------- |
| Deal | Get All | | Deal | Get All |
| Connector | Get All | | Connector | Get All |
| E-commerce Order | Get All | | E-commerce Order | Get All |
| E-commerce Customer | Get All | | E-commerce Customer | Get All |
| E-commerce Order Products | Get All | | E-commerce Order Products | Get All |
### How to upgrade: ### How to upgrade:
@ -393,7 +421,6 @@ If you have used the Attachments option in your Twitter nodes.
You'll need to re-create the attachments for the Twitter node. You'll need to re-create the attachments for the Twitter node.
## 0.68.0 ## 0.68.0
### What changed? ### What changed?
@ -413,36 +440,39 @@ All values that get referenced which were before under the property "channel" ar
This means that these expressions have to get adjusted. This means that these expressions have to get adjusted.
Meaning if the expression used before was: Meaning if the expression used before was:
``` ```
{{ $node["Slack"].data["channel"]["id"] }} {{ $node["Slack"].data["channel"]["id"] }}
``` ```
it has to get changed to: it has to get changed to:
``` ```
{{ $node["Slack"].data["id"] }} {{ $node["Slack"].data["id"] }}
``` ```
## 0.67.0 ## 0.67.0
### What changed? ### What changed?
The names of the following nodes were not set correctly and got fixed: The names of the following nodes were not set correctly and got fixed:
- AMQP Sender
- Bitbucket-Trigger - AMQP Sender
- Coda - Bitbucket-Trigger
- Eventbrite-Trigger - Coda
- Flow - Eventbrite-Trigger
- Flow-Trigger - Flow
- Gumroad-Trigger - Flow-Trigger
- Jira - Gumroad-Trigger
- Mailchimp-Trigger - Jira
- PayPal Trigger - Mailchimp-Trigger
- Read PDF - PayPal Trigger
- Rocketchat - Read PDF
- Shopify - Rocketchat
- Shopify-Trigger - Shopify
- Stripe-Trigger - Shopify-Trigger
- Toggl-Trigger - Stripe-Trigger
- Toggl-Trigger
### When is action necessary? ### When is action necessary?
@ -454,32 +484,32 @@ For the nodes mentioned above, you'll need to give them access to the credential
**Simple** **Simple**
- Note down the settings of the nodes before upgrading - Note down the settings of the nodes before upgrading
- After upgrading, delete the nodes mentioned above from your workflow, and recreate them - After upgrading, delete the nodes mentioned above from your workflow, and recreate them
**Advanced** **Advanced**
After upgrading, select the whole workflow in the editor, copy it, and paste it into a text editor. In the JSON, change the node types manually by replacing the values for "type" as follows: After upgrading, select the whole workflow in the editor, copy it, and paste it into a text editor. In the JSON, change the node types manually by replacing the values for "type" as follows:
- "n8n-nodes-base.amqpSender" -> "n8n-nodes-base.amqp"
- "n8n-nodes-base.bitbucket" -> "n8n-nodes-base.bitbucketTrigger" - "n8n-nodes-base.amqpSender" -> "n8n-nodes-base.amqp"
- "n8n-nodes-base.Coda" -> "n8n-nodes-base.coda" - "n8n-nodes-base.bitbucket" -> "n8n-nodes-base.bitbucketTrigger"
- "n8n-nodes-base.eventbrite" -> "n8n-nodes-base.eventbriteTrigger" - "n8n-nodes-base.Coda" -> "n8n-nodes-base.coda"
- "n8n-nodes-base.Flow" -> "n8n-nodes-base.flow" - "n8n-nodes-base.eventbrite" -> "n8n-nodes-base.eventbriteTrigger"
- "n8n-nodes-base.flow" -> "n8n-nodes-base.flowTrigger" - "n8n-nodes-base.Flow" -> "n8n-nodes-base.flow"
- "n8n-nodes-base.gumroad" -> "n8n-nodes-base.gumroadTrigger" - "n8n-nodes-base.flow" -> "n8n-nodes-base.flowTrigger"
- "n8n-nodes-base.Jira Software Cloud" -> "n8n-nodes-base.jira" - "n8n-nodes-base.gumroad" -> "n8n-nodes-base.gumroadTrigger"
- "n8n-nodes-base.Mailchimp" -> "n8n-nodes-base.mailchimpTrigger" - "n8n-nodes-base.Jira Software Cloud" -> "n8n-nodes-base.jira"
- "n8n-nodes-base.PayPal" -> "n8n-nodes-base.payPalTrigger" - "n8n-nodes-base.Mailchimp" -> "n8n-nodes-base.mailchimpTrigger"
- "n8n-nodes-base.Read PDF" -> "n8n-nodes-base.readPDF" - "n8n-nodes-base.PayPal" -> "n8n-nodes-base.payPalTrigger"
- "n8n-nodes-base.Rocketchat" -> "n8n-nodes-base.rocketchat" - "n8n-nodes-base.Read PDF" -> "n8n-nodes-base.readPDF"
- "n8n-nodes-base.shopify" -> "n8n-nodes-base.shopifyTrigger" - "n8n-nodes-base.Rocketchat" -> "n8n-nodes-base.rocketchat"
- "n8n-nodes-base.shopifyNode" -> "n8n-nodes-base.shopify" - "n8n-nodes-base.shopify" -> "n8n-nodes-base.shopifyTrigger"
- "n8n-nodes-base.stripe" -> "n8n-nodes-base.stripeTrigger" - "n8n-nodes-base.shopifyNode" -> "n8n-nodes-base.shopify"
- "n8n-nodes-base.toggl" -> "n8n-nodes-base.togglTrigger" - "n8n-nodes-base.stripe" -> "n8n-nodes-base.stripeTrigger"
- "n8n-nodes-base.toggl" -> "n8n-nodes-base.togglTrigger"
Then delete all existing nodes, and then paste the changed JSON directly into n8n. It should then recreate all the nodes and connections again, this time with working nodes. Then delete all existing nodes, and then paste the changed JSON directly into n8n. It should then recreate all the nodes and connections again, this time with working nodes.
## 0.62.0 ## 0.62.0
### What changed? ### What changed?
@ -496,7 +526,6 @@ If "evaluateExpression(...)" gets used in any Function or FunctionItem Node.
Simply replace the "evaluateExpression(...)" with "$evaluateExpression(...)". Simply replace the "evaluateExpression(...)" with "$evaluateExpression(...)".
## 0.52.0 ## 0.52.0
### What changed? ### What changed?
@ -517,7 +546,6 @@ Open the "Date & Time"-Nodes and reference the date that should be converted
via an expression. Also, set the "Property Name" to the name of the property the via an expression. Also, set the "Property Name" to the name of the property the
converted date should be set on. converted date should be set on.
## 0.37.0 ## 0.37.0
### What changed? ### What changed?
@ -534,7 +562,6 @@ When you currently use the Rocketchat-Node.
Open the Rocketchat credentials and fill the parameter `domain`. If you had previously the Open the Rocketchat credentials and fill the parameter `domain`. If you had previously the
subdomain "example" set you have to set now "https://example.rocket.chat". subdomain "example" set you have to set now "https://example.rocket.chat".
## 0.19.0 ## 0.19.0
### What changed? ### What changed?
@ -556,9 +583,7 @@ it and paste it in a text-editor, it will display all the data the node
contained). Then set the "Response Format" to "File". Everything will then contained). Then set the "Response Format" to "File". Everything will then
function again like before. function again like before.
---
----------------------------
### What changed? ### What changed?
@ -576,7 +601,6 @@ When "HTTP Request" nodes get used which have "Response Format" set to "String".
After upgrading open all workflows which contain the concerning Nodes and set After upgrading open all workflows which contain the concerning Nodes and set
"Binary Property" to "response". "Binary Property" to "response".
## 0.18.0 ## 0.18.0
### What changed? ### What changed?
@ -591,8 +615,7 @@ When Webhook-Nodes get used which have "Response Mode" set to "Last Node".
After upgrading open all workflows which contain the concerning Webhook-Nodes and set "Response Mode" again manually to "Last Node". After upgrading open all workflows which contain the concerning Webhook-Nodes and set "Response Mode" again manually to "Last Node".
---
----------------------------
### What changed? ### What changed?
@ -603,6 +626,7 @@ packages with security vulnerabilities we had to switch to a different one.
When you currently start n8n in your setup directly via its JavaScript file. When you currently start n8n in your setup directly via its JavaScript file.
For example like this: For example like this:
``` ```
/usr/local/bin/node ./dist/index.js start /usr/local/bin/node ./dist/index.js start
``` ```
@ -610,6 +634,7 @@ For example like this:
### How to upgrade: ### How to upgrade:
Change the path to its new location: Change the path to its new location:
``` ```
/usr/local/bin/node bin/n8n start /usr/local/bin/node bin/n8n start
``` ```

View file

@ -12,7 +12,7 @@ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensa
## Limitations ## Limitations
You may use or modify the software only for your own internal business purposes or for non-commercial or personal use You may use or modify the software only for your own internal business purposes or for non-commercial or personal use.
You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes. You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes.
You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law. You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law.

View file

@ -146,7 +146,7 @@ export class ImportCredentialsCommand extends Command {
} }
private reportSuccess(total: number) { private reportSuccess(total: number) {
console.info(`Successfully imported ${total} ${total === 1 ? 'workflow.' : 'workflows.'}`); console.info(`Successfully imported ${total} ${total === 1 ? 'credential.' : 'credentials.'}`);
} }
private async initOwnerCredentialRole() { private async initOwnerCredentialRole() {

View file

@ -119,9 +119,14 @@ export class Worker extends Command {
async runJob(job: Bull.Job, nodeTypes: INodeTypes): Promise<IBullJobResponse> { async runJob(job: Bull.Job, nodeTypes: INodeTypes): Promise<IBullJobResponse> {
const jobData = job.data as IBullJobData; const jobData = job.data as IBullJobData;
const executionDb = (await Db.collections.Execution!.findOne( const executionDb = await Db.collections.Execution!.findOne(jobData.executionId);
jobData.executionId,
)) as IExecutionFlattedDb; if (!executionDb) {
LoggerProxy.error('Worker failed to find execution data in database. Cannot continue.', {
executionId: jobData.executionId,
});
throw new Error('Unable to find execution data in database. Aborting execution.');
}
const currentExecutionDb = ResponseHelper.unflattenExecutionData(executionDb); const currentExecutionDb = ResponseHelper.unflattenExecutionData(executionDb);
LoggerProxy.info( LoggerProxy.info(
`Start job: ${job.id} (Workflow ID: ${currentExecutionDb.workflowData.id} | Execution: ${jobData.executionId})`, `Start job: ${job.id} (Workflow ID: ${currentExecutionDb.workflowData.id} | Execution: ${jobData.executionId})`,
@ -139,6 +144,13 @@ export class Worker extends Command {
findOptions, findOptions,
); );
if (workflowData === undefined) { if (workflowData === undefined) {
LoggerProxy.error(
'Worker execution failed because workflow could not be found in database.',
{
workflowId: currentExecutionDb.workflowData.id,
executionId: jobData.executionId,
},
);
throw new Error( throw new Error(
`The workflow with the ID "${currentExecutionDb.workflowData.id}" could not be found`, `The workflow with the ID "${currentExecutionDb.workflowData.id}" could not be found`,
); );

View file

@ -728,7 +728,7 @@ const config = convict({
logs: { logs: {
level: { level: {
doc: 'Log output level', doc: 'Log output level',
format: ['error', 'warn', 'info', 'verbose', 'debug'], format: ['error', 'warn', 'info', 'verbose', 'debug', 'silent'],
default: 'info', default: 'info',
env: 'N8N_LOG_LEVEL', env: 'N8N_LOG_LEVEL',
}, },

View file

@ -1,6 +1,6 @@
{ {
"name": "n8n", "name": "n8n",
"version": "0.169.0", "version": "0.171.0",
"description": "n8n Workflow Automation Tool", "description": "n8n Workflow Automation Tool",
"license": "SEE LICENSE IN LICENSE.md", "license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io", "homepage": "https://n8n.io",
@ -30,7 +30,7 @@
"start:default": "cd bin && ./n8n", "start:default": "cd bin && ./n8n",
"start:windows": "cd bin && n8n", "start:windows": "cd bin && n8n",
"test": "npm run test:sqlite", "test": "npm run test:sqlite",
"test:sqlite": "export DB_TYPE=sqlite && jest --forceExit", "test:sqlite": "export N8N_LOG_LEVEL='silent'; export DB_TYPE=sqlite; jest",
"test:postgres": "export DB_TYPE=postgresdb && jest", "test:postgres": "export DB_TYPE=postgresdb && jest",
"test:mysql": "export DB_TYPE=mysqldb && jest", "test:mysql": "export DB_TYPE=mysqldb && jest",
"watch": "tsc --watch", "watch": "tsc --watch",
@ -125,10 +125,10 @@
"lodash.get": "^4.4.2", "lodash.get": "^4.4.2",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"mysql2": "~2.3.0", "mysql2": "~2.3.0",
"n8n-core": "~0.110.0", "n8n-core": "~0.112.0",
"n8n-editor-ui": "~0.136.0", "n8n-editor-ui": "~0.138.0",
"n8n-nodes-base": "~0.167.0", "n8n-nodes-base": "~0.169.0",
"n8n-workflow": "~0.92.0", "n8n-workflow": "~0.94.0",
"nodemailer": "^6.7.1", "nodemailer": "^6.7.1",
"oauth-1.0a": "^2.2.6", "oauth-1.0a": "^2.2.6",
"open": "^7.0.0", "open": "^7.0.0",

View file

@ -682,6 +682,15 @@ export class ActiveWorkflowRunner {
(error) => console.error(error), (error) => console.error(error),
); );
}; };
returnFunctions.emitError = async (error: Error): Promise<void> => {
await this.activeWorkflows?.remove(workflowData.id.toString());
this.activationErrors[workflowData.id.toString()] = {
time: new Date().getTime(),
error: {
message: error.message,
},
};
};
return returnFunctions; return returnFunctions;
}; };
} }

View file

@ -11,14 +11,15 @@ class Logger implements ILogger {
private logger: winston.Logger; private logger: winston.Logger;
constructor() { constructor() {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const level = config.get('logs.level') as string;
const level = config.get('logs.level');
// eslint-disable-next-line @typescript-eslint/no-shadow // eslint-disable-next-line @typescript-eslint/no-shadow
const output = (config.get('logs.output') as string).split(',').map((output) => output.trim()); const output = (config.get('logs.output') as string).split(',').map((output) => output.trim());
this.logger = winston.createLogger({ this.logger = winston.createLogger({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
level, level,
silent: level === 'silent',
}); });
if (output.includes('console')) { if (output.includes('console')) {

View file

@ -96,15 +96,13 @@ export function sendSuccessResponse(
} }
} }
export function sendErrorResponse(res: Response, error: ResponseError, shouldLog = true) { export function sendErrorResponse(res: Response, error: ResponseError) {
let httpStatusCode = 500; let httpStatusCode = 500;
if (error.httpStatusCode) { if (error.httpStatusCode) {
httpStatusCode = error.httpStatusCode; httpStatusCode = error.httpStatusCode;
} }
shouldLog = !process.argv[1].split('/').includes('jest'); if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
if (process.env.NODE_ENV !== 'production' && shouldLog) {
console.error('ERROR RESPONSE'); console.error('ERROR RESPONSE');
console.error(error); console.error(error);
} }

View file

@ -4,8 +4,10 @@
import { Workflow } from 'n8n-workflow'; import { Workflow } from 'n8n-workflow';
import { In, IsNull, Not } from 'typeorm'; import { In, IsNull, Not } from 'typeorm';
import express = require('express'); import express = require('express');
import { compare } from 'bcryptjs';
import { PublicUser } from './Interfaces'; import { PublicUser } from './Interfaces';
import { Db, GenericHelpers, ResponseHelper } from '..'; import { Db, ResponseHelper } from '..';
import { MAX_PASSWORD_LENGTH, MIN_PASSWORD_LENGTH, User } from '../databases/entities/User'; import { MAX_PASSWORD_LENGTH, MIN_PASSWORD_LENGTH, User } from '../databases/entities/User';
import { Role } from '../databases/entities/Role'; import { Role } from '../databases/entities/Role';
import { AuthenticatedRequest } from '../requests'; import { AuthenticatedRequest } from '../requests';
@ -216,3 +218,20 @@ export function isPostUsersId(req: express.Request, restEndpoint: string): boole
export function isAuthenticatedRequest(request: express.Request): request is AuthenticatedRequest { export function isAuthenticatedRequest(request: express.Request): request is AuthenticatedRequest {
return request.user !== undefined; return request.user !== undefined;
} }
// ----------------------------------
// hashing
// ----------------------------------
export async function compareHash(str: string, hash: string): Promise<boolean | undefined> {
try {
return await compare(str, hash);
} catch (error) {
if (error instanceof Error && error.message.includes('Invalid salt version')) {
error.message +=
'. Comparison against unhashed string. Please check that the value compared against has been hashed.';
}
throw new Error(error);
}
}

View file

@ -3,13 +3,12 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { compare } from 'bcryptjs';
import { IDataObject } from 'n8n-workflow'; import { IDataObject } from 'n8n-workflow';
import { Db, ResponseHelper } from '../..'; import { Db, ResponseHelper } from '../..';
import { AUTH_COOKIE_NAME } from '../../constants'; import { AUTH_COOKIE_NAME } from '../../constants';
import { issueCookie, resolveJwt } from '../auth/jwt'; import { issueCookie, resolveJwt } from '../auth/jwt';
import { N8nApp, PublicUser } from '../Interfaces'; import { N8nApp, PublicUser } from '../Interfaces';
import { isInstanceOwnerSetup, sanitizeUser } from '../UserManagementHelper'; import { compareHash, isInstanceOwnerSetup, sanitizeUser } from '../UserManagementHelper';
import { User } from '../../databases/entities/User'; import { User } from '../../databases/entities/User';
import type { LoginRequest } from '../../requests'; import type { LoginRequest } from '../../requests';
@ -43,7 +42,8 @@ export function authenticationMethods(this: N8nApp): void {
} catch (error) { } catch (error) {
throw new Error('Unable to access database.'); throw new Error('Unable to access database.');
} }
if (!user || !user.password || !(await compare(req.body.password, user.password))) {
if (!user || !user.password || !(await compareHash(req.body.password, user.password))) {
// password is empty until user signs up // password is empty until user signs up
const error = new Error('Wrong username or password. Do you have caps lock on?'); const error = new Error('Wrong username or password. Do you have caps lock on?');
// @ts-ignore // @ts-ignore

View file

@ -551,6 +551,7 @@ export async function executeWebhook(
if (returnData.data!.main[0]![0] === undefined) { if (returnData.data!.main[0]![0] === undefined) {
responseCallback(new Error('No item to return got found.'), {}); responseCallback(new Error('No item to return got found.'), {});
didSendResponse = true; didSendResponse = true;
return undefined;
} }
data = returnData.data!.main[0]![0].json; data = returnData.data!.main[0]![0].json;
@ -602,11 +603,13 @@ export async function executeWebhook(
if (data === undefined) { if (data === undefined) {
responseCallback(new Error('No item to return got found.'), {}); responseCallback(new Error('No item to return got found.'), {});
didSendResponse = true; didSendResponse = true;
return undefined;
} }
if (data.binary === undefined) { if (data.binary === undefined) {
responseCallback(new Error('No binary data to return got found.'), {}); responseCallback(new Error('No binary data to return got found.'), {});
didSendResponse = true; didSendResponse = true;
return undefined;
} }
const responseBinaryPropertyName = workflow.expression.getSimpleParameterValue( const responseBinaryPropertyName = workflow.expression.getSimpleParameterValue(

View file

@ -12,6 +12,8 @@ import { randomEmail, randomValidPassword, randomName } from './shared/random';
import { getGlobalOwnerRole } from './shared/testDb'; import { getGlobalOwnerRole } from './shared/testDb';
import * as testDb from './shared/testDb'; import * as testDb from './shared/testDb';
jest.mock('../../src/telemetry');
let globalOwnerRole: Role; let globalOwnerRole: Role;
let app: express.Application; let app: express.Application;
@ -35,7 +37,7 @@ beforeEach(async () => {
email: TEST_USER.email, email: TEST_USER.email,
firstName: TEST_USER.firstName, firstName: TEST_USER.firstName,
lastName: TEST_USER.lastName, lastName: TEST_USER.lastName,
password: hashSync(TEST_USER.password, genSaltSync(10)), password: TEST_USER.password,
globalRole: globalOwnerRole, globalRole: globalOwnerRole,
}); });

View file

@ -1,14 +1,16 @@
import express = require('express'); import express = require('express');
import * as request from 'supertest'; import * as request from 'supertest';
import { import {
REST_PATH_SEGMENT, REST_PATH_SEGMENT,
ROUTES_REQUIRING_AUTHORIZATION, ROUTES_REQUIRING_AUTHORIZATION,
ROUTES_REQUIRING_AUTHENTICATION, ROUTES_REQUIRING_AUTHENTICATION,
} from './shared/constants'; } from './shared/constants';
import * as utils from './shared/utils'; import * as utils from './shared/utils';
import * as testDb from './shared/testDb'; import * as testDb from './shared/testDb';
jest.mock('../../src/telemetry');
let app: express.Application; let app: express.Application;
let testDbName = ''; let testDbName = '';

View file

@ -8,6 +8,8 @@ import { Role } from '../../src/databases/entities/Role';
import { User } from '../../src/databases/entities/User'; import { User } from '../../src/databases/entities/User';
import * as testDb from './shared/testDb'; import * as testDb from './shared/testDb';
jest.mock('../../src/telemetry');
let app: express.Application; let app: express.Application;
let testDbName = ''; let testDbName = '';
let saveCredential: SaveCredentialFunction; let saveCredential: SaveCredentialFunction;

View file

@ -10,6 +10,8 @@ import { Role } from '../../src/databases/entities/Role';
import { randomValidPassword, randomEmail, randomName, randomString } from './shared/random'; import { randomValidPassword, randomEmail, randomName, randomString } from './shared/random';
import * as testDb from './shared/testDb'; import * as testDb from './shared/testDb';
jest.mock('../../src/telemetry');
let app: express.Application; let app: express.Application;
let testDbName = ''; let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
@ -275,7 +277,7 @@ describe('Member', () => {
test('PATCH /me/password should succeed with valid inputs', async () => { test('PATCH /me/password should succeed with valid inputs', async () => {
const memberPassword = randomValidPassword(); const memberPassword = randomValidPassword();
const member = await testDb.createUser({ const member = await testDb.createUser({
password: hashSync(memberPassword, genSaltSync(10)), password: memberPassword,
}); });
const authMemberAgent = utils.createAgent(app, { auth: true, user: member }); const authMemberAgent = utils.createAgent(app, { auth: true, user: member });

View file

@ -12,6 +12,8 @@ import {
randomInvalidPassword, randomInvalidPassword,
} from './shared/random'; } from './shared/random';
jest.mock('../../src/telemetry');
let app: express.Application; let app: express.Application;
let testDbName = ''; let testDbName = '';
@ -82,7 +84,7 @@ test('POST /owner should create owner and enable isInstanceOwnerSetUp', async ()
test('POST /owner should fail with invalid inputs', async () => { test('POST /owner should fail with invalid inputs', async () => {
const owner = await Db.collections.User!.findOneOrFail(); const owner = await Db.collections.User!.findOneOrFail();
const authOwnerAgent = await utils.createAgent(app, { auth: true, user: owner }); const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
for (const invalidPayload of INVALID_POST_OWNER_PAYLOADS) { for (const invalidPayload of INVALID_POST_OWNER_PAYLOADS) {
const response = await authOwnerAgent.post('/owner').send(invalidPayload); const response = await authOwnerAgent.post('/owner').send(invalidPayload);
@ -92,7 +94,7 @@ test('POST /owner should fail with invalid inputs', async () => {
test('POST /owner/skip-setup should persist skipping setup to the DB', async () => { test('POST /owner/skip-setup should persist skipping setup to the DB', async () => {
const owner = await Db.collections.User!.findOneOrFail(); const owner = await Db.collections.User!.findOneOrFail();
const authOwnerAgent = await utils.createAgent(app, { auth: true, user: owner }); const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
const response = await authOwnerAgent.post('/owner/skip-setup').send(); const response = await authOwnerAgent.post('/owner/skip-setup').send();

View file

@ -14,6 +14,8 @@ import {
import { Role } from '../../src/databases/entities/Role'; import { Role } from '../../src/databases/entities/Role';
import * as testDb from './shared/testDb'; import * as testDb from './shared/testDb';
jest.mock('../../src/telemetry');
let app: express.Application; let app: express.Application;
let globalOwnerRole: Role; let globalOwnerRole: Role;
let testDbName = ''; let testDbName = '';

View file

@ -17,7 +17,7 @@ const randomDigit = () => Math.floor(Math.random() * 10);
const randomUppercaseLetter = () => chooseRandomly('ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')); const randomUppercaseLetter = () => chooseRandomly('ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''));
export const randomValidPassword = () => export const randomValidPassword = () =>
randomString(MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH) + randomUppercaseLetter() + randomDigit(); randomString(MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH - 2) + randomUppercaseLetter() + randomDigit();
export const randomInvalidPassword = () => export const randomInvalidPassword = () =>
chooseRandomly([ chooseRandomly([

View file

@ -16,6 +16,7 @@ import { sqliteMigrations } from '../../../src/databases/sqlite/migrations';
import type { Role } from '../../../src/databases/entities/Role'; import type { Role } from '../../../src/databases/entities/Role';
import type { User } from '../../../src/databases/entities/User'; import type { User } from '../../../src/databases/entities/User';
import type { CredentialPayload } from './types'; import type { CredentialPayload } from './types';
import { genSaltSync, hashSync } from 'bcryptjs';
/** /**
* Initialize one test DB per suite run, with bootstrap connection if needed. * Initialize one test DB per suite run, with bootstrap connection if needed.
@ -188,7 +189,7 @@ export async function createUser(attributes: Partial<User> = {}): Promise<User>
const { email, password, firstName, lastName, globalRole, ...rest } = attributes; const { email, password, firstName, lastName, globalRole, ...rest } = attributes;
const user = { const user = {
email: email ?? randomEmail(), email: email ?? randomEmail(),
password: password ?? randomValidPassword(), password: hashSync(password ?? randomValidPassword(), genSaltSync(10)),
firstName: firstName ?? randomName(), firstName: firstName ?? randomName(),
lastName: lastName ?? randomName(), lastName: lastName ?? randomName(),
globalRole: globalRole ?? (await getGlobalMemberRole()), globalRole: globalRole ?? (await getGlobalMemberRole()),

View file

@ -89,12 +89,13 @@ export function initTestServer({
return testServer.app; return testServer.app;
} }
/**
* Pre-requisite: Mock the telemetry module before calling.
*/
export function initTestTelemetry() { export function initTestTelemetry() {
const mockNodeTypes = { nodeTypes: {} } as INodeTypes; const mockNodeTypes = { nodeTypes: {} } as INodeTypes;
void InternalHooksManager.init('test-instance-id', 'test-version', mockNodeTypes); void InternalHooksManager.init('test-instance-id', 'test-version', mockNodeTypes);
jest.spyOn(Telemetry.prototype, 'track').mockResolvedValue();
} }
/** /**
@ -117,10 +118,9 @@ const classifyEndpointGroups = (endpointGroups: string[]) => {
// ---------------------------------- // ----------------------------------
/** /**
* Initialize a silent logger for test runs. * Initialize a logger for test runs.
*/ */
export function initTestLogger() { export function initTestLogger() {
config.set('logs.output', 'file'); // declutter console output
LoggerProxy.init(getLogger()); LoggerProxy.init(getLogger());
} }

View file

@ -1,7 +1,7 @@
import express = require('express'); import express = require('express');
import validator from 'validator'; import validator from 'validator';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { compare } from 'bcryptjs'; import { compare, genSaltSync, hashSync } from 'bcryptjs';
import { Db } from '../../src'; import { Db } from '../../src';
import config = require('../../config'); import config = require('../../config');
@ -18,6 +18,8 @@ import { WorkflowEntity } from '../../src/databases/entities/WorkflowEntity';
import * as utils from './shared/utils'; import * as utils from './shared/utils';
import * as testDb from './shared/testDb'; import * as testDb from './shared/testDb';
jest.mock('../../src/telemetry');
let app: express.Application; let app: express.Application;
let testDbName = ''; let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
@ -404,7 +406,7 @@ test('POST /users/:id should fail with invalid inputs', async () => {
} }
}); });
test.skip('POST /users/:id should fail with already accepted invite', async () => { test('POST /users/:id should fail with already accepted invite', async () => {
const authlessAgent = utils.createAgent(app); const authlessAgent = utils.createAgent(app);
const globalMemberRole = await Db.collections.Role!.findOneOrFail({ const globalMemberRole = await Db.collections.Role!.findOneOrFail({
@ -414,7 +416,7 @@ test.skip('POST /users/:id should fail with already accepted invite', async () =
const shell = await Db.collections.User!.save({ const shell = await Db.collections.User!.save({
email: randomEmail(), email: randomEmail(),
password: randomValidPassword(), // simulate accepted invite password: hashSync(randomValidPassword(), genSaltSync(10)), // simulate accepted invite
globalRole: globalMemberRole, globalRole: globalMemberRole,
}); });
@ -424,7 +426,7 @@ test.skip('POST /users/:id should fail with already accepted invite', async () =
inviterId: INITIAL_TEST_USER.id, inviterId: INITIAL_TEST_USER.id,
firstName: randomName(), firstName: randomName(),
lastName: randomName(), lastName: randomName(),
password: newPassword, password: randomValidPassword(),
}); });
expect(response.statusCode).toBe(400); expect(response.statusCode).toBe(400);
@ -458,7 +460,7 @@ test('POST /users should fail if user management is disabled', async () => {
expect(response.statusCode).toBe(500); expect(response.statusCode).toBe(500);
}); });
test.skip('POST /users should email invites and create user shells', async () => { test('POST /users should email invites and create user shells', async () => {
const owner = await Db.collections.User!.findOneOrFail(); const owner = await Db.collections.User!.findOneOrFail();
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner }); const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
@ -502,7 +504,7 @@ test.skip('POST /users should email invites and create user shells', async () =>
} }
}); });
test.skip('POST /users should fail with invalid inputs', async () => { test('POST /users should fail with invalid inputs', async () => {
const owner = await Db.collections.User!.findOneOrFail(); const owner = await Db.collections.User!.findOneOrFail();
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner }); const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
@ -525,7 +527,7 @@ test.skip('POST /users should fail with invalid inputs', async () => {
} }
}); });
test.skip('POST /users should ignore an empty payload', async () => { test('POST /users should ignore an empty payload', async () => {
const owner = await Db.collections.User!.findOneOrFail(); const owner = await Db.collections.User!.findOneOrFail();
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner }); const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });

View file

@ -12,7 +12,7 @@ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensa
## Limitations ## Limitations
You may use or modify the software only for your own internal business purposes or for non-commercial or personal use You may use or modify the software only for your own internal business purposes or for non-commercial or personal use.
You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes. You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes.
You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law. You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law.

View file

@ -1,6 +1,6 @@
{ {
"name": "n8n-core", "name": "n8n-core",
"version": "0.110.0", "version": "0.112.0",
"description": "Core functionality of n8n", "description": "Core functionality of n8n",
"license": "SEE LICENSE IN LICENSE.md", "license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io", "homepage": "https://n8n.io",
@ -52,7 +52,7 @@
"form-data": "^4.0.0", "form-data": "^4.0.0",
"lodash.get": "^4.4.2", "lodash.get": "^4.4.2",
"mime-types": "^2.1.27", "mime-types": "^2.1.27",
"n8n-workflow": "~0.92.0", "n8n-workflow": "~0.94.0",
"oauth-1.0a": "^2.2.6", "oauth-1.0a": "^2.2.6",
"p-cancelable": "^2.0.0", "p-cancelable": "^2.0.0",
"qs": "^6.10.1", "qs": "^6.10.1",

View file

@ -1750,6 +1750,9 @@ export function getExecuteTriggerFunctions(
emit: (data: INodeExecutionData[][]): void => { emit: (data: INodeExecutionData[][]): void => {
throw new Error('Overwrite NodeExecuteFunctions.getExecuteTriggerFunctions.emit function!'); throw new Error('Overwrite NodeExecuteFunctions.getExecuteTriggerFunctions.emit function!');
}, },
emitError: (error: Error): void => {
throw new Error('Overwrite NodeExecuteFunctions.getExecuteTriggerFunctions.emit function!');
},
async getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined> { async getCredentials(type: string): Promise<ICredentialDataDecryptedObject | undefined> {
return getCredentials(workflow, node, type, additionalData, mode); return getCredentials(workflow, node, type, additionalData, mode);
}, },

View file

@ -12,7 +12,7 @@ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensa
## Limitations ## Limitations
You may use or modify the software only for your own internal business purposes or for non-commercial or personal use You may use or modify the software only for your own internal business purposes or for non-commercial or personal use.
You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes. You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes.
You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law. You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law.

View file

@ -1,6 +1,6 @@
{ {
"name": "n8n-design-system", "name": "n8n-design-system",
"version": "0.15.0", "version": "0.16.0",
"license": "SEE LICENSE IN LICENSE.md", "license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io", "homepage": "https://n8n.io",
"author": { "author": {

View file

@ -12,7 +12,7 @@ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensa
## Limitations ## Limitations
You may use or modify the software only for your own internal business purposes or for non-commercial or personal use You may use or modify the software only for your own internal business purposes or for non-commercial or personal use.
You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes. You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes.
You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law. You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law.

View file

@ -1,6 +1,6 @@
{ {
"name": "n8n-editor-ui", "name": "n8n-editor-ui",
"version": "0.136.0", "version": "0.138.0",
"description": "Workflow Editor UI for n8n", "description": "Workflow Editor UI for n8n",
"license": "SEE LICENSE IN LICENSE.md", "license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io", "homepage": "https://n8n.io",
@ -26,7 +26,7 @@
"dependencies": { "dependencies": {
"@fontsource/open-sans": "^4.5.0", "@fontsource/open-sans": "^4.5.0",
"luxon": "^2.3.0", "luxon": "^2.3.0",
"n8n-design-system": "~0.15.0", "n8n-design-system": "~0.16.0",
"monaco-editor": "^0.29.1", "monaco-editor": "^0.29.1",
"timeago.js": "^4.0.2", "timeago.js": "^4.0.2",
"v-click-outside": "^3.1.2", "v-click-outside": "^3.1.2",
@ -77,7 +77,7 @@
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"lodash.get": "^4.4.2", "lodash.get": "^4.4.2",
"lodash.set": "^4.3.2", "lodash.set": "^4.3.2",
"n8n-workflow": "~0.92.0", "n8n-workflow": "~0.94.0",
"monaco-editor-webpack-plugin": "^5.0.0", "monaco-editor-webpack-plugin": "^5.0.0",
"normalize-wheel": "^1.0.1", "normalize-wheel": "^1.0.1",
"prismjs": "^1.17.1", "prismjs": "^1.17.1",

View file

@ -170,6 +170,7 @@
<div :class="$style.binaryButtonContainer"> <div :class="$style.binaryButtonContainer">
<n8n-button size="small" :label="$locale.baseText('runData.showBinaryData')" class="binary-data-show-data-button" @click="displayBinaryData(index, key)" /> <n8n-button size="small" :label="$locale.baseText('runData.showBinaryData')" class="binary-data-show-data-button" @click="displayBinaryData(index, key)" />
<n8n-button v-if="isDownloadable(index, key)" size="small" type="outline" :label="$locale.baseText('runData.downloadBinaryData')" class="binary-data-show-data-button" @click="downloadBinaryData(index, key)" />
</div> </div>
</div> </div>
</div> </div>
@ -197,6 +198,7 @@
import VueJsonPretty from 'vue-json-pretty'; import VueJsonPretty from 'vue-json-pretty';
import { import {
GenericValue, GenericValue,
IBinaryData,
IBinaryKeyData, IBinaryKeyData,
IDataObject, IDataObject,
INodeExecutionData, INodeExecutionData,
@ -231,6 +233,8 @@ import { nodeHelpers } from '@/components/mixins/nodeHelpers';
import mixins from 'vue-typed-mixins'; import mixins from 'vue-typed-mixins';
import Vue from 'vue/types/umd'; import Vue from 'vue/types/umd';
import { saveAs } from 'file-saver';
// A path that does not exist so that nothing is selected by default // A path that does not exist so that nothing is selected by default
const deselectedPlaceholder = '_!^&*'; const deselectedPlaceholder = '_!^&*';
@ -568,6 +572,24 @@ export default mixins(
dataItemClicked (path: string, data: object | number | string) { dataItemClicked (path: string, data: object | number | string) {
this.state.value = data; this.state.value = data;
}, },
isDownloadable (index: number, key: string): boolean {
const binaryDataItem: IBinaryData = this.binaryData[index][key];
return !!(binaryDataItem.mimeType && binaryDataItem.fileName);
},
async downloadBinaryData (index: number, key: string) {
const binaryDataItem: IBinaryData = this.binaryData[index][key];
let bufferString = 'data:' + binaryDataItem.mimeType + ';base64,';
if(binaryDataItem.id) {
bufferString += await this.restApi().getBinaryBufferString(binaryDataItem.id);
} else {
bufferString += binaryDataItem.data;
}
const data = await fetch(bufferString);
const blob = await data.blob();
saveAs(blob, binaryDataItem.fileName);
},
displayBinaryData (index: number, key: string) { displayBinaryData (index: number, key: string) {
this.binaryDataDisplayVisible = true; this.binaryDataDisplayVisible = true;

View file

@ -742,7 +742,12 @@
"mimeType": "Mime Type", "mimeType": "Mime Type",
"ms": "ms", "ms": "ms",
"noBinaryDataFound": "No binary data found", "noBinaryDataFound": "No binary data found",
"showBinaryData": "Show Binary Data", "noData": "No data",
"noTextDataFound": "No text data found",
"nodeReturnedALargeAmountOfData": "Node returned a large amount of data",
"output": "Output",
"downloadBinaryData": "Download",
"showBinaryData": "View",
"startTime": "Start Time", "startTime": "Start Time",
"table": "Table" "table": "Table"
}, },

View file

@ -12,7 +12,7 @@ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensa
## Limitations ## Limitations
You may use or modify the software only for your own internal business purposes or for non-commercial or personal use You may use or modify the software only for your own internal business purposes or for non-commercial or personal use.
You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes. You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes.
You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law. You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law.

View file

@ -1,6 +1,6 @@
{ {
"name": "n8n-node-dev", "name": "n8n-node-dev",
"version": "0.49.0", "version": "0.51.0",
"description": "CLI to simplify n8n credentials/node development", "description": "CLI to simplify n8n credentials/node development",
"license": "SEE LICENSE IN LICENSE.md", "license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io", "homepage": "https://n8n.io",
@ -61,8 +61,8 @@
"change-case": "^4.1.1", "change-case": "^4.1.1",
"copyfiles": "^2.1.1", "copyfiles": "^2.1.1",
"inquirer": "^7.0.1", "inquirer": "^7.0.1",
"n8n-core": "~0.110.0", "n8n-core": "~0.112.0",
"n8n-workflow": "~0.92.0", "n8n-workflow": "~0.94.0",
"oauth-1.0a": "^2.2.6", "oauth-1.0a": "^2.2.6",
"replace-in-file": "^6.0.0", "replace-in-file": "^6.0.0",
"request": "^2.88.2", "request": "^2.88.2",

View file

@ -12,7 +12,7 @@ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensa
## Limitations ## Limitations
You may use or modify the software only for your own internal business purposes or for non-commercial or personal use You may use or modify the software only for your own internal business purposes or for non-commercial or personal use.
You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes. You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes.
You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law. You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law.

View file

@ -22,14 +22,14 @@ export class MauticOAuth2Api implements ICredentialType {
displayName: 'Authorization URL', displayName: 'Authorization URL',
name: 'authUrl', name: 'authUrl',
type: 'hidden', type: 'hidden',
default: '={{$self["url"]}}/oauth/v2/authorize', default: '={{$self["url"].endsWith("/") ? $self["url"].slice(0, -1) : $self["url"]}}/oauth/v2/authorize',
required: true, required: true,
}, },
{ {
displayName: 'Access Token URL', displayName: 'Access Token URL',
name: 'accessTokenUrl', name: 'accessTokenUrl',
type: 'hidden', type: 'hidden',
default: '={{$self["url"]}}/oauth/v2/token', default: '={{$self["url"].endsWith("/") ? $self["url"].slice(0, -1) : $self["url"]}}/oauth/v2/token',
required: true, required: true,
}, },
{ {

View file

@ -16,7 +16,7 @@ export class MicrosoftTeamsOAuth2Api implements ICredentialType {
displayName: 'Scope', displayName: 'Scope',
name: 'scope', name: 'scope',
type: 'hidden', type: 'hidden',
default: 'openid offline_access User.ReadWrite.All Group.ReadWrite.All', default: 'openid offline_access User.ReadWrite.All Group.ReadWrite.All Chat.ReadWrite',
}, },
]; ];
} }

View file

@ -0,0 +1,35 @@
import {
IAuthenticateBasicAuth,
ICredentialTestRequest,
ICredentialType,
INodeProperties,
} from 'n8n-workflow';
export class ServiceNowBasicApi implements ICredentialType {
name = 'serviceNowBasicApi';
extends = [
'httpBasicAuth',
];
displayName = 'ServiceNow Basic Auth API';
documentationUrl = 'serviceNow';
properties: INodeProperties[] = [
{
displayName: 'Subdomain',
name: 'subdomain',
type: 'string',
default: '',
hint: 'The subdomain can be extracted from the URL. If the URL is: https://dev99890.service-now.com the subdomain is dev99890',
required: true,
},
];
authenticate: IAuthenticateBasicAuth = {
type: 'basicAuth',
properties: {},
};
test: ICredentialTestRequest = {
request: {
baseURL: '=https://{{$credentials?.subdomain}}.service-now.com',
url: '/api/now/table/sys_user_role',
},
};
}

View file

@ -16,8 +16,7 @@ export class ServiceNowOAuth2Api implements ICredentialType {
name: 'subdomain', name: 'subdomain',
type: 'string', type: 'string',
default: '', default: '',
placeholder: 'n8n', hint: 'The subdomain can be extracted from the URL. If the URL is: https://dev99890.service-now.com the subdomain is dev99890',
description: 'The subdomain of your ServiceNow environment',
required: true, required: true,
}, },
{ {

View file

@ -6,6 +6,7 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
JsonObject,
NodeApiError, NodeApiError,
NodeOperationError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -191,7 +192,7 @@ export class AwsLambda implements INodeType {
}, },
); );
if (responseData !== null && responseData.errorMessage !== undefined) { if (responseData !== null && responseData?.errorMessage !== undefined) {
let errorMessage = responseData.errorMessage; let errorMessage = responseData.errorMessage;
if (responseData.stackTrace) { if (responseData.stackTrace) {
@ -206,7 +207,7 @@ export class AwsLambda implements INodeType {
} }
} catch (error) { } catch (error) {
if (this.continueOnFail()) { if (this.continueOnFail()) {
returnData.push({ error: error.message }); returnData.push({ error: (error as JsonObject).message });
continue; continue;
} }
throw error; throw error;

View file

@ -1,11 +1,18 @@
import { set } from 'lodash'; import {
import { IExecuteFunctions } from 'n8n-core'; set,
} from 'lodash';
import {
IExecuteFunctions,
} from 'n8n-core';
import { import {
ILoadOptionsFunctions, ILoadOptionsFunctions,
INodeExecutionData, INodeExecutionData,
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
JsonObject,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -14,8 +21,11 @@ import {
createHmac, createHmac,
createSign, createSign,
getHashes, getHashes,
randomBytes,
} from 'crypto'; } from 'crypto';
import { v4 as uuid } from 'uuid';
export class Crypto implements INodeType { export class Crypto implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
displayName: 'Crypto', displayName: 'Crypto',
@ -37,19 +47,24 @@ export class Crypto implements INodeType {
name: 'action', name: 'action',
type: 'options', type: 'options',
options: [ options: [
{
name: 'Generate',
description: 'Generate random string',
value: 'generate',
},
{ {
name: 'Hash', name: 'Hash',
description: 'Hash a text in a specified format.', description: 'Hash a text in a specified format',
value: 'hash', value: 'hash',
}, },
{ {
name: 'Hmac', name: 'Hmac',
description: 'Hmac a text in a specified format.', description: 'Hmac a text in a specified format',
value: 'hmac', value: 'hmac',
}, },
{ {
name: 'Sign', name: 'Sign',
description: 'Sign a string using a private key.', description: 'Sign a string using a private key',
value: 'sign', value: 'sign',
}, },
], ],
@ -100,7 +115,7 @@ export class Crypto implements INodeType {
}, },
type: 'string', type: 'string',
default: '', default: '',
description: 'The value that should be hashed.', description: 'The value that should be hashed',
required: true, required: true,
}, },
{ {
@ -116,7 +131,7 @@ export class Crypto implements INodeType {
], ],
}, },
}, },
description: 'Name of the property to which to write the hash.', description: 'Name of the property to which to write the hash',
}, },
{ {
displayName: 'Encoding', displayName: 'Encoding',
@ -187,7 +202,7 @@ export class Crypto implements INodeType {
}, },
type: 'string', type: 'string',
default: '', default: '',
description: 'The value of which the hmac should be created.', description: 'The value of which the hmac should be created',
required: true, required: true,
}, },
{ {
@ -203,7 +218,7 @@ export class Crypto implements INodeType {
], ],
}, },
}, },
description: 'Name of the property to which to write the hmac.', description: 'Name of the property to which to write the hmac',
}, },
{ {
displayName: 'Secret', displayName: 'Secret',
@ -255,7 +270,7 @@ export class Crypto implements INodeType {
}, },
type: 'string', type: 'string',
default: '', default: '',
description: 'The value that should be signed.', description: 'The value that should be signed',
required: true, required: true,
}, },
{ {
@ -271,7 +286,7 @@ export class Crypto implements INodeType {
], ],
}, },
}, },
description: 'Name of the property to which to write the signed value.', description: 'Name of the property to which to write the signed value',
}, },
{ {
displayName: 'Algorithm', displayName: 'Algorithm',
@ -328,10 +343,77 @@ export class Crypto implements INodeType {
typeOptions: { typeOptions: {
alwaysOpenEditWindow: true, alwaysOpenEditWindow: true,
}, },
description: 'Private key to use when signing the string.', description: 'Private key to use when signing the string',
default: '', default: '',
required: true, required: true,
}, },
{
displayName: 'Property Name',
name: 'dataPropertyName',
type: 'string',
default: 'data',
required: true,
displayOptions: {
show: {
action: [
'generate',
],
},
},
description: 'Name of the property to which to write the random string',
},
{
displayName: 'Type',
name: 'encodingType',
displayOptions: {
show: {
action: [
'generate',
],
},
},
type: 'options',
options: [
{
name: 'ASCII',
value: 'ascii',
},
{
name: 'BASE64',
value: 'base64',
},
{
name: 'HEX',
value: 'hex',
},
{
name: 'UUID',
value: 'uuid',
},
],
default: 'uuid',
description: 'Encoding that will be used to generate string',
required: true,
},
{
displayName: 'Length',
name: 'stringLength',
type: 'number',
default: 32,
description: 'Length of the generated string',
displayOptions: {
show: {
action: [
'generate',
],
encodingType: [
'ascii',
'base64',
'hex',
],
},
},
},
], ],
}; };
@ -369,9 +451,22 @@ export class Crypto implements INodeType {
item = items[i]; item = items[i];
const dataPropertyName = this.getNodeParameter('dataPropertyName', i) as string; const dataPropertyName = this.getNodeParameter('dataPropertyName', i) as string;
const value = this.getNodeParameter('value', i) as string; const value = this.getNodeParameter('value', i, '') as string;
let newValue; let newValue;
if (action === 'generate') {
const encodingType = this.getNodeParameter('encodingType', i) as string;
if (encodingType === 'uuid') {
newValue = uuid();
} else {
const stringLength = this.getNodeParameter('stringLength', i) as number;
if (encodingType === 'base64') {
newValue = randomBytes(stringLength).toString(encodingType as BufferEncoding).replace(/\W/g, '').slice(0, stringLength);
} else {
newValue = randomBytes(stringLength).toString(encodingType as BufferEncoding).slice(0, stringLength);
}
}
}
if (action === 'hash') { if (action === 'hash') {
const type = this.getNodeParameter('type', i) as string; const type = this.getNodeParameter('type', i) as string;
const encoding = this.getNodeParameter('encoding', i) as BinaryToTextEncoding; const encoding = this.getNodeParameter('encoding', i) as BinaryToTextEncoding;
@ -413,10 +508,10 @@ export class Crypto implements INodeType {
set(newItem, `json.${dataPropertyName}`, newValue); set(newItem, `json.${dataPropertyName}`, newValue);
returnData.push(newItem); returnData.push(newItem);
} catch (error) { } catch (error) {
if (this.continueOnFail()) { if (this.continueOnFail()) {
returnData.push({json:{ error: error.message }}); returnData.push({ json: { error: (error as JsonObject).message } });
continue; continue;
} }
throw error; throw error;

View file

@ -1,13 +1,15 @@
import { ITriggerFunctions } from 'n8n-core'; import { ITriggerFunctions } from 'n8n-core';
import { import {
createDeferredPromise,
IBinaryData, IBinaryData,
IBinaryKeyData, IBinaryKeyData,
IDataObject, IDataObject,
IDeferredPromise,
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
ITriggerResponse, ITriggerResponse,
LoggerProxy, LoggerProxy as Logger,
NodeOperationError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -25,10 +27,6 @@ import {
import * as lodash from 'lodash'; import * as lodash from 'lodash';
import {
LoggerProxy as Logger
} from 'n8n-workflow';
export class EmailReadImap implements INodeType { export class EmailReadImap implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
displayName: 'EmailReadImap', displayName: 'EmailReadImap',
@ -377,6 +375,8 @@ export class EmailReadImap implements INodeType {
return newEmails; return newEmails;
}; };
const returnedPromise: IDeferredPromise<void> | undefined = await createDeferredPromise<void>();
const establishConnection = (): Promise<ImapSimple> => { const establishConnection = (): Promise<ImapSimple> => {
let searchCriteria = [ let searchCriteria = [
@ -425,7 +425,11 @@ export class EmailReadImap implements INodeType {
} }
} catch (error) { } catch (error) {
Logger.error('Email Read Imap node encountered an error fetching new emails', { error }); Logger.error('Email Read Imap node encountered an error fetching new emails', { error });
throw error; // Wait with resolving till the returnedPromise got resolved, else n8n will be unhappy
// if it receives an error before the workflow got activated
returnedPromise.promise().then(() => {
this.emitError(error as Error);
});
} }
} }
}, },
@ -475,10 +479,12 @@ export class EmailReadImap implements INodeType {
await connection.end(); await connection.end();
} }
// Resolve returned-promise so that waiting errors can be emitted
returnedPromise.resolve();
return { return {
closeFunction, closeFunction,
}; };
} }
} }

View file

@ -9,6 +9,7 @@ export const campaignOperations: INodeProperties[] = [
type: 'options', type: 'options',
default: 'get', default: 'get',
description: 'Operation to perform', description: 'Operation to perform',
noDataExpression: true,
options: [ options: [
{ {
name: 'Add Contact', name: 'Add Contact',
@ -18,6 +19,10 @@ export const campaignOperations: INodeProperties[] = [
name: 'Create', name: 'Create',
value: 'create', value: 'create',
}, },
{
name: 'Duplicate',
value: 'duplicate',
},
{ {
name: 'Get', name: 'Get',
value: 'get', value: 'get',
@ -58,7 +63,7 @@ export const campaignFields: INodeProperties[] = [
}, },
default: [], default: [],
required: true, required: true,
description: 'The ID of the campaign to add the contact to.', description: 'The ID of the campaign to add the contact to',
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -76,7 +81,7 @@ export const campaignFields: INodeProperties[] = [
type: 'string', type: 'string',
required: true, required: true,
default: '', default: '',
description: 'The email of the contact to add to the campaign.', description: 'The email of the contact to add to the campaign',
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -113,7 +118,7 @@ export const campaignFields: INodeProperties[] = [
typeOptions: { typeOptions: {
multipleValues: true, multipleValues: true,
}, },
description: 'Filter by custom fields ', description: 'Filter by custom fields',
default: {}, default: {},
options: [ options: [
{ {
@ -125,14 +130,14 @@ export const campaignFields: INodeProperties[] = [
name: 'fieldName', name: 'fieldName',
type: 'string', type: 'string',
default: '', default: '',
description: 'The name of the field to add custom field to.', description: 'The name of the field to add custom field to',
}, },
{ {
displayName: 'Value', displayName: 'Value',
name: 'value', name: 'value',
type: 'string', type: 'string',
default: '', default: '',
description: 'The value to set on custom field.', description: 'The value to set on custom field',
}, },
], ],
}, },
@ -143,49 +148,49 @@ export const campaignFields: INodeProperties[] = [
name: 'firstName', name: 'firstName',
type: 'string', type: 'string',
default: '', default: '',
description: 'First name of the contact to add.', description: 'First name of the contact to add',
},
{
displayName: 'Last Contacted',
name: 'lastContacted',
type: 'dateTime',
default: '',
description: 'Last contacted date of the contact to add',
}, },
{ {
displayName: 'Last Name', displayName: 'Last Name',
name: 'lastName', name: 'lastName',
type: 'string', type: 'string',
default: '', default: '',
description: 'Last name of the contact to add.', description: 'Last name of the contact to add',
},
{
displayName: 'Last Contacted',
name: 'lastContacted',
type: 'string',
default: '',
description: 'Last contacted date of the contact to add.',
}, },
{ {
displayName: 'Last Open', displayName: 'Last Open',
name: 'lastOpen', name: 'lastOpen',
type: 'string', type: 'dateTime',
default: '', default: '',
description: 'Last opened date of the contact to add.', description: 'Last opened date of the contact to add',
}, },
{ {
displayName: 'Last Replied', displayName: 'Last Replied',
name: 'lastReplied', name: 'lastReplied',
type: 'string', type: 'dateTime',
default: '', default: '',
description: 'Last replied date of the contact to add.', description: 'Last replied date of the contact to add',
}, },
{ {
displayName: 'Mails Sent', displayName: 'Mails Sent',
name: 'mailsSent', name: 'mailsSent',
type: 'number', type: 'number',
default: 0, default: 0,
description: 'Number of emails sent to the contact to add.', description: 'Number of emails sent to the contact to add',
}, },
{ {
displayName: 'Phone Number', displayName: 'Phone Number',
name: 'phoneNumber', name: 'phoneNumber',
type: 'string', type: 'string',
default: '', default: '',
description: 'Phone number of the contact to add.', description: 'Phone number of the contact to add',
}, },
], ],
}, },
@ -199,7 +204,7 @@ export const campaignFields: INodeProperties[] = [
type: 'string', type: 'string',
required: true, required: true,
default: '', default: '',
description: 'The name of the campaign to create.', description: 'The name of the campaign to create',
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -221,7 +226,7 @@ export const campaignFields: INodeProperties[] = [
type: 'string', type: 'string',
default: '', default: '',
required: true, required: true,
description: 'The ID of the campaign to retrieve.', description: 'The ID of the campaign to retrieve',
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -242,7 +247,7 @@ export const campaignFields: INodeProperties[] = [
name: 'returnAll', name: 'returnAll',
type: 'boolean', type: 'boolean',
default: false, default: false,
description: 'Return all results.', description: 'Whether to return all results or only up to a given limit',
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -259,7 +264,7 @@ export const campaignFields: INodeProperties[] = [
name: 'limit', name: 'limit',
type: 'number', type: 'number',
default: 100, default: 100,
description: 'The number of results to return.', description: 'Max number of results to return',
typeOptions: { typeOptions: {
minValue: 1, minValue: 1,
maxValue: 100, maxValue: 100,
@ -323,4 +328,93 @@ export const campaignFields: INodeProperties[] = [
}, },
}, },
// ----------------------------------
// campaign: duplicate
// ----------------------------------
{
displayName: 'Campaign ID',
name: 'campaignId',
type: 'options',
default: '',
required: true,
description: 'The ID of the campaign to duplicate',
typeOptions: {
loadOptionsMethod: 'getCampaigns',
},
displayOptions: {
show: {
resource: [
'campaign',
],
operation: [
'duplicate',
],
},
},
},
{
displayName: 'New Campaign Name',
name: 'campaignName',
type: 'string',
required: true,
default: '',
description: 'The name of the new campaign to create',
displayOptions: {
show: {
resource: [
'campaign',
],
operation: [
'duplicate',
],
},
},
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
default: {},
placeholder: 'Add Field',
displayOptions: {
show: {
operation: [
'duplicate',
],
resource: [
'campaign',
],
},
},
options: [
{
displayName: 'Copy Contacts',
name: 'copyContacts',
type: 'boolean',
default: false,
description: 'Whether to copy all the contacts from the original campaign',
},
{
displayName: 'Copy Email Provider',
name: 'copyProvider',
type: 'boolean',
default: true,
description: 'Whether to set the same email provider than the original campaign',
},
{
displayName: 'Copy Email Sequence',
name: 'copyMails',
type: 'boolean',
default: true,
description: 'Whether to copy all the steps of the email sequence from the original campaign',
},
{
displayName: 'Copy Global Settings',
name: 'copySettings',
type: 'boolean',
default: true,
description: 'Whether to copy all the general settings from the original campaign',
},
],
},
]; ];

View file

@ -7,8 +7,9 @@ export const contactListOperations: INodeProperties[] = [
displayName: 'Operation', displayName: 'Operation',
name: 'operation', name: 'operation',
type: 'options', type: 'options',
default: 'get', default: 'getAll',
description: 'Operation to perform', description: 'Operation to perform',
noDataExpression: true,
options: [ options: [
{ {
name: 'Add', name: 'Add',
@ -42,7 +43,7 @@ export const contactListFields: INodeProperties[] = [
}, },
default: [], default: [],
required: true, required: true,
description: 'The ID of the contact list to add the contact to.', description: 'The ID of the contact list to add the contact to',
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -60,7 +61,7 @@ export const contactListFields: INodeProperties[] = [
type: 'string', type: 'string',
required: true, required: true,
default: '', default: '',
description: 'The email of the contact to add to the contact list.', description: 'The email of the contact to add to the contact list',
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -97,7 +98,7 @@ export const contactListFields: INodeProperties[] = [
typeOptions: { typeOptions: {
multipleValues: true, multipleValues: true,
}, },
description: 'Filter by custom fields ', description: 'Filter by custom fields',
default: {}, default: {},
options: [ options: [
{ {
@ -109,14 +110,14 @@ export const contactListFields: INodeProperties[] = [
name: 'fieldName', name: 'fieldName',
type: 'string', type: 'string',
default: '', default: '',
description: 'The name of the field to add custom field to.', description: 'The name of the field to add custom field to',
}, },
{ {
displayName: 'Value', displayName: 'Value',
name: 'value', name: 'value',
type: 'string', type: 'string',
default: '', default: '',
description: 'The value to set on custom field.', description: 'The value to set on custom field',
}, },
], ],
}, },
@ -127,49 +128,49 @@ export const contactListFields: INodeProperties[] = [
name: 'firstName', name: 'firstName',
type: 'string', type: 'string',
default: '', default: '',
description: 'First name of the contact to add.', description: 'First name of the contact to add',
},
{
displayName: 'Last Name',
name: 'lastName',
type: 'string',
default: '',
description: 'Last name of the contact to add.',
}, },
{ {
displayName: 'Last Contacted', displayName: 'Last Contacted',
name: 'lastContacted', name: 'lastContacted',
type: 'dateTime', type: 'dateTime',
default: '', default: '',
description: 'Last contacted date of the contact to add.', description: 'Last contacted date of the contact to add',
},
{
displayName: 'Last Name',
name: 'lastName',
type: 'string',
default: '',
description: 'Last name of the contact to add',
}, },
{ {
displayName: 'Last Open', displayName: 'Last Open',
name: 'lastOpen', name: 'lastOpen',
type: 'dateTime', type: 'dateTime',
default: '', default: '',
description: 'Last opened date of the contact to add.', description: 'Last opened date of the contact to add',
}, },
{ {
displayName: 'Last Replied', displayName: 'Last Replied',
name: 'lastReplied', name: 'lastReplied',
type: 'dateTime', type: 'dateTime',
default: '', default: '',
description: 'Last replied date of the contact to add.', description: 'Last replied date of the contact to add',
}, },
{ {
displayName: 'Mails Sent', displayName: 'Mails Sent',
name: 'mailsSent', name: 'mailsSent',
type: 'number', type: 'number',
default: 0, default: 0,
description: 'Number of emails sent to the contact to add.', description: 'Number of emails sent to the contact to add',
}, },
{ {
displayName: 'Phone Number', displayName: 'Phone Number',
name: 'phoneNumber', name: 'phoneNumber',
type: 'string', type: 'string',
default: '', default: '',
description: 'Phone number of the contact to add.', description: 'Phone number of the contact to add',
}, },
], ],
}, },
@ -182,7 +183,7 @@ export const contactListFields: INodeProperties[] = [
name: 'returnAll', name: 'returnAll',
type: 'boolean', type: 'boolean',
default: false, default: false,
description: 'Return all results.', description: 'Whether to return all results or only up to a given limit',
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -199,7 +200,7 @@ export const contactListFields: INodeProperties[] = [
name: 'limit', name: 'limit',
type: 'number', type: 'number',
default: 100, default: 100,
description: 'The number of results to return.', description: 'Max number of results to return',
typeOptions: { typeOptions: {
minValue: 1, minValue: 1,
maxValue: 100, maxValue: 100,

View file

@ -7,10 +7,12 @@ import {
ILoadOptionsFunctions, ILoadOptionsFunctions,
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription INodeTypeDescription,
JsonObject
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
emeliaApiTest,
emeliaGraphqlRequest, emeliaGraphqlRequest,
loadResource, loadResource,
} from './GenericFunctions'; } from './GenericFunctions';
@ -36,7 +38,7 @@ export class Emelia implements INodeType {
icon: 'file:emelia.svg', icon: 'file:emelia.svg',
group: ['input'], group: ['input'],
version: 1, version: 1,
subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}', subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume the Emelia API', description: 'Consume the Emelia API',
defaults: { defaults: {
name: 'Emelia', name: 'Emelia',
@ -47,6 +49,7 @@ export class Emelia implements INodeType {
{ {
name: 'emeliaApi', name: 'emeliaApi',
required: true, required: true,
testedBy: 'emeliaApiTest',
}, },
], ],
properties: [ properties: [
@ -54,6 +57,7 @@ export class Emelia implements INodeType {
displayName: 'Resource', displayName: 'Resource',
name: 'resource', name: 'resource',
type: 'options', type: 'options',
noDataExpression: true,
options: [ options: [
{ {
name: 'Campaign', name: 'Campaign',
@ -66,7 +70,7 @@ export class Emelia implements INodeType {
], ],
default: 'campaign', default: 'campaign',
required: true, required: true,
description: 'The resource to operate on.', description: 'The resource to operate on',
}, },
...campaignOperations, ...campaignOperations,
...campaignFields, ...campaignFields,
@ -76,6 +80,10 @@ export class Emelia implements INodeType {
}; };
methods = { methods = {
credentialTest: {
emeliaApiTest,
},
loadOptions: { loadOptions: {
async getCampaigns(this: ILoadOptionsFunctions) { async getCampaigns(this: ILoadOptionsFunctions) {
return loadResource.call(this, 'campaign'); return loadResource.call(this, 'campaign');
@ -290,6 +298,46 @@ export class Emelia implements INodeType {
returnData.push({ success: true }); returnData.push({ success: true });
} else if (operation === 'duplicate') {
// ----------------------------------
// campaign: duplicate
// ----------------------------------
const options = this.getNodeParameter('options', i) as IDataObject;
const variables = {
fromId: this.getNodeParameter('campaignId', i),
name: this.getNodeParameter('campaignName', i),
copySettings: true,
copyMails: true,
copyContacts: false,
copyProvider: true,
...options,
};
const { data: { duplicateCampaign } } = await emeliaGraphqlRequest.call(this, {
query: `
mutation duplicateCampaign(
$fromId: ID!
$name: String!
$copySettings: Boolean!
$copyMails: Boolean!
$copyContacts: Boolean!
$copyProvider: Boolean!
) {
duplicateCampaign(
fromId: $fromId
name: $name
copySettings: $copySettings
copyMails: $copyMails
copyContacts: $copyContacts
copyProvider: $copyProvider
)
}`,
operationName: 'duplicateCampaign',
variables,
});
returnData.push({ _id: duplicateCampaign });
} }
} else if (resource === 'contactList') { } else if (resource === 'contactList') {
@ -373,7 +421,7 @@ export class Emelia implements INodeType {
} catch (error) { } catch (error) {
if (this.continueOnFail()) { if (this.continueOnFail()) {
returnData.push({ error: error.message }); returnData.push({ error: (error as JsonObject).message });
continue; continue;
} }

View file

@ -10,6 +10,7 @@ import {
import { import {
emeliaApiRequest, emeliaApiRequest,
emeliaApiTest,
emeliaGraphqlRequest, emeliaGraphqlRequest,
} from './GenericFunctions'; } from './GenericFunctions';
@ -23,6 +24,7 @@ export class EmeliaTrigger implements INodeType {
displayName: 'Emelia Trigger', displayName: 'Emelia Trigger',
name: 'emeliaTrigger', name: 'emeliaTrigger',
icon: 'file:emelia.svg', icon: 'file:emelia.svg',
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
group: ['trigger'], group: ['trigger'],
version: 1, version: 1,
description: 'Handle Emelia campaign activity events via webhooks', description: 'Handle Emelia campaign activity events via webhooks',
@ -35,6 +37,7 @@ export class EmeliaTrigger implements INodeType {
{ {
name: 'emeliaApi', name: 'emeliaApi',
required: true, required: true,
testedBy: 'emeliaApiTest',
}, },
], ],
webhooks: [ webhooks: [
@ -93,6 +96,10 @@ export class EmeliaTrigger implements INodeType {
}; };
methods = { methods = {
credentialTest: {
emeliaApiTest,
},
loadOptions: { loadOptions: {
async getCampaigns(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> { async getCampaigns(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const responseData = await emeliaGraphqlRequest.call(this, { const responseData = await emeliaGraphqlRequest.call(this, {

View file

@ -4,8 +4,12 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
ICredentialsDecrypted,
ICredentialTestFunctions,
IHookFunctions, IHookFunctions,
INodeCredentialTestResult,
INodePropertyOptions, INodePropertyOptions,
JsonObject,
NodeApiError, NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -51,7 +55,7 @@ export async function emeliaApiRequest(
try { try {
return await this.helpers.request!.call(this, options); return await this.helpers.request!.call(this, options);
} catch (error) { } catch (error) {
throw new NodeApiError(this.getNode(), error); throw new NodeApiError(this.getNode(), (error as JsonObject));
} }
} }
@ -91,5 +95,47 @@ export async function loadResource(
name: campaign.name, name: campaign.name,
value: campaign._id, value: campaign._id,
})); }));
}
export async function emeliaApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
const credentials = credential.data;
const body = {
query: `
query all_campaigns {
all_campaigns {
_id
name
status
createdAt
stats {
mailsSent
}
}
}`,
operationName: 'all_campaigns',
};
const options = {
headers: {
Authorization: credentials?.apiKey,
},
method: 'POST',
body,
uri: `https://graphql.emelia.io/graphql`,
json: true,
};
try {
await this.helpers.request!(options);
} catch (error) {
return {
status: 'Error',
message: `Connection details not valid: ${(error as JsonObject).message}`,
};
}
return {
status: 'OK',
message: 'Authentication successful!',
};
} }

View file

@ -51,6 +51,7 @@ export class Ftp implements INodeType {
outputs: ['main'], outputs: ['main'],
credentials: [ credentials: [
{ {
// nodelinter-ignore-next-line
name: 'ftp', name: 'ftp',
required: true, required: true,
displayOptions: { displayOptions: {
@ -62,6 +63,7 @@ export class Ftp implements INodeType {
}, },
}, },
{ {
// nodelinter-ignore-next-line
name: 'sftp', name: 'sftp',
required: true, required: true,
displayOptions: { displayOptions: {
@ -124,6 +126,7 @@ export class Ftp implements INodeType {
], ],
default: 'download', default: 'download',
description: 'Operation to perform.', description: 'Operation to perform.',
noDataExpression: true,
}, },
// ---------------------------------- // ----------------------------------
@ -253,6 +256,29 @@ export class Ftp implements INodeType {
description: 'The new path', description: 'The new path',
required: true, required: true,
}, },
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
operation: [
'rename',
],
},
},
options: [
{
displayName: 'Create Directories',
name: 'createDirectories',
type: 'boolean',
default: false,
description: `Recursively create destination directory when renaming an existing file or folder`,
},
],
},
// ---------------------------------- // ----------------------------------
// upload // upload
@ -381,8 +407,8 @@ export class Ftp implements INodeType {
throw new NodeOperationError(this.getNode(), 'Failed to get credentials!'); throw new NodeOperationError(this.getNode(), 'Failed to get credentials!');
} }
let ftp : ftpClient; let ftp: ftpClient;
let sftp : sftpClient; let sftp: sftpClient;
if (protocol === 'sftp') { if (protocol === 'sftp') {
sftp = new sftpClient(); sftp = new sftpClient();
@ -452,9 +478,13 @@ export class Ftp implements INodeType {
if (operation === 'rename') { if (operation === 'rename') {
const oldPath = this.getNodeParameter('oldPath', i) as string; const oldPath = this.getNodeParameter('oldPath', i) as string;
const { createDirectories = false } = this.getNodeParameter('options', i) as { createDirectories: boolean };
const newPath = this.getNodeParameter('newPath', i) as string; const newPath = this.getNodeParameter('newPath', i) as string;
if (createDirectories) {
await recursivelyCreateSftpDirs(sftp!, newPath);
}
responseData = await sftp!.rename(oldPath, newPath); responseData = await sftp!.rename(oldPath, newPath);
returnItems.push({ json: { success: true } }); returnItems.push({ json: { success: true } });
@ -475,16 +505,7 @@ export class Ftp implements INodeType {
if (operation === 'upload') { if (operation === 'upload') {
const remotePath = this.getNodeParameter('path', i) as string; const remotePath = this.getNodeParameter('path', i) as string;
await recursivelyCreateSftpDirs(sftp!, remotePath);
// Check if dir path exists
const dirPath = dirname(remotePath);
const dirExists = await sftp!.exists(dirPath);
// If dir does not exist, create all recursively in path
if (!dirExists) {
// Create directory
await sftp!.mkdir(dirPath, true);
}
if (this.getNodeParameter('binaryData', i) === true) { if (this.getNodeParameter('binaryData', i) === true) {
// Is binary file to upload // Is binary file to upload
@ -635,7 +656,7 @@ export class Ftp implements INodeType {
} catch (error) { } catch (error) {
if (this.continueOnFail()) { if (this.continueOnFail()) {
return this.prepareOutputData([{json:{ error: error.message }}]); return this.prepareOutputData([{ json: { error: error.message } }]);
} }
throw error; throw error;
@ -661,17 +682,17 @@ function normalizeSFtpItem(input: sftpClient.FileInfo, path: string, recursive =
} }
async function callRecursiveList(path: string, client: sftpClient | ftpClient, normalizeFunction: (input: ftpClient.ListingElement & sftpClient.FileInfo, path: string, recursive?: boolean) => void) { async function callRecursiveList(path: string, client: sftpClient | ftpClient, normalizeFunction: (input: ftpClient.ListingElement & sftpClient.FileInfo, path: string, recursive?: boolean) => void) {
const pathArray : string[] = [path]; const pathArray: string[] = [path];
let currentPath = path; let currentPath = path;
const directoryItems : sftpClient.FileInfo[] = []; const directoryItems: sftpClient.FileInfo[] = [];
let index = 0; let index = 0;
do { do {
// tslint:disable-next-line: array-type // tslint:disable-next-line: array-type
const returnData : sftpClient.FileInfo[] | (string | ftpClient.ListingElement)[] = await client.list(pathArray[index]); const returnData: sftpClient.FileInfo[] | (string | ftpClient.ListingElement)[] = await client.list(pathArray[index]);
// @ts-ignore // @ts-ignore
returnData.map((item : sftpClient.FileInfo) => { returnData.map((item: sftpClient.FileInfo) => {
if ((pathArray[index] as string).endsWith('/')) { if ((pathArray[index] as string).endsWith('/')) {
currentPath = `${pathArray[index]}${item.name}`; currentPath = `${pathArray[index]}${item.name}`;
} else { } else {
@ -693,3 +714,12 @@ async function callRecursiveList(path: string, client: sftpClient | ftpClient, n
return directoryItems; return directoryItems;
} }
async function recursivelyCreateSftpDirs(sftp: sftpClient, path: string) {
const dirPath = dirname(path);
const dirExists = await sftp!.exists(dirPath);
if (!dirExists) {
await sftp!.mkdir(dirPath, true);
}
}

View file

@ -171,7 +171,7 @@ export class GoogleTasks implements INodeType {
//https://developers.google.com/tasks/v1/reference/tasks/list //https://developers.google.com/tasks/v1/reference/tasks/list
const returnAll = this.getNodeParameter('returnAll', i) as boolean; const returnAll = this.getNodeParameter('returnAll', i) as boolean;
const taskListId = this.getNodeParameter('task', i) as string; const taskListId = this.getNodeParameter('task', i) as string;
const options = this.getNodeParameter( const { showCompleted = true, showDeleted = false, showHidden = false, ...options } = this.getNodeParameter(
'additionalFields', 'additionalFields',
i, i,
) as IDataObject; ) as IDataObject;
@ -187,15 +187,11 @@ export class GoogleTasks implements INodeType {
if (options.dueMax) { if (options.dueMax) {
qs.dueMax = options.dueMax as string; qs.dueMax = options.dueMax as string;
} }
if (options.showCompleted) {
qs.showCompleted = options.showCompleted as boolean; qs.showCompleted = showCompleted;
} qs.showDeleted = showDeleted;
if (options.showDeleted) { qs.showHidden = showHidden;
qs.showDeleted = options.showDeleted as boolean;
}
if (options.showHidden) {
qs.showHidden = options.showHidden as boolean;
}
if (options.updatedMin) { if (options.updatedMin) {
qs.updatedMin = options.updatedMin as string; qs.updatedMin = options.updatedMin as string;
} }

View file

@ -76,6 +76,16 @@ export const taskFields: INodeProperties[] = [
type: 'string', type: 'string',
default: '', default: '',
description: 'Title of the task.', description: 'Title of the task.',
displayOptions: {
show: {
operation: [
'create',
],
resource: [
'task',
],
},
},
}, },
{ {
displayName: 'Additional Fields', displayName: 'Additional Fields',
@ -350,21 +360,21 @@ export const taskFields: INodeProperties[] = [
name: 'showCompleted', name: 'showCompleted',
type: 'boolean', type: 'boolean',
default: true, default: true,
description: 'Flag indicating whether completed tasks are returned in the result', description: 'Flag indicating whether completed tasks are returned in the result. <strong>Show Hidden</strong> must also be True to show tasks completed in first party clients such as the web UI or Google\'s mobile apps.',
}, },
{ {
displayName: 'Show Deleted', displayName: 'Show Deleted',
name: 'showDeleted', name: 'showDeleted',
type: 'boolean', type: 'boolean',
default: false, default: false,
description: 'Flag indicating whether deleted tasks are returned in the result', description: 'Flag indicating whether deleted tasks are returned in the result.',
}, },
{ {
displayName: 'Show Hidden', displayName: 'Show Hidden',
name: 'showHidden', name: 'showHidden',
type: 'boolean', type: 'boolean',
default: false, default: false,
description: 'Flag indicating whether hidden tasks are returned in the result', description: 'Flag indicating whether hidden tasks are returned in the result.',
}, },
{ {
displayName: 'Updated Min', displayName: 'Updated Min',

View file

@ -4,6 +4,7 @@ import {
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
JsonObject,
NodeApiError, NodeApiError,
NodeOperationError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -412,17 +413,22 @@ export class GraphQL implements INodeType {
} else { } else {
if (typeof response === 'string') { if (typeof response === 'string') {
try { try {
returnItems.push({ json: JSON.parse(response) }); response = JSON.parse(response);
} catch (error) { } catch (error) {
throw new NodeOperationError(this.getNode(), 'Response body is not valid JSON. Change "Response Format" to "String"'); throw new NodeOperationError(this.getNode(), 'Response body is not valid JSON. Change "Response Format" to "String"');
} }
} else {
returnItems.push({ json: response });
} }
if (response.errors) {
const message = response.errors?.map((error: IDataObject) => error.message).join(', ') || 'Unexpected error';
throw new NodeApiError(this.getNode(), response.errors, { message });
}
returnItems.push({ json: response });
} }
} catch (error) { } catch (error) {
if (this.continueOnFail()) { if (this.continueOnFail()) {
returnItems.push({ json: { error: error.message } }); returnItems.push({ json: { error: (error as JsonObject).message } });
continue; continue;
} }
throw error; throw error;

View file

@ -161,6 +161,10 @@ export class HttpRequest implements INodeType {
name: 'HEAD', name: 'HEAD',
value: 'HEAD', value: 'HEAD',
}, },
{
name: 'OPTIONS',
value: 'OPTIONS',
},
{ {
name: 'PATCH', name: 'PATCH',
value: 'PATCH', value: 'PATCH',

View file

@ -133,7 +133,7 @@ export class If implements INodeType {
value: 'smaller', value: 'smaller',
}, },
{ {
name: 'Smaller Equal', name: 'Smaller or Equal',
value: 'smallerEqual', value: 'smallerEqual',
}, },
{ {
@ -149,7 +149,7 @@ export class If implements INodeType {
value: 'larger', value: 'larger',
}, },
{ {
name: 'Larger Equal', name: 'Larger or Equal',
value: 'largerEqual', value: 'largerEqual',
}, },
{ {

View file

@ -12,6 +12,7 @@ import {
import { import {
ICredentialDataDecryptedObject, ICredentialDataDecryptedObject,
IDataObject, IDataObject,
JsonObject,
NodeApiError, NodeApiError,
NodeOperationError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -69,7 +70,7 @@ export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecut
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
throw new NodeApiError(this.getNode(), error); throw new NodeApiError(this.getNode(), error as JsonObject);
} }
} }
@ -119,6 +120,49 @@ export function getId(url: string) {
return url.split('/').pop(); return url.split('/').pop();
} }
export function simplifyIssueOutput(responseData: {
names: { [key: string]: string },
fields: IDataObject,
id: string,
key: string,
self: string
}) {
const mappedFields: IDataObject = {
id: responseData.id,
key: responseData.key,
self: responseData.self,
};
// Sort custom fields last so we map them last
const customField = /^customfield_\d+$/;
const sortedFields: string[] = Object.keys(responseData.fields).sort((a, b) => {
if (customField.test(a) && customField.test(b)) {
return a > b ? 1 : -1;
}
if (customField.test(a)) {
return 1;
}
if (customField.test(b)) {
return -1;
}
return a > b ? 1 : -1;
});
for (const field of sortedFields) {
if (responseData.names[field] in mappedFields) {
let newField: string = responseData.names[field];
let counter = 0;
while (newField in mappedFields) {
counter++;
newField = `${responseData.names[field]}_${counter}`;
}
mappedFields[newField] = responseData.fields[field];
} else {
mappedFields[responseData.names[field] || field] = responseData.fields[field];
}
}
return mappedFields;
}
export const allEvents = [ export const allEvents = [
'board_created', 'board_created',
'board_updated', 'board_updated',

View file

@ -530,6 +530,24 @@ export const issueFields: INodeProperties[] = [
default: '', default: '',
description: 'Issue Key', description: 'Issue Key',
}, },
{
displayName: 'Simplify Output',
name: 'simplifyOutput',
type: 'boolean',
required: false,
displayOptions: {
show: {
resource: [
'issue',
],
operation: [
'get',
],
},
},
default: false,
description: `Return a simplified output of the issues fields.`,
},
{ {
displayName: 'Additional Fields', displayName: 'Additional Fields',
name: 'additionalFields', name: 'additionalFields',

View file

@ -18,12 +18,14 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
JsonObject,
NodeOperationError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
jiraSoftwareCloudApiRequest, jiraSoftwareCloudApiRequest,
jiraSoftwareCloudApiRequestAllItems, jiraSoftwareCloudApiRequestAllItems,
simplifyIssueOutput,
validateJSON, validateJSON,
} from './GenericFunctions'; } from './GenericFunctions';
@ -178,7 +180,7 @@ export class Jira implements INodeType {
} catch (err) { } catch (err) {
return { return {
status: 'Error', status: 'Error',
message: `Connection details not valid: ${err.message}`, message: `Connection details not valid: ${(err as JsonObject).message}`,
}; };
} }
return { return {
@ -303,45 +305,29 @@ export class Jira implements INodeType {
// Get all the users to display them to user so that he can // Get all the users to display them to user so that he can
// select them easily // select them easily
async getUsers(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> { async getUsers(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const jiraVersion = this.getCurrentNodeParameter('jiraVersion') as string; const jiraVersion = this.getCurrentNodeParameter('jiraVersion') as string;
const query: IDataObject = {};
let endpoint = '/api/2/users/search';
if (jiraVersion === 'server') { if (jiraVersion === 'server') {
// the interface call must bring username endpoint = '/api/2/user/search';
const users = await jiraSoftwareCloudApiRequest.call(this, '/api/2/user/search', 'GET', {}, query.username = '\'';
{
username: '\'',
},
);
for (const user of users) {
const userName = user.displayName;
const userId = user.name;
returnData.push({
name: userName,
value: userId,
});
}
} else {
const users = await jiraSoftwareCloudApiRequest.call(this, '/api/2/users/search', 'GET');
for (const user of users) {
const userName = user.displayName;
const userId = user.accountId;
returnData.push({
name: userName,
value: userId,
});
}
} }
returnData.sort((a, b) => { const users = await jiraSoftwareCloudApiRequest.call(this, endpoint, 'GET', {}, query);
if (a.name < b.name) { return -1; }
if (a.name > b.name) { return 1; } return users.reduce((activeUsers: INodePropertyOptions[], user: IDataObject) => {
return 0; if (user.active) {
activeUsers.push({
name: user.displayName as string,
value: (user.accountId || user.name) as string,
});
}
return activeUsers;
}, []).sort((a: INodePropertyOptions, b: INodePropertyOptions) => {
return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1;
}); });
return returnData;
}, },
// Get all the groups to display them to user so that he can // Get all the groups to display them to user so that he can
@ -418,10 +404,10 @@ export class Jira implements INodeType {
for (const key of Object.keys(fields)) { for (const key of Object.keys(fields)) {
const field = fields[key]; const field = fields[key];
if (field.schema && Object.keys(field.schema).includes('customId')) { if (field.schema && Object.keys(field.schema).includes('customId')) {
returnData.push({ returnData.push({
name: field.name, name: field.name,
value: field.key || field.fieldId, value: field.key || field.fieldId,
}); });
} }
} }
return returnData; return returnData;
@ -642,6 +628,7 @@ export class Jira implements INodeType {
if (operation === 'get') { if (operation === 'get') {
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
const issueKey = this.getNodeParameter('issueKey', i) as string; const issueKey = this.getNodeParameter('issueKey', i) as string;
const simplifyOutput = this.getNodeParameter('simplifyOutput', i) as boolean;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
if (additionalFields.fields) { if (additionalFields.fields) {
qs.fields = additionalFields.fields as string; qs.fields = additionalFields.fields as string;
@ -652,6 +639,9 @@ export class Jira implements INodeType {
if (additionalFields.expand) { if (additionalFields.expand) {
qs.expand = additionalFields.expand as string; qs.expand = additionalFields.expand as string;
} }
if (simplifyOutput) {
qs.expand = `${qs.expand || ''},names`;
}
if (additionalFields.properties) { if (additionalFields.properties) {
qs.properties = additionalFields.properties as string; qs.properties = additionalFields.properties as string;
} }
@ -659,7 +649,12 @@ export class Jira implements INodeType {
qs.updateHistory = additionalFields.updateHistory as string; qs.updateHistory = additionalFields.updateHistory as string;
} }
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}`, 'GET', {}, qs); responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}`, 'GET', {}, qs);
returnData.push(responseData);
if (simplifyOutput) {
returnData.push(simplifyIssueOutput(responseData));
} else {
returnData.push(responseData);
}
} }
} }
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-search-post //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-search-post
@ -689,7 +684,7 @@ export class Jira implements INodeType {
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/search`, 'POST', body); responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/search`, 'POST', body);
responseData = responseData.issues; responseData = responseData.issues;
} }
returnData.push.apply(returnData, responseData); returnData.push(...responseData);
} }
} }
//https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-changelog-get //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-changelog-get

View file

@ -0,0 +1,21 @@
{
"node": "n8n-nodes-base.koBoToolbox",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": [
"Communication",
"Data & Storage"
],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/credentials/kobotoolbox"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.koBoToolbox/"
}
]
}
}

View file

@ -0,0 +1,21 @@
{
"node": "n8n-nodes-base.koBoToolboxTrigger",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": [
"Communication",
"Data & Storage"
],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/credentials/kobotoolbox"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.koBoToolboxTrigger/"
}
]
}
}

View file

@ -0,0 +1,20 @@
{
"node": "n8n-nodes-base.linear",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": [
"Productivity"
],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/credentials/linear/"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.linear/"
}
]
}
}

View file

@ -10,7 +10,11 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, JsonObject, NodeApiError, ICredentialDataDecryptedObject,
ICredentialTestFunctions,
IDataObject,
JsonObject,
NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function mauticApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query?: IDataObject, uri?: string): Promise<any> { // tslint:disable-line:no-any export async function mauticApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, query?: IDataObject, uri?: string): Promise<any> { // tslint:disable-line:no-any
@ -31,19 +35,21 @@ export async function mauticApiRequest(this: IHookFunctions | IExecuteFunctions
if (authenticationMethod === 'credentials') { if (authenticationMethod === 'credentials') {
const credentials = await this.getCredentials('mauticApi') as IDataObject; const credentials = await this.getCredentials('mauticApi') as IDataObject;
const baseUrl = credentials!.url as string;
const base64Key = Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64'); const base64Key = Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64');
options.headers!.Authorization = `Basic ${base64Key}`; options.headers!.Authorization = `Basic ${base64Key}`;
options.uri = `${credentials.url}${options.uri}`; options.uri = `${baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl}${options.uri}`;
//@ts-ignore //@ts-ignore
returnData = await this.helpers.request(options); returnData = await this.helpers.request(options);
} else { } else {
const credentials = await this.getCredentials('mauticOAuth2Api') as IDataObject; const credentials = await this.getCredentials('mauticOAuth2Api') as IDataObject;
const baseUrl = credentials!.url as string;
options.uri = `${credentials.url}${options.uri}`; options.uri = `${baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl}${options.uri}`;
//@ts-ignore //@ts-ignore
returnData = await this.helpers.requestOAuth2.call(this, 'mauticOAuth2Api', options, { includeCredentialsOnRefreshOnBody: true }); returnData = await this.helpers.requestOAuth2.call(this, 'mauticOAuth2Api', options, { includeCredentialsOnRefreshOnBody: true });
} }
@ -96,3 +102,30 @@ export function validateJSON(json: string | undefined): any { // tslint:disable-
} }
return result; return result;
} }
export async function validateCredentials(this: ICredentialTestFunctions, decryptedCredentials: ICredentialDataDecryptedObject): Promise<any> { // tslint:disable-line:no-any
const credentials = decryptedCredentials;
const {
url,
username,
password,
} = credentials as {
url: string,
username: string,
password: string,
};
const base64Key = Buffer.from(`${username}:${password}`).toString('base64');
const options: OptionsWithUri = {
method: 'GET',
headers: {
Authorization: `Basic ${base64Key}`,
},
uri: url.endsWith('/') ? `${url}api/users/self` : `${url}/api/users/self`,
json: true,
};
return await this.helpers.request(options);
}

View file

@ -3,8 +3,12 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
ICredentialDataDecryptedObject,
ICredentialsDecrypted,
ICredentialTestFunctions,
IDataObject, IDataObject,
ILoadOptionsFunctions, ILoadOptionsFunctions,
INodeCredentialTestResult,
INodeExecutionData, INodeExecutionData,
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
@ -17,6 +21,7 @@ import {
import { import {
mauticApiRequest, mauticApiRequest,
mauticApiRequestAllItems, mauticApiRequestAllItems,
validateCredentials,
validateJSON, validateJSON,
} from './GenericFunctions'; } from './GenericFunctions';
@ -80,6 +85,7 @@ export class Mautic implements INodeType {
], ],
}, },
}, },
testedBy: 'mauticCredentialTest',
}, },
{ {
name: 'mauticOAuth2Api', name: 'mauticOAuth2Api',
@ -166,6 +172,29 @@ export class Mautic implements INodeType {
}; };
methods = { methods = {
credentialTest: {
async mauticCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
try {
let responseData;
responseData = await validateCredentials.call(this, credential.data as ICredentialDataDecryptedObject);
if (responseData.id) {
return {
status: 'OK',
message: 'Authentication successful!',
};
}
} catch (error) {
return {
status: 'Error',
message: 'Invalid credentials',
};
}
return {
status: 'Error',
message: 'Invalid credentials',
};
},
},
loadOptions: { loadOptions: {
// Get all the available companies to display them to user so that he can // Get all the available companies to display them to user so that he can
// select them easily // select them easily

View file

@ -0,0 +1,201 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const chatMessageOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'chatMessage',
],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a message',
},
{
name: 'Get',
value: 'get',
description: 'Get a message',
},
{
name: 'Get All',
value: 'getAll',
description: 'Get all messages',
},
],
default: 'create',
description: 'The operation to perform.',
},
];
export const chatMessageFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* chatMessage:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'Chat ID',
name: 'chatId',
required: true,
type: 'options',
typeOptions: {
loadOptionsMethod: 'getChats',
},
displayOptions: {
show: {
operation: [
'create',
'get',
],
resource: [
'chatMessage',
],
},
},
default: '',
description: 'Chat ID',
},
{
displayName: 'Message Type',
name: 'messageType',
required: true,
type: 'options',
options: [
{
name: 'Text',
value: 'text',
},
{
name: 'HTML',
value: 'html',
},
],
displayOptions: {
show: {
operation: [
'create',
],
resource: [
'chatMessage',
],
},
},
default: '',
description: 'The type of the content',
},
{
displayName: 'Message',
name: 'message',
required: true,
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
displayOptions: {
show: {
operation: [
'create',
],
resource: [
'chatMessage',
],
},
},
default: '',
description: 'The content of the item.',
},
/* -------------------------------------------------------------------------- */
/* chatMessage:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Message ID',
name: 'messageId',
required: true,
type: 'string',
displayOptions: {
show: {
operation: [
'get',
],
resource: [
'chatMessage',
],
},
},
default: '',
},
/* -------------------------------------------------------------------------- */
/* chatMessage:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Chat ID',
name: 'chatId',
required: true,
type: 'options',
typeOptions: {
loadOptionsMethod: 'getChats',
},
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'chatMessage',
],
},
},
default: '',
description: 'Chat ID',
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'chatMessage',
],
},
},
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: [
'chatMessage',
],
returnAll: [
false,
],
},
},
typeOptions: {
minValue: 1,
maxValue: 500,
},
default: 100,
description: 'How many results to return.',
},
];

View file

@ -26,6 +26,11 @@ import {
channelMessageOperations, channelMessageOperations,
} from './ChannelMessageDescription'; } from './ChannelMessageDescription';
import {
chatMessageFields,
chatMessageOperations,
} from './ChatMessageDescription';
import { import {
taskFields, taskFields,
taskOperations, taskOperations,
@ -65,6 +70,10 @@ export class MicrosoftTeams implements INodeType {
name: 'Channel Message (Beta)', name: 'Channel Message (Beta)',
value: 'channelMessage', value: 'channelMessage',
}, },
{
name: 'Chat Message',
value: 'chatMessage',
},
{ {
name: 'Task', name: 'Task',
value: 'task', value: 'task',
@ -79,6 +88,8 @@ export class MicrosoftTeams implements INodeType {
/// MESSAGE /// MESSAGE
...channelMessageOperations, ...channelMessageOperations,
...channelMessageFields, ...channelMessageFields,
...chatMessageOperations,
...chatMessageFields,
///TASK ///TASK
...taskOperations, ...taskOperations,
...taskFields, ...taskFields,
@ -189,6 +200,29 @@ export class MicrosoftTeams implements INodeType {
} }
return returnData; return returnData;
}, },
// Get all the chats to display them to user so that they can
// select them easily
async getChats(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const qs: IDataObject = {
$expand: 'members',
};
const { value } = await microsoftApiRequest.call(this, 'GET', '/v1.0/chats', {}, qs);
for (const chat of value) {
if (!chat.topic) {
chat.topic = chat.members
.filter((member: IDataObject) => member.displayName)
.map((member: IDataObject) => member.displayName).join(', ');
}
const chatName = `${chat.topic || '(no title) - ' + chat.id} (${chat.chatType})`;
const chatId = chat.id;
returnData.push({
name: chatName,
value: chatId,
});
}
return returnData;
},
}, },
}; };
@ -298,6 +332,39 @@ export class MicrosoftTeams implements INodeType {
} }
} }
} }
if (resource === 'chatMessage') {
// https://docs.microsoft.com/en-us/graph/api/channel-post-messages?view=graph-rest-1.0&tabs=http
if (operation === 'create') {
const chatId = this.getNodeParameter('chatId', i) as string;
const messageType = this.getNodeParameter('messageType', i) as string;
const message = this.getNodeParameter('message', i) as string;
const body: IDataObject = {
body: {
contentType: messageType,
content: message,
},
};
responseData = await microsoftApiRequest.call(this, 'POST', `/v1.0/chats/${chatId}/messages`, body);
}
// https://docs.microsoft.com/en-us/graph/api/chat-list-messages?view=graph-rest-1.0&tabs=http
if (operation === 'get') {
const chatId = this.getNodeParameter('chatId', i) as string;
const messageId = this.getNodeParameter('messageId', i) as string;
responseData = await microsoftApiRequest.call(this, 'GET', `/v1.0/chats/${chatId}/messages/${messageId}`);
}
// https://docs.microsoft.com/en-us/graph/api/chat-list-messages?view=graph-rest-1.0&tabs=http
if (operation === 'getAll') {
const chatId = this.getNodeParameter('chatId', i) as string;
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
if (returnAll) {
responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/v1.0/chats/${chatId}/messages`);
} else {
qs.limit = this.getNodeParameter('limit', i) as number;
responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/v1.0/chats/${chatId}/messages`, {});
responseData = responseData.splice(0, qs.limit);
}
}
}
if (resource === 'task') { if (resource === 'task') {
//https://docs.microsoft.com/en-us/graph/api/planner-post-tasks?view=graph-rest-1.0&tabs=http //https://docs.microsoft.com/en-us/graph/api/planner-post-tasks?view=graph-rest-1.0&tabs=http
if (operation === 'create') { if (operation === 'create') {

View file

@ -4,7 +4,7 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, NodeApiError, NodeOperationError, IDataObject, JsonObject, NodeApiError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
/** /**
@ -47,6 +47,6 @@ export async function moceanApiRequest(this: IHookFunctions | IExecuteFunctions,
try { try {
return await this.helpers.request(options); return await this.helpers.request(options);
} catch (error) { } catch (error) {
throw new NodeApiError(this.getNode(), error); throw new NodeApiError(this.getNode(), (error as JsonObject));
} }
} }

View file

@ -1,20 +1,29 @@
import { IExecuteFunctions } from 'n8n-core';
import { import {
IExecuteFunctions,
} from 'n8n-core';
import {
ICredentialsDecrypted,
ICredentialTestFunctions,
IDataObject, IDataObject,
INodeCredentialTestResult,
INodeExecutionData, INodeExecutionData,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
JsonObject,
NodeOperationError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import {moceanApiRequest} from './GenericFunctions'; import {
moceanApiRequest,
} from './GenericFunctions';
export class Mocean implements INodeType { export class Mocean implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
displayName: 'Mocean', displayName: 'Mocean',
name: 'mocean', name: 'mocean',
icon: 'file:mocean.png', subtitle: `={{$parameter["operation"] + ": " + $parameter["resource"]}}`,
icon: 'file:mocean.svg',
group: ['transform'], group: ['transform'],
version: 1, version: 1,
description: 'Send SMS and voice messages via Mocean', description: 'Send SMS and voice messages via Mocean',
@ -27,6 +36,7 @@ export class Mocean implements INodeType {
{ {
name: 'moceanApi', name: 'moceanApi',
required: true, required: true,
testedBy: 'moceanApiTest',
}, },
], ],
properties: [ properties: [
@ -36,7 +46,8 @@ export class Mocean implements INodeType {
displayName: 'Resource', displayName: 'Resource',
name: 'resource', name: 'resource',
type: 'options', type: 'options',
options:[ noDataExpression: true,
options: [
{ {
name: 'SMS', name: 'SMS',
value: 'sms', value: 'sms',
@ -52,6 +63,7 @@ export class Mocean implements INodeType {
displayName: 'Operation', displayName: 'Operation',
name: 'operation', name: 'operation',
type: 'options', type: 'options',
noDataExpression: true,
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -68,7 +80,7 @@ export class Mocean implements INodeType {
}, },
], ],
default: 'send', default: 'send',
description: 'The operation to perform.', description: 'Operation to perform',
}, },
{ {
displayName: 'From', displayName: 'From',
@ -88,7 +100,7 @@ export class Mocean implements INodeType {
], ],
}, },
}, },
description: 'The number to which to send the message', description: 'Number to which to send the message',
}, },
{ {
@ -109,14 +121,14 @@ export class Mocean implements INodeType {
], ],
}, },
}, },
description: 'The number from which to send the message', description: 'Number from which to send the message',
}, },
{ {
displayName: 'Language', displayName: 'Language',
name: 'language', name: 'language',
type: 'options', type: 'options',
options:[ options: [
{ {
name: 'Chinese Mandarin (China)', name: 'Chinese Mandarin (China)',
value: 'cmn-CN', value: 'cmn-CN',
@ -169,10 +181,66 @@ export class Mocean implements INodeType {
], ],
}, },
}, },
description: 'The message to send', description: 'Message to send',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Field',
displayOptions: {
show: {
operation: [
'send',
],
resource: [
'sms',
],
},
},
default: {},
options: [
{
displayName: 'Delivery Report URL',
name: 'dlrUrl',
type: 'string',
default: '',
placeholder: '',
description: 'Delivery report URL',
},
],
}, },
], ],
};
methods = {
credentialTest: {
async moceanApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
const credentials = credential.data;
const query: IDataObject = {};
query['mocean-api-key'] = credentials!['mocean-api-key'];
query['mocean-api-secret'] = credentials!['mocean-api-secret'];
const options = {
method: 'GET',
qs: query,
uri: `https://rest.moceanapi.com/rest/2/account/balance`,
json: true,
};
try {
await this.helpers.request!(options);
} catch (error) {
return {
status: 'Error',
message: `Connection details not valid: ${(error as JsonObject).message}`,
};
}
return {
status: 'OK',
message: 'Authentication successful!',
};
},
},
}; };
@ -185,6 +253,7 @@ export class Mocean implements INodeType {
let requesetMethod: string; let requesetMethod: string;
let resource: string; let resource: string;
let text: string; let text: string;
let dlrUrl: string;
let dataKey: string; let dataKey: string;
// For Post // For Post
let body: IDataObject; let body: IDataObject;
@ -196,7 +265,7 @@ export class Mocean implements INodeType {
qs = {}; qs = {};
try { try {
resource = this.getNodeParameter('resource', itemIndex, '') as string; resource = this.getNodeParameter('resource', itemIndex, '') as string;
operation = this.getNodeParameter('operation',itemIndex,'') as string; operation = this.getNodeParameter('operation', itemIndex, '') as string;
text = this.getNodeParameter('message', itemIndex, '') as string; text = this.getNodeParameter('message', itemIndex, '') as string;
requesetMethod = 'POST'; requesetMethod = 'POST';
body['mocean-from'] = this.getNodeParameter('from', itemIndex, '') as string; body['mocean-from'] = this.getNodeParameter('from', itemIndex, '') as string;
@ -215,16 +284,21 @@ export class Mocean implements INodeType {
dataKey = 'voice'; dataKey = 'voice';
body['mocean-command'] = JSON.stringify(command); body['mocean-command'] = JSON.stringify(command);
endpoint = '/rest/2/voice/dial'; endpoint = '/rest/2/voice/dial';
} else if(resource === 'sms') { } else if (resource === 'sms') {
dlrUrl = this.getNodeParameter('options.dlrUrl', itemIndex, '') as string;
dataKey = 'messages'; dataKey = 'messages';
body['mocean-text'] = text; body['mocean-text'] = text;
if (dlrUrl !== '') {
body['mocean-dlr-url'] = dlrUrl;
body['mocean-dlr-mask'] = '1';
}
endpoint = '/rest/2/sms'; endpoint = '/rest/2/sms';
} else { } else {
throw new NodeOperationError(this.getNode(), `Unknown resource ${resource}`); throw new NodeOperationError(this.getNode(), `Unknown resource ${resource}`);
} }
if (operation === 'send') { if (operation === 'send') {
const responseData = await moceanApiRequest.call(this,requesetMethod,endpoint,body,qs); const responseData = await moceanApiRequest.call(this, requesetMethod, endpoint, body, qs);
for (const item of responseData[dataKey] as IDataObject[]) { for (const item of responseData[dataKey] as IDataObject[]) {
item.type = resource; item.type = resource;
@ -236,7 +310,7 @@ export class Mocean implements INodeType {
} }
} catch (error) { } catch (error) {
if (this.continueOnFail()) { if (this.continueOnFail()) {
returnData.push({ error: error.message }); returnData.push({ error: (error as JsonObject).message });
continue; continue;
} }
throw error; throw error;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 669 B

View file

@ -0,0 +1,24 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.16, written by Peter Selinger 2001-2019
</metadata>
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
fill="#0748A6" stroke="none">
<path d="M0 2560 l0 -2560 2560 0 2560 0 0 2560 0 2560 -2560 0 -2560 0 0
-2560z m1544 1460 c204 -20 306 -107 374 -320 54 -167 640 -1761 648 -1762 5
0 46 96 90 213 171 450 574 1519 590 1566 30 90 85 182 137 228 81 74 113 80
447 80 l285 0 45 -25 c24 -14 58 -45 75 -68 l30 -44 3 -1375 c2 -1261 1 -1378
-14 -1394 -32 -35 -82 -44 -259 -44 -180 0 -227 8 -261 46 -18 20 -19 67 -24
1233 l-5 1211 -47 -130 c-26 -71 -221 -615 -432 -1208 -212 -594 -393 -1088
-403 -1099 -44 -49 -66 -53 -268 -53 -174 0 -194 2 -237 22 -25 11 -53 32 -62
45 -8 12 -82 219 -165 458 -329 959 -660 1916 -669 1940 -8 17 -11 -347 -11
-1181 -1 -1194 -1 -1207 -21 -1232 -34 -43 -79 -52 -270 -52 -155 0 -180 2
-215 20 -22 11 -45 31 -52 45 -10 20 -12 306 -10 1370 l2 1346 26 49 c31 62
92 107 157 116 74 10 407 9 516 -1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -96,13 +96,12 @@ export async function apiRequestAllItems(this: IHookFunctions | IExecuteFunction
do { do {
responseData = await apiRequest.call(this, method, endpoint, body, query); responseData = await apiRequest.call(this, method, endpoint, body, query);
returnData.push(...responseData); returnData.push(...responseData);
query.offset += query.limit; query.offset += query.limit;
} while ( } while (
responseData.length === 0 responseData.length !== 0
); );
return returnData; return returnData;

View file

@ -16,5 +16,8 @@
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.odoo/" "url": "https://docs.n8n.io/nodes/n8n-nodes-base.odoo/"
} }
] ]
} },
} "alias": [
"ERP"
]
}

View file

@ -4,7 +4,7 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, NodeApiError, NodeOperationError, IDataObject, JsonObject, NodeApiError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -45,7 +45,7 @@ export async function redditApiRequest(
try { try {
return await this.helpers.requestOAuth2.call(this, 'redditOAuth2Api', options); return await this.helpers.requestOAuth2.call(this, 'redditOAuth2Api', options);
} catch (error) { } catch (error) {
throw new NodeApiError(this.getNode(), error); throw new NodeApiError(this.getNode(), error as JsonObject);
} }
} else { } else {
@ -53,7 +53,7 @@ export async function redditApiRequest(
try { try {
return await this.helpers.request.call(this, options); return await this.helpers.request.call(this, options);
} catch (error) { } catch (error) {
throw new NodeApiError(this.getNode(), error); throw new NodeApiError(this.getNode(), error as JsonObject);
} }
} }
} }

View file

@ -60,6 +60,11 @@ export const profileFields: INodeProperties[] = [
value: 'prefs', value: 'prefs',
description: 'Return the settings preferences of the logged-in user', description: 'Return the settings preferences of the logged-in user',
}, },
{
name: 'Saved',
value: 'saved',
description: 'Return the saved posts for the user',
},
{ {
name: 'Trophies', name: 'Trophies',
value: 'trophies', value: 'trophies',
@ -77,4 +82,51 @@ export const profileFields: INodeProperties[] = [
}, },
}, },
}, },
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
default: false,
description: 'Return all results.',
displayOptions: {
show: {
resource: [
'profile',
],
operation: [
'get',
],
details: [
'saved',
],
},
},
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
default: 100,
description: 'The number of results to return.',
typeOptions: {
minValue: 1,
maxValue: 100,
},
displayOptions: {
show: {
resource: [
'profile',
],
operation: [
'get',
],
details: [
'saved',
],
returnAll: [
false,
],
},
},
},
]; ];

View file

@ -352,7 +352,15 @@ export class Reddit implements INodeType {
const details = this.getNodeParameter('details', i) as string; const details = this.getNodeParameter('details', i) as string;
const endpoint = `api/v1/${endpoints[details]}`; const endpoint = `api/v1/${endpoints[details]}`;
responseData = await redditApiRequest.call(this, 'GET', endpoint, {}); let username;
if (details === 'saved') {
({ name: username } = await redditApiRequest.call(this, 'GET', `api/v1/me`, {}));
}
responseData = details === 'saved'
? await handleListing.call(this, i, `user/${username}/saved.json`)
: await redditApiRequest.call(this, 'GET', endpoint, {});
if (details === 'identity') { if (details === 'identity') {
responseData = responseData.features; responseData = responseData.features;

View file

@ -4,7 +4,8 @@
"codexVersion": "1.0", "codexVersion": "1.0",
"categories": [ "categories": [
"Communication", "Communication",
"Development" "Development",
"Data & Storage"
], ],
"resources": { "resources": {
"credentialDocumentation": [ "credentialDocumentation": [

View file

@ -2184,6 +2184,9 @@ export class Salesforce implements INodeType {
if (additionalFields.reason !== undefined) { if (additionalFields.reason !== undefined) {
body.Reason = additionalFields.reason as string; body.Reason = additionalFields.reason as string;
} }
if (additionalFields.status !== undefined) {
body.Status = additionalFields.status as string;
}
if (additionalFields.owner !== undefined) { if (additionalFields.owner !== undefined) {
body.OwnerId = additionalFields.owner as string; body.OwnerId = additionalFields.owner as string;
} }
@ -2248,6 +2251,9 @@ export class Salesforce implements INodeType {
if (updateFields.reason !== undefined) { if (updateFields.reason !== undefined) {
body.Reason = updateFields.reason as string; body.Reason = updateFields.reason as string;
} }
if (updateFields.status !== undefined) {
body.Status = updateFields.status as string;
}
if (updateFields.owner !== undefined) { if (updateFields.owner !== undefined) {
body.OwnerId = updateFields.owner as string; body.OwnerId = updateFields.owner as string;
} }

View file

@ -7,6 +7,7 @@ export const businessServiceOperations: INodeProperties[] = [
displayName: 'Operation', displayName: 'Operation',
name: 'operation', name: 'operation',
type: 'options', type: 'options',
noDataExpression: true,
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -44,7 +45,7 @@ export const businessServiceFields: INodeProperties[] = [
}, },
}, },
default: false, default: false,
description: 'If all results should be returned or only up to a given limit', description: 'Whether to return all results or only up to a given limit',
}, },
{ {
displayName: 'Limit', displayName: 'Limit',
@ -99,17 +100,19 @@ export const businessServiceFields: INodeProperties[] = [
name: 'sysparm_fields', name: 'sysparm_fields',
type: 'multiOptions', type: 'multiOptions',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getColumns', loadOptionsMethod: 'getColumns',
}, },
default: '', default: [],
description: 'A list of fields to return', description: 'A list of fields to return',
hint: 'String of comma separated values or an array of strings can be set in an expression',
}, },
{ {
displayName: 'Filter', displayName: 'Filter',
name: 'sysparm_query', name: 'sysparm_query',
type: 'string', type: 'string',
default: '', default: '',
description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>', description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>.',
}, },
{ {
displayName: 'Return Values', displayName: 'Return Values',

View file

@ -7,6 +7,7 @@ export const configurationItemsOperations: INodeProperties[] = [
displayName: 'Operation', displayName: 'Operation',
name: 'operation', name: 'operation',
type: 'options', type: 'options',
noDataExpression: true,
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -44,7 +45,7 @@ export const configurationItemsFields: INodeProperties[] = [
}, },
}, },
default: false, default: false,
description: 'If all results should be returned or only up to a given limit', description: 'Whether to return all results or only up to a given limit',
}, },
{ {
displayName: 'Limit', displayName: 'Limit',
@ -68,7 +69,7 @@ export const configurationItemsFields: INodeProperties[] = [
maxValue: 500, maxValue: 500,
}, },
default: 50, default: 50,
description: 'The max number of results to return', description: 'Max number of results to return',
}, },
{ {
displayName: 'Options', displayName: 'Options',
@ -99,17 +100,19 @@ export const configurationItemsFields: INodeProperties[] = [
name: 'sysparm_fields', name: 'sysparm_fields',
type: 'multiOptions', type: 'multiOptions',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getColumns', loadOptionsMethod: 'getColumns',
}, },
default: '', default: [],
description: 'A list of fields to return', description: 'A list of fields to return',
hint: 'String of comma separated values or an array of strings can be set in an expression',
}, },
{ {
displayName: 'Filter', displayName: 'Filter',
name: 'sysparm_query', name: 'sysparm_query',
type: 'string', type: 'string',
default: '', default: '',
description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>', description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>.',
}, },
{ {
displayName: 'Return Values', displayName: 'Return Values',

View file

@ -7,6 +7,7 @@ export const departmentOperations: INodeProperties[] = [
displayName: 'Operation', displayName: 'Operation',
name: 'operation', name: 'operation',
type: 'options', type: 'options',
noDataExpression: true,
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -44,7 +45,7 @@ export const departmentFields: INodeProperties[] = [
}, },
}, },
default: false, default: false,
description: 'If all results should be returned or only up to a given limit', description: 'Whether to return all results or only up to a given limit',
}, },
{ {
displayName: 'Limit', displayName: 'Limit',
@ -99,17 +100,19 @@ export const departmentFields: INodeProperties[] = [
name: 'sysparm_fields', name: 'sysparm_fields',
type: 'multiOptions', type: 'multiOptions',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getColumns', loadOptionsMethod: 'getColumns',
}, },
default: '', default: [],
description: 'A list of fields to return', description: 'A list of fields to return',
hint: 'String of comma separated values or an array of strings can be set in an expression',
}, },
{ {
displayName: 'Filter', displayName: 'Filter',
name: 'sysparm_query', name: 'sysparm_query',
type: 'string', type: 'string',
default: '', default: '',
description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>', description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>.',
}, },
{ {
displayName: 'Return Values', displayName: 'Return Values',

View file

@ -7,6 +7,7 @@ export const dictionaryOperations: INodeProperties[] = [
displayName: 'Operation', displayName: 'Operation',
name: 'operation', name: 'operation',
type: 'options', type: 'options',
noDataExpression: true,
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -44,7 +45,7 @@ export const dictionaryFields: INodeProperties[] = [
}, },
}, },
default: false, default: false,
description: 'If all results should be returned or only up to a given limit', description: 'Whether to return all results or only up to a given limit',
}, },
{ {
displayName: 'Limit', displayName: 'Limit',
@ -99,17 +100,19 @@ export const dictionaryFields: INodeProperties[] = [
name: 'sysparm_fields', name: 'sysparm_fields',
type: 'multiOptions', type: 'multiOptions',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getColumns', loadOptionsMethod: 'getColumns',
}, },
default: '', default: [],
description: 'A list of fields to return', description: 'A list of fields to return',
hint: 'String of comma separated values or an array of strings can be set in an expression',
}, },
{ {
displayName: 'Filter', displayName: 'Filter',
name: 'sysparm_query', name: 'sysparm_query',
type: 'string', type: 'string',
default: '', default: '',
description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>', description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>.',
}, },
{ {
displayName: 'Return Values', displayName: 'Return Values',

View file

@ -10,15 +10,25 @@ import {
import { import {
IDataObject, IDataObject,
INodePropertyOptions, INodePropertyOptions,
JsonObject,
NodeApiError, NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function serviceNowApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function serviceNowApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const credentials = await this.getCredentials('serviceNowOAuth2Api'); const headers = {} as IDataObject;
const authenticationMethod = this.getNodeParameter('authentication', 0, 'oAuth2') as string;
let credentials;
if (authenticationMethod === 'basicAuth') {
credentials = await this.getCredentials('serviceNowBasicApi');
} else {
credentials = await this.getCredentials('serviceNowOAuth2Api');
}
const options: OptionsWithUri = { const options: OptionsWithUri = {
headers: {}, headers,
method, method,
qs, qs,
body, body,
@ -38,11 +48,11 @@ export async function serviceNowApiRequest(this: IExecuteFunctions | ILoadOption
} }
try { try {
const credentialType = authenticationMethod === 'oAuth2' ? 'serviceNowOAuth2Api' : 'serviceNowBasicApi';
return await this.helpers.requestOAuth2!.call(this, 'serviceNowOAuth2Api', options); return await this.helpers.requestWithAuthentication.call(this, credentialType, options);
} catch (error) { } catch (error) {
throw new NodeApiError(this.getNode(), error); throw new NodeApiError(this.getNode(), (error as JsonObject));
} }
} }

View file

@ -7,6 +7,7 @@ export const incidentOperations: INodeProperties[] = [
displayName: 'Operation', displayName: 'Operation',
name: 'operation', name: 'operation',
type: 'options', type: 'options',
noDataExpression: true,
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -85,19 +86,21 @@ export const incidentFields: INodeProperties[] = [
name: 'assigned_to', name: 'assigned_to',
type: 'options', type: 'options',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getUsers', loadOptionsMethod: 'getUsers',
loadOptionsDependsOn: [ loadOptionsDependsOn: [
'additionalFields.assignment_group', 'additionalFields.assignment_group',
], ],
}, },
default: '', default: '',
description: 'Which user is the incident assigned to. Requires the selection of an assignment group', description: 'Which user is the incident assigned to. Requires the selection of an assignment group.',
}, },
{ {
displayName: 'Assignment Group', displayName: 'Assignment Group',
name: 'assignment_group', name: 'assignment_group',
type: 'options', type: 'options',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getAssignmentGroups', loadOptionsMethod: 'getAssignmentGroups',
}, },
default: '', default: '',
@ -108,6 +111,7 @@ export const incidentFields: INodeProperties[] = [
name: 'business_service', name: 'business_service',
type: 'options', type: 'options',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getBusinessServices', loadOptionsMethod: 'getBusinessServices',
}, },
default: '', default: '',
@ -125,6 +129,7 @@ export const incidentFields: INodeProperties[] = [
name: 'category', name: 'category',
type: 'options', type: 'options',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentCategories', loadOptionsMethod: 'getIncidentCategories',
}, },
default: '', default: '',
@ -142,9 +147,10 @@ export const incidentFields: INodeProperties[] = [
name: 'cmdb_ci', name: 'cmdb_ci',
type: 'multiOptions', type: 'multiOptions',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getConfigurationItems', loadOptionsMethod: 'getConfigurationItems',
}, },
default: '', default: [],
description: 'Configuration Items, \'cmdb_ci\' in metadata', description: 'Configuration Items, \'cmdb_ci\' in metadata',
}, },
{ {
@ -197,7 +203,7 @@ export const incidentFields: INodeProperties[] = [
value: 1, value: 1,
}, },
], ],
default: '', default: 1,
description: 'The impact of the incident', description: 'The impact of the incident',
}, },
{ {
@ -205,16 +211,18 @@ export const incidentFields: INodeProperties[] = [
name: 'close_code', name: 'close_code',
type: 'options', type: 'options',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentResolutionCodes', loadOptionsMethod: 'getIncidentResolutionCodes',
}, },
default: '', default: '',
description: 'The resolution code of the incident. \'close_code\' in metadata', description: 'The resolution code of the incident, \'close_code\' in metadata',
}, },
{ {
displayName: 'State', displayName: 'State',
name: 'state', name: 'state',
type: 'options', type: 'options',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentStates', loadOptionsMethod: 'getIncidentStates',
}, },
default: '', default: '',
@ -225,6 +233,7 @@ export const incidentFields: INodeProperties[] = [
name: 'subcategory', name: 'subcategory',
type: 'options', type: 'options',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentSubcategories', loadOptionsMethod: 'getIncidentSubcategories',
loadOptionsDependsOn: [ loadOptionsDependsOn: [
'additionalFields.category', 'additionalFields.category',
@ -251,7 +260,7 @@ export const incidentFields: INodeProperties[] = [
value: 1, value: 1,
}, },
], ],
default: '', default: 1,
description: 'The urgency of the incident', description: 'The urgency of the incident',
}, },
], ],
@ -275,7 +284,7 @@ export const incidentFields: INodeProperties[] = [
}, },
}, },
default: false, default: false,
description: 'If all results should be returned or only up to a given limit', description: 'Whether to return all results or only up to a given limit',
}, },
{ {
displayName: 'Limit', displayName: 'Limit',
@ -330,17 +339,19 @@ export const incidentFields: INodeProperties[] = [
name: 'sysparm_fields', name: 'sysparm_fields',
type: 'multiOptions', type: 'multiOptions',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getColumns', loadOptionsMethod: 'getColumns',
}, },
default: '', default: [],
description: 'A list of fields to return', description: 'A list of fields to return',
hint: 'String of comma separated values or an array of strings can be set in an expression',
}, },
{ {
displayName: 'Filter', displayName: 'Filter',
name: 'sysparm_query', name: 'sysparm_query',
type: 'string', type: 'string',
default: '', default: '',
description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>', description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>.',
}, },
{ {
displayName: 'Return Values', displayName: 'Return Values',
@ -417,10 +428,12 @@ export const incidentFields: INodeProperties[] = [
name: 'sysparm_fields', name: 'sysparm_fields',
type: 'multiOptions', type: 'multiOptions',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getColumns', loadOptionsMethod: 'getColumns',
}, },
default: '', default: [],
description: 'A list of fields to return', description: 'A list of fields to return',
hint: 'String of comma separated values or an array of strings can be set in an expression',
}, },
{ {
displayName: 'Return Values', displayName: 'Return Values',
@ -489,19 +502,21 @@ export const incidentFields: INodeProperties[] = [
name: 'assigned_to', name: 'assigned_to',
type: 'options', type: 'options',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getUsers', loadOptionsMethod: 'getUsers',
loadOptionsDependsOn: [ loadOptionsDependsOn: [
'additionalFields.assignment_group', 'additionalFields.assignment_group',
], ],
}, },
default: '', default: '',
description: 'Which user is the incident assigned to. Requires the selection of an assignment group', description: 'Which user is the incident assigned to. Requires the selection of an assignment group.',
}, },
{ {
displayName: 'Assignment Group', displayName: 'Assignment Group',
name: 'assignment_group', name: 'assignment_group',
type: 'options', type: 'options',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getAssignmentGroups', loadOptionsMethod: 'getAssignmentGroups',
}, },
default: '', default: '',
@ -512,6 +527,7 @@ export const incidentFields: INodeProperties[] = [
name: 'business_service', name: 'business_service',
type: 'options', type: 'options',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getBusinessServices', loadOptionsMethod: 'getBusinessServices',
}, },
default: '', default: '',
@ -529,6 +545,7 @@ export const incidentFields: INodeProperties[] = [
name: 'category', name: 'category',
type: 'options', type: 'options',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentCategories', loadOptionsMethod: 'getIncidentCategories',
}, },
default: '', default: '',
@ -546,9 +563,10 @@ export const incidentFields: INodeProperties[] = [
name: 'cmdb_ci', name: 'cmdb_ci',
type: 'multiOptions', type: 'multiOptions',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getConfigurationItems', loadOptionsMethod: 'getConfigurationItems',
}, },
default: '', default: [],
description: 'Configuration Items, \'cmdb_ci\' in metadata', description: 'Configuration Items, \'cmdb_ci\' in metadata',
}, },
{ {
@ -601,7 +619,7 @@ export const incidentFields: INodeProperties[] = [
value: 1, value: 1,
}, },
], ],
default: '', default: 1,
description: 'The impact of the incident', description: 'The impact of the incident',
}, },
{ {
@ -609,9 +627,11 @@ export const incidentFields: INodeProperties[] = [
name: 'close_code', name: 'close_code',
type: 'options', type: 'options',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentResolutionCodes', loadOptionsMethod: 'getIncidentResolutionCodes',
}, },
default: '', default: '',
// nodelinter-ignore-next-line
description: 'The resolution code of the incident. \'close_code\' in metadata', description: 'The resolution code of the incident. \'close_code\' in metadata',
}, },
{ {
@ -619,6 +639,7 @@ export const incidentFields: INodeProperties[] = [
name: 'hold_reason', name: 'hold_reason',
type: 'options', type: 'options',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentHoldReasons', loadOptionsMethod: 'getIncidentHoldReasons',
}, },
default: '', default: '',
@ -629,6 +650,7 @@ export const incidentFields: INodeProperties[] = [
name: 'state', name: 'state',
type: 'options', type: 'options',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentStates', loadOptionsMethod: 'getIncidentStates',
}, },
default: '', default: '',
@ -639,6 +661,7 @@ export const incidentFields: INodeProperties[] = [
name: 'subcategory', name: 'subcategory',
type: 'options', type: 'options',
typeOptions: { typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentSubcategories', loadOptionsMethod: 'getIncidentSubcategories',
loadOptionsDependsOn: [ loadOptionsDependsOn: [
'additionalFields.category', 'additionalFields.category',
@ -665,7 +688,7 @@ export const incidentFields: INodeProperties[] = [
value: 1, value: 1,
}, },
], ],
default: '', default: 1,
description: 'The urgency of the incident', description: 'The urgency of the incident',
}, },
], ],

View file

@ -9,6 +9,7 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
JsonObject,
NodeOperationError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -82,13 +83,49 @@ export class ServiceNow implements INodeType {
{ {
name: 'serviceNowOAuth2Api', name: 'serviceNowOAuth2Api',
required: true, required: true,
displayOptions: {
show: {
authentication: [
'oAuth2',
],
},
},
},
{
name: 'serviceNowBasicApi',
required: true,
displayOptions: {
show: {
authentication: [
'basicAuth',
],
},
},
}, },
], ],
properties: [ properties: [
{
displayName: 'Authentication',
name: 'authentication',
type: 'options',
options: [
{
name: 'Basic Auth',
value: 'basicAuth',
},
{
name: 'OAuth2',
value: 'oAuth2',
},
],
default: 'oAuth2',
description: 'Authentication method to use',
},
{ {
displayName: 'Resource', displayName: 'Resource',
name: 'resource', name: 'resource',
type: 'options', type: 'options',
noDataExpression: true,
options: [ options: [
{ {
name: 'Business Service', name: 'Business Service',
@ -427,7 +464,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean; const returnAll = this.getNodeParameter('returnAll', i) as boolean;
qs = this.getNodeParameter('options', i) as IDataObject; qs = this.getNodeParameter('options', i) as IDataObject;
if (qs.sysparm_fields) { if (qs.sysparm_fields && typeof qs.sysparm_fields !== 'string') {
qs.sysparm_fields = (qs.sysparm_fields as string[]).join(','); qs.sysparm_fields = (qs.sysparm_fields as string[]).join(',');
} }
@ -448,7 +485,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean; const returnAll = this.getNodeParameter('returnAll', i) as boolean;
qs = this.getNodeParameter('options', i) as IDataObject; qs = this.getNodeParameter('options', i) as IDataObject;
if (qs.sysparm_fields) { if (qs.sysparm_fields && typeof qs.sysparm_fields !== 'string') {
qs.sysparm_fields = (qs.sysparm_fields as string[]).join(','); qs.sysparm_fields = (qs.sysparm_fields as string[]).join(',');
} }
@ -469,7 +506,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean; const returnAll = this.getNodeParameter('returnAll', i) as boolean;
qs = this.getNodeParameter('options', i) as IDataObject; qs = this.getNodeParameter('options', i) as IDataObject;
if (qs.sysparm_fields) { if (qs.sysparm_fields && typeof qs.sysparm_fields !== 'string') {
qs.sysparm_fields = (qs.sysparm_fields as string[]).join(','); qs.sysparm_fields = (qs.sysparm_fields as string[]).join(',');
} }
@ -490,7 +527,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean; const returnAll = this.getNodeParameter('returnAll', i) as boolean;
qs = this.getNodeParameter('options', i) as IDataObject; qs = this.getNodeParameter('options', i) as IDataObject;
if (qs.sysparm_fields) { if (qs.sysparm_fields && typeof qs.sysparm_fields !== 'string') {
qs.sysparm_fields = (qs.sysparm_fields as string[]).join(','); qs.sysparm_fields = (qs.sysparm_fields as string[]).join(',');
} }
@ -529,7 +566,7 @@ export class ServiceNow implements INodeType {
const id = this.getNodeParameter('id', i) as string; const id = this.getNodeParameter('id', i) as string;
qs = this.getNodeParameter('options', i) as IDataObject; qs = this.getNodeParameter('options', i) as IDataObject;
if (qs.sysparm_fields) { if (qs.sysparm_fields && typeof qs.sysparm_fields !== 'string') {
qs.sysparm_fields = (qs.sysparm_fields as string[]).join(','); qs.sysparm_fields = (qs.sysparm_fields as string[]).join(',');
} }
@ -541,7 +578,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean; const returnAll = this.getNodeParameter('returnAll', i) as boolean;
qs = this.getNodeParameter('options', i) as IDataObject; qs = this.getNodeParameter('options', i) as IDataObject;
if (qs.sysparm_fields) { if (qs.sysparm_fields && typeof qs.sysparm_fields !== 'string') {
qs.sysparm_fields = (qs.sysparm_fields as string[]).join(','); qs.sysparm_fields = (qs.sysparm_fields as string[]).join(',');
} }
@ -604,7 +641,7 @@ export class ServiceNow implements INodeType {
const id = this.getNodeParameter('id', i) as string; const id = this.getNodeParameter('id', i) as string;
qs = this.getNodeParameter('options', i) as IDataObject; qs = this.getNodeParameter('options', i) as IDataObject;
if (qs.sysparm_fields) { if (qs.sysparm_fields && typeof qs.sysparm_fields !== 'string') {
qs.sysparm_fields = (qs.sysparm_fields as string[]).join(','); qs.sysparm_fields = (qs.sysparm_fields as string[]).join(',');
} }
@ -617,7 +654,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean; const returnAll = this.getNodeParameter('returnAll', i) as boolean;
qs = this.getNodeParameter('options', i) as IDataObject; qs = this.getNodeParameter('options', i) as IDataObject;
if (qs.sysparm_fields) { if (qs.sysparm_fields && typeof qs.sysparm_fields !== 'string') {
qs.sysparm_fields = (qs.sysparm_fields as string[]).join(','); qs.sysparm_fields = (qs.sysparm_fields as string[]).join(',');
} }
@ -679,7 +716,7 @@ export class ServiceNow implements INodeType {
const getOption = this.getNodeParameter('getOption', i) as string; const getOption = this.getNodeParameter('getOption', i) as string;
qs = this.getNodeParameter('options', i) as IDataObject; qs = this.getNodeParameter('options', i) as IDataObject;
if (qs.sysparm_fields) { if (qs.sysparm_fields && typeof qs.sysparm_fields !== 'string') {
qs.sysparm_fields = (qs.sysparm_fields as string[]).join(','); qs.sysparm_fields = (qs.sysparm_fields as string[]).join(',');
} }
@ -700,7 +737,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean; const returnAll = this.getNodeParameter('returnAll', i) as boolean;
qs = this.getNodeParameter('options', i) as IDataObject; qs = this.getNodeParameter('options', i) as IDataObject;
if (qs.sysparm_fields) { if (qs.sysparm_fields && typeof qs.sysparm_fields !== 'string') {
qs.sysparm_fields = (qs.sysparm_fields as string[]).join(','); qs.sysparm_fields = (qs.sysparm_fields as string[]).join(',');
} }
@ -730,7 +767,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean; const returnAll = this.getNodeParameter('returnAll', i) as boolean;
qs = this.getNodeParameter('options', i) as IDataObject; qs = this.getNodeParameter('options', i) as IDataObject;
if (qs.sysparm_fields) { if (qs.sysparm_fields && typeof qs.sysparm_fields !== 'string') {
qs.sysparm_fields = (qs.sysparm_fields as string[]).join(','); qs.sysparm_fields = (qs.sysparm_fields as string[]).join(',');
} }
@ -751,7 +788,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean; const returnAll = this.getNodeParameter('returnAll', i) as boolean;
qs = this.getNodeParameter('options', i) as IDataObject; qs = this.getNodeParameter('options', i) as IDataObject;
if (qs.sysparm_fields) { if (qs.sysparm_fields && typeof qs.sysparm_fields !== 'string') {
qs.sysparm_fields = (qs.sysparm_fields as string[]).join(','); qs.sysparm_fields = (qs.sysparm_fields as string[]).join(',');
} }
@ -771,7 +808,7 @@ export class ServiceNow implements INodeType {
} }
} catch (error) { } catch (error) {
if (this.continueOnFail()) { if (this.continueOnFail()) {
returnData.push({ error: error.message }); returnData.push({ error: (error as JsonObject).message });
continue; continue;
} }

View file

@ -7,6 +7,7 @@ export const tableRecordOperations: INodeProperties[] = [
displayName: 'Operation', displayName: 'Operation',
name: 'operation', name: 'operation',
type: 'options', type: 'options',
noDataExpression: true,
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -71,7 +72,7 @@ export const tableRecordFields: INodeProperties[] = [
type: 'options', type: 'options',
options: [ options: [
{ {
name: 'Auto-map Input Data to Columns', name: 'Auto-Map Input Data to Columns',
value: 'mapInput', value: 'mapInput',
description: 'Use when node input names match destination field names', description: 'Use when node input names match destination field names',
}, },
@ -117,7 +118,7 @@ export const tableRecordFields: INodeProperties[] = [
}, },
default: '', default: '',
required: false, required: false,
description: 'List of input properties to avoid sending, separated by commas. Leave empty to send all inputs', description: 'List of input properties to avoid sending, separated by commas. Leave empty to send all inputs.',
}, },
{ {
displayName: 'Fields to Send', displayName: 'Fields to Send',
@ -208,7 +209,7 @@ export const tableRecordFields: INodeProperties[] = [
}, },
}, },
default: false, default: false,
description: 'If all results should be returned or only up to a given limit', description: 'Whether to return all results or only up to a given limit',
}, },
{ {
displayName: 'Limit', displayName: 'Limit',
@ -268,15 +269,16 @@ export const tableRecordFields: INodeProperties[] = [
'tableName', 'tableName',
], ],
}, },
default: '', default: [],
description: 'A list of fields to return', description: 'A list of fields to return',
hint: 'String of comma separated values or an array of strings can be set in an expression',
}, },
{ {
displayName: 'Filter', displayName: 'Filter',
name: 'sysparm_query', name: 'sysparm_query',
type: 'string', type: 'string',
default: '', default: '',
description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>', description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>.',
}, },
{ {
displayName: 'Return Values', displayName: 'Return Values',
@ -380,8 +382,9 @@ export const tableRecordFields: INodeProperties[] = [
'tableName', 'tableName',
], ],
}, },
default: '', default: [],
description: 'A list of fields to return', description: 'A list of fields to return',
hint: 'String of comma separated values or an array of strings can be set in an expression',
}, },
{ {
displayName: 'Return Values', displayName: 'Return Values',
@ -455,7 +458,7 @@ export const tableRecordFields: INodeProperties[] = [
type: 'options', type: 'options',
options: [ options: [
{ {
name: 'Auto-map Input Data to Columns', name: 'Auto-Map Input Data to Columns',
value: 'mapInput', value: 'mapInput',
description: 'Use when node input names match destination field names', description: 'Use when node input names match destination field names',
}, },
@ -501,7 +504,7 @@ export const tableRecordFields: INodeProperties[] = [
}, },
default: '', default: '',
required: false, required: false,
description: 'List of input properties to avoid sending, separated by commas. Leave empty to send all inputs', description: 'List of input properties to avoid sending, separated by commas. Leave empty to send all inputs.',
}, },
{ {
displayName: 'Fields to Send', displayName: 'Fields to Send',

View file

@ -7,6 +7,7 @@ export const userOperations: INodeProperties[] = [
displayName: 'Operation', displayName: 'Operation',
name: 'operation', name: 'operation',
type: 'options', type: 'options',
noDataExpression: true,
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -84,7 +85,7 @@ export const userFields: INodeProperties[] = [
displayName: 'Active', displayName: 'Active',
name: 'active', name: 'active',
type: 'boolean', type: 'boolean',
default: '', default: false,
description: 'Whether to activate the user', description: 'Whether to activate the user',
}, },
{ {
@ -196,7 +197,7 @@ export const userFields: INodeProperties[] = [
displayName: 'Password Needs Reset', displayName: 'Password Needs Reset',
name: 'password_needs_reset', name: 'password_needs_reset',
type: 'boolean', type: 'boolean',
default: '', default: false,
description: 'Whether to require a password reset when the user logs in', description: 'Whether to require a password reset when the user logs in',
}, },
{ {
@ -213,7 +214,7 @@ export const userFields: INodeProperties[] = [
typeOptions: { typeOptions: {
loadOptionsMethod: 'getUserRoles', loadOptionsMethod: 'getUserRoles',
}, },
default: '', default: [],
description: 'Roles of the user', description: 'Roles of the user',
}, },
{ {
@ -242,6 +243,7 @@ export const userFields: INodeProperties[] = [
name: 'user_name', name: 'user_name',
type: 'string', type: 'string',
default: '', default: '',
// nodelinter-ignore-next-line
description: 'A username associated with the user (e.g. user_name.123)', description: 'A username associated with the user (e.g. user_name.123)',
}, },
{ {
@ -272,7 +274,7 @@ export const userFields: INodeProperties[] = [
}, },
}, },
default: false, default: false,
description: 'If all results should be returned or only up to a given limit', description: 'Whether to return all results or only up to a given limit',
}, },
{ {
displayName: 'Limit', displayName: 'Limit',
@ -332,15 +334,16 @@ export const userFields: INodeProperties[] = [
'operation', 'operation',
], ],
}, },
default: '', default: [],
description: 'A list of fields to return', description: 'A list of fields to return',
hint: 'String of comma separated values or an array of strings can be set in an expression',
}, },
{ {
displayName: 'Filter', displayName: 'Filter',
name: 'sysparm_query', name: 'sysparm_query',
type: 'string', type: 'string',
default: '', default: '',
description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>', description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>.',
}, },
{ {
displayName: 'Return Values', displayName: 'Return Values',
@ -491,8 +494,9 @@ export const userFields: INodeProperties[] = [
'operation', 'operation',
], ],
}, },
default: '', default: [],
description: 'A list of fields to return', description: 'A list of fields to return',
hint: 'String of comma separated values or an array of strings can be set in an expression',
}, },
{ {
displayName: 'Return Values', displayName: 'Return Values',
@ -560,7 +564,7 @@ export const userFields: INodeProperties[] = [
displayName: 'Active', displayName: 'Active',
name: 'active', name: 'active',
type: 'boolean', type: 'boolean',
default: '', default: false,
description: 'Whether to activate the user', description: 'Whether to activate the user',
}, },
{ {
@ -672,7 +676,7 @@ export const userFields: INodeProperties[] = [
displayName: 'Password Needs Reset', displayName: 'Password Needs Reset',
name: 'password_needs_reset', name: 'password_needs_reset',
type: 'boolean', type: 'boolean',
default: '', default: false,
description: 'Whether to require a password reset when the user logs in', description: 'Whether to require a password reset when the user logs in',
}, },
{ {
@ -689,7 +693,7 @@ export const userFields: INodeProperties[] = [
typeOptions: { typeOptions: {
loadOptionsMethod: 'getUserRoles', loadOptionsMethod: 'getUserRoles',
}, },
default: '', default: [],
description: 'Roles of the user', description: 'Roles of the user',
}, },
{ {
@ -718,6 +722,7 @@ export const userFields: INodeProperties[] = [
name: 'user_name', name: 'user_name',
type: 'string', type: 'string',
default: '', default: '',
// nodelinter-ignore-next-line
description: 'A username associated with the user (e.g. user_name.123)', description: 'A username associated with the user (e.g. user_name.123)',
}, },
{ {

View file

@ -7,6 +7,7 @@ export const userGroupOperations: INodeProperties[] = [
displayName: 'Operation', displayName: 'Operation',
name: 'operation', name: 'operation',
type: 'options', type: 'options',
noDataExpression: true,
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -44,7 +45,7 @@ export const userGroupFields: INodeProperties[] = [
}, },
}, },
default: false, default: false,
description: 'If all results should be returned or only up to a given limit', description: 'Whether to return all results or only up to a given limit',
}, },
{ {
displayName: 'Limit', displayName: 'Limit',
@ -101,15 +102,16 @@ export const userGroupFields: INodeProperties[] = [
typeOptions: { typeOptions: {
loadOptionsMethod: 'getColumns', loadOptionsMethod: 'getColumns',
}, },
default: '', default: [],
description: 'A list of fields to return', description: 'A list of fields to return',
hint: 'String of comma separated values or an array of strings can be set in an expression',
}, },
{ {
displayName: 'Filter', displayName: 'Filter',
name: 'sysparm_query', name: 'sysparm_query',
type: 'string', type: 'string',
default: '', default: '',
description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>', description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>.',
}, },
{ {
displayName: 'Return Values', displayName: 'Return Values',

View file

@ -7,6 +7,7 @@ export const userRoleOperations: INodeProperties[] = [
displayName: 'Operation', displayName: 'Operation',
name: 'operation', name: 'operation',
type: 'options', type: 'options',
noDataExpression: true,
displayOptions: { displayOptions: {
show: { show: {
resource: [ resource: [
@ -44,7 +45,7 @@ export const userRoleFields: INodeProperties[] = [
}, },
}, },
default: false, default: false,
description: 'If all results should be returned or only up to a given limit', description: 'Whether to return all results or only up to a given limit',
}, },
{ {
displayName: 'Limit', displayName: 'Limit',
@ -101,15 +102,16 @@ export const userRoleFields: INodeProperties[] = [
typeOptions: { typeOptions: {
loadOptionsMethod: 'getColumns', loadOptionsMethod: 'getColumns',
}, },
default: '', default: [],
description: 'A list of fields to return', description: 'A list of fields to return',
hint: 'String of comma separated values or an array of strings can be set in an expression',
}, },
{ {
displayName: 'Filter', displayName: 'Filter',
name: 'sysparm_query', name: 'sysparm_query',
type: 'string', type: 'string',
default: '', default: '',
description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>', description: 'An encoded query string used to filter the results. <a href="https://developer.servicenow.com/dev.do#!/learn/learning-plans/quebec/servicenow_application_developer/app_store_learnv2_rest_quebec_more_about_query_parameters">More info</a>.',
}, },
{ {
displayName: 'Return Values', displayName: 'Return Values',

View file

@ -46,6 +46,11 @@ export const activityOperations: INodeProperties[] = [
value: 'getLaps', value: 'getLaps',
description: 'Get all activity laps', description: 'Get all activity laps',
}, },
{
name: 'Get Streams',
value: 'getStreams',
description: 'Get activity streams',
},
{ {
name: 'Get Zones', name: 'Get Zones',
value: 'getZones', value: 'getZones',
@ -316,6 +321,7 @@ export const activityFields: INodeProperties[] = [
'getLaps', 'getLaps',
'getKudos', 'getKudos',
'getZones', 'getZones',
'getStreams',
], ],
}, },
}, },
@ -369,7 +375,70 @@ export const activityFields: INodeProperties[] = [
default: 50, default: 50,
description: 'How many results to return.', description: 'How many results to return.',
}, },
{
displayName: 'Keys',
name: 'keys',
type: 'multiOptions',
options: [
{
name: 'Altitude',
value: 'altitude',
},
{
name: 'Cadence',
value: 'cadence',
},
{
name: 'Distance',
value: 'distance',
},
{
name: 'Gradient',
value: 'grade_smooth',
},
{
name: 'Heartrate',
value: 'heartrate',
},
{
name: 'Latitude / Longitude',
value: 'latlng',
},
{
name: 'Moving',
value: 'moving',
},
{
name: 'Temperature',
value: 'temp',
},
{
name: 'Time',
value: 'time',
},
{
name: 'Velocity',
value: 'velocity_smooth',
},
{
name: 'Watts',
value: 'watts',
},
],
displayOptions: {
show: {
resource: [
'activity',
],
operation: [
'getStreams',
],
},
},
required: true,
default: [],
description: 'Desired stream types to return',
},
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* activity:getAll */ /* activity:getAll */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */

View file

@ -129,6 +129,15 @@ export class Strava implements INodeType {
responseData = responseData.splice(0, limit); responseData = responseData.splice(0, limit);
} }
} }
//https://developers.strava.com/docs/reference/#api-Streams-getActivityStreams
if (operation === 'getStreams') {
const activityId = this.getNodeParameter('activityId', i) as string;
const keys = this.getNodeParameter('keys', i) as string[];
qs.keys = keys.toString();
qs.key_by_type = true;
responseData = await stravaApiRequest.call(this, 'GET', `/activities/${activityId}/streams`, {}, qs);
}
//https://developers.mailerlite.com/reference#subscribers //https://developers.mailerlite.com/reference#subscribers
if (operation === 'getAll') { if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', i) as boolean; const returnAll = this.getNodeParameter('returnAll', i) as boolean;

View file

@ -24,6 +24,7 @@ export async function supabaseApiRequest(this: IExecuteFunctions | IExecuteSingl
const options: OptionsWithUri = { const options: OptionsWithUri = {
headers: { headers: {
apikey: credentials.serviceRole, apikey: credentials.serviceRole,
Authorization: 'Bearer ' + credentials.serviceRole,
Prefer: 'return=representation', Prefer: 'return=representation',
}, },
method, method,
@ -296,7 +297,7 @@ export const buildGetQuery = (obj: IDataObject, value: IDataObject) => {
return Object.assign(obj, { [`${value.keyName}`]: `eq.${value.keyValue}` }); return Object.assign(obj, { [`${value.keyName}`]: `eq.${value.keyValue}` });
}; };
export async function validateCrendentials( export async function validateCredentials(
this: ICredentialTestFunctions, this: ICredentialTestFunctions,
decryptedCredentials: ICredentialDataDecryptedObject): Promise<any> { // tslint:disable-line:no-any decryptedCredentials: ICredentialDataDecryptedObject): Promise<any> { // tslint:disable-line:no-any
@ -309,6 +310,7 @@ export async function validateCrendentials(
const options: OptionsWithUri = { const options: OptionsWithUri = {
headers: { headers: {
apikey: serviceRole, apikey: serviceRole,
Authorization: 'Bearer ' + serviceRole,
}, },
method: 'GET', method: 'GET',
uri: `${credentials.host}/rest/v1/`, uri: `${credentials.host}/rest/v1/`,

View file

@ -228,7 +228,7 @@ export const rowFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Select Conditions', displayName: 'Select Conditions',
name: 'primaryKey', name: 'filters',
type: 'fixedCollection', type: 'fixedCollection',
typeOptions: { typeOptions: {
multipleValues: true, multipleValues: true,

View file

@ -21,7 +21,7 @@ import {
buildOrQuery, buildOrQuery,
buildQuery, buildQuery,
supabaseApiRequest, supabaseApiRequest,
validateCrendentials, validateCredentials,
} from './GenericFunctions'; } from './GenericFunctions';
import { import {
@ -106,7 +106,7 @@ export class Supabase implements INodeType {
credentialTest: { credentialTest: {
async supabaseApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> { async supabaseApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
try { try {
await validateCrendentials.call(this, credential.data as ICredentialDataDecryptedObject); await validateCredentials.call(this, credential.data as ICredentialDataDecryptedObject);
} catch (error) { } catch (error) {
return { return {
status: 'Error', status: 'Error',

View file

@ -70,7 +70,7 @@ export async function wiseApiRequest(
throw new NodeApiError(this.getNode(), error); throw new NodeApiError(this.getNode(), error);
} }
if (response.statusCode === 200) { if (response.statusCode >= 200 && response.statusCode < 300) {
return response.body; return response.body;
} }

View file

@ -1,6 +1,6 @@
import { import {
INodeProperties, INodeProperties,
} from 'n8n-workflow'; } from 'n8n-workflow';
export const contactOperations: INodeProperties[] = [ export const contactOperations: INodeProperties[] = [
{ {
@ -43,9 +43,9 @@ export const contactOperations: INodeProperties[] = [
export const contactFields: INodeProperties[] = [ export const contactFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* contact:create */ /* contact:create */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Organization ID', displayName: 'Organization ID',
name: 'organizationId', name: 'organizationId',
@ -108,82 +108,82 @@ export const contactFields: INodeProperties[] = [
default: '', default: '',
description: 'A user defined account number', description: 'A user defined account number',
}, },
// { {
// displayName: 'Addresses', displayName: 'Addresses',
// name: 'addressesUi', name: 'addressesUi',
// type: 'fixedCollection', type: 'fixedCollection',
// typeOptions: { typeOptions: {
// multipleValues: true, multipleValues: true,
// }, },
// default: '', default: '',
// placeholder: 'Add Address', placeholder: 'Add Address',
// options: [ options: [
// { {
// name: 'addressesValues', name: 'addressesValues',
// displayName: 'Address', displayName: 'Address',
// values: [ values: [
// { {
// displayName: 'Type', displayName: 'Type',
// name: 'type', name: 'type',
// type: 'options', type: 'options',
// options: [ options: [
// { {
// name: 'PO Box', name: 'PO Box',
// value: 'POBOX', value: 'POBOX',
// }, },
// { {
// name: 'Street', name: 'Street',
// value: 'STREET', value: 'STREET',
// }, },
// ], ],
// default: '', default: '',
// }, },
// { {
// displayName: 'Line 1', displayName: 'Line 1',
// name: 'line1', name: 'line1',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// { {
// displayName: 'Line 2', displayName: 'Line 2',
// name: 'line2', name: 'line2',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// { {
// displayName: 'City', displayName: 'City',
// name: 'city', name: 'city',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// { {
// displayName: 'Region', displayName: 'Region',
// name: 'region', name: 'region',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// { {
// displayName: 'Postal Code', displayName: 'Postal Code',
// name: 'postalCode', name: 'postalCode',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// { {
// displayName: 'Country', displayName: 'Country',
// name: 'country', name: 'country',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// { {
// displayName: 'Attention To', displayName: 'Attention To',
// name: 'attentionTo', name: 'attentionTo',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// ], ],
// }, },
// ], ],
// }, },
{ {
displayName: 'Bank Account Details', displayName: 'Bank Account Details',
name: 'bankAccountDetails', name: 'bankAccountDetails',
@ -250,66 +250,66 @@ export const contactFields: INodeProperties[] = [
default: '', default: '',
description: 'Last name of contact person (max length = 255)', description: 'Last name of contact person (max length = 255)',
}, },
// { {
// displayName: 'Phones', displayName: 'Phones',
// name: 'phonesUi', name: 'phonesUi',
// type: 'fixedCollection', type: 'fixedCollection',
// typeOptions: { typeOptions: {
// multipleValues: true, multipleValues: true,
// }, },
// default: '', default: '',
// placeholder: 'Add Phone', placeholder: 'Add Phone',
// options: [ options: [
// { {
// name: 'phonesValues', name: 'phonesValues',
// displayName: 'Phones', displayName: 'Phones',
// values: [ values: [
// { {
// displayName: 'Type', displayName: 'Type',
// name: 'type', name: 'phoneType',
// type: 'options', type: 'options',
// options: [ options: [
// { {
// name: 'Default', name: 'Default',
// value: 'DEFAULT', value: 'DEFAULT',
// }, },
// { {
// name: 'DDI', name: 'DDI',
// value: 'DDI', value: 'DDI',
// }, },
// { {
// name: 'Mobile', name: 'Mobile',
// value: 'MOBILE', value: 'MOBILE',
// }, },
// { {
// name: 'Fax', name: 'Fax',
// value: 'FAX', value: 'FAX',
// }, },
// ], ],
// default: '', default: '',
// }, },
// { {
// displayName: 'Number', displayName: 'Number',
// name: 'phoneNumber', name: 'phoneNumber',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// { {
// displayName: 'Area Code', displayName: 'Area Code',
// name: 'phoneAreaCode', name: 'phoneAreaCode',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// { {
// displayName: 'Country Code', displayName: 'Country Code',
// name: 'phoneCountryCode', name: 'phoneCountryCode',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// ], ],
// }, },
// ], ],
// }, },
{ {
displayName: 'Purchase Default Account Code', displayName: 'Purchase Default Account Code',
name: 'purchasesDefaultAccountCode', name: 'purchasesDefaultAccountCode',
@ -353,9 +353,9 @@ export const contactFields: INodeProperties[] = [
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* contact:get */ /* contact:get */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Organization ID', displayName: 'Organization ID',
name: 'organizationId', name: 'organizationId',
@ -393,9 +393,9 @@ export const contactFields: INodeProperties[] = [
}, },
required: true, required: true,
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* contact:getAll */ /* contact:getAll */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Organization ID', displayName: 'Organization ID',
name: 'organizationId', name: 'organizationId',
@ -519,9 +519,9 @@ export const contactFields: INodeProperties[] = [
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* contact:update */ /* contact:update */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Organization ID', displayName: 'Organization ID',
name: 'organizationId', name: 'organizationId',
@ -583,82 +583,82 @@ export const contactFields: INodeProperties[] = [
default: '', default: '',
description: 'A user defined account number', description: 'A user defined account number',
}, },
// { {
// displayName: 'Addresses', displayName: 'Addresses',
// name: 'addressesUi', name: 'addressesUi',
// type: 'fixedCollection', type: 'fixedCollection',
// typeOptions: { typeOptions: {
// multipleValues: true, multipleValues: true,
// }, },
// default: '', default: '',
// placeholder: 'Add Address', placeholder: 'Add Address',
// options: [ options: [
// { {
// name: 'addressesValues', name: 'addressesValues',
// displayName: 'Address', displayName: 'Address',
// values: [ values: [
// { {
// displayName: 'Type', displayName: 'Type',
// name: 'type', name: 'type',
// type: 'options', type: 'options',
// options: [ options: [
// { {
// name: 'PO Box', name: 'PO Box',
// value: 'POBOX', value: 'POBOX',
// }, },
// { {
// name: 'Street', name: 'Street',
// value: 'STREET', value: 'STREET',
// }, },
// ], ],
// default: '', default: '',
// }, },
// { {
// displayName: 'Line 1', displayName: 'Line 1',
// name: 'line1', name: 'line1',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// { {
// displayName: 'Line 2', displayName: 'Line 2',
// name: 'line2', name: 'line2',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// { {
// displayName: 'City', displayName: 'City',
// name: 'city', name: 'city',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// { {
// displayName: 'Region', displayName: 'Region',
// name: 'region', name: 'region',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// { {
// displayName: 'Postal Code', displayName: 'Postal Code',
// name: 'postalCode', name: 'postalCode',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// { {
// displayName: 'Country', displayName: 'Country',
// name: 'country', name: 'country',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// { {
// displayName: 'Attention To', displayName: 'Attention To',
// name: 'attentionTo', name: 'attentionTo',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// ], ],
// }, },
// ], ],
// }, },
{ {
displayName: 'Bank Account Details', displayName: 'Bank Account Details',
name: 'bankAccountDetails', name: 'bankAccountDetails',
@ -732,66 +732,66 @@ export const contactFields: INodeProperties[] = [
default: '', default: '',
description: 'Full name of contact/organisation', description: 'Full name of contact/organisation',
}, },
// { {
// displayName: 'Phones', displayName: 'Phones',
// name: 'phonesUi', name: 'phonesUi',
// type: 'fixedCollection', type: 'fixedCollection',
// typeOptions: { typeOptions: {
// multipleValues: true, multipleValues: true,
// }, },
// default: '', default: '',
// placeholder: 'Add Phone', placeholder: 'Add Phone',
// options: [ options: [
// { {
// name: 'phonesValues', name: 'phonesValues',
// displayName: 'Phones', displayName: 'Phones',
// values: [ values: [
// { {
// displayName: 'Type', displayName: 'Type',
// name: 'type', name: 'phoneType',
// type: 'options', type: 'options',
// options: [ options: [
// { {
// name: 'Default', name: 'Default',
// value: 'DEFAULT', value: 'DEFAULT',
// }, },
// { {
// name: 'DDI', name: 'DDI',
// value: 'DDI', value: 'DDI',
// }, },
// { {
// name: 'Mobile', name: 'Mobile',
// value: 'MOBILE', value: 'MOBILE',
// }, },
// { {
// name: 'Fax', name: 'Fax',
// value: 'FAX', value: 'FAX',
// }, },
// ], ],
// default: '', default: '',
// }, },
// { {
// displayName: 'Number', displayName: 'Number',
// name: 'phoneNumber', name: 'phoneNumber',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// { {
// displayName: 'Area Code', displayName: 'Area Code',
// name: 'phoneAreaCode', name: 'phoneAreaCode',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// { {
// displayName: 'Country Code', displayName: 'Country Code',
// name: 'phoneCountryCode', name: 'phoneCountryCode',
// type: 'string', type: 'string',
// default: '', default: '',
// }, },
// ], ],
// }, },
// ], ],
// }, },
{ {
displayName: 'Purchase Default Account Code', displayName: 'Purchase Default Account Code',
name: 'purchasesDefaultAccountCode', name: 'purchasesDefaultAccountCode',

View file

@ -9,7 +9,9 @@ import {
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, NodeApiError, IDataObject,
JsonObject,
NodeApiError,
} from 'n8n-workflow'; } from 'n8n-workflow';
export async function xeroApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any export async function xeroApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
@ -26,6 +28,7 @@ export async function xeroApiRequest(this: IExecuteFunctions | IExecuteSingleFun
try { try {
if (body.organizationId) { if (body.organizationId) {
options.headers = { ...options.headers, 'Xero-tenant-id': body.organizationId }; options.headers = { ...options.headers, 'Xero-tenant-id': body.organizationId };
delete body.organizationId;
} }
if (Object.keys(headers).length !== 0) { if (Object.keys(headers).length !== 0) {
options.headers = Object.assign({}, options.headers, headers); options.headers = Object.assign({}, options.headers, headers);
@ -36,7 +39,7 @@ export async function xeroApiRequest(this: IExecuteFunctions | IExecuteSingleFun
//@ts-ignore //@ts-ignore
return await this.helpers.requestOAuth2.call(this, 'xeroOAuth2Api', options); return await this.helpers.requestOAuth2.call(this, 'xeroOAuth2Api', options);
} catch (error) { } catch (error) {
throw new NodeApiError(this.getNode(), error); throw new NodeApiError(this.getNode(), error as JsonObject);
} }
} }

View file

@ -1,6 +1,6 @@
export interface IAddress { export interface IAddress {
Type?: string; AddressType?: string;
AddressLine1?: string; AddressLine1?: string;
AddressLine2?: string; AddressLine2?: string;
City?: string; City?: string;
@ -11,7 +11,7 @@ export interface IAddress {
} }
export interface IPhone { export interface IPhone {
Type?: string; PhoneType?: string;
PhoneNumber?: string; PhoneNumber?: string;
PhoneAreaCode?: string; PhoneAreaCode?: string;
PhoneCountryCode?: string; PhoneCountryCode?: string;

View file

@ -9,6 +9,7 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
JsonObject,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
@ -32,9 +33,9 @@ import {
} from './InvoiceInterface'; } from './InvoiceInterface';
import { import {
IAddress,
IContact, IContact,
// IPhone, IPhone,
// IAddress,
} from './IContactInterface'; } from './IContactInterface';
export class Xero implements INodeType { export class Xero implements INodeType {
@ -222,9 +223,9 @@ export class Xero implements INodeType {
const lineItemsValues = ((this.getNodeParameter('lineItemsUi', i) as IDataObject).lineItemsValues as IDataObject[]); const lineItemsValues = ((this.getNodeParameter('lineItemsUi', i) as IDataObject).lineItemsValues as IDataObject[]);
const body: IInvoice = { const body: IInvoice = {
organizationId, organizationId,
Type: type, Type: type,
Contact: { ContactID: contactId }, Contact: { ContactID: contactId },
}; };
if (lineItemsValues) { if (lineItemsValues) {
@ -311,7 +312,7 @@ export class Xero implements INodeType {
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
const body: IInvoice = { const body: IInvoice = {
organizationId, organizationId,
}; };
if (updateFields.lineItemsUi) { if (updateFields.lineItemsUi) {
@ -353,7 +354,7 @@ export class Xero implements INodeType {
body.Type = updateFields.type as string; body.Type = updateFields.type as string;
} }
if (updateFields.Contact) { if (updateFields.Contact) {
body.Contact = { ContactID: updateFields.contactId as string }; body.Contact = { ContactID: updateFields.contactId as string };
} }
if (updateFields.brandingThemeId) { if (updateFields.brandingThemeId) {
body.BrandingThemeID = updateFields.brandingThemeId as string; body.BrandingThemeID = updateFields.brandingThemeId as string;
@ -438,11 +439,11 @@ export class Xero implements INodeType {
const organizationId = this.getNodeParameter('organizationId', i) as string; const organizationId = this.getNodeParameter('organizationId', i) as string;
const name = this.getNodeParameter('name', i) as string; const name = this.getNodeParameter('name', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
// const addressesUi = additionalFields.addressesUi as IDataObject; const addressesUi = additionalFields.addressesUi as IDataObject;
// const phonesUi = additionalFields.phonesUi as IDataObject; const phonesUi = additionalFields.phonesUi as IDataObject;
const body: IContact = { const body: IContact = {
Name: name, Name: name,
}; };
if (additionalFields.accountNumber) { if (additionalFields.accountNumber) {
@ -497,41 +498,41 @@ export class Xero implements INodeType {
body.xeroNetworkKey = additionalFields.xeroNetworkKey as string; body.xeroNetworkKey = additionalFields.xeroNetworkKey as string;
} }
// if (phonesUi) { if (phonesUi) {
// const phoneValues = phonesUi?.phonesValues as IDataObject[]; const phoneValues = phonesUi?.phonesValues as IDataObject[];
// if (phoneValues) { if (phoneValues) {
// const phones: IPhone[] = []; const phones: IPhone[] = [];
// for (const phoneValue of phoneValues) { for (const phoneValue of phoneValues) {
// const phone: IPhone = {}; const phone: IPhone = {};
// phone.Type = phoneValue.type as string; phone.PhoneType = phoneValue.phoneType as string;
// phone.PhoneNumber = phoneValue.PhoneNumber as string; phone.PhoneNumber = phoneValue.phoneNumber as string;
// phone.PhoneAreaCode = phoneValue.phoneAreaCode as string; phone.PhoneAreaCode = phoneValue.phoneAreaCode as string;
// phone.PhoneCountryCode = phoneValue.phoneCountryCode as string; phone.PhoneCountryCode = phoneValue.phoneCountryCode as string;
// phones.push(phone); phones.push(phone);
// } }
// body.Phones = phones; body.Phones = phones;
// } }
// } }
// if (addressesUi) { if (addressesUi) {
// const addressValues = addressesUi?.addressesValues as IDataObject[]; const addressValues = addressesUi?.addressesValues as IDataObject[];
// if (addressValues) { if (addressValues) {
// const addresses: IAddress[] = []; const addresses: IAddress[] = [];
// for (const addressValue of addressValues) { for (const addressValue of addressValues) {
// const address: IAddress = {}; const address: IAddress = {};
// address.Type = addressValue.type as string; address.AddressType = addressValue.type as string;
// address.AddressLine1 = addressValue.line1 as string; address.AddressLine1 = addressValue.line1 as string;
// address.AddressLine2 = addressValue.line2 as string; address.AddressLine2 = addressValue.line2 as string;
// address.City = addressValue.city as string; address.City = addressValue.city as string;
// address.Region = addressValue.region as string; address.Region = addressValue.region as string;
// address.PostalCode = addressValue.postalCode as string; address.PostalCode = addressValue.postalCode as string;
// address.Country = addressValue.country as string; address.Country = addressValue.country as string;
// address.AttentionTo = addressValue.attentionTo as string; address.AttentionTo = addressValue.attentionTo as string;
// addresses.push(address); addresses.push(address);
// } }
// body.Addresses = addresses; body.Addresses = addresses;
// } }
// } }
responseData = await xeroApiRequest.call(this, 'POST', '/Contacts', { organizationId, Contacts: [body] }); responseData = await xeroApiRequest.call(this, 'POST', '/Contacts', { organizationId, Contacts: [body] });
responseData = responseData.Contacts; responseData = responseData.Contacts;
@ -569,8 +570,8 @@ export class Xero implements INodeType {
const organizationId = this.getNodeParameter('organizationId', i) as string; const organizationId = this.getNodeParameter('organizationId', i) as string;
const contactId = this.getNodeParameter('contactId', i) as string; const contactId = this.getNodeParameter('contactId', i) as string;
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
// const addressesUi = updateFields.addressesUi as IDataObject; const addressesUi = updateFields.addressesUi as IDataObject;
// const phonesUi = updateFields.phonesUi as IDataObject; const phonesUi = updateFields.phonesUi as IDataObject;
const body: IContact = {}; const body: IContact = {};
@ -630,41 +631,41 @@ export class Xero implements INodeType {
body.xeroNetworkKey = updateFields.xeroNetworkKey as string; body.xeroNetworkKey = updateFields.xeroNetworkKey as string;
} }
// if (phonesUi) { if (phonesUi) {
// const phoneValues = phonesUi?.phonesValues as IDataObject[]; const phoneValues = phonesUi?.phonesValues as IDataObject[];
// if (phoneValues) { if (phoneValues) {
// const phones: IPhone[] = []; const phones: IPhone[] = [];
// for (const phoneValue of phoneValues) { for (const phoneValue of phoneValues) {
// const phone: IPhone = {}; const phone: IPhone = {};
// phone.Type = phoneValue.type as string; phone.PhoneType = phoneValue.phoneType as string;
// phone.PhoneNumber = phoneValue.PhoneNumber as string; phone.PhoneNumber = phoneValue.phoneNumber as string;
// phone.PhoneAreaCode = phoneValue.phoneAreaCode as string; phone.PhoneAreaCode = phoneValue.phoneAreaCode as string;
// phone.PhoneCountryCode = phoneValue.phoneCountryCode as string; phone.PhoneCountryCode = phoneValue.phoneCountryCode as string;
// phones.push(phone); phones.push(phone);
// } }
// body.Phones = phones; body.Phones = phones;
// } }
// } }
// if (addressesUi) { if (addressesUi) {
// const addressValues = addressesUi?.addressesValues as IDataObject[]; const addressValues = addressesUi?.addressesValues as IDataObject[];
// if (addressValues) { if (addressValues) {
// const addresses: IAddress[] = []; const addresses: IAddress[] = [];
// for (const addressValue of addressValues) { for (const addressValue of addressValues) {
// const address: IAddress = {}; const address: IAddress = {};
// address.Type = addressValue.type as string; address.AddressType = addressValue.type as string;
// address.AddressLine1 = addressValue.line1 as string; address.AddressLine1 = addressValue.line1 as string;
// address.AddressLine2 = addressValue.line2 as string; address.AddressLine2 = addressValue.line2 as string;
// address.City = addressValue.city as string; address.City = addressValue.city as string;
// address.Region = addressValue.region as string; address.Region = addressValue.region as string;
// address.PostalCode = addressValue.postalCode as string; address.PostalCode = addressValue.postalCode as string;
// address.Country = addressValue.country as string; address.Country = addressValue.country as string;
// address.AttentionTo = addressValue.attentionTo as string; address.AttentionTo = addressValue.attentionTo as string;
// addresses.push(address); addresses.push(address);
// } }
// body.Addresses = addresses; body.Addresses = addresses;
// } }
// } }
responseData = await xeroApiRequest.call(this, 'POST', `/Contacts/${contactId}`, { organizationId, Contacts: [body] }); responseData = await xeroApiRequest.call(this, 'POST', `/Contacts/${contactId}`, { organizationId, Contacts: [body] });
responseData = responseData.Contacts; responseData = responseData.Contacts;
@ -677,7 +678,7 @@ export class Xero implements INodeType {
} }
} catch (error) { } catch (error) {
if (this.continueOnFail()) { if (this.continueOnFail()) {
returnData.push({ error: error.message }); returnData.push({ error: (error as JsonObject).message });
continue; continue;
} }
throw error; throw error;

View file

@ -179,13 +179,21 @@ export const ticketFields: INodeProperties[] = [
type: 'options', type: 'options',
options: [ options: [
{ {
name: 'Open', name: 'Closed',
value: 'open', value: 'closed',
}, },
{ {
name: 'New', name: 'New',
value: 'new', value: 'new',
}, },
{
name: 'On-hold',
value: 'hold',
},
{
name: 'Open',
value: 'open',
},
{ {
name: 'Pending', name: 'Pending',
value: 'pending', value: 'pending',
@ -194,10 +202,6 @@ export const ticketFields: INodeProperties[] = [
name: 'Solved', name: 'Solved',
value: 'solved', value: 'solved',
}, },
{
name: 'Closed',
value: 'closed',
},
], ],
default: '', default: '',
description: 'The state of the ticket', description: 'The state of the ticket',
@ -414,13 +418,21 @@ export const ticketFields: INodeProperties[] = [
type: 'options', type: 'options',
options: [ options: [
{ {
name: 'Open', name: 'Closed',
value: 'open', value: 'closed',
}, },
{ {
name: 'New', name: 'New',
value: 'new', value: 'new',
}, },
{
name: 'On-hold',
value: 'hold',
},
{
name: 'Open',
value: 'open',
},
{ {
name: 'Pending', name: 'Pending',
value: 'pending', value: 'pending',
@ -429,10 +441,6 @@ export const ticketFields: INodeProperties[] = [
name: 'Solved', name: 'Solved',
value: 'solved', value: 'solved',
}, },
{
name: 'Closed',
value: 'closed',
},
], ],
default: '', default: '',
description: 'The state of the ticket', description: 'The state of the ticket',
@ -729,13 +737,21 @@ export const ticketFields: INodeProperties[] = [
}, },
options: [ options: [
{ {
name: 'Open', name: 'Closed',
value: 'open', value: 'closed',
}, },
{ {
name: 'New', name: 'New',
value: 'new', value: 'new',
}, },
{
name: 'On-hold',
value: 'hold',
},
{
name: 'Open',
value: 'open',
},
{ {
name: 'Pending', name: 'Pending',
value: 'pending', value: 'pending',
@ -744,10 +760,6 @@ export const ticketFields: INodeProperties[] = [
name: 'Solved', name: 'Solved',
value: 'solved', value: 'solved',
}, },
{
name: 'Closed',
value: 'closed',
},
], ],
default: '', default: '',
description: 'The state of the ticket', description: 'The state of the ticket',

View file

@ -1,6 +1,6 @@
{ {
"name": "n8n-nodes-base", "name": "n8n-nodes-base",
"version": "0.167.0", "version": "0.169.0",
"description": "Base nodes of n8n", "description": "Base nodes of n8n",
"license": "SEE LICENSE IN LICENSE.md", "license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io", "homepage": "https://n8n.io",
@ -258,6 +258,7 @@
"dist/credentials/SentryIoOAuth2Api.credentials.js", "dist/credentials/SentryIoOAuth2Api.credentials.js",
"dist/credentials/SentryIoServerApi.credentials.js", "dist/credentials/SentryIoServerApi.credentials.js",
"dist/credentials/ServiceNowOAuth2Api.credentials.js", "dist/credentials/ServiceNowOAuth2Api.credentials.js",
"dist/credentials/ServiceNowBasicApi.credentials.js",
"dist/credentials/Sftp.credentials.js", "dist/credentials/Sftp.credentials.js",
"dist/credentials/ShopifyApi.credentials.js", "dist/credentials/ShopifyApi.credentials.js",
"dist/credentials/Signl4Api.credentials.js", "dist/credentials/Signl4Api.credentials.js",
@ -712,7 +713,7 @@
"@types/xml2js": "^0.4.3", "@types/xml2js": "^0.4.3",
"gulp": "^4.0.0", "gulp": "^4.0.0",
"jest": "^27.4.7", "jest": "^27.4.7",
"n8n-workflow": "~0.92.0", "n8n-workflow": "~0.94.0",
"nodelinter": "^0.1.9", "nodelinter": "^0.1.9",
"ts-jest": "^27.1.3", "ts-jest": "^27.1.3",
"tslint": "^6.1.2", "tslint": "^6.1.2",
@ -754,7 +755,7 @@
"mqtt": "4.2.6", "mqtt": "4.2.6",
"mssql": "^6.2.0", "mssql": "^6.2.0",
"mysql2": "~2.3.0", "mysql2": "~2.3.0",
"n8n-core": "~0.110.0", "n8n-core": "~0.112.0",
"node-ssh": "^12.0.0", "node-ssh": "^12.0.0",
"nodemailer": "^6.5.0", "nodemailer": "^6.5.0",
"pdf-parse": "^1.1.1", "pdf-parse": "^1.1.1",
@ -765,7 +766,7 @@
"request": "^2.88.2", "request": "^2.88.2",
"rhea": "^1.0.11", "rhea": "^1.0.11",
"rss-parser": "^3.7.0", "rss-parser": "^3.7.0",
"simple-git": "^2.36.2", "simple-git": "^3.5.0",
"snowflake-sdk": "^1.5.3", "snowflake-sdk": "^1.5.3",
"ssh2-sftp-client": "^7.0.0", "ssh2-sftp-client": "^7.0.0",
"tmp-promise": "^3.0.2", "tmp-promise": "^3.0.2",

View file

@ -12,7 +12,7 @@ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensa
## Limitations ## Limitations
You may use or modify the software only for your own internal business purposes or for non-commercial or personal use You may use or modify the software only for your own internal business purposes or for non-commercial or personal use.
You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes. You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes.
You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law. You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law.

Some files were not shown because too many files have changed in this diff Show more