react-querybuilder/react-querybuilder

Add CustomOperations option to parseMongoDB function

christinalettner1 opened this issue · 9 comments

Hi!

I was wondering if it is possible to add something similar as the jsonLogicOperations that is implemented for the parseJsonLogic function to the parseMongoDB function? Or maybe it is already possible and I just can't seem to find it?

Right now, I have a custom operator called $anyin. And, for example, this is a valid saved query for my project:

{"$and":[{"orderItemCount":{"$gt":"5"}},{"orderCategoryIds":{"$anyin":["1016"]}}]}

But when I call parseMongoDB with that query, the result just ignores the "orderCategoryIds" entry and does not add it in the rules because the operator is "$anyin".

There's not a method today, but I'm thinking I can probably add a mechanism to support custom operators fairly easily. parseMongoDB isn't a black box like parseSQL or parseCEL, so it should be possible. It may not come until version 7 (or the next alpha release, which can happen any time), but I'll definitely try to get it in.

I added an additionalOperators option to parseMongoDB in #595. If you like you can try it out in this sandbox:

https://codesandbox.io/p/devbox/react-querybuilder-ci-forked-hrp39h?file=%2Fsrc%2FApp.tsx%3A9%2C1-12%2C1

The textbox at the top will initially be populated with a sample MongoDB query that uses a custom $x operator. With that operator configured in the parser options, the $x value will be processed through the associated function.

This is the type definition of additionalOperators directly from the code, with JSDoc comment:

export interface ParseMongoDbOptions extends ParserCommonOptions {
/**
* Map of additional operators to their respective processing functions. Functions
* are passed the operator, the associated value, and any other options. They should
* return either a {@link RuleType} or {@link RuleGroupType}.
*
* (The functions should _not_ return {@link RuleGroupTypeIC}, even if using independent
* combinators. If the `independentCombinators` option is `true`, `parseMongoDB`
* will convert the final query to {@link RuleGroupTypeIC} before returning it.)
*/
additionalOperators?: Record<
string,
(operator: string, value: any, options: ParserCommonOptions) => RuleType | RuleGroupType
>;
}

If that works for you, I'll publish a new v7 alpha with that added. I'm not sure about adding it to v6, but I'd consider it if it's a priority. (V7 migration instructions are here.)

Thanks for your fast response :)

Unfortunately, I think this doesn't quite fix my problem. In my case, the additionalOperators would need to look something like this:

const additionalOperators = {
  $anyin: (fieldName: string, value: any, options: ParserCommonOptions) => ({ field: fieldName operator: '$anyin', value }) 
}

As far as I understood, in your codesandbox example the additionalOperator $x would correspond to the "orderCategoryIds" in my example above right?

Ah, no. Sorry that wasn't super clear. But I didn't look at your example close enough either, so the current implementation doesn't meet the requirement. I'll try to fix it today.

Currently, the first argument is the custom MongoDB operator as a string, in the case of the example it would be "$x". The second argument is the value associated to that key (not anything to do with the value property of a rule, since RQB can't deduce that). For the example I used {}, but since it's a custom operator it could be an array, primitive, object, whatever (which is why it's typed as any).

I think in the next iteration I'll have the arguments be the same, except the field name will come first. So it will be field, operator, value, options

A MongoDB query like yours would be called like this:

const additionalOperators = {
  $anyin: myCustomOperatorFn
}

// For this MongoDB query:
// {"$and":[{"orderItemCount":{"$gt":"5"}},{"orderCategoryIds":{"$anyin":["1016"]}}]}

// RQB will do this:
myCustomOperatorFn("orderCategoryIds", "$anyin", ["1016"], /* otherOptions */)

Would that work?

Ok, sorry if my first message wasn't specific enough. Yes, your codesnippet is exactly what I would need!
I'm not in a hurry so if you have other more important stuff to do please take your time and just notify me when I should test something again :)

Ok, sorry if my first message wasn't specific enough. Yes, your codesnippet is exactly what I would need! I'm not in a hurry so if you have other more important stuff to do please take your time and just notify me when I should test something again :)

Oh your message was clear, I was apologizing for my code!

I've adjusted the implementation as discussed. This sandbox demonstrates the new API:

https://codesandbox.io/p/devbox/react-querybuilder-ci-forked-yv9d3j?file=%2Fsrc%2FApp.tsx

Relevant code:

const additionalOperators = {
  $x: (field: string, operator: string, value: any, opts: any) => {
    // Log all parameters so you can see them:
    console.log(field, operator, value, opts);
    // Return a rule based on the field and value:
    return { field, operator: '!=', value: JSON.stringify(value) };
  },
};

const initialMongoQuery = {
  $and: [
    { lastName: { $x: { some: 'stuff here', doesnt: 'matter what' } } },
    { $or: [{ isMusician: true }, { instrument: 'Guitar' }] },
  ],
};

This works very well for my case now. Thanks so much for your quick response!

Hey @christinalettner1 - v7.0.0-alpha.3 is out now. It includes the new additionalOperators option for the "mongodb" export format.

Awesome, thanks again! I've already implemented it in my project and it works as expected.