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',
+ },
+ ],
+ },
+];