fix(MongoDB Node): Stringify response ObjectIDs (#6990)

Github issue / Community forum post (link here to close automatically):
This commit is contained in:
Marcus 2023-08-29 17:44:37 +02:00 committed by GitHub
parent 8a01d063c9
commit 9ca990b993
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 320 additions and 307 deletions

View file

@ -6,15 +6,15 @@ import type {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow';
import get from 'lodash/get';
import set from 'lodash/set';
import { ObjectId } from 'mongodb';
import type { import type {
IMongoCredentials, IMongoCredentials,
IMongoCredentialsType, IMongoCredentialsType,
IMongoParametricCredentials, IMongoParametricCredentials,
} from './mongoDb.types'; } 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 * 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()) .map((field) => field.trim())
.filter((field) => !!field); .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();
}
});
}

View file

@ -11,15 +11,6 @@ import type {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow';
import { nodeDescription } from './MongoDbDescription';
import {
buildParameterizedConnString,
prepareFields,
prepareItems,
validateAndResolveMongoCredentials,
} from './GenericFunctions';
import type { import type {
FindOneAndReplaceOptions, FindOneAndReplaceOptions,
FindOneAndUpdateOptions, FindOneAndUpdateOptions,
@ -27,11 +18,40 @@ import type {
Sort, Sort,
} from 'mongodb'; } from 'mongodb';
import { MongoClient, ObjectId } 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'; import type { IMongoParametricCredentials } from './mongoDb.types';
export class MongoDb implements INodeType { 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 = { methods = {
credentialTest: { credentialTest: {
@ -65,7 +85,7 @@ export class MongoDb implements INodeType {
} catch (error) { } catch (error) {
return { return {
status: 'Error', status: 'Error',
message: error.message, message: (error as Error).message,
}; };
} }
return { return {
@ -98,15 +118,17 @@ export class MongoDb implements INodeType {
// ---------------------------------- // ----------------------------------
try { 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') { if (queryParameter._id && typeof queryParameter._id === 'string') {
queryParameter._id = new ObjectId(queryParameter._id as string); queryParameter._id = new ObjectId(queryParameter._id);
} }
const query = mdb const query = mdb
.collection(this.getNodeParameter('collection', 0) as string) .collection(this.getNodeParameter('collection', 0) as string)
.aggregate(queryParameter as Document[]); .aggregate(queryParameter as unknown as Document[]);
responseData = await query.toArray(); responseData = await query.toArray();
} catch (error) { } catch (error) {
@ -140,20 +162,22 @@ export class MongoDb implements INodeType {
// ---------------------------------- // ----------------------------------
try { 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') { if (queryParameter._id && typeof queryParameter._id === 'string') {
queryParameter._id = new ObjectId(queryParameter._id as string); queryParameter._id = new ObjectId(queryParameter._id);
} }
let query = mdb let query = mdb
.collection(this.getNodeParameter('collection', 0) as string) .collection(this.getNodeParameter('collection', 0) as string)
.find(queryParameter as Document); .find(queryParameter as unknown as Document);
const options = this.getNodeParameter('options', 0); const options = this.getNodeParameter('options', 0);
const limit = options.limit as number; const limit = options.limit as number;
const skip = options.skip 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) { if (skip > 0) {
query = query.skip(skip); query = query.skip(skip);
} }
@ -339,6 +363,8 @@ export class MongoDb implements INodeType {
await client.close(); await client.close();
stringifyObjectIDs(responseData);
const executionData = this.helpers.constructExecutionMetaData( const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData), this.helpers.returnJsonArray(responseData),
{ itemData: { item: 0 } }, { itemData: { item: 0 } },

View file

@ -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 <a href="https://docs.mongodb.com/manual/core/aggregation-pipeline/">here</a>',
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',
},
],
},
],
};

View file

@ -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 <a href="https://docs.mongodb.com/manual/core/aggregation-pipeline/">here</a>',
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',
},
],
},
];