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)
### 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))
- **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))
> **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
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 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.
## 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
### What changed?
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.
@ -28,7 +42,7 @@ If you are using the Hubspot Trigger.
### 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
@ -59,15 +73,14 @@ const credentials = await this.getCredentials(myNodeCredentials);
Example:
```typescript
const items = this.getInputData();
for (const i = 0; i < items.length; i++) {
const item = items[i].binary as IBinaryKeyData;
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string;
const binaryData = item[binaryPropertyName] as IBinaryData;
const item = items[i].binary as IBinaryKeyData;
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string;
const binaryData = item[binaryPropertyName] as IBinaryData;
// Before 0.135.0:
const binaryDataBuffer = Buffer.from(binaryData.data, BINARY_ENCODING);
const binaryDataBuffer = Buffer.from(binaryData.data, BINARY_ENCODING);
// From 0.135.0:
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
### What changed?
The minimum Node.js version required for n8n is now v14.
### When is action necessary?
If you're using n8n via npm or PM2 or if you're contributing to n8n.
### How to upgrade:
Update the Node.js version to v14 or above.
----------------------------
---
### 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.
### 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
### What changed?
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.
@ -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.
----------------------------
---
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.
## 0.113.0
### What changed?
In the Dropbox node, both credential types (Access Token & OAuth2) have a new parameter called "APP Access Type".
### 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
### What changed?
In the Dropbox node, now all operations are performed relative to the user's root directory.
### 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
### What changed?
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?
If you are using the Hubspot Trigger node.
### 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
### 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.
### 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.
### 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.
```
@ -227,21 +251,26 @@ n8n import:credentials --separate --input=backups/latest/
## 0.102.0
### 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.
### When is action necessary?
If you are using the following fields or properties in the Slack node:
- As User
- Ephemeral
- User Name
### How to upgrade:
Open the Slack node and set them again to the appropriate values.
----------------------------
---
### 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:
`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?`
### 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.
### 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.
## 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:
| Resource | Operation |
|--|--|
| Identify | Create |
| Track | Event |
| Track | Page |
| Group | Add |
| -------- | --------- |
| Identify | Create |
| Track | Event |
| Track | Page |
| Group | Add |
### 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.
## 0.90.0
### 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/).
## 0.87.0
### 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.
## 0.83.0
### 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:
| Resource | Operation |
|--|--|
| Deal | Get All |
| Connector | Get All |
| E-commerce Order | Get All |
| E-commerce Customer | Get All |
| E-commerce Order Products | Get All |
| Resource | Operation |
| ------------------------- | --------- |
| Deal | Get All |
| Connector | Get All |
| E-commerce Order | Get All |
| E-commerce Customer | Get All |
| E-commerce Order Products | Get All |
### 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.
## 0.68.0
### 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.
Meaning if the expression used before was:
```
{{ $node["Slack"].data["channel"]["id"] }}
```
it has to get changed to:
```
{{ $node["Slack"].data["id"] }}
```
## 0.67.0
### What changed?
The names of the following nodes were not set correctly and got fixed:
- AMQP Sender
- Bitbucket-Trigger
- Coda
- Eventbrite-Trigger
- Flow
- Flow-Trigger
- Gumroad-Trigger
- Jira
- Mailchimp-Trigger
- PayPal Trigger
- Read PDF
- Rocketchat
- Shopify
- Shopify-Trigger
- Stripe-Trigger
- Toggl-Trigger
- AMQP Sender
- Bitbucket-Trigger
- Coda
- Eventbrite-Trigger
- Flow
- Flow-Trigger
- Gumroad-Trigger
- Jira
- Mailchimp-Trigger
- PayPal Trigger
- Read PDF
- Rocketchat
- Shopify
- Shopify-Trigger
- Stripe-Trigger
- Toggl-Trigger
### When is action necessary?
@ -454,32 +484,32 @@ For the nodes mentioned above, you'll need to give them access to the credential
**Simple**
- Note down the settings of the nodes before upgrading
- After upgrading, delete the nodes mentioned above from your workflow, and recreate them
- Note down the settings of the nodes before upgrading
- After upgrading, delete the nodes mentioned above from your workflow, and recreate them
**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:
- "n8n-nodes-base.amqpSender" -> "n8n-nodes-base.amqp"
- "n8n-nodes-base.bitbucket" -> "n8n-nodes-base.bitbucketTrigger"
- "n8n-nodes-base.Coda" -> "n8n-nodes-base.coda"
- "n8n-nodes-base.eventbrite" -> "n8n-nodes-base.eventbriteTrigger"
- "n8n-nodes-base.Flow" -> "n8n-nodes-base.flow"
- "n8n-nodes-base.flow" -> "n8n-nodes-base.flowTrigger"
- "n8n-nodes-base.gumroad" -> "n8n-nodes-base.gumroadTrigger"
- "n8n-nodes-base.Jira Software Cloud" -> "n8n-nodes-base.jira"
- "n8n-nodes-base.Mailchimp" -> "n8n-nodes-base.mailchimpTrigger"
- "n8n-nodes-base.PayPal" -> "n8n-nodes-base.payPalTrigger"
- "n8n-nodes-base.Read PDF" -> "n8n-nodes-base.readPDF"
- "n8n-nodes-base.Rocketchat" -> "n8n-nodes-base.rocketchat"
- "n8n-nodes-base.shopify" -> "n8n-nodes-base.shopifyTrigger"
- "n8n-nodes-base.shopifyNode" -> "n8n-nodes-base.shopify"
- "n8n-nodes-base.stripe" -> "n8n-nodes-base.stripeTrigger"
- "n8n-nodes-base.toggl" -> "n8n-nodes-base.togglTrigger"
- "n8n-nodes-base.amqpSender" -> "n8n-nodes-base.amqp"
- "n8n-nodes-base.bitbucket" -> "n8n-nodes-base.bitbucketTrigger"
- "n8n-nodes-base.Coda" -> "n8n-nodes-base.coda"
- "n8n-nodes-base.eventbrite" -> "n8n-nodes-base.eventbriteTrigger"
- "n8n-nodes-base.Flow" -> "n8n-nodes-base.flow"
- "n8n-nodes-base.flow" -> "n8n-nodes-base.flowTrigger"
- "n8n-nodes-base.gumroad" -> "n8n-nodes-base.gumroadTrigger"
- "n8n-nodes-base.Jira Software Cloud" -> "n8n-nodes-base.jira"
- "n8n-nodes-base.Mailchimp" -> "n8n-nodes-base.mailchimpTrigger"
- "n8n-nodes-base.PayPal" -> "n8n-nodes-base.payPalTrigger"
- "n8n-nodes-base.Read PDF" -> "n8n-nodes-base.readPDF"
- "n8n-nodes-base.Rocketchat" -> "n8n-nodes-base.rocketchat"
- "n8n-nodes-base.shopify" -> "n8n-nodes-base.shopifyTrigger"
- "n8n-nodes-base.shopifyNode" -> "n8n-nodes-base.shopify"
- "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.
## 0.62.0
### What changed?
@ -496,7 +526,6 @@ If "evaluateExpression(...)" gets used in any Function or FunctionItem Node.
Simply replace the "evaluateExpression(...)" with "$evaluateExpression(...)".
## 0.52.0
### 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
converted date should be set on.
## 0.37.0
### 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
subdomain "example" set you have to set now "https://example.rocket.chat".
## 0.19.0
### 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
function again like before.
----------------------------
---
### 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
"Binary Property" to "response".
## 0.18.0
### 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".
----------------------------
---
### 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.
For example like this:
```
/usr/local/bin/node ./dist/index.js start
```
@ -610,6 +634,7 @@ For example like this:
### How to upgrade:
Change the path to its new location:
```
/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
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 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) {
console.info(`Successfully imported ${total} ${total === 1 ? 'workflow.' : 'workflows.'}`);
console.info(`Successfully imported ${total} ${total === 1 ? 'credential.' : 'credentials.'}`);
}
private async initOwnerCredentialRole() {

View file

@ -119,9 +119,14 @@ export class Worker extends Command {
async runJob(job: Bull.Job, nodeTypes: INodeTypes): Promise<IBullJobResponse> {
const jobData = job.data as IBullJobData;
const executionDb = (await Db.collections.Execution!.findOne(
jobData.executionId,
)) as IExecutionFlattedDb;
const executionDb = await Db.collections.Execution!.findOne(jobData.executionId);
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);
LoggerProxy.info(
`Start job: ${job.id} (Workflow ID: ${currentExecutionDb.workflowData.id} | Execution: ${jobData.executionId})`,
@ -139,6 +144,13 @@ export class Worker extends Command {
findOptions,
);
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(
`The workflow with the ID "${currentExecutionDb.workflowData.id}" could not be found`,
);

View file

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

View file

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

View file

@ -682,6 +682,15 @@ export class ActiveWorkflowRunner {
(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;
};
}

View file

@ -11,14 +11,15 @@ class Logger implements ILogger {
private logger: winston.Logger;
constructor() {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const level = config.get('logs.level');
const level = config.get('logs.level') as string;
// eslint-disable-next-line @typescript-eslint/no-shadow
const output = (config.get('logs.output') as string).split(',').map((output) => output.trim());
this.logger = winston.createLogger({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
level,
silent: level === 'silent',
});
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;
if (error.httpStatusCode) {
httpStatusCode = error.httpStatusCode;
}
shouldLog = !process.argv[1].split('/').includes('jest');
if (process.env.NODE_ENV !== 'production' && shouldLog) {
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
console.error('ERROR RESPONSE');
console.error(error);
}

View file

@ -4,8 +4,10 @@
import { Workflow } from 'n8n-workflow';
import { In, IsNull, Not } from 'typeorm';
import express = require('express');
import { compare } from 'bcryptjs';
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 { Role } from '../databases/entities/Role';
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 {
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-unsafe-member-access */
import { Request, Response } from 'express';
import { compare } from 'bcryptjs';
import { IDataObject } from 'n8n-workflow';
import { Db, ResponseHelper } from '../..';
import { AUTH_COOKIE_NAME } from '../../constants';
import { issueCookie, resolveJwt } from '../auth/jwt';
import { N8nApp, PublicUser } from '../Interfaces';
import { isInstanceOwnerSetup, sanitizeUser } from '../UserManagementHelper';
import { compareHash, isInstanceOwnerSetup, sanitizeUser } from '../UserManagementHelper';
import { User } from '../../databases/entities/User';
import type { LoginRequest } from '../../requests';
@ -43,7 +42,8 @@ export function authenticationMethods(this: N8nApp): void {
} catch (error) {
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
const error = new Error('Wrong username or password. Do you have caps lock on?');
// @ts-ignore

View file

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

View file

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

View file

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

View file

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

View file

@ -12,6 +12,8 @@ import {
randomInvalidPassword,
} from './shared/random';
jest.mock('../../src/telemetry');
let app: express.Application;
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 () => {
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) {
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 () => {
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();

View file

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

View file

@ -17,7 +17,7 @@ const randomDigit = () => Math.floor(Math.random() * 10);
const randomUppercaseLetter = () => chooseRandomly('ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''));
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 = () =>
chooseRandomly([

View file

@ -16,6 +16,7 @@ import { sqliteMigrations } from '../../../src/databases/sqlite/migrations';
import type { Role } from '../../../src/databases/entities/Role';
import type { User } from '../../../src/databases/entities/User';
import type { CredentialPayload } from './types';
import { genSaltSync, hashSync } from 'bcryptjs';
/**
* 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 user = {
email: email ?? randomEmail(),
password: password ?? randomValidPassword(),
password: hashSync(password ?? randomValidPassword(), genSaltSync(10)),
firstName: firstName ?? randomName(),
lastName: lastName ?? randomName(),
globalRole: globalRole ?? (await getGlobalMemberRole()),

View file

@ -89,12 +89,13 @@ export function initTestServer({
return testServer.app;
}
/**
* Pre-requisite: Mock the telemetry module before calling.
*/
export function initTestTelemetry() {
const mockNodeTypes = { nodeTypes: {} } as INodeTypes;
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() {
config.set('logs.output', 'file'); // declutter console output
LoggerProxy.init(getLogger());
}

View file

@ -1,7 +1,7 @@
import express = require('express');
import validator from 'validator';
import { v4 as uuid } from 'uuid';
import { compare } from 'bcryptjs';
import { compare, genSaltSync, hashSync } from 'bcryptjs';
import { Db } from '../../src';
import config = require('../../config');
@ -18,6 +18,8 @@ import { WorkflowEntity } from '../../src/databases/entities/WorkflowEntity';
import * as utils from './shared/utils';
import * as testDb from './shared/testDb';
jest.mock('../../src/telemetry');
let app: express.Application;
let testDbName = '';
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 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({
email: randomEmail(),
password: randomValidPassword(), // simulate accepted invite
password: hashSync(randomValidPassword(), genSaltSync(10)), // simulate accepted invite
globalRole: globalMemberRole,
});
@ -424,7 +426,7 @@ test.skip('POST /users/:id should fail with already accepted invite', async () =
inviterId: INITIAL_TEST_USER.id,
firstName: randomName(),
lastName: randomName(),
password: newPassword,
password: randomValidPassword(),
});
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);
});
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 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 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 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
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 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",
"version": "0.110.0",
"version": "0.112.0",
"description": "Core functionality of n8n",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
@ -52,7 +52,7 @@
"form-data": "^4.0.0",
"lodash.get": "^4.4.2",
"mime-types": "^2.1.27",
"n8n-workflow": "~0.92.0",
"n8n-workflow": "~0.94.0",
"oauth-1.0a": "^2.2.6",
"p-cancelable": "^2.0.0",
"qs": "^6.10.1",

View file

@ -1750,6 +1750,9 @@ export function getExecuteTriggerFunctions(
emit: (data: INodeExecutionData[][]): void => {
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> {
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
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 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",
"version": "0.15.0",
"version": "0.16.0",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
"author": {

View file

@ -12,7 +12,7 @@ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensa
## 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 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",
"version": "0.136.0",
"version": "0.138.0",
"description": "Workflow Editor UI for n8n",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
@ -26,7 +26,7 @@
"dependencies": {
"@fontsource/open-sans": "^4.5.0",
"luxon": "^2.3.0",
"n8n-design-system": "~0.15.0",
"n8n-design-system": "~0.16.0",
"monaco-editor": "^0.29.1",
"timeago.js": "^4.0.2",
"v-click-outside": "^3.1.2",
@ -77,7 +77,7 @@
"lodash.debounce": "^4.0.8",
"lodash.get": "^4.4.2",
"lodash.set": "^4.3.2",
"n8n-workflow": "~0.92.0",
"n8n-workflow": "~0.94.0",
"monaco-editor-webpack-plugin": "^5.0.0",
"normalize-wheel": "^1.0.1",
"prismjs": "^1.17.1",

View file

@ -170,6 +170,7 @@
<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 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>
@ -197,6 +198,7 @@
import VueJsonPretty from 'vue-json-pretty';
import {
GenericValue,
IBinaryData,
IBinaryKeyData,
IDataObject,
INodeExecutionData,
@ -231,6 +233,8 @@ import { nodeHelpers } from '@/components/mixins/nodeHelpers';
import mixins from 'vue-typed-mixins';
import Vue from 'vue/types/umd';
import { saveAs } from 'file-saver';
// A path that does not exist so that nothing is selected by default
const deselectedPlaceholder = '_!^&*';
@ -568,6 +572,24 @@ export default mixins(
dataItemClicked (path: string, data: object | number | string) {
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) {
this.binaryDataDisplayVisible = true;

View file

@ -742,7 +742,12 @@
"mimeType": "Mime Type",
"ms": "ms",
"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",
"table": "Table"
},

View file

@ -12,7 +12,7 @@ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensa
## 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 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",
"version": "0.49.0",
"version": "0.51.0",
"description": "CLI to simplify n8n credentials/node development",
"license": "SEE LICENSE IN LICENSE.md",
"homepage": "https://n8n.io",
@ -61,8 +61,8 @@
"change-case": "^4.1.1",
"copyfiles": "^2.1.1",
"inquirer": "^7.0.1",
"n8n-core": "~0.110.0",
"n8n-workflow": "~0.92.0",
"n8n-core": "~0.112.0",
"n8n-workflow": "~0.94.0",
"oauth-1.0a": "^2.2.6",
"replace-in-file": "^6.0.0",
"request": "^2.88.2",

View file

@ -12,7 +12,7 @@ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensa
## 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 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',
name: 'authUrl',
type: 'hidden',
default: '={{$self["url"]}}/oauth/v2/authorize',
default: '={{$self["url"].endsWith("/") ? $self["url"].slice(0, -1) : $self["url"]}}/oauth/v2/authorize',
required: true,
},
{
displayName: 'Access Token URL',
name: 'accessTokenUrl',
type: 'hidden',
default: '={{$self["url"]}}/oauth/v2/token',
default: '={{$self["url"].endsWith("/") ? $self["url"].slice(0, -1) : $self["url"]}}/oauth/v2/token',
required: true,
},
{

View file

@ -16,7 +16,7 @@ export class MicrosoftTeamsOAuth2Api implements ICredentialType {
displayName: 'Scope',
name: 'scope',
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',
type: 'string',
default: '',
placeholder: 'n8n',
description: 'The subdomain of your ServiceNow environment',
hint: 'The subdomain can be extracted from the URL. If the URL is: https://dev99890.service-now.com the subdomain is dev99890',
required: true,
},
{

View file

@ -6,6 +6,7 @@ import {
INodePropertyOptions,
INodeType,
INodeTypeDescription,
JsonObject,
NodeApiError,
NodeOperationError,
} 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;
if (responseData.stackTrace) {
@ -206,7 +207,7 @@ export class AwsLambda implements INodeType {
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ error: error.message });
returnData.push({ error: (error as JsonObject).message });
continue;
}
throw error;

View file

@ -1,11 +1,18 @@
import { set } from 'lodash';
import { IExecuteFunctions } from 'n8n-core';
import {
set,
} from 'lodash';
import {
IExecuteFunctions,
} from 'n8n-core';
import {
ILoadOptionsFunctions,
INodeExecutionData,
INodePropertyOptions,
INodeType,
INodeTypeDescription,
JsonObject,
} from 'n8n-workflow';
import {
@ -14,8 +21,11 @@ import {
createHmac,
createSign,
getHashes,
randomBytes,
} from 'crypto';
import { v4 as uuid } from 'uuid';
export class Crypto implements INodeType {
description: INodeTypeDescription = {
displayName: 'Crypto',
@ -37,19 +47,24 @@ export class Crypto implements INodeType {
name: 'action',
type: 'options',
options: [
{
name: 'Generate',
description: 'Generate random string',
value: 'generate',
},
{
name: 'Hash',
description: 'Hash a text in a specified format.',
description: 'Hash a text in a specified format',
value: 'hash',
},
{
name: 'Hmac',
description: 'Hmac a text in a specified format.',
description: 'Hmac a text in a specified format',
value: 'hmac',
},
{
name: 'Sign',
description: 'Sign a string using a private key.',
description: 'Sign a string using a private key',
value: 'sign',
},
],
@ -100,7 +115,7 @@ export class Crypto implements INodeType {
},
type: 'string',
default: '',
description: 'The value that should be hashed.',
description: 'The value that should be hashed',
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',
@ -187,7 +202,7 @@ export class Crypto implements INodeType {
},
type: 'string',
default: '',
description: 'The value of which the hmac should be created.',
description: 'The value of which the hmac should be created',
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',
@ -255,7 +270,7 @@ export class Crypto implements INodeType {
},
type: 'string',
default: '',
description: 'The value that should be signed.',
description: 'The value that should be signed',
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',
@ -328,10 +343,77 @@ export class Crypto implements INodeType {
typeOptions: {
alwaysOpenEditWindow: true,
},
description: 'Private key to use when signing the string.',
description: 'Private key to use when signing the string',
default: '',
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];
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;
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') {
const type = this.getNodeParameter('type', i) as string;
const encoding = this.getNodeParameter('encoding', i) as BinaryToTextEncoding;
@ -413,10 +508,10 @@ export class Crypto implements INodeType {
set(newItem, `json.${dataPropertyName}`, newValue);
returnData.push(newItem);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({json:{ error: error.message }});
returnData.push({ json: { error: (error as JsonObject).message } });
continue;
}
throw error;

View file

@ -1,13 +1,15 @@
import { ITriggerFunctions } from 'n8n-core';
import {
createDeferredPromise,
IBinaryData,
IBinaryKeyData,
IDataObject,
IDeferredPromise,
INodeExecutionData,
INodeType,
INodeTypeDescription,
ITriggerResponse,
LoggerProxy,
LoggerProxy as Logger,
NodeOperationError,
} from 'n8n-workflow';
@ -25,10 +27,6 @@ import {
import * as lodash from 'lodash';
import {
LoggerProxy as Logger
} from 'n8n-workflow';
export class EmailReadImap implements INodeType {
description: INodeTypeDescription = {
displayName: 'EmailReadImap',
@ -377,6 +375,8 @@ export class EmailReadImap implements INodeType {
return newEmails;
};
const returnedPromise: IDeferredPromise<void> | undefined = await createDeferredPromise<void>();
const establishConnection = (): Promise<ImapSimple> => {
let searchCriteria = [
@ -425,7 +425,11 @@ export class EmailReadImap implements INodeType {
}
} catch (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();
}
// Resolve returned-promise so that waiting errors can be emitted
returnedPromise.resolve();
return {
closeFunction,
};
}
}

View file

@ -9,6 +9,7 @@ export const campaignOperations: INodeProperties[] = [
type: 'options',
default: 'get',
description: 'Operation to perform',
noDataExpression: true,
options: [
{
name: 'Add Contact',
@ -18,6 +19,10 @@ export const campaignOperations: INodeProperties[] = [
name: 'Create',
value: 'create',
},
{
name: 'Duplicate',
value: 'duplicate',
},
{
name: 'Get',
value: 'get',
@ -58,7 +63,7 @@ export const campaignFields: INodeProperties[] = [
},
default: [],
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: {
show: {
resource: [
@ -76,7 +81,7 @@ export const campaignFields: INodeProperties[] = [
type: 'string',
required: true,
default: '',
description: 'The email of the contact to add to the campaign.',
description: 'The email of the contact to add to the campaign',
displayOptions: {
show: {
resource: [
@ -113,7 +118,7 @@ export const campaignFields: INodeProperties[] = [
typeOptions: {
multipleValues: true,
},
description: 'Filter by custom fields ',
description: 'Filter by custom fields',
default: {},
options: [
{
@ -125,14 +130,14 @@ export const campaignFields: INodeProperties[] = [
name: 'fieldName',
type: 'string',
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',
name: 'value',
type: 'string',
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',
type: 'string',
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',
name: 'lastName',
type: 'string',
default: '',
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.',
description: 'Last name of the contact to add',
},
{
displayName: 'Last Open',
name: 'lastOpen',
type: 'string',
type: 'dateTime',
default: '',
description: 'Last opened date of the contact to add.',
description: 'Last opened date of the contact to add',
},
{
displayName: 'Last Replied',
name: 'lastReplied',
type: 'string',
type: 'dateTime',
default: '',
description: 'Last replied date of the contact to add.',
description: 'Last replied date of the contact to add',
},
{
displayName: 'Mails Sent',
name: 'mailsSent',
type: 'number',
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',
name: 'phoneNumber',
type: 'string',
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',
required: true,
default: '',
description: 'The name of the campaign to create.',
description: 'The name of the campaign to create',
displayOptions: {
show: {
resource: [
@ -221,7 +226,7 @@ export const campaignFields: INodeProperties[] = [
type: 'string',
default: '',
required: true,
description: 'The ID of the campaign to retrieve.',
description: 'The ID of the campaign to retrieve',
displayOptions: {
show: {
resource: [
@ -242,7 +247,7 @@ export const campaignFields: INodeProperties[] = [
name: 'returnAll',
type: 'boolean',
default: false,
description: 'Return all results.',
description: 'Whether to return all results or only up to a given limit',
displayOptions: {
show: {
resource: [
@ -259,7 +264,7 @@ export const campaignFields: INodeProperties[] = [
name: 'limit',
type: 'number',
default: 100,
description: 'The number of results to return.',
description: 'Max number of results to return',
typeOptions: {
minValue: 1,
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',
name: 'operation',
type: 'options',
default: 'get',
default: 'getAll',
description: 'Operation to perform',
noDataExpression: true,
options: [
{
name: 'Add',
@ -42,7 +43,7 @@ export const contactListFields: INodeProperties[] = [
},
default: [],
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: {
show: {
resource: [
@ -60,7 +61,7 @@ export const contactListFields: INodeProperties[] = [
type: 'string',
required: true,
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: {
show: {
resource: [
@ -97,7 +98,7 @@ export const contactListFields: INodeProperties[] = [
typeOptions: {
multipleValues: true,
},
description: 'Filter by custom fields ',
description: 'Filter by custom fields',
default: {},
options: [
{
@ -109,14 +110,14 @@ export const contactListFields: INodeProperties[] = [
name: 'fieldName',
type: 'string',
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',
name: 'value',
type: 'string',
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',
type: 'string',
default: '',
description: 'First name of the contact to add.',
},
{
displayName: 'Last Name',
name: 'lastName',
type: 'string',
default: '',
description: 'Last 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.',
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',
name: 'lastOpen',
type: 'dateTime',
default: '',
description: 'Last opened date of the contact to add.',
description: 'Last opened date of the contact to add',
},
{
displayName: 'Last Replied',
name: 'lastReplied',
type: 'dateTime',
default: '',
description: 'Last replied date of the contact to add.',
description: 'Last replied date of the contact to add',
},
{
displayName: 'Mails Sent',
name: 'mailsSent',
type: 'number',
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',
name: 'phoneNumber',
type: 'string',
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',
type: 'boolean',
default: false,
description: 'Return all results.',
description: 'Whether to return all results or only up to a given limit',
displayOptions: {
show: {
resource: [
@ -199,7 +200,7 @@ export const contactListFields: INodeProperties[] = [
name: 'limit',
type: 'number',
default: 100,
description: 'The number of results to return.',
description: 'Max number of results to return',
typeOptions: {
minValue: 1,
maxValue: 100,

View file

@ -7,10 +7,12 @@ import {
ILoadOptionsFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription
INodeTypeDescription,
JsonObject
} from 'n8n-workflow';
import {
emeliaApiTest,
emeliaGraphqlRequest,
loadResource,
} from './GenericFunctions';
@ -36,7 +38,7 @@ export class Emelia implements INodeType {
icon: 'file:emelia.svg',
group: ['input'],
version: 1,
subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}',
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume the Emelia API',
defaults: {
name: 'Emelia',
@ -47,6 +49,7 @@ export class Emelia implements INodeType {
{
name: 'emeliaApi',
required: true,
testedBy: 'emeliaApiTest',
},
],
properties: [
@ -54,6 +57,7 @@ export class Emelia implements INodeType {
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Campaign',
@ -66,7 +70,7 @@ export class Emelia implements INodeType {
],
default: 'campaign',
required: true,
description: 'The resource to operate on.',
description: 'The resource to operate on',
},
...campaignOperations,
...campaignFields,
@ -76,6 +80,10 @@ export class Emelia implements INodeType {
};
methods = {
credentialTest: {
emeliaApiTest,
},
loadOptions: {
async getCampaigns(this: ILoadOptionsFunctions) {
return loadResource.call(this, 'campaign');
@ -290,6 +298,46 @@ export class Emelia implements INodeType {
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') {
@ -373,7 +421,7 @@ export class Emelia implements INodeType {
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ error: error.message });
returnData.push({ error: (error as JsonObject).message });
continue;
}

View file

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

View file

@ -4,8 +4,12 @@ import {
} from 'n8n-core';
import {
ICredentialsDecrypted,
ICredentialTestFunctions,
IHookFunctions,
INodeCredentialTestResult,
INodePropertyOptions,
JsonObject,
NodeApiError,
} from 'n8n-workflow';
@ -51,7 +55,7 @@ export async function emeliaApiRequest(
try {
return await this.helpers.request!.call(this, options);
} 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,
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'],
credentials: [
{
// nodelinter-ignore-next-line
name: 'ftp',
required: true,
displayOptions: {
@ -62,6 +63,7 @@ export class Ftp implements INodeType {
},
},
{
// nodelinter-ignore-next-line
name: 'sftp',
required: true,
displayOptions: {
@ -124,6 +126,7 @@ export class Ftp implements INodeType {
],
default: 'download',
description: 'Operation to perform.',
noDataExpression: true,
},
// ----------------------------------
@ -253,6 +256,29 @@ export class Ftp implements INodeType {
description: 'The new path',
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
@ -381,8 +407,8 @@ export class Ftp implements INodeType {
throw new NodeOperationError(this.getNode(), 'Failed to get credentials!');
}
let ftp : ftpClient;
let sftp : sftpClient;
let ftp: ftpClient;
let sftp: sftpClient;
if (protocol === 'sftp') {
sftp = new sftpClient();
@ -452,9 +478,13 @@ export class Ftp implements INodeType {
if (operation === 'rename') {
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;
if (createDirectories) {
await recursivelyCreateSftpDirs(sftp!, newPath);
}
responseData = await sftp!.rename(oldPath, newPath);
returnItems.push({ json: { success: true } });
@ -475,16 +505,7 @@ export class Ftp implements INodeType {
if (operation === 'upload') {
const remotePath = this.getNodeParameter('path', i) as string;
// 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);
}
await recursivelyCreateSftpDirs(sftp!, remotePath);
if (this.getNodeParameter('binaryData', i) === true) {
// Is binary file to upload
@ -635,7 +656,7 @@ export class Ftp implements INodeType {
} catch (error) {
if (this.continueOnFail()) {
return this.prepareOutputData([{json:{ error: error.message }}]);
return this.prepareOutputData([{ json: { error: error.message } }]);
}
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) {
const pathArray : string[] = [path];
const pathArray: string[] = [path];
let currentPath = path;
const directoryItems : sftpClient.FileInfo[] = [];
const directoryItems: sftpClient.FileInfo[] = [];
let index = 0;
do {
// 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
returnData.map((item : sftpClient.FileInfo) => {
returnData.map((item: sftpClient.FileInfo) => {
if ((pathArray[index] as string).endsWith('/')) {
currentPath = `${pathArray[index]}${item.name}`;
} else {
@ -693,3 +714,12 @@ async function callRecursiveList(path: string, client: sftpClient | ftpClient, n
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
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
const taskListId = this.getNodeParameter('task', i) as string;
const options = this.getNodeParameter(
const { showCompleted = true, showDeleted = false, showHidden = false, ...options } = this.getNodeParameter(
'additionalFields',
i,
) as IDataObject;
@ -187,15 +187,11 @@ export class GoogleTasks implements INodeType {
if (options.dueMax) {
qs.dueMax = options.dueMax as string;
}
if (options.showCompleted) {
qs.showCompleted = options.showCompleted as boolean;
}
if (options.showDeleted) {
qs.showDeleted = options.showDeleted as boolean;
}
if (options.showHidden) {
qs.showHidden = options.showHidden as boolean;
}
qs.showCompleted = showCompleted;
qs.showDeleted = showDeleted;
qs.showHidden = showHidden;
if (options.updatedMin) {
qs.updatedMin = options.updatedMin as string;
}

View file

@ -76,6 +76,16 @@ export const taskFields: INodeProperties[] = [
type: 'string',
default: '',
description: 'Title of the task.',
displayOptions: {
show: {
operation: [
'create',
],
resource: [
'task',
],
},
},
},
{
displayName: 'Additional Fields',
@ -350,21 +360,21 @@ export const taskFields: INodeProperties[] = [
name: 'showCompleted',
type: 'boolean',
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',
name: 'showDeleted',
type: 'boolean',
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',
name: 'showHidden',
type: 'boolean',
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',

View file

@ -4,6 +4,7 @@ import {
INodeExecutionData,
INodeType,
INodeTypeDescription,
JsonObject,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow';
@ -412,17 +413,22 @@ export class GraphQL implements INodeType {
} else {
if (typeof response === 'string') {
try {
returnItems.push({ json: JSON.parse(response) });
response = JSON.parse(response);
} catch (error) {
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) {
if (this.continueOnFail()) {
returnItems.push({ json: { error: error.message } });
returnItems.push({ json: { error: (error as JsonObject).message } });
continue;
}
throw error;

View file

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

View file

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

View file

@ -12,6 +12,7 @@ import {
import {
ICredentialDataDecryptedObject,
IDataObject,
JsonObject,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow';
@ -69,7 +70,7 @@ export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecut
try {
return await this.helpers.request!(options);
} 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();
}
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 = [
'board_created',
'board_updated',

View file

@ -530,6 +530,24 @@ export const issueFields: INodeProperties[] = [
default: '',
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',
name: 'additionalFields',

View file

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

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';
import {
IDataObject, JsonObject, NodeApiError,
ICredentialDataDecryptedObject,
ICredentialTestFunctions,
IDataObject,
JsonObject,
NodeApiError,
} 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
@ -31,19 +35,21 @@ export async function mauticApiRequest(this: IHookFunctions | IExecuteFunctions
if (authenticationMethod === 'credentials') {
const credentials = await this.getCredentials('mauticApi') as IDataObject;
const baseUrl = credentials!.url as string;
const base64Key = Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64');
options.headers!.Authorization = `Basic ${base64Key}`;
options.uri = `${credentials.url}${options.uri}`;
options.uri = `${baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl}${options.uri}`;
//@ts-ignore
returnData = await this.helpers.request(options);
} else {
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
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;
}
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';
import {
ICredentialDataDecryptedObject,
ICredentialsDecrypted,
ICredentialTestFunctions,
IDataObject,
ILoadOptionsFunctions,
INodeCredentialTestResult,
INodeExecutionData,
INodePropertyOptions,
INodeType,
@ -17,6 +21,7 @@ import {
import {
mauticApiRequest,
mauticApiRequestAllItems,
validateCredentials,
validateJSON,
} from './GenericFunctions';
@ -80,6 +85,7 @@ export class Mautic implements INodeType {
],
},
},
testedBy: 'mauticCredentialTest',
},
{
name: 'mauticOAuth2Api',
@ -166,6 +172,29 @@ export class Mautic implements INodeType {
};
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: {
// Get all the available companies to display them to user so that he can
// 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,
} from './ChannelMessageDescription';
import {
chatMessageFields,
chatMessageOperations,
} from './ChatMessageDescription';
import {
taskFields,
taskOperations,
@ -65,6 +70,10 @@ export class MicrosoftTeams implements INodeType {
name: 'Channel Message (Beta)',
value: 'channelMessage',
},
{
name: 'Chat Message',
value: 'chatMessage',
},
{
name: 'Task',
value: 'task',
@ -79,6 +88,8 @@ export class MicrosoftTeams implements INodeType {
/// MESSAGE
...channelMessageOperations,
...channelMessageFields,
...chatMessageOperations,
...chatMessageFields,
///TASK
...taskOperations,
...taskFields,
@ -189,6 +200,29 @@ export class MicrosoftTeams implements INodeType {
}
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') {
//https://docs.microsoft.com/en-us/graph/api/planner-post-tasks?view=graph-rest-1.0&tabs=http
if (operation === 'create') {

View file

@ -4,7 +4,7 @@ import {
} from 'n8n-core';
import {
IDataObject, NodeApiError, NodeOperationError,
IDataObject, JsonObject, NodeApiError, NodeOperationError,
} from 'n8n-workflow';
/**
@ -47,6 +47,6 @@ export async function moceanApiRequest(this: IHookFunctions | IExecuteFunctions,
try {
return await this.helpers.request(options);
} 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 {
IExecuteFunctions,
} from 'n8n-core';
import {
ICredentialsDecrypted,
ICredentialTestFunctions,
IDataObject,
INodeCredentialTestResult,
INodeExecutionData,
INodeType,
INodeTypeDescription,
JsonObject,
NodeOperationError,
} from 'n8n-workflow';
import {moceanApiRequest} from './GenericFunctions';
import {
moceanApiRequest,
} from './GenericFunctions';
export class Mocean implements INodeType {
description: INodeTypeDescription = {
displayName: 'Mocean',
name: 'mocean',
icon: 'file:mocean.png',
subtitle: `={{$parameter["operation"] + ": " + $parameter["resource"]}}`,
icon: 'file:mocean.svg',
group: ['transform'],
version: 1,
description: 'Send SMS and voice messages via Mocean',
@ -27,6 +36,7 @@ export class Mocean implements INodeType {
{
name: 'moceanApi',
required: true,
testedBy: 'moceanApiTest',
},
],
properties: [
@ -36,7 +46,8 @@ export class Mocean implements INodeType {
displayName: 'Resource',
name: 'resource',
type: 'options',
options:[
noDataExpression: true,
options: [
{
name: 'SMS',
value: 'sms',
@ -52,6 +63,7 @@ export class Mocean implements INodeType {
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: [
@ -68,7 +80,7 @@ export class Mocean implements INodeType {
},
],
default: 'send',
description: 'The operation to perform.',
description: 'Operation to perform',
},
{
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',
name: 'language',
type: 'options',
options:[
options: [
{
name: 'Chinese Mandarin (China)',
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 resource: string;
let text: string;
let dlrUrl: string;
let dataKey: string;
// For Post
let body: IDataObject;
@ -196,7 +265,7 @@ export class Mocean implements INodeType {
qs = {};
try {
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;
requesetMethod = 'POST';
body['mocean-from'] = this.getNodeParameter('from', itemIndex, '') as string;
@ -215,16 +284,21 @@ export class Mocean implements INodeType {
dataKey = 'voice';
body['mocean-command'] = JSON.stringify(command);
endpoint = '/rest/2/voice/dial';
} else if(resource === 'sms') {
} else if (resource === 'sms') {
dlrUrl = this.getNodeParameter('options.dlrUrl', itemIndex, '') as string;
dataKey = 'messages';
body['mocean-text'] = text;
if (dlrUrl !== '') {
body['mocean-dlr-url'] = dlrUrl;
body['mocean-dlr-mask'] = '1';
}
endpoint = '/rest/2/sms';
} else {
throw new NodeOperationError(this.getNode(), `Unknown resource ${resource}`);
}
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[]) {
item.type = resource;
@ -236,7 +310,7 @@ export class Mocean implements INodeType {
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ error: error.message });
returnData.push({ error: (error as JsonObject).message });
continue;
}
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 {
responseData = await apiRequest.call(this, method, endpoint, body, query);
returnData.push(...responseData);
query.offset += query.limit;
} while (
responseData.length === 0
responseData.length !== 0
);
return returnData;

View file

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

View file

@ -4,7 +4,7 @@ import {
} from 'n8n-core';
import {
IDataObject, NodeApiError, NodeOperationError,
IDataObject, JsonObject, NodeApiError, NodeOperationError,
} from 'n8n-workflow';
import {
@ -45,7 +45,7 @@ export async function redditApiRequest(
try {
return await this.helpers.requestOAuth2.call(this, 'redditOAuth2Api', options);
} catch (error) {
throw new NodeApiError(this.getNode(), error);
throw new NodeApiError(this.getNode(), error as JsonObject);
}
} else {
@ -53,7 +53,7 @@ export async function redditApiRequest(
try {
return await this.helpers.request.call(this, options);
} 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',
description: 'Return the settings preferences of the logged-in user',
},
{
name: 'Saved',
value: 'saved',
description: 'Return the saved posts for the user',
},
{
name: '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 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') {
responseData = responseData.features;

View file

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

View file

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

View file

@ -7,6 +7,7 @@ export const businessServiceOperations: INodeProperties[] = [
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: [
@ -44,7 +45,7 @@ export const businessServiceFields: INodeProperties[] = [
},
},
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',
@ -99,17 +100,19 @@ export const businessServiceFields: INodeProperties[] = [
name: 'sysparm_fields',
type: 'multiOptions',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getColumns',
},
default: '',
default: [],
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',
name: 'sysparm_query',
type: 'string',
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',

View file

@ -7,6 +7,7 @@ export const configurationItemsOperations: INodeProperties[] = [
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: [
@ -44,7 +45,7 @@ export const configurationItemsFields: INodeProperties[] = [
},
},
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',
@ -68,7 +69,7 @@ export const configurationItemsFields: INodeProperties[] = [
maxValue: 500,
},
default: 50,
description: 'The max number of results to return',
description: 'Max number of results to return',
},
{
displayName: 'Options',
@ -99,17 +100,19 @@ export const configurationItemsFields: INodeProperties[] = [
name: 'sysparm_fields',
type: 'multiOptions',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getColumns',
},
default: '',
default: [],
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',
name: 'sysparm_query',
type: 'string',
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',

View file

@ -7,6 +7,7 @@ export const departmentOperations: INodeProperties[] = [
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: [
@ -44,7 +45,7 @@ export const departmentFields: INodeProperties[] = [
},
},
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',
@ -99,17 +100,19 @@ export const departmentFields: INodeProperties[] = [
name: 'sysparm_fields',
type: 'multiOptions',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getColumns',
},
default: '',
default: [],
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',
name: 'sysparm_query',
type: 'string',
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',

View file

@ -7,6 +7,7 @@ export const dictionaryOperations: INodeProperties[] = [
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: [
@ -44,7 +45,7 @@ export const dictionaryFields: INodeProperties[] = [
},
},
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',
@ -99,17 +100,19 @@ export const dictionaryFields: INodeProperties[] = [
name: 'sysparm_fields',
type: 'multiOptions',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getColumns',
},
default: '',
default: [],
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',
name: 'sysparm_query',
type: 'string',
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',

View file

@ -10,15 +10,25 @@ import {
import {
IDataObject,
INodePropertyOptions,
JsonObject,
NodeApiError,
} 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
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 = {
headers: {},
headers,
method,
qs,
body,
@ -38,11 +48,11 @@ export async function serviceNowApiRequest(this: IExecuteFunctions | ILoadOption
}
try {
return await this.helpers.requestOAuth2!.call(this, 'serviceNowOAuth2Api', options);
const credentialType = authenticationMethod === 'oAuth2' ? 'serviceNowOAuth2Api' : 'serviceNowBasicApi';
return await this.helpers.requestWithAuthentication.call(this, credentialType, options);
} 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',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: [
@ -85,19 +86,21 @@ export const incidentFields: INodeProperties[] = [
name: 'assigned_to',
type: 'options',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getUsers',
loadOptionsDependsOn: [
'additionalFields.assignment_group',
],
},
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',
name: 'assignment_group',
type: 'options',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getAssignmentGroups',
},
default: '',
@ -108,6 +111,7 @@ export const incidentFields: INodeProperties[] = [
name: 'business_service',
type: 'options',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getBusinessServices',
},
default: '',
@ -125,6 +129,7 @@ export const incidentFields: INodeProperties[] = [
name: 'category',
type: 'options',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentCategories',
},
default: '',
@ -142,9 +147,10 @@ export const incidentFields: INodeProperties[] = [
name: 'cmdb_ci',
type: 'multiOptions',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getConfigurationItems',
},
default: '',
default: [],
description: 'Configuration Items, \'cmdb_ci\' in metadata',
},
{
@ -197,7 +203,7 @@ export const incidentFields: INodeProperties[] = [
value: 1,
},
],
default: '',
default: 1,
description: 'The impact of the incident',
},
{
@ -205,16 +211,18 @@ export const incidentFields: INodeProperties[] = [
name: 'close_code',
type: 'options',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentResolutionCodes',
},
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',
name: 'state',
type: 'options',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentStates',
},
default: '',
@ -225,6 +233,7 @@ export const incidentFields: INodeProperties[] = [
name: 'subcategory',
type: 'options',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentSubcategories',
loadOptionsDependsOn: [
'additionalFields.category',
@ -251,7 +260,7 @@ export const incidentFields: INodeProperties[] = [
value: 1,
},
],
default: '',
default: 1,
description: 'The urgency of the incident',
},
],
@ -275,7 +284,7 @@ export const incidentFields: INodeProperties[] = [
},
},
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',
@ -330,17 +339,19 @@ export const incidentFields: INodeProperties[] = [
name: 'sysparm_fields',
type: 'multiOptions',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getColumns',
},
default: '',
default: [],
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',
name: 'sysparm_query',
type: 'string',
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',
@ -417,10 +428,12 @@ export const incidentFields: INodeProperties[] = [
name: 'sysparm_fields',
type: 'multiOptions',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getColumns',
},
default: '',
default: [],
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',
@ -489,19 +502,21 @@ export const incidentFields: INodeProperties[] = [
name: 'assigned_to',
type: 'options',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getUsers',
loadOptionsDependsOn: [
'additionalFields.assignment_group',
],
},
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',
name: 'assignment_group',
type: 'options',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getAssignmentGroups',
},
default: '',
@ -512,6 +527,7 @@ export const incidentFields: INodeProperties[] = [
name: 'business_service',
type: 'options',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getBusinessServices',
},
default: '',
@ -529,6 +545,7 @@ export const incidentFields: INodeProperties[] = [
name: 'category',
type: 'options',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentCategories',
},
default: '',
@ -546,9 +563,10 @@ export const incidentFields: INodeProperties[] = [
name: 'cmdb_ci',
type: 'multiOptions',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getConfigurationItems',
},
default: '',
default: [],
description: 'Configuration Items, \'cmdb_ci\' in metadata',
},
{
@ -601,7 +619,7 @@ export const incidentFields: INodeProperties[] = [
value: 1,
},
],
default: '',
default: 1,
description: 'The impact of the incident',
},
{
@ -609,9 +627,11 @@ export const incidentFields: INodeProperties[] = [
name: 'close_code',
type: 'options',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentResolutionCodes',
},
default: '',
// nodelinter-ignore-next-line
description: 'The resolution code of the incident. \'close_code\' in metadata',
},
{
@ -619,6 +639,7 @@ export const incidentFields: INodeProperties[] = [
name: 'hold_reason',
type: 'options',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentHoldReasons',
},
default: '',
@ -629,6 +650,7 @@ export const incidentFields: INodeProperties[] = [
name: 'state',
type: 'options',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentStates',
},
default: '',
@ -639,6 +661,7 @@ export const incidentFields: INodeProperties[] = [
name: 'subcategory',
type: 'options',
typeOptions: {
// nodelinter-ignore-next-line
loadOptionsMethod: 'getIncidentSubcategories',
loadOptionsDependsOn: [
'additionalFields.category',
@ -665,7 +688,7 @@ export const incidentFields: INodeProperties[] = [
value: 1,
},
],
default: '',
default: 1,
description: 'The urgency of the incident',
},
],

View file

@ -9,6 +9,7 @@ import {
INodePropertyOptions,
INodeType,
INodeTypeDescription,
JsonObject,
NodeOperationError,
} from 'n8n-workflow';
@ -82,13 +83,49 @@ export class ServiceNow implements INodeType {
{
name: 'serviceNowOAuth2Api',
required: true,
displayOptions: {
show: {
authentication: [
'oAuth2',
],
},
},
},
{
name: 'serviceNowBasicApi',
required: true,
displayOptions: {
show: {
authentication: [
'basicAuth',
],
},
},
},
],
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',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Business Service',
@ -427,7 +464,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
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(',');
}
@ -448,7 +485,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
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(',');
}
@ -469,7 +506,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
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(',');
}
@ -490,7 +527,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
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(',');
}
@ -529,7 +566,7 @@ export class ServiceNow implements INodeType {
const id = this.getNodeParameter('id', i) as string;
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(',');
}
@ -541,7 +578,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
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(',');
}
@ -604,7 +641,7 @@ export class ServiceNow implements INodeType {
const id = this.getNodeParameter('id', i) as string;
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(',');
}
@ -617,7 +654,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
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(',');
}
@ -679,7 +716,7 @@ export class ServiceNow implements INodeType {
const getOption = this.getNodeParameter('getOption', i) as string;
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(',');
}
@ -700,7 +737,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
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(',');
}
@ -730,7 +767,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
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(',');
}
@ -751,7 +788,7 @@ export class ServiceNow implements INodeType {
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
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(',');
}
@ -771,7 +808,7 @@ export class ServiceNow implements INodeType {
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ error: error.message });
returnData.push({ error: (error as JsonObject).message });
continue;
}

View file

@ -7,6 +7,7 @@ export const tableRecordOperations: INodeProperties[] = [
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: [
@ -71,7 +72,7 @@ export const tableRecordFields: INodeProperties[] = [
type: 'options',
options: [
{
name: 'Auto-map Input Data to Columns',
name: 'Auto-Map Input Data to Columns',
value: 'mapInput',
description: 'Use when node input names match destination field names',
},
@ -117,7 +118,7 @@ export const tableRecordFields: INodeProperties[] = [
},
default: '',
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',
@ -208,7 +209,7 @@ export const tableRecordFields: INodeProperties[] = [
},
},
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',
@ -268,15 +269,16 @@ export const tableRecordFields: INodeProperties[] = [
'tableName',
],
},
default: '',
default: [],
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',
name: 'sysparm_query',
type: 'string',
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',
@ -380,8 +382,9 @@ export const tableRecordFields: INodeProperties[] = [
'tableName',
],
},
default: '',
default: [],
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',
@ -455,7 +458,7 @@ export const tableRecordFields: INodeProperties[] = [
type: 'options',
options: [
{
name: 'Auto-map Input Data to Columns',
name: 'Auto-Map Input Data to Columns',
value: 'mapInput',
description: 'Use when node input names match destination field names',
},
@ -501,7 +504,7 @@ export const tableRecordFields: INodeProperties[] = [
},
default: '',
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',

View file

@ -7,6 +7,7 @@ export const userOperations: INodeProperties[] = [
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: [
@ -84,7 +85,7 @@ export const userFields: INodeProperties[] = [
displayName: 'Active',
name: 'active',
type: 'boolean',
default: '',
default: false,
description: 'Whether to activate the user',
},
{
@ -196,7 +197,7 @@ export const userFields: INodeProperties[] = [
displayName: 'Password Needs Reset',
name: 'password_needs_reset',
type: 'boolean',
default: '',
default: false,
description: 'Whether to require a password reset when the user logs in',
},
{
@ -213,7 +214,7 @@ export const userFields: INodeProperties[] = [
typeOptions: {
loadOptionsMethod: 'getUserRoles',
},
default: '',
default: [],
description: 'Roles of the user',
},
{
@ -242,6 +243,7 @@ export const userFields: INodeProperties[] = [
name: 'user_name',
type: 'string',
default: '',
// nodelinter-ignore-next-line
description: 'A username associated with the user (e.g. user_name.123)',
},
{
@ -272,7 +274,7 @@ export const userFields: INodeProperties[] = [
},
},
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',
@ -332,15 +334,16 @@ export const userFields: INodeProperties[] = [
'operation',
],
},
default: '',
default: [],
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',
name: 'sysparm_query',
type: 'string',
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',
@ -491,8 +494,9 @@ export const userFields: INodeProperties[] = [
'operation',
],
},
default: '',
default: [],
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',
@ -560,7 +564,7 @@ export const userFields: INodeProperties[] = [
displayName: 'Active',
name: 'active',
type: 'boolean',
default: '',
default: false,
description: 'Whether to activate the user',
},
{
@ -672,7 +676,7 @@ export const userFields: INodeProperties[] = [
displayName: 'Password Needs Reset',
name: 'password_needs_reset',
type: 'boolean',
default: '',
default: false,
description: 'Whether to require a password reset when the user logs in',
},
{
@ -689,7 +693,7 @@ export const userFields: INodeProperties[] = [
typeOptions: {
loadOptionsMethod: 'getUserRoles',
},
default: '',
default: [],
description: 'Roles of the user',
},
{
@ -718,6 +722,7 @@ export const userFields: INodeProperties[] = [
name: 'user_name',
type: 'string',
default: '',
// nodelinter-ignore-next-line
description: 'A username associated with the user (e.g. user_name.123)',
},
{

View file

@ -7,6 +7,7 @@ export const userGroupOperations: INodeProperties[] = [
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: [
@ -44,7 +45,7 @@ export const userGroupFields: INodeProperties[] = [
},
},
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',
@ -101,15 +102,16 @@ export const userGroupFields: INodeProperties[] = [
typeOptions: {
loadOptionsMethod: 'getColumns',
},
default: '',
default: [],
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',
name: 'sysparm_query',
type: 'string',
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',

View file

@ -7,6 +7,7 @@ export const userRoleOperations: INodeProperties[] = [
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: [
@ -44,7 +45,7 @@ export const userRoleFields: INodeProperties[] = [
},
},
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',
@ -101,15 +102,16 @@ export const userRoleFields: INodeProperties[] = [
typeOptions: {
loadOptionsMethod: 'getColumns',
},
default: '',
default: [],
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',
name: 'sysparm_query',
type: 'string',
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',

View file

@ -46,6 +46,11 @@ export const activityOperations: INodeProperties[] = [
value: 'getLaps',
description: 'Get all activity laps',
},
{
name: 'Get Streams',
value: 'getStreams',
description: 'Get activity streams',
},
{
name: 'Get Zones',
value: 'getZones',
@ -316,6 +321,7 @@ export const activityFields: INodeProperties[] = [
'getLaps',
'getKudos',
'getZones',
'getStreams',
],
},
},
@ -369,7 +375,70 @@ export const activityFields: INodeProperties[] = [
default: 50,
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 */
/* -------------------------------------------------------------------------- */

View file

@ -129,6 +129,15 @@ export class Strava implements INodeType {
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
if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', i) as boolean;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -9,7 +9,9 @@ import {
} from 'n8n-core';
import {
IDataObject, NodeApiError,
IDataObject,
JsonObject,
NodeApiError,
} 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
@ -26,6 +28,7 @@ export async function xeroApiRequest(this: IExecuteFunctions | IExecuteSingleFun
try {
if (body.organizationId) {
options.headers = { ...options.headers, 'Xero-tenant-id': body.organizationId };
delete body.organizationId;
}
if (Object.keys(headers).length !== 0) {
options.headers = Object.assign({}, options.headers, headers);
@ -36,7 +39,7 @@ export async function xeroApiRequest(this: IExecuteFunctions | IExecuteSingleFun
//@ts-ignore
return await this.helpers.requestOAuth2.call(this, 'xeroOAuth2Api', options);
} 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 {
Type?: string;
AddressType?: string;
AddressLine1?: string;
AddressLine2?: string;
City?: string;
@ -11,7 +11,7 @@ export interface IAddress {
}
export interface IPhone {
Type?: string;
PhoneType?: string;
PhoneNumber?: string;
PhoneAreaCode?: string;
PhoneCountryCode?: string;

View file

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

View file

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

View file

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

View file

@ -12,7 +12,7 @@ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensa
## 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 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