From a12f813cdee12ebe26085e1e395312029f199ce5 Mon Sep 17 00:00:00 2001 From: Leonardo <leomilho@gmail.com> Date: Sat, 26 Oct 2024 20:11:52 +0200 Subject: [PATCH] feat(qb): allow multigraph for same entity logic --- .../data-access/store/querybuilderSlice.ts | 4 +-- .../querybuilder/model/graphology/utils.ts | 6 ++-- .../lib/querybuilder/panel/QueryBuilder.tsx | 14 ++++----- .../query-utils/query2backend.spec.ts | 31 ++++++++++++------- .../querybuilder/query-utils/query2backend.ts | 25 +++++++++------ 5 files changed, 46 insertions(+), 34 deletions(-) diff --git a/libs/shared/lib/data-access/store/querybuilderSlice.ts b/libs/shared/lib/data-access/store/querybuilderSlice.ts index ce77ca763..bfa417fda 100644 --- a/libs/shared/lib/data-access/store/querybuilderSlice.ts +++ b/libs/shared/lib/data-access/store/querybuilderSlice.ts @@ -1,6 +1,6 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import type { RootState } from './store'; -import Graph from 'graphology'; +import Graph, { MultiGraph } from 'graphology'; import { QueryMultiGraph, QueryMultiGraphology as QueryGraphology } from '../../querybuilder/model/graphology/utils'; import { AllLayoutAlgorithms } from '../../graph-layout'; import { QueryGraphEdgeHandle } from '../../querybuilder'; @@ -106,7 +106,7 @@ export const setQuerybuilderGraphology = (payload: QueryGraphology) => { /** Select the querybuilder nodes in serialized fromat */ export const toQuerybuilderGraphology = (graph: QueryMultiGraph): QueryGraphology => { let ret = new QueryGraphology(); - ret.import(Graph.from(graph).export()); + ret.import(MultiGraph.from(graph).export()); return ret; }; diff --git a/libs/shared/lib/querybuilder/model/graphology/utils.ts b/libs/shared/lib/querybuilder/model/graphology/utils.ts index d05f1c764..5f6610db5 100644 --- a/libs/shared/lib/querybuilder/model/graphology/utils.ts +++ b/libs/shared/lib/querybuilder/model/graphology/utils.ts @@ -1,4 +1,4 @@ -import Graph from 'graphology'; +import Graph, { MultiGraph } from 'graphology'; import { Attributes as GAttributes, Attributes, SerializedGraph } from 'graphology-types'; import { EntityNodeAttributes, @@ -32,7 +32,7 @@ export type AddEdge2GraphologyOptions = { export type QueryMultiGraph = SerializedGraph<QueryGraphNodes, QueryGraphEdges, GAttributes>; -export class QueryMultiGraphology extends Graph<QueryGraphNodes, QueryGraphEdges, GAttributes> { +export class QueryMultiGraphology extends MultiGraph<QueryGraphNodes, QueryGraphEdges, GAttributes> { public configureDefaults(attributes: QueryGraphNodes): QueryGraphNodes { const { type, name } = attributes; if (!type || !name) throw Error('type or name is not defined'); @@ -295,7 +295,7 @@ export function calcWidthHeightOfPill(attributes: Attributes): { } /** Interface for x and y position of node */ -export interface NodePosition extends XYPosition {} +export interface NodePosition extends XYPosition { } /** Returns from-position of relation node */ export function RelationPosToFromEntityPos(position: XYPosition): NodePosition { diff --git a/libs/shared/lib/querybuilder/panel/QueryBuilder.tsx b/libs/shared/lib/querybuilder/panel/QueryBuilder.tsx index ccfe0197a..10579c534 100644 --- a/libs/shared/lib/querybuilder/panel/QueryBuilder.tsx +++ b/libs/shared/lib/querybuilder/panel/QueryBuilder.tsx @@ -357,14 +357,12 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { isOnConnect.current = true; if (!connection.sourceHandle || !connection.targetHandle) throw new Error('Connection has no source or target'); - if (!graphologyGraph.hasEdge(connection.source, connection.target)) { - graphologyGraph.addEdge(connection.source, connection.target, { - type: 'connection', - sourceHandleData: toHandleData(connection.sourceHandle), - targetHandleData: toHandleData(connection.targetHandle), - }); - dispatch(setQuerybuilderGraphology(graphologyGraph)); - } + graphologyGraph.addEdge(connection.source, connection.target, { + type: 'connection', + sourceHandleData: toHandleData(connection.sourceHandle), + targetHandleData: toHandleData(connection.targetHandle), + }); + dispatch(setQuerybuilderGraphology(graphologyGraph)); } }, [graph], diff --git a/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts b/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts index da531d725..18fe1b7c7 100644 --- a/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts +++ b/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts @@ -664,7 +664,7 @@ describe('QueryUtils Entity and Relations', () => { const graph = new QueryMultiGraphology(); const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] }); - const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1', attributes: [] }); + const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 2', attributes: [] }); graph.addEdge2Graphology(e1, e2, { type: 'connection' }); graph.addEdge2Graphology(e2, e1, { type: 'connection' }); @@ -677,17 +677,18 @@ describe('QueryUtils Entity and Relations', () => { node: { ID: 'e1', label: 'Airport 1', - relation: { direction: 'BOTH', node: { ID: 'e2', label: 'Airport 1' } }, - }, - }, - { - ID: 'path_1', - node: { - ID: 'e2', - label: 'Airport 1', - relation: { direction: 'BOTH', node: { ID: 'e1', label: 'Airport 1' } }, + relation: { + direction: 'BOTH', + node: { + ID: 'e2', label: 'Airport 2', + relation: { + direction: 'BOTH', + node: { ID: 'e1', label: 'Airport 1' } + }, + } + }, }, - }, + } ], }; let ret = Query2BackendQuery('database', graph.export(), defaultSettings); @@ -706,7 +707,13 @@ describe('QueryUtils Entity and Relations', () => { query: [ { ID: 'path_0', - node: { ID: 'e1', label: 'Airport 1', relation: { direction: 'BOTH', node: { ID: 'e1', label: 'Airport 1' } } }, + node: { + ID: 'e1', label: 'Airport 1', + relation: { + direction: 'BOTH', + node: { ID: 'e1', label: 'Airport 1' } + } + }, }, ], }; diff --git a/libs/shared/lib/querybuilder/query-utils/query2backend.ts b/libs/shared/lib/querybuilder/query-utils/query2backend.ts index 875783278..670d1c1f8 100644 --- a/libs/shared/lib/querybuilder/query-utils/query2backend.ts +++ b/libs/shared/lib/querybuilder/query-utils/query2backend.ts @@ -175,7 +175,7 @@ export function Query2BackendQuery( graph: QueryMultiGraph, settings: QueryBuilderSettings, ml: ML = mlDefaultState, - unionTypes: { [node_id: string]: QueryUnionType }, + unionTypes: { [node_id: string]: QueryUnionType } = {}, ): BackendQueryFormat { let query: BackendQueryFormat = { saveStateID: saveStateID, @@ -205,16 +205,23 @@ export function Query2BackendQuery( const cycles = entities.map((entity, i) => { return allSimplePaths(graphologyQuery, entity.key, entity.key); }); - cycles.forEach((cycles_inner, i) => { - cycles_inner.forEach((cycle, j) => { + for (let i = 0; i < cycles.length; i++) { + for (let j = 0; j < cycles[i].length; j++) { + const cycle = cycles[i][j]; const origin = cycle[0]; const target = cycle[cycle.length - 2]; - const newOrigin = graphologyQuery.addNode(origin + 'cycle', graphologyQuery.getNodeAttributes(origin)); - const edgeAttributes = graphologyQuery.getEdgeAttributes(target, origin); - graphologyQuery.dropEdge(target, origin); - graphologyQuery.addEdge(target, newOrigin, edgeAttributes); - }); - }); + const edges = graphologyQuery.edges(target, origin); + if (edges.length > 0) { + const edge = edges[edges.length - 1]; + const newOrigin = graphologyQuery.addNode(origin + 'cycle' + edge, graphologyQuery.getNodeAttributes(origin)); + const edgeAttributes = graphologyQuery.getEdgeAttributes(edge); + graphologyQuery.dropEdge(edge); + graphologyQuery.addEdge(target, newOrigin, edgeAttributes); + } + break; // only do one cycle + } + break; // only do one cycle + } return Query2BackendQuery(saveStateID, graphologyQuery.export(), settings, ml, unionTypes); } -- GitLab