🔀 Merge branch 'clonardo-mongodb_updates'

This commit is contained in:
Jan Oberhauser 2020-05-01 20:25:26 +02:00
commit 069228f00a
11 changed files with 412 additions and 208 deletions

View file

@ -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

View file

@ -6,6 +6,7 @@
"bootstrap": "lerna bootstrap --hoist --no-ci",
"build": "lerna exec npm run build",
"dev": "lerna exec npm run dev --parallel",
"clean:dist": "lerna exec -- rimraf ./dist",
"start": "run-script-os",
"start:default": "cd packages/cli/bin && ./n8n",
"start:windows": "cd packages/cli/bin && n8n",
@ -14,6 +15,7 @@
},
"devDependencies": {
"lerna": "^3.13.1",
"rimraf": "^3.0.2",
"run-script-os": "^1.0.7"
},
"postcss": {}

View file

@ -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.32.0",
"n8n-editor-ui": "~0.43.0",

View file

@ -21,18 +21,20 @@
<font-awesome-icon icon="question-circle" />
</el-tooltip>
</div>
<el-row v-for="parameter in credentialTypeData.properties" :key="parameter.name" class="parameter-wrapper">
<el-col :span="6" class="parameter-name">
{{parameter.displayName}}:
<el-tooltip placement="top" class="parameter-info" v-if="parameter.description" effect="light">
<div slot="content" v-html="parameter.description"></div>
<font-awesome-icon icon="question-circle"/>
</el-tooltip>
</el-col>
<el-col :span="18">
<parameter-input :parameter="parameter" :value="propertyValue[parameter.name]" :path="parameter.name" :isCredential="true" @valueChanged="valueChanged" />
</el-col>
</el-row>
<span v-for="parameter in credentialTypeData.properties" :key="parameter.name">
<el-row v-if="displayCredentialParameter(parameter)" class="parameter-wrapper">
<el-col :span="6" class="parameter-name">
{{parameter.displayName}}:
<el-tooltip placement="top" class="parameter-info" v-if="parameter.description" effect="light">
<div slot="content" v-html="parameter.description"></div>
<font-awesome-icon icon="question-circle"/>
</el-tooltip>
</el-col>
<el-col :span="18">
<parameter-input :parameter="parameter" :value="propertyValue[parameter.name]" :path="parameter.name" :isCredential="true" @valueChanged="valueChanged" />
</el-col>
</el-row>
</span>
<el-row class="nodes-access-wrapper">
<el-col :span="6" class="headline">
@ -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<void> {
const nodesAccess = this.nodesAccess.map((nodeType) => {
return {

View file

@ -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
};

View file

@ -1,45 +1,106 @@
import {
ICredentialType,
NodePropertyTypes,
} from 'n8n-workflow';
import { ICredentialType, NodePropertyTypes } from 'n8n-workflow';
export class MongoDb implements ICredentialType {
name = 'mongoDb';
displayName = 'MongoDB';
properties = [
{
displayName: 'Configuration Type',
name: 'configurationType',
type: 'options' as NodePropertyTypes,
options: [
{
name: 'Connection String',
value: 'connectionString',
description: 'Provide connection data via string',
},
{
name: 'Values',
value: 'values',
description: 'Provide connection data via values',
},
],
default: 'values',
description: 'The operation to perform.',
},
{
displayName: 'Connection String',
name: 'connectionString',
type: 'string' as NodePropertyTypes,
displayOptions: {
show: {
configurationType: [
'connectionString',
],
},
},
default: '',
placeholder: 'mongodb://<USERNAME>:<PASSWORD>@localhost:27017/?authSource=admin&readPreference=primary&appname=n8n&ssl=false',
required: false,
description: `If provided, the value here will be used as a MongoDB connection string,<br />
and the MongoDB credentials will be ignored`
},
{
displayName: 'Host',
name: 'host',
type: 'string' as NodePropertyTypes,
default: 'localhost',
displayOptions: {
show: {
configurationType: [
'values',
],
},
},
default: 'localhost'
},
{
displayName: 'Database',
name: 'database',
type: 'string' as NodePropertyTypes,
default: '',
description: 'Note: the database should still be provided even if using an override connection string'
},
{
displayName: 'User',
name: 'user',
type: 'string' as NodePropertyTypes,
default: '',
displayOptions: {
show: {
configurationType: [
'values',
],
},
},
default: ''
},
{
displayName: 'Password',
name: 'password',
type: 'string' as NodePropertyTypes,
typeOptions: {
password: true,
password: true
},
default: '',
displayOptions: {
show: {
configurationType: [
'values',
],
},
},
default: ''
},
{
displayName: 'Port',
name: 'port',
type: 'number' as NodePropertyTypes,
default: 27017,
displayOptions: {
show: {
configurationType: [
'values',
],
},
},
default: 27017
},
];
}

View file

@ -3,192 +3,29 @@ import {
IDataObject,
INodeExecutionData,
INodeType,
INodeTypeDescription,
INodeTypeDescription
} from 'n8n-workflow';
import { nodeDescription } from './mongo.node.options';
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;
});
}
import {
getItemCopy,
validateAndResolveMongoCredentials
} 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<INodeExecutionData[][]> {
const { database, connectionString } = validateAndResolveMongoCredentials(
this.getCredentials('mongoDb')
);
const credentials = this.getCredentials('mongoDb');
const client: MongoClient = await MongoClient.connect(connectionString, {
useNewUrlParser: true,
useUnifiedTopology: true
});
if (credentials === undefined) {
throw new Error('No credentials got returned!');
}
let connectionUri = '';
if (credentials.port) {
connectionUri = `mongodb://${credentials.user}:${credentials.password}@${credentials.host}:${credentials.port}`;
} else {
connectionUri = `mongodb+srv://${credentials.user}:${credentials.password}@${credentials.host}`;
}
const client = await MongoClient.connect(connectionUri, { useNewUrlParser: true, useUnifiedTopology: true });
const mdb = client.db(credentials.database as string);
const mdb = client.db(database as string);
let returnItems = [];
@ -206,7 +43,6 @@ export class MongoDb implements INodeType {
.toArray();
returnItems = this.helpers.returnJsonArray(queryResult as IDataObject[]);
} else if (operation === 'insert') {
// ----------------------------------
// insert
@ -229,7 +65,7 @@ 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
}
});
}
@ -258,7 +94,7 @@ export class MongoDb implements INodeType {
continue;
}
const filter: { [key: string] :string } = {};
const filter: { [key: string]: string } = {};
filter[updateKey] = item[updateKey] as string;
await mdb
@ -267,7 +103,6 @@ export class MongoDb implements INodeType {
}
returnItems = this.helpers.returnJsonArray(updateItems as IDataObject[]);
} else {
throw new Error(`The operation "${operation}" is not supported!`);
}

View file

@ -0,0 +1,131 @@
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: '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.'
}
]
};

View file

@ -0,0 +1,53 @@
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
*/
configurationType: 'values';
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
*/
configurationType: 'connectionString';
/**
* If using an override connection string, this is where it will be.
*/
connectionString: 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;
};

View file

@ -0,0 +1,104 @@
import {
IDataObject,
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
*/
function buildParameterizedConnString(
credentials: IMongoParametricCredentials
): string {
if (credentials.port) {
return `mongodb://${credentials.user}:${credentials.password}@${credentials.host}:${credentials.port}`;
} else {
return `mongodb+srv://${credentials.user}:${credentials.password}@${credentials.host}`;
}
}
/**
* 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.configurationType === 'connectionString') {
if (
credentials.connectionString &&
credentials.connectionString.trim().length > 0
) {
return {
connectionString: credentials.connectionString.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 unknown as IMongoCredentialsType,
);
}
}
/**
* 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;
});
}

View file

@ -261,7 +261,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",
@ -292,7 +292,7 @@
"lodash.unset": "^4.5.2",
"moment": "2.24.0",
"moment-timezone": "^0.5.28",
"mongodb": "^3.3.2",
"mongodb": "^3.5.5",
"mysql2": "^2.0.1",
"n8n-core": "~0.32.0",
"nodemailer": "^5.1.1",