diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 79daf4cadb..348ae253e0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,5 +23,6 @@ jobs: npm run bootstrap npm run build --if-present npm test + npm run tslint env: CI: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fcc046beef..7fa19c8cbd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -119,6 +119,10 @@ To start n8n execute: npm run start ``` +To start n8n with tunnel: +``` +./packages/cli/bin/n8n start --tunnel +``` ## Development Cycle @@ -213,23 +217,7 @@ If you'd like to submit a new node, please go through the following checklist. T ## Extend Documentation -All the files which get used in the n8n documentation on [https://docs.n8n.io](https://docs.n8n.io) -can be found in the [/docs](https://github.com/n8n-io/n8n/tree/master/docs) folder. So all changes -and additions can directly be made in there - -That the markdown docs look pretty we use [docsify](https://docsify.js.org). It is possible to test -locally how it looks like rendered with the following commands: - -```bash -# 1. Install docisify -npm i docsify-cli -g - -# 2. Go into n8n folder (the same folder which contains this file). For example: -cd /data/n8n - -# 3. Start docsificy -docsify serve ./docs -``` +The repository for the n8n documentation on https://docs.n8n.io can be found [here](https://github.com/n8n-io/n8n-docs). ## Contributor License Agreement diff --git a/README.md b/README.md index 0150479ff0..9287a865ed 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![n8n.io - Workflow Automation](https://raw.githubusercontent.com/n8n-io/n8n/master/assets/n8n-logo.png) -n8n is a free and open [fair-code](http://faircode.io) licensed node based Workflow Automation Tool. It can be self-hosted, easily extended, and so also used with internal tools. +n8n is an extendable workflow automation tool. With a [fair-code](http://faircode.io) distribution model, n8n will always have visible source code, be available to self-host, and allow you to add your own custom functions, logic and apps. n8n's node-based approach makes it highly versatile, enabling you to connect anything to everything. n8n.io - Screenshot @@ -16,7 +16,7 @@ received or lost a star. ## Available integrations -n8n has 100+ different nodes to automate workflows. The list can be found on: [https://n8n.io/nodes](https://n8n.io/nodes) +n8n has 170+ different nodes to automate workflows. The list can be found on: [https://n8n.io/nodes](https://n8n.io/nodes) ## Documentation @@ -67,16 +67,15 @@ check out our job posts: **Short answer:** It means "nodemation" and it is pronounced as n-eight-n. -**Long answer:** I get that question quite often (more often than I expected) +**Long answer:** "I get that question quite often (more often than I expected) so I decided it is probably best to answer it here. While looking for a good name for the project with a free domain I realized very quickly that all the good ones I could think of were already taken. So, in the end, I chose -nodemation. "node-" in the sense that it uses a Node-View and that it uses -Node.js and "-mation" for "automation" which is what the project is supposed to help with. +nodemation. 'node-' in the sense that it uses a Node-View and that it uses +Node.js and '-mation' for 'automation' which is what the project is supposed to help with. However, I did not like how long the name was and I could not imagine writing something that long every time in the CLI. That is when I then ended up on -"n8n". Sure does not work perfectly but does neither for Kubernetes (k8s) and -did not hear anybody complain there. So I guess it should be ok. +'n8n'." - **Jan Oberhauser, Founder and CEO, n8n.io** @@ -88,6 +87,6 @@ Have you found a bug :bug: ? Or maybe you have a nice feature :sparkles: to cont ## License -n8n is [fair-code](http://faircode.io) licensed under [**Apache 2.0 with Commons Clause**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md) +n8n is [fair-code](http://faircode.io) licensed under [**Apache 2.0 with Commons Clause**](https://github.com/n8n-io/n8n/blob/master/packages/cli/LICENSE.md). -Additional information about license can be found in the [FAQ](https://docs.n8n.io/#/faq?id=license) +Additional information about license can be found in the [FAQ](https://docs.n8n.io/#/faq?id=license). diff --git a/packages/cli/package.json b/packages/cli/package.json index e14f9d45d1..225d4998fe 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.79.0", + "version": "0.79.3", "description": "n8n Workflow Automation Tool", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io", @@ -102,7 +102,7 @@ "mysql2": "^2.0.1", "n8n-core": "~0.43.0", "n8n-editor-ui": "~0.55.0", - "n8n-nodes-base": "~0.74.0", + "n8n-nodes-base": "~0.74.1", "n8n-workflow": "~0.39.0", "oauth-1.0a": "^2.2.6", "open": "^7.0.0", diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index 8b7bc0aff5..0d2e9bad3e 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -52,6 +52,9 @@ export class ActiveWorkflowRunner { // so intead of pulling all the active wehhooks just pull the actives that have a trigger const workflowsData: IWorkflowDb[] = await Db.collections.Workflow!.find({ active: true }) as IWorkflowDb[]; + // Clear up active workflow table + await Db.collections.Webhook?.clear(); + this.activeWorkflows = new ActiveWorkflows(); if (workflowsData.length !== 0) { @@ -59,22 +62,14 @@ export class ActiveWorkflowRunner { console.log(' Start Active Workflows:'); console.log(' ================================'); - const nodeTypes = NodeTypes(); - for (const workflowData of workflowsData) { - - const workflow = new Workflow({ id: workflowData.id.toString(), name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings}); - - if (workflow.getTriggerNodes().length !== 0 - || workflow.getPollNodes().length !== 0) { - console.log(` - ${workflowData.name}`); - try { - await this.add(workflowData.id.toString(), workflowData); - console.log(` => Started`); - } catch (error) { - console.log(` => ERROR: Workflow could not be activated:`); - console.log(` ${error.message}`); - } + console.log(` - ${workflowData.name}`); + try { + await this.add(workflowData.id.toString(), workflowData); + console.log(` => Started`); + } catch (error) { + console.log(` => ERROR: Workflow could not be activated:`); + console.log(` ${error.message}`); } } } @@ -87,14 +82,18 @@ export class ActiveWorkflowRunner { * @memberof ActiveWorkflowRunner */ async removeAll(): Promise { - if (this.activeWorkflows === null) { - return; + const activeWorkflowId: string[] = []; + + if (this.activeWorkflows !== null) { + // TODO: This should be renamed! + activeWorkflowId.push.apply(activeWorkflowId, this.activeWorkflows.allActiveWorkflows()); } - const activeWorkflows = this.activeWorkflows.allActiveWorkflows(); + const activeWorkflows = await this.getActiveWorkflows(); + activeWorkflowId.push.apply(activeWorkflowId, activeWorkflows.map(workflow => workflow.id)); const removePromises = []; - for (const workflowId of activeWorkflows) { + for (const workflowId of activeWorkflowId) { removePromises.push(this.remove(workflowId)); } @@ -183,7 +182,7 @@ export class ActiveWorkflowRunner { * @memberof ActiveWorkflowRunner */ getActiveWorkflows(): Promise { - return Db.collections.Workflow?.find({ select: ['id'] }) as Promise; + return Db.collections.Workflow?.find({ where: { active: true }, select: ['id'] }) as Promise; } diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 9ff844d36e..aa3a30a6f4 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -249,7 +249,7 @@ class App { if (token === undefined || token === '') { return ResponseHelper.jwtAuthAuthorizationError(res, "Missing token"); } - if (jwtHeaderValuePrefix != '' && token.startsWith(jwtHeaderValuePrefix)) { + if (jwtHeaderValuePrefix !== '' && token.startsWith(jwtHeaderValuePrefix)) { token = token.replace(jwtHeaderValuePrefix + ' ', '').trimLeft(); } @@ -340,7 +340,12 @@ class App { })); //support application/x-www-form-urlencoded post data - this.app.use(bodyParser.urlencoded({ extended: false })); + this.app.use(bodyParser.urlencoded({ extended: false, + verify: (req, res, buf) => { + // @ts-ignore + req.rawBody = buf; + } + })); if (process.env['NODE_ENV'] !== 'production') { this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { diff --git a/packages/cli/src/databases/mongodb/migrations/1592679094242-WebhookModel.ts b/packages/cli/src/databases/mongodb/migrations/1592679094242-WebhookModel.ts index c05a44f765..22c4db53fa 100644 --- a/packages/cli/src/databases/mongodb/migrations/1592679094242-WebhookModel.ts +++ b/packages/cli/src/databases/mongodb/migrations/1592679094242-WebhookModel.ts @@ -2,20 +2,6 @@ import { MigrationInterface, } from 'typeorm'; -import { - IWorkflowDb, - NodeTypes, - WebhookHelpers, -} from '../../..'; - -import { - Workflow, -} from 'n8n-workflow/dist/src/Workflow'; - -import { - IWebhookDb, -} from '../../../Interfaces'; - import * as config from '../../../../config'; import { @@ -27,26 +13,6 @@ export class WebhookModel1592679094242 implements MigrationInterface { async up(queryRunner: MongoQueryRunner): Promise { const tablePrefix = config.get('database.tablePrefix'); - const workflows = await queryRunner.cursor( `${tablePrefix}workflow_entity`, { active: true }).toArray() as IWorkflowDb[]; - const data: IWebhookDb[] = []; - const nodeTypes = NodeTypes(); - for (const workflow of workflows) { - const workflowInstance = new Workflow({ id: workflow.id as string, name: workflow.name, nodes: workflow.nodes, connections: workflow.connections, active: workflow.active, nodeTypes, staticData: workflow.staticData, settings: workflow.settings }); - const webhooks = WebhookHelpers.getWorkflowWebhooksBasic(workflowInstance); - for (const webhook of webhooks) { - data.push({ - workflowId: workflowInstance.id as string, - webhookPath: webhook.path, - method: webhook.httpMethod, - node: webhook.node, - }); - } - } - - if (data.length !== 0) { - await queryRunner.manager.insertMany(`${tablePrefix}webhook_entity`, data); - } - await queryRunner.manager.createCollectionIndex(`${tablePrefix}webhook_entity`, ['webhookPath', 'method'], { unique: true, background: false }); } diff --git a/packages/cli/src/databases/mysqldb/migrations/1592447867632-WebhookModel.ts b/packages/cli/src/databases/mysqldb/migrations/1592447867632-WebhookModel.ts index 8a49080462..13969d72ef 100644 --- a/packages/cli/src/databases/mysqldb/migrations/1592447867632-WebhookModel.ts +++ b/packages/cli/src/databases/mysqldb/migrations/1592447867632-WebhookModel.ts @@ -5,20 +5,6 @@ import { import * as config from '../../../../config'; -import { - IWorkflowDb, - NodeTypes, - WebhookHelpers, -} from '../../..'; - -import { - Workflow, -} from 'n8n-workflow'; - -import { - IWebhookDb, -} from '../../../Interfaces'; - export class WebhookModel1592447867632 implements MigrationInterface { name = 'WebhookModel1592447867632'; @@ -26,30 +12,6 @@ export class WebhookModel1592447867632 implements MigrationInterface { const tablePrefix = config.get('database.tablePrefix'); await queryRunner.query(`CREATE TABLE IF NOT EXISTS ${tablePrefix}webhook_entity (workflowId int NOT NULL, webhookPath varchar(255) NOT NULL, method varchar(255) NOT NULL, node varchar(255) NOT NULL, PRIMARY KEY (webhookPath, method)) ENGINE=InnoDB`); - - const workflows = await queryRunner.query(`SELECT * FROM ${tablePrefix}workflow_entity WHERE active=true`) as IWorkflowDb[]; - const data: IWebhookDb[] = []; - const nodeTypes = NodeTypes(); - for (const workflow of workflows) { - const workflowInstance = new Workflow({ id: workflow.id as string, name: workflow.name, nodes: workflow.nodes, connections: workflow.connections, active: workflow.active, nodeTypes, staticData: workflow.staticData, settings: workflow.settings }); - const webhooks = WebhookHelpers.getWorkflowWebhooksBasic(workflowInstance); - for (const webhook of webhooks) { - data.push({ - workflowId: workflowInstance.id as string, - webhookPath: webhook.path, - method: webhook.httpMethod, - node: webhook.node, - }); - } - } - - if (data.length !== 0) { - await queryRunner.manager.createQueryBuilder() - .insert() - .into(`${tablePrefix}webhook_entity`) - .values(data) - .execute(); - } } async down(queryRunner: QueryRunner): Promise { diff --git a/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts b/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts index e53fc28915..0c195f9d54 100644 --- a/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts +++ b/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts @@ -3,20 +3,6 @@ import { QueryRunner, } from 'typeorm'; -import { - IWorkflowDb, - NodeTypes, - WebhookHelpers, -} from '../../..'; - -import { - Workflow, -} from 'n8n-workflow'; - -import { - IWebhookDb, -} from '../../../Interfaces'; - import * as config from '../../../../config'; export class WebhookModel1589476000887 implements MigrationInterface { @@ -31,30 +17,6 @@ export class WebhookModel1589476000887 implements MigrationInterface { } await queryRunner.query(`CREATE TABLE ${tablePrefix}webhook_entity ("workflowId" integer NOT NULL, "webhookPath" character varying NOT NULL, "method" character varying NOT NULL, "node" character varying NOT NULL, CONSTRAINT "PK_${tablePrefixIndex}b21ace2e13596ccd87dc9bf4ea6" PRIMARY KEY ("webhookPath", "method"))`, undefined); - - const workflows = await queryRunner.query(`SELECT * FROM ${tablePrefix}workflow_entity WHERE active=true`) as IWorkflowDb[]; - const data: IWebhookDb[] = []; - const nodeTypes = NodeTypes(); - for (const workflow of workflows) { - const workflowInstance = new Workflow({ id: workflow.id as string, name: workflow.name, nodes: workflow.nodes, connections: workflow.connections, active: workflow.active, nodeTypes, staticData: workflow.staticData, settings: workflow.settings }); - const webhooks = WebhookHelpers.getWorkflowWebhooksBasic(workflowInstance); - for (const webhook of webhooks) { - data.push({ - workflowId: workflowInstance.id as string, - webhookPath: webhook.path, - method: webhook.httpMethod, - node: webhook.node, - }); - } - } - - if (data.length !== 0) { - await queryRunner.manager.createQueryBuilder() - .insert() - .into(`${tablePrefix}webhook_entity`) - .values(data) - .execute(); - } } async down(queryRunner: QueryRunner): Promise { diff --git a/packages/cli/src/databases/sqlite/migrations/1592445003908-WebhookModel.ts b/packages/cli/src/databases/sqlite/migrations/1592445003908-WebhookModel.ts index 92704482b2..b011061e28 100644 --- a/packages/cli/src/databases/sqlite/migrations/1592445003908-WebhookModel.ts +++ b/packages/cli/src/databases/sqlite/migrations/1592445003908-WebhookModel.ts @@ -5,20 +5,6 @@ import { import * as config from '../../../../config'; -import { - IWorkflowDb, - NodeTypes, - WebhookHelpers, -} from '../../..'; - -import { - Workflow, -} from 'n8n-workflow'; - -import { - IWebhookDb, -} from '../../../Interfaces'; - export class WebhookModel1592445003908 implements MigrationInterface { name = 'WebhookModel1592445003908'; @@ -26,34 +12,6 @@ export class WebhookModel1592445003908 implements MigrationInterface { const tablePrefix = config.get('database.tablePrefix'); await queryRunner.query(`CREATE TABLE IF NOT EXISTS ${tablePrefix}webhook_entity ("workflowId" integer NOT NULL, "webhookPath" varchar NOT NULL, "method" varchar NOT NULL, "node" varchar NOT NULL, PRIMARY KEY ("webhookPath", "method"))`); - - const workflows = await queryRunner.query(`SELECT * FROM ${tablePrefix}workflow_entity WHERE active=true`) as IWorkflowDb[]; - const data: IWebhookDb[] = []; - const nodeTypes = NodeTypes(); - for (const workflow of workflows) { - workflow.nodes = JSON.parse(workflow.nodes as unknown as string); - workflow.connections = JSON.parse(workflow.connections as unknown as string); - workflow.staticData = JSON.parse(workflow.staticData as unknown as string); - workflow.settings = JSON.parse(workflow.settings as unknown as string); - const workflowInstance = new Workflow({ id: workflow.id as string, name: workflow.name, nodes: workflow.nodes, connections: workflow.connections, active: workflow.active, nodeTypes, staticData: workflow.staticData, settings: workflow.settings }); - const webhooks = WebhookHelpers.getWorkflowWebhooksBasic(workflowInstance); - for (const webhook of webhooks) { - data.push({ - workflowId: workflowInstance.id as string, - webhookPath: webhook.path, - method: webhook.httpMethod, - node: webhook.node, - }); - } - } - - if (data.length !== 0) { - await queryRunner.manager.createQueryBuilder() - .insert() - .into(`${tablePrefix}webhook_entity`) - .values(data) - .execute(); - } } async down(queryRunner: QueryRunner): Promise { diff --git a/packages/node-dev/package.json b/packages/node-dev/package.json index b816d7b543..5b3be4fca0 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -58,7 +58,7 @@ "change-case": "^4.1.1", "copyfiles": "^2.1.1", "inquirer": "^7.0.1", - "n8n-core": "^0.36.0", + "n8n-core": "^0.43.0", "n8n-workflow": "^0.33.0", "replace-in-file": "^6.0.0", "request": "^2.88.2", diff --git a/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts b/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts index 49d96e395c..c23c84ce6e 100644 --- a/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts +++ b/packages/nodes-base/nodes/AcuityScheduling/GenericFunctions.ts @@ -24,7 +24,7 @@ export async function acuitySchedulingApiRequest(this: IHookFunctions | IExecute }; try { - if (authenticationMethod === 'accessToken') { + if (authenticationMethod === 'apiKey') { const credentials = this.getCredentials('acuitySchedulingApi'); if (credentials === undefined) { throw new Error('No credentials got returned!'); diff --git a/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts b/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts index 5562274887..280f9dedee 100644 --- a/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts +++ b/packages/nodes-base/nodes/ClickUp/ClickUp.node.ts @@ -371,8 +371,8 @@ export class ClickUp implements INodeType { const body: IDataObject = { name, }; - if (additionalFields.assigneeId) { - body.assignee = parseInt(additionalFields.assigneeId as string, 10); + if (additionalFields.assignee) { + body.assignee = parseInt(additionalFields.assignee as string, 10); } responseData = await clickupApiRequest.call(this, 'POST', `/checklist/${checklistId}/checklist_item`, body); responseData = responseData.checklist; @@ -414,7 +414,7 @@ export class ClickUp implements INodeType { comment_text: commentText, }; if (additionalFields.assignee) { - body.assigneeId = additionalFields.assignee as string; + body.assignee = parseInt(additionalFields.assignee as string, 10); } if (additionalFields.notifyAll) { body.notify_all = additionalFields.notifyAll as boolean; diff --git a/packages/nodes-base/nodes/Contentful/AssetDescription.ts b/packages/nodes-base/nodes/Contentful/AssetDescription.ts index a753f6dbdc..30b1677136 100644 --- a/packages/nodes-base/nodes/Contentful/AssetDescription.ts +++ b/packages/nodes-base/nodes/Contentful/AssetDescription.ts @@ -192,26 +192,6 @@ export const fields = [ default: '', description: ' Full-text search is case insensitive and might return more results than expected. A query will only take values with more than 1 character.', }, - ], - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Select Option', - default: {}, - displayOptions: { - show: { - resource: [ - resource.value, - ], - operation: [ - 'getAll', - 'get' - ], - }, - }, - options: [ { displayName: 'RAW Data', name: 'rawData', diff --git a/packages/nodes-base/nodes/Contentful/ContentTypeDescription.ts b/packages/nodes-base/nodes/Contentful/ContentTypeDescription.ts index 9c31a32f48..05e18b1373 100644 --- a/packages/nodes-base/nodes/Contentful/ContentTypeDescription.ts +++ b/packages/nodes-base/nodes/Contentful/ContentTypeDescription.ts @@ -67,10 +67,10 @@ export const fields = [ }, }, { - displayName: 'Options', - name: 'options', + displayName: 'Additional Fields', + name: 'additionalFields', type: 'collection', - placeholder: 'Select Option', + placeholder: 'Add Field', default: {}, displayOptions: { show: { diff --git a/packages/nodes-base/nodes/Contentful/Contentful.node.ts b/packages/nodes-base/nodes/Contentful/Contentful.node.ts index 0ad0a96bb6..b4dde2ba0e 100644 --- a/packages/nodes-base/nodes/Contentful/Contentful.node.ts +++ b/packages/nodes-base/nodes/Contentful/Contentful.node.ts @@ -118,11 +118,11 @@ export class Contentful implements INodeType { const id = this.getNodeParameter('contentTypeId', 0) as string; - const options = this.getNodeParameter('options', i) as IDataObject; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/content_types/${id}`); - if (!options.rawData) { + if (!additionalFields.rawData) { responseData = responseData.fields; } } @@ -137,11 +137,11 @@ export class Contentful implements INodeType { const id = this.getNodeParameter('entryId', 0) as string; - const options = this.getNodeParameter('options', i) as IDataObject; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/entries/${id}`, {}, qs); - if (!options.rawData) { + if (!additionalFields.rawData) { responseData = responseData.fields; } @@ -151,11 +151,11 @@ export class Contentful implements INodeType { const returnAll = this.getNodeParameter('returnAll', 0) as boolean; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const rawData = additionalFields.rawData; + additionalFields.rawData = undefined; const env = this.getNodeParameter('environmentId', i) as string; - const options = this.getNodeParameter('options', i) as IDataObject; - Object.assign(qs, additionalFields); if (qs.equal) { @@ -185,7 +185,7 @@ export class Contentful implements INodeType { if (returnAll) { responseData = await contenfulApiRequestAllItems.call(this, 'items', 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/entries`, {}, qs); - if (!options.rawData) { + if (!rawData) { const assets : IDataObject[] = []; // tslint:disable-next-line: no-any responseData.map((asset : any) => { @@ -199,7 +199,7 @@ export class Contentful implements INodeType { responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/entries`, {}, qs); responseData = responseData.items; - if (!options.rawData) { + if (!rawData) { const assets : IDataObject[] = []; // tslint:disable-next-line: no-any responseData.map((asset : any) => { @@ -219,11 +219,11 @@ export class Contentful implements INodeType { const id = this.getNodeParameter('assetId', 0) as string; - const options = this.getNodeParameter('options', i) as IDataObject; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/assets/${id}`, {}, qs); - if (!options.rawData) { + if (!additionalFields.rawData) { responseData = responseData.fields; } @@ -234,11 +234,11 @@ export class Contentful implements INodeType { const returnAll = this.getNodeParameter('returnAll', 0) as boolean; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + const rawData = additionalFields.rawData; + additionalFields.rawData = undefined; const env = this.getNodeParameter('environmentId', i) as string; - const options = this.getNodeParameter('options', i) as IDataObject; - Object.assign(qs, additionalFields); if (qs.equal) { @@ -268,7 +268,7 @@ export class Contentful implements INodeType { if (returnAll) { responseData = await contenfulApiRequestAllItems.call(this, 'items', 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/assets`, {}, qs); - if (!options.rawData) { + if (!rawData) { const assets : IDataObject[] = []; // tslint:disable-next-line: no-any responseData.map((asset : any) => { @@ -277,12 +277,12 @@ export class Contentful implements INodeType { responseData = assets; } } else { - const limit = this.getNodeParameter('limit', 0) as number; + const limit = this.getNodeParameter('limit', i) as number; qs.limit = limit; responseData = await contentfulApiRequest.call(this, 'GET', `/spaces/${credentials?.spaceId}/environments/${env}/assets`, {}, qs); responseData = responseData.items; - if (!options.rawData) { + if (!rawData) { const assets : IDataObject[] = []; // tslint:disable-next-line: no-any responseData.map((asset : any) => { diff --git a/packages/nodes-base/nodes/Contentful/EntryDescription.ts b/packages/nodes-base/nodes/Contentful/EntryDescription.ts index fbbee25123..dcce1d7c62 100644 --- a/packages/nodes-base/nodes/Contentful/EntryDescription.ts +++ b/packages/nodes-base/nodes/Contentful/EntryDescription.ts @@ -182,6 +182,13 @@ export const fields = [ default: '', description: ' Full-text search is case insensitive and might return more results than expected. A query will only take values with more than 1 character.', }, + { + displayName: 'RAW Data', + name: 'rawData', + type: 'boolean', + default: false, + description: 'If the data should be returned RAW instead of parsed.', + }, ], }, { @@ -201,31 +208,4 @@ export const fields = [ }, }, }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - placeholder: 'Select Option', - default: {}, - displayOptions: { - show: { - resource: [ - resource.value, - ], - operation: [ - 'get', - 'getAll', - ], - }, - }, - options: [ - { - displayName: 'RAW Data', - name: 'rawData', - type: 'boolean', - default: false, - description: 'If the data should be returned RAW instead of parsed.', - }, - ], - }, ] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/HttpRequest.node.ts b/packages/nodes-base/nodes/HttpRequest.node.ts index ca84937cb8..e308962522 100644 --- a/packages/nodes-base/nodes/HttpRequest.node.ts +++ b/packages/nodes-base/nodes/HttpRequest.node.ts @@ -797,13 +797,14 @@ export class HttpRequest implements INodeType { }; } - if (responseFormat === 'json') { - - requestOptions.headers!['accept'] = 'application/json,text/*;q=0.99'; - } else if (responseFormat === 'string') { - requestOptions.headers!['accept'] = 'application/json,text/html,application/xhtml+xml,application/xml,text/*;q=0.9, */*;q=0.1'; - } else { - requestOptions.headers!['accept'] = 'application/json,text/html,application/xhtml+xml,application/xml,text/*;q=0.9, image/*;q=0.8, */*;q=0.7'; + if (requestOptions.headers!['accept'] === undefined) { + if (responseFormat === 'json') { + requestOptions.headers!['accept'] = 'application/json,text/*;q=0.99'; + } else if (responseFormat === 'string') { + requestOptions.headers!['accept'] = 'application/json,text/html,application/xhtml+xml,application/xml,text/*;q=0.9, */*;q=0.1'; + } else { + requestOptions.headers!['accept'] = 'application/json,text/html,application/xhtml+xml,application/xml,text/*;q=0.9, image/*;q=0.8, */*;q=0.7'; + } } if (responseFormat === 'file') { diff --git a/packages/nodes-base/nodes/Salesforce/GenericFunctions.ts b/packages/nodes-base/nodes/Salesforce/GenericFunctions.ts index 9b183ca73d..d7bdc7ddb3 100644 --- a/packages/nodes-base/nodes/Salesforce/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Salesforce/GenericFunctions.ts @@ -10,7 +10,7 @@ import { export async function salesforceApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const credentials = this.getCredentials('salesforceOAuth2Api'); - const subdomain = ((credentials!.accessTokenUrl as string).match(/https:\/\/(.+).salesforce\.com/) || [])[1] + const subdomain = ((credentials!.accessTokenUrl as string).match(/https:\/\/(.+).salesforce\.com/) || [])[1]; const options: OptionsWithUri = { method, body: method === "GET" ? undefined : body, diff --git a/packages/nodes-base/nodes/Zendesk/UserDescription.ts b/packages/nodes-base/nodes/Zendesk/UserDescription.ts index bc43738ae0..6e606a5c22 100644 --- a/packages/nodes-base/nodes/Zendesk/UserDescription.ts +++ b/packages/nodes-base/nodes/Zendesk/UserDescription.ts @@ -35,6 +35,11 @@ export const userOperations = [ value: 'getAll', description: 'Get all users', }, + { + name: 'Search', + value: 'search', + description: 'Search users', + }, { name: 'Update', value: 'update', @@ -667,7 +672,81 @@ export const userFields = [ }, ], }, - +/* -------------------------------------------------------------------------- */ +/* user:search */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'search', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'search', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + default: 100, + description: 'How many results to return.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'search', + ], + }, + }, + options: [ + { + displayName: 'Query', + name: 'query', + type: 'string', + default: '', + }, + { + displayName: 'External ID', + name: 'external_id', + type: 'string', + default: '', + }, + ], + }, /* -------------------------------------------------------------------------- */ /* user:delete */ /* -------------------------------------------------------------------------- */ diff --git a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts index e349d58cbe..632660a343 100644 --- a/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts +++ b/packages/nodes-base/nodes/Zendesk/Zendesk.node.ts @@ -477,6 +477,22 @@ export class Zendesk implements INodeType { responseData = responseData.users; } } + //https://developer.zendesk.com/rest_api/docs/support/users#search-users + if (operation === 'search') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const options = this.getNodeParameter('filters', i) as IDataObject; + + Object.assign(qs, options); + + if (returnAll) { + responseData = await zendeskApiRequestAllItems.call(this, 'users', 'GET', `/users/search`, {}, qs); + } else { + const limit = this.getNodeParameter('limit', i) as number; + qs.per_page = limit; + responseData = await zendeskApiRequest.call(this, 'GET', `/users/search`, {}, qs); + responseData = responseData.users; + } + } //https://developer.zendesk.com/rest_api/docs/support/users#delete-user if (operation === 'delete') { const userId = this.getNodeParameter('id', i) as string; diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 2119e11a7f..baa3ed6483 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "0.74.0", + "version": "0.74.1", "description": "Base nodes of n8n", "license": "SEE LICENSE IN LICENSE.md", "homepage": "https://n8n.io",