From c6aa73cc2f4598737e332f489db1a3a6abbbb81e Mon Sep 17 00:00:00 2001 From: Chris Lonardo Date: Sun, 5 Apr 2020 21:06:00 -0400 Subject: [PATCH 1/4] upgrade mongodb client to support mongodb 4.2 and allow conn string override --- package.json | 38 +-- packages/cli/package.json | 2 +- packages/editor-ui/src/main.ts | 2 +- .../nodes-base/nodes/MongoDb/MongoDb.node.ts | 245 ++++-------------- .../nodes/MongoDb/mongo.node.options.ts | 156 +++++++++++ .../nodes/MongoDb/mongo.node.utils.ts | 47 ++++ packages/nodes-base/package.json | 6 +- 7 files changed, 279 insertions(+), 217 deletions(-) create mode 100644 packages/nodes-base/nodes/MongoDb/mongo.node.options.ts create mode 100644 packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts diff --git a/package.json b/package.json index b045830f51..e14010e7a8 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,22 @@ { - "name": "n8n", - "private": true, - "homepage": "https://n8n.io", - "scripts": { - "bootstrap": "lerna bootstrap --hoist --no-ci", - "build": "lerna exec npm run build", - "dev": "lerna exec npm run dev --parallel", - "start": "run-script-os", - "start:default": "cd packages/cli/bin && ./n8n", - "start:windows": "cd packages/cli/bin && n8n", - "test": "lerna run test", - "watch": "lerna run --parallel watch" - }, - "devDependencies": { - "lerna": "^3.13.1", - "run-script-os": "^1.0.7" - }, - "postcss": {} + "name": "n8n", + "private": true, + "homepage": "https://n8n.io", + "scripts": { + "bootstrap": "lerna bootstrap --hoist --no-ci", + "build": "lerna exec npm run build", + "dev": "lerna exec npm run dev --parallel", + "clean:dist": "lerna exec -- rimraf ./dist", + "start": "run-script-os", + "start:default": "cd packages/cli/bin && ./n8n", + "start:windows": "cd packages/cli/bin && n8n", + "test": "lerna run test", + "watch": "lerna run --parallel watch" + }, + "devDependencies": { + "lerna": "^3.13.1", + "rimraf": "^3.0.2", + "run-script-os": "^1.0.7" + }, + "postcss": {} } diff --git a/packages/cli/package.json b/packages/cli/package.json index aabfbb7f7a..386fce5dc1 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -93,7 +93,7 @@ "jwks-rsa": "^1.6.0", "localtunnel": "^2.0.0", "lodash.get": "^4.4.2", - "mongodb": "^3.2.3", + "mongodb": "^3.5.5", "mysql2": "^2.0.1", "n8n-core": "~0.29.0", "n8n-editor-ui": "~0.40.0", diff --git a/packages/editor-ui/src/main.ts b/packages/editor-ui/src/main.ts index f9a7f879a0..e1b30cd166 100644 --- a/packages/editor-ui/src/main.ts +++ b/packages/editor-ui/src/main.ts @@ -179,7 +179,7 @@ if (process.env.NODE_ENV !== 'production') { // not do anything about it anyway return; } - console.error('error cought in main.ts'); // eslint-disable-line no-console + console.error('error caught in main.ts'); // eslint-disable-line no-console console.error(message); // eslint-disable-line no-console console.error(error); // eslint-disable-line no-console }; diff --git a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts index 0aa6d603ac..d15fd0112e 100644 --- a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts +++ b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts @@ -1,227 +1,85 @@ -import { IExecuteFunctions } from 'n8n-core'; +import { IExecuteFunctions } from "n8n-core"; import { IDataObject, INodeExecutionData, INodeType, INodeTypeDescription, -} from 'n8n-workflow'; - -import { MongoClient } from 'mongodb'; - - -/** - * Returns of copy of the items which only contains the json data and - * of that only the define properties - * - * @param {INodeExecutionData[]} items The items to copy - * @param {string[]} properties The properties it should include - * @returns - */ -function getItemCopy(items: INodeExecutionData[], properties: string[]): IDataObject[] { - // Prepare the data to insert and copy it to be returned - let newItem: IDataObject; - return items.map((item) => { - newItem = {}; - for (const property of properties) { - if (item.json[property] === undefined) { - newItem[property] = null; - } else { - newItem[property] = JSON.parse(JSON.stringify(item.json[property])); - } - } - return newItem; - }); -} - +} from "n8n-workflow"; +import { nodeDescription } from "./mongo.node.options"; +import { MongoClient } from "mongodb"; +import { getItemCopy, buildParameterizedConnString } from "./mongo.node.utils"; export class MongoDb implements INodeType { - description: INodeTypeDescription = { - displayName: 'MongoDB', - name: 'mongoDb', - icon: 'file:mongoDb.png', - group: ['input'], - version: 1, - description: 'Find, insert and update documents in MongoDB.', - defaults: { - name: 'MongoDB', - color: '#13AA52', - }, - inputs: ['main'], - outputs: ['main'], - credentials: [ - { - name: 'mongoDb', - required: true, - } - ], - properties: [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - options: [ - { - name: 'Find', - value: 'find', - description: 'Find documents.', - }, - { - name: 'Insert', - value: 'insert', - description: 'Insert documents.', - }, - { - name: 'Update', - value: 'update', - description: 'Updates documents.', - }, - ], - default: 'find', - description: 'The operation to perform.', - }, - - { - displayName: 'Collection', - name: 'collection', - type: 'string', - required: true, - default: '', - description: 'MongoDB Collection' - }, - - // ---------------------------------- - // find - // ---------------------------------- - { - displayName: 'Query (JSON format)', - name: 'query', - type: 'string', - typeOptions: { - rows: 5, - }, - displayOptions: { - show: { - operation: [ - 'find' - ], - }, - }, - default: '{}', - placeholder: `{ "birth": { "$gt": "1950-01-01" } }`, - required: true, - description: 'MongoDB Find query.', - }, - - - // ---------------------------------- - // insert - // ---------------------------------- - { - displayName: 'Fields', - name: 'fields', - type: 'string', - displayOptions: { - show: { - operation: [ - 'insert' - ], - }, - }, - default: '', - placeholder: 'name,description', - description: 'Comma separated list of the fields to be included into the new document.', - }, - - - // ---------------------------------- - // update - // ---------------------------------- - { - displayName: 'Update Key', - name: 'updateKey', - type: 'string', - displayOptions: { - show: { - operation: [ - 'update' - ], - }, - }, - default: 'id', - required: true, - description: 'Name of the property which decides which rows in the database should be updated. Normally that would be "id".', - }, - { - displayName: 'Fields', - name: 'fields', - type: 'string', - displayOptions: { - show: { - operation: [ - 'update' - ], - }, - }, - default: '', - placeholder: 'name,description', - description: 'Comma separated list of the fields to be included into the new document.', - }, - - ] - }; - + description: INodeTypeDescription = nodeDescription; async execute(this: IExecuteFunctions): Promise { + const credentials = this.getCredentials("mongoDb"); - const credentials = this.getCredentials('mongoDb'); + // user can optionally override connection string + const useOverriddenConnString = this.getNodeParameter( + "shouldOverrideConnString", + 0 + ) as boolean; if (credentials === undefined) { - throw new Error('No credentials got returned!'); + throw new Error("No credentials got returned!"); } - let connectionUri = ''; - - if (credentials.port) { - connectionUri = `mongodb://${credentials.user}:${credentials.password}@${credentials.host}:${credentials.port}`; + let connectionUri = ""; + if (useOverriddenConnString === true) { + const connStringInput = this.getNodeParameter( + "connStringOverrideVal", + 0 + ) as string; + if (connStringInput && connStringInput.length > 0) { + connectionUri = connStringInput; + } else { + throw new Error( + "Cannot override credentials: valid MongoDB connection string not provided " + ); + } } else { - connectionUri = `mongodb+srv://${credentials.user}:${credentials.password}@${credentials.host}`; + connectionUri = buildParameterizedConnString(credentials); } - const client = await MongoClient.connect(connectionUri, { useNewUrlParser: true, useUnifiedTopology: true }); + const client: MongoClient = await MongoClient.connect(connectionUri, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + const mdb = client.db(credentials.database as string); let returnItems = []; const items = this.getInputData(); - const operation = this.getNodeParameter('operation', 0) as string; + const operation = this.getNodeParameter("operation", 0) as string; - if (operation === 'find') { + if (operation === "find") { // ---------------------------------- // find // ---------------------------------- const queryResult = await mdb - .collection(this.getNodeParameter('collection', 0) as string) - .find(JSON.parse(this.getNodeParameter('query', 0) as string)) + .collection(this.getNodeParameter("collection", 0) as string) + .find(JSON.parse(this.getNodeParameter("query", 0) as string)) .toArray(); returnItems = this.helpers.returnJsonArray(queryResult as IDataObject[]); - - } else if (operation === 'insert') { + } else if (operation === "insert") { // ---------------------------------- // insert // ---------------------------------- // Prepare the data to insert and copy it to be returned - const fields = (this.getNodeParameter('fields', 0) as string) - .split(',') - .map(f => f.trim()) - .filter(f => !!f); + const fields = (this.getNodeParameter("fields", 0) as string) + .split(",") + .map((f) => f.trim()) + .filter((f) => !!f); const insertItems = getItemCopy(items, fields); const { insertedIds } = await mdb - .collection(this.getNodeParameter('collection', 0) as string) + .collection(this.getNodeParameter("collection", 0) as string) .insertMany(insertItems); // Add the id to the data @@ -230,20 +88,20 @@ export class MongoDb implements INodeType { json: { ...insertItems[parseInt(i, 10)], id: insertedIds[parseInt(i, 10)] as string, - } + }, }); } - } else if (operation === 'update') { + } else if (operation === "update") { // ---------------------------------- // update // ---------------------------------- - const fields = (this.getNodeParameter('fields', 0) as string) - .split(',') - .map(f => f.trim()) - .filter(f => !!f); + const fields = (this.getNodeParameter("fields", 0) as string) + .split(",") + .map((f) => f.trim()) + .filter((f) => !!f); - let updateKey = this.getNodeParameter('updateKey', 0) as string; + let updateKey = this.getNodeParameter("updateKey", 0) as string; updateKey = updateKey.trim(); if (!fields.includes(updateKey)) { @@ -258,16 +116,15 @@ export class MongoDb implements INodeType { continue; } - const filter: { [key: string] :string } = {}; + const filter: { [key: string]: string } = {}; filter[updateKey] = item[updateKey] as string; await mdb - .collection(this.getNodeParameter('collection', 0) as string) + .collection(this.getNodeParameter("collection", 0) as string) .updateOne(filter, { $set: item }); } returnItems = this.helpers.returnJsonArray(updateItems as IDataObject[]); - } else { throw new Error(`The operation "${operation}" is not supported!`); } diff --git a/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts b/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts new file mode 100644 index 0000000000..19f18f4ab7 --- /dev/null +++ b/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts @@ -0,0 +1,156 @@ +import { INodeTypeDescription } from "n8n-workflow"; + +/** + * Options to be displayed + */ +export const nodeDescription: INodeTypeDescription = { + displayName: "MongoDB", + name: "mongoDb", + icon: "file:mongoDb.png", + group: ["input"], + version: 1, + description: "Find, insert and update documents in MongoDB.", + defaults: { + name: "MongoDB", + color: "#13AA52", + }, + inputs: ["main"], + outputs: ["main"], + credentials: [ + { + name: "mongoDb", + required: true, + }, + ], + properties: [ + { + displayName: "Override conn string", + name: "shouldOverrideConnString", + type: "boolean", + default: false, + description: + "Whether to override the generated connection string. Credentials will also be ignored in this case.", + }, + { + displayName: "Conn string", + name: "connStringOverrideVal", + type: "string", + typeOptions: { + rows: 1, + }, + displayOptions: { + show: { + shouldOverrideConnString: [true], + }, + }, + default: "", + placeholder: `mongodb://USERNAMEHERE:PASSWORDHERE@localhost:27017/?authSource=admin&readPreference=primary&appname=n8n&ssl=false`, + required: false, + description: `If "Override conn string" is checked, the value here will be used as a MongoDB connection string, and the MongoDB credentials will be ignored`, + }, + { + displayName: "Operation", + name: "operation", + type: "options", + options: [ + { + name: "Find", + value: "find", + description: "Find documents.", + }, + { + name: "Insert", + value: "insert", + description: "Insert documents.", + }, + { + name: "Update", + value: "update", + description: "Updates documents.", + }, + ], + default: "find", + description: "The operation to perform.", + }, + + { + displayName: "Collection", + name: "collection", + type: "string", + required: true, + default: "", + description: "MongoDB Collection", + }, + + // ---------------------------------- + // find + // ---------------------------------- + { + displayName: "Query (JSON format)", + name: "query", + type: "string", + typeOptions: { + rows: 5, + }, + displayOptions: { + show: { + operation: ["find"], + }, + }, + default: "{}", + placeholder: `{ "birth": { "$gt": "1950-01-01" } }`, + required: true, + description: "MongoDB Find query.", + }, + + // ---------------------------------- + // insert + // ---------------------------------- + { + displayName: "Fields", + name: "fields", + type: "string", + displayOptions: { + show: { + operation: ["insert"], + }, + }, + default: "", + placeholder: "name,description", + description: + "Comma separated list of the fields to be included into the new document.", + }, + + // ---------------------------------- + // update + // ---------------------------------- + { + displayName: "Update Key", + name: "updateKey", + type: "string", + displayOptions: { + show: { + operation: ["update"], + }, + }, + default: "id", + required: true, + description: + 'Name of the property which decides which rows in the database should be updated. Normally that would be "id".', + }, + { + displayName: "Fields", + name: "fields", + type: "string", + displayOptions: { + show: { + operation: ["update"], + }, + }, + default: "", + placeholder: "name,description", + description: + "Comma separated list of the fields to be included into the new document.", + }, + ], +}; diff --git a/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts b/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts new file mode 100644 index 0000000000..7de725f08a --- /dev/null +++ b/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts @@ -0,0 +1,47 @@ +import { + IDataObject, + INodeExecutionData, + ICredentialDataDecryptedObject, +} from "n8n-workflow"; + +/** + * Standard way of building the MongoDB connection string, unless overridden with a provided string + * + * @param {ICredentialDataDecryptedObject} credentials MongoDB credentials to use, unless conn string is overridden + */ +export function buildParameterizedConnString( + credentials: ICredentialDataDecryptedObject +): string { + if (credentials.port) { + return `mongodb://${credentials.user}:${credentials.password}@${credentials.host}:${credentials.port}`; + } else { + return `mongodb+srv://${credentials.user}:${credentials.password}@${credentials.host}`; + } +} + +/** + * Returns of copy of the items which only contains the json data and + * of that only the define properties + * + * @param {INodeExecutionData[]} items The items to copy + * @param {string[]} properties The properties it should include + * @returns + */ +export function getItemCopy( + items: INodeExecutionData[], + properties: string[] +): IDataObject[] { + // Prepare the data to insert and copy it to be returned + let newItem: IDataObject; + return items.map((item) => { + newItem = {}; + for (const property of properties) { + if (item.json[property] === undefined) { + newItem[property] = null; + } else { + newItem[property] = JSON.parse(JSON.stringify(item.json[property])); + } + } + return newItem; + }); +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index e197bd6ca4..b1a3cbda7b 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -75,7 +75,7 @@ "dist/credentials/MandrillApi.credentials.js", "dist/credentials/MattermostApi.credentials.js", "dist/credentials/MauticApi.credentials.js", - "dist/credentials/MoceanApi.credentials.js", + "dist/credentials/MoceanApi.credentials.js", "dist/credentials/MondayComApi.credentials.js", "dist/credentials/MongoDb.credentials.js", "dist/credentials/Msg91Api.credentials.js", @@ -255,7 +255,7 @@ "@types/jest": "^24.0.18", "@types/lodash.set": "^4.3.6", "@types/moment-timezone": "^0.5.12", - "@types/mongodb": "^3.3.6", + "@types/mongodb": "^3.5.4", "@types/node": "^10.10.1", "@types/nodemailer": "^4.6.5", "@types/redis": "^2.8.11", @@ -284,7 +284,7 @@ "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", "lodash.unset": "^4.5.2", - "mongodb": "^3.3.2", + "mongodb": "^3.5.5", "mysql2": "^2.0.1", "n8n-core": "~0.29.0", "nodemailer": "^5.1.1", From 00fc6e598025022edca6b9461e1d5c9caaf85060 Mon Sep 17 00:00:00 2001 From: Chris Lonardo Date: Mon, 6 Apr 2020 14:04:59 -0400 Subject: [PATCH 2/4] enhance .editorconfig and apply formatting cleanup to last commit --- .editorconfig | 7 + package.json | 40 ++--- .../nodes-base/nodes/MongoDb/MongoDb.node.ts | 64 +++---- .../nodes/MongoDb/mongo.node.options.ts | 156 +++++++++--------- .../nodes/MongoDb/mongo.node.utils.ts | 6 +- 5 files changed, 140 insertions(+), 133 deletions(-) diff --git a/.editorconfig b/.editorconfig index bec7553240..5d02a5688b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,3 +6,10 @@ indent_style = tab end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true + +[package.json] +indent_style = space +indent_size = 2 + +[*.ts] +quote_type = single \ No newline at end of file diff --git a/package.json b/package.json index e14010e7a8..0c7d8dac15 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,22 @@ { - "name": "n8n", - "private": true, - "homepage": "https://n8n.io", - "scripts": { - "bootstrap": "lerna bootstrap --hoist --no-ci", - "build": "lerna exec npm run build", - "dev": "lerna exec npm run dev --parallel", - "clean:dist": "lerna exec -- rimraf ./dist", - "start": "run-script-os", - "start:default": "cd packages/cli/bin && ./n8n", - "start:windows": "cd packages/cli/bin && n8n", - "test": "lerna run test", - "watch": "lerna run --parallel watch" - }, - "devDependencies": { - "lerna": "^3.13.1", - "rimraf": "^3.0.2", - "run-script-os": "^1.0.7" - }, - "postcss": {} + "name": "n8n", + "private": true, + "homepage": "https://n8n.io", + "scripts": { + "bootstrap": "lerna bootstrap --hoist --no-ci", + "build": "lerna exec npm run build", + "dev": "lerna exec npm run dev --parallel", + "clean:dist": "lerna exec -- rimraf ./dist", + "start": "run-script-os", + "start:default": "cd packages/cli/bin && ./n8n", + "start:windows": "cd packages/cli/bin && n8n", + "test": "lerna run test", + "watch": "lerna run --parallel watch" + }, + "devDependencies": { + "lerna": "^3.13.1", + "rimraf": "^3.0.2", + "run-script-os": "^1.0.7" + }, + "postcss": {} } diff --git a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts index d15fd0112e..7dc8daa957 100644 --- a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts +++ b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts @@ -1,41 +1,41 @@ -import { IExecuteFunctions } from "n8n-core"; +import { IExecuteFunctions } from 'n8n-core'; import { IDataObject, INodeExecutionData, INodeType, - INodeTypeDescription, -} from "n8n-workflow"; -import { nodeDescription } from "./mongo.node.options"; -import { MongoClient } from "mongodb"; -import { getItemCopy, buildParameterizedConnString } from "./mongo.node.utils"; + INodeTypeDescription +} from 'n8n-workflow'; +import { nodeDescription } from './mongo.node.options'; +import { MongoClient } from 'mongodb'; +import { getItemCopy, buildParameterizedConnString } from './mongo.node.utils'; export class MongoDb implements INodeType { description: INodeTypeDescription = nodeDescription; async execute(this: IExecuteFunctions): Promise { - const credentials = this.getCredentials("mongoDb"); + const credentials = this.getCredentials('mongoDb'); // user can optionally override connection string const useOverriddenConnString = this.getNodeParameter( - "shouldOverrideConnString", + 'shouldOverrideConnString', 0 ) as boolean; if (credentials === undefined) { - throw new Error("No credentials got returned!"); + throw new Error('No credentials got returned!'); } - let connectionUri = ""; + let connectionUri = ''; if (useOverriddenConnString === true) { const connStringInput = this.getNodeParameter( - "connStringOverrideVal", + 'connStringOverrideVal', 0 ) as string; if (connStringInput && connStringInput.length > 0) { connectionUri = connStringInput; } else { throw new Error( - "Cannot override credentials: valid MongoDB connection string not provided " + 'Cannot override credentials: valid MongoDB connection string not provided ' ); } } else { @@ -44,7 +44,7 @@ export class MongoDb implements INodeType { const client: MongoClient = await MongoClient.connect(connectionUri, { useNewUrlParser: true, - useUnifiedTopology: true, + useUnifiedTopology: true }); const mdb = client.db(credentials.database as string); @@ -52,34 +52,34 @@ export class MongoDb implements INodeType { let returnItems = []; const items = this.getInputData(); - const operation = this.getNodeParameter("operation", 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; - if (operation === "find") { + if (operation === 'find') { // ---------------------------------- // find // ---------------------------------- const queryResult = await mdb - .collection(this.getNodeParameter("collection", 0) as string) - .find(JSON.parse(this.getNodeParameter("query", 0) as string)) + .collection(this.getNodeParameter('collection', 0) as string) + .find(JSON.parse(this.getNodeParameter('query', 0) as string)) .toArray(); returnItems = this.helpers.returnJsonArray(queryResult as IDataObject[]); - } else if (operation === "insert") { + } else if (operation === 'insert') { // ---------------------------------- // insert // ---------------------------------- // Prepare the data to insert and copy it to be returned - const fields = (this.getNodeParameter("fields", 0) as string) - .split(",") - .map((f) => f.trim()) - .filter((f) => !!f); + const fields = (this.getNodeParameter('fields', 0) as string) + .split(',') + .map(f => f.trim()) + .filter(f => !!f); const insertItems = getItemCopy(items, fields); const { insertedIds } = await mdb - .collection(this.getNodeParameter("collection", 0) as string) + .collection(this.getNodeParameter('collection', 0) as string) .insertMany(insertItems); // Add the id to the data @@ -87,21 +87,21 @@ export class MongoDb implements INodeType { returnItems.push({ json: { ...insertItems[parseInt(i, 10)], - id: insertedIds[parseInt(i, 10)] as string, - }, + id: insertedIds[parseInt(i, 10)] as string + } }); } - } else if (operation === "update") { + } else if (operation === 'update') { // ---------------------------------- // update // ---------------------------------- - const fields = (this.getNodeParameter("fields", 0) as string) - .split(",") - .map((f) => f.trim()) - .filter((f) => !!f); + const fields = (this.getNodeParameter('fields', 0) as string) + .split(',') + .map(f => f.trim()) + .filter(f => !!f); - let updateKey = this.getNodeParameter("updateKey", 0) as string; + let updateKey = this.getNodeParameter('updateKey', 0) as string; updateKey = updateKey.trim(); if (!fields.includes(updateKey)) { @@ -120,7 +120,7 @@ export class MongoDb implements INodeType { filter[updateKey] = item[updateKey] as string; await mdb - .collection(this.getNodeParameter("collection", 0) as string) + .collection(this.getNodeParameter('collection', 0) as string) .updateOne(filter, { $set: item }); } diff --git a/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts b/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts index 19f18f4ab7..4a98eeabd5 100644 --- a/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts +++ b/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts @@ -1,156 +1,156 @@ -import { INodeTypeDescription } from "n8n-workflow"; +import { INodeTypeDescription } from 'n8n-workflow'; /** * Options to be displayed */ export const nodeDescription: INodeTypeDescription = { - displayName: "MongoDB", - name: "mongoDb", - icon: "file:mongoDb.png", - group: ["input"], + displayName: 'MongoDB', + name: 'mongoDb', + icon: 'file:mongoDb.png', + group: ['input'], version: 1, - description: "Find, insert and update documents in MongoDB.", + description: 'Find, insert and update documents in MongoDB.', defaults: { - name: "MongoDB", - color: "#13AA52", + name: 'MongoDB', + color: '#13AA52' }, - inputs: ["main"], - outputs: ["main"], + inputs: ['main'], + outputs: ['main'], credentials: [ { - name: "mongoDb", - required: true, - }, + name: 'mongoDb', + required: true + } ], properties: [ { - displayName: "Override conn string", - name: "shouldOverrideConnString", - type: "boolean", + displayName: 'Override conn string', + name: 'shouldOverrideConnString', + type: 'boolean', default: false, description: - "Whether to override the generated connection string. Credentials will also be ignored in this case.", + 'Whether to override the generated connection string. Credentials will also be ignored in this case.' }, { - displayName: "Conn string", - name: "connStringOverrideVal", - type: "string", + displayName: 'Conn string', + name: 'connStringOverrideVal', + type: 'string', typeOptions: { - rows: 1, + rows: 1 }, displayOptions: { show: { - shouldOverrideConnString: [true], - }, + shouldOverrideConnString: [true] + } }, - default: "", + default: '', placeholder: `mongodb://USERNAMEHERE:PASSWORDHERE@localhost:27017/?authSource=admin&readPreference=primary&appname=n8n&ssl=false`, required: false, - description: `If "Override conn string" is checked, the value here will be used as a MongoDB connection string, and the MongoDB credentials will be ignored`, + description: `If "Override conn string" is checked, the value here will be used as a MongoDB connection string, and the MongoDB credentials will be ignored` }, { - displayName: "Operation", - name: "operation", - type: "options", + displayName: 'Operation', + name: 'operation', + type: 'options', options: [ { - name: "Find", - value: "find", - description: "Find documents.", + name: 'Find', + value: 'find', + description: 'Find documents.' }, { - name: "Insert", - value: "insert", - description: "Insert documents.", + name: 'Insert', + value: 'insert', + description: 'Insert documents.' }, { - name: "Update", - value: "update", - description: "Updates documents.", - }, + name: 'Update', + value: 'update', + description: 'Updates documents.' + } ], - default: "find", - description: "The operation to perform.", + default: 'find', + description: 'The operation to perform.' }, { - displayName: "Collection", - name: "collection", - type: "string", + displayName: 'Collection', + name: 'collection', + type: 'string', required: true, - default: "", - description: "MongoDB Collection", + default: '', + description: 'MongoDB Collection' }, // ---------------------------------- // find // ---------------------------------- { - displayName: "Query (JSON format)", - name: "query", - type: "string", + displayName: 'Query (JSON format)', + name: 'query', + type: 'string', typeOptions: { - rows: 5, + rows: 5 }, displayOptions: { show: { - operation: ["find"], - }, + operation: ['find'] + } }, - default: "{}", + default: '{}', placeholder: `{ "birth": { "$gt": "1950-01-01" } }`, required: true, - description: "MongoDB Find query.", + description: 'MongoDB Find query.' }, // ---------------------------------- // insert // ---------------------------------- { - displayName: "Fields", - name: "fields", - type: "string", + displayName: 'Fields', + name: 'fields', + type: 'string', displayOptions: { show: { - operation: ["insert"], - }, + operation: ['insert'] + } }, - default: "", - placeholder: "name,description", + default: '', + placeholder: 'name,description', description: - "Comma separated list of the fields to be included into the new document.", + 'Comma separated list of the fields to be included into the new document.' }, // ---------------------------------- // update // ---------------------------------- { - displayName: "Update Key", - name: "updateKey", - type: "string", + displayName: 'Update Key', + name: 'updateKey', + type: 'string', displayOptions: { show: { - operation: ["update"], - }, + operation: ['update'] + } }, - default: "id", + default: 'id', required: true, description: - 'Name of the property which decides which rows in the database should be updated. Normally that would be "id".', + 'Name of the property which decides which rows in the database should be updated. Normally that would be "id".' }, { - displayName: "Fields", - name: "fields", - type: "string", + displayName: 'Fields', + name: 'fields', + type: 'string', displayOptions: { show: { - operation: ["update"], - }, + operation: ['update'] + } }, - default: "", - placeholder: "name,description", + default: '', + placeholder: 'name,description', description: - "Comma separated list of the fields to be included into the new document.", - }, - ], + 'Comma separated list of the fields to be included into the new document.' + } + ] }; diff --git a/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts b/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts index 7de725f08a..54cae7d53c 100644 --- a/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts +++ b/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts @@ -1,8 +1,8 @@ import { IDataObject, INodeExecutionData, - ICredentialDataDecryptedObject, -} from "n8n-workflow"; + ICredentialDataDecryptedObject +} from 'n8n-workflow'; /** * Standard way of building the MongoDB connection string, unless overridden with a provided string @@ -33,7 +33,7 @@ export function getItemCopy( ): IDataObject[] { // Prepare the data to insert and copy it to be returned let newItem: IDataObject; - return items.map((item) => { + return items.map(item => { newItem = {}; for (const property of properties) { if (item.json[property] === undefined) { From d80deda34fc8271f08256cba8c0bf5928c5691fd Mon Sep 17 00:00:00 2001 From: Chris Lonardo Date: Mon, 6 Apr 2020 21:07:54 -0400 Subject: [PATCH 3/4] Moved MongoDb connection string override into credentials (#1) * interim work on credentials refactor * move conn string override for mongodb into credentials and enhance credentials typings --- .../credentials/MongoDb.credentials.ts | 39 +++++++++--- .../nodes-base/nodes/MongoDb/MongoDb.node.ts | 40 +++--------- .../nodes/MongoDb/mongo.node.options.ts | 25 -------- .../nodes/MongoDb/mongo.node.types.ts | 52 ++++++++++++++++ .../nodes/MongoDb/mongo.node.utils.ts | 61 ++++++++++++++++++- 5 files changed, 149 insertions(+), 68 deletions(-) create mode 100644 packages/nodes-base/nodes/MongoDb/mongo.node.types.ts diff --git a/packages/nodes-base/credentials/MongoDb.credentials.ts b/packages/nodes-base/credentials/MongoDb.credentials.ts index b39c7016de..436e9f2e4f 100644 --- a/packages/nodes-base/credentials/MongoDb.credentials.ts +++ b/packages/nodes-base/credentials/MongoDb.credentials.ts @@ -1,8 +1,4 @@ -import { - ICredentialType, - NodePropertyTypes, -} from 'n8n-workflow'; - +import { ICredentialType, NodePropertyTypes } from 'n8n-workflow'; export class MongoDb implements ICredentialType { name = 'mongoDb'; @@ -12,34 +8,57 @@ export class MongoDb implements ICredentialType { displayName: 'Host', name: 'host', type: 'string' as NodePropertyTypes, - default: 'localhost', + default: 'localhost' }, { displayName: 'Database', name: 'database', type: 'string' as NodePropertyTypes, default: '', + description: + 'Note: the database should still be provided even if using an override connection string' }, { displayName: 'User', name: 'user', type: 'string' as NodePropertyTypes, - default: '', + default: '' }, { displayName: 'Password', name: 'password', type: 'string' as NodePropertyTypes, typeOptions: { - password: true, + password: true }, - default: '', + default: '' }, { displayName: 'Port', name: 'port', type: 'number' as NodePropertyTypes, - default: 27017, + default: 27017 }, + { + displayName: 'Override conn string', + name: 'shouldOverrideConnString', + type: 'boolean' as NodePropertyTypes, + default: false, + required: false, + description: + 'Whether to override the generated connection string. Credentials will also be ignored in this case.' + }, + { + displayName: 'Conn string override', + name: 'connStringOverrideVal', + type: 'string' as NodePropertyTypes, + typeOptions: { + rows: 1 + }, + default: '', + placeholder: `mongodb://USERNAMEHERE:PASSWORDHERE@localhost:27017/?authSource=admin&readPreference=primary&appname=n8n&ssl=false`, + required: false, + description: `If provided, the value here will be used as a MongoDB connection string, and the MongoDB credentials will be ignored` + } ]; } diff --git a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts index 7dc8daa957..dbd7ab2929 100644 --- a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts +++ b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts @@ -7,47 +7,25 @@ import { } from 'n8n-workflow'; import { nodeDescription } from './mongo.node.options'; import { MongoClient } from 'mongodb'; -import { getItemCopy, buildParameterizedConnString } from './mongo.node.utils'; +import { + getItemCopy, + validateAndResolveMongoCredentials +} from './mongo.node.utils'; export class MongoDb implements INodeType { description: INodeTypeDescription = nodeDescription; async execute(this: IExecuteFunctions): Promise { - const credentials = this.getCredentials('mongoDb'); + const { database, connectionString } = validateAndResolveMongoCredentials( + this.getCredentials('mongoDb') + ); - // user can optionally override connection string - const useOverriddenConnString = this.getNodeParameter( - 'shouldOverrideConnString', - 0 - ) as boolean; - - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } - - let connectionUri = ''; - if (useOverriddenConnString === true) { - const connStringInput = this.getNodeParameter( - 'connStringOverrideVal', - 0 - ) as string; - if (connStringInput && connStringInput.length > 0) { - connectionUri = connStringInput; - } else { - throw new Error( - 'Cannot override credentials: valid MongoDB connection string not provided ' - ); - } - } else { - connectionUri = buildParameterizedConnString(credentials); - } - - const client: MongoClient = await MongoClient.connect(connectionUri, { + const client: MongoClient = await MongoClient.connect(connectionString, { useNewUrlParser: true, useUnifiedTopology: true }); - const mdb = client.db(credentials.database as string); + const mdb = client.db(database as string); let returnItems = []; diff --git a/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts b/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts index 4a98eeabd5..190ff3de88 100644 --- a/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts +++ b/packages/nodes-base/nodes/MongoDb/mongo.node.options.ts @@ -23,31 +23,6 @@ export const nodeDescription: INodeTypeDescription = { } ], properties: [ - { - displayName: 'Override conn string', - name: 'shouldOverrideConnString', - type: 'boolean', - default: false, - description: - 'Whether to override the generated connection string. Credentials will also be ignored in this case.' - }, - { - displayName: 'Conn string', - name: 'connStringOverrideVal', - type: 'string', - typeOptions: { - rows: 1 - }, - displayOptions: { - show: { - shouldOverrideConnString: [true] - } - }, - default: '', - placeholder: `mongodb://USERNAMEHERE:PASSWORDHERE@localhost:27017/?authSource=admin&readPreference=primary&appname=n8n&ssl=false`, - required: false, - description: `If "Override conn string" is checked, the value here will be used as a MongoDB connection string, and the MongoDB credentials will be ignored` - }, { displayName: 'Operation', name: 'operation', diff --git a/packages/nodes-base/nodes/MongoDb/mongo.node.types.ts b/packages/nodes-base/nodes/MongoDb/mongo.node.types.ts new file mode 100644 index 0000000000..2995fdd2a2 --- /dev/null +++ b/packages/nodes-base/nodes/MongoDb/mongo.node.types.ts @@ -0,0 +1,52 @@ +import { CredentialInformation } from 'n8n-workflow'; + +/** + * Credentials object for Mongo, if using individual parameters + */ +export interface IMongoParametricCredentials { + /** + * Whether to allow overriding the parametric credentials with a connection string + */ + shouldOverrideConnString: false | undefined | null; + host: string; + database: string; + user: string; + password: string; + port?: number; +} + +/** + * Credentials object for Mongo, if using override connection string + */ +export interface IMongoOverrideCredentials { + /** + * Whether to allow overriding the parametric credentials with a connection string + */ + shouldOverrideConnString: true; + /** + * If using an override connection string, this is where it will be. + */ + connStringOverrideVal: string; + database: string; +} + +/** + * Unified credential object type (whether params are overridden with a connection string or not) + */ +export type IMongoCredentialsType = + | IMongoParametricCredentials + | IMongoOverrideCredentials; + +/** + * Resolve the database and connection string from input credentials + */ +export type IMongoCredentials = { + /** + * Database name (used to create the Mongo client) + */ + database: string; + /** + * Generated connection string (after validating and figuring out overrides) + */ + connectionString: string; +}; diff --git a/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts b/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts index 54cae7d53c..1d682a450a 100644 --- a/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts +++ b/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts @@ -3,14 +3,19 @@ import { INodeExecutionData, ICredentialDataDecryptedObject } from 'n8n-workflow'; +import { + IMongoCredentialsType, + IMongoParametricCredentials, + IMongoCredentials +} from './mongo.node.types'; /** * Standard way of building the MongoDB connection string, unless overridden with a provided string * * @param {ICredentialDataDecryptedObject} credentials MongoDB credentials to use, unless conn string is overridden */ -export function buildParameterizedConnString( - credentials: ICredentialDataDecryptedObject +function buildParameterizedConnString( + credentials: IMongoParametricCredentials ): string { if (credentials.port) { return `mongodb://${credentials.user}:${credentials.password}@${credentials.host}:${credentials.port}`; @@ -19,6 +24,58 @@ export function buildParameterizedConnString( } } +/** + * Build mongoDb connection string and resolve database name. + * If a connection string override value is provided, that will be used in place of individual args + * + * @param {ICredentialDataDecryptedObject} credentials raw/input MongoDB credentials to use + */ +function buildMongoConnectionParams( + credentials: IMongoCredentialsType +): IMongoCredentials { + const sanitizedDbName = + credentials.database && credentials.database.trim().length > 0 + ? credentials.database.trim() + : ''; + if (credentials.shouldOverrideConnString) { + if ( + credentials.connStringOverrideVal && + credentials.connStringOverrideVal.trim().length > 0 + ) { + return { + connectionString: credentials.connStringOverrideVal.trim(), + database: sanitizedDbName + }; + } else { + throw new Error( + 'Cannot override credentials: valid MongoDB connection string not provided ' + ); + } + } else { + return { + connectionString: buildParameterizedConnString(credentials), + database: sanitizedDbName + }; + } +} + +/** + * Verify credentials. If ok, build mongoDb connection string and resolve database name. + * + * @param {ICredentialDataDecryptedObject} credentials raw/input MongoDB credentials to use + */ +export function validateAndResolveMongoCredentials( + credentials?: ICredentialDataDecryptedObject +): IMongoCredentials { + if (credentials == undefined) { + throw new Error('No credentials got returned!'); + } else { + return buildMongoConnectionParams( + (credentials as any) as IMongoCredentialsType + ); + } +} + /** * Returns of copy of the items which only contains the json data and * of that only the define properties From 384efade9fd7150c8a1da55f029a5b1944b6fd5a Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Fri, 1 May 2020 20:24:20 +0200 Subject: [PATCH 4/4] :zap: Small improvements to Mongo connection string support --- .../src/components/CredentialsInput.vue | 35 +++++--- .../credentials/MongoDb.credentials.ts | 88 ++++++++++++++----- .../nodes/MongoDb/mongo.node.types.ts | 7 +- .../nodes/MongoDb/mongo.node.utils.ts | 12 +-- 4 files changed, 98 insertions(+), 44 deletions(-) diff --git a/packages/editor-ui/src/components/CredentialsInput.vue b/packages/editor-ui/src/components/CredentialsInput.vue index 0830b2a2a0..064d88fc71 100644 --- a/packages/editor-ui/src/components/CredentialsInput.vue +++ b/packages/editor-ui/src/components/CredentialsInput.vue @@ -21,18 +21,20 @@ - - - {{parameter.displayName}}: - -
- -
-
- - - -
+ + + + {{parameter.displayName}}: + +
+ +
+
+ + + +
+
@@ -85,6 +87,7 @@ import { ICredentialType, ICredentialNodeAccess, INodeCredentialDescription, + INodeProperties, INodeTypeDescription, } from 'n8n-workflow'; @@ -162,6 +165,14 @@ export default mixins( tempValue[name] = parameterData.value; Vue.set(this, 'propertyValue', tempValue); }, + displayCredentialParameter (parameter: INodeProperties): boolean { + if (parameter.displayOptions === undefined) { + // If it is not defined no need to do a proper check + return true; + } + + return this.displayParameter(this.propertyValue, parameter, ''); + }, async createCredentials (): Promise { const nodesAccess = this.nodesAccess.map((nodeType) => { return { diff --git a/packages/nodes-base/credentials/MongoDb.credentials.ts b/packages/nodes-base/credentials/MongoDb.credentials.ts index 436e9f2e4f..a6de302172 100644 --- a/packages/nodes-base/credentials/MongoDb.credentials.ts +++ b/packages/nodes-base/credentials/MongoDb.credentials.ts @@ -4,10 +4,53 @@ export class MongoDb implements ICredentialType { name = 'mongoDb'; displayName = 'MongoDB'; properties = [ + { + displayName: 'Configuration Type', + name: 'configurationType', + type: 'options' as NodePropertyTypes, + options: [ + { + name: 'Connection String', + value: 'connectionString', + description: 'Provide connection data via string', + }, + { + name: 'Values', + value: 'values', + description: 'Provide connection data via values', + }, + ], + default: 'values', + description: 'The operation to perform.', + }, + { + displayName: 'Connection String', + name: 'connectionString', + type: 'string' as NodePropertyTypes, + displayOptions: { + show: { + configurationType: [ + 'connectionString', + ], + }, + }, + default: '', + placeholder: 'mongodb://:@localhost:27017/?authSource=admin&readPreference=primary&appname=n8n&ssl=false', + required: false, + description: `If provided, the value here will be used as a MongoDB connection string,
+ and the MongoDB credentials will be ignored` + }, { displayName: 'Host', name: 'host', type: 'string' as NodePropertyTypes, + displayOptions: { + show: { + configurationType: [ + 'values', + ], + }, + }, default: 'localhost' }, { @@ -15,13 +58,19 @@ export class MongoDb implements ICredentialType { name: 'database', type: 'string' as NodePropertyTypes, default: '', - description: - 'Note: the database should still be provided even if using an override connection string' + description: 'Note: the database should still be provided even if using an override connection string' }, { displayName: 'User', name: 'user', type: 'string' as NodePropertyTypes, + displayOptions: { + show: { + configurationType: [ + 'values', + ], + }, + }, default: '' }, { @@ -31,34 +80,27 @@ export class MongoDb implements ICredentialType { typeOptions: { password: true }, + displayOptions: { + show: { + configurationType: [ + 'values', + ], + }, + }, default: '' }, { displayName: 'Port', name: 'port', type: 'number' as NodePropertyTypes, + displayOptions: { + show: { + configurationType: [ + 'values', + ], + }, + }, default: 27017 }, - { - displayName: 'Override conn string', - name: 'shouldOverrideConnString', - type: 'boolean' as NodePropertyTypes, - default: false, - required: false, - description: - 'Whether to override the generated connection string. Credentials will also be ignored in this case.' - }, - { - displayName: 'Conn string override', - name: 'connStringOverrideVal', - type: 'string' as NodePropertyTypes, - typeOptions: { - rows: 1 - }, - default: '', - placeholder: `mongodb://USERNAMEHERE:PASSWORDHERE@localhost:27017/?authSource=admin&readPreference=primary&appname=n8n&ssl=false`, - required: false, - description: `If provided, the value here will be used as a MongoDB connection string, and the MongoDB credentials will be ignored` - } ]; } diff --git a/packages/nodes-base/nodes/MongoDb/mongo.node.types.ts b/packages/nodes-base/nodes/MongoDb/mongo.node.types.ts index 2995fdd2a2..212ebc159d 100644 --- a/packages/nodes-base/nodes/MongoDb/mongo.node.types.ts +++ b/packages/nodes-base/nodes/MongoDb/mongo.node.types.ts @@ -7,7 +7,8 @@ export interface IMongoParametricCredentials { /** * Whether to allow overriding the parametric credentials with a connection string */ - shouldOverrideConnString: false | undefined | null; + configurationType: 'values'; + host: string; database: string; user: string; @@ -22,11 +23,11 @@ export interface IMongoOverrideCredentials { /** * Whether to allow overriding the parametric credentials with a connection string */ - shouldOverrideConnString: true; + configurationType: 'connectionString'; /** * If using an override connection string, this is where it will be. */ - connStringOverrideVal: string; + connectionString: string; database: string; } diff --git a/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts b/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts index 1d682a450a..17ccc0dc4a 100644 --- a/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts +++ b/packages/nodes-base/nodes/MongoDb/mongo.node.utils.ts @@ -37,13 +37,13 @@ function buildMongoConnectionParams( credentials.database && credentials.database.trim().length > 0 ? credentials.database.trim() : ''; - if (credentials.shouldOverrideConnString) { + if (credentials.configurationType === 'connectionString') { if ( - credentials.connStringOverrideVal && - credentials.connStringOverrideVal.trim().length > 0 + credentials.connectionString && + credentials.connectionString.trim().length > 0 ) { return { - connectionString: credentials.connStringOverrideVal.trim(), + connectionString: credentials.connectionString.trim(), database: sanitizedDbName }; } else { @@ -67,11 +67,11 @@ function buildMongoConnectionParams( export function validateAndResolveMongoCredentials( credentials?: ICredentialDataDecryptedObject ): IMongoCredentials { - if (credentials == undefined) { + if (credentials === undefined) { throw new Error('No credentials got returned!'); } else { return buildMongoConnectionParams( - (credentials as any) as IMongoCredentialsType + credentials as unknown as IMongoCredentialsType, ); } }