From 0e21671550008c04f70ac246729cf05cd9c54e80 Mon Sep 17 00:00:00 2001 From: Leonardo <leomilho@gmail.com> Date: Wed, 5 Jun 2024 17:13:05 +0200 Subject: [PATCH] feat(qb): new logic pill setup (cherry picked from commit 0519fe603401054b15d998e82529b81a9eada23b) --- .../shared/lib/components/dropdowns/index.tsx | 13 +- libs/shared/lib/components/inputs/index.tsx | 22 +- .../lib/components/inputs/inputs.module.scss | 2 + libs/shared/lib/components/pills/Pill.tsx | 8 +- .../querybuilder/model/graphology/utils.ts | 34 +- .../lib/querybuilder/model/logic/general.ts | 3 +- .../model/logic/numberAggregations.tsx | 176 +++---- .../model/logic/numberFilters.tsx | 61 +-- .../model/logic/numberFunctions.tsx | 41 +- .../model/logic/stringFilters.tsx | 34 +- .../model/logic/stringFunctions.tsx | 16 +- .../lib/querybuilder/panel/QueryBuilder.tsx | 441 +++++++++--------- .../panel/QueryBuilderDispatcher.tsx | 10 + .../queryBuilderLogicPillsPanel.tsx | 59 ++- .../logicpill/QueryLogicPill.tsx | 62 ++- .../pills/pilldropdown/PillDropdown.tsx | 2 +- .../query-utils/query2backend.spec.ts | 34 +- .../querybuilder/query-utils/query2backend.ts | 19 +- 18 files changed, 578 insertions(+), 459 deletions(-) create mode 100644 libs/shared/lib/querybuilder/panel/QueryBuilderDispatcher.tsx diff --git a/libs/shared/lib/components/dropdowns/index.tsx b/libs/shared/lib/components/dropdowns/index.tsx index 54cfd4bab..dbc2ce4b9 100644 --- a/libs/shared/lib/components/dropdowns/index.tsx +++ b/libs/shared/lib/components/dropdowns/index.tsx @@ -23,16 +23,23 @@ type DropdownButtonProps = { onClick: (e: React.MouseEvent<HTMLButtonElement>) => void; size?: 'xs' | 'sm' | 'md' | 'xl'; disabled?: boolean; + variant?: 'primary' | 'ghost' | 'outline'; }; -export function DropdownButton({ title, onClick, size, disabled }: DropdownButtonProps) { - const paddingClass = size === 'xs' ? 'py-1' : size === 'sm' ? 'px-1 py-1' : size === 'md' ? 'px-2 py-1' : 'px-4 py-2'; +export function DropdownButton({ title, onClick, size, disabled, variant }: DropdownButtonProps) { + const paddingClass = size === 'xs' ? 'py-0' : size === 'sm' ? 'px-1 py-1' : size === 'md' ? 'px-2 py-1' : 'px-4 py-2'; const textSizeClass = size === 'xs' ? 'text-xs' : size === 'sm' ? 'text-sm' : size === 'md' ? 'text-base' : 'text-lg'; + const variantClass = + variant === 'primary' || !variant + ? 'border bg-light rounded' + : variant === 'ghost' + ? 'bg-transparent shadow-none' + : 'border rounded bg-transparent'; return ( <> <button - className={`inline-flex border w-full justify-between items-center gap-x-1.5 rounded bg-light ${textSizeClass} ${paddingClass} text-secondary-900 shadow-sm hover:bg-secondary-50 disabled:bg-secondary-100 disabled:cursor-not-allowed disabled:text-secondary-400 pl-1 truncate`} + className={`inline-flex w-full justify-between items-center gap-x-1.5 ${variantClass} ${textSizeClass} ${paddingClass} text-secondary-900 shadow-sm hover:bg-secondary-50 disabled:bg-secondary-100 disabled:cursor-not-allowed disabled:text-secondary-400 pl-1 truncate`} onClick={onClick} disabled={disabled} > diff --git a/libs/shared/lib/components/inputs/index.tsx b/libs/shared/lib/components/inputs/index.tsx index cb8c073e7..86d440b59 100644 --- a/libs/shared/lib/components/inputs/index.tsx +++ b/libs/shared/lib/components/inputs/index.tsx @@ -19,6 +19,7 @@ type SliderProps = { type TextProps = { label?: string; type: 'text'; + size?: 'xs' | 'sm' | 'md' | 'xl'; placeholder?: string; value: string; required?: boolean; @@ -91,8 +92,10 @@ type DropdownProps = { onChange?: (value: string | number) => void; required?: boolean; inline?: boolean; + buttonVariant?: 'primary' | 'outline' | 'ghost'; info?: string; disabled?: boolean; + className?: string; }; export type InputProps = TextProps | SliderProps | CheckboxProps | DropdownProps | RadioProps | BooleanProps | NumberProps; @@ -152,6 +155,7 @@ export const TextInput = ({ label, placeholder, value = '', + size = 'md', required = false, visible = true, errorText, @@ -169,7 +173,9 @@ export const TextInput = ({ return ( <div data-tip={tooltip || null} - className={'form-control w-full' + (inline ? ' grid grid-cols-2 items-center' : '') + (tooltip ? ' tooltip' : '')} + className={ + styles['input'] + ' form-control w-full' + (inline ? ' grid grid-cols-2 items-center gap-0.5' : '') + (tooltip ? ' tooltip' : '') + } > {label && ( <label className="label p-0"> @@ -186,7 +192,7 @@ export const TextInput = ({ type={visible ? 'text' : 'password'} placeholder={placeholder} className={ - `px-3 py-2 bg-light border border-secondary-300 placeholder-secondary-400 focus:outline-none block w-full sm:text-sm focus:ring-1 ${ + `${size} bg-light border border-secondary-300 placeholder-secondary-400 focus:outline-none block w-full focus:ring-1 ${ isValid ? '' : 'input-error' }` + (className ? ` ${className}` : '') } @@ -230,7 +236,9 @@ export const NumberInput = ({ return ( <div data-tip={tooltip || null} - className={styles['input'] + ' form-control w-full' + (inline ? ' grid grid-cols-2 items-center' : '') + (tooltip ? ' tooltip' : '')} + className={ + styles['input'] + ' form-control w-full' + (inline ? ' grid grid-cols-2 items-center gap-0.5' : '') + (tooltip ? ' tooltip' : '') + } > <label className="label p-0"> <span @@ -362,6 +370,8 @@ export const DropDownInput = ({ size = 'sm', inline = false, disabled = false, + buttonVariant = 'primary', + className = '', info, }: DropdownProps) => { const dropdownRef = React.useRef<HTMLDivElement>(null); @@ -381,7 +391,10 @@ export const DropDownInput = ({ }, [isDropdownOpen]); return ( - <div data-tip={tooltip || null} className={'w-full' + (inline ? ' grid grid-cols-2 items-center' : '') + (tooltip ? ' tooltip' : '')}> + <div + data-tip={tooltip || null} + className={className + ' w-full' + (inline ? ' grid grid-cols-2 items-center gap-0.5' : '') + (tooltip ? ' tooltip' : '')} + > {label && ( <label className="label p-0"> <span @@ -394,6 +407,7 @@ export const DropDownInput = ({ )} <DropdownContainer className="w-full right-0 left-auto" ref={dropdownRef}> <DropdownButton + variant={buttonVariant} title={overrideRender || value} size={size} disabled={disabled} diff --git a/libs/shared/lib/components/inputs/inputs.module.scss b/libs/shared/lib/components/inputs/inputs.module.scss index f1733a091..71c96c83b 100644 --- a/libs/shared/lib/components/inputs/inputs.module.scss +++ b/libs/shared/lib/components/inputs/inputs.module.scss @@ -26,10 +26,12 @@ input[class~='xs'] { @apply py-0; @apply px-0; + @apply sm:text-2xs; } input[class~='sm'] { @apply py-1; @apply px-1; + @apply sm:text-sm; } input[class~='md'] { @apply py-2; diff --git a/libs/shared/lib/components/pills/Pill.tsx b/libs/shared/lib/components/pills/Pill.tsx index 05b35b689..05bf3475e 100644 --- a/libs/shared/lib/components/pills/Pill.tsx +++ b/libs/shared/lib/components/pills/Pill.tsx @@ -155,5 +155,11 @@ export const RelationPill = React.memo((props: Omit<PillI, 'topColor'> & { withH }); export const LogicPill = React.memo((props: Omit<PillI, 'topColor'>) => { - return <Pill {...props} corner="square" topColor="bg-[#543719]" />; + const handles = ( + <PillHandle position={Position.Left} className={'fill-[#543719] stroke-white'} type="square"> + {props.handleLeft} + </PillHandle> + ); + + return <Pill {...props} corner="square" topColor="bg-[#543719]" handles={handles} />; }); diff --git a/libs/shared/lib/querybuilder/model/graphology/utils.ts b/libs/shared/lib/querybuilder/model/graphology/utils.ts index 7c2878826..041fe0cde 100644 --- a/libs/shared/lib/querybuilder/model/graphology/utils.ts +++ b/libs/shared/lib/querybuilder/model/graphology/utils.ts @@ -118,19 +118,22 @@ export class QueryMultiGraphology extends Graph<QueryGraphNodes, QueryGraphEdges if (!attributes.type) attributes.type = QueryElementTypes.Logic; if (!attributes.name || !attributes.id) throw Error('type or name is not defined'); + // add default inputs, but only if not there yet if (attributes.type === QueryElementTypes.Logic) { - attributes = attributes as LogicNodeAttributes; - (attributes as LogicNodeAttributes).logic.inputs.forEach((input, i) => { - // Setup default non-linked inputs as regular values matching the input expected type - if (!(attributes as LogicNodeAttributes).inputs) (attributes as LogicNodeAttributes).inputs = {}; - (attributes as LogicNodeAttributes).inputs[input.name] = inputValues?.[input.name] || input.default; - }); - // (attributes as LogicNodeAttributes).leftEntityHandleId = getHandleId(attributes.id, name, type, Handles.RelationLeft, ''); - // (attributes as LogicNodeAttributes).rightEntityHandleId = getHandleId(attributes.id, name, type, Handles.RelationRight, ''); + if ((attributes as LogicNodeAttributes).inputs === undefined) { + attributes = attributes as LogicNodeAttributes; + (attributes as LogicNodeAttributes).logic.inputs.forEach((input, i) => { + // Setup default non-linked inputs as regular values matching the input expected type + if (!(attributes as LogicNodeAttributes).inputs) (attributes as LogicNodeAttributes).inputs = {}; + (attributes as LogicNodeAttributes).inputs[input.name] = inputValues?.[input.name] || input.default; + }); + // (attributes as LogicNodeAttributes).leftEntityHandleId = getHandleId(attributes.id, name, type, Handles.RelationLeft, ''); + // (attributes as LogicNodeAttributes).rightEntityHandleId = getHandleId(attributes.id, name, type, Handles.RelationRight, ''); + } } else throw Error('using wrong function! use addPill2Graphology instead'); // Add a node to the graphology object - this.addNode(attributes.id, { ...attributes }); + const nodeMergeResult = this.mergeNode(attributes.id, { ...attributes }); // Set the new nodes in the query builder slice TODO: maybe remove for efficiency // dispatch(setQuerybuilderNodes(this.export())); // Can't do this due to loop import @@ -192,7 +195,12 @@ export class QueryMultiGraphology extends Graph<QueryGraphNodes, QueryGraphEdges if (!options.targetHandleName) throw Error('targetHandleName is not defined'); targetHandleType = Handles.LogicLeft; targetAttributeName = options.targetHandleName; - targetAttributeType = (target as LogicNodeAttributes).logic.inputs.find((i) => i.name === targetAttributeName)?.type; + const _target = target as LogicNodeAttributes; + if (targetAttributeName === _target.logic.input.name) { + targetAttributeType = _target.logic.input.type; + } else { + targetAttributeType = _target.logic.inputs.find((i) => i.name === targetAttributeName)?.type; + } if (!targetAttributeType) throw Error(`targetHandleName ${targetAttributeName} does not exist!`); } else { throw Error('target.type is not correctly defined'); @@ -221,13 +229,13 @@ export class QueryMultiGraphology extends Graph<QueryGraphNodes, QueryGraphEdges // console.log('newEdge', attributes, source, target); - // Add an edge to the graphology object - const edgeId = this.addEdge(source.id, target.id, attributes as QueryGraphEdges); + // Add/edit an edge to the graphology object + const edgeResult = this.mergeEdge(source.id, target.id, attributes as QueryGraphEdges); // Set the new nodes in the query builder slice TODO: maybe remove for efficiency // store.dispatch(setQuerybuilderNodes(this.export())); - return edgeId; + return edgeResult[0]; // edge id } } diff --git a/libs/shared/lib/querybuilder/model/logic/general.ts b/libs/shared/lib/querybuilder/model/logic/general.ts index 16e81c19e..e0dee2de5 100644 --- a/libs/shared/lib/querybuilder/model/logic/general.ts +++ b/libs/shared/lib/querybuilder/model/logic/general.ts @@ -198,7 +198,8 @@ export interface GeneralDescription<T> { name: string; type: T; description: string; - numInputs?: number; + numExtraInputs: number; + input: InputNode; inputs: InputNode[]; output: OutputNode; key: string; diff --git a/libs/shared/lib/querybuilder/model/logic/numberAggregations.tsx b/libs/shared/lib/querybuilder/model/logic/numberAggregations.tsx index 32c5604b0..929bef837 100644 --- a/libs/shared/lib/querybuilder/model/logic/numberAggregations.tsx +++ b/libs/shared/lib/querybuilder/model/logic/numberAggregations.tsx @@ -13,51 +13,55 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription name: 'Average', type: NumberAggregationTypes.AVG, description: 'Average of all values', - numInputs: 1, - inputs: [{ name: '1', type: 'float', default: 0 }], + input: { name: 'Value', type: 'float', default: 0 }, + numExtraInputs: 0, + inputs: [], output: { name: 'avg', type: 'float' }, - logic: ['Avg', '@1'], + logic: ['Avg', '@i'], }, [NumberAggregationTypes.COUNT]: { key: 'numberAggregationCount', name: 'Count', type: NumberAggregationTypes.COUNT, description: 'Count the number of values', - numInputs: 1, - inputs: [{ name: '1', type: 'float', default: 0 }], - + input: { name: 'Value', type: 'float', default: 0 }, + numExtraInputs: 0, + inputs: [], output: { name: 'count', type: 'float' }, - logic: ['Count', '@1'], + logic: ['Count', '@i'], }, [NumberAggregationTypes.MAX]: { key: 'numberAggregationMax', name: 'Maximum', type: NumberAggregationTypes.MAX, description: 'Maximum of all values', - numInputs: 1, - inputs: [{ name: '1', type: 'float', default: 0 }], + input: { name: 'Value', type: 'float', default: 0 }, + numExtraInputs: 0, + inputs: [], output: { name: 'max', type: 'float' }, - logic: ['Max', '@1'], + logic: ['Max', '@i'], }, [NumberAggregationTypes.MIN]: { key: 'numberAggregationMin', name: 'Minimum', type: NumberAggregationTypes.MIN, description: 'Minimum of all values', - numInputs: 1, - inputs: [{ name: '1', type: 'float', default: 0 }], + input: { name: 'Value', type: 'float', default: 0 }, + numExtraInputs: 0, + inputs: [], output: { name: 'min', type: 'float' }, - logic: ['Min', '@1'], + logic: ['Min', '@i'], }, [NumberAggregationTypes.SUM]: { key: 'numberAggregationSum', name: 'Sum', type: NumberAggregationTypes.SUM, description: 'Sum of all values', - numInputs: 1, - inputs: [{ name: '1', type: 'float', default: 0 }], + input: { name: 'Value', type: 'float', default: 0 }, + numExtraInputs: 0, + inputs: [], output: { name: 'sum', type: 'float' }, - logic: ['Sum', '@1'], + logic: ['Sum', '@i'], }, // [MathAggregationTypes.STD]: { // key: 'numberAggregationStd', @@ -67,7 +71,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'std', type: 'float' }, - // logic: ['Std', '@1'], + // logic: ['Std', '@i'], // }, // [MathAggregationTypes.ADD]: { // key: 'numberAggregationAdd', @@ -77,10 +81,10 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 2, // inputs: [ // { name: '1', type: 'float', default: 0 }, - // { name: '2', type: 'float', default: 0 }, + // { name: '1', type: 'float', default: 0 }, // ], // output: { name: '+', type: 'float' }, - // logic: ['+', '@1', '@2'], + // logic: ['+', '@i', '@1'], // }, // [MathAggregationTypes.SUBTRACT]: { // key: 'numberAggregationSubtract', @@ -90,10 +94,10 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 2, // inputs: [ // { name: '1', type: 'float' }, - // { name: '2', type: 'float' }, + // { name: '1', type: 'float' }, // ], // output: { name: '-', type: 'float' }, - // logic: ['-', '@1', '@2'], + // logic: ['-', '@i', '@1'], // }, // [MathAggregationTypes.MULTIPLY]: { // key: 'numberAggregationMultiply', @@ -103,10 +107,10 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 2, // inputs: [ // { name: '1', type: 'float' }, - // { name: '2', type: 'float' }, + // { name: '1', type: 'float' }, // ], // output: { name: '*', type: 'float' }, - // logic: ['*', '@1', '@2'], + // logic: ['*', '@i', '@1'], // }, // [MathAggregationTypes.DIVIDE]: { // key: 'numberAggregationDivide', @@ -117,10 +121,10 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 2, // inputs: [ // { name: '1', type: 'float' }, - // { name: '2', type: 'float' }, + // { name: '1', type: 'float' }, // ], // output: { name: '/', type: 'float' }, - // logic: ['/', '@1', '@2'], + // logic: ['/', '@i', '@1'], // }, // [MathAggregationTypes.POWER]: { // key: 'numberAggregationPower', @@ -130,10 +134,10 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 2, // inputs: [ // { name: '1', type: 'float' }, - // { name: '2', type: 'float' }, + // { name: '1', type: 'float' }, // ], // output: { name: '^', type: 'float' }, - // logic: ['^', '@1', '@2'], + // logic: ['^', '@i', '@1'], // }, // [MathAggregationTypes.SQRT]: { // key: 'numberAggregationSqrt', @@ -143,7 +147,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'sqrt', type: 'float' }, - // logic: ['Sqrt', '@1'], + // logic: ['Sqrt', '@i'], // }, // [MathAggregationTypes.LOG]: { // key: 'numberAggregationLog', @@ -153,7 +157,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'log', type: 'float' }, - // logic: ['Log', '@1'], + // logic: ['Log', '@i'], // }, // [MathAggregationTypes.EXP]: { // key: 'numberAggregationExp', @@ -163,7 +167,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'exp', type: 'float' }, - // logic: ['Exp', '@1'], + // logic: ['Exp', '@i'], // }, // [MathAggregationTypes.ABS]: { // key: 'numberAggregationAbs', @@ -173,7 +177,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'abs', type: 'float' }, - // logic: ['Abs', '@1'], + // logic: ['Abs', '@i'], // }, // [MathAggregationTypes.CEIL]: { // key: 'numberAggregationCeil', @@ -183,7 +187,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'ceil', type: 'float' }, - // logic: ['Ceil', '@1'], + // logic: ['Ceil', '@i'], // }, // [MathAggregationTypes.FLOOR]: { // key: 'numberAggregationFloor', @@ -193,7 +197,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'floor', type: 'float' }, - // logic: ['Floor', '@1'], + // logic: ['Floor', '@i'], // }, // [MathAggregationTypes.ROUND]: { // key: 'numberAggregationRound', @@ -203,7 +207,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'round', type: 'float' }, - // logic: ['Round', '@1'], + // logic: ['Round', '@i'], // }, // [MathAggregationTypes.TRUNC]: { // key: 'numberAggregationTrunc', @@ -214,7 +218,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // inputs: [{ name: '1', type: 'float' }], // output: { name: 'trunc', type: 'float' }, - // logic: ['Trunc', '@1'], + // logic: ['Trunc', '@i'], // }, // [MathAggregationTypes.RANDOM]: { // key: 'numberAggregationRandom', @@ -237,7 +241,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // { name: 'max', type: 'float' }, // ], // output: { name: 'randomInt', type: 'float' }, - // logic: ['RandomInt', '@1', '@2'], + // logic: ['RandomInt', '@i', '@1'], // }, // [MathAggregationTypes.SIN]: { // key: 'numberAggregationSin', @@ -247,7 +251,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'sin', type: 'float' }, - // logic: ['Sin', '@1'], + // logic: ['Sin', '@i'], // }, // [MathAggregationTypes.COS]: { // key: 'numberAggregationCos', @@ -257,7 +261,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'cos', type: 'float' }, - // logic: ['Cos', '@1'], + // logic: ['Cos', '@i'], // }, // [MathAggregationTypes.TAN]: { // key: 'numberAggregationTan', @@ -267,7 +271,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'tan', type: 'float' }, - // logic: ['Tan', '@1'], + // logic: ['Tan', '@i'], // }, // [MathAggregationTypes.ASIN]: { // key: 'numberAggregationAsin', @@ -277,7 +281,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'asin', type: 'float' }, - // logic: ['Asin', '@1'], + // logic: ['Asin', '@i'], // }, // [MathAggregationTypes.ACOS]: { // key: 'numberAggregationAcos', @@ -287,7 +291,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'acos', type: 'float' }, - // logic: ['Acos', '@1'], + // logic: ['Acos', '@i'], // }, // [MathAggregationTypes.ATAN]: { // key: 'numberAggregationAtan', @@ -297,7 +301,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'atan', type: 'float' }, - // logic: ['Atan', '@1'], + // logic: ['Atan', '@i'], // }, // [MathAggregationTypes.SINH]: { // key: 'numberAggregationSinh', @@ -307,7 +311,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'sinh', type: 'float' }, - // logic: ['Sinh', '@1'], + // logic: ['Sinh', '@i'], // }, // [MathAggregationTypes.COSH]: { // key: 'numberAggregationCosh', @@ -317,7 +321,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'cosh', type: 'float' }, - // logic: ['Cosh', '@1'], + // logic: ['Cosh', '@i'], // }, // [MathAggregationTypes.TANH]: { // key: 'numberAggregationTanh', @@ -327,7 +331,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'tanh', type: 'float' }, - // logic: ['Tanh', '@1'], + // logic: ['Tanh', '@i'], // }, // [MathAggregationTypes.ASINH]: { // key: 'numberAggregationAsinh', @@ -337,7 +341,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'asinh', type: 'float' }, - // logic: ['Asinh', '@1'], + // logic: ['Asinh', '@i'], // }, // [MathAggregationTypes.ACOSH]: { // key: 'numberAggregationAcosh', @@ -347,7 +351,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'acosh', type: 'float' }, - // logic: ['Acosh', '@1'], + // logic: ['Acosh', '@i'], // }, // [MathAggregationTypes.ATANH]: { // key: 'numberAggregationAtanh', @@ -357,7 +361,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'atanh', type: 'float' }, - // logic: ['Atanh', '@1'], + // logic: ['Atanh', '@i'], // }, // [MathAggregationTypes.DEGREES]: { // key: 'numberAggregationDegrees', @@ -367,7 +371,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'degrees', type: 'float' }, - // logic: ['Degrees', '@1'], + // logic: ['Degrees', '@i'], // }, // [MathAggregationTypes.RADIANS]: { // key: 'numberAggregationRadians', @@ -378,7 +382,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // inputs: [{ name: '1', type: 'float' }], // output: { name: 'radians', type: 'float' }, - // logic: ['Radians', '@1'], + // logic: ['Radians', '@i'], // }, // [MathAggregationTypes.SIGN]: { // key: 'numberAggregationSign', @@ -388,7 +392,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'sign', type: 'float' }, - // logic: ['Sign', '@1'], + // logic: ['Sign', '@i'], // }, // [MathAggregationTypes.RANDOMNORMAL]: { // key: 'numberAggregationRandomNormal', @@ -401,7 +405,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // { name: 'std', type: 'float' }, // ], // output: { name: 'randomNormal', type: 'float' }, - // logic: ['RandomNormal', '@1', '@2'], + // logic: ['RandomNormal', '@i', '@1'], // }, // [MathAggregationTypes.RANDOMLOGNORMAL]: { // key: 'numberAggregationRandomLogNormal', @@ -414,7 +418,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // { name: 'std', type: 'float' }, // ], // output: { name: 'randomLogNormal', type: 'float' }, - // logic: ['RandomLogNormal', '@1', '@2'], + // logic: ['RandomLogNormal', '@i', '@1'], // }, // [MathAggregationTypes.RANDOMEXPONENTIAL]: { // key: 'numberAggregationRandomExponential', @@ -424,7 +428,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: 'lambda', type: 'float' }], // output: { name: 'randomExponential', type: 'float' }, - // logic: ['RandomExponential', '@1'], + // logic: ['RandomExponential', '@i'], // }, // [MathAggregationTypes.RANDOMGAMMA]: { // key: 'numberAggregationRandomGamma', @@ -437,7 +441,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // { name: 'beta', type: 'float' }, // ], // output: { name: 'randomGamma', type: 'float' }, - // logic: ['RandomGamma', '@1', '@2'], + // logic: ['RandomGamma', '@i', '@1'], // }, // [MathAggregationTypes.RANDOMBETA]: { // key: 'numberAggregationRandomBeta', @@ -450,7 +454,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // { name: 'beta', type: 'float' }, // ], // output: { name: 'randomBeta', type: 'float' }, - // logic: ['RandomBeta', '@1', '@2'], + // logic: ['RandomBeta', '@i', '@1'], // }, // [MathAggregationTypes.RANDOMCHISQUARE]: { // key: 'numberAggregationRandomChiSquare', @@ -460,7 +464,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: 'k', type: 'float' }], // output: { name: 'randomChiSquare', type: 'float' }, - // logic: ['RandomChiSquare', '@1'], + // logic: ['RandomChiSquare', '@i'], // }, // [MathAggregationTypes.RANDOMWEIBULL]: { // key: 'numberAggregationRandomWeibull', @@ -473,7 +477,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // { name: 'lambda', type: 'float' }, // ], // output: { name: 'randomWeibull', type: 'float' }, - // logic: ['RandomWeibull', '@1', '@2'], + // logic: ['RandomWeibull', '@i', '@1'], // }, // [MathAggregationTypes.RANDOMCAUCHY]: { // key: 'numberAggregationRandomCauchy', @@ -486,7 +490,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // { name: 'gamma', type: 'float' }, // ], // output: { name: 'randomCauchy', type: 'float' }, - // logic: ['RandomCauchy', '@1', '@2'], + // logic: ['RandomCauchy', '@i', '@1'], // }, // [MathAggregationTypes.RANDOMPOISSON]: { // key: 'numberAggregationRandomPoisson', @@ -496,7 +500,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: 'lambda', type: 'float' }], // output: { name: 'randomPoisson', type: 'float' }, - // logic: ['RandomPoisson', '@1'], + // logic: ['RandomPoisson', '@i'], // }, // [MathAggregationTypes.RANDOMIRWINHALL]: { // key: 'numberAggregationRandomIrwinHall', @@ -509,7 +513,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // { name: 'scale', type: 'float' }, // ], // output: { name: 'randomIrwinHall', type: 'float' }, - // logic: ['RandomIrwinHall', '@1', '@2'], + // logic: ['RandomIrwinHall', '@i', '@1'], // }, // [MathAggregationTypes.CHIQUARETEST]: { // key: 'numberAggregationChiSquareTest', @@ -522,7 +526,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // { name: 'expected', type: 'float' }, // ], // output: { name: 'chiSquareTest', type: 'float' }, - // logic: ['ChiSquareTest', '@1', '@2'], + // logic: ['ChiSquareTest', '@i', '@1'], // }, // [MathAggregationTypes.CORRELATION]: { // key: 'numberAggregationCorrelation', @@ -532,10 +536,10 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 2, // inputs: [ // { name: '1', type: 'float' }, - // { name: '2', type: 'float' }, + // { name: '1', type: 'float' }, // ], // output: { name: 'correlation', type: 'float' }, - // logic: ['Correlation', '@1', '@2'], + // logic: ['Correlation', '@i', '@1'], // }, // [MathAggregationTypes.COVARIANCE]: { // key: 'numberAggregationCovariance', @@ -545,10 +549,10 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 2, // inputs: [ // { name: '1', type: 'float' }, - // { name: '2', type: 'float' }, + // { name: '1', type: 'float' }, // ], // output: { name: 'covariance', type: 'float' }, - // logic: ['Covariance', '@1', '@2'], + // logic: ['Covariance', '@i', '@1'], // }, // [MathAggregationTypes.FREQUENCY]: { // key: 'numberAggregationFrequency', @@ -561,7 +565,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // { name: 'dataset', type: 'float' }, // ], // output: { name: 'frequency', type: 'float' }, - // logic: ['Frequency', '@1', '@2'], + // logic: ['Frequency', '@i', '@1'], // }, // [MathAggregationTypes.MEAN]: { // key: 'numberAggregationMean', @@ -571,7 +575,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: 'dataset', type: 'float' }], // output: { name: 'mean', type: 'float' }, - // logic: ['Mean', '@1'], + // logic: ['Mean', '@i'], // }, // [MathAggregationTypes.MEDIAN]: { // key: 'numberAggregationMedian', @@ -581,7 +585,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: 'dataset', type: 'float' }], // output: { name: 'median', type: 'float' }, - // logic: ['Median', '@1'], + // logic: ['Median', '@i'], // }, // [MathAggregationTypes.MODE]: { // key: 'numberAggregationMode', @@ -591,7 +595,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: 'dataset', type: 'float' }], // output: { name: 'mode', type: 'float' }, - // logic: ['Mode', '@1'], + // logic: ['Mode', '@i'], // }, // [MathAggregationTypes.RANK]: { // key: 'numberAggregationRank', @@ -604,7 +608,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // { name: 'dataset', type: 'float' }, // ], // output: { name: 'rank', type: 'float' }, - // logic: ['Rank', '@1', '@2'], + // logic: ['Rank', '@i', '@1'], // }, // [MathAggregationTypes.STDEV]: { // key: 'numberAggregationStdev', @@ -614,7 +618,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: 'dataset', type: 'float' }], // output: { name: 'stdev', type: 'float' }, - // logic: ['Stdev', '@1'], + // logic: ['Stdev', '@i'], // }, // [MathAggregationTypes.VARIANCE]: { // key: 'numberAggregationVariance', @@ -624,7 +628,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: 'dataset', type: 'float' }], // output: { name: 'variance', type: 'float' }, - // logic: ['Variance', '@1'], + // logic: ['Variance', '@i'], // }, // [MathAggregationTypes.ZSCORE]: { // key: 'numberAggregationZscore', @@ -637,7 +641,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // { name: 'dataset', type: 'float' }, // ], // output: { name: 'zscore', type: 'float' }, - // logic: ['Zscore', '@1', '@2'], + // logic: ['Zscore', '@i', '@1'], // }, // [MathAggregationTypes.AND]: { // key: 'numberAggregationAnd', @@ -647,10 +651,10 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 2, // inputs: [ // { name: '1', type: 'boolean' }, - // { name: '2', type: 'boolean' }, + // { name: '1', type: 'boolean' }, // ], // output: { name: 'and', type: 'boolean' }, - // logic: ['And', '@1', '@2'], + // logic: ['And', '@i', '@1'], // }, // [MathAggregationTypes.OR]: { // key: 'numberAggregationOr', @@ -660,10 +664,10 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 2, // inputs: [ // { name: '1', type: 'boolean' }, - // { name: '2', type: 'boolean' }, + // { name: '1', type: 'boolean' }, // ], // output: { name: 'or', type: 'boolean' }, - // logic: ['Or', '@1', '@2'], + // logic: ['Or', '@i', '@1'], // }, // [MathAggregationTypes.NOT]: { // key: 'numberAggregationNot', @@ -673,7 +677,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'boolean' }], // output: { name: 'not', type: 'boolean' }, - // logic: ['Not', '@1'], + // logic: ['Not', '@i'], // }, // { @@ -684,7 +688,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'count', type: 'float' }, - // logic: ['Count', '@1'], + // logic: ['Count', '@i'], // }, // { // key: 'numberAggregationMax', @@ -694,7 +698,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'max', type: 'float' }, - // logic: ['Max', '@1'], + // logic: ['Max', '@i'], // }, // { // key: 'numberAggregationMin', @@ -704,7 +708,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'min', type: 'float' }, - // logic: ['Min', '@1'], + // logic: ['Min', '@i'], // }, // { // key: 'numberAggregationSum', @@ -714,7 +718,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 1, // inputs: [{ name: '1', type: 'float' }], // output: { name: 'sum', type: 'float' }, - // logic: ['Sum', '@1'], + // logic: ['Sum', '@i'], // }, // // { // // key: 'numberAggregationStd', @@ -724,7 +728,7 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // // numInputs: 1, // // inputs: [{ name: '1', type: 'float' }], // // output: { name: 'std', type: 'float' }, - // // logic: ['Std', '@1'], + // // logic: ['Std', '@i'], // // }, // { // key: 'numberAggregationAdd', @@ -734,10 +738,10 @@ export const MathAggregations: Record<NumberAggregationTypes, GeneralDescription // numInputs: 2, // inputs: [ // { name: '1', type: 'float' }, - // { name: '2', type: 'float' }, + // { name: '1', type: 'float' }, // ], // output: { name: '+', type: 'float' }, - // logic: ['+', '@1', '@2'], + // logic: ['+', '@i', '@1'], // }, }; diff --git a/libs/shared/lib/querybuilder/model/logic/numberFilters.tsx b/libs/shared/lib/querybuilder/model/logic/numberFilters.tsx index b54336f44..3f1ba9d85 100644 --- a/libs/shared/lib/querybuilder/model/logic/numberFilters.tsx +++ b/libs/shared/lib/querybuilder/model/logic/numberFilters.tsx @@ -13,13 +13,11 @@ export const MathFilters: Record<NumberFilterTypes, GeneralDescription<NumberFil name: 'Equal', type: NumberFilterTypes.EQUAL, description: 'Equal to another value', - numInputs: 2, - inputs: [ - { name: '1', type: 'float', default: 0 }, - { name: '2', type: 'float', default: 0 }, - ], + input: { name: 'Value', type: 'float', default: 0 }, + numExtraInputs: 1, + inputs: [{ name: '1', type: 'float', default: 0 }], output: { name: '==', type: 'float' }, - logic: ['==', '@1', '@2'], + logic: ['==', '@i', '@1'], icon: '=', }, [NumberFilterTypes.NOT_EQUAL]: { @@ -27,13 +25,11 @@ export const MathFilters: Record<NumberFilterTypes, GeneralDescription<NumberFil name: 'Not Equal', type: NumberFilterTypes.NOT_EQUAL, description: 'Not equal to another value', - numInputs: 2, - inputs: [ - { name: '1', type: 'float', default: 0 }, - { name: '2', type: 'float', default: 0 }, - ], + input: { name: 'Value', type: 'float', default: 0 }, + numExtraInputs: 1, + inputs: [{ name: '1', type: 'float', default: 0 }], output: { name: '!=', type: 'float' }, - logic: ['!=', '@1', '@2'], + logic: ['!=', '@i', '@1'], icon: '≠', }, [NumberFilterTypes.LESS_THAN]: { @@ -41,13 +37,11 @@ export const MathFilters: Record<NumberFilterTypes, GeneralDescription<NumberFil name: 'Less Than', type: NumberFilterTypes.LESS_THAN, description: 'Less than another value', - numInputs: 2, - inputs: [ - { name: '1', type: 'float', default: 0 }, - { name: '2', type: 'float', default: 0 }, - ], + input: { name: 'Value', type: 'float', default: 0 }, + numExtraInputs: 1, + inputs: [{ name: '1', type: 'float', default: 0 }], output: { name: '<', type: 'float' }, - logic: ['<', '@1', '@2'], + logic: ['<', '@i', '@1'], icon: '<', }, [NumberFilterTypes.LESS_THAN_EQUAL]: { @@ -55,13 +49,11 @@ export const MathFilters: Record<NumberFilterTypes, GeneralDescription<NumberFil name: 'Less Than or Equal', type: NumberFilterTypes.LESS_THAN_EQUAL, description: 'Less than or equal to another value', - numInputs: 2, - inputs: [ - { name: '1', type: 'float', default: 0 }, - { name: '2', type: 'float', default: 0 }, - ], + input: { name: 'Value', type: 'float', default: 0 }, + numExtraInputs: 1, + inputs: [{ name: '1', type: 'float', default: 0 }], output: { name: '<=', type: 'float' }, - logic: ['<=', '@1', '@2'], + logic: ['<=', '@i', '@1'], icon: '≤', }, [NumberFilterTypes.GREATER_THAN]: { @@ -69,14 +61,11 @@ export const MathFilters: Record<NumberFilterTypes, GeneralDescription<NumberFil name: 'Greater Than', type: NumberFilterTypes.GREATER_THAN, description: 'Greater than another value', - - numInputs: 2, - inputs: [ - { name: '1', type: 'float', default: 0 }, - { name: '2', type: 'float', default: 0 }, - ], + input: { name: 'Value', type: 'float', default: 0 }, + numExtraInputs: 1, + inputs: [{ name: '1', type: 'float', default: 0 }], output: { name: '>', type: 'float' }, - logic: ['>', '@1', '@2'], + logic: ['>', '@i', '@1'], icon: '>', }, [NumberFilterTypes.GREATER_THAN_EQUAL]: { @@ -84,13 +73,11 @@ export const MathFilters: Record<NumberFilterTypes, GeneralDescription<NumberFil name: 'Greater Than or Equal', type: NumberFilterTypes.GREATER_THAN_EQUAL, description: 'Greater than or equal to another value', - numInputs: 2, - inputs: [ - { name: '1', type: 'float', default: 0 }, - { name: '2', type: 'float', default: 0 }, - ], + input: { name: 'Value', type: 'float', default: 0 }, + numExtraInputs: 1, + inputs: [{ name: '1', type: 'float', default: 0 }], output: { name: '>=', type: 'float' }, - logic: ['>=', '@1', '@2'], + logic: ['>=', '@i', '@1'], icon: '≥', }, }; diff --git a/libs/shared/lib/querybuilder/model/logic/numberFunctions.tsx b/libs/shared/lib/querybuilder/model/logic/numberFunctions.tsx index 25d6c6899..65f70fd7d 100644 --- a/libs/shared/lib/querybuilder/model/logic/numberFunctions.tsx +++ b/libs/shared/lib/querybuilder/model/logic/numberFunctions.tsx @@ -13,13 +13,11 @@ export const NumberFunctions: Record<NumberFunctionTypes, GeneralDescription<Num name: 'Add', type: NumberFunctionTypes.ADD, description: 'Add two values', - numInputs: 2, - inputs: [ - { name: '1', type: 'float', default: 0 }, - { name: '2', type: 'float', default: 0 }, - ], + input: { name: 'Value', type: 'float', default: 0 }, + numExtraInputs: 1, + inputs: [{ name: '1', type: 'float', default: 0 }], output: { name: '+', type: 'float' }, - logic: ['+', '@1', '@2'], + logic: ['+', '@i', '@1'], icon: '+', }, [NumberFunctionTypes.SUBTRACT]: { @@ -27,13 +25,11 @@ export const NumberFunctions: Record<NumberFunctionTypes, GeneralDescription<Num name: 'Subtract', type: NumberFunctionTypes.SUBTRACT, description: 'Subtract two values', - numInputs: 2, - inputs: [ - { name: '1', type: 'float', default: 0 }, - { name: '2', type: 'float', default: 0 }, - ], + input: { name: 'Value', type: 'float', default: 0 }, + numExtraInputs: 1, + inputs: [{ name: '1', type: 'float', default: 0 }], output: { name: '-', type: 'float' }, - logic: ['-', '@1', '@2'], + logic: ['-', '@i', '@1'], icon: '-', }, [NumberFunctionTypes.MULTIPLY]: { @@ -41,13 +37,11 @@ export const NumberFunctions: Record<NumberFunctionTypes, GeneralDescription<Num name: 'Multiply', type: NumberFunctionTypes.MULTIPLY, description: 'Multiply two values', - numInputs: 2, - inputs: [ - { name: '1', type: 'float', default: 0 }, - { name: '2', type: 'float', default: 0 }, - ], + input: { name: 'Value', type: 'float', default: 0 }, + numExtraInputs: 1, + inputs: [{ name: '1', type: 'float', default: 0 }], output: { name: '*', type: 'float' }, - logic: ['*', '@1', '@2'], + logic: ['*', '@i', '@1'], icon: '×', }, [NumberFunctionTypes.DIVIDE]: { @@ -55,14 +49,11 @@ export const NumberFunctions: Record<NumberFunctionTypes, GeneralDescription<Num name: 'Divide', type: NumberFunctionTypes.DIVIDE, description: 'Divide two values', - - numInputs: 2, - inputs: [ - { name: '1', type: 'float', default: 0 }, - { name: '2', type: 'float', default: 0 }, - ], + input: { name: 'Value', type: 'float', default: 0 }, + numExtraInputs: 1, + inputs: [{ name: '1', type: 'float', default: 0 }], output: { name: '/', type: 'float' }, - logic: ['/', '@1', '@2'], + logic: ['/', '@i', '@1'], icon: '÷', }, }; diff --git a/libs/shared/lib/querybuilder/model/logic/stringFilters.tsx b/libs/shared/lib/querybuilder/model/logic/stringFilters.tsx index 5799d1804..3012370e7 100644 --- a/libs/shared/lib/querybuilder/model/logic/stringFilters.tsx +++ b/libs/shared/lib/querybuilder/model/logic/stringFilters.tsx @@ -13,26 +13,22 @@ export const StringFilters: Record<StringFilterTypes, GeneralDescription<StringF name: 'Equal', type: StringFilterTypes.EQUAL, description: 'Equal to another value', - numInputs: 1, - inputs: [ - { name: '1', type: 'string', default: '' }, - { name: '2', type: 'string', default: '' }, - ], + input: { name: 'Value', type: 'string', default: '' }, + numExtraInputs: 1, + inputs: [{ name: '1', type: 'string', default: '' }], output: { name: StringFilterTypes.EQUAL, type: 'bool' }, - logic: [StringFilterTypes.EQUAL, '@1', '@2'], + logic: [StringFilterTypes.EQUAL, '@i', '@1'], }, [StringFilterTypes.NOT_EQUAL]: { key: 'stringFilterNotEqual', name: 'Not Equal', type: StringFilterTypes.NOT_EQUAL, description: 'Not equal to another value', - numInputs: 1, - inputs: [ - { name: '1', type: 'string', default: '' }, - { name: '2', type: 'string', default: '' }, - ], + input: { name: 'Value', type: 'string', default: '' }, + numExtraInputs: 1, + inputs: [{ name: '1', type: 'string', default: '' }], output: { name: StringFilterTypes.NOT_EQUAL, type: 'bool' }, - logic: [StringFilterTypes.NOT_EQUAL, '@1', '@2'], + logic: [StringFilterTypes.NOT_EQUAL, '@i', '@1'], }, // [StringFilterTypes.IN]: { // key: 'stringFilterContains', @@ -42,23 +38,21 @@ export const StringFilters: Record<StringFilterTypes, GeneralDescription<StringF // numInputs: 1, // inputs: [ // { name: '1', type: 'string', default: '' }, - // { name: '2', type: 'string', default: '' }, + // { name: '1', type: 'string', default: '' }, // ], // output: { name: StringFilterTypes.IN, type: 'bool' }, - // logic: [StringFilterTypes.IN, '@1', '@2'], + // logic: [StringFilterTypes.IN, '@i', '@1'], // }, [StringFilterTypes.LIKE]: { key: 'stringFilterLike', name: 'Like', type: StringFilterTypes.LIKE, description: 'Like another value', - numInputs: 1, - inputs: [ - { name: '1', type: 'string', default: '' }, - { name: '2', type: 'string', default: '' }, - ], + input: { name: 'Value', type: 'string', default: '' }, + numExtraInputs: 1, + inputs: [{ name: '1', type: 'string', default: '' }], output: { name: StringFilterTypes.LIKE, type: 'bool' }, - logic: [StringFilterTypes.LIKE, '@1', '@2'], + logic: [StringFilterTypes.LIKE, '@i', '@1'], }, }; diff --git a/libs/shared/lib/querybuilder/model/logic/stringFunctions.tsx b/libs/shared/lib/querybuilder/model/logic/stringFunctions.tsx index 3ba9a3d1a..befc8e686 100644 --- a/libs/shared/lib/querybuilder/model/logic/stringFunctions.tsx +++ b/libs/shared/lib/querybuilder/model/logic/stringFunctions.tsx @@ -16,27 +16,29 @@ export const StringFunctions: Record<StringFunctionTypes, GeneralDescription<Str // numInputs: 1, // inputs: [{ name: '1', type: 'string', default: '' }], // output: { name: 'lower_case', type: 'string' }, - // logic: ['Lower', '@1'], + // logic: ['Lower', '@i'], // }, [StringFunctionTypes.LOWER]: { key: 'stringFunctionLowerCase', name: 'Lower Case', type: StringFunctionTypes.LOWER, description: 'Lowercase all characters', - numInputs: 1, - inputs: [{ name: '1', type: 'string', default: '' }], + input: { name: 'Value', type: 'string', default: '' }, + numExtraInputs: 0, + inputs: [], output: { name: 'lower_case', type: 'string' }, - logic: ['Lower', '@1'], + logic: ['Lower', '@i'], }, [StringFunctionTypes.UPPER]: { key: 'stringFunctionUpperCase', name: 'Upper Case', type: StringFunctionTypes.UPPER, description: 'Uppercase all characters', - numInputs: 1, - inputs: [{ name: '1', type: 'string', default: '' }], + input: { name: 'Value', type: 'string', default: '' }, + numExtraInputs: 0, + inputs: [], output: { name: 'upper_case', type: 'string' }, - logic: ['Upper', '@1'], + logic: ['Upper', '@i'], }, }; diff --git a/libs/shared/lib/querybuilder/panel/QueryBuilder.tsx b/libs/shared/lib/querybuilder/panel/QueryBuilder.tsx index f5b3872b7..5793a554b 100644 --- a/libs/shared/lib/querybuilder/panel/QueryBuilder.tsx +++ b/libs/shared/lib/querybuilder/panel/QueryBuilder.tsx @@ -30,7 +30,7 @@ import { ControlContainer } from '../../components/controls'; import { addError } from '../../data-access/store/configSlice'; import { toSchemaGraphology } from '../../data-access/store/schemaSlice'; import { LayoutFactory } from '../../graph-layout'; -import { AllLogicMap, QueryElementTypes, createReactFlowElements, isLogicHandle, toHandleData } from '../model'; +import { AllLogicMap, QueryElementTypes, SchemaReactflowLogicNode, createReactFlowElements, isLogicHandle, toHandleData } from '../model'; import { ConnectionDragLine, ConnectionLine, QueryEntityPill, QueryRelationPill } from '../pills'; import { QueryLogicPill } from '../pills/customFlowPills/logicpill/QueryLogicPill'; import { dragPillStarted, movePillTo } from '../pills/dragging/dragPill'; @@ -42,6 +42,7 @@ import { ConnectingNodeDataI } from './utils/connectorDrop'; import { CameraAlt, Cached, Difference, ImportExport, Lightbulb, Settings, Fullscreen, Delete } from '@mui/icons-material'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components/tooltip'; import { resultSetFocus } from '../../data-access/store/interactionSlice'; +import { QueryBuilderDispatcherContext } from './QueryBuilderDispatcher'; export type QueryBuilderProps = { onRunQuery?: () => void; @@ -76,6 +77,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { const dispatch = useDispatch(); const isDraggingPill = useRef(false); const connectingNodeId = useRef<ConnectingNodeDataI | null>(null); + const editLogicNode = useRef<SchemaReactflowLogicNode | undefined>(undefined); const reactFlow = useReactFlow(); const isEdgeUpdating = useRef(false); const isOnConnect = useRef(false); @@ -265,7 +267,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { break; default: const logic = AllLogicMap[dragData.value.key]; - const firstLeftLogicInput = logic.inputs?.[0]; + const firstLeftLogicInput = logic.input; if (!firstLeftLogicInput) return; const logicNode = graphologyGraph.addLogicPill2Graphology({ @@ -436,224 +438,235 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { }, [queryBuilderSettings]); return ( - <div ref={reactFlowWrapper} className="h-full w-full flex flex-col"> - <QueryMLDialog open={toggleSettings === 'ml'} onClose={() => setToggleSettings(undefined)} /> - - <div className="sticky shrink-0 top-0 flex items-stretch justify-between h-7 bg-secondary-100 border-b border-secondary-200 max-w-full"> - <div className="flex items-center"> - <h1 className="text-xs font-semibold text-secondary-600 px-2 truncate">Query builder</h1> - </div> - <div className="shrink-0 sticky right-0 px-0.5 ml-auto items-center flex"> - <ControlContainer> - <TooltipProvider delayDuration={0}> - <Tooltip> - <TooltipTrigger asChild> - <Button type="secondary" variant="ghost" size="xs" iconComponent={<Fullscreen />} onClick={fitView} /> - </TooltipTrigger> - <TooltipContent side={'top'}> - <p>Fit to screen</p> - </TooltipContent> - </Tooltip> - <Tooltip> - <TooltipTrigger asChild> - <Button type="secondary" variant="ghost" size="xs" iconComponent={<Delete />} onClick={() => clearAllNodes()} /> - </TooltipTrigger> - <TooltipContent side={'top'}> - <p>Clear query panel</p> - </TooltipContent> - </Tooltip> - <Tooltip> - <TooltipTrigger asChild> - <Button - type="secondary" - variant="ghost" - size="xs" - iconComponent={<CameraAlt />} - onClick={(event) => { - event.stopPropagation(); - }} - /> - </TooltipTrigger> - <TooltipContent side={'top'}> - <p>Capture screen</p> - </TooltipContent> - </Tooltip> - <Tooltip> - <TooltipTrigger asChild> - <Button - type="secondary" - variant="ghost" - size="xs" - iconComponent={<ImportExport />} - onClick={(event) => { - event.stopPropagation(); - applyLayout(); - }} - /> - </TooltipTrigger> - <TooltipContent side={'top'}> - <p>Layouts</p> - </TooltipContent> - </Tooltip> - <Tooltip> - <TooltipTrigger asChild> - <Button - type="secondary" - variant="ghost" - size="xs" - iconComponent={<Settings />} - className="query-settings" - onClick={(event) => { - event.stopPropagation(); - if (toggleSettings === 'settings') setToggleSettings(undefined); - else setToggleSettings('settings'); - }} - /> - </TooltipTrigger> - <TooltipContent side={'top'} disabled={toggleSettings === 'settings'}> - <p>Query builder settings</p> - </TooltipContent> - </Tooltip> - <Tooltip> - <TooltipTrigger asChild> - <Button - type="secondary" - variant="ghost" - size="xs" - iconComponent={<Cached />} - onClick={(event) => { - event.stopPropagation(); - if (props.onRunQuery) props.onRunQuery(); - }} - /> - </TooltipTrigger> - <TooltipContent side={'top'}> - <p>Rerun query</p> - </TooltipContent> - </Tooltip> - <Tooltip> - <TooltipTrigger asChild> - <Button - type="secondary" - variant="ghost" - size="xs" - iconComponent={<Difference />} - onClick={(event) => { - event.stopPropagation(); - if (toggleSettings === 'logic') setToggleSettings(undefined); - else setToggleSettings('logic'); - }} - /> - </TooltipTrigger> - <TooltipContent side={'top'} disabled={toggleSettings === 'logic'}> - <p>Logic settings</p> - </TooltipContent> - </Tooltip> - <Tooltip> - <TooltipTrigger asChild> - <Button - type="secondary" - variant="ghost" - size="xs" - iconComponent={<Lightbulb />} - onClick={(event) => { - event.stopPropagation(); - if (toggleSettings === 'ml') setToggleSettings(undefined); - else setToggleSettings('ml'); - }} - /> - </TooltipTrigger> - <TooltipContent side={'top'} disabled={toggleSettings === 'ml'}> - <p>Machine learning</p> - </TooltipContent> - </Tooltip> - </TooltipProvider> - </ControlContainer> + <QueryBuilderDispatcherContext.Provider + value={{ + openLogicPillChooser: (node) => { + editLogicNode.current = node; + setToggleSettings('logic'); + }, + }} + > + <div ref={reactFlowWrapper} className="h-full w-full flex flex-col"> + <QueryMLDialog open={toggleSettings === 'ml'} onClose={() => setToggleSettings(undefined)} /> + + <div className="sticky shrink-0 top-0 flex items-stretch justify-between h-7 bg-secondary-100 border-b border-secondary-200 max-w-full"> + <div className="flex items-center"> + <h1 className="text-xs font-semibold text-secondary-600 px-2 truncate">Query builder</h1> + </div> + <div className="shrink-0 sticky right-0 px-0.5 ml-auto items-center flex"> + <ControlContainer> + <TooltipProvider delayDuration={0}> + <Tooltip> + <TooltipTrigger asChild> + <Button type="secondary" variant="ghost" size="xs" iconComponent={<Fullscreen />} onClick={fitView} /> + </TooltipTrigger> + <TooltipContent side={'top'}> + <p>Fit to screen</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger asChild> + <Button type="secondary" variant="ghost" size="xs" iconComponent={<Delete />} onClick={() => clearAllNodes()} /> + </TooltipTrigger> + <TooltipContent side={'top'}> + <p>Clear query panel</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger asChild> + <Button + type="secondary" + variant="ghost" + size="xs" + iconComponent={<CameraAlt />} + onClick={(event) => { + event.stopPropagation(); + }} + /> + </TooltipTrigger> + <TooltipContent side={'top'}> + <p>Capture screen</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger asChild> + <Button + type="secondary" + variant="ghost" + size="xs" + iconComponent={<ImportExport />} + onClick={(event) => { + event.stopPropagation(); + applyLayout(); + }} + /> + </TooltipTrigger> + <TooltipContent side={'top'}> + <p>Layouts</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger asChild> + <Button + type="secondary" + variant="ghost" + size="xs" + iconComponent={<Settings />} + className="query-settings" + onClick={(event) => { + event.stopPropagation(); + if (toggleSettings === 'settings') setToggleSettings(undefined); + else setToggleSettings('settings'); + }} + /> + </TooltipTrigger> + <TooltipContent side={'top'} disabled={toggleSettings === 'settings'}> + <p>Query builder settings</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger asChild> + <Button + type="secondary" + variant="ghost" + size="xs" + iconComponent={<Cached />} + onClick={(event) => { + event.stopPropagation(); + if (props.onRunQuery) props.onRunQuery(); + }} + /> + </TooltipTrigger> + <TooltipContent side={'top'}> + <p>Rerun query</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger asChild> + <Button + type="secondary" + variant="ghost" + size="xs" + iconComponent={<Difference />} + onClick={(event) => { + event.stopPropagation(); + if (toggleSettings === 'logic') setToggleSettings(undefined); + else setToggleSettings('logic'); + }} + /> + </TooltipTrigger> + <TooltipContent side={'top'} disabled={toggleSettings === 'logic'}> + <p>Logic settings</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger asChild> + <Button + type="secondary" + variant="ghost" + size="xs" + iconComponent={<Lightbulb />} + onClick={(event) => { + event.stopPropagation(); + if (toggleSettings === 'ml') setToggleSettings(undefined); + else setToggleSettings('ml'); + }} + /> + </TooltipTrigger> + <TooltipContent side={'top'} disabled={toggleSettings === 'ml'}> + <p>Machine learning</p> + </TooltipContent> + </Tooltip> + </TooltipProvider> + </ControlContainer> + </div> </div> - </div> - <Dialog - open={toggleSettings === 'logic'} - onClose={() => { - setToggleSettings(undefined); - }} - > - <QueryBuilderLogicPillsPanel - onClick={(v) => { - connectingNodeId.current = null; + <Dialog + open={toggleSettings === 'logic'} + onClose={() => { setToggleSettings(undefined); }} - reactFlowWrapper={reactFlowWrapper.current} - title="Logic Pills usable by the node" - className="min-h-[75vh] max-h-[75vh]" - onDrag={() => {}} - connection={connectingNodeId?.current} - /> - </Dialog> - <Dialog - open={toggleSettings === 'relatedNodes'} - onClose={() => { - setToggleSettings(undefined); - }} - > - <QueryBuilderRelatedNodesPanel - onFinished={() => { - connectingNodeId.current = null; + > + <QueryBuilderLogicPillsPanel + onClick={(v) => { + connectingNodeId.current = null; + editLogicNode.current = undefined; + setToggleSettings(undefined); + }} + reactFlowWrapper={reactFlowWrapper.current} + title="Logic Pills usable by the node" + className="min-h-[75vh] max-h-[75vh]" + onDrag={() => {}} + connection={connectingNodeId?.current} + editNode={editLogicNode.current} + /> + </Dialog> + <Dialog + open={toggleSettings === 'relatedNodes'} + onClose={() => { setToggleSettings(undefined); }} - reactFlowWrapper={reactFlowWrapper.current} - title="Related nodes available to add to the query" - className="min-h-[75vh] max-h-[75vh]" - connection={connectingNodeId?.current} - /> - </Dialog> - <svg height={0}> - <defs> - <marker id="arrowIn" markerWidth="9" markerHeight="10" refX={0} refY="5" orient="auto"> - <polygon points="0 0, 9 5, 0 10" /> - </marker> - <marker id="arrowOut" markerWidth="10" markerHeight="10" refX={0} refY="5" orient="auto"> - <polygon points="0 0, 10 5, 0 10" /> - </marker> - </defs> - </svg> - <ReactFlow - edges={elements.edges} - nodes={elements.nodes} - snapGrid={[10, 10]} - zoomOnScroll={allowZoom} - snapToGrid - nodeTypes={nodeTypes} - edgeTypes={edgeTypes} - connectionLineComponent={ConnectionDragLine} - onMouseDownCapture={() => dispatch(resultSetFocus({ focusType: 'query' }))} - // connectionMode={ConnectionMode.Loose} - onInit={(reactFlowInstance) => { - reactFlowInstanceRef.current = reactFlowInstance; - onInit(reactFlowInstance); - }} - onNodesChange={onNodesChange} - onDragOver={onDragOver} - onConnect={onConnect} - onConnectStart={onConnectStart} - onConnectEnd={onConnectEnd} - // onNodeMouseEnter={onNodeMouseEnter} - // onNodeMouseLeave={onNodeMouseLeave} - onEdgeUpdate={onEdgeUpdate} - onEdgeUpdateStart={onEdgeUpdateStart} - onEdgeUpdateEnd={onEdgeUpdateEnd} - onDrop={onDrop} - // onContextMenu={onContextMenu} - onNodeContextMenu={onNodeContextMenu} - // onNodesDelete={onNodesDelete} - // onNodesChange={onNodesChange} - deleteKeyCode="Backspace" - className={styles.reactflow} - proOptions={{ hideAttribution: true }} - > - <Background gap={10} size={0.7} /> - </ReactFlow> - </div> + > + <QueryBuilderRelatedNodesPanel + onFinished={() => { + connectingNodeId.current = null; + setToggleSettings(undefined); + }} + reactFlowWrapper={reactFlowWrapper.current} + title="Related nodes available to add to the query" + className="min-h-[75vh] max-h-[75vh]" + connection={connectingNodeId?.current} + /> + </Dialog> + <svg height={0}> + <defs> + <marker id="arrowIn" markerWidth="9" markerHeight="10" refX={0} refY="5" orient="auto"> + <polygon points="0 0, 9 5, 0 10" /> + </marker> + <marker id="arrowOut" markerWidth="10" markerHeight="10" refX={0} refY="5" orient="auto"> + <polygon points="0 0, 10 5, 0 10" /> + </marker> + </defs> + </svg> + <ReactFlow + edges={elements.edges} + nodes={elements.nodes} + snapGrid={[10, 10]} + zoomOnScroll={allowZoom} + snapToGrid + nodeTypes={nodeTypes} + edgeTypes={edgeTypes} + connectionLineComponent={ConnectionDragLine} + onMouseDownCapture={() => dispatch(resultSetFocus({ focusType: 'query' }))} + // connectionMode={ConnectionMode.Loose} + onInit={(reactFlowInstance) => { + reactFlowInstanceRef.current = reactFlowInstance; + onInit(reactFlowInstance); + }} + onNodesChange={onNodesChange} + onDragOver={onDragOver} + onConnect={onConnect} + onConnectStart={onConnectStart} + onConnectEnd={onConnectEnd} + // onNodeMouseEnter={onNodeMouseEnter} + // onNodeMouseLeave={onNodeMouseLeave} + onEdgeUpdate={onEdgeUpdate} + onEdgeUpdateStart={onEdgeUpdateStart} + onEdgeUpdateEnd={onEdgeUpdateEnd} + onDrop={onDrop} + // onContextMenu={onContextMenu} + onNodeContextMenu={onNodeContextMenu} + // onNodesDelete={onNodesDelete} + // onNodesChange={onNodesChange} + deleteKeyCode="Backspace" + className={styles.reactflow} + proOptions={{ hideAttribution: true }} + > + <Background gap={10} size={0.7} /> + </ReactFlow> + </div> + </QueryBuilderDispatcherContext.Provider> ); }; diff --git a/libs/shared/lib/querybuilder/panel/QueryBuilderDispatcher.tsx b/libs/shared/lib/querybuilder/panel/QueryBuilderDispatcher.tsx new file mode 100644 index 000000000..fd7329e7b --- /dev/null +++ b/libs/shared/lib/querybuilder/panel/QueryBuilderDispatcher.tsx @@ -0,0 +1,10 @@ +import React, { createContext } from 'react'; +import { SchemaReactflowLogicNode } from '../model'; + +export const QueryBuilderDispatcherContext = createContext<{ + openLogicPillChooser: (node: SchemaReactflowLogicNode) => void; +}>({ + openLogicPillChooser: (node: SchemaReactflowLogicNode) => { + throw new Error('openLogicPillChooser not implemented'); + }, +}); diff --git a/libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderLogicPillsPanel.tsx b/libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderLogicPillsPanel.tsx index 27729ccef..39426ce74 100644 --- a/libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderLogicPillsPanel.tsx +++ b/libs/shared/lib/querybuilder/panel/querysidepanel/queryBuilderLogicPillsPanel.tsx @@ -7,7 +7,7 @@ import { } from '@mui/icons-material'; import { useState } from 'react'; -import { AllLogicDescriptions, AllLogicMap, QueryElementTypes, toHandleData } from '../../model'; +import { AllLogicDescriptions, AllLogicMap, QueryElementTypes, SchemaReactflowLogicNode, toHandleData } from '../../model'; import { ConnectingNodeDataI } from '../utils/connectorDrop'; import { useQuerybuilderGraph } from '@graphpolaris/shared/lib/data-access'; import { toQuerybuilderGraphology, setQuerybuilderGraphology } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; @@ -21,6 +21,7 @@ export const QueryBuilderLogicPillsPanel = (props: { onClick: (item: AllLogicDescriptions) => void; onDrag?: (item: AllLogicDescriptions) => void; connection?: ConnectingNodeDataI | null; + editNode?: SchemaReactflowLogicNode; }) => { const graph = useQuerybuilderGraph(); const dispatch = useDispatch(); @@ -28,7 +29,9 @@ export const QueryBuilderLogicPillsPanel = (props: { const [selectedOp, setSelectedOp] = useState(-1); const [selectedType, setSelectedType] = useState(-1); - let filterType = (props.connection?.params?.handleId ? toHandleData(props.connection.params.handleId).attributeType : null) as string; + let filterType = props.editNode + ? props.editNode.data.logic.input.type + : ((props.connection?.params?.handleId ? toHandleData(props.connection.params.handleId).attributeType : null) as string); if (!filterType) return <></>; else if (filterType === 'string') filterType = 'string'; else if (filterType === 'int' || filterType === 'float') filterType = 'number'; @@ -73,8 +76,6 @@ export const QueryBuilderLogicPillsPanel = (props: { const onNewNodeFromPopup = (value: AllLogicDescriptions) => { const logic = AllLogicMap[value.key]; - const firstLeftLogicInput = logic.inputs?.[0]; - if (!firstLeftLogicInput) return; if (props.connection === null || props.connection?.params?.handleId == null) { const bounds = props.reactFlowWrapper?.getBoundingClientRect() || { x: 0, y: 0, width: 0, height: 0 }; @@ -107,7 +108,7 @@ export const QueryBuilderLogicPillsPanel = (props: { graphologyGraph.getNodeAttributes(params.nodeId), graphologyGraph.getNodeAttributes(logicNode.id), { type: 'connection' }, - { sourceHandleName: sourceHandleData.attributeName, targetHandleName: firstLeftLogicInput.name }, + { sourceHandleName: sourceHandleData.attributeName, targetHandleName: logic.input.name }, ); } @@ -115,10 +116,53 @@ export const QueryBuilderLogicPillsPanel = (props: { props.onClick(value); }; + const onEditNodeFromPopup = (value: AllLogicDescriptions) => { + if (!props.editNode?.id) return; + const logic = AllLogicMap[value.key]; + const edges = graphologyGraph + .edges(props.editNode.id) + .map((edge) => ({ id: edge, attributes: graphologyGraph.getEdgeAttributes(edge) })); + console.log('edit node', props.editNode.data, edges); + + // graphologyGraph + graphologyGraph.addLogicPill2Graphology({ + ...props.editNode.data, + name: value.name, + type: QueryElementTypes.Logic, + logic: logic, + }); + + edges.forEach((edge, i) => { + graphologyGraph.dropEdge(edge.id); + + if (i >= logic.numExtraInputs + 1) return; // ignore edges not present in new logic pill + + graphologyGraph.addEdge2Graphology( + graphologyGraph.getNodeAttributes(edge.attributes.sourceHandleData.nodeId), + graphologyGraph.getNodeAttributes(edge.attributes.targetHandleData.nodeId), + { type: 'connection' }, + { + sourceHandleName: edge.attributes.sourceHandleData.attributeName, + targetHandleName: edge.attributes.targetHandleData.attributeName, + }, + ); + }); + + // graphologyGraph.addEdge2Graphology( + // graphologyGraph.getNodeAttributes(params.nodeId), + // graphologyGraph.getNodeAttributes(logicNode.id), + // { type: 'connection' }, + // { sourceHandleName: sourceHandleData.attributeName, targetHandleName: logic.input.name }, + // ); + + dispatch(setQuerybuilderGraphology(graphologyGraph)); + props.onClick(value); + }; + return ( <div className={props.className + ' card'}> {props.title && <h1 className="card-title mb-7">{props.title}</h1>} - <div className="gap-1 flex gap-1"> + <div className="gap-1 flex"> {dataOps.map((item, index) => ( <div key={item.title} data-tip={item.description} className="tooltip tooltip-top m-0 p-0"> <Button @@ -161,7 +205,8 @@ export const QueryBuilderLogicPillsPanel = (props: { onDragStart={(e) => onDragStart(e, item)} draggable={true} onClick={() => { - onNewNodeFromPopup(item); + if (props.editNode) onEditNodeFromPopup(item); + else onNewNodeFromPopup(item); }} > {item.icon && ( diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/QueryLogicPill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/QueryLogicPill.tsx index d3e6f409b..2c2f75c77 100644 --- a/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/QueryLogicPill.tsx +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/QueryLogicPill.tsx @@ -1,17 +1,21 @@ import { useAppDispatch, useQuerybuilderGraph, useQuerybuilderHash } from '@graphpolaris/shared/lib/data-access'; -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useContext, useEffect, useMemo, useRef, useState } from 'react'; import { Handle, Position } from 'reactflow'; -import { Handles, LogicNodeAttributes, SchemaReactflowLogicNode, toHandleId } from '../../../model'; -import { InputNode, InputNodeTypeTypes } from '../../../model/logic/general'; +import { AllLogicTypes, Handles, LogicNodeAttributes, SchemaReactflowLogicNode, toHandleId } from '../../../model'; +import { GeneralDescription, InputNode, InputNodeTypeTypes } from '../../../model/logic/general'; import { styleHandleMap } from '../../utils'; import { setQuerybuilderGraphology, toQuerybuilderGraphology } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; import { LogicInput } from './LogicInput'; -import { LogicPill } from '@graphpolaris/shared/lib/components'; +import { Button, DropdownButton, Input, LogicPill } from '@graphpolaris/shared/lib/components'; +import { ArrowDropDown } from '@mui/icons-material'; +import { QueryBuilderDispatcherContext } from '../../../panel/QueryBuilderDispatcher'; export function QueryLogicPill(node: SchemaReactflowLogicNode) { const dispatch = useAppDispatch(); const data = node.data; const logic = data.logic; + const { openLogicPillChooser } = useContext(QueryBuilderDispatcherContext); + const output = data.logic.output; const inputReference = useRef<HTMLInputElement>(null); @@ -51,20 +55,42 @@ export function QueryLogicPill(node: SchemaReactflowLogicNode) { }, [node.id]); return ( - <LogicPill title="LogicPill"> + <LogicPill + title={connectionsToLeft[0]?.attributes?.sourceHandleData.attributeName} + handleLeft={ + <Handle + className={'!rounded-none !bg-transparent !w-full !h-full !border-0 !right-0 !left-0'} + type="target" + position={Position.Left} + id={toHandleId({ + ...defaultHandleData, + attributeName: logic.input.name, + attributeType: logic.input.type, + handleType: Handles.LogicLeft, + })} + ></Handle> + } + > <div className={`py-1 h-fit border-[1px] border-secondary-200 ${data.selected ? 'bg-secondary-400' : 'bg-secondary-100'}`}> - <div className="m-1 mx-2 text-left"> - {connectionsToLeft.map((e) => e?.attributes?.sourceHandleData.attributeName)}.{output.name} - </div> + {/* <div className="m-1 mx-2 text-left">{output.name}</div> */} + <DropdownButton + title={output.name} + variant="ghost" + size="xs" + onClick={() => { + openLogicPillChooser(node); + }} + /> {node.data.logic.inputs.map((input, i) => { + const connection = connectionsToLeft.find( + (edge) => + edge?.attributes?.targetHandleData.nodeId === data.id && edge?.attributes?.targetHandleData.attributeName === input.name, + ); + return ( <div key={i}> <div className="w-full flex"> - {!connectionsToLeft.some( - (edge) => - edge?.attributes?.targetHandleData.nodeId === data.id && - edge?.attributes?.targetHandleData.attributeName === input.name, - ) && ( + {!connection ? ( <LogicInput value={localInputCache?.[input.name] as string} type={input.type} @@ -74,6 +100,10 @@ export function QueryLogicPill(node: SchemaReactflowLogicNode) { onEnter={() => onInputUpdated(localInputCache?.[input.name] as string, input, i)} onBlur={() => onInputUpdated(localInputCache?.[input.name] as string, input, i)} /> + ) : ( + <span className="px-1 m-0 mx-1 p-0 h-5 w-full rounded-sm border-[1px]"> + {connection?.attributes?.sourceHandleData.attributeName} + </span> )} </div> <Handle @@ -86,13 +116,13 @@ export function QueryLogicPill(node: SchemaReactflowLogicNode) { handleType: Handles.LogicLeft, })} key={input.name + input.type} - style={{ top: `${((i + 0.7) / (node.data.logic.inputs.length + 0.4)) * 100}%` }} + style={{ top: `${((i + 1.05) / (node.data.logic.inputs.length + 0.4)) * 100}%` }} className={styleHandleMap[input.type] + ''} ></Handle> </div> ); })} - {!!node.data.logic.output && ( + {/* {!!node.data.logic.output && ( <Handle type={'source'} position={Position.Right} @@ -104,7 +134,7 @@ export function QueryLogicPill(node: SchemaReactflowLogicNode) { })} className={styleHandleMap?.[output.type]} ></Handle> - )} + )} */} </div> </LogicPill> ); diff --git a/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx b/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx index bb72abda7..e357b0237 100644 --- a/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx +++ b/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx @@ -93,7 +93,7 @@ export const PillDropdown = (props: PillDropdownProps) => { {(props.hovered || forceOpen) && ( <> <h4 className="p-1 bg-white border-t-[2px] font-semibold text-2xs">Available Attributes:</h4> - <TextInput type={'text'} placeholder="Filter" className="!p-0.5" value={filter} onChange={(v) => setFilter(v)} /> + <TextInput type={'text'} placeholder="Filter" size="xs" className="!p-0.5" value={filter} onChange={(v) => setFilter(v)} /> <div className="max-h-28 overflow-auto flex flex-col bg-white"> {props.attributes.map((attribute, i) => { if (filter && !attribute.handleData.attributeName?.toLowerCase().includes(filter.toLowerCase())) return null; diff --git a/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts b/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts index 6438a1e19..a2ed912b9 100644 --- a/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts +++ b/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts @@ -722,7 +722,7 @@ describe('QueryUtils calculateQueryLogic', () => { y: 100, name: 'Airport 1', }, - [{ name: 'age', type: 'string' }] + [{ name: 'age', type: 'string' }], ); const l1 = graph.addLogicPill2Graphology({ @@ -755,7 +755,7 @@ describe('QueryUtils with Logic', () => { y: 100, name: 'Airport 1', }, - [{ name: 'age', type: 'string' }] + [{ name: 'age', type: 'string' }], ); const l1 = graph.addLogicPill2Graphology({ @@ -767,7 +767,7 @@ describe('QueryUtils with Logic', () => { logic: MathFilters[NumberFilterTypes.EQUAL], }); - graph.addEdge2Graphology(e1, l1, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: '1' }); + graph.addEdge2Graphology(e1, l1, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: 'Value' }); const expected: BackendQueryFormat = { ...defaultQuery, @@ -801,7 +801,7 @@ describe('QueryUtils with Logic', () => { y: 100, name: 'Airport 1', }, - [{ name: 'age', type: 'string' }] + [{ name: 'age', type: 'string' }], ); const e2 = graph.addPill2Graphology( @@ -812,7 +812,7 @@ describe('QueryUtils with Logic', () => { y: 100, name: 'Airport 2', }, - [{ name: 'age', type: 'string' }] + [{ name: 'age', type: 'string' }], ); const l1 = graph.addLogicPill2Graphology({ @@ -833,9 +833,9 @@ describe('QueryUtils with Logic', () => { logic: NumberFunctions[NumberFunctionTypes.ADD], }); - graph.addEdge2Graphology(e1, l2, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: '1' }); - graph.addEdge2Graphology(e2, l2, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: '2' }); - graph.addEdge2Graphology(l2, l1, { type: 'connection' }, { sourceHandleName: NumberFilterTypes.EQUAL, targetHandleName: '1' }); + graph.addEdge2Graphology(e1, l2, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: 'Value' }); + graph.addEdge2Graphology(e2, l2, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: '1' }); + graph.addEdge2Graphology(l2, l1, { type: 'connection' }, { sourceHandleName: NumberFilterTypes.EQUAL, targetHandleName: 'Value' }); const expected: BackendQueryFormat = { ...defaultQuery, @@ -877,7 +877,7 @@ describe('QueryUtils with Logic', () => { y: 100, name: 'Airport 1', }, - [{ name: 'age', type: 'string' }] + [{ name: 'age', type: 'string' }], ); const l1 = graph.addLogicPill2Graphology({ @@ -898,8 +898,8 @@ describe('QueryUtils with Logic', () => { logic: MathAggregations[NumberAggregationTypes.AVG], }); - graph.addEdge2Graphology(e1, l2, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: '1' }); - graph.addEdge2Graphology(l2, l1, { type: 'connection' }, { sourceHandleName: NumberAggregationTypes.AVG, targetHandleName: '1' }); + graph.addEdge2Graphology(e1, l2, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: 'Value' }); + graph.addEdge2Graphology(l2, l1, { type: 'connection' }, { sourceHandleName: NumberAggregationTypes.AVG, targetHandleName: 'Value' }); const expected: BackendQueryFormat = { ...defaultQuery, @@ -933,7 +933,7 @@ describe('QueryUtils with Logic', () => { y: 100, name: 'Airport 1', }, - [{ name: 'age', type: 'string' }] + [{ name: 'age', type: 'string' }], ); const l1 = graph.addLogicPill2Graphology( @@ -945,10 +945,10 @@ describe('QueryUtils with Logic', () => { name: 'Logic LT', logic: MathFilters[NumberFilterTypes.LESS_THAN], }, - { '2': 5 } + { '1': 5 }, ); - graph.addEdge2Graphology(e1, l1, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: '1' }); + graph.addEdge2Graphology(e1, l1, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: 'Value' }); const expected: BackendQueryFormat = { ...defaultQuery, @@ -983,7 +983,7 @@ it('should no connections between entities and relations', () => { y: 100, name: 'Airport 1', }, - [{ name: 'age', type: 'string' }] + [{ name: 'age', type: 'string' }], ); const e2 = graph.addPill2Graphology( @@ -994,7 +994,7 @@ it('should no connections between entities and relations', () => { y: 100, name: 'Airport 2', }, - [{ name: 'age', type: 'string' }] + [{ name: 'age', type: 'string' }], ); const r1 = graph.addPill2Graphology( { @@ -1005,7 +1005,7 @@ it('should no connections between entities and relations', () => { name: 'Relation 1', depth: { min: 0, max: 1 }, }, - [{ name: 'age', type: 'string' }] + [{ name: 'age', type: 'string' }], ); const expected: BackendQueryFormat = { diff --git a/libs/shared/lib/querybuilder/query-utils/query2backend.ts b/libs/shared/lib/querybuilder/query-utils/query2backend.ts index 9246a7286..92fd9e6f2 100644 --- a/libs/shared/lib/querybuilder/query-utils/query2backend.ts +++ b/libs/shared/lib/querybuilder/query-utils/query2backend.ts @@ -22,7 +22,7 @@ const traverseEntityRelationPaths = ( graph: QueryMultiGraph, entities: SerializedNode<EntityNodeAttributes>[], relations: SerializedNode<RelationNodeAttributes>[], - settings: QueryBuilderSettings + settings: QueryBuilderSettings, ): number => { if (!node?.attributes) throw Error('Malformed Graph! Node has no attributes'); // console.log(paths); @@ -54,7 +54,7 @@ const traverseEntityRelationPaths = ( const edges = graph.edges.filter( (n) => n?.attributes?.sourceHandleData.nodeType !== QueryElementTypes.Logic && - n?.attributes?.targetHandleData.nodeType !== QueryElementTypes.Logic + n?.attributes?.targetHandleData.nodeType !== QueryElementTypes.Logic, ); let connections = edges.filter((e) => e.source === node.key); if (connections.length === 0) { @@ -97,7 +97,7 @@ const traverseEntityRelationPaths = ( export function calculateQueryLogic( node: SerializedNode<LogicNodeAttributes>, graph: QueryMultiGraph, - logics: SerializedNode<LogicNodeAttributes>[] + logics: SerializedNode<LogicNodeAttributes>[], ): AllLogicStatement { if (!node?.attributes) throw Error('Malformed Graph! Node has no attributes'); let connectionsToLeft = graph.edges.filter((e) => e.target === node.key); @@ -108,9 +108,14 @@ export function calculateQueryLogic( if (l.includes('@')) { // @ means it needs to fetch data from connection - const inputRefIdx = node.attributes.logic.inputs.findIndex((input, i) => input.name === l.replace('@', '')); // fetches the corresponding element from input definition - const inputRef = node.attributes.logic.inputs[inputRefIdx]; - if (!inputRef) throw Error('Malformed Graph! Logic node has incorrect input reference'); + let inputRef = node.attributes.logic.input; + if (l !== '@i' && node.attributes.logic.inputs.length > 0) { + const inputRefIdx = node.attributes.logic.inputs.findIndex((input, i) => input.name === l.replace('@', '')); // fetches the corresponding element from input definition + inputRef = node.attributes.logic.inputs[inputRefIdx]; + } + if (!inputRef) { + throw Error('Malformed Graph! Logic node has incorrect input reference'); + } const connectionToInputRef = connectionsToLeft.find((c) => c?.attributes?.targetHandleData.attributeName === inputRef.name); if (!connectionToInputRef) { @@ -160,7 +165,7 @@ export function Query2BackendQuery( saveStateID: string, graph: QueryMultiGraph, settings: QueryBuilderSettings, - ml: ML = mlDefaultState + ml: ML = mlDefaultState, ): BackendQueryFormat { let query: BackendQueryFormat = { saveStateID: saveStateID, -- GitLab