From 9ca990b9936ee80972952d0a1ad73c2926809ba2 Mon Sep 17 00:00:00 2001 From: Marcus <56945030+maspio@users.noreply.github.com> Date: Tue, 29 Aug 2023 17:44:37 +0200 Subject: [PATCH] fix(MongoDB Node): Stringify response ObjectIDs (#6990) Github issue / Community forum post (link here to close automatically): --- .../nodes/MongoDb/GenericFunctions.ts | 17 +- .../nodes-base/nodes/MongoDb/MongoDb.node.ts | 62 ++-- .../nodes/MongoDb/MongoDbDescription.ts | 286 ------------------ .../nodes/MongoDb/MongoDbProperties.ts | 262 ++++++++++++++++ 4 files changed, 320 insertions(+), 307 deletions(-) delete mode 100644 packages/nodes-base/nodes/MongoDb/MongoDbDescription.ts create mode 100644 packages/nodes-base/nodes/MongoDb/MongoDbProperties.ts diff --git a/packages/nodes-base/nodes/MongoDb/GenericFunctions.ts b/packages/nodes-base/nodes/MongoDb/GenericFunctions.ts index 2d485c2fea..1be1332537 100644 --- a/packages/nodes-base/nodes/MongoDb/GenericFunctions.ts +++ b/packages/nodes-base/nodes/MongoDb/GenericFunctions.ts @@ -6,15 +6,15 @@ import type { } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; +import get from 'lodash/get'; +import set from 'lodash/set'; +import { ObjectId } from 'mongodb'; import type { IMongoCredentials, IMongoCredentialsType, IMongoParametricCredentials, } from './mongoDb.types'; -import get from 'lodash/get'; -import set from 'lodash/set'; - /** * Standard way of building the MongoDB connection string, unless overridden with a provided string * @@ -129,3 +129,14 @@ export function prepareFields(fields: string) { .map((field) => field.trim()) .filter((field) => !!field); } + +export function stringifyObjectIDs(items: IDataObject[]) { + items.forEach((item) => { + if (item._id instanceof ObjectId) { + item._id = item._id.toString(); + } + if (item.id instanceof ObjectId) { + item.id = item.id.toString(); + } + }); +} diff --git a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts index 5bf7c723d2..cd7b72798b 100644 --- a/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts +++ b/packages/nodes-base/nodes/MongoDb/MongoDb.node.ts @@ -11,15 +11,6 @@ import type { } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; -import { nodeDescription } from './MongoDbDescription'; - -import { - buildParameterizedConnString, - prepareFields, - prepareItems, - validateAndResolveMongoCredentials, -} from './GenericFunctions'; - import type { FindOneAndReplaceOptions, FindOneAndUpdateOptions, @@ -27,11 +18,40 @@ import type { Sort, } from 'mongodb'; import { MongoClient, ObjectId } from 'mongodb'; +import { nodeProperties } from './MongoDbProperties'; + +import { + buildParameterizedConnString, + prepareFields, + prepareItems, + stringifyObjectIDs, + validateAndResolveMongoCredentials, +} from './GenericFunctions'; import type { IMongoParametricCredentials } from './mongoDb.types'; export class MongoDb implements INodeType { - description: INodeTypeDescription = nodeDescription; + description: INodeTypeDescription = { + displayName: 'MongoDB', + name: 'mongoDb', + icon: 'file:mongodb.svg', + group: ['input'], + version: 1, + description: 'Find, insert and update documents in MongoDB', + defaults: { + name: 'MongoDB', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'mongoDb', + required: true, + testedBy: 'mongoDbCredentialTest', + }, + ], + properties: nodeProperties, + }; methods = { credentialTest: { @@ -65,7 +85,7 @@ export class MongoDb implements INodeType { } catch (error) { return { status: 'Error', - message: error.message, + message: (error as Error).message, }; } return { @@ -98,15 +118,17 @@ export class MongoDb implements INodeType { // ---------------------------------- try { - const queryParameter = JSON.parse(this.getNodeParameter('query', 0) as string); + const queryParameter = JSON.parse( + this.getNodeParameter('query', 0) as string, + ) as IDataObject; if (queryParameter._id && typeof queryParameter._id === 'string') { - queryParameter._id = new ObjectId(queryParameter._id as string); + queryParameter._id = new ObjectId(queryParameter._id); } const query = mdb .collection(this.getNodeParameter('collection', 0) as string) - .aggregate(queryParameter as Document[]); + .aggregate(queryParameter as unknown as Document[]); responseData = await query.toArray(); } catch (error) { @@ -140,20 +162,22 @@ export class MongoDb implements INodeType { // ---------------------------------- try { - const queryParameter = JSON.parse(this.getNodeParameter('query', 0) as string); + const queryParameter = JSON.parse( + this.getNodeParameter('query', 0) as string, + ) as IDataObject; if (queryParameter._id && typeof queryParameter._id === 'string') { - queryParameter._id = new ObjectId(queryParameter._id as string); + queryParameter._id = new ObjectId(queryParameter._id); } let query = mdb .collection(this.getNodeParameter('collection', 0) as string) - .find(queryParameter as Document); + .find(queryParameter as unknown as Document); const options = this.getNodeParameter('options', 0); const limit = options.limit as number; const skip = options.skip as number; - const sort: Sort = options.sort && JSON.parse(options.sort as string); + const sort = options.sort && (JSON.parse(options.sort as string) as Sort); if (skip > 0) { query = query.skip(skip); } @@ -339,6 +363,8 @@ export class MongoDb implements INodeType { await client.close(); + stringifyObjectIDs(responseData); + const executionData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray(responseData), { itemData: { item: 0 } }, diff --git a/packages/nodes-base/nodes/MongoDb/MongoDbDescription.ts b/packages/nodes-base/nodes/MongoDb/MongoDbDescription.ts deleted file mode 100644 index c113f702e3..0000000000 --- a/packages/nodes-base/nodes/MongoDb/MongoDbDescription.ts +++ /dev/null @@ -1,286 +0,0 @@ -/* eslint-disable n8n-nodes-base/node-filename-against-convention */ -import type { INodeTypeDescription } from 'n8n-workflow'; - -/** - * Options to be displayed - */ -export const nodeDescription: INodeTypeDescription = { - displayName: 'MongoDB', - name: 'mongoDb', - icon: 'file:mongodb.svg', - group: ['input'], - version: 1, - description: 'Find, insert and update documents in MongoDB', - defaults: { - name: 'MongoDB', - }, - inputs: ['main'], - outputs: ['main'], - credentials: [ - { - name: 'mongoDb', - required: true, - testedBy: 'mongoDbCredentialTest', - }, - ], - properties: [ - { - displayName: 'Operation', - name: 'operation', - type: 'options', - noDataExpression: true, - options: [ - { - name: 'Aggregate', - value: 'aggregate', - description: 'Aggregate documents', - action: 'Aggregate documents', - }, - { - name: 'Delete', - value: 'delete', - description: 'Delete documents', - action: 'Delete documents', - }, - { - name: 'Find', - value: 'find', - description: 'Find documents', - action: 'Find documents', - }, - { - name: 'Find And Replace', - value: 'findOneAndReplace', - description: 'Find and replace documents', - action: 'Find and replace documents', - }, - { - name: 'Find And Update', - value: 'findOneAndUpdate', - description: 'Find and update documents', - action: 'Find and update documents', - }, - { - name: 'Insert', - value: 'insert', - description: 'Insert documents', - action: 'Insert documents', - }, - { - name: 'Update', - value: 'update', - description: 'Update documents', - action: 'Update documents', - }, - ], - default: 'find', - }, - - { - displayName: 'Collection', - name: 'collection', - type: 'string', - required: true, - default: '', - description: 'MongoDB Collection', - }, - - // ---------------------------------- - // aggregate - // ---------------------------------- - { - displayName: 'Query', - name: 'query', - type: 'json', - typeOptions: { - alwaysOpenEditWindow: true, - }, - displayOptions: { - show: { - operation: ['aggregate'], - }, - }, - default: '', - placeholder: '[{ "$match": { "$gt": "1950-01-01" }, ... }]', - hint: 'Learn more about aggregation pipeline here', - required: true, - description: 'MongoDB aggregation pipeline query in JSON format', - }, - - // ---------------------------------- - // delete - // ---------------------------------- - { - displayName: 'Delete Query (JSON Format)', - name: 'query', - type: 'json', - typeOptions: { - rows: 5, - }, - displayOptions: { - show: { - operation: ['delete'], - }, - }, - default: '{}', - placeholder: '{ "birth": { "$gt": "1950-01-01" } }', - required: true, - description: 'MongoDB Delete query', - }, - - // ---------------------------------- - // find - // ---------------------------------- - { - displayName: 'Options', - name: 'options', - type: 'collection', - displayOptions: { - show: { - operation: ['find'], - }, - }, - default: {}, - placeholder: 'Add options', - description: 'Add query options', - options: [ - { - displayName: 'Limit', - name: 'limit', - type: 'number', - typeOptions: { - minValue: 1, - }, - default: 0, - // eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-limit - description: - 'Use limit to specify the maximum number of documents or 0 for unlimited documents', - }, - { - displayName: 'Skip', - name: 'skip', - type: 'number', - default: 0, - description: 'The number of documents to skip in the results set', - }, - { - displayName: 'Sort (JSON Format)', - name: 'sort', - type: 'json', - typeOptions: { - rows: 2, - }, - default: '{}', - placeholder: '{ "field": -1 }', - description: 'A JSON that defines the sort order of the result set', - }, - ], - }, - { - displayName: 'Query (JSON Format)', - name: 'query', - type: 'json', - 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', 'findOneAndReplace', 'findOneAndUpdate'], - }, - }, - default: 'id', - required: true, - // eslint-disable-next-line n8n-nodes-base/node-param-description-miscased-id - 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', 'findOneAndReplace', 'findOneAndUpdate'], - }, - }, - default: '', - placeholder: 'name,description', - description: 'Comma-separated list of the fields to be included into the new document', - }, - { - displayName: 'Upsert', - name: 'upsert', - type: 'boolean', - displayOptions: { - show: { - operation: ['update', 'findOneAndReplace', 'findOneAndUpdate'], - }, - }, - default: false, - description: 'Whether to perform an insert if no documents match the update key', - }, - { - displayName: 'Options', - name: 'options', - type: 'collection', - displayOptions: { - show: { - operation: ['update', 'insert', 'findOneAndReplace', 'findOneAndUpdate'], - }, - }, - placeholder: 'Add Option', - default: {}, - options: [ - { - displayName: 'Date Fields', - name: 'dateFields', - type: 'string', - default: '', - description: 'Comma separeted list of fields that will be parse as Mongo Date type', - }, - { - displayName: 'Use Dot Notation', - name: 'useDotNotation', - type: 'boolean', - default: false, - description: 'Whether to use dot notation to access date fields', - }, - ], - }, - ], -}; diff --git a/packages/nodes-base/nodes/MongoDb/MongoDbProperties.ts b/packages/nodes-base/nodes/MongoDb/MongoDbProperties.ts new file mode 100644 index 0000000000..467f019e03 --- /dev/null +++ b/packages/nodes-base/nodes/MongoDb/MongoDbProperties.ts @@ -0,0 +1,262 @@ +import type { INodeProperties } from 'n8n-workflow'; + +export const nodeProperties: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'Aggregate', + value: 'aggregate', + description: 'Aggregate documents', + action: 'Aggregate documents', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete documents', + action: 'Delete documents', + }, + { + name: 'Find', + value: 'find', + description: 'Find documents', + action: 'Find documents', + }, + { + name: 'Find And Replace', + value: 'findOneAndReplace', + description: 'Find and replace documents', + action: 'Find and replace documents', + }, + { + name: 'Find And Update', + value: 'findOneAndUpdate', + description: 'Find and update documents', + action: 'Find and update documents', + }, + { + name: 'Insert', + value: 'insert', + description: 'Insert documents', + action: 'Insert documents', + }, + { + name: 'Update', + value: 'update', + description: 'Update documents', + action: 'Update documents', + }, + ], + default: 'find', + }, + + { + displayName: 'Collection', + name: 'collection', + type: 'string', + required: true, + default: '', + description: 'MongoDB Collection', + }, + + // ---------------------------------- + // aggregate + // ---------------------------------- + { + displayName: 'Query', + name: 'query', + type: 'json', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + operation: ['aggregate'], + }, + }, + default: '', + placeholder: '[{ "$match": { "$gt": "1950-01-01" }, ... }]', + hint: 'Learn more about aggregation pipeline here', + required: true, + description: 'MongoDB aggregation pipeline query in JSON format', + }, + + // ---------------------------------- + // delete + // ---------------------------------- + { + displayName: 'Delete Query (JSON Format)', + name: 'query', + type: 'json', + typeOptions: { + rows: 5, + }, + displayOptions: { + show: { + operation: ['delete'], + }, + }, + default: '{}', + placeholder: '{ "birth": { "$gt": "1950-01-01" } }', + required: true, + description: 'MongoDB Delete query', + }, + + // ---------------------------------- + // find + // ---------------------------------- + { + displayName: 'Options', + name: 'options', + type: 'collection', + displayOptions: { + show: { + operation: ['find'], + }, + }, + default: {}, + placeholder: 'Add options', + description: 'Add query options', + options: [ + { + displayName: 'Limit', + name: 'limit', + type: 'number', + typeOptions: { + minValue: 1, + }, + default: 0, + // eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-limit + description: + 'Use limit to specify the maximum number of documents or 0 for unlimited documents', + }, + { + displayName: 'Skip', + name: 'skip', + type: 'number', + default: 0, + description: 'The number of documents to skip in the results set', + }, + { + displayName: 'Sort (JSON Format)', + name: 'sort', + type: 'json', + typeOptions: { + rows: 2, + }, + default: '{}', + placeholder: '{ "field": -1 }', + description: 'A JSON that defines the sort order of the result set', + }, + ], + }, + { + displayName: 'Query (JSON Format)', + name: 'query', + type: 'json', + 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', 'findOneAndReplace', 'findOneAndUpdate'], + }, + }, + default: 'id', + required: true, + // eslint-disable-next-line n8n-nodes-base/node-param-description-miscased-id + 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', 'findOneAndReplace', 'findOneAndUpdate'], + }, + }, + default: '', + placeholder: 'name,description', + description: 'Comma-separated list of the fields to be included into the new document', + }, + { + displayName: 'Upsert', + name: 'upsert', + type: 'boolean', + displayOptions: { + show: { + operation: ['update', 'findOneAndReplace', 'findOneAndUpdate'], + }, + }, + default: false, + description: 'Whether to perform an insert if no documents match the update key', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + displayOptions: { + show: { + operation: ['update', 'insert', 'findOneAndReplace', 'findOneAndUpdate'], + }, + }, + placeholder: 'Add Option', + default: {}, + options: [ + { + displayName: 'Date Fields', + name: 'dateFields', + type: 'string', + default: '', + description: 'Comma separeted list of fields that will be parse as Mongo Date type', + }, + { + displayName: 'Use Dot Notation', + name: 'useDotNotation', + type: 'boolean', + default: false, + description: 'Whether to use dot notation to access date fields', + }, + ], + }, +];