diff --git a/apps/web/src/app/App.tsx b/apps/web/src/app/App.tsx index 17355b18515c817eda3e5f0c0d4f859ee51ac3d7..215d4ec57134c57e86329b7101cf9fe4e39993db 100644 --- a/apps/web/src/app/App.tsx +++ b/apps/web/src/app/App.tsx @@ -6,6 +6,7 @@ import { useQuerybuilderGraph, useQuerybuilderSettings, useSessionCache, + useQuerybuilderUnionTypes, } from '@graphpolaris/shared/lib/data-access'; import { addError, setCurrentTheme } from '@graphpolaris/shared/lib/data-access/store/configSlice'; import { resetGraphQueryResults, queryingBackend } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice'; @@ -38,6 +39,7 @@ export function App(props: App) { const dispatch = useAppDispatch(); const queryBuilderSettings = useQuerybuilderSettings(); const [monitoringOpen, setMonitoringOpen] = useState<boolean>(false); + const unionTypes = useQuerybuilderUnionTypes(); const runQuery = () => { if (session?.currentSaveState && query) { @@ -45,7 +47,7 @@ export function App(props: App) { dispatch(resetGraphQueryResults()); } else { dispatch(queryingBackend()); - wsQueryRequest(Query2BackendQuery(session.currentSaveState, query, queryBuilderSettings, ml)); + wsQueryRequest(Query2BackendQuery(session.currentSaveState, query, queryBuilderSettings, ml, unionTypes)); } } }; diff --git a/libs/shared/lib/components/pills/Pill.tsx b/libs/shared/lib/components/pills/Pill.tsx index 0164732da422c2d6b1261773fe630093ed786fc5..0799316258efb1d71a44e6b59b1d40aca515eb08 100644 --- a/libs/shared/lib/components/pills/Pill.tsx +++ b/libs/shared/lib/components/pills/Pill.tsx @@ -1,8 +1,9 @@ -import React, { useContext, useState } from 'react'; +import React, { useContext, useState, useMemo } from 'react'; import { pillWidth, pillHeight, pillXPadding, pillInnerMargin, topLineHeight, pillBorderWidth, pillAttributesPadding } from './pill.const'; import { Position } from 'reactflow'; import { PillHandle } from './PillHandle'; import { PillContext } from './PillContext'; +import { QueryUnionType } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; export type PillI = { onHovered?: (hovered: boolean) => void; diff --git a/libs/shared/lib/components/pills/PillHandle.tsx b/libs/shared/lib/components/pills/PillHandle.tsx index 88aa24aaab58e18c6cdea18043d902e1d0e05937..50bf2ea2031a850f34e6d9a2b606468b49f7a3b1 100644 --- a/libs/shared/lib/components/pills/PillHandle.tsx +++ b/libs/shared/lib/components/pills/PillHandle.tsx @@ -10,14 +10,14 @@ export const PillHandle = (props: { children?: React.ReactNode; className?: string; position: Position; - type: 'square' | 'arrowUp' | 'arrowDown'; + type: 'square' | 'diamond' | 'arrowUp' | 'arrowDown'; mr?: number; outerSize?: number; innerSize?: number; }) => { const pillContext = useContext(PillContext); - const outerSize = props.outerSize || (props.type === 'square' ? 6 : 4); - const innerSize = props.innerSize || (props.type === 'square' ? 4 : 6); + const outerSize = props.outerSize || (['square', 'diamond'].includes(props.type) ? 6 : 4); + const innerSize = props.innerSize || (['square', 'diamond'].includes(props.type) ? 4 : 6); const style: React.CSSProperties = { width: outerSize * 2, @@ -49,6 +49,20 @@ export const PillHandle = (props: { <rect x="0.5" y="0.5" width="7" height="7" className={props.className} /> </svg> ); + if (props.type === 'diamond') + return ( + <svg className={'absolute pointer-events-none'} style={innerStyle} width="8" height="8" viewBox="0 0 8 8" fill="none"> + <rect + x="0.5" + y="0.5" + width="7" + height="7" + className={props.className} + style={{ transformOrigin: 'center center' }} + transform="rotate(45)" + /> + </svg> + ); if (props.type === 'arrowUp') return ( <svg width="14" height="7" viewBox="0 0 14 7" fill="none" className={'absolute pointer-events-none'} style={innerStyle}> diff --git a/libs/shared/lib/data-access/api/eventBus.tsx b/libs/shared/lib/data-access/api/eventBus.tsx index d783b723d32a4c5d639ce02ead74eeac076ed6d3..bc471755dc093378de92519c25eaabf783662afc 100644 --- a/libs/shared/lib/data-access/api/eventBus.tsx +++ b/libs/shared/lib/data-access/api/eventBus.tsx @@ -14,11 +14,17 @@ import { wsSchemaSubscription, useQuerybuilderAttributesShown, wsSchemaStatsRequest, + useQuerybuilderUnionTypes, } from '@graphpolaris/shared/lib/data-access'; import { Broker, wsQuerySubscription, wsQueryTranslationSubscription } from '@graphpolaris/shared/lib/data-access/broker'; import { addInfo } from '@graphpolaris/shared/lib/data-access/store/configSlice'; import { allMLTypes, LinkPredictionInstance, setMLResult } from '@graphpolaris/shared/lib/data-access/store/mlSlice'; -import { QueryBuilderText, setQueryText, setQuerybuilderNodes } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; +import { + QueryBuilderText, + queryUnionTypes, + setQueryText, + setQuerybuilderNodes, +} from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; import { useEffect } from 'react'; import { SaveStateI, @@ -64,6 +70,7 @@ export const EventBus = (props: { onRunQuery: Function; onAuthorized: Function } const mlHash = useMLEnabledHash(); const visState = useVisualization(); const queryBuilderSettings = useQuerybuilderSettings(); + const unionTypes = useQuerybuilderUnionTypes(); function loadSaveState(saveStateID: string | undefined, saveStates: Record<string, SaveStateI>) { if (saveStateID && saveStates && saveStateID in saveStates) { @@ -282,7 +289,7 @@ export const EventBus = (props: { onRunQuery: Function; onAuthorized: Function } // body: { type: 'query_builder_state', status: '', value: queryBuilder }, // }); } - }, [queryHash, mlHash, queryBuilderSettings]); + }, [queryHash, mlHash, queryBuilderSettings, unionTypes]); return <div className="hide"></div>; }; diff --git a/libs/shared/lib/data-access/store/hooks.ts b/libs/shared/lib/data-access/store/hooks.ts index 9e7feb3fe4ce8d36c449f2901f17a347fa1f3ffc..16fc370b5030a06ec10e737fdc2027df79293421 100644 --- a/libs/shared/lib/data-access/store/hooks.ts +++ b/libs/shared/lib/data-access/store/hooks.ts @@ -20,6 +20,8 @@ import { queryBuilderState, selectQuerybuilderGraph, selectQuerybuilderHash, + queryUnionTypes, + QueryUnionType } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; import { activeSaveState, activeSaveStateAuthorization, SessionCacheI, sessionCacheState } from './sessionSlice'; import { AuthSliceState, authState } from './authSlice'; @@ -67,6 +69,7 @@ export const useQuerybuilderHash: () => string = () => useAppSelector(selectQuer export const useQuerybuilderSettings: () => QueryBuilderSettings = () => useAppSelector(queryBuilderSettingsState); export const useQuerybuilder: () => QueryBuilderState = () => useAppSelector(queryBuilderState); export const useQuerybuilderAttributesShown: () => QueryGraphEdgeHandle[] = () => useAppSelector(queryBuilderAttributesShown); +export const useQuerybuilderUnionTypes: () => {[nodeId: string]: QueryUnionType} = () => useAppSelector(queryUnionTypes); // Overall Configuration of the app export const useConfig: () => ConfigStateI = () => useAppSelector(configState); diff --git a/libs/shared/lib/data-access/store/querybuilderSlice.ts b/libs/shared/lib/data-access/store/querybuilderSlice.ts index df417947af928f0c66ed2b874afcb56b8c795315..ce77ca76365a1c942848864d64f1529870cfb35a 100644 --- a/libs/shared/lib/data-access/store/querybuilderSlice.ts +++ b/libs/shared/lib/data-access/store/querybuilderSlice.ts @@ -20,6 +20,11 @@ export type QueryBuilderText = { result: string; }; +export enum QueryUnionType { + AND = 'And', + OR = 'Or', +} + export type QueryBuilderAttributeBeingShown = {}; export type QueryBuilderState = { @@ -28,6 +33,7 @@ export type QueryBuilderState = { settings: QueryBuilderSettings; queryTranslation: QueryBuilderText; attributesBeingShown: QueryGraphEdgeHandle[]; + unionTypes: { [nodeId: string]: QueryUnionType }; }; // Define the initial state using that type @@ -45,6 +51,7 @@ export const initialState: QueryBuilderState = { result: '', }, attributesBeingShown: [], + unionTypes: {}, // schemaLayout: 'Graphology_noverlap', } as QueryBuilderState; @@ -83,6 +90,9 @@ export const querybuilderSlice = createSlice({ state.attributesBeingShown.splice(existing, 1); } }, + setQueryUnionType: (state: QueryBuilderState, action: PayloadAction<{ nodeId: string; unionType: QueryUnionType }>) => { + state.unionTypes[action.payload.nodeId] = action.payload.unionType; + }, }, }); @@ -140,7 +150,16 @@ export const selectQuerybuilderHash = (state: RootState): string => { // state.schema.schemaLayout; export default querybuilderSlice.reducer; -export const { setQuerybuilderGraph, clearQB, setQuerybuilderSettings, setQuerybuilderNodes, setQueryText, attributeShownToggle } = - querybuilderSlice.actions; +export const { + setQuerybuilderGraph, + clearQB, + setQuerybuilderSettings, + setQuerybuilderNodes, + setQueryText, + attributeShownToggle, + setQueryUnionType, +} = querybuilderSlice.actions; export const queryBuilderAttributesShown = (state: RootState) => state.querybuilder.attributesBeingShown; + +export const queryUnionTypes = (state: RootState) => state.querybuilder.unionTypes; diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/QueryEntityPill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/QueryEntityPill.tsx index 0aa50ebbaa262b490f867a65ecb9a523da8eca63..f3900d7a4f51bef00815fac4a92cb5aa0fa61c34 100644 --- a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/QueryEntityPill.tsx +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/QueryEntityPill.tsx @@ -1,4 +1,16 @@ -import { useQuerybuilderAttributesShown, useQuerybuilderGraph, useQuerybuilderHash } from '@graphpolaris/shared/lib/data-access'; +import { + useQuerybuilderAttributesShown, + useQuerybuilderGraph, + useQuerybuilderHash, + useQuerybuilderUnionTypes, +} from '@graphpolaris/shared/lib/data-access'; +import { + setQuerybuilderGraphology, + toQuerybuilderGraphology, + attributeShownToggle, + setQueryUnionType, + QueryUnionType, +} from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Button } from '@graphpolaris/shared/lib/components/buttons'; import { Icon } from '@graphpolaris/shared/lib/components/icon'; @@ -9,13 +21,8 @@ import { NodeAttribute, SchemaReactflowEntityNode, toHandleId } from '../../../m import { PillAttributes } from '../../pillattributes/PillAttributes'; import { DropdownTrigger, DropdownContainer, DropdownItemContainer, DropdownItem } from '@graphpolaris/shared/lib/components/dropdowns'; import { PopoverContext } from '@graphpolaris/shared/lib/components/layout/Popover'; -import { - toQuerybuilderGraphology, - setQuerybuilderGraphology, - attributeShownToggle, -} from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; -import { isEqual } from 'lodash-es'; import { useDispatch } from 'react-redux'; +import { isEqual } from 'lodash-es'; /** * Component to render an entity flow element @@ -68,6 +75,12 @@ export const QueryEntityPill = React.memo((node: SchemaReactflowEntityNode) => { dispatch(attributeShownToggle(attribute.handleData)); } + const unionType = useQuerybuilderUnionTypes()[node.id]; + + function setUnionType(unionType: QueryUnionType) { + dispatch(setQueryUnionType({ nodeId: node.id, unionType: unionType })); + } + const attributesBeingShown = useQuerybuilderAttributesShown(); function isAttributeAdded(attribute: NodeAttribute): boolean { return attributesBeingShown.some((x) => isEqual(x, attribute.handleData)); @@ -93,7 +106,7 @@ export const QueryEntityPill = React.memo((node: SchemaReactflowEntityNode) => { <DropdownItemContainer> <PopoverContext.Consumer> - {(popover) => ( + {(popover) => [ <DropdownItem value={'Add/remove attribute'} onClick={(e) => { @@ -128,8 +141,27 @@ export const QueryEntityPill = React.memo((node: SchemaReactflowEntityNode) => { </DropdownItem> )), ]} - /> - )} + />, + <DropdownItem + value="Union type" + onClick={(e) => { + popover?.setOpen(false); + setOpenDropdown(false); + }} + submenu={[ + <DropdownItem + value="AND" + onClick={(_) => setUnionType(QueryUnionType.AND)} + selected={unionType != QueryUnionType.OR} // Also selected when null + />, + <DropdownItem + value="OR" + onClick={(_) => setUnionType(QueryUnionType.OR)} + selected={unionType == QueryUnionType.OR} + />, + ]} + />, + ]} </PopoverContext.Consumer> <DropdownItem value="Remove" className="text-danger" onClick={(e) => removeNode()} /> </DropdownItemContainer> @@ -156,7 +188,12 @@ export const QueryEntityPill = React.memo((node: SchemaReactflowEntityNode) => { } > {data?.attributes && ( - <PillAttributes node={node} attributes={data.attributes} attributeEdges={attributeEdges.map((edge) => edge?.attributes)} /> + <PillAttributes + node={node} + attributes={data.attributes} + attributeEdges={attributeEdges.map((edge) => edge?.attributes)} + unionType={unionType} + /> )} </EntityPill> </div> diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/QueryRelationPill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/QueryRelationPill.tsx index adc55c38689cd9d0bf01f20dd6eafd950bb94cae..f97bbda5d83b4a3073812cfbb865264c93c55930 100644 --- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/QueryRelationPill.tsx +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/QueryRelationPill.tsx @@ -1,10 +1,26 @@ import { memo, useState, useMemo, useEffect } from 'react'; import { NodeAttribute, RelationNodeAttributes, SchemaReactflowRelationNode, toHandleId } from '../../../model'; -import { useAppDispatch, useQuerybuilderGraph, useQuerybuilderSettings } from '@graphpolaris/shared/lib/data-access'; +import { + useAppDispatch, + useQuerybuilderGraph, + useQuerybuilderSettings, + useQuerybuilderUnionTypes, +} from '@graphpolaris/shared/lib/data-access'; import { addWarning } from '@graphpolaris/shared/lib/data-access/store/configSlice'; -import { setQuerybuilderGraphology, toQuerybuilderGraphology, attributeShownToggle } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; +import { + setQuerybuilderGraphology, + toQuerybuilderGraphology, + attributeShownToggle, +} from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; import { PillAttributes } from '../../pillattributes/PillAttributes'; -import { Button, DropdownContainer, DropdownTrigger, RelationPill, DropdownItemContainer, DropdownItem } from '@graphpolaris/shared/lib/components'; +import { + Button, + DropdownContainer, + DropdownTrigger, + RelationPill, + DropdownItemContainer, + DropdownItem, +} from '@graphpolaris/shared/lib/components'; import { Icon } from '@graphpolaris/shared/lib/components/icon'; import { TextInput } from '@graphpolaris/shared/lib/components/inputs'; import { PopoverContext } from '@graphpolaris/shared/lib/components/layout/Popover'; @@ -82,9 +98,11 @@ export const QueryRelationPill = memo((node: SchemaReactflowRelationNode) => { dispatch(attributeShownToggle(attribute.handleData)); } + const unionType = useQuerybuilderUnionTypes()[node.id]; + const attributesBeingShown = useQuerybuilderAttributesShown(); function isAttributeAdded(attribute: NodeAttribute): boolean { - return attributesBeingShown.some((x) => isEqual(x, attribute.handleData)) + return attributesBeingShown.some((x) => isEqual(x, attribute.handleData)); } // TODO: must do this once design is chosen @@ -105,7 +123,7 @@ export const QueryRelationPill = memo((node: SchemaReactflowRelationNode) => { title={ <div className="flex flex-row w-full"> <span className="flex-grow text-justify truncate">{data?.name}</span> - + <DropdownContainer> <DropdownTrigger size="md"> <Button @@ -119,34 +137,46 @@ export const QueryRelationPill = memo((node: SchemaReactflowRelationNode) => { <DropdownItemContainer> <PopoverContext.Consumer> - {popover => <DropdownItem value={'Add/remove attribute'} onClick={(e) => { - popover?.setOpen(false); - setOpenDropdown(false); - }} submenu={ - [ - <TextInput - type={'text'} - placeholder="Filter" - size="xs" - className="mb-1 min-w-40 rounded-sm" - value={filter} - onClick={(e) => e.stopPropagation()} - onChange={(v) => setFilter(v)} />, - - filteredAttributes.map(attr => - <DropdownItem - key={attr.handleData.attributeName + attr.handleData.nodeId} - value={attr.handleData.attributeName ?? ''} - selected={isAttributeAdded(attr)} - onClick={(_) => addAttribute(attr)}> - <Icon component={attr?.handleData?.attributeDimension != null ? IconMap[attr.handleData.attributeDimension] : undefined} className="ms-2 float-end" size={16} /> - </DropdownItem> - ) - ] - } />} + {(popover) => ( + <DropdownItem + value={'Add/remove attribute'} + onClick={(e) => { + popover?.setOpen(false); + setOpenDropdown(false); + }} + submenu={[ + <TextInput + type={'text'} + placeholder="Filter" + size="xs" + className="mb-1 min-w-40 rounded-sm" + value={filter} + onClick={(e) => e.stopPropagation()} + onChange={(v) => setFilter(v)} + />, + + filteredAttributes.map((attr) => ( + <DropdownItem + key={attr.handleData.attributeName + attr.handleData.nodeId} + value={attr.handleData.attributeName ?? ''} + selected={isAttributeAdded(attr)} + onClick={(_) => addAttribute(attr)} + > + <Icon + component={ + attr?.handleData?.attributeDimension != null ? IconMap[attr.handleData.attributeDimension] : undefined + } + className="ms-2 float-end" + size={16} + /> + </DropdownItem> + )), + ]} + /> + )} </PopoverContext.Consumer> + <DropdownItem value="Union type" submenu={[<DropdownItem value="AND" selected />, <DropdownItem value="OR" />]} /> <DropdownItem value="Remove" className="text-danger" onClick={(e) => removeNode()} /> - </DropdownItemContainer> </DropdownContainer> </div> @@ -176,6 +206,7 @@ export const QueryRelationPill = memo((node: SchemaReactflowRelationNode) => { attributes={data?.attributes || []} attributeEdges={attributeEdges.map((edge) => edge?.attributes)} mr={-pillWidth * 0.05} + unionType={unionType} /> </RelationPill> </div> diff --git a/libs/shared/lib/querybuilder/pills/pillattributes/PillAttributes.tsx b/libs/shared/lib/querybuilder/pills/pillattributes/PillAttributes.tsx index 9f9b1c7bead15456095de1716a89bd42daf726ed..28e63fefbf505e51e525e0be6ba7caa2b6fbe901 100644 --- a/libs/shared/lib/querybuilder/pills/pillattributes/PillAttributes.tsx +++ b/libs/shared/lib/querybuilder/pills/pillattributes/PillAttributes.tsx @@ -3,6 +3,7 @@ import { NodeAttribute, QueryGraphEdges, SchemaReactflowEntityNode, SchemaReactf import { useAppDispatch, useQuerybuilderAttributesShown } from '../../..'; import { isEqual } from 'lodash-es'; import { PillAttributesItem } from './PillAttributesItem'; +import { QueryUnionType } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; type PillAttributesProps = { node: SchemaReactflowEntityNode | SchemaReactflowRelationNode; @@ -10,6 +11,7 @@ type PillAttributesProps = { attributeEdges: (QueryGraphEdges | undefined)[]; mr?: number; className?: string; + unionType?: QueryUnionType; }; type IconMapType = { @@ -50,6 +52,7 @@ export const PillAttributes = (props: PillAttributesProps) => { ? IconMap[props.attributes[i].handleData.attributeDimension || 0] : 'icon-[ic--outline-question-mark]' } + unionType={props.unionType} /> ); })} diff --git a/libs/shared/lib/querybuilder/pills/pillattributes/PillAttributesItem.tsx b/libs/shared/lib/querybuilder/pills/pillattributes/PillAttributesItem.tsx index 7052033b50c1431fe50d03dd554ab6ce53929508..5fdb64a1014c05f0776700edeb97614a77536df7 100644 --- a/libs/shared/lib/querybuilder/pills/pillattributes/PillAttributesItem.tsx +++ b/libs/shared/lib/querybuilder/pills/pillattributes/PillAttributesItem.tsx @@ -11,6 +11,7 @@ import { PillHandle } from '@graphpolaris/shared/lib/components/pills/PillHandle import { pillAttributesPadding } from '@graphpolaris/shared/lib/components/pills/pill.const'; import { Button } from '../../..'; import { QueryBuilderDispatcherContext } from '../../panel/QueryBuilderDispatcher'; +import { QueryUnionType } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; type PillAttributesItemProps = { attribute: NodeAttribute; @@ -18,6 +19,7 @@ type PillAttributesItemProps = { className?: string; mr?: number; icon: string | undefined; + unionType?: QueryUnionType; }; export const PillAttributesItem = (props: PillAttributesItemProps) => { @@ -30,6 +32,17 @@ export const PillAttributesItem = (props: PillAttributesItemProps) => { const handleId = toHandleId(handleDataFromReactflowToDataId(props.node, props.attribute)); const handleType = 'source'; + function shapeForType(unionType: QueryUnionType | undefined) { + if (unionType == null) return 'square'; + + switch (unionType) { + case QueryUnionType.AND: + return 'square'; + case QueryUnionType.OR: + return 'diamond'; + } + } + return ( <div className="px-2 py-1 bg-secondary-100 flex justify-between items-center"> <p className="truncate text-[0.6rem]">{props.attribute.handleData.attributeName}</p> @@ -57,7 +70,7 @@ export const PillAttributesItem = (props: PillAttributesItemProps) => { handleTop="auto" position={Position.Right} className={`stroke-white${props.className ? ` ${props.className}` : ''}`} - type="square" + type={shapeForType(props.unionType)} > <Handle id={handleId} diff --git a/libs/shared/lib/querybuilder/query-utils/query2backend.ts b/libs/shared/lib/querybuilder/query-utils/query2backend.ts index b498011a8ad43cf68f8c5f6c99f8a42a6a666188..8757832789b1dfb4a3b63bcdccf3210ec7043a41 100644 --- a/libs/shared/lib/querybuilder/query-utils/query2backend.ts +++ b/libs/shared/lib/querybuilder/query-utils/query2backend.ts @@ -8,7 +8,7 @@ import Graph from 'graphology'; import { allSimplePaths } from 'graphology-simple-path'; import { AllLogicStatement, ReferenceStatement } from '../model/logic/general'; import { ML, MLTypes, mlDefaultState } from '../../data-access/store/mlSlice'; -import { QueryBuilderSettings } from '../../data-access/store/querybuilderSlice'; +import { QueryBuilderSettings, QueryUnionType } from '../../data-access/store/querybuilderSlice'; // export type QueryI { @@ -148,12 +148,22 @@ export function calculateQueryLogic( return ret as AllLogicStatement; } -function queryLogicUnion(graphLogicChunks: AllLogicStatement[]): AllLogicStatement | undefined { +function queryLogicUnion( + nodes: SerializedNode<LogicNodeAttributes>[], + graph: QueryMultiGraph, + logics: SerializedNode<LogicNodeAttributes>[], + unionTypes: { [node_id: string]: QueryUnionType }, +): AllLogicStatement | undefined { + let graphLogicChunks = nodes.map((node) => calculateQueryLogic(node, graph, logics)); + + if (graphLogicChunks.length === 0) return undefined; if (graphLogicChunks.length === 1) return graphLogicChunks[0]; - else if (graphLogicChunks.length > 1) { - return ['And', graphLogicChunks[0], queryLogicUnion(graphLogicChunks.slice(1)) || '0']; - } - return undefined; + + const constraintNodeId = nodes[0].key; + const entityNodeId = graph.edges.filter((x) => x.target == constraintNodeId)[0].source; + const unionType = unionTypes[entityNodeId] || QueryUnionType.AND; + + return [unionType, graphLogicChunks[0], queryLogicUnion(nodes.slice(1), graph, logics, unionTypes) || '0']; } /** @@ -165,6 +175,7 @@ export function Query2BackendQuery( graph: QueryMultiGraph, settings: QueryBuilderSettings, ml: ML = mlDefaultState, + unionTypes: { [node_id: string]: QueryUnionType }, ): BackendQueryFormat { let query: BackendQueryFormat = { saveStateID: saveStateID, @@ -205,7 +216,7 @@ export function Query2BackendQuery( }); }); - return Query2BackendQuery(saveStateID, graphologyQuery.export(), settings, ml); + return Query2BackendQuery(saveStateID, graphologyQuery.export(), settings, ml, unionTypes); } // Chunk extraction: traverse graph to find all paths of logic between relations and entities let graphSequenceChunks: QueryGraphNodes[][] = []; @@ -240,8 +251,7 @@ export function Query2BackendQuery( let logicsRightHandleFinal = logics.filter((n) => { return !graph.edges.some((e) => e.source === n.key); }); - let graphLogicChunks = logicsRightHandleFinal.map((node) => calculateQueryLogic(node, graph, logics)); - query.logic = queryLogicUnion(graphLogicChunks); + query.logic = queryLogicUnion(logicsRightHandleFinal, graph, logics, unionTypes); if (!graphSequenceChunks || graphSequenceChunks.length === 0 || graphSequenceChunks?.[0].length === 0) return query;