Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic number of args for function (eg. SUM of any number of numbers) #957

Open
FlorianRuen opened this issue Jul 25, 2023 · 20 comments
Open

Comments

@FlorianRuen
Copy link

FlorianRuen commented Jul 25, 2023

I'm using a jsonlogic standard to create some queries, but on react query builder I didn't see a way to create custom operators like plus, minus, multiply with some constraints

  • Multiply and plus, can take multiple value (array [2,2,2,2] for example)
  • Minus and Divide can take only two args (array [4,2] for example)

I tried to add an element to the operators, but it's not showing (very simple without logic, but not showing at this time)

const operators = {
    ...BasicConfig.operators,
    plus: {
        label: "Plus"
    }
};

There is a way to create this kind of arithmetic operators ?
I saw a answer using #809 but I want to create an operator instead of function (easier to use and better UI for users)

Also, I tried to copy the LINEAR_REGRESSION and put the content in operators, but not showing anymore

In JSONLOGIC, I need to generate something like :

{
   ">=":[
      {
         "+":[
            {
               "var":"a"
            },
            {
               "var":"b"
            }
         ]
      },
      10
   ]
}

Any idea ?

@ukrbublik
Copy link
Owner

The correct way to achieve this is to add custom function (you can take LINEAR_REGRESSION as example).

With version 6.4.0 you can use functions in LHS.
I will add support of simple math functions in next version.
For now there is no support of dynamic arg length (like SUM(...numbers) instead of SUM(a, b)), but I'll work on it soon.

@FlorianRuen
Copy link
Author

@ukrbublik Thanks for the answer
While waiting to have a list of arguments, would it be possible to accept at least 3 rather than 2?
I saw on the LINEAR_REGRESSION we can ave multiple args, so my idea is to create multiple args here

@ukrbublik
Copy link
Owner

As a workaround, you can have several functions:

  • SUM2(a, b) with 2 args
  • SUM3(a, b, c) with 3 args
  • SUM4(a, b, c, d) with 4 args

Or (probably better) one function SUM4(a, b, c, d) where c and d are optional

@FlorianRuen
Copy link
Author

FlorianRuen commented Jul 25, 2023

I tried using this, but I think, two problems will appear if I want var1 + var2 + var3 <= 21 (as example) :

const funcs = {
    SUM: {
        label: '+',
        returnType: 'number',
        jsonLogic: ({a, b, c, d}) => ({ "+": [a, b, c, d]}),
        renderBrackets: ['', ''],
        renderSeps: [' + ', ' + ', ' + ', ' + '],
        args: {
            a: {
                label: "A",
                type: 'number',
                defaultValue: 1,
                valueSources: ['value'],
            },
            b: {
                label: "B",
                type: 'number',
                defaultValue: 0,
                valueSources: ['value'],
            },
            c: {
                label: "C",
                type: 'number',
                defaultValue: 0,
                valueSources: ['field', 'value'],
            },
            d: {
                label: "D",
                type: 'number',
                defaultValue: 0,
                valueSources: ['value'],
            }
        }
    }
};
  • It seems the first operator is mandatory, when I select varA, it ask for an operator, I want only func sum
  • Using valueSources = ['field', 'value'] seems not working very well, because of the first field selector ;
  • Even if the function param are optional, it seems the fields selector can't be hidden for now ;

Want I want is sum(a, b, c, d) == 10 and I appear that I can only create varA = sum(a, b, c, d)

Capture d’écran du 2023-07-25 14-20-38

@ukrbublik
Copy link
Owner

Please use

fieldSources: ["field", "func"],

in config.settings, then you can put sum in left side and 10 in right side

@FlorianRuen
Copy link
Author

FlorianRuen commented Jul 26, 2023

Please use

fieldSources: ["field", "func"],

in config.settings, then you can put sum in left side and 10 in right side

Indeed, I already had it is, but I had not installed the latest version from yesterday

The correct way to achieve this is to add custom function (you can take LINEAR_REGRESSION as example).

With version 6.4.0 you can use functions in LHS. I will add support of simple math functions in next version. For now there is no support of dynamic arg length (like SUM(...numbers) instead of SUM(a, b)), but I'll work on it soon.

Do you have an estimate of the release dates for these versions? For both simple math function (because should be better in operator than in funcs) and for the dynamic arg lenght

@ukrbublik
Copy link
Owner

(because should be better in operator than in funcs)

I don't have plans to extend operators. I have plans to add new math funcs. In your example you use operator ==

@FlorianRuen
Copy link
Author

All right. So the math functions will be considered as funcs

But in JsonLogic, the negative operator can take only two values (similar as division), the alternative should be
A - (B + C) is equal to A - B - C, using funcs, this will not be possible (like func in func or something) ?

@ukrbublik
Copy link
Owner

It's already possible to put func in func, of you specify valueSources: ['value', 'func'] for arg

@FlorianRuen
Copy link
Author

If this is already possible, there might already be a way to manage the sum more easily:

Could the arg be of type multi select ? like this, a sum function could take the set of values selected in the list (like for cars / vendor on the demo)

If this is possible, it would be quite possible to be able to sum with several values (as for a dynamic arg number finally)

@ukrbublik
Copy link
Owner

Yes, it can be multiselect, but it provides array of strings, and you need array of integers. Probably you can customize multiselect widget to not allow any characters other than 0-9
I plan to support array of integers in future, but can't give you ETA as it's my hobby project and I have full time work

@FlorianRuen
Copy link
Author

Great, I will go deeper in the configuration, and maybe If I found some time, I can submit some pull request to improve the project

Last thing, you said, we can select func in func, but on my side, the result isn't working (the second func dropdown is always empty), am I doing something wrong?

Capture.video.du.26-07-2023.11.33.01.webm

@ukrbublik
Copy link
Owner

Please add allowNesting: true in func config

@FlorianRuen
Copy link
Author

@ukrbublik Thanks for the advice, the property is allowSelfNesting
I'm testing some custom multi select components, and if works, I will comment here, can be helpful

@FlorianRuen
Copy link
Author

FlorianRuen commented Jul 27, 2023

@ukrbublik

One question : with custom function it seems the loadFromJsonLogic isn't working. Does that mean importing is only possible with default items?

The error in console is : Errors while importing from JsonLogic: ['Unknown LHS']
I tried to import a custom function called SUM

Also, a standard jsonLogic generated by the UI, cannot be imported using the same method

{"and":[{"==":[{"var":"a"},10]},{">=":[{"var":"b"},12]}]};

Got an error : Cannot real propety of null (reading type) happend in jsonLogic.js:465:1

    at Array.map (<anonymous>)
    at convertConj (http://localhost:4040/main.5cedf9f968dbbd461af5.hot-update.js:8803:83)
    at convertFromLogic (http://localhost:4040/main.5cedf9f968dbbd461af5.hot-update.js:8489:11)
    at _loadFromJsonLogic (http://localhost:4040/main.5cedf9f968dbbd461af5.hot-update.js:8394:28)
    at loadFromJsonLogic (http://localhost:4040/main.5cedf9f968dbbd461af5.hot-update.js:8386:10)

@ukrbublik
Copy link
Owner

You need to add jsonLogicImport

const SUM = {
  label: '+',
  returnType: 'number',
  // export
  jsonLogic: ({a, b, c, d}) => ({ "+": [a, b, c, d]}),
  // import
  jsonLogicImport: (v) => {
    const args = v["+"];
    return [...args];
  },
...

@ukrbublik
Copy link
Owner

As for the error, please provide your config, or codesandbox to reproduce

@FlorianRuen
Copy link
Author

FlorianRuen commented Jul 27, 2023

Here is my config :

[... imports ...]

const operators = {
    ...BootstrapConfig.operators,
    between: {
        ...BootstrapConfig.operators.between,
        label: "Between",
        valueLabels: ['from', 'to'],
        textSeparators: ['from', 'to'],
    },
};

const widgets = {
    ...BootstrapConfig.widgets,
    number: { ...BootstrapConfig.widgets.number },
    select: { ...BootstrapConfig.widgets.select },
    func: { ...BootstrapConfig.widgets.func },
    time: {
        ...BootstrapConfig.widgets.time,
        timeFormat: 'HH:mm',
        valueFormat: 'HH:mm:ss',
    }
};

const types = {
    ...BootstrapConfig.types,
    boolean: merge(BootstrapConfig.types.boolean, {
        widgets: {
            boolean: {
                widgetProps: {
                    hideOperator: true,
                    operatorInlineLabel: "is",
                }
            },
        },
    }),
};

const localeSettings = { locale: { short: 'en', full: 'en-US' } };

const settings = {
    ...BootstrapConfig.settings,
    ...localeSettings,

    valueSourcesInfo: {
        value: { label: "Value" },
        field: { label: "Variable", widget: "field" },
        func: { label: "Function", widget: "func" }
    },
    maxNesting: 3,
    canLeaveEmptyGroup: false,
    canReorder: true,
    canRegroup: false,
    fieldSources: ["field", "func"],
    renderField: (props) => <BootstrapFieldSelect {...props} />,
    renderValueSources: (props) => <BootstrapValueSources {...props} />,
};

const fields = {
    "Pipeline": {
        label: 'Pipeline',
        tooltip: 'Pipeline',
        type: '!struct',
        subfields: GetPipelineQueryFields()
    }
};

const funcs = {
    ...BootstrapConfig.funcs,
    SUM: {
        label: 'Sum',
        returnType: 'number',
        allowSelfNesting: true,
        jsonLogic: ({firstNumber, secondNumber}) => ({ "+": [firstNumber, secondNumber]}),
        jsonLogicImport: (v) => {
            const args = v["+"];
            return [...args];
        },
        renderSeps: [' + '],
        args: {
            firstNumber: {
                label: "Number",
                type: "number",
                valueSources: ['field']
            },
            secondNumber: {
                label: "Number or func",
                type: "number",
                valueSources: ['field', 'func']
            },
        }
    },
    MINUS: {
        label: 'Subtraction',
        returnType: 'number',
        allowSelfNesting: true,
        jsonLogic: ({firstNumber, secondNumber}) => ({ "-": [firstNumber, secondNumber]}),
        jsonLogicImport: (v) => {
            const args = v["-"];
            return [...args];
        },
        renderSeps: [' - '],
        args: {
            firstNumber: {
                label: "Number",
                type: "number",
                valueSources: ['field']
            },
            secondNumber: {
                label: "Number or func",
                type: "number",
                valueSources: ['field', 'func']
            },
        }
    },
    MULTIPLY: {
        label: 'Multiply',
        returnType: 'number',
        allowSelfNesting: true,
        jsonLogic: ({firstNumber, secondNumber}) => ({ "*": [firstNumber, secondNumber]}),
        jsonLogicImport: (v) => {
            const args = v["*"];
            return [...args];
        },
        renderSeps: [' * '],
        args: {
            firstNumber: {
                label: "Number",
                type: "number",
                valueSources: ['field']
            },
            secondNumber: {
                label: "Number or func",
                type: "number",
                valueSources: ['field', 'func']
            },
        }
    },
    DIVIDE: {
        label: 'Division',
        returnType: 'number',
        allowSelfNesting: true,
        jsonLogic: ({firstNumber, secondNumber}) => ({ "/": [firstNumber, secondNumber]}),
        jsonLogicImport: (v) => {
            const args = v["/"];
            return [...args];
        },
        renderSeps: [' / '],
        args: {
            firstNumber: {
                label: "Number",
                type: "number",
                valueSources: ['field']
            },
            secondNumber: {
                label: "Number or func",
                type: "number",
                valueSources: ['field', 'func']
            },
        }
    }
};

const config = {
    ctx: BootstrapConfig.ctx,
    conjunctions,
    operators,
    widgets,
    types,
    settings,
    fields,
    funcs
};

export default config;

@ukrbublik
Copy link
Owner

Please try version 6.4.1 to fix the error
Just triggered the release, should be available soon at NPM

@FlorianRuen
Copy link
Author

@ukrbublik seems working perfectly now! good and fast release !

@ukrbublik ukrbublik added this to the Functions milestone Jul 27, 2023
@ukrbublik ukrbublik changed the title Create custom operators like plus, minus and multiply Dynamic number of args for function (eg. SUM of any number of numbers) May 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants