From 4f4bfcdf9a10fbb6cd8310549e13cf897f33aa0c Mon Sep 17 00:00:00 2001
From: Leonardo Christino <leomilho@gmail.com>
Date: Wed, 26 Jul 2023 21:22:09 +0200
Subject: [PATCH] feat(queryLogic): enables query logic

Includes logic in query building, parsing and execution
Logic pills can be created from handle drop and from side panel
---
 apps/web/src/app/app.tsx                      |   23 +-
 apps/web/src/components/navbar/navbar.tsx     |    2 +-
 apps/web/src/main.tsx                         |    4 +-
 libs/shared/lib/data-access/api/database.ts   |    4 +-
 libs/shared/lib/data-access/api/query.ts      |    2 +-
 libs/shared/lib/data-access/api/schema.ts     |    2 +-
 .../lib/data-access/authorization/useAuth.jsx |    2 +-
 .../WebSocketHandler.tsx                      |    4 +-
 .../lib/data-access/socket/broker/index.tsx   |    2 +-
 .../store/graphQueryResultSlice.ts            |   10 +-
 .../data-access/store/querybuilderSlice.ts    |   41 +-
 .../lib/data-access/store/schemaSlice.ts      |    2 +-
 .../lib/data-access/store/sessionSlice.ts     |    2 +-
 .../lib/graph-layout/cytoscape-layouts.ts     |    4 +-
 libs/shared/lib/graph-layout/layout.ts        |    4 +-
 .../querybuilder/model/BackendQueryFormat.tsx |   15 +-
 .../querybuilder/model/graphology/model.ts    |   48 +-
 .../querybuilder/model/graphology/utils.ts    |  199 +-
 libs/shared/lib/querybuilder/model/index.ts   |   46 +-
 .../lib/querybuilder/model/logic/general.ts   |  195 +-
 .../lib/querybuilder/model/logic/index.ts     |   30 +-
 .../model/logic/mathAggregations.tsx          |  745 +++++
 .../querybuilder/model/logic/mathFilters.tsx  |   88 +-
 .../model/logic/mathFunctions.tsx             |   90 +-
 .../model/logic/stringFilters.tsx             |   66 +-
 .../model/logic/stringFunctions.tsx           |   51 +-
 .../querybuilder/model/reactflow/handles.tsx  |    1 +
 .../lib/querybuilder/model/reactflow/utils.ts |    9 +-
 .../lib/querybuilder/panel/querybuilder.tsx   |  355 ++-
 ...erybuilder-simple-disconnected.stories.tsx |   42 +-
 .../stories/querybuilder-simple.stories.tsx   |   50 +-
 .../querybuilder-single-entity.stories.tsx    |    4 +-
 ...erybuilder-single-relationship.stories.tsx |    4 +-
 .../entitypill/entitypill-full.stories.tsx    |    3 +-
 .../entitypill/entitypill.module.scss         |    1 +
 .../customFlowPills/entitypill/entitypill.tsx |   53 +-
 .../logicpill/logicpill.module.scss           |    8 +-
 .../customFlowPills/logicpill/logicpill.tsx   |  149 +-
 .../relation-full_reactflow.stories.tsx       |    4 +-
 .../relationpill/relationpill.tsx             |   30 +-
 .../querybuilder/pills/handles.module.scss    |    4 +-
 libs/shared/lib/querybuilder/pills/utils.ts   |    3 +-
 .../lib/querybuilder/query-utils/index.ts     |    2 +-
 .../query-utils/query-utils.spec.ts           |   16 -
 .../querybuilder/query-utils/query-utils.ts   |  142 -
 .../query-utils/query2backend.spec.ts         |  960 ++++++
 .../querybuilder/query-utils/query2backend.ts |  311 ++
 libs/shared/lib/schema/model/reactflow.tsx    |    1 +
 libs/shared/lib/schema/panel/schema.tsx       |    3 +-
 .../panel/view-model/schemaViewModel.test.t   | 2640 ++++++++---------
 .../schema/pills/nodes/entity/entity-node.tsx |    4 +-
 .../pills/nodes/relation/relation-node.tsx    |    6 +-
 .../schema-utils/schema-usecases.spec.ts      |    4 +-
 .../schema/schema-utils/schema-usecases.ts    |    7 +-
 .../lib/vis/nodelink/NodeLinkViewModel.tsx    |   27 +-
 libs/shared/lib/vis/nodelink/nodelinkvis.tsx  |    4 +-
 libs/shared/package.json                      |    2 +
 libs/shared/vite.config.ts                    |   14 +-
 libs/shared/vitest.setup.ts                   |    1 -
 pnpm-lock.yaml                                |   56 +-
 60 files changed, 4605 insertions(+), 1996 deletions(-)
 create mode 100644 libs/shared/lib/querybuilder/model/logic/mathAggregations.tsx
 delete mode 100644 libs/shared/lib/querybuilder/query-utils/query-utils.spec.ts
 delete mode 100644 libs/shared/lib/querybuilder/query-utils/query-utils.ts
 create mode 100644 libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts
 create mode 100644 libs/shared/lib/querybuilder/query-utils/query2backend.ts
 delete mode 100644 libs/shared/vitest.setup.ts

diff --git a/apps/web/src/app/app.tsx b/apps/web/src/app/app.tsx
index 0ecebbec8..c616b6452 100644
--- a/apps/web/src/app/app.tsx
+++ b/apps/web/src/app/app.tsx
@@ -19,7 +19,7 @@ import {
 } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice';
 import { Query2BackendQuery, QueryBuilder } from '@graphpolaris/shared/lib/querybuilder';
 import { Schema } from '@graphpolaris/shared/lib/schema/panel';
-import { useEffect, useRef, useState } from 'react';
+import { useCallback, useEffect, useRef, useState } from 'react';
 import { Navbar } from '../components/navbar/navbar';
 import Panel from '../components/panels/panel';
 import { VisualizationPanel } from './panels/Visualization';
@@ -70,9 +70,9 @@ export function App(props: App) {
 
   useEffect(() => {
     // Newly (un)authorized
-    console.log('Auth changed', auth.authorized, isLogin);
+    // console.log('Auth changed', auth.authorized, isLogin);
     if (auth.authorized) {
-      console.info('App is authorized; Getting Databases', isLogin);
+      console.debug('App is authorized; Getting Databases', isLogin);
       api.GetAllDatabases({ updateSessionCache: true });
       setAuthCheck(true);
     } else {
@@ -80,17 +80,18 @@ export function App(props: App) {
     }
   }, [auth]);
 
-  useEffect(() => {
-    // New query
+  const runQuery = () => {
     if (session?.currentDatabase && query) {
-      console.log('New query', query);
-
       if (query.edges.length === 0) {
         dispatch(resetGraphQueryResults());
       } else {
         api_query.execute(Query2BackendQuery(session.currentDatabase, query));
       }
     }
+  };
+
+  useEffect(() => {
+    runQuery();
   }, [queryHash]);
 
   return (
@@ -114,7 +115,13 @@ export function App(props: App) {
               </div>
               <div className={styles.queryBuilder}>
                 <Panel content="Query Panel">
-                  <QueryBuilder />
+                  <QueryBuilder
+                    onRunQuery={() => {
+                      console.log('Run Query');
+
+                      runQuery();
+                    }}
+                  />
                 </Panel>
               </div>
             </div>
diff --git a/apps/web/src/components/navbar/navbar.tsx b/apps/web/src/components/navbar/navbar.tsx
index c5918591a..9e1fe7716 100644
--- a/apps/web/src/components/navbar/navbar.tsx
+++ b/apps/web/src/components/navbar/navbar.tsx
@@ -58,7 +58,7 @@ export const Navbar = (props: NavbarComponentProps) => {
   const dispatch = useAppDispatch();
 
   useEffect(() => {
-    console.log(auth);
+    // console.log(auth);
   }, [auth.accessToken]);
 
   // const { navbarViewModel, currentColours } = props;
diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx
index 483b0d21b..b6d8c226a 100644
--- a/apps/web/src/main.tsx
+++ b/apps/web/src/main.tsx
@@ -16,8 +16,6 @@ const domNode = document.getElementById('root');
 
 if (domNode) {
   const root = createRoot(domNode);
-
-  console.log(store);
   root.render(
     <Provider store={store}>
       <GraphPolarisThemeProvider>
@@ -29,6 +27,6 @@ if (domNode) {
           </Routes>
         </Router>
       </GraphPolarisThemeProvider>
-    </Provider>
+    </Provider>,
   );
 }
diff --git a/libs/shared/lib/data-access/api/database.ts b/libs/shared/lib/data-access/api/database.ts
index 9c3abbe85..11caee2a6 100644
--- a/libs/shared/lib/data-access/api/database.ts
+++ b/libs/shared/lib/data-access/api/database.ts
@@ -53,7 +53,7 @@ export const useDatabaseAPI = () => {
         }),
         body: JSON.stringify(request),
       }).then((response: Response) => {
-        console.log(response);
+        console.info('Added Database', response);
 
         if (!response.ok) {
           reject(response.statusText);
@@ -76,7 +76,7 @@ export const useDatabaseAPI = () => {
         Authorization: 'Bearer ' + accessToken,
       }),
     });
-    console.log(response);
+    // console.log(response);
 
     if (!response.ok) {
       const text = await response.text();
diff --git a/libs/shared/lib/data-access/api/query.ts b/libs/shared/lib/data-access/api/query.ts
index 5fd651fac..1aad449bf 100644
--- a/libs/shared/lib/data-access/api/query.ts
+++ b/libs/shared/lib/data-access/api/query.ts
@@ -25,7 +25,7 @@ export const useQueryAPI = () => {
       return;
     }
     const ret = await response.json();
-    console.log('Sent Query EXECUTION', ret);
+    console.debug('Sent Query EXECUTION', ret);
   }
 
   async function retrieveCachedQuery(queryID: string) {
diff --git a/libs/shared/lib/data-access/api/schema.ts b/libs/shared/lib/data-access/api/schema.ts
index 59a7b2ecf..417b60783 100644
--- a/libs/shared/lib/data-access/api/schema.ts
+++ b/libs/shared/lib/data-access/api/schema.ts
@@ -33,7 +33,7 @@ export const useSchemaAPI = () => {
       console.error(response, ret);
     }
     // const ret = await response.json();
-    console.log(response);
+    // console.debug('Schema Requested', response);
   }
 
   return { RequestSchema };
diff --git a/libs/shared/lib/data-access/authorization/useAuth.jsx b/libs/shared/lib/data-access/authorization/useAuth.jsx
index ab8bb0a74..25e0643c6 100644
--- a/libs/shared/lib/data-access/authorization/useAuth.jsx
+++ b/libs/shared/lib/data-access/authorization/useAuth.jsx
@@ -17,7 +17,7 @@ export const useAuth = () => {
 
   useEffect(() => {
     if (import.meta.env.VITE_SKIP_LOGIN) {
-      console.log('skipping login');
+      console.warn('skipping login');
       setLogin(true);
       dispatch(
         authorized({
diff --git a/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx b/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx
index bca2d0453..64b41b9df 100644
--- a/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx
+++ b/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx
@@ -67,7 +67,7 @@ export class WebSocketHandler implements BackendMessageReceiver {
    */
   public onWebSocketMessage = (event: MessageEvent<any>) => {
     let data = JSON.parse(event.data);
-    console.log('WS message: ', data);
+    console.debug('WS message: ', data);
 
     Broker.instance().publish(data.value, data.type);
   };
@@ -77,6 +77,6 @@ export class WebSocketHandler implements BackendMessageReceiver {
    * @param {any} event contains the event data.
    */
   private onError(event: any): void {
-    console.log(event);
+    console.error(event);
   }
 }
diff --git a/libs/shared/lib/data-access/socket/broker/index.tsx b/libs/shared/lib/data-access/socket/broker/index.tsx
index b78b7acc9..098413fd9 100644
--- a/libs/shared/lib/data-access/socket/broker/index.tsx
+++ b/libs/shared/lib/data-access/socket/broker/index.tsx
@@ -42,7 +42,7 @@ export default class Broker {
       Object.values(this.listeners[routingKey]).forEach((listener) => listener(jsonObject, routingKey));
     // If there are no listeners, log the message
     else
-      console.log(
+      console.debug(
         `no listeners for message with routing key %c${routingKey}`,
         'font-weight:bold; color: blue; background-color: white;',
         jsonObject
diff --git a/libs/shared/lib/data-access/store/graphQueryResultSlice.ts b/libs/shared/lib/data-access/store/graphQueryResultSlice.ts
index 95c983209..a776ada94 100644
--- a/libs/shared/lib/data-access/store/graphQueryResultSlice.ts
+++ b/libs/shared/lib/data-access/store/graphQueryResultSlice.ts
@@ -65,7 +65,7 @@ export const graphQueryResultSlice = createSlice({
   reducers: {
     assignNewGraphQueryResult: (state, action: PayloadAction<GraphQueryResultFromBackendPayload>) => {
       const payload = action.payload.payload;
-      console.log('!!!assignNewGraphQueryResult', action.payload.payload);
+      // console.log('!!!assignNewGraphQueryResult', action.payload.payload);
 
       // Maybe do some data quality checking and parsing
       // ...
@@ -73,15 +73,15 @@ export const graphQueryResultSlice = createSlice({
       // Collect all the different nodetypes in the result
       const nodeTypes: string[] = [];
       payload.nodes = payload.nodes.map((node) => {
-        // TODO FIXME!! Note: works only for arangodb
+        let _node = { ...node };
         let nodeType = node.id.split('/')[0];
         let innerLabels = node?.attributes?.labels as string[];
         if (innerLabels.length > 0) {
-          nodeType = innerLabels[0] as string; // TODO: Not sure it works yet
+          nodeType = innerLabels[0] as string;
         }
         if (!nodeTypes.includes(nodeType)) nodeTypes.push(nodeType);
-        node.label = nodeType;
-        return node;
+        _node.label = nodeType;
+        return _node;
       });
 
       payload.edges = payload.edges.map((edge) => {
diff --git a/libs/shared/lib/data-access/store/querybuilderSlice.ts b/libs/shared/lib/data-access/store/querybuilderSlice.ts
index 09903d787..26993303d 100644
--- a/libs/shared/lib/data-access/store/querybuilderSlice.ts
+++ b/libs/shared/lib/data-access/store/querybuilderSlice.ts
@@ -1,12 +1,14 @@
 import { createSlice, PayloadAction } from '@reduxjs/toolkit';
 import type { RootState } from './store';
-import { MultiGraph } from 'graphology';
+import Graph, { MultiGraph } from 'graphology';
 import { Attributes, SerializedGraph } from 'graphology-types';
-import { QueryMultiGraph, QueryMultiGraphology } from '../../querybuilder';
+import { QueryMultiGraph, QueryMultiGraphology as QueryGraphology } from '../../querybuilder/model/graphology/utils';
 
 // Define the initial state using that type
-export const initialState = {
-  graphologySerialized: new QueryMultiGraphology().export(),
+export const initialState: {
+  graphologySerialized: QueryMultiGraph;
+} = {
+  graphologySerialized: new QueryGraphology().export(),
   // schemaLayout: 'Graphology_noverlap',
 };
 
@@ -15,9 +17,11 @@ export const querybuilderSlice = createSlice({
   // `createSlice` will infer the state type from the `initialState` argument
   initialState,
   reducers: {
-    setQuerybuilderNodes: (state, action: PayloadAction<SerializedGraph<Attributes, Attributes, Attributes>>) => {
+    setQuerybuilderNodes: (state, action: PayloadAction<QueryMultiGraph>) => {
       // console.log('setQuerybuilderNodes', action.payload);
-      state.graphologySerialized = QueryMultiGraphology.from(action.payload).export() as QueryMultiGraph;
+      // @ts-ignore
+      state.graphologySerialized = QueryGraphology.from(action.payload).export();
+      // state.graphologySerialized = action.payload;
     },
     // updateQBAttributeOperator: (state, action: PayloadAction<{ id: string; operator: string }>) => {
     //   const graph = QueryMultiGraphology.from(state.graphologySerialized);
@@ -30,7 +34,7 @@ export const querybuilderSlice = createSlice({
     //   state.graphologySerialized = graph.export();
     // },
     clearQB: (state) => {
-      state.graphologySerialized = new QueryMultiGraphology().export();
+      state.graphologySerialized = new QueryGraphology().export();
     },
 
     // addQuerybuilderNode: (
@@ -50,11 +54,11 @@ export const querybuilderSlice = createSlice({
 export const { setQuerybuilderNodes, clearQB } = querybuilderSlice.actions;
 
 /** Select the querybuilder nodes in serialized fromat */
-export const selectQuerybuilderGraphology = (state: RootState): QueryMultiGraphology => {
+export const selectQuerybuilderGraphology = (state: RootState): QueryGraphology => {
   // This is really weird but for some reason all the attributes appeared as read-only otherwise
 
-  let ret = new QueryMultiGraphology();
-  ret.import(MultiGraph.from(state.querybuilder.graphologySerialized).export());
+  let ret = new QueryGraphology();
+  ret.import(Graph.from(state.querybuilder.graphologySerialized).export());
   return ret;
 };
 
@@ -66,10 +70,21 @@ export const selectQuerybuilderGraph = (state: RootState): QueryMultiGraph => {
 
 /** Select the querybuilder nodes and convert it to a graphology object */
 export const selectQuerybuilderHash = (state: RootState): any => {
+  const hashedNodes = state.querybuilder.graphologySerialized.nodes.map((n) => {
+    let node = { ...n };
+    if (n?.attributes) {
+      let newAttributes = { ...n?.attributes };
+      newAttributes.x = 0;
+      newAttributes.y = 0;
+      newAttributes.height = 0;
+      newAttributes.width = 0;
+      node.attributes = newAttributes;
+    }
+    return node;
+  });
+
   return JSON.stringify({
-    nodes: state.querybuilder.graphologySerialized.nodes.map((n) => ({
-      key: n.key,
-    })),
+    nodes: hashedNodes,
     edges: state.querybuilder.graphologySerialized.edges.map((n) => ({
       key: n.key,
       source: n.source,
diff --git a/libs/shared/lib/data-access/store/schemaSlice.ts b/libs/shared/lib/data-access/store/schemaSlice.ts
index 3b5c28905..80f957305 100644
--- a/libs/shared/lib/data-access/store/schemaSlice.ts
+++ b/libs/shared/lib/data-access/store/schemaSlice.ts
@@ -33,7 +33,7 @@ export const schemaSlice = createSlice({
 
     readInSchemaFromBackend: (state, action: PayloadAction<SchemaFromBackend>) => {
       state.graphologySerialized = SchemaUtils.schemaBackend2Graphology(action.payload).export();
-      console.log('Updated schema from backend');
+      // console.log('Updated schema from backend');
       // The graph schema needs a node for each node AND edge. These need then be connected
 
       // nodes.forEach((node) => {
diff --git a/libs/shared/lib/data-access/store/sessionSlice.ts b/libs/shared/lib/data-access/store/sessionSlice.ts
index 4de3a9179..bc9e98a8b 100644
--- a/libs/shared/lib/data-access/store/sessionSlice.ts
+++ b/libs/shared/lib/data-access/store/sessionSlice.ts
@@ -51,7 +51,7 @@ export const sessionSlice = createSlice({
       state.currentDatabase = action.payload;
     },
     updateDatabaseList(state, action: PayloadAction<string[]>) {
-      console.log('Updating database list', action);
+      console.debug('Updating database list', action);
       state.databases = action.payload;
       if (state.databases.length > 0) {
         if (!state.currentDatabase || !state.databases.includes(state.currentDatabase)) state.currentDatabase = state.databases[0];
diff --git a/libs/shared/lib/graph-layout/cytoscape-layouts.ts b/libs/shared/lib/graph-layout/cytoscape-layouts.ts
index 7970ad5aa..7b0fe6945 100644
--- a/libs/shared/lib/graph-layout/cytoscape-layouts.ts
+++ b/libs/shared/lib/graph-layout/cytoscape-layouts.ts
@@ -241,10 +241,10 @@ class CytoscapeKlay extends Cytoscape {
       // boundingBox: undefined,
 
       ready: function () {
-        console.info('Layout.ready');
+        // console.info('Layout.ready');
       }, // on layoutready
       stop: function () {
-        console.debug('Layout.stop');
+        // console.debug('Layout.stop');
       }, // on layoutstop
     } as any);
     layout.run();
diff --git a/libs/shared/lib/graph-layout/layout.ts b/libs/shared/lib/graph-layout/layout.ts
index 8066aed2d..e99ef69bb 100644
--- a/libs/shared/lib/graph-layout/layout.ts
+++ b/libs/shared/lib/graph-layout/layout.ts
@@ -8,11 +8,11 @@ import { Providers, LayoutAlgorithm } from './layout-creator-usecase';
 
 export abstract class Layout<provider extends Providers> {
   constructor(public provider: provider, public algorithm: LayoutAlgorithm<provider>) {
-    console.info(`Created the following Layout: ${provider} - ${this.algorithm}`);
+    // console.info(`Created the following Layout: ${provider} - ${this.algorithm}`);
   }
 
   public layout(graph: Graph, verbose?: boolean) {
-    console.log(`${this.provider} [${this.algorithm}] layouting now`);
+    // console.log(`${this.provider} [${this.algorithm}] layouting now`);
 
     graph.forEachNode((node) => {
       const attr = graph.getNodeAttributes(node);
diff --git a/libs/shared/lib/querybuilder/model/BackendQueryFormat.tsx b/libs/shared/lib/querybuilder/model/BackendQueryFormat.tsx
index 98608922b..06a266125 100644
--- a/libs/shared/lib/querybuilder/model/BackendQueryFormat.tsx
+++ b/libs/shared/lib/querybuilder/model/BackendQueryFormat.tsx
@@ -4,7 +4,8 @@
  * © Copyright Utrecht University (Department of Information and Computing Sciences)
  */
 
-import { InputNodeType } from './logic/general';
+import { type } from 'os';
+import { AllLogicStatement, AnyStatement, InputNodeType } from './logic/general';
 
 /** JSON query format used to send a query to the backend. */
 export interface BackendQueryResultFormat {
@@ -29,6 +30,7 @@ export interface BackendQueryFormat {
   limit: number;
   return: string[];
   query: QueryStruct[];
+  logic?: AnyStatement;
   // entities: Entity[];
   // relations: Relation[];
   // groupBys: GroupBy[];
@@ -46,27 +48,28 @@ export interface QueryStruct {
 export interface NodeStruct {
   label?: string;
   ID?: string;
-  logic?: LogicStruct[];
+  logic?: AllLogicStatement;
+  filter?: FilterStruct[];
   relation?: RelationStruct;
   subQuery?: QueryStruct;
   export?: ExportNodeStruct[];
 }
 
 export interface ExportNodeStruct {
-  ID: number;
+  ID: string;
   attribute: string;
 }
 
 export interface LogicStruct {
-  ID: number;
+  ID: string;
   attribute: string;
   operation: LogicOperationType;
 }
 
 export interface FilterStruct {
-  ID: number;
   attribute: string;
   operation: FilterOperationType;
+  value: string;
 }
 
 export interface RelationStruct {
@@ -82,7 +85,7 @@ export interface QuerySearchDepthStruct {
   max: number;
 }
 
-export type FilterOperationType = 'AND' | 'OR';
+export type FilterOperationType = 'EQ' | 'NEQ' | 'GT' | 'LT' | 'GTE' | 'LTE' | 'LIKE' | 'NOT LIKE' | 'IN' | 'NOT IN';
 export type LogicOperationType = 'AND' | 'OR';
 
 /** Interface for an entity in the JSON for the query. */
diff --git a/libs/shared/lib/querybuilder/model/graphology/model.ts b/libs/shared/lib/querybuilder/model/graphology/model.ts
index 650f0e489..09832ac55 100644
--- a/libs/shared/lib/querybuilder/model/graphology/model.ts
+++ b/libs/shared/lib/querybuilder/model/graphology/model.ts
@@ -2,15 +2,15 @@ import { Attributes as GAttributes } from 'graphology-types';
 import { XYPosition } from 'reactflow';
 import { MultiGraph } from 'graphology';
 import './utils';
-import { SchemaAttribute } from '@graphpolaris/shared/lib/schema';
-import { GeneralDescription, InputNodeType } from '../logic/general';
+import { GeneralDescription, InputNodeType, InputNodeTypeTypes } from '../logic/general';
 import { AllLogicTypes } from '../logic';
+import { QueryElementTypes } from '../reactflow';
 
 // export interface Attributes extends EntityNode | RelationNode | AttributeNode | FunctionNode | ModifierNode  {
 // }
 
-export interface NodeAttribute extends SchemaAttribute {
-  handleId: string;
+export interface NodeAttribute {
+  handleData: QueryGraphEdgeHandle;
   // nodeCount: number;
   // summedNullAmount: number;
   // connectedRatio: number;
@@ -19,7 +19,7 @@ export interface NodeAttribute extends SchemaAttribute {
 
 export type NodeDefaults = {
   id?: string;
-  type: string;
+  type: QueryElementTypes;
   width?: number;
   height?: number;
   attributes?: NodeAttribute[];
@@ -27,18 +27,18 @@ export type NodeDefaults = {
 
 /** Interface for the data in an entity node. */
 export interface EntityData {
-  name: string;
-  leftRelationHandleId?: string;
-  rightRelationHandleId?: string;
+  name?: string;
+  leftRelationHandleId?: QueryGraphEdgeHandle;
+  rightRelationHandleId?: QueryGraphEdgeHandle;
 }
 
 /** Interface for the data in an relation node. */
 export interface RelationData {
-  name: string;
+  name?: string;
   collection: string;
   depth: { min: number; max: number };
-  leftEntityHandleId?: string;
-  rightEntityHandleId?: string;
+  leftEntityHandleId?: QueryGraphEdgeHandle;
+  rightEntityHandleId?: QueryGraphEdgeHandle;
 }
 
 export interface LogicData {
@@ -47,6 +47,7 @@ export interface LogicData {
   // logicType: AllLogicTypes;
   // key: string;
   logic: GeneralDescription<AllLogicTypes>;
+  inputs: Record<string, InputNodeTypeTypes>; // name from InputNode -> InputNodeTypeTypes
 }
 
 export type EntityNodeAttributes = XYPosition & EntityData & NodeDefaults;
@@ -55,4 +56,27 @@ export type LogicNodeAttributes = XYPosition & LogicData & NodeDefaults;
 
 export type QueryGraphNodes = EntityNodeAttributes | RelationNodeAttributes | LogicNodeAttributes;
 
-// export class QueryGraph extends MultiGraph<QueryGraphNodes, GAttributes, GAttributes>; // is in utils.ts
+export type QueryGraphEdgeAttribute = {
+  attributeName?: string;
+  attributeType?: InputNodeType;
+};
+
+export type QueryGraphEdgeHandle = {
+  nodeId: string;
+  nodeName: string;
+  nodeType: QueryElementTypes;
+} & QueryGraphEdgeAttribute;
+
+export type QueryGraphEdges = {
+  type: string;
+  sourceHandleData: QueryGraphEdgeHandle;
+  targetHandleData: QueryGraphEdgeHandle;
+};
+
+export type QueryGraphEdgesOpt = {
+  type?: string;
+  sourceHandleData?: QueryGraphEdgeHandle;
+  targetHandleData?: QueryGraphEdgeHandle;
+};
+
+// export class QueryGraph extends Graph<QueryGraphNodes, GAttributes, GAttributes>; // is in utils.ts
diff --git a/libs/shared/lib/querybuilder/model/graphology/utils.ts b/libs/shared/lib/querybuilder/model/graphology/utils.ts
index 027d1661b..5da3f1cd3 100644
--- a/libs/shared/lib/querybuilder/model/graphology/utils.ts
+++ b/libs/shared/lib/querybuilder/model/graphology/utils.ts
@@ -1,10 +1,22 @@
-import { setQuerybuilderNodes, store } from '@graphpolaris/shared/lib/data-access/store';
+// import { setQuerybuilderNodes, store } from '@graphpolaris/shared/lib/data-access/store';
 import Graph, { MultiGraph } from 'graphology';
 import { Attributes as GAttributes, Attributes, SerializedGraph } from 'graphology-types';
-import { EntityNodeAttributes, LogicNodeAttributes, QueryGraphNodes, RelationNodeAttributes } from './model';
+import {
+  EntityNodeAttributes,
+  LogicNodeAttributes,
+  QueryGraphEdgeAttribute,
+  QueryGraphEdgeHandle,
+  QueryGraphEdges,
+  QueryGraphEdgesOpt,
+  QueryGraphNodes,
+  RelationNodeAttributes,
+} from './model';
 import { XYPosition } from 'reactflow';
 import { Handles, QueryElementTypes } from '../reactflow';
-import { getHandleId } from '..';
+import { toHandleId } from '..';
+import { s } from 'vitest/dist/env-afee91f0';
+import { SchemaAttribute, SchemaAttributeTypes } from '@graphpolaris/shared/lib/schema';
+import { InputNodeType, InputNodeTypeTypes } from '../logic/general';
 
 /** monospace fontsize table */
 const widthPerFontsize = {
@@ -13,13 +25,20 @@ const widthPerFontsize = {
   10: 6.0167,
 };
 
-export type QueryMultiGraph = SerializedGraph<QueryGraphNodes, GAttributes, GAttributes>;
+export type AddEdge2GraphologyOptions = {
+  // attributeSourceHandle?: string;
+  sourceHandleName?: string;
+  targetHandleName?: string;
+  attributeTargetHandleName?: string;
+  attributeTargetHandleType?: string;
+};
+
+export type QueryMultiGraph = SerializedGraph<QueryGraphNodes, QueryGraphEdges, GAttributes>;
 
-export class QueryMultiGraphology extends MultiGraph<QueryGraphNodes, GAttributes, GAttributes> {
-  public addPill2Graphology(attributes: QueryGraphNodes): QueryGraphNodes {
+export class QueryMultiGraphology extends Graph<QueryGraphNodes, QueryGraphEdges, GAttributes> {
+  public configureDefaults(attributes: QueryGraphNodes): QueryGraphNodes {
     const { type, name } = attributes;
     if (!type || !name) throw Error('type or name is not defined');
-    if (!attributes.id) attributes.id = 'id_' + (Date.now() + Math.floor(Math.random() * 1000)).toString();
     let { x, y } = attributes;
 
     // Check if x and y are present, otherwise set them to 0
@@ -29,47 +48,161 @@ export class QueryMultiGraphology extends MultiGraph<QueryGraphNodes, GAttribute
     // Get the width and height of a node
     const { width, height } = calcWidthHeightOfPill(attributes);
 
+    attributes.x = x;
+    attributes.y = y;
+    attributes.width = width;
+    attributes.height = height;
+
+    if (!attributes.id) attributes.id = 'id_' + (Date.now() + Math.floor(Math.random() * 1000)).toString();
+
+    return attributes;
+  }
+
+  public addPill2Graphology(attributes: QueryGraphNodes, optAttributes: SchemaAttribute[] | undefined = undefined): QueryGraphNodes {
+    attributes = this.configureDefaults(attributes);
+    if (!attributes.type || !attributes.name || !attributes.id) throw Error('type or name is not defined');
+
     // fix handles
-    if (type === QueryElementTypes.Entity) {
-      (attributes as EntityNodeAttributes).leftRelationHandleId = getHandleId(attributes.id, name, type, Handles.EntityLeft, '');
-      (attributes as EntityNodeAttributes).rightRelationHandleId = getHandleId(attributes.id, name, type, Handles.EntityRight, '');
-    } else if (type === QueryElementTypes.Relation) {
-      (attributes as RelationNodeAttributes).leftEntityHandleId = getHandleId(attributes.id, name, type, Handles.RelationLeft, '');
-      (attributes as RelationNodeAttributes).rightEntityHandleId = getHandleId(attributes.id, name, type, Handles.RelationRight, '');
-    } else if (type === QueryElementTypes.Logic) {
+    const defaultHandleData: QueryGraphEdgeHandle = {
+      nodeId: attributes.id,
+      nodeName: attributes.name,
+      nodeType: attributes.type,
+    };
+    if (attributes.type === QueryElementTypes.Entity) {
+      var entityAttributes = attributes as EntityNodeAttributes;
+      entityAttributes.leftRelationHandleId = { ...defaultHandleData, attributeName: Handles.EntityLeft };
+      entityAttributes.rightRelationHandleId = { ...defaultHandleData, attributeName: Handles.EntityRight };
+      optAttributes?.forEach((optAttribute) => {
+        if (!entityAttributes.attributes) entityAttributes.attributes = [];
+        entityAttributes.attributes.push({
+          handleData: { ...defaultHandleData, attributeName: optAttribute.name, attributeType: optAttribute.type },
+        });
+      });
+    } else if (attributes.type === QueryElementTypes.Relation) {
+      var relationAttributes = attributes as RelationNodeAttributes;
+      relationAttributes.leftEntityHandleId = { ...defaultHandleData, attributeName: Handles.RelationLeft };
+      relationAttributes.rightEntityHandleId = { ...defaultHandleData, attributeName: Handles.RelationRight };
+      optAttributes?.forEach((optAttribute) => {
+        if (!relationAttributes.attributes) relationAttributes.attributes = [];
+        relationAttributes.attributes.push({
+          handleData: { ...defaultHandleData, attributeName: optAttribute.name, attributeType: optAttribute.type },
+        });
+      });
+    } else if (attributes.type === QueryElementTypes.Logic) {
+      throw Error('using wrong function! use addLogicPill2Graphology instead');
     }
 
     // Add a node to the graphology object
-    const nodeId = this.addNode(attributes.id, { ...attributes, x, y, width, height });
+    const nodeId = this.addNode(attributes.id, { ...attributes });
 
     // Set the new nodes in the query builder slice TODO: maybe remove for efficiency
-    store.dispatch(setQuerybuilderNodes(this.export()));
+    // dispatch(setQuerybuilderNodes(this.export())); // Can't do this due to loop import
 
     return attributes;
   }
 
-  public addEdge2Graphology(source: QueryGraphNodes, target: QueryGraphNodes, attributes: GAttributes): string | null {
+  public addLogicPill2Graphology(attributes: QueryGraphNodes, inputValues: Record<string, InputNodeTypeTypes> = {}): QueryGraphNodes {
+    attributes = this.configureDefaults(attributes);
+    if (!attributes.type) attributes.type = QueryElementTypes.Logic;
+    if (!attributes.name || !attributes.id) throw Error('type or name is not defined');
+
+    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, '');
+    } else throw Error('using wrong function! use addPill2Graphology instead');
+
+    // Add a node to the graphology object
+    const nodeId = this.addNode(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
+
+    return attributes;
+  }
+
+  public addEdge2Graphology(
+    source: QueryGraphNodes,
+    target: QueryGraphNodes,
+    attributes: QueryGraphEdgesOpt = {},
+    options: AddEdge2GraphologyOptions = {}
+  ): string | null {
+    let sourceAttributeName = '';
+    let sourceAttributeType: InputNodeType | undefined;
+    let targetAttributeName = '';
+    let targetAttributeType: InputNodeType | undefined;
+
+    if (source.type === QueryElementTypes.Entity) {
+      source = source as EntityNodeAttributes;
+      if (!!options?.sourceHandleName) {
+        sourceAttributeName = options?.sourceHandleName;
+        sourceAttributeType = source?.attributes?.find((a) => a.handleData.attributeName === sourceAttributeName)?.handleData.attributeType;
+      } else {
+        sourceAttributeName = Handles.EntityRight;
+      }
+    } else if (source.type === QueryElementTypes.Relation) {
+      sourceAttributeName = Handles.RelationRight;
+      // } else if (source.type === QueryElementTypes.Logic && !!options?.attributeSourceHandle) {
+      //   sourceAttributeName = Handles.LogicRight;
+      //   sourceAttributeType = options.attributeSourceHandle;
+    } else if (source.type === QueryElementTypes.Logic) {
+      if (!options.sourceHandleName) throw Error('sourceHandleName is not defined');
+      sourceAttributeName = options.sourceHandleName;
+      sourceAttributeType = (source as LogicNodeAttributes).logic.output.type;
+      if (!sourceAttributeType) throw Error(`sourceHandleName ${sourceAttributeName} does not exist!`);
+    } else {
+      throw Error('source.type is not correctly defined');
+    }
+
+    if (target.type === QueryElementTypes.Entity) {
+      // if (!!options?.attributeTargetHandle) {
+      //   targetAttributeName = Handles.EntityAttribute;
+      //   targetAttributeType = options.attributeTargetHandle;
+      // } else {
+      targetAttributeName = Handles.EntityRight;
+      // }
+    } else if (target.type === QueryElementTypes.Relation) {
+      targetAttributeName = Handles.RelationRight;
+    } else if (target.type === QueryElementTypes.Logic) {
+      if (!options.targetHandleName) throw Error('targetHandleName is not defined');
+      targetAttributeName = options.targetHandleName;
+      targetAttributeType = (target as LogicNodeAttributes).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');
+    }
+
+    if (!source.id) throw Error('source.id is not defined');
+    if (!target.id) throw Error('target.id is not defined');
+    if (!attributes.type) attributes.type = 'connection';
     // fix handles
-    attributes.sourceHandle = getHandleId(
-      source.id + '_TO_' + target.id,
-      source.name,
-      source.type,
-      source.type === QueryElementTypes.Entity ? Handles.EntityRight : Handles.RelationRight,
-      ''
-    );
-    attributes.targetHandle = getHandleId(
-      source.id + '_TO_' + target.id,
-      target.name,
-      target.type,
-      target.type === QueryElementTypes.Entity ? Handles.EntityLeft : Handles.RelationLeft,
-      ''
-    );
+    attributes.sourceHandleData = {
+      nodeId: source.id,
+      nodeName: source.name || '',
+      nodeType: source.type,
+      attributeName: sourceAttributeName,
+      attributeType: sourceAttributeType,
+    };
+    attributes.targetHandleData = {
+      nodeId: target.id,
+      nodeName: target.name || '',
+      nodeType: target.type,
+      attributeName: targetAttributeName,
+      attributeType: targetAttributeType,
+    };
+
+    // console.log('newEdge', attributes, source, target);
 
     // Add an edge to the graphology object
-    const edgeId = this.addEdge(source.id, target.id, attributes);
+    const edgeId = this.addEdge(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()));
+    // store.dispatch(setQuerybuilderNodes(this.export()));
 
     return edgeId;
   }
diff --git a/libs/shared/lib/querybuilder/model/index.ts b/libs/shared/lib/querybuilder/model/index.ts
index d9af8a031..b1b8ea45a 100644
--- a/libs/shared/lib/querybuilder/model/index.ts
+++ b/libs/shared/lib/querybuilder/model/index.ts
@@ -1,5 +1,6 @@
-import { NodeAttribute, QueryGraphNodes } from './graphology';
-import { SchemaReactflowNode } from './reactflow';
+import { NodeAttribute, QueryGraphEdgeHandle, QueryGraphNodes } from './graphology';
+import { InputNodeType } from './logic/general';
+import { QueryElementTypes, SchemaReactflowNode } from './reactflow';
 
 export * from './BackendQueryFormat';
 export * from './graphology';
@@ -7,28 +8,27 @@ export * from './logic';
 export * from './reactflow';
 
 type ExtraProps = { extra?: string; separator?: string };
-export function getHandleId(
-  nodeId: string,
-  nodeName: string,
-  nodeType: string,
-  attributeName: string,
-  attributeType: string,
-  { extra, separator }: ExtraProps = {}
-): string {
-  if (!extra) extra = '';
+export function toHandleId(handleData: QueryGraphEdgeHandle, separator: string = '__'): string {
+  // if (!extra) extra = '';
   if (!separator) separator = '__';
-  return [nodeId, nodeType, nodeName, attributeName, attributeType, extra].join(separator);
+  return [handleData.nodeId, handleData.nodeType, handleData.nodeName, handleData.attributeName, handleData.attributeType].join(separator);
 }
-export function getHandleIdFromGraphology(node: QueryGraphNodes, attribute: NodeAttribute, options: ExtraProps = {}): string {
-  return getHandleId(node.id || '', node.name, node.type, attribute.name, attribute.type, options);
+// export function getHandleIdFromGraphology(node: QueryGraphNodes, attribute: NodeAttribute, options: ExtraProps = {}): string {
+//   return toHandleId(node.id || '', node.name, node.type, attribute.name, attribute.type, options);
+// }
+export function handleDataFromReactflowToId(node: SchemaReactflowNode, attribute: NodeAttribute, options: ExtraProps = {}): string {
+  if (!node.data.name) throw Error('node.data is not defined');
+  return toHandleId({
+    nodeId: node.id,
+    nodeName: node.data.name,
+    nodeType: node.type as QueryElementTypes,
+    attributeName: attribute.handleData.attributeName,
+    attributeType: attribute.handleData.attributeType,
+  });
 }
-export function getHandleIdFromReactflow(node: SchemaReactflowNode, attribute: NodeAttribute, options: ExtraProps = {}): string {
-  return getHandleId(node.id, node.data.name, node.type, attribute.name, attribute.type, options);
-}
-export function fromHandleId(
-  handleId: string,
-  separator: string = '__'
-): { nodeId: string; nodeName: string; nodeType: string; attributeName: string; attributeType: string } {
-  const [nodeId, nodeType, nodeName, attributeName, attributeType] = handleId.split(separator);
-  return { nodeId, nodeType, nodeName, attributeName, attributeType };
+export function toHandleData(handleId: string, separator: string = '__'): QueryGraphEdgeHandle {
+  let [nodeId, nodeType, nodeName, attributeName, attributeType] = handleId.split(separator);
+  const _nodeType = nodeType as QueryElementTypes;
+  const _attributeType = attributeType as InputNodeType;
+  return { nodeId, nodeType: _nodeType, nodeName, attributeName, attributeType: _attributeType };
 }
diff --git a/libs/shared/lib/querybuilder/model/logic/general.ts b/libs/shared/lib/querybuilder/model/logic/general.ts
index c706c77c9..7963d68ac 100644
--- a/libs/shared/lib/querybuilder/model/logic/general.ts
+++ b/libs/shared/lib/querybuilder/model/logic/general.ts
@@ -1,11 +1,196 @@
-import { Position } from 'reactflow';
-
+export type InputNodeTypeTypes = string | number | boolean;
 export type InputNodeType = 'string' | 'float' | 'int' | 'bool' | 'date' | 'time' | 'datetime' | 'duration';
 
+export enum MathFilterTypes {
+  EQUAL = '==',
+  NOT_EQUAL = '!=',
+  GREATER_THAN = '>',
+  LESS_THAN = '<',
+  GREATER_THAN_EQUAL = '>=',
+  LESS_THAN_EQUAL = '<=',
+}
+
+export enum MathAggregationTypes {
+  AVG = 'Avg',
+  COUNT = 'Count',
+  MAX = 'Max',
+  MIN = 'Min',
+  SUM = 'Sum',
+}
+
+export enum MathFunctionTypes {
+  ADD = '+',
+  SUBTRACT = '-',
+  MULTIPLY = '*',
+  DIVIDE = '/',
+  // MODULO = '%',
+  // POWER = '^',
+  // SQRT = 'Sqrt',
+  // ABS = 'Abs',
+  // LOG = 'Log',
+  // EXP = 'Exp',
+  // ROUND = 'Round',
+  // CEIL = 'Ceil',
+  // FLOOR = 'Floor',
+}
+
+export enum StringFunctionTypes {
+  LOWER = 'Lower',
+  UPPER = 'Upper',
+  // CONCAT = 'Concat',
+  // SUBSTRING = 'Substring',
+  // TRIM = 'Trim',
+}
+
+export enum StringFilterTypes {
+  EQUAL = '==',
+  NOT_EQUAL = '!=',
+  LIKE = 'Like',
+  // NOT_LIKE = 'Not Like',
+  // IN = 'In',
+  // NOT_IN = 'Not In',
+}
+
+export enum LogicFunctionTypes {
+  AND = 'And',
+  OR = 'Or',
+  NOT = 'Not',
+}
+
+// Logic
+export type AndLogicStatement = ['And', AnyStatement, AnyStatement];
+export type OrLogicStatement = ['Or', AnyStatement, AnyStatement];
+export type NotLogicStatement = ['Not', AnyStatement];
+export type LogicStatement = AndLogicStatement | OrLogicStatement | NotLogicStatement;
+
+// Numbers
+export type PlusStatement = ['+', AnyStatement, AnyStatement];
+export type MinusStatement = ['-', AnyStatement, AnyStatement];
+export type MultiplyStatement = ['*', AnyStatement, AnyStatement];
+export type DivideStatement = ['/', AnyStatement, AnyStatement];
+export type ModuloStatement = ['%', AnyStatement, AnyStatement];
+export type PowerStatement = ['^', AnyStatement, AnyStatement];
+export type SqrtStatement = ['Sqrt', AnyStatement];
+export type AbsStatement = ['Abs', AnyStatement];
+export type LogStatement = ['Log', AnyStatement];
+export type ExpStatement = ['Exp', AnyStatement];
+export type AllMathStatement =
+  | PlusStatement
+  | MinusStatement
+  | MultiplyStatement
+  | DivideStatement
+  | ModuloStatement
+  | PowerStatement
+  | SqrtStatement
+  | AbsStatement
+  | LogStatement
+  | ExpStatement;
+
+// Comparisons
+export type EqualStatement = ['==', AnyStatement, AnyStatement];
+export type NotEqualStatement = ['!=', AnyStatement, AnyStatement];
+export type GreaterThanStatement = ['>', AnyStatement, AnyStatement];
+export type LessThanStatement = ['<', AnyStatement, AnyStatement];
+export type GreaterThanEqualStatement = ['>=', AnyStatement, AnyStatement];
+export type LessThanEqualStatement = ['<=', AnyStatement, AnyStatement];
+export type AllComparisonStatement =
+  | EqualStatement
+  | NotEqualStatement
+  | GreaterThanStatement
+  | LessThanStatement
+  | GreaterThanEqualStatement
+  | LessThanEqualStatement;
+
+// Strings
+export type ConcatStatement = ['Concat', AnyStatement, AnyStatement];
+export type LowerStatement = ['Lower', AnyStatement];
+export type UpperStatement = ['Upper', AnyStatement];
+export type SubstringStatement = ['Substring', AnyStatement, AnyStatement, AnyStatement];
+export type TrimStatement = ['Trim', AnyStatement];
+export type AllStringStatement = ConcatStatement | LowerStatement | UpperStatement | SubstringStatement | TrimStatement;
+
+// Dates
+export type DateStatement = ['Date', AnyStatement];
+export type YearStatement = ['Year', AnyStatement];
+export type MonthStatement = ['Month', AnyStatement];
+export type DayStatement = ['Day', AnyStatement];
+export type HourStatement = ['Hour', AnyStatement];
+export type MinuteStatement = ['Minute', AnyStatement];
+export type SecondStatement = ['Second', AnyStatement];
+export type AllDateStatement =
+  | DateStatement
+  | YearStatement
+  | MonthStatement
+  | DayStatement
+  | HourStatement
+  | MinuteStatement
+  | SecondStatement;
+
+// Aggregations
+export type AvgStatement = ['Avg', AnyStatement];
+export type CountStatement = ['Count', AnyStatement];
+export type MaxStatement = ['Max', AnyStatement];
+export type MinStatement = ['Min', AnyStatement];
+export type SumStatement = ['Sum', AnyStatement];
+export type RoundStatement = ['Round', AnyStatement];
+export type CeilStatement = ['Ceil', AnyStatement];
+export type FloorStatement = ['Floor', AnyStatement];
+export type AllAggregationStatement =
+  | AvgStatement
+  | CountStatement
+  | MaxStatement
+  | MinStatement
+  | SumStatement
+  | RoundStatement
+  | CeilStatement
+  | FloorStatement;
+
+// Filters
+export type EqualFilterStatement = ['==', AnyStatement, AnyStatement];
+export type NotEqualFilterStatement = ['!=', AnyStatement, AnyStatement];
+export type LikeFilterStatement = ['Like', AnyStatement, AnyStatement];
+export type NotLikeFilterStatement = ['Not Like', AnyStatement, AnyStatement];
+export type InFilterStatement = ['In', AnyStatement, AnyStatement];
+export type NotInFilterStatement = ['Not In', AnyStatement, AnyStatement];
+export type FilterStatement =
+  | EqualFilterStatement
+  | NotEqualFilterStatement
+  | LikeFilterStatement
+  | NotLikeFilterStatement
+  | InFilterStatement
+  | NotInFilterStatement;
+
+// Regular
+type WithPrefix<T extends string> = `${T}${string}`;
+
+export type NumberStatement = number;
+export type StringStatement = string;
+export type BooleanStatement = boolean;
+export type DateStringStatement = string;
+export type ReferenceStatement = WithPrefix<'@'>;
+export type RegularStatement = NumberStatement | StringStatement | BooleanStatement | DateStringStatement;
+export type AllRegularStatement = RegularStatement | ReferenceStatement;
+
+export type AllLogicStatement =
+  | LogicStatement
+  | AllMathStatement
+  | AllComparisonStatement
+  | AllStringStatement
+  | AllDateStatement
+  | AllAggregationStatement
+  | FilterStatement;
+
+export type AnyStatement = AllLogicStatement | AllRegularStatement;
+
+export interface OutputNode {
+  name: string;
+  type: InputNodeType;
+}
+
 export interface InputNode {
   name: string;
-  type: InputNodeType[];
-  position: Position;
+  type: InputNodeType;
+  default: number | string | boolean;
 }
 
 export interface GeneralDescription<T> {
@@ -14,5 +199,7 @@ export interface GeneralDescription<T> {
   description: string;
   numInputs?: number;
   inputs: InputNode[];
+  output: OutputNode;
   key: string;
+  logic: AllLogicStatement;
 }
diff --git a/libs/shared/lib/querybuilder/model/logic/index.ts b/libs/shared/lib/querybuilder/model/logic/index.ts
index 9b2dd3301..00f4dfeff 100644
--- a/libs/shared/lib/querybuilder/model/logic/index.ts
+++ b/libs/shared/lib/querybuilder/model/logic/index.ts
@@ -1,17 +1,27 @@
-import { GeneralDescription, InputNodeType } from './general';
-import { MathFilterTypes, MathFilters } from './mathFilters';
-import { MathFunctionTypes, MathFunctions } from './mathFunctions';
-import { StringFilterTypes, StringFilters } from './stringFilters';
-import { StringFunctionTypes, StringFunctions } from './stringFunctions';
+import {
+  GeneralDescription,
+  InputNodeType,
+  MathFunctionTypes,
+  MathFilterTypes,
+  StringFilterTypes,
+  StringFunctionTypes,
+  MathAggregationTypes,
+} from './general';
+import { MathAggregations } from './mathAggregations';
+import { MathFilters } from './mathFilters';
+import { MathFunctions } from './mathFunctions';
+import { StringFilters } from './stringFilters';
+import { StringFunctions } from './stringFunctions';
 
-export type AllLogicTypes = MathFilterTypes | MathFunctionTypes | StringFilterTypes | StringFunctionTypes;
+export type AllLogicTypes = MathFilterTypes | MathFunctionTypes | MathAggregationTypes | StringFilterTypes | StringFunctionTypes;
 export type AllLogicDescriptions = GeneralDescription<AllLogicTypes>;
 
 export const AllLogicMap: Record<string, AllLogicDescriptions> = {
-  ...Object.assign({}, ...MathFilters.map((d) => ({ [d.key]: d }))),
-  ...Object.assign({}, ...MathFunctions.map((d) => ({ [d.key]: d }))),
-  ...Object.assign({}, ...StringFilters.map((d) => ({ [d.key]: d }))),
-  ...Object.assign({}, ...StringFunctions.map((d) => ({ [d.key]: d }))),
+  ...Object.fromEntries(Object.values(MathFilters).map((x) => [x.key, x])),
+  ...Object.fromEntries(Object.values(MathFunctions).map((x) => [x.key, x])),
+  ...Object.fromEntries(Object.values(MathAggregations).map((x) => [x.key, x])),
+  ...Object.fromEntries(Object.values(StringFilters).map((x) => [x.key, x])),
+  ...Object.fromEntries(Object.values(StringFunctions).map((x) => [x.key, x])),
 };
 
 export * from './graphFunctions';
diff --git a/libs/shared/lib/querybuilder/model/logic/mathAggregations.tsx b/libs/shared/lib/querybuilder/model/logic/mathAggregations.tsx
new file mode 100644
index 000000000..5baebb3b6
--- /dev/null
+++ b/libs/shared/lib/querybuilder/model/logic/mathAggregations.tsx
@@ -0,0 +1,745 @@
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+
+import { Position } from 'reactflow';
+import { GeneralDescription, MathAggregationTypes } from './general';
+
+export const MathAggregations: Record<MathAggregationTypes, GeneralDescription<MathAggregationTypes>> = {
+  [MathAggregationTypes.AVG]: {
+    key: 'mathFunctionAvg',
+    name: 'Average',
+    type: MathAggregationTypes.AVG,
+    description: 'Average of all values',
+    numInputs: 1,
+    inputs: [{ name: '1', type: 'float', default: 0 }],
+    output: { name: 'avg', type: 'float' },
+    logic: ['Avg', '@1'],
+  },
+  [MathAggregationTypes.COUNT]: {
+    key: 'mathFunctionCount',
+    name: 'Count',
+    type: MathAggregationTypes.COUNT,
+    description: 'Count the number of values',
+    numInputs: 1,
+    inputs: [{ name: '1', type: 'float', default: 0 }],
+
+    output: { name: 'count', type: 'float' },
+    logic: ['Count', '@1'],
+  },
+  [MathAggregationTypes.MAX]: {
+    key: 'mathFunctionMax',
+    name: 'Maximum',
+    type: MathAggregationTypes.MAX,
+    description: 'Maximum of all values',
+    numInputs: 1,
+    inputs: [{ name: '1', type: 'float', default: 0 }],
+    output: { name: 'max', type: 'float' },
+    logic: ['Max', '@1'],
+  },
+  [MathAggregationTypes.MIN]: {
+    key: 'mathFunctionMin',
+    name: 'Minimum',
+    type: MathAggregationTypes.MIN,
+    description: 'Minimum of all values',
+    numInputs: 1,
+    inputs: [{ name: '1', type: 'float', default: 0 }],
+    output: { name: 'min', type: 'float' },
+    logic: ['Min', '@1'],
+  },
+  [MathAggregationTypes.SUM]: {
+    key: 'mathFunctionSum',
+    name: 'Sum',
+    type: MathAggregationTypes.SUM,
+    description: 'Sum of all values',
+    numInputs: 1,
+    inputs: [{ name: '1', type: 'float', default: 0 }],
+    output: { name: 'sum', type: 'float' },
+    logic: ['Sum', '@1'],
+  },
+  // [MathAggregationTypes.STD]: {
+  //   key: 'mathFunctionStd',
+  //   name: 'Standard Deviation',
+  //   type: MathAggregationTypes.STD,
+  //   description: 'Standard deviation of all values',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'std', type: 'float' },
+  //   logic: ['Std', '@1'],
+  // },
+  // [MathAggregationTypes.ADD]: {
+  //   key: 'mathFunctionAdd',
+  //   name: 'Add',
+  //   type: MathAggregationTypes.ADD,
+  //   description: 'Add two values',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: '1', type: 'float', default: 0 },
+  //     { name: '2', type: 'float', default: 0 },
+  //   ],
+  //   output: { name: '+', type: 'float' },
+  //   logic: ['+', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.SUBTRACT]: {
+  //   key: 'mathFunctionSubtract',
+  //   name: 'Subtract',
+  //   type: MathAggregationTypes.SUBTRACT,
+  //   description: 'Subtract two values',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: '1', type: 'float' },
+  //     { name: '2', type: 'float' },
+  //   ],
+  //   output: { name: '-', type: 'float' },
+  //   logic: ['-', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.MULTIPLY]: {
+  //   key: 'mathFunctionMultiply',
+  //   name: 'Multiply',
+  //   type: MathAggregationTypes.MULTIPLY,
+  //   description: 'Multiply two values',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: '1', type: 'float' },
+  //     { name: '2', type: 'float' },
+  //   ],
+  //   output: { name: '*', type: 'float' },
+  //   logic: ['*', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.DIVIDE]: {
+  //   key: 'mathFunctionDivide',
+  //   name: 'Divide',
+  //   type: MathAggregationTypes.DIVIDE,
+  //   description: 'Divide two values',
+
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: '1', type: 'float' },
+  //     { name: '2', type: 'float' },
+  //   ],
+  //   output: { name: '/', type: 'float' },
+  //   logic: ['/', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.POWER]: {
+  //   key: 'mathFunctionPower',
+  //   name: 'Power',
+  //   type: MathAggregationTypes.POWER,
+  //   description: 'Raise a value to the power of another value',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: '1', type: 'float' },
+  //     { name: '2', type: 'float' },
+  //   ],
+  //   output: { name: '^', type: 'float' },
+  //   logic: ['^', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.SQRT]: {
+  //   key: 'mathFunctionSqrt',
+  //   name: 'Square Root',
+  //   type: MathAggregationTypes.SQRT,
+  //   description: 'Square root of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'sqrt', type: 'float' },
+  //   logic: ['Sqrt', '@1'],
+  // },
+  // [MathAggregationTypes.LOG]: {
+  //   key: 'mathFunctionLog',
+  //   name: 'Logarithm',
+  //   type: MathAggregationTypes.LOG,
+  //   description: 'Logarithm of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'log', type: 'float' },
+  //   logic: ['Log', '@1'],
+  // },
+  // [MathAggregationTypes.EXP]: {
+  //   key: 'mathFunctionExp',
+  //   name: 'Exponential',
+  //   type: MathAggregationTypes.EXP,
+  //   description: 'Exponential of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'exp', type: 'float' },
+  //   logic: ['Exp', '@1'],
+  // },
+  // [MathAggregationTypes.ABS]: {
+  //   key: 'mathFunctionAbs',
+  //   name: 'Absolute Value',
+  //   type: MathAggregationTypes.ABS,
+  //   description: 'Absolute value of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'abs', type: 'float' },
+  //   logic: ['Abs', '@1'],
+  // },
+  // [MathAggregationTypes.CEIL]: {
+  //   key: 'mathFunctionCeil',
+  //   name: 'Ceiling',
+  //   type: MathAggregationTypes.CEIL,
+  //   description: 'Ceiling of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'ceil', type: 'float' },
+  //   logic: ['Ceil', '@1'],
+  // },
+  // [MathAggregationTypes.FLOOR]: {
+  //   key: 'mathFunctionFloor',
+  //   name: 'Floor',
+  //   type: MathAggregationTypes.FLOOR,
+  //   description: 'Floor of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'floor', type: 'float' },
+  //   logic: ['Floor', '@1'],
+  // },
+  // [MathAggregationTypes.ROUND]: {
+  //   key: 'mathFunctionRound',
+  //   name: 'Round',
+  //   type: MathAggregationTypes.ROUND,
+  //   description: 'Round a value to the nearest integer',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'round', type: 'float' },
+  //   logic: ['Round', '@1'],
+  // },
+  // [MathAggregationTypes.TRUNC]: {
+  //   key: 'mathFunctionTrunc',
+  //   name: 'Truncate',
+  //   type: MathAggregationTypes.TRUNC,
+  //   description: 'Truncate a value to the nearest integer towards zero',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+
+  //   output: { name: 'trunc', type: 'float' },
+  //   logic: ['Trunc', '@1'],
+  // },
+  // [MathAggregationTypes.RANDOM]: {
+  //   key: 'mathFunctionRandom',
+  //   name: 'Random',
+  //   type: MathAggregationTypes.RANDOM,
+  //   description: 'Random value between 0 and 1',
+  //   numInputs: 0,
+  //   inputs: [],
+  //   output: { name: 'random', type: 'float' },
+  //   logic: ['Random'],
+  // },
+  // [MathAggregationTypes.RANDOMINT]: {
+  //   key: 'mathFunctionRandomInt',
+  //   name: 'Random Integer',
+  //   type: MathAggregationTypes.RANDOMINT,
+  //   description: 'Random integer between two values',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: 'min', type: 'float' },
+  //     { name: 'max', type: 'float' },
+  //   ],
+  //   output: { name: 'randomInt', type: 'float' },
+  //   logic: ['RandomInt', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.SIN]: {
+  //   key: 'mathFunctionSin',
+  //   name: 'Sine',
+  //   type: MathAggregationTypes.SIN,
+  //   description: 'Sine of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'sin', type: 'float' },
+  //   logic: ['Sin', '@1'],
+  // },
+  // [MathAggregationTypes.COS]: {
+  //   key: 'mathFunctionCos',
+  //   name: 'Cosine',
+  //   type: MathAggregationTypes.COS,
+  //   description: 'Cosine of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'cos', type: 'float' },
+  //   logic: ['Cos', '@1'],
+  // },
+  // [MathAggregationTypes.TAN]: {
+  //   key: 'mathFunctionTan',
+  //   name: 'Tangent',
+  //   type: MathAggregationTypes.TAN,
+  //   description: 'Tangent of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'tan', type: 'float' },
+  //   logic: ['Tan', '@1'],
+  // },
+  // [MathAggregationTypes.ASIN]: {
+  //   key: 'mathFunctionAsin',
+  //   name: 'Arcsine',
+  //   type: MathAggregationTypes.ASIN,
+  //   description: 'Arcsine of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'asin', type: 'float' },
+  //   logic: ['Asin', '@1'],
+  // },
+  // [MathAggregationTypes.ACOS]: {
+  //   key: 'mathFunctionAcos',
+  //   name: 'Arccosine',
+  //   type: MathAggregationTypes.ACOS,
+  //   description: 'Arccosine of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'acos', type: 'float' },
+  //   logic: ['Acos', '@1'],
+  // },
+  // [MathAggregationTypes.ATAN]: {
+  //   key: 'mathFunctionAtan',
+  //   name: 'Arctangent',
+  //   type: MathAggregationTypes.ATAN,
+  //   description: 'Arctangent of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'atan', type: 'float' },
+  //   logic: ['Atan', '@1'],
+  // },
+  // [MathAggregationTypes.SINH]: {
+  //   key: 'mathFunctionSinh',
+  //   name: 'Hyperbolic Sine',
+  //   type: MathAggregationTypes.SINH,
+  //   description: 'Hyperbolic sine of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'sinh', type: 'float' },
+  //   logic: ['Sinh', '@1'],
+  // },
+  // [MathAggregationTypes.COSH]: {
+  //   key: 'mathFunctionCosh',
+  //   name: 'Hyperbolic Cosine',
+  //   type: MathAggregationTypes.COSH,
+  //   description: 'Hyperbolic cosine of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'cosh', type: 'float' },
+  //   logic: ['Cosh', '@1'],
+  // },
+  // [MathAggregationTypes.TANH]: {
+  //   key: 'mathFunctionTanh',
+  //   name: 'Hyperbolic Tangent',
+  //   type: MathAggregationTypes.TANH,
+  //   description: 'Hyperbolic tangent of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'tanh', type: 'float' },
+  //   logic: ['Tanh', '@1'],
+  // },
+  // [MathAggregationTypes.ASINH]: {
+  //   key: 'mathFunctionAsinh',
+  //   name: 'Hyperbolic Arcsine',
+  //   type: MathAggregationTypes.ASINH,
+  //   description: 'Hyperbolic arcsine of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  // output: { name: 'asinh', type: 'float' },
+  //   logic: ['Asinh', '@1'],
+  // },
+  // [MathAggregationTypes.ACOSH]: {
+  //   key: 'mathFunctionAcosh',
+  //   name: 'Hyperbolic Arccosine',
+  //   type: MathAggregationTypes.ACOSH,
+  //   description: 'Hyperbolic arccosine of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  // output: { name: 'acosh', type: 'float' },
+  //   logic: ['Acosh', '@1'],
+  // },
+  // [MathAggregationTypes.ATANH]: {
+  //   key: 'mathFunctionAtanh',
+  //   name: 'Hyperbolic Arctangent',
+  //   type: MathAggregationTypes.ATANH,
+  //   description: 'Hyperbolic arctangent of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  // output: { name: 'atanh', type: 'float' },
+  //   logic: ['Atanh', '@1'],
+  // },
+  // [MathAggregationTypes.DEGREES]: {
+  //   key: 'mathFunctionDegrees',
+  //   name: 'Degrees',
+  //   type: MathAggregationTypes.DEGREES,
+  //   description: 'Convert a value from radians to degrees',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  // output: { name: 'degrees', type: 'float' },
+  //   logic: ['Degrees', '@1'],
+  // },
+  // [MathAggregationTypes.RADIANS]: {
+  //   key: 'mathFunctionRadians',
+  //   name: 'Radians',
+  //   type: MathAggregationTypes.RADIANS,
+  //   description: 'Convert a value from degrees to radians',
+  //   numInputs: 1,
+
+  //   inputs: [{ name: '1', type: 'float' }],
+  // output: { name: 'radians', type: 'float' },
+  //   logic: ['Radians', '@1'],
+  // },
+  // [MathAggregationTypes.SIGN]: {
+  //   key: 'mathFunctionSign',
+  //   name: 'Sign',
+  //   type: MathAggregationTypes.SIGN,
+  //   description: 'Sign of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  // output: { name: 'sign', type: 'float' },
+  //   logic: ['Sign', '@1'],
+  // },
+  // [MathAggregationTypes.RANDOMNORMAL]: {
+  //   key: 'mathFunctionRandomNormal',
+  //   name: 'Random Normal',
+  //   type: MathAggregationTypes.RANDOMNORMAL,
+  //   description: 'Random value from a normal distribution',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: 'mean', type: 'float' },
+  //     { name: 'std', type: 'float' },
+  //   ],
+  // output: { name: 'randomNormal', type: 'float' },
+  //   logic: ['RandomNormal', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.RANDOMLOGNORMAL]: {
+  //   key: 'mathFunctionRandomLogNormal',
+  //   name: 'Random Log Normal',
+  //   type: MathAggregationTypes.RANDOMLOGNORMAL,
+  //   description: 'Random value from a log normal distribution',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: 'mean', type: 'float' },
+  //     { name: 'std', type: 'float' },
+  //   ],
+  // output: { name: 'randomLogNormal', type: 'float' },
+  //   logic: ['RandomLogNormal', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.RANDOMEXPONENTIAL]: {
+  //   key: 'mathFunctionRandomExponential',
+  //   name: 'Random Exponential',
+  //   type: MathAggregationTypes.RANDOMEXPONENTIAL,
+  //   description: 'Random value from an exponential distribution',
+  //   numInputs: 1,
+  //   inputs: [{ name: 'lambda', type: 'float' }],
+  // output: { name: 'randomExponential', type: 'float' },
+  //   logic: ['RandomExponential', '@1'],
+  // },
+  // [MathAggregationTypes.RANDOMGAMMA]: {
+  //   key: 'mathFunctionRandomGamma',
+  //   name: 'Random Gamma',
+  //   type: MathAggregationTypes.RANDOMGAMMA,
+  //   description: 'Random value from a gamma distribution',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: 'alpha', type: 'float' },
+  //     { name: 'beta', type: 'float' },
+  //   ],
+  // output: { name: 'randomGamma', type: 'float' },
+  //   logic: ['RandomGamma', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.RANDOMBETA]: {
+  //   key: 'mathFunctionRandomBeta',
+  //   name: 'Random Beta',
+  //   type: MathAggregationTypes.RANDOMBETA,
+  //   description: 'Random value from a beta distribution',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: 'alpha', type: 'float' },
+  //     { name: 'beta', type: 'float' },
+  //   ],
+  // output: { name: 'randomBeta', type: 'float' },
+  //   logic: ['RandomBeta', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.RANDOMCHISQUARE]: {
+  //   key: 'mathFunctionRandomChiSquare',
+  //   name: 'Random Chi Square',
+  //   type: MathAggregationTypes.RANDOMCHISQUARE,
+  //   description: 'Random value from a chi square distribution',
+  //   numInputs: 1,
+  //   inputs: [{ name: 'k', type: 'float' }],
+  // output: { name: 'randomChiSquare', type: 'float' },
+  //   logic: ['RandomChiSquare', '@1'],
+  // },
+  // [MathAggregationTypes.RANDOMWEIBULL]: {
+  //   key: 'mathFunctionRandomWeibull',
+  //   name: 'Random Weibull',
+  //   type: MathAggregationTypes.RANDOMWEIBULL,
+  //   description: 'Random value from a Weibull distribution',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: 'k', type: 'float' },
+  //     { name: 'lambda', type: 'float' },
+  //   ],
+  // output: { name: 'randomWeibull', type: 'float' },
+  //   logic: ['RandomWeibull', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.RANDOMCAUCHY]: {
+  //   key: 'mathFunctionRandomCauchy',
+  //   name: 'Random Cauchy',
+  //   type: MathAggregationTypes.RANDOMCAUCHY,
+  //   description: 'Random value from a Cauchy distribution',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: 'x0', type: 'float' },
+  //     { name: 'gamma', type: 'float' },
+  //   ],
+  // output: { name: 'randomCauchy', type: 'float' },
+  //   logic: ['RandomCauchy', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.RANDOMPOISSON]: {
+  //   key: 'mathFunctionRandomPoisson',
+  //   name: 'Random Poisson',
+  //   type: MathAggregationTypes.RANDOMPOISSON,
+  //   description: 'Random value from a Poisson distribution',
+  //   numInputs: 1,
+  //   inputs: [{ name: 'lambda', type: 'float' }],
+  // output: { name: 'randomPoisson', type: 'float' },
+  //   logic: ['RandomPoisson', '@1'],
+  // },
+  // [MathAggregationTypes.RANDOMIRWINHALL]: {
+  //   key: 'mathFunctionRandomIrwinHall',
+  //   name: 'Random Irwin Hall',
+  //   type: MathAggregationTypes.RANDOMIRWINHALL,
+  //   description: 'Random value from an Irwin Hall distribution',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: 'n', type: 'float' },
+  //     { name: 'scale', type: 'float' },
+  //   ],
+  // output: { name: 'randomIrwinHall', type: 'float' },
+  //   logic: ['RandomIrwinHall', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.CHIQUARETEST]: {
+  //   key: 'mathFunctionChiSquareTest',
+  //   name: 'Chi Square Test',
+  //   type: MathAggregationTypes.CHIQUARETEST,
+  //   description: 'Chi square test',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: 'observed', type: 'float' },
+  //     { name: 'expected', type: 'float' },
+  //   ],
+  // output: { name: 'chiSquareTest', type: 'float' },
+  //   logic: ['ChiSquareTest', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.CORRELATION]: {
+  //   key: 'mathFunctionCorrelation',
+  //   name: 'Correlation',
+  //   type: MathAggregationTypes.CORRELATION,
+  //   description: 'Correlation between two values',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: '1', type: 'float' },
+  //     { name: '2', type: 'float' },
+  //   ],
+  // output: { name: 'correlation', type: 'float' },
+  //   logic: ['Correlation', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.COVARIANCE]: {
+  //   key: 'mathFunctionCovariance',
+  //   name: 'Covariance',
+  //   type: MathAggregationTypes.COVARIANCE,
+  //   description: 'Covariance between two values',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: '1', type: 'float' },
+  //     { name: '2', type: 'float' },
+  //   ],
+  // output: { name: 'covariance', type: 'float' },
+  //   logic: ['Covariance', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.FREQUENCY]: {
+  //   key: 'mathFunctionFrequency',
+  //   name: 'Frequency',
+  //   type: MathAggregationTypes.FREQUENCY,
+  //   description: 'Frequency of a value in a dataset',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: 'value', type: 'float' },
+  //     { name: 'dataset', type: 'float' },
+  //   ],
+  // output: { name: 'frequency', type: 'float' },
+  //   logic: ['Frequency', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.MEAN]: {
+  //   key: 'mathFunctionMean',
+  //   name: 'Mean',
+  //   type: MathAggregationTypes.MEAN,
+  //   description: 'Mean of a dataset',
+  //   numInputs: 1,
+  //   inputs: [{ name: 'dataset', type: 'float' }],
+  // output: { name: 'mean', type: 'float' },
+  //   logic: ['Mean', '@1'],
+  // },
+  // [MathAggregationTypes.MEDIAN]: {
+  //   key: 'mathFunctionMedian',
+  //   name: 'Median',
+  //   type: MathAggregationTypes.MEDIAN,
+  //   description: 'Median of a dataset',
+  //   numInputs: 1,
+  //   inputs: [{ name: 'dataset', type: 'float' }],
+  // output: { name: 'median', type: 'float' },
+  //   logic: ['Median', '@1'],
+  // },
+  // [MathAggregationTypes.MODE]: {
+  //   key: 'mathFunctionMode',
+  //   name: 'Mode',
+  //   type: MathAggregationTypes.MODE,
+  //   description: 'Mode of a dataset',
+  //   numInputs: 1,
+  //   inputs: [{ name: 'dataset', type: 'float' }],
+  // output: { name: 'mode', type: 'float' },
+  //   logic: ['Mode', '@1'],
+  // },
+  // [MathAggregationTypes.RANK]: {
+  //   key: 'mathFunctionRank',
+  //   name: 'Rank',
+  //   type: MathAggregationTypes.RANK,
+  //   description: 'Rank of a value in a dataset',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: 'value', type: 'float' },
+  //     { name: 'dataset', type: 'float' },
+  //   ],
+  // output: { name: 'rank', type: 'float' },
+  //   logic: ['Rank', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.STDEV]: {
+  //   key: 'mathFunctionStdev',
+  //   name: 'Standard Deviation',
+  //   type: MathAggregationTypes.STDEV,
+  //   description: 'Standard deviation of a dataset',
+  //   numInputs: 1,
+  //   inputs: [{ name: 'dataset', type: 'float' }],
+  // output: { name: 'stdev', type: 'float' },
+  //   logic: ['Stdev', '@1'],
+  // },
+  // [MathAggregationTypes.VARIANCE]: {
+  //   key: 'mathFunctionVariance',
+  //   name: 'Variance',
+  //   type: MathAggregationTypes.VARIANCE,
+  //   description: 'Variance of a dataset',
+  //   numInputs: 1,
+  //   inputs: [{ name: 'dataset', type: 'float' }],
+  // output: { name: 'variance', type: 'float' },
+  //   logic: ['Variance', '@1'],
+  // },
+  // [MathAggregationTypes.ZSCORE]: {
+  //   key: 'mathFunctionZscore',
+  //   name: 'Z-Score',
+  //   type: MathAggregationTypes.ZSCORE,
+  //   description: 'Z-score of a value in a dataset',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: 'value', type: 'float' },
+  //     { name: 'dataset', type: 'float' },
+  //   ],
+  // output: { name: 'zscore', type: 'float' },
+  //   logic: ['Zscore', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.AND]: {
+  //   key: 'mathFunctionAnd',
+  //   name: 'And',
+  //   type: MathAggregationTypes.AND,
+  //   description: 'Logical AND of two values',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: '1', type: 'boolean' },
+  //     { name: '2', type: 'boolean' },
+  //   ],
+  // output: { name: 'and', type: 'boolean' },
+  //   logic: ['And', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.OR]: {
+  //   key: 'mathFunctionOr',
+  //   name: 'Or',
+  //   type: MathAggregationTypes.OR,
+  //   description: 'Logical OR of two values',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: '1', type: 'boolean' },
+  //     { name: '2', type: 'boolean' },
+  //   ],
+  // output: { name: 'or', type: 'boolean' },
+  //   logic: ['Or', '@1', '@2'],
+  // },
+  // [MathAggregationTypes.NOT]: {
+  //   key: 'mathFunctionNot',
+  //   name: 'Not',
+  //   type: MathAggregationTypes.NOT,
+  //   description: 'Logical NOT of a value',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'boolean' }],
+  // output: { name: 'not', type: 'boolean' },
+  //   logic: ['Not', '@1'],
+  // },
+
+  // {
+  //   key: 'mathFunctionCount',
+  //   name: 'Count',
+  //   type: MathAggregationTypes.COUNT,
+  //   description: 'Count the number of values',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'count', type: 'float' },
+  //   logic: ['Count', '@1'],
+  // },
+  // {
+  //   key: 'mathFunctionMax',
+  //   name: 'Maximum',
+  //   type: MathAggregationTypes.MAX,
+  //   description: 'Maximum of all values',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'max', type: 'float' },
+  //   logic: ['Max', '@1'],
+  // },
+  // {
+  //   key: 'mathFunctionMin',
+  //   name: 'Minimum',
+  //   type: MathAggregationTypes.MIN,
+  //   description: 'Minimum of all values',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'min', type: 'float' },
+  //   logic: ['Min', '@1'],
+  // },
+  // {
+  //   key: 'mathFunctionSum',
+  //   name: 'Sum',
+  //   type: MathAggregationTypes.SUM,
+  //   description: 'Sum of all values',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'float' }],
+  //   output: { name: 'sum', type: 'float' },
+  //   logic: ['Sum', '@1'],
+  // },
+  // // {
+  // //   key: 'mathFunctionStd',
+  // //   name: 'Standard Deviation',
+  // //   type: MathAggregationTypes.STD,
+  // //   description: 'Standard deviation of all values',
+  // //   numInputs: 1,
+  // //   inputs: [{ name: '1', type: 'float' }],
+  // //   output: { name: 'std', type: 'float' },
+  // //   logic: ['Std', '@1'],
+  // // },
+  // {
+  //   key: 'mathFunctionAdd',
+  //   name: 'Add',
+  //   type: MathAggregationTypes.ADD,
+  //   description: 'Add two values',
+  //   numInputs: 2,
+  //   inputs: [
+  //     { name: '1', type: 'float' },
+  //     { name: '2', type: 'float' },
+  //   ],
+  //   output: { name: '+', type: 'float' },
+  //   logic: ['+', '@1', '@2'],
+  // },
+};
+
+/** All available functions in the function bar. */
+export const MathAggregationArray: Array<GeneralDescription<MathAggregationTypes>> = Object.values(MathAggregations);
diff --git a/libs/shared/lib/querybuilder/model/logic/mathFilters.tsx b/libs/shared/lib/querybuilder/model/logic/mathFilters.tsx
index 7c86cdb19..0c235dd78 100644
--- a/libs/shared/lib/querybuilder/model/logic/mathFilters.tsx
+++ b/libs/shared/lib/querybuilder/model/logic/mathFilters.tsx
@@ -5,39 +5,89 @@
  */
 
 import { Position } from 'reactflow';
-import { GeneralDescription, InputNode } from './general';
+import { GeneralDescription, InputNode, MathFilterTypes } from './general';
 
-export enum MathFilterTypes {
-  EQUAL = '==',
-  NOT_EQUAL = '!=',
-  GREATER_THAN = '>',
-  LESS_THAN = '<',
-  GREATER_THAN_EQUAL = '>=',
-  LESS_THAN_EQUAL = '<=',
-}
-
-/** All available functions in the function bar. */
-export const MathFilters: Array<GeneralDescription<MathFilterTypes>> = [
-  {
+export const MathFilters: Record<MathFilterTypes, GeneralDescription<MathFilterTypes>> = {
+  [MathFilterTypes.EQUAL]: {
     key: 'mathFilterEqual',
     name: 'Equal',
     type: MathFilterTypes.EQUAL,
     description: 'Equal to another value',
     numInputs: 2,
     inputs: [
-      { name: '1', type: ['float'], position: Position.Left },
-      { name: '2', type: ['float'], position: Position.Left },
+      { name: '1', type: 'float', default: 0 },
+      { name: '2', type: 'float', default: 0 },
     ],
+    output: { name: '==', type: 'float' },
+    logic: ['==', '@1', '@2'],
   },
-  {
+  [MathFilterTypes.NOT_EQUAL]: {
     key: 'mathFilterNotEqual',
     name: 'Not Equal',
     type: MathFilterTypes.NOT_EQUAL,
     description: 'Not equal to another value',
     numInputs: 2,
     inputs: [
-      { name: '1', type: ['float'], position: Position.Left },
-      { name: '2', type: ['float'], position: Position.Left },
+      { name: '1', type: 'float', default: 0 },
+      { name: '2', type: 'float', default: 0 },
+    ],
+    output: { name: '!=', type: 'float' },
+    logic: ['!=', '@1', '@2'],
+  },
+  [MathFilterTypes.LESS_THAN]: {
+    key: 'mathFilterLessThan',
+    name: 'Less Than',
+    type: MathFilterTypes.LESS_THAN,
+    description: 'Less than another value',
+    numInputs: 2,
+    inputs: [
+      { name: '1', type: 'float', default: 0 },
+      { name: '2', type: 'float', default: 0 },
     ],
+    output: { name: '<', type: 'float' },
+    logic: ['<', '@1', '@2'],
   },
-];
+  [MathFilterTypes.LESS_THAN_EQUAL]: {
+    key: 'mathFilterLessThanOrEqual',
+    name: 'Less Than or Equal',
+    type: MathFilterTypes.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 },
+    ],
+    output: { name: '<=', type: 'float' },
+    logic: ['<=', '@1', '@2'],
+  },
+  [MathFilterTypes.GREATER_THAN]: {
+    key: 'mathFilterGreaterThan',
+    name: 'Greater Than',
+    type: MathFilterTypes.GREATER_THAN,
+    description: 'Greater than another value',
+
+    numInputs: 2,
+    inputs: [
+      { name: '1', type: 'float', default: 0 },
+      { name: '2', type: 'float', default: 0 },
+    ],
+    output: { name: '>', type: 'float' },
+    logic: ['>', '@1', '@2'],
+  },
+  [MathFilterTypes.GREATER_THAN_EQUAL]: {
+    key: 'mathFilterGreaterThanOrEqual',
+    name: 'Greater Than or Equal',
+    type: MathFilterTypes.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 },
+    ],
+    output: { name: '>=', type: 'float' },
+    logic: ['>=', '@1', '@2'],
+  },
+};
+
+/** All available functions in the function bar. */
+export const MathFilterArray: Array<GeneralDescription<MathFilterTypes>> = Object.values(MathFilters);
diff --git a/libs/shared/lib/querybuilder/model/logic/mathFunctions.tsx b/libs/shared/lib/querybuilder/model/logic/mathFunctions.tsx
index 1e0fce8d7..5375664aa 100644
--- a/libs/shared/lib/querybuilder/model/logic/mathFunctions.tsx
+++ b/libs/shared/lib/querybuilder/model/logic/mathFunctions.tsx
@@ -5,47 +5,63 @@
  */
 
 import { Position } from 'reactflow';
-import { GeneralDescription } from './general';
+import { GeneralDescription, MathFunctionTypes } from './general';
 
-export enum MathFunctionTypes {
-  AVG = 'AVG',
-  COUNT = 'COUNT',
-  MAX = 'MAX',
-  MIN = 'MIN',
-  SUM = 'SUM',
-  ROUND = 'ROUND',
-  CEIL = 'CEIL',
-  FLOOR = 'FLOOR',
-  ADD = '+',
-  SUBTRACT = '-',
-  MULTIPLY = '*',
-  DIVIDE = '/',
-  MODULO = '%',
-  CUSTOM = 'CUSTOM',
-}
-
-/** All available functions in the function bar. */
-export const MathFunctions: Array<GeneralDescription<MathFunctionTypes>> = [
-  {
-    key: 'mathFunctionAvg',
-    name: 'Average',
-    type: MathFunctionTypes.AVG,
-    description: 'Average of all values',
-    numInputs: 1,
+export const MathFunctions: Record<MathFunctionTypes, GeneralDescription<MathFunctionTypes>> = {
+  [MathFunctionTypes.ADD]: {
+    key: 'mathFunctionAdd',
+    name: 'Add',
+    type: MathFunctionTypes.ADD,
+    description: 'Add two values',
+    numInputs: 2,
+    inputs: [
+      { name: '1', type: 'float', default: 0 },
+      { name: '2', type: 'float', default: 0 },
+    ],
+    output: { name: '+', type: 'float' },
+    logic: ['+', '@1', '@2'],
+  },
+  [MathFunctionTypes.SUBTRACT]: {
+    key: 'mathFunctionSubtract',
+    name: 'Subtract',
+    type: MathFunctionTypes.SUBTRACT,
+    description: 'Subtract two values',
+    numInputs: 2,
     inputs: [
-      { name: '1', type: ['float'], position: Position.Left },
-      { name: 'out', type: ['float'], position: Position.Right },
+      { name: '1', type: 'float', default: 0 },
+      { name: '2', type: 'float', default: 0 },
     ],
+    output: { name: '-', type: 'float' },
+    logic: ['-', '@1', '@2'],
   },
-  {
-    key: 'mathFunctionCount',
-    name: 'Count',
-    type: MathFunctionTypes.COUNT,
-    description: 'Count the number of values',
-    numInputs: 1,
+  [MathFunctionTypes.MULTIPLY]: {
+    key: 'mathFunctionMultiply',
+    name: 'Multiply',
+    type: MathFunctionTypes.MULTIPLY,
+    description: 'Multiply two values',
+    numInputs: 2,
     inputs: [
-      { name: '1', type: ['float'], position: Position.Left },
-      { name: 'out', type: ['float'], position: Position.Right },
+      { name: '1', type: 'float', default: 0 },
+      { name: '2', type: 'float', default: 0 },
     ],
+    output: { name: '*', type: 'float' },
+    logic: ['*', '@1', '@2'],
   },
-];
+  [MathFunctionTypes.DIVIDE]: {
+    key: 'mathFunctionDivide',
+    name: 'Divide',
+    type: MathFunctionTypes.DIVIDE,
+    description: 'Divide two values',
+
+    numInputs: 2,
+    inputs: [
+      { name: '1', type: 'float', default: 0 },
+      { name: '2', type: 'float', default: 0 },
+    ],
+    output: { name: '/', type: 'float' },
+    logic: ['/', '@1', '@2'],
+  },
+};
+
+/** All available functions in the function bar. */
+export const MathFunctionArray: Array<GeneralDescription<MathFunctionTypes>> = Object.values(MathFunctions);
diff --git a/libs/shared/lib/querybuilder/model/logic/stringFilters.tsx b/libs/shared/lib/querybuilder/model/logic/stringFilters.tsx
index db8b52b3c..5799d1804 100644
--- a/libs/shared/lib/querybuilder/model/logic/stringFilters.tsx
+++ b/libs/shared/lib/querybuilder/model/logic/stringFilters.tsx
@@ -5,28 +5,62 @@
  */
 
 import { Position } from 'reactflow';
-import { GeneralDescription } from './general';
+import { GeneralDescription, StringFilterTypes } from './general';
 
-export enum StringFilterTypes {
-  EQUAL = '==',
-  NOT_EQUAL = '!=',
-  LIKE = 'LIKE',
-  NOT_LIKE = 'NOT LIKE',
-  IN = 'IN',
-  NOT_IN = 'NOT IN',
-}
-
-/** All available functions in the function bar. */
-export const StringFilters: Array<GeneralDescription<StringFilterTypes>> = [
-  {
+export const StringFilters: Record<StringFilterTypes, GeneralDescription<StringFilterTypes>> = {
+  [StringFilterTypes.EQUAL]: {
     key: 'stringFilterEqual',
     name: 'Equal',
     type: StringFilterTypes.EQUAL,
     description: 'Equal to another value',
     numInputs: 1,
     inputs: [
-      { name: '1', type: ['string'], position: Position.Left },
-      { name: '2', type: ['string'], position: Position.Left },
+      { name: '1', type: 'string', default: '' },
+      { name: '2', type: 'string', default: '' },
+    ],
+    output: { name: StringFilterTypes.EQUAL, type: 'bool' },
+    logic: [StringFilterTypes.EQUAL, '@1', '@2'],
+  },
+  [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: '' },
     ],
+    output: { name: StringFilterTypes.NOT_EQUAL, type: 'bool' },
+    logic: [StringFilterTypes.NOT_EQUAL, '@1', '@2'],
   },
-];
+  // [StringFilterTypes.IN]: {
+  //   key: 'stringFilterContains',
+  //   name: 'Contains',
+  //   type: StringFilterTypes.IN,
+  //   description: 'Contains another value',
+  //   numInputs: 1,
+  //   inputs: [
+  //     { name: '1', type: 'string', default: '' },
+  //     { name: '2', type: 'string', default: '' },
+  //   ],
+  //   output: { name: StringFilterTypes.IN, type: 'bool' },
+  //   logic: [StringFilterTypes.IN, '@1', '@2'],
+  // },
+  [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: '' },
+    ],
+    output: { name: StringFilterTypes.LIKE, type: 'bool' },
+    logic: [StringFilterTypes.LIKE, '@1', '@2'],
+  },
+};
+
+/** All available functions in the function bar. */
+export const StringFilterArray: Array<GeneralDescription<StringFilterTypes>> = Object.values(StringFilters);
diff --git a/libs/shared/lib/querybuilder/model/logic/stringFunctions.tsx b/libs/shared/lib/querybuilder/model/logic/stringFunctions.tsx
index df031c477..3ba9a3d1a 100644
--- a/libs/shared/lib/querybuilder/model/logic/stringFunctions.tsx
+++ b/libs/shared/lib/querybuilder/model/logic/stringFunctions.tsx
@@ -5,27 +5,40 @@
  */
 
 import { Position } from 'reactflow';
-import { GeneralDescription } from './general';
+import { GeneralDescription, StringFunctionTypes } from './general';
 
-export enum StringFunctionTypes {
-  CONCAT = 'CONCAT',
-  LOWER = 'LOWER',
-  UPPER = 'UPPER',
-  SUBSTRING = 'SUBSTRING',
-  TRIM = 'TRIM',
-}
-
-/** All available functions in the function bar. */
-export const StringFunctions: Array<GeneralDescription<StringFunctionTypes>> = [
-  {
-    key: 'stringFunctionConcat',
-    name: 'Lower',
+export const StringFunctions: Record<StringFunctionTypes, GeneralDescription<StringFunctionTypes>> = {
+  // [StringFunctionTypes.CONCAT]: {
+  //   key: 'stringFunctionConcat',
+  //   name: 'Concat',
+  //   type: StringFunctionTypes.CONCAT,
+  //   description: 'Lowercase all characters',
+  //   numInputs: 1,
+  //   inputs: [{ name: '1', type: 'string', default: '' }],
+  //   output: { name: 'lower_case', type: 'string' },
+  //   logic: ['Lower', '@1'],
+  // },
+  [StringFunctionTypes.LOWER]: {
+    key: 'stringFunctionLowerCase',
+    name: 'Lower Case',
     type: StringFunctionTypes.LOWER,
     description: 'Lowercase all characters',
     numInputs: 1,
-    inputs: [
-      { name: '1', type: ['string'], position: Position.Left },
-      { name: 'lower_case', type: ['string'], position: Position.Right },
-    ],
+    inputs: [{ name: '1', type: 'string', default: '' }],
+    output: { name: 'lower_case', type: 'string' },
+    logic: ['Lower', '@1'],
+  },
+  [StringFunctionTypes.UPPER]: {
+    key: 'stringFunctionUpperCase',
+    name: 'Upper Case',
+    type: StringFunctionTypes.UPPER,
+    description: 'Uppercase all characters',
+    numInputs: 1,
+    inputs: [{ name: '1', type: 'string', default: '' }],
+    output: { name: 'upper_case', type: 'string' },
+    logic: ['Upper', '@1'],
   },
-];
+};
+
+/** All available functions in the function bar. */
+export const StringFunctionArray: Array<GeneralDescription<StringFunctionTypes>> = Object.values(StringFunctions);
diff --git a/libs/shared/lib/querybuilder/model/reactflow/handles.tsx b/libs/shared/lib/querybuilder/model/reactflow/handles.tsx
index c0b7fdca5..8c913b2c3 100644
--- a/libs/shared/lib/querybuilder/model/reactflow/handles.tsx
+++ b/libs/shared/lib/querybuilder/model/reactflow/handles.tsx
@@ -18,6 +18,7 @@ export enum Handles {
   ToAttribute = 'attributesHandle', //target
   EntityLeft = 'entityLeftHandle', //source
   EntityRight = 'entityRightHandle', //target
+  EntityAttribute = 'entityAttributeHandle', //source
   OnAttribute = 'onAttributeHandle', //source
   ReceiveFunction = 'receiveFunctionHandle', //target
   FunctionBase = 'functionHandle_', // + name from FunctionTypes args //source
diff --git a/libs/shared/lib/querybuilder/model/reactflow/utils.ts b/libs/shared/lib/querybuilder/model/reactflow/utils.ts
index 984eb3ea1..b8a0dd791 100644
--- a/libs/shared/lib/querybuilder/model/reactflow/utils.ts
+++ b/libs/shared/lib/querybuilder/model/reactflow/utils.ts
@@ -1,5 +1,6 @@
 import Graph from 'graphology';
 import { Node, Edge } from 'reactflow';
+import { toHandleId } from '..';
 
 // Takes the querybuilder graph as an input and creates react flow elements for them.
 export function createReactFlowElements<T extends Graph>(
@@ -12,6 +13,8 @@ export function createReactFlowElements<T extends Graph>(
   const edges: Array<Edge> = [];
 
   graph.forEachNode((node, attributes): void => {
+    // console.log('attributes', attributes);
+
     let position = { x: attributes?.x || 0, y: attributes?.y || 0 };
     const RFNode: Node<typeof attributes> = {
       id: node,
@@ -32,13 +35,15 @@ export function createReactFlowElements<T extends Graph>(
       source: source,
       target: target,
       type: 'connection',
-      sourceHandle: attributes.sourceHandle,
-      targetHandle: attributes.targetHandle,
+      sourceHandle: toHandleId(attributes.sourceHandleData),
+      targetHandle: toHandleId(attributes.targetHandleData),
       data: attributes,
       zIndex: 1,
     };
     edges.push(RFEdge);
   });
 
+  // console.log('nodes', nodes, 'edges', edges);
+
   return { nodes, edges };
 }
diff --git a/libs/shared/lib/querybuilder/panel/querybuilder.tsx b/libs/shared/lib/querybuilder/panel/querybuilder.tsx
index 93f4bb8de..73bc730e7 100644
--- a/libs/shared/lib/querybuilder/panel/querybuilder.tsx
+++ b/libs/shared/lib/querybuilder/panel/querybuilder.tsx
@@ -29,6 +29,7 @@ import ReactFlow, {
   NodePositionChange,
 } from 'reactflow';
 import styles from './querybuilder.module.scss';
+import CachedIcon from '@mui/icons-material/Cached';
 
 import React, { ReactComponentElement, useMemo, useRef, useEffect, useCallback, useState, DragEventHandler } from 'react';
 import { AttributePill, ConnectionDragLine, ConnectionLine, EntityFlowElement, RelationPill } from '../pills';
@@ -38,6 +39,7 @@ import { clearQB } from '@graphpolaris/shared/lib/data-access/store/querybuilder
 import { RelationPosToFromEntityPos, RelationPosToToEntityPos } from '@graphpolaris/shared/lib/querybuilder/model/graphology/utils';
 import { useDispatch } from 'react-redux';
 import {
+  Box,
   Card,
   CardContent,
   Dialog,
@@ -49,26 +51,31 @@ import {
   ListItemText,
   Paper,
   PaperProps,
+  Tab,
+  Tabs,
   Typography,
 } from '@mui/material';
-import { Handles, NodeAttribute, QueryElementTypes, QueryGraphNodes, createReactFlowElements, fromHandleId, getHandleId } from '../model';
-import Draggable from 'react-draggable';
-import { GeneralDescription, InputNodeType } from '../model/logic/general';
 import {
-  MathFilterTypes,
+  AllLogicDescriptions,
+  AllLogicMap,
+  Handles,
   MathFilters,
-  MathFunctionTypes,
-  StringFilterTypes,
+  StringFilters,
   MathFunctions,
+  NodeAttribute,
+  QueryElementTypes,
+  QueryGraphNodes,
   StringFunctions,
-  StringFilters,
-  StringFunctionTypes,
-  AllLogicDescriptions,
-  AllLogicMap,
-} from '../model/logic';
+  createReactFlowElements,
+  toHandleData,
+  QueryGraphEdgeHandle,
+} from '../model';
+import Draggable from 'react-draggable';
+import { GeneralDescription, InputNodeType } from '../model/logic/general';
 import LogicPill from '../pills/customFlowPills/logicpill/logicpill';
 import { current } from '@reduxjs/toolkit';
 import { SchemaAttributeTypes } from '../../schema';
+import { MathAggregations } from '../model/logic/mathAggregations';
 
 const nodeTypes = {
   entity: EntityFlowElement,
@@ -84,7 +91,7 @@ const edgeTypes = {
 /**
  * This is the main querybuilder component. It is responsible for holding all pills and fire off the visual part of the querybuilder panel logic
  */
-export const QueryBuilderInner: React.FC = () => {
+export const QueryBuilderInner = (props: QueryBuilderProps) => {
   const [openPopup, setOpenPopup] = useState(false);
   const reactFlowWrapper = useRef<HTMLDivElement>(null);
 
@@ -229,42 +236,44 @@ export const QueryBuilderInner: React.FC = () => {
 
     switch (dragData.type) {
       case QueryElementTypes.Entity:
-        graphologyGraph.addPill2Graphology({
-          type: QueryElementTypes.Entity,
-          x: position.x,
-          y: position.y,
-          name: dragData.name,
-        });
+        // console.log('entity drop', dragData, schema.getNodeAttribute(dragData.name, 'attributes'));
+        graphologyGraph.addPill2Graphology(
+          {
+            type: QueryElementTypes.Entity,
+            x: position.x,
+            y: position.y,
+            name: dragData.name,
+          },
+          schema.getNodeAttribute(dragData.name, 'attributes')
+        );
         dispatch(setQuerybuilderNodes(graphologyGraph.export()));
         break;
       // Creates a relation element and will also create the 2 related entities together with the connections
       case QueryElementTypes.Relation:
-        const relation = graphologyGraph.addPill2Graphology({
-          type: QueryElementTypes.Relation,
-          x: position.x,
-          y: position.y,
-          depth: { min: 0, max: 1 },
-          // name: dragData.name,
-          name: dragData.collection,
-          collection: dragData.collection,
-        });
-        const leftEntity = graphologyGraph.addPill2Graphology({
-          type: QueryElementTypes.Entity,
-          ...RelationPosToFromEntityPos(position),
-          name: dragData.from,
-        });
-        const rightEntity = graphologyGraph.addPill2Graphology({
-          type: QueryElementTypes.Entity,
-          ...RelationPosToToEntityPos(position),
-          name: dragData.to,
-        });
-
-        graphologyGraph.addEdge2Graphology(leftEntity, relation, {
-          type: 'connection',
-        });
-        graphologyGraph.addEdge2Graphology(relation, rightEntity, {
-          type: 'connection',
-        });
+        console.log('relation drop', dragData, schema.export());
+
+        const relation = graphologyGraph.addPill2Graphology(
+          {
+            type: QueryElementTypes.Relation,
+            x: position.x,
+            y: position.y,
+            depth: { min: 0, max: 1 },
+            name: dragData.collection,
+            collection: dragData.collection,
+          },
+          schema.getEdgeAttribute(dragData.label, 'attributes')
+        );
+        // const leftEntity = graphologyGraph.addPill2Graphology(
+        //   { type: QueryElementTypes.Entity, ...RelationPosToFromEntityPos(position), name: dragData.from },
+        //   schema.getNodeAttribute(dragData.from, 'attributes')
+        // );
+        // const rightEntity = graphologyGraph.addPill2Graphology(
+        //   { type: QueryElementTypes.Entity, ...RelationPosToToEntityPos(position), name: dragData.to },
+        //   schema.getNodeAttribute(dragData.to, 'attributes')
+        // );
+
+        // graphologyGraph.addEdge2Graphology(leftEntity, relation);
+        // graphologyGraph.addEdge2Graphology(relation, rightEntity);
 
         if (config.autoSendQueries) {
           // sendQuery();
@@ -281,6 +290,21 @@ export const QueryBuilderInner: React.FC = () => {
       //     dragData.datatype
       //   );
       //   break;
+      default:
+        const logic = AllLogicMap[dragData.value.key];
+        const firstLeftLogicInput = logic.inputs?.[0];
+        if (!firstLeftLogicInput) return;
+
+        // logicAttributes[0].handles = [connectingNodeId.current.handleId];
+        const logicNode = graphologyGraph.addLogicPill2Graphology({
+          name: dragData.value.name,
+          type: QueryElementTypes.Logic,
+          x: position.x,
+          y: position.y,
+          logic: logic,
+        });
+
+        dispatch(setQuerybuilderNodes(graphologyGraph.export()));
     }
   };
 
@@ -297,12 +321,17 @@ export const QueryBuilderInner: React.FC = () => {
     (connection: Connection) => {
       if (!isEdgeUpdating.current) {
         isOnConnect.current = true;
-        graphologyGraph.addEdge(connection.source, connection.target, {
-          type: 'connection',
-          sourceHandle: connection.sourceHandle,
-          targetHandle: connection.targetHandle,
-        });
-        dispatch(setQuerybuilderNodes(graphologyGraph.export()));
+        if (!connection.sourceHandle || !connection.targetHandle) throw new Error('Connection has no source or target');
+        console.log('onConnect', connection);
+
+        if (!graphologyGraph.hasEdge(connection.source, connection.target)) {
+          graphologyGraph.addEdge(connection.source, connection.target, {
+            type: 'connection',
+            sourceHandleData: toHandleData(connection.sourceHandle),
+            targetHandleData: toHandleData(connection.targetHandle),
+          });
+          dispatch(setQuerybuilderNodes(graphologyGraph.export()));
+        }
       }
     },
     [graph]
@@ -310,17 +339,18 @@ export const QueryBuilderInner: React.FC = () => {
 
   const onConnectStart = useCallback(
     (event: React.MouseEvent | React.TouchEvent, params: OnConnectStartParams) => {
+      // console.log('onConnectStart', params);
       if (!params?.handleId) return;
 
       let node = graphologyGraph.getNodeAttributes(params.nodeId);
-      const { attributeName, attributeType } = fromHandleId(params.handleId);
+      const handleData = toHandleData(params.handleId);
       // console.log(attributeName, attributeType, node.attributes?.filter((a) => a.name === attributeName)?.[0]);
 
       connectingNodeId.current = {
         params,
         node,
         position: { x: 0, y: 0 },
-        attribute: { name: attributeName, type: attributeType as SchemaAttributeTypes, handleId: params.handleId },
+        attribute: { handleData: handleData },
       };
     },
     [graph]
@@ -362,27 +392,32 @@ export const QueryBuilderInner: React.FC = () => {
     const params = connectingNodeId.current.params;
     const position = connectingNodeId.current.position;
 
+    // console.log('onNewNodeFromPopup', value, type, params, position);
+
     const logic = AllLogicMap[value.key];
-    const firstLeftLogicInput = logic.inputs.filter((input) => input.position === Position.Left)?.[0];
+    const firstLeftLogicInput = logic.inputs?.[0];
     if (!firstLeftLogicInput) return;
 
     // logicAttributes[0].handles = [connectingNodeId.current.handleId];
-    const logicNode = graphologyGraph.addPill2Graphology({
+    const logicNode = graphologyGraph.addLogicPill2Graphology({
       name: value.name,
       type: QueryElementTypes.Logic,
       x: position.x,
       y: position.y,
       logic: logic,
     });
+
     if (!logicNode?.id) throw new Error('Logic node has no id');
+    if (!logicNode?.name) throw new Error('Logic node has no name');
+    if (!params.handleId) throw new Error('Connection has no source or target');
 
-    graphologyGraph.addEdge(params.nodeId, logicNode.id, {
-      type: 'connection',
-      sourceHandle: params.handleId, // newAttribute data?
-      targetHandle: getHandleId(logicNode.id, logicNode.name, logicNode.type, firstLeftLogicInput.name, firstLeftLogicInput.type.join(''), {
-        extra: 'left',
-      }),
-    });
+    const sourceHandleData = toHandleData(params.handleId);
+    graphologyGraph.addEdge2Graphology(
+      graphologyGraph.getNodeAttributes(params.nodeId),
+      graphologyGraph.getNodeAttributes(logicNode.id),
+      { type: 'connection' },
+      { sourceHandleName: sourceHandleData.attributeName, targetHandleName: firstLeftLogicInput.name }
+    );
 
     dispatch(setQuerybuilderNodes(graphologyGraph.export()));
     setOpenPopup(false);
@@ -401,10 +436,11 @@ export const QueryBuilderInner: React.FC = () => {
           graphologyGraph.dropEdge(oldEdge.id);
         }
 
+        if (!newConnection.sourceHandle || !newConnection.targetHandle) throw new Error('Connection has no source or target');
         graphologyGraph.addEdge(newConnection.source, newConnection.target, {
           type: 'connection',
-          sourceHandle: newConnection.sourceHandle,
-          targetHandle: newConnection.targetHandle,
+          sourceHandleData: toHandleData(newConnection.sourceHandle),
+          targetHandleData: toHandleData(newConnection.targetHandle),
         });
         dispatch(setQuerybuilderNodes(graphologyGraph.export()));
       }
@@ -430,11 +466,18 @@ export const QueryBuilderInner: React.FC = () => {
     [graph]
   );
 
+  const onNodeContextMenu = (event: React.MouseEvent, node: Node) => {
+    event.preventDefault();
+    // console.log('context menu', node);
+    graphologyGraph.dropNode(node.id);
+    dispatch(setQuerybuilderNodes(graphologyGraph.export()));
+  };
+
   return (
     <div ref={reactFlowWrapper} className={styles.full}>
       <PopupMenu
         open={openPopup}
-        type={connectingNodeId.current?.attribute?.type || 'int'}
+        type={connectingNodeId.current?.attribute.handleData.attributeType || 'int'}
         onClose={() => {
           setOpenPopup(false);
         }}
@@ -463,6 +506,8 @@ export const QueryBuilderInner: React.FC = () => {
         onEdgeUpdateStart={onEdgeUpdateStart}
         onEdgeUpdateEnd={onEdgeUpdateEnd}
         onDrop={onDrop}
+        // onContextMenu={onContextMenu}
+        onNodeContextMenu={onNodeContextMenu}
         // onNodesDelete={onNodesDelete}
         // onNodesChange={onNodesChange}
         deleteKeyCode="Backspace"
@@ -510,6 +555,16 @@ export const QueryBuilderInner: React.FC = () => {
           >
             <SettingsIcon />
           </ControlButton>
+          <ControlButton
+            className={styles.buttons}
+            title={'Run Query'}
+            onClick={(event) => {
+              event.stopPropagation();
+              if (props.onRunQuery) props.onRunQuery();
+            }}
+          >
+            <CachedIcon />
+          </ControlButton>
         </Controls>
       </ReactFlow>
     </div>
@@ -529,12 +584,45 @@ function PaperComponent(props: PaperProps) {
   );
 }
 
+interface TabPanelProps {
+  children?: React.ReactNode;
+  index: number;
+  value: number;
+}
+
+function CustomTabPanel(props: TabPanelProps) {
+  const { children, value, index, ...other } = props;
+
+  return (
+    <div
+      role="tabpanel"
+      hidden={value !== index}
+      id={`simple-tabpanel-${index}`}
+      aria-labelledby={`simple-tab-${index}`}
+      {...other}
+      style={{ maxHeight: '85%', overflow: 'auto' }}
+    >
+      {value === index && (
+        <Box>
+          <Typography>{children}</Typography>
+        </Box>
+      )}
+    </div>
+  );
+}
+
 function PopupMenu(props: {
   open: boolean;
   type: InputNodeType;
   onClose: () => void;
   onClick: (value: AllLogicDescriptions, type: InputNodeType) => void;
 }) {
+  const [value, setValue] = React.useState(props.type === 'string' ? 1 : 0);
+
+  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
+    setValue(newValue);
+  };
+
   const handleClose = () => {
     props.onClose();
   };
@@ -543,60 +631,100 @@ function PopupMenu(props: {
     props.onClick(value, type);
   };
 
-  const generateList = (list: AllLogicDescriptions[], type: InputNodeType) => (
-    <Grid container sx={{ pt: 0 }}>
-      {list.map((f, i) => (
-        <Grid item key={JSON.stringify(f) + type + i}>
-          <ListItemButton onClick={() => handleListItemClick(f, type)} key={f + type}>
-            {/* <ListItemAvatar>
+  const generateList = (list: Record<string, AllLogicDescriptions>, type: InputNodeType) => (
+    <List sx={{ pt: 0 }}>
+      {Object.keys(list).map((f, i) => (
+        <ListItemButton onClick={() => handleListItemClick(list[f], type)} key={f + type}>
+          {/* <ListItemAvatar>
                 <Avatar sx={{ bgcolor: blue[100], color: blue[600] }}>
                   <PersonIcon />
                 </Avatar>
               </ListItemAvatar> */}
-            <ListItemText primary={f.name} secondary={f.description} />
-          </ListItemButton>
-        </Grid>
+          <ListItemText primary={list[f].name} secondary={list[f].description} />
+        </ListItemButton>
       ))}
-    </Grid>
+    </List>
   );
+
+  function a11yProps(index: number) {
+    return {
+      id: `simple-tab-${index}`,
+      'aria-controls': `simple-tabpanel-${index}`,
+    };
+  }
+
   return (
     <Dialog onClose={handleClose} open={props.open} PaperComponent={PaperComponent}>
       <DialogTitle>Add New Node</DialogTitle>
-      <DialogTitle>Collection Nodes</DialogTitle>
-      {props.type === 'float' && generateList(MathFunctions, props.type)}
-      {props.type === 'int' && generateList(MathFunctions, props.type)}
-      {props.type === 'string' && generateList(StringFunctions, props.type)}
-      <DialogTitle>Filter Nodes</DialogTitle>
-      {props.type === 'float' && generateList(MathFilters, props.type)}
-      {props.type === 'int' && generateList(MathFilters, props.type)}
-      {props.type === 'string' && generateList(StringFilters, props.type)}
+      <Box>
+        <Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
+          <Tabs value={value} onChange={handleChange} aria-label="basic tabs example">
+            <Tab label="Aggregations" disabled={props.type === 'string'} {...a11yProps(0)} />
+            <Tab label="Operations" {...a11yProps(1)} />
+            <Tab label="Filters" {...a11yProps(2)} />
+          </Tabs>
+        </Box>
+        <CustomTabPanel value={value} index={0}>
+          {props.type === 'float' && generateList(MathAggregations, props.type)}
+          {props.type === 'int' && generateList(MathAggregations, props.type)}
+          {/* {props.type === 'string' && generateList(MathAggregations, props.type)} */}
+        </CustomTabPanel>
+        <CustomTabPanel value={value} index={1}>
+          {props.type === 'float' && generateList(MathFunctions, props.type)}
+          {props.type === 'int' && generateList(MathFunctions, props.type)}
+          {props.type === 'string' && generateList(StringFunctions, props.type)}
+        </CustomTabPanel>
+        <CustomTabPanel value={value} index={2}>
+          {props.type === 'float' && generateList(MathFilters, props.type)}
+          {props.type === 'int' && generateList(MathFilters, props.type)}
+          {props.type === 'string' && generateList(StringFilters, props.type)}
+        </CustomTabPanel>
+      </Box>
     </Dialog>
   );
 }
 
-export const QueryBuilderPills = (props: { onClose: () => void; onClick: (value: AllLogicDescriptions, type: InputNodeType) => void }) => {
-  const onDragStart = (event: React.DragEvent, nodeType: InputNodeType) => {
+export const QueryBuilderPills = () => {
+  const onDragStart = (event: React.DragEvent, value: AllLogicDescriptions, nodeType: InputNodeType) => {
     console.log('drag start', nodeType);
 
-    event.dataTransfer.setData('application/reactflow', nodeType);
+    event.dataTransfer.setData('application/reactflow', JSON.stringify({ value, nodeType }));
     event.dataTransfer.effectAllowed = 'move';
   };
 
-  const generateList = (list: AllLogicDescriptions[], type: InputNodeType) => (
-    <List>
-      {list.map((f, i) => (
-        <ListItem key={JSON.stringify(f) + type + i}>
-          {/* <ListItemAvatar>
-                <Avatar sx={{ bgcolor: blue[100], color: blue[600] }}>
-                  <PersonIcon />
-                </Avatar>
-              </ListItemAvatar> */}
+  const [value, setValue] = React.useState(0);
 
+  const handleChange = (event: React.SyntheticEvent, newValue: number) => {
+    setValue(newValue);
+  };
+
+  function a11yProps(index: number) {
+    return {
+      id: `simple-tab-${index}`,
+      'aria-controls': `simple-tabpanel-${index}`,
+    };
+  }
+
+  const handleListItemClick = (value: AllLogicDescriptions, type: InputNodeType) => {};
+
+  // const generateList = (list: Record<string, AllLogicDescriptions>, type: InputNodeType) => (
+  //   <List sx={{ pt: 0 }}>
+  //     {Object.keys(list).map((f, i) => (
+  //       <ListItemButton onClick={() => handleListItemClick(list[f], type)} key={f + type}>
+  //         <ListItemText primary={list[f].name} secondary={list[f].description} />
+  //       </ListItemButton>
+  //     ))}
+  //   </List>
+  // );
+  const generateList = (list: Record<string, AllLogicDescriptions>, type: InputNodeType) => (
+    <List>
+      {Object.keys(list).map((f, i) => (
+        <ListItem key={JSON.stringify(list[f]) + type + i}>
           <ListItemText
             draggable
-            primary={f.name}
-            secondary={f.description}
-            onDragStart={(event) => onDragStart(event, type)}
+            primary={list[f].name}
+            secondary={list[f].description}
+            onDragStart={(event) => onDragStart(event, list[f], type)}
             key={f + type}
           />
         </ListItem>
@@ -605,25 +733,46 @@ export const QueryBuilderPills = (props: { onClose: () => void; onClick: (value:
   );
 
   return (
-    <aside>
-      <div>{generateList(MathFunctions, 'string')}</div>
+    <aside className="">
+      <Box>
+        <Tabs value={value} onChange={handleChange} aria-label="basic tabs example">
+          <Tab label="Aggregations" {...a11yProps(0)} />
+          <Tab label="Operations" {...a11yProps(1)} />
+          <Tab label="Filters" {...a11yProps(2)} />
+        </Tabs>
+      </Box>
+      <CustomTabPanel value={value} index={0}>
+        {generateList(MathAggregations, 'float')}
+      </CustomTabPanel>
+      <CustomTabPanel value={value} index={1}>
+        {generateList(MathFunctions, 'float')}
+        {generateList(StringFunctions, 'string')}
+      </CustomTabPanel>
+      <CustomTabPanel value={value} index={2}>
+        {generateList(MathFilters, 'float')}
+        {generateList(StringFilters, 'string')}
+      </CustomTabPanel>
     </aside>
   );
 };
 
-export const QueryBuilder = () => {
+export type QueryBuilderProps = {
+  onRunQuery?: () => void;
+};
+
+export const QueryBuilder = (props: QueryBuilderProps) => {
   return (
     <div
       style={{
         width: '100%',
-        height: '100%',
+        height: '22rem',
         display: 'flex',
         gap: '1rem',
       }}
     >
-      {/* <QueryBuilderPills /> */}
+      <QueryBuilderPills />
       <ReactFlowProvider>
-        <QueryBuilderInner />
+        <QueryBuilderInner {...props} />
       </ReactFlowProvider>
     </div>
   );
diff --git a/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple-disconnected.stories.tsx b/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple-disconnected.stories.tsx
index a71f52347..6d8c661f3 100644
--- a/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple-disconnected.stories.tsx
+++ b/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple-disconnected.stories.tsx
@@ -11,7 +11,7 @@ import { configureStore } from '@reduxjs/toolkit';
 import { Meta } from '@storybook/react';
 import { Provider } from 'react-redux';
 import QueryBuilderInner from '../querybuilder';
-import { Handles, NodeAttribute, QueryElementTypes, QueryMultiGraphology, getHandleId } from '../../model';
+import { Handles, NodeAttribute, QueryElementTypes, QueryMultiGraphology, toHandleId } from '../../model';
 import { SchemaUtils } from '../../../schema/schema-utils';
 import { ReactFlowProvider } from 'reactflow';
 
@@ -53,27 +53,31 @@ export const SimpleDisconnected = {
 
       store.dispatch(setSchema(schema.export()));
 
-      const entity1 = graph.addPill2Graphology({
-        id: '0',
-        type: 'entity',
-        x: 100,
-        y: 100,
-        name: 'Airport 1',
-        attributes: schema.getNodeAttribute('entity', 'attributes') as NodeAttribute[],
-      });
-      const entity2 = graph.addPill2Graphology({
-        id: '10',
-        type: 'entity',
-        x: 200,
-        y: 200,
-        name: 'Airport 2',
-        attributes: schema.getNodeAttribute('entity', 'attributes') as NodeAttribute[],
-      });
+      const entity1 = graph.addPill2Graphology(
+        {
+          id: '0',
+          type: QueryElementTypes.Entity,
+          x: 100,
+          y: 100,
+          name: 'Airport 1',
+        },
+        schema.getNodeAttribute('entity', 'attributes')
+      );
+      const entity2 = graph.addPill2Graphology(
+        {
+          id: '10',
+          type: QueryElementTypes.Entity,
+          x: 200,
+          y: 200,
+          name: 'Airport 2',
+        },
+        schema.getNodeAttribute('entity', 'attributes')
+      );
 
-      // graph.addNode('0', { type: 'entity', x: 100, y: 100, name: 'Entity Pill' });
+      // graph.addNode('0', { type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Entity Pill' });
       const relation1 = graph.addPill2Graphology({
         id: '1',
-        type: 'relation',
+        type: QueryElementTypes.Relation,
         x: 140,
         y: 140,
         name: 'Flight between airports',
diff --git a/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple.stories.tsx b/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple.stories.tsx
index 955a41623..bd57e342d 100644
--- a/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple.stories.tsx
+++ b/libs/shared/lib/querybuilder/panel/stories/querybuilder-simple.stories.tsx
@@ -11,7 +11,7 @@ import { configureStore } from '@reduxjs/toolkit';
 import { Meta } from '@storybook/react';
 import { Provider } from 'react-redux';
 import QueryBuilder from '../querybuilder';
-import { Handles, NodeAttribute, QueryElementTypes, QueryMultiGraphology, getHandleId } from '../../model';
+import { Handles, NodeAttribute, QueryElementTypes, QueryMultiGraphology, toHandleId } from '../../model';
 import { SchemaUtils } from '../../../schema/schema-utils';
 
 const Component: Meta<typeof QueryBuilder> = {
@@ -67,27 +67,31 @@ export const Simple = {
 
     store.dispatch(setSchema(schema.export()));
 
-    const entity1 = graph.addPill2Graphology({
-      id: '0',
-      type: 'entity',
-      x: 100,
-      y: 100,
-      name: 'Airport 1',
-      attributes: schema.getNodeAttribute('entity', 'attributes') as NodeAttribute[],
-    });
-    const entity2 = graph.addPill2Graphology({
-      id: '10',
-      type: 'entity',
-      x: 200,
-      y: 200,
-      name: 'Airport 2',
-      attributes: schema.getNodeAttribute('entity', 'attributes') as NodeAttribute[],
-    });
+    const entity1 = graph.addPill2Graphology(
+      {
+        id: '0',
+        type: QueryElementTypes.Entity,
+        x: 100,
+        y: 100,
+        name: 'Airport 1',
+      },
+      schema.getNodeAttribute('entity', 'attributes')
+    );
+    const entity2 = graph.addPill2Graphology(
+      {
+        id: '10',
+        type: QueryElementTypes.Entity,
+        x: 200,
+        y: 200,
+        name: 'Airport 2',
+      },
+      schema.getNodeAttribute('entity', 'attributes')
+    );
 
-    // graph.addNode('0', { type: 'entity', x: 100, y: 100, name: 'Entity Pill' });
+    // graph.addNode('0', { type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Entity Pill' });
     const relation1 = graph.addPill2Graphology({
       id: '1',
-      type: 'relation',
+      type: QueryElementTypes.Relation,
       x: 140,
       y: 140,
       name: 'Flight between airports',
@@ -136,12 +140,8 @@ export const Simple = {
     //   },
     //   graph
     // );
-    graph.addEdge2Graphology(entity1, relation1, {
-      type: 'connection',
-    });
-    graph.addEdge2Graphology(relation1, entity2, {
-      type: 'connection',
-    });
+    graph.addEdge2Graphology(entity1, relation1);
+    graph.addEdge2Graphology(relation1, entity2);
     // console.log(graph.getNodeAttributes('2'));
     // graph.addEdge('2', '1', { type: 'attribute_connection' });
     // graph.addEdge('3', '1', { type: 'attribute_connection' });
diff --git a/libs/shared/lib/querybuilder/panel/stories/querybuilder-single-entity.stories.tsx b/libs/shared/lib/querybuilder/panel/stories/querybuilder-single-entity.stories.tsx
index a5ca1fc7f..c147c023d 100644
--- a/libs/shared/lib/querybuilder/panel/stories/querybuilder-single-entity.stories.tsx
+++ b/libs/shared/lib/querybuilder/panel/stories/querybuilder-single-entity.stories.tsx
@@ -11,7 +11,7 @@ import { configureStore } from '@reduxjs/toolkit';
 import { Meta } from '@storybook/react';
 import { Provider } from 'react-redux';
 import QueryBuilder from '../querybuilder';
-import { QueryElementTypes, QueryMultiGraphology, getHandleId } from '../../model';
+import { QueryElementTypes, QueryMultiGraphology, toHandleId } from '../../model';
 
 const Component: Meta<typeof QueryBuilder> = {
   component: QueryBuilder,
@@ -39,7 +39,7 @@ export const SingleEntity = {
   play: async () => {
     const graph = new QueryMultiGraphology();
     graph.addPill2Graphology({
-      type: 'entity',
+      type: QueryElementTypes.Entity,
       x: 100,
       y: 100,
       name: 'Entity Pill',
diff --git a/libs/shared/lib/querybuilder/panel/stories/querybuilder-single-relationship.stories.tsx b/libs/shared/lib/querybuilder/panel/stories/querybuilder-single-relationship.stories.tsx
index 017a42f10..db2d6634f 100644
--- a/libs/shared/lib/querybuilder/panel/stories/querybuilder-single-relationship.stories.tsx
+++ b/libs/shared/lib/querybuilder/panel/stories/querybuilder-single-relationship.stories.tsx
@@ -11,7 +11,7 @@ import { configureStore } from '@reduxjs/toolkit';
 import { Meta } from '@storybook/react';
 import { Provider } from 'react-redux';
 import QueryBuilder from '../querybuilder';
-import { QueryElementTypes, QueryMultiGraphology, getHandleId } from '../../model';
+import { QueryElementTypes, QueryMultiGraphology, toHandleId } from '../../model';
 
 const Component: Meta<typeof QueryBuilder> = {
   component: QueryBuilder,
@@ -39,7 +39,7 @@ export const SingleRelationship = {
   play: async () => {
     const graph = new QueryMultiGraphology();
     graph.addPill2Graphology({
-      type: 'relation',
+      type: QueryElementTypes.Relation,
       x: 140,
       y: 140,
       name: 'Relation Pill',
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill-full.stories.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill-full.stories.tsx
index ac79584b6..fd47ee277 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill-full.stories.tsx
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill-full.stories.tsx
@@ -6,6 +6,7 @@ import { Meta } from '@storybook/react';
 import { Provider } from 'react-redux';
 import { QueryBuilder } from '../../../panel';
 import { QueryMultiGraphology } from '@graphpolaris/shared/lib/querybuilder/model/graphology/utils';
+import { QueryElementTypes } from '../../../model';
 
 const Component: Meta<typeof QueryBuilder> = {
   component: QueryBuilder,
@@ -27,7 +28,7 @@ const mockStore = configureStore({
   },
 });
 const graph = new QueryMultiGraphology();
-graph.addPill2Graphology({ id: '2', type: 'entity', x: 100, y: 100, name: 'Entity Pill' });
+graph.addPill2Graphology({ id: '2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Entity Pill' });
 console.log(graph.export());
 
 mockStore.dispatch(setQuerybuilderNodes(graph.export()));
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.module.scss b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.module.scss
index 5dc94dd97..2590cb9d8 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.module.scss
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.module.scss
@@ -36,6 +36,7 @@
   font-family: monospace;
   font-weight: bold;
   color: black;
+  min-width: 8rem;
 
   font-size: 10px;
   border-radius: 2px;
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx
index c2b191fda..2d0cd37a3 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx
@@ -1,9 +1,16 @@
 // import { handles } from '@graphpolaris/shared/lib/querybuilder/usecases';
 import { useTheme } from '@mui/material';
-import React, { MouseEventHandler, useEffect } from 'react';
+import React, { MouseEventHandler, useEffect, useMemo } from 'react';
 import { ReactFlow, Handle, Position, getConnectedEdges } from 'reactflow';
 import styles from './entitypill.module.scss';
-import { SchemaReactflowEntityNode, Handles, getHandleIdFromReactflow } from '../../../model';
+import {
+  SchemaReactflowEntityNode,
+  Handles,
+  toHandleId,
+  handleDataFromReactflowToId,
+  QueryElementTypes,
+  NodeAttribute,
+} from '../../../model';
 import { SchemaAttribute } from '@graphpolaris/shared/lib/schema';
 import { styleHandleMap } from '../../utils';
 import { useQuerybuilderGraph } from '@graphpolaris/shared/lib/data-access';
@@ -14,12 +21,17 @@ import { useQuerybuilderGraph } from '@graphpolaris/shared/lib/data-access';
  */
 export const EntityFlowElement = React.memo((node: SchemaReactflowEntityNode) => {
   const theme = useTheme();
+  // console.log('EntityFlowElement', node);
+
   const data = node.data;
-  const forceOpen: boolean = true;
+  const forceOpen: boolean = false;
+  if (!data.leftRelationHandleId) throw new Error('EntityFlowElement: data.leftRelationHandleId is undefined');
+  if (!data.rightRelationHandleId) throw new Error('EntityFlowElement: data.rightRelationHandleId is undefined');
 
   const graph = useQuerybuilderGraph();
-  const myEdges = graph.edges.filter(
-    (edge) => (edge.source === node.id || edge.target === node.id) && !edge?.attributes?.sourceHandle.includes(Handles.EntityRight) // no need to show if only the relation handle is connected
+  const attributeEdges = useMemo(
+    () => graph.edges.filter((edge) => edge.source === node.id && !!edge?.attributes?.sourceHandleData.attributeType),
+    [graph]
   );
 
   const [hovered, setHovered] = React.useState(false);
@@ -36,7 +48,7 @@ export const EntityFlowElement = React.memo((node: SchemaReactflowEntityNode) =>
     setHovered(false);
   };
 
-  const onHandleMouseDown = (attribute: SchemaAttribute, i: number, event: React.MouseEvent) => {
+  const onHandleMouseDown = (attribute: NodeAttribute, i: number, event: React.MouseEvent) => {
     setHandleBeingDragged(i);
     window.addEventListener('mouseup', onHandleMouseUp, true);
   };
@@ -50,7 +62,7 @@ export const EntityFlowElement = React.memo((node: SchemaReactflowEntityNode) =>
     console.log('EntityPill onConnect', params);
   };
 
-  const showingDropdown = hovered || handleBeingDragged !== -1 || myEdges.length > 0;
+  const showingDropdown = hovered || handleBeingDragged !== -1 || attributeEdges.length > 0;
 
   return (
     <div
@@ -65,14 +77,14 @@ export const EntityFlowElement = React.memo((node: SchemaReactflowEntityNode) =>
     >
       <Handle
         // id={getHandleId(data.name, data.type, Handles.ToRelation, '')}
-        id={data.leftRelationHandleId}
+        id={toHandleId(data.leftRelationHandleId)}
         type="target"
         position={Position.Left}
         className={styles.handle_to_relation}
       />
       <Handle
         // id={getHandleId(data.name, data.type, Handles.ToRelation, '')}
-        id={data.rightRelationHandleId}
+        id={toHandleId(data.rightRelationHandleId)}
         type="source"
         position={Position.Right}
         className={styles.handle_to_relation}
@@ -110,28 +122,39 @@ export const EntityFlowElement = React.memo((node: SchemaReactflowEntityNode) =>
         <span className={styles.entitySpan}>{data.name}</span>
       </div> */}
       {data?.attributes && (
-        <div className={styles.content + ' ' + (showingDropdown || forceOpen ? styles.content_display : '')}>
+        <div className={styles.content + ' ' + (showingDropdown || forceOpen || hovered ? styles.content_display : '')}>
           {data.attributes
             .filter(
               (attribute, i) =>
                 forceOpen ||
                 hovered ||
                 handleBeingDragged === i ||
-                myEdges.some((edge) => edge?.attributes?.sourceHandle === getHandleIdFromReactflow(node, attribute))
+                (attributeEdges.some(
+                  (edge) =>
+                    edge?.attributes?.sourceHandleData &&
+                    toHandleId(edge?.attributes?.sourceHandleData) === handleDataFromReactflowToId(node, attribute)
+                ) &&
+                  !!attribute.handleData.attributeName)
             )
             .map((attribute, i) => (
               <div
-                key={attribute.name + i}
+                key={(attribute.handleData.attributeName || '') + i}
                 onMouseDown={(event: React.MouseEvent) => {
                   onHandleMouseDown(attribute, i, event);
                 }}
               >
-                {attribute.name}
+                {attribute.handleData.attributeName}
                 <Handle
-                  id={getHandleIdFromReactflow(node, attribute)}
+                  id={handleDataFromReactflowToId(node, attribute)}
                   type="source"
                   position={Position.Right}
-                  className={(styleHandleMap?.[attribute.type] || '') + ' ' + styles.io + ' ' + styles.io_right}
+                  className={
+                    (attribute.handleData.attributeType ? styleHandleMap[attribute.handleData.attributeType] : '') +
+                    ' ' +
+                    styles.io +
+                    ' ' +
+                    styles.io_right
+                  }
                 ></Handle>
               </div>
             ))}
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/logicpill.module.scss b/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/logicpill.module.scss
index 97dd71472..81e3710c8 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/logicpill.module.scss
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/logicpill.module.scss
@@ -5,13 +5,13 @@
   // background-color: #e68067;
   background-color: #fdfdfd;
   border: #e68067 solid 1px;
-  border-radius: 5px;
+  border-radius: 2px;
   font-family: monospace;
-  font-weight: bolder;
+  font-weight: bold;
   font-size: 10;
   display: flex;
-  border-top-right-radius: 5px;
-  border-bottom-right-radius: 5px;
+  // border-top-right-radius: 5px;
+  // border-bottom-right-radius: 5px;
   // height: 3rem;
   .logicInput {
     // float: right;
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/logicpill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/logicpill.tsx
index 37294f09e..538e6391a 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/logicpill.tsx
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/logicpill.tsx
@@ -8,80 +8,129 @@
 /* The comment above was added so the code coverage wouldn't count this file towards code coverage.
  * We do not test components/renderfunctions/styling files.
  * See testing plan for more details.*/
-import React from 'react';
+import React, { useCallback, useMemo } from 'react';
 import { Handle, HandleType, NodeProps, Position } from 'reactflow';
 import styles from './logicpill.module.scss';
-import { AllLogicMap, Handles, SchemaReactflowLogicNode, fromHandleId, getHandleId, getHandleIdFromReactflow } from '../../../model';
+import {
+  AllLogicMap,
+  EntityNodeAttributes,
+  Handles,
+  LogicData,
+  LogicNodeAttributes,
+  QueryGraphEdges,
+  QueryGraphNodes,
+  SchemaReactflowLogicNode,
+  toHandleData,
+  toHandleId,
+} from '../../../model';
 import { Input } from '@mui/material';
 import { styleHandleMap } from '../../utils';
-import { useQuerybuilderGraph } from '@graphpolaris/shared/lib/data-access';
-import { InputNode } from '../../../model/logic/general';
+import {
+  setQuerybuilderNodes,
+  useAppDispatch,
+  useQuerybuilderGraph,
+  useQuerybuilderGraphology,
+} from '@graphpolaris/shared/lib/data-access';
+import { InputNode, InputNodeTypeTypes } from '../../../model/logic/general';
+import { SerializedEdge, SerializedNode } from 'graphology-types';
 
 /**
  * Component to render an entity flow element
  * @param param0 Data of the flow element.
  */
 export default function LogicPill(node: SchemaReactflowLogicNode) {
+  const dispatch = useAppDispatch();
   const data = node.data;
   const logic = data.logic;
-  const left = logic.inputs.filter((input) => input.position === Position.Left);
-  const right = logic.inputs.filter((input) => input.position === Position.Right);
+  const output = data.logic.output;
 
   const graph = useQuerybuilderGraph();
-  const leftEdges = graph.edges.filter((edge) => edge.target === node.id);
-  const rightEdges = graph.edges.filter((edge) => edge.source === node.id);
+  const graphology = useQuerybuilderGraphology();
+  const connectionsToLeft = useMemo(() => graph.edges.filter((edge) => edge.target === node.id), [graph]);
+  const connectionsToRight = useMemo(() => graph.edges.filter((edge) => edge.source === node.id), [graph]);
 
-  function createHandles(side: InputNode[], positionSide: Position, handleType: HandleType) {
-    let numOfInputs = 0;
-    let ret = side.map((input, i) => {
-      let inputTextBox = null;
-      if (
-        positionSide === Position.Left &&
-        !leftEdges.some(
-          (edge) =>
-            edge?.attributes?.targetHandle ===
-            getHandleId(data?.id || 'awkudgi', data.name, node.type, input.name, input.type.join(''), { extra: positionSide }) // TODO
-        )
-      ) {
-        inputTextBox = (
-          <Input
-            className={styles.logicInput}
-            // value={0}
-            style={{ top: -5, transform: `translateY(-${i * 20}%)` }}
-            onChange={(e) => {}}
-          />
+  if (!data.id) throw new Error('LogicPill: data.id is undefined');
+  const defaultHandleData = {
+    nodeId: data.id,
+    nodeName: data.name,
+    nodeType: data.type,
+  };
+
+  const onInputUpdated = (value: string, input: InputNode, idx: number) => {
+    let logicNode = { ...(graphology.getNodeAttributes(node.id) as LogicNodeAttributes) };
+    if (!logicNode) throw new Error('LogicPill: logicNode is undefined');
+    let logicNodeInputs = { ...logicNode.inputs };
+    if (logicNodeInputs[input.name] != value) {
+      logicNodeInputs[input.name] = value;
+      logicNode.inputs = logicNodeInputs;
+      graphology.setNodeAttribute<any>(node.id, 'inputs', logicNodeInputs); // FIXME: I'm not sure why TS requires <any> to work here
+      dispatch(setQuerybuilderNodes(graphology.export()));
+    }
+  };
+
+  const createLeftHandles = useCallback(
+    (sideInputs: InputNode[], positionSide: Position, handleType: HandleType) => {
+      let numOfInputs = 0;
+      let ret = sideInputs.map((input, i) => {
+        let inputTextBox = null;
+        if (
+          !connectionsToLeft.some(
+            (edge) =>
+              edge?.attributes?.targetHandleData.nodeId === data.id && edge?.attributes?.targetHandleData.attributeName === input.name
+          )
+        ) {
+          inputTextBox = (
+            <Input
+              className={styles.logicInput}
+              // value={0}
+              style={{ top: -5, transform: `translateY(-${i * 20}%)` }}
+              // onChange={(e) => onInputUpdated(e.target.value, input)}
+              onKeyDown={(e) => {
+                if (e.key === 'Enter') onInputUpdated((e.target as HTMLInputElement).value, input, i);
+              }}
+              onBlur={(e) => onInputUpdated(e.target.value, input, i)}
+            />
+          );
+          numOfInputs++;
+        }
+        return (
+          <Handle
+            type={handleType}
+            position={positionSide}
+            id={toHandleId({ ...defaultHandleData, attributeName: input.name, attributeType: input.type })} // TODO
+            key={input.name + input.type}
+            // style={{ top: `${((i + 0.8) / (side.length + 0.6)) * 120}%` }}
+            style={{ top: `${((i + 0.8) / (sideInputs.length + 0.6)) * 100}%` }}
+            className={styleHandleMap[input.type]}
+          >
+            {inputTextBox}
+          </Handle>
         );
-        numOfInputs++;
-      }
-      return (
-        <Handle
-          type={handleType}
-          position={positionSide}
-          id={getHandleId(data?.id || 'awkudgi', data.name, node.type, input.name, input.type.join(''), { extra: positionSide })} // TODO
-          key={input.name + input.type}
-          // style={{ top: `${((i + 0.8) / (side.length + 0.6)) * 120}%` }}
-          style={{ top: `${((i + 0.8) / (side.length + 0.6)) * 100}%` }}
-          className={input.type.map((type) => styleHandleMap?.[type] || '').join(' ')}
-        >
-          {inputTextBox}
-        </Handle>
-      );
-    });
-    return { handles: ret, number: numOfInputs };
-  }
-  const { handles: leftHandles, number: leftInputsNumber } = createHandles(left, Position.Left, 'target');
-  const { handles: rightHandles, number: rightInputsNumber } = createHandles(right, Position.Right, 'source');
+      });
+      return { handles: ret, number: numOfInputs };
+    },
+    [node]
+  );
+  const { handles: leftHandles, number: leftInputsNumber } = createLeftHandles(node.data.logic.inputs, Position.Left, 'target');
 
   return (
     <div className={styles.logic}>
-      <span className={styles.logicSpan} style={{ height: `${Math.max(leftInputsNumber * 2.6, rightInputsNumber * 2, 1)}rem` }}>
+      <span className={styles.logicSpan} style={{ height: `${Math.max(leftInputsNumber * 2.6, 1.5)}rem` }}>
         {
           <span>
-            {leftEdges.map((e) => fromHandleId(e?.attributes?.sourceHandle)?.attributeName).join(', ')}.{right?.[0].name}
+            {connectionsToLeft.map((e) => e?.attributes?.sourceHandleData.attributeName)}.{output.name}
           </span>
         }
         {leftHandles}
-        {rightHandles}
+        {!!node.data.logic.output && (
+          <Handle
+            type={'source'}
+            position={Position.Right}
+            id={toHandleId({ ...defaultHandleData, attributeName: output.name, attributeType: output.type })} // TODO
+            // style={{ top: `${((i + 0.8) / (side.length + 0.6)) * 100}%` }}
+            className={styleHandleMap?.[output.type]}
+          ></Handle>
+        )}
       </span>
     </div>
   );
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-full_reactflow.stories.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-full_reactflow.stories.tsx
index ad157810b..5b6d0c231 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-full_reactflow.stories.tsx
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-full_reactflow.stories.tsx
@@ -6,7 +6,7 @@ import { Meta } from '@storybook/react';
 import { Provider } from 'react-redux';
 import { MultiGraph } from 'graphology';
 import { QueryBuilder } from '../../../panel';
-import { QueryMultiGraphology } from '../../../model';
+import { QueryElementTypes, QueryMultiGraphology } from '../../../model';
 
 const Component: Meta<typeof QueryBuilder> = {
   component: QueryBuilder,
@@ -30,7 +30,7 @@ const mockStore = configureStore({
 const graph = new QueryMultiGraphology();
 graph.addPill2Graphology({
   id: '2',
-  type: 'relation',
+  type: QueryElementTypes.Relation,
   x: 140,
   y: 140,
   name: 'Relation Pill',
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx
index a714cd120..323310f8a 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx
@@ -4,7 +4,7 @@ import { useTheme } from '@mui/material';
 import { Handle, Position } from 'reactflow';
 
 import styles from './relationpill.module.scss';
-import { SchemaReactflowRelationNode, Handles, getHandleId } from '../../../model';
+import { SchemaReactflowRelationNode, Handles, toHandleId } from '../../../model';
 
 // export type RelationRFPillProps = {
 //   data: {
@@ -81,12 +81,14 @@ export const RelationPill = memo((node: SchemaReactflowRelationNode) => {
           className={styles.relationHandleFiller}
           // style={{ transform: 'translate(-100px,0)' }}
         >
-          <Handle
-            id={data.leftEntityHandleId}
-            type="target"
-            position={Position.Left}
-            className={styles.relationHandleLeft + ' ' + (false ? styles.handleConnectedBorderLeft : '')}
-          />
+          {data.leftEntityHandleId && (
+            <Handle
+              id={toHandleId(data.leftEntityHandleId)}
+              type="target"
+              position={Position.Left}
+              className={styles.relationHandleLeft + ' ' + (false ? styles.handleConnectedBorderLeft : '')}
+            />
+          )}
         </span>
         {/* <span className={styles.relationHandleFiller}>
           <Handle
@@ -165,12 +167,14 @@ export const RelationPill = memo((node: SchemaReactflowRelationNode) => {
           </span>
         </div>
         <span className={styles.relationHandleFiller}>
-          <Handle
-            id={data.rightEntityHandleId}
-            type="source"
-            position={Position.Right}
-            className={styles.relationHandleRight + ' ' + (false ? styles.handleConnectedBorderRight : '')}
-          />
+          {data.rightEntityHandleId && (
+            <Handle
+              id={toHandleId(data.rightEntityHandleId)}
+              type="source"
+              position={Position.Right}
+              className={styles.relationHandleRight + ' ' + (false ? styles.handleConnectedBorderRight : '')}
+            />
+          )}
         </span>
       </div>
     </div>
diff --git a/libs/shared/lib/querybuilder/pills/handles.module.scss b/libs/shared/lib/querybuilder/pills/handles.module.scss
index 7a138b0ed..a39b5f8ea 100644
--- a/libs/shared/lib/querybuilder/pills/handles.module.scss
+++ b/libs/shared/lib/querybuilder/pills/handles.module.scss
@@ -1,11 +1,13 @@
 .handle {
   z-index: 1;
+  height: 8px !important;
+  width: 8px !important;
 }
 
 .handle_to_relation {
   @extend .handle;
   border-radius: 1px !important;
-  top: 0.55rem !important;
+  top: 0.6rem !important;
   background: rgb(39, 131, 145) !important;
 }
 
diff --git a/libs/shared/lib/querybuilder/pills/utils.ts b/libs/shared/lib/querybuilder/pills/utils.ts
index 6570ee17c..02098af66 100644
--- a/libs/shared/lib/querybuilder/pills/utils.ts
+++ b/libs/shared/lib/querybuilder/pills/utils.ts
@@ -1,6 +1,7 @@
+import { InputNodeType } from '../model/logic/general';
 import styles from './querypills.module.scss';
 
-export const styleHandleMap = {
+export const styleHandleMap: Record<InputNodeType, string> = {
   string: styles.handle_logic_string,
   int: styles.handle_logic_int,
   float: styles.handle_logic_float,
diff --git a/libs/shared/lib/querybuilder/query-utils/index.ts b/libs/shared/lib/querybuilder/query-utils/index.ts
index b63764185..0477263bf 100644
--- a/libs/shared/lib/querybuilder/query-utils/index.ts
+++ b/libs/shared/lib/querybuilder/query-utils/index.ts
@@ -1,2 +1,2 @@
-export * from './query-utils';
+export * from './query2backend';
 export * from '../model/BackendQueryFormat';
diff --git a/libs/shared/lib/querybuilder/query-utils/query-utils.spec.ts b/libs/shared/lib/querybuilder/query-utils/query-utils.spec.ts
deleted file mode 100644
index 8e9b2b1f2..000000000
--- a/libs/shared/lib/querybuilder/query-utils/query-utils.spec.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { describe, it, expect } from 'vitest';
-// import { Query2BackendQuery } from './query-utils';
-import { QueryMultiGraphology } from '../model/graphology/utils';
-
-describe('QueryUtils', () => {
-  it('should create an instance', () => {
-    expect(true).toBeTruthy();
-  });
-
-  // it('should run query translation', () => {
-  // const graph = new QueryMultiGraphology(); // FIXME: not working for some reason
-
-  // let ret = Query2BackendQuery('database', graph.export());
-  // console.log(ret);
-  // });
-});
diff --git a/libs/shared/lib/querybuilder/query-utils/query-utils.ts b/libs/shared/lib/querybuilder/query-utils/query-utils.ts
deleted file mode 100644
index 71a935e62..000000000
--- a/libs/shared/lib/querybuilder/query-utils/query-utils.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-import { EntityNodeAttributes, QueryGraphNodes, RelationNodeAttributes } from '../model/graphology/model';
-import { QueryMultiGraph } from '../model/graphology/utils';
-import { BackendQueryFormat, NodeStruct, QueryStruct, RelationStruct } from '../model/BackendQueryFormat';
-import { Handles, QueryElementTypes, fromHandleId } from '../model';
-import { get } from 'http';
-import { SerializedEdge } from 'graphology-types';
-import { G } from 'vitest/dist/types-fafda418';
-
-// export type QueryI {
-
-// }
-
-/**
- * Converts the ReactFlow query to a json data structure to send the query to the backend.
- * @returns {BackendQueryFormat} A JSON object in the `JSONFormat`.
- */
-export function Query2BackendQuery(databaseName: string, graph: QueryMultiGraph): BackendQueryFormat {
-  let query: BackendQueryFormat = {
-    databaseName: databaseName,
-    query: [],
-    limit: 5000, // TODO
-    return: ['*'], // TODO
-  };
-  let graphLogicChunks: QueryGraphNodes[][] = [[]];
-
-  const entities: EntityNodeAttributes[] = graph.nodes
-    .map((n) => n.attributes)
-    .filter((n) => n?.type === QueryElementTypes.Entity) as EntityNodeAttributes[];
-  const entitiesEmptyLeftHandle = entities.filter((n) => !graph.edges.some((e) => e.target === n?.id));
-  // const entitiesEmptyRightHandle = entities.filter((n) => !n?.rightRelationHandleId);
-
-  const relations: RelationNodeAttributes[] = graph.nodes
-    .map((n) => n.attributes)
-    .filter((n) => n?.type === QueryElementTypes.Relation) as RelationNodeAttributes[];
-  // const relationsEmptyLeftHandle = relations.filter((n) => !n?.leftEntityHandleId);
-  // const relationsEmptyRightHandle = relations.filter((n) => !n?.rightEntityHandleId);
-
-  const traversePaths = (node: QueryGraphNodes, paths: QueryGraphNodes[][], currentIdx: number): number => {
-    if (!paths?.[currentIdx]) paths.push([]);
-    paths[currentIdx].push(node);
-
-    // const rightHandle =
-    //   node.type === QueryElementTypes.Entity
-    //     ? (node as EntityNodeAttributes)?.rightRelationHandleId
-    //     : (node as RelationNodeAttributes)?.rightEntityHandleId;
-
-    let connections = graph.edges.filter((e) => e.source === node.id);
-    if (connections.length === 0) return 0;
-
-    const nodesToRight = connections
-      .map((c, i) => {
-        const rightNodeHandleData = fromHandleId(c.attributes?.targetHandle);
-        const rightNode =
-          rightNodeHandleData.nodeType === QueryElementTypes.Entity
-            ? entities.find((r) => r.id === c.target)
-            : relations.find((r) => r.id === c.target);
-        return rightNode;
-      })
-      .filter((n) => n !== undefined) as QueryGraphNodes[];
-
-    // if (!rightNode) return;
-    // if (currentIdx + i === paths.length) paths.push(paths[currentIdx - 1 + i]);
-    // inverseTraverse(rightNode, paths, currentIdx + i);
-    // paths[currentIdx + i].push(rightNode);
-    // nodesToRight.forEach((rightNode, i) => {
-    //   if (currentIdx + i === paths.length) {
-    //     paths.push(paths[currentIdx - 1 + i]);
-    //   }
-    // });
-
-    let chunkOffset = 0;
-    nodesToRight.forEach((rightNode, i) => {
-      chunkOffset += traversePaths(rightNode, paths, currentIdx + i + chunkOffset);
-    });
-
-    return nodesToRight.length - 1; // offset
-  };
-
-  // find paths which ends at an entity with no right handle
-  let chunkOffset = 0;
-  entitiesEmptyLeftHandle.map((entity, i) => {
-    chunkOffset += traversePaths(entity, graphLogicChunks, i + chunkOffset);
-  });
-
-  console.log('graphLogicChunks', graphLogicChunks);
-  if (!graphLogicChunks || graphLogicChunks.length === 0 || graphLogicChunks?.[0].length === 0) return query;
-
-  const processConnection = (chunk: QueryGraphNodes[], position: number): RelationStruct | NodeStruct => {
-    const currNode = chunk[position];
-
-    if (currNode.type === QueryElementTypes.Relation) {
-      const ret: RelationStruct = {
-        ID: currNode?.id,
-        label: currNode?.name,
-        // depth: QuerySearchDepthStruct;
-        direction: 'TO',
-        node: chunk.length === position + 1 ? undefined : (processConnection(chunk, position + 1) as NodeStruct),
-      };
-      return ret;
-    } else if (currNode.type === QueryElementTypes.Entity) {
-      const ret: NodeStruct = {
-        ID: currNode?.id,
-        label: currNode?.name,
-        // logic: LogicStruct[];
-        // subQuery?: QueryStruct;
-        // export: ExportNodeStruct[];
-        relation: chunk.length === position + 1 ? undefined : (processConnection(chunk, position + 1) as RelationStruct),
-      };
-      return ret;
-    }
-
-    throw Error('Malformed Chunks! ' + chunk + position);
-  };
-
-  query.query = graphLogicChunks.map((chunk, i) => {
-    const ret: QueryStruct = {
-      ID: 'path_' + i, //TODO: chunk[0].id ||
-      node: processConnection(chunk.reverse(), 0),
-    };
-    return ret;
-  });
-
-  console.log(query);
-
-  return query;
-}
-
-// function processConnectionFromRelation(
-//   c: SerializedEdge,
-//   entities: EntityNodeAttributes[],
-//   relations: RelationNodeAttributes[]
-// ): NodeStruct {
-//   if (c.attributes?.targetHandle === null) throw Error('Malformed Graph! One or more edges of a relation node do not exist');
-//   const targetHandleData = fromHandleId(c.attributes?.targetHandle);
-
-//   if (targetHandleData.nodeType === QueryElementTypes.Entity) {
-//     const targetEntity = entities.find((r) => r.id === c.target);
-//   } else if (targetHandleData.nodeType === QueryElementTypes.Relation) {
-//     const targetRelation = relations.find((r) => r.id === c.target);
-//   } else if (targetHandleData.nodeType === QueryElementTypes.Logic) {
-//   }
-// }
diff --git a/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts b/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts
new file mode 100644
index 000000000..e99347c6f
--- /dev/null
+++ b/libs/shared/lib/querybuilder/query-utils/query2backend.spec.ts
@@ -0,0 +1,960 @@
+import { describe, expect, it } from 'vitest';
+// import { Query2BackendQuery } from './query-utils';
+import { BackendQueryFormat, LogicNodeAttributes, MathFilters, MathFunctions, QueryElementTypes } from '../model';
+import { QueryMultiGraphology } from '../model/graphology/utils';
+import { MathAggregationTypes, MathFilterTypes, MathFunctionTypes } from '../model/logic/general';
+import { Query2BackendQuery, calculateQueryLogic } from './query2backend';
+import { SerializedNode } from 'graphology-types';
+import { MathAggregations } from '../model/logic/mathAggregations';
+
+const defaultQuery = {
+  databaseName: 'database',
+  query: [],
+  limit: 500,
+  return: ['*'],
+};
+
+describe('QueryUtils Entity and Relations', () => {
+  it('should create an instance', () => {
+    const graph = new QueryMultiGraphology();
+    expect(true).toBeTruthy();
+  });
+
+  it('should run simple query translation', () => {
+    const graph = new QueryMultiGraphology();
+
+    const entity1 = graph.addPill2Graphology({
+      id: '0',
+      type: QueryElementTypes.Entity,
+      x: 100,
+      y: 100,
+      name: 'Airport 1',
+    });
+    const entity2 = graph.addPill2Graphology({
+      id: '10',
+      type: QueryElementTypes.Entity,
+      x: 200,
+      y: 200,
+      name: 'Airport 2',
+    });
+
+    const relation1 = graph.addPill2Graphology({
+      id: '1',
+      type: QueryElementTypes.Relation,
+      x: 140,
+      y: 140,
+      name: 'Flight between airports',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+    });
+
+    graph.addEdge2Graphology(entity1, relation1);
+    graph.addEdge2Graphology(relation1, entity2);
+
+    const expected = {
+      ...defaultQuery,
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            ID: '0',
+            label: 'Airport 1',
+            relation: {
+              ID: '1',
+              label: 'Flight between airports',
+              direction: 'TO',
+              node: {
+                ID: '10',
+                label: 'Airport 2',
+                relation: undefined,
+              },
+            },
+          },
+        },
+      ],
+    };
+
+    let ret = Query2BackendQuery('database', graph.export());
+    expect(ret).toMatchObject(expected);
+  });
+
+  it('should run multiple path query translation', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e1 = graph.addPill2Graphology({ id: 'e0', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
+    const e2 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 2' });
+
+    const r1 = graph.addPill2Graphology({
+      id: 'r1',
+      type: QueryElementTypes.Relation,
+      x: 140,
+      y: 140,
+      name: 'Flight between airports',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+    });
+
+    graph.addEdge2Graphology(e1, r1, { type: 'connection' });
+    graph.addEdge2Graphology(r1, e2, { type: 'connection' });
+
+    const e12 = graph.addPill2Graphology({ id: 'e12', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 12' });
+    const e22 = graph.addPill2Graphology({ id: 'e22', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 22' });
+
+    const r12 = graph.addPill2Graphology({
+      id: 'r12',
+      type: QueryElementTypes.Relation,
+      x: 140,
+      y: 140,
+      name: 'Flight between airports 2',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+    });
+
+    graph.addEdge2Graphology(e12, r12);
+    graph.addEdge2Graphology(r12, e22);
+
+    const expected = {
+      ...defaultQuery,
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            ID: 'e0',
+            label: 'Airport 1',
+            relation: {
+              ID: 'r1',
+              label: 'Flight between airports',
+              direction: 'TO',
+              node: {
+                ID: 'e1',
+                label: 'Airport 2',
+                relation: undefined,
+              },
+            },
+          },
+        },
+        {
+          ID: 'path_1',
+          node: {
+            ID: 'e12',
+            label: 'Airport 12',
+            relation: {
+              ID: 'r12',
+              label: 'Flight between airports 2',
+              direction: 'TO',
+              node: {
+                ID: 'e22',
+                label: 'Airport 22',
+                relation: undefined,
+              },
+            },
+          },
+        },
+      ],
+    };
+
+    let ret = Query2BackendQuery('database', graph.export());
+    expect(ret).toMatchObject(expected);
+  });
+
+  it('should run one relation multiple entity query translation', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
+    const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 2' });
+    const e12 = graph.addPill2Graphology({ id: 'e12', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 12' });
+    const e22 = graph.addPill2Graphology({ id: 'e22', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 22' });
+
+    const r1 = graph.addPill2Graphology({
+      id: 'r1',
+      type: QueryElementTypes.Relation,
+      x: 140,
+      y: 140,
+      name: 'Flight between airports',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+    });
+
+    graph.addEdge2Graphology(e1, r1);
+    graph.addEdge2Graphology(r1, e2);
+
+    graph.addEdge2Graphology(e12, r1);
+    graph.addEdge2Graphology(r1, e22);
+
+    const expected = {
+      ...defaultQuery,
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            ID: 'e1',
+            label: 'Airport 1',
+            relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e2', label: 'Airport 2' } },
+          },
+        },
+        {
+          ID: 'path_1',
+          node: {
+            ID: 'e1',
+            label: 'Airport 1',
+            relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e22', label: 'Airport 22' } },
+          },
+        },
+        {
+          ID: 'path_2',
+          node: {
+            ID: 'e12',
+            label: 'Airport 12',
+            relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e2', label: 'Airport 2' } },
+          },
+        },
+        {
+          ID: 'path_3',
+          node: {
+            ID: 'e12',
+            label: 'Airport 12',
+            relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e22', label: 'Airport 22' } },
+          },
+        },
+      ],
+    };
+    let ret = Query2BackendQuery('database', graph.export());
+    // console.log(JSON.stringify(ret, null, 2));
+    expect(ret).toMatchObject(expected);
+  });
+
+  it('should run lone entities query translation', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e1 = graph.addPill2Graphology({ id: 'e0', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
+    const e2 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 2' });
+    const e12 = graph.addPill2Graphology({ id: 'e12', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 12' });
+    const e22 = graph.addPill2Graphology({ id: 'e22', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 22' });
+
+    const r1 = graph.addPill2Graphology({
+      id: 'r1',
+      type: QueryElementTypes.Relation,
+      x: 140,
+      y: 140,
+      name: 'Flight between airports',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+    });
+
+    graph.addEdge2Graphology(e1, r1);
+    graph.addEdge2Graphology(r1, e2);
+
+    const expected = {
+      ...defaultQuery,
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            ID: 'e0',
+            label: 'Airport 1',
+            relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e1', label: 'Airport 2' } },
+          },
+        },
+        { ID: 'path_1', node: { ID: 'e12', label: 'Airport 12' } },
+        { ID: 'path_2', node: { ID: 'e22', label: 'Airport 22' } },
+      ],
+    };
+    let ret = Query2BackendQuery('database', graph.export());
+    // console.log(JSON.stringify(ret, null, 2));
+    expect(ret).toMatchObject(expected);
+  });
+
+  it('should run lone relations query translation', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e1 = graph.addPill2Graphology({ id: 'e0', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
+    const e2 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 200, y: 200, name: 'Airport 2' });
+
+    const r1 = graph.addPill2Graphology({
+      id: 'r1',
+      type: QueryElementTypes.Relation,
+      x: 140,
+      y: 140,
+      name: 'Flight between airports',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+    });
+    const r2 = graph.addPill2Graphology({
+      id: 'r2',
+      type: QueryElementTypes.Relation,
+      x: 140,
+      y: 140,
+      name: 'Flight between airports 2',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+    });
+
+    graph.addEdge2Graphology(e1, r1);
+    graph.addEdge2Graphology(r1, e2);
+
+    const expected = {
+      ...defaultQuery,
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            ID: 'e0',
+            label: 'Airport 1',
+            relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e1', label: 'Airport 2' } },
+          },
+        },
+        {
+          ID: 'path_1',
+          node: { relation: { ID: 'r2', label: 'Flight between airports 2', direction: 'TO', node: {} } },
+        },
+      ],
+    };
+    let ret = Query2BackendQuery('database', graph.export());
+    // console.log(JSON.stringify(ret, null, 2));
+    expect(ret).toMatchObject(expected);
+  });
+
+  it('should run relation only left side connected query translation', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e1 = graph.addPill2Graphology({ id: 'e0', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
+
+    const r1 = graph.addPill2Graphology({
+      id: 'r1',
+      type: QueryElementTypes.Relation,
+      x: 140,
+      y: 140,
+      name: 'Flight between airports',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+    });
+
+    graph.addEdge2Graphology(e1, r1);
+
+    const expected = {
+      ...defaultQuery,
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            ID: 'e0',
+            label: 'Airport 1',
+            relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: {} },
+          },
+        },
+      ],
+    };
+    let ret = Query2BackendQuery('database', graph.export());
+    // console.log(JSON.stringify(ret, null, 2));
+    expect(ret).toMatchObject(expected);
+  });
+
+  it('should run relation only right side connected query translation', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e2 = graph.addPill2Graphology({ id: 'e0', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 2' });
+
+    const r1 = graph.addPill2Graphology({
+      id: 'r1',
+      type: QueryElementTypes.Relation,
+      x: 140,
+      y: 140,
+      name: 'Flight between airports',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+    });
+
+    graph.addEdge2Graphology(r1, e2);
+
+    const expected = {
+      ...defaultQuery,
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e0', label: 'Airport 2' } },
+          },
+        },
+      ],
+    };
+    let ret = Query2BackendQuery('database', graph.export());
+    // console.log(JSON.stringify(ret, null, 2));
+    expect(ret).toMatchObject(expected);
+  });
+
+  it('should run entity and relations multi connection', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
+    const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 2' });
+
+    const r1 = graph.addPill2Graphology({
+      id: 'r1',
+      type: QueryElementTypes.Relation,
+      x: 140,
+      y: 140,
+      name: 'Flight between airports',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+    });
+
+    const r2 = graph.addPill2Graphology({
+      id: 'r2',
+      type: QueryElementTypes.Relation,
+      x: 140,
+      y: 140,
+      name: 'Flight between airports 2',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+    });
+
+    graph.addEdge2Graphology(e1, r1);
+    graph.addEdge2Graphology(e1, r2);
+    graph.addEdge2Graphology(r1, e2);
+    graph.addEdge2Graphology(r2, e2);
+
+    const expected = {
+      ...defaultQuery,
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            ID: 'e1',
+            label: 'Airport 1',
+            relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e2', label: 'Airport 2' } },
+          },
+        },
+        {
+          ID: 'path_1',
+          node: {
+            ID: 'e1',
+            label: 'Airport 1',
+            relation: { ID: 'r2', label: 'Flight between airports 2', direction: 'TO', node: { ID: 'e2', label: 'Airport 2' } },
+          },
+        },
+      ],
+    };
+    let ret = Query2BackendQuery('database', graph.export());
+    // console.log(JSON.stringify(ret, null, 2));
+    expect(ret).toMatchObject(expected);
+  });
+
+  it('should run in case of loops', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
+
+    const r1 = graph.addPill2Graphology({
+      id: 'r1',
+      type: QueryElementTypes.Relation,
+      x: 140,
+      y: 140,
+      name: 'Flight between airports',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+    });
+
+    graph.addEdge2Graphology(e1, r1, { type: 'connection' });
+    graph.addEdge2Graphology(r1, e1, { type: 'connection' });
+
+    const expected = {
+      ...defaultQuery,
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            ID: 'e1',
+            label: 'Airport 1',
+            relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e1', label: 'Airport 1' } },
+          },
+        },
+      ],
+    };
+    let ret = Query2BackendQuery('database', graph.export());
+    // console.log(JSON.stringify(ret, null, 2));
+    expect(ret).toMatchObject(expected);
+  });
+
+  it('should run in case of loops and regular', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
+    const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
+
+    const r1 = graph.addPill2Graphology({
+      id: 'r1',
+      type: QueryElementTypes.Relation,
+      x: 140,
+      y: 140,
+      name: 'Flight between airports',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+    });
+
+    graph.addEdge2Graphology(e1, r1, { type: 'connection' });
+    graph.addEdge2Graphology(r1, e1, { type: 'connection' });
+    graph.addEdge2Graphology(r1, e2, { type: 'connection' });
+
+    const expected = {
+      ...defaultQuery,
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            ID: 'e1',
+            label: 'Airport 1',
+            relation: {
+              ID: 'r1',
+              label: 'Flight between airports',
+              direction: 'TO',
+              node: { ID: 'e2', label: 'Airport 1', relation: undefined },
+            },
+          },
+        },
+        {
+          ID: 'path_1',
+          node: {
+            ID: 'e1',
+            label: 'Airport 1',
+            relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e1', label: 'Airport 1' } },
+          },
+        },
+      ],
+    };
+    let ret = Query2BackendQuery('database', graph.export());
+    // console.log(JSON.stringify(ret, null, 2));
+    expect(ret).toMatchObject(expected);
+  });
+
+  it('should run in case of loops and regular left', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
+    const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
+
+    const r1 = graph.addPill2Graphology({
+      id: 'r1',
+      type: QueryElementTypes.Relation,
+      x: 140,
+      y: 140,
+      name: 'Flight between airports',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+    });
+
+    graph.addEdge2Graphology(e1, r1, { type: 'connection' });
+    graph.addEdge2Graphology(e2, r1, { type: 'connection' });
+    graph.addEdge2Graphology(r1, e1, { type: 'connection' });
+
+    const expected = {
+      ...defaultQuery,
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            ID: 'e1',
+            label: 'Airport 1',
+            relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e1', label: 'Airport 1' } },
+          },
+        },
+        {
+          ID: 'path_1',
+          node: {
+            ID: 'e2',
+            label: 'Airport 1',
+            relation: { ID: 'r1', label: 'Flight between airports', direction: 'TO', node: { ID: 'e1', label: 'Airport 1' } },
+          },
+        },
+      ],
+    };
+    let ret = Query2BackendQuery('database', graph.export());
+    // console.log(JSON.stringify(ret, null, 2));
+    expect(ret).toMatchObject(expected);
+  });
+
+  it('should run in case of direct entity connection', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
+    const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
+
+    graph.addEdge2Graphology(e1, e2, { type: 'connection' });
+
+    const expected = {
+      ...defaultQuery,
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            ID: 'e1',
+            label: 'Airport 1',
+            relation: { direction: 'TO', node: { ID: 'e2', label: 'Airport 1' } },
+          },
+        },
+      ],
+    };
+    let ret = Query2BackendQuery('database', graph.export());
+    // console.log(JSON.stringify(ret, null, 2));
+    expect(ret).toMatchObject(expected);
+  });
+
+  it('should run in case of direct relation connection', () => {
+    const graph = new QueryMultiGraphology();
+
+    const r1 = graph.addPill2Graphology({
+      id: 'r1',
+      type: QueryElementTypes.Relation,
+      x: 140,
+      y: 140,
+      name: 'Flight between airports',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+    });
+
+    const r2 = graph.addPill2Graphology({
+      id: 'r2',
+      type: QueryElementTypes.Relation,
+      x: 140,
+      y: 140,
+      name: 'Flight between airports 2',
+      collection: 'Relation Pill',
+      depth: { min: 0, max: 1 },
+    });
+
+    graph.addEdge2Graphology(r1, r2, { type: 'connection' });
+
+    const expected = {
+      ...defaultQuery,
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            relation: {
+              ID: 'r1',
+              label: 'Flight between airports',
+              direction: 'TO',
+              node: { relation: { ID: 'r2', label: 'Flight between airports 2', direction: 'TO', node: {} } },
+            },
+          },
+        },
+      ],
+    };
+    let ret = Query2BackendQuery('database', graph.export());
+    // console.log(JSON.stringify(ret, null, 2));
+    expect(ret).toMatchObject(expected);
+  });
+
+  it('should run in case of entity only loop', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
+    const e2 = graph.addPill2Graphology({ id: 'e2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
+
+    graph.addEdge2Graphology(e1, e2, { type: 'connection' });
+    graph.addEdge2Graphology(e2, e1, { type: 'connection' });
+
+    const expected = {
+      ...defaultQuery,
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            ID: 'e1',
+            label: 'Airport 1',
+            relation: { direction: 'TO', node: { ID: 'e2', label: 'Airport 1' } },
+          },
+        },
+        {
+          ID: 'path_1',
+          node: {
+            ID: 'e2',
+            label: 'Airport 1',
+            relation: { direction: 'TO', node: { ID: 'e1', label: 'Airport 1' } },
+          },
+        },
+      ],
+    };
+    let ret = Query2BackendQuery('database', graph.export());
+    // console.log(JSON.stringify(ret, null, 2));
+    expect(ret).toMatchObject(expected);
+  });
+
+  it('should run in case of entity only self loop', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e1 = graph.addPill2Graphology({ id: 'e1', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Airport 1' });
+
+    graph.addEdge2Graphology(e1, e1, { type: 'connection' });
+
+    const expected = {
+      ...defaultQuery,
+      query: [
+        {
+          ID: 'path_0',
+          node: { ID: 'e1', label: 'Airport 1', relation: { direction: 'TO', node: { ID: 'e1', label: 'Airport 1' } } },
+        },
+      ],
+    };
+    let ret = Query2BackendQuery('database', graph.export());
+    // console.log(JSON.stringify(ret, null, 2));
+    expect(ret).toMatchObject(expected);
+  });
+});
+
+describe('QueryUtils calculateQueryLogic', () => {
+  it('should run simple logic', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e1 = graph.addPill2Graphology(
+      {
+        id: 'e1',
+        type: QueryElementTypes.Entity,
+        x: 100,
+        y: 100,
+        name: 'Airport 1',
+      },
+      [{ name: 'age', type: 'string' }]
+    );
+
+    const l1 = graph.addLogicPill2Graphology({
+      id: 'l1',
+      type: QueryElementTypes.Logic,
+      x: 100,
+      y: 100,
+      name: 'Logic 1',
+      logic: MathFilters[MathFilterTypes.EQUAL],
+    });
+
+    graph.addEdge2Graphology(e1, l1, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: '1' });
+    const graphExport = graph.export();
+    let logics = graphExport.nodes.filter((n) => n?.attributes?.type === QueryElementTypes.Logic) as SerializedNode<LogicNodeAttributes>[];
+
+    const ret = calculateQueryLogic(logics[0], graphExport, logics);
+    console.log(ret);
+  });
+});
+
+describe('QueryUtils with Logic', () => {
+  it('should run simple query with logic', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e1 = graph.addPill2Graphology(
+      {
+        id: 'e1',
+        type: QueryElementTypes.Entity,
+        x: 100,
+        y: 100,
+        name: 'Airport 1',
+      },
+      [{ name: 'age', type: 'string' }]
+    );
+
+    const l1 = graph.addLogicPill2Graphology({
+      id: 'l1',
+      type: QueryElementTypes.Logic,
+      x: 100,
+      y: 100,
+      name: 'Logic 1',
+      logic: MathFilters[MathFilterTypes.EQUAL],
+    });
+
+    graph.addEdge2Graphology(e1, l1, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: '1' });
+
+    const expected: BackendQueryFormat = {
+      ...defaultQuery,
+      logic: ['==', '@e1.age', 0],
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            ID: 'e1',
+            label: 'Airport 1',
+            relation: undefined,
+          },
+        },
+      ],
+    };
+
+    let ret = Query2BackendQuery('database', graph.export());
+    // console.log(JSON.stringify(ret, null, 2));
+    expect(ret).toMatchObject(expected);
+  });
+
+  it('should run add age and filter', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e1 = graph.addPill2Graphology(
+      {
+        id: 'e1',
+        type: QueryElementTypes.Entity,
+        x: 100,
+        y: 100,
+        name: 'Airport 1',
+      },
+      [{ name: 'age', type: 'string' }]
+    );
+
+    const e2 = graph.addPill2Graphology(
+      {
+        id: 'e2',
+        type: QueryElementTypes.Entity,
+        x: 100,
+        y: 100,
+        name: 'Airport 2',
+      },
+      [{ name: 'age', type: 'string' }]
+    );
+
+    const l1 = graph.addLogicPill2Graphology({
+      id: 'l1',
+      type: QueryElementTypes.Logic,
+      x: 100,
+      y: 100,
+      name: 'Filter EQ',
+      logic: MathFilters[MathFilterTypes.EQUAL],
+    });
+
+    const l2 = graph.addLogicPill2Graphology({
+      id: 'l2',
+      type: QueryElementTypes.Logic,
+      x: 100,
+      y: 100,
+      name: 'Logic ADD',
+      logic: MathFunctions[MathFunctionTypes.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: MathFilterTypes.EQUAL, targetHandleName: '1' });
+
+    const expected: BackendQueryFormat = {
+      ...defaultQuery,
+      logic: ['==', ['+', '@e1.age', '@e2.age'], 0],
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            ID: 'e1',
+            label: 'Airport 1',
+            relation: undefined,
+          },
+        },
+        {
+          ID: 'path_1',
+          node: {
+            ID: 'e2',
+            label: 'Airport 2',
+            relation: undefined,
+          },
+        },
+      ],
+    };
+
+    let ret = Query2BackendQuery('database', graph.export());
+    // console.log(JSON.stringify(ret, null, 2));
+    expect(ret).toMatchObject(expected);
+  });
+
+  it('should handle average logic', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e1 = graph.addPill2Graphology(
+      {
+        id: 'e1',
+        type: QueryElementTypes.Entity,
+        x: 100,
+        y: 100,
+        name: 'Airport 1',
+      },
+      [{ name: 'age', type: 'string' }]
+    );
+
+    const l1 = graph.addLogicPill2Graphology({
+      id: 'l1',
+      type: QueryElementTypes.Logic,
+      x: 100,
+      y: 100,
+      name: 'Logic LT',
+      logic: MathFilters[MathFilterTypes.LESS_THAN],
+    });
+
+    const l2 = graph.addLogicPill2Graphology({
+      id: 'l2',
+      type: QueryElementTypes.Logic,
+      x: 100,
+      y: 100,
+      name: 'Logic average',
+      logic: MathAggregations[MathAggregationTypes.AVG],
+    });
+
+    graph.addEdge2Graphology(e1, l2, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: '1' });
+    graph.addEdge2Graphology(l2, l1, { type: 'connection' }, { sourceHandleName: MathAggregationTypes.AVG, targetHandleName: '1' });
+
+    const expected: BackendQueryFormat = {
+      ...defaultQuery,
+      logic: ['<', ['Avg', '@e1.age'], 0],
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            ID: 'e1',
+            label: 'Airport 1',
+            relation: undefined,
+          },
+        },
+      ],
+    };
+
+    let ret = Query2BackendQuery('database', graph.export());
+    // console.log(JSON.stringify(ret, null, 2));
+    expect(ret).toMatchObject(expected);
+  });
+
+  it('should allow custom values in logic pills', () => {
+    const graph = new QueryMultiGraphology();
+
+    const e1 = graph.addPill2Graphology(
+      {
+        id: 'e1',
+        type: QueryElementTypes.Entity,
+        x: 100,
+        y: 100,
+        name: 'Airport 1',
+      },
+      [{ name: 'age', type: 'string' }]
+    );
+
+    const l1 = graph.addLogicPill2Graphology(
+      {
+        id: 'l1',
+        type: QueryElementTypes.Logic,
+        x: 100,
+        y: 100,
+        name: 'Logic LT',
+        logic: MathFilters[MathFilterTypes.LESS_THAN],
+      },
+      { '2': 5 }
+    );
+
+    graph.addEdge2Graphology(e1, l1, { type: 'connection' }, { sourceHandleName: 'age', targetHandleName: '1' });
+
+    const expected: BackendQueryFormat = {
+      ...defaultQuery,
+      logic: ['<', '@e1.age', 5],
+      query: [
+        {
+          ID: 'path_0',
+          node: {
+            ID: 'e1',
+            label: 'Airport 1',
+            relation: undefined,
+          },
+        },
+      ],
+    };
+
+    let ret = Query2BackendQuery('database', graph.export());
+    // console.log(JSON.stringify(ret, null, 2));
+    expect(ret).toMatchObject(expected);
+  });
+});
diff --git a/libs/shared/lib/querybuilder/query-utils/query2backend.ts b/libs/shared/lib/querybuilder/query-utils/query2backend.ts
new file mode 100644
index 000000000..f4f74943d
--- /dev/null
+++ b/libs/shared/lib/querybuilder/query-utils/query2backend.ts
@@ -0,0 +1,311 @@
+import { EntityNodeAttributes, LogicNodeAttributes, QueryGraphNodes, RelationNodeAttributes } from '../model/graphology/model';
+import { QueryMultiGraph } from '../model/graphology/utils';
+import { BackendQueryFormat, NodeStruct, QueryStruct, RelationStruct } from '../model/BackendQueryFormat';
+import { Handles, QueryElementTypes, toHandleData } from '../model';
+import { get } from 'http';
+import { SerializedEdge, SerializedNode } from 'graphology-types';
+import { G } from 'vitest/dist/types-fafda418';
+import { hasCycle } from 'graphology-dag';
+import Graph, { MultiGraph } from 'graphology';
+import { allSimplePaths } from 'graphology-simple-path';
+import { AllLogicStatement, ReferenceStatement } from '../model/logic/general';
+
+// export type QueryI {
+
+// }
+
+export type Query2BackendQueryOptions = {
+  limit?: number;
+};
+
+// Chunk extraction: traverse graph to find all paths of logic between relations and entities
+const traverseEntityRelationPaths = (
+  node: SerializedNode<QueryGraphNodes>,
+  paths: QueryGraphNodes[][],
+  currentIdx: number,
+  graph: QueryMultiGraph,
+  entities: SerializedNode<EntityNodeAttributes>[],
+  relations: SerializedNode<RelationNodeAttributes>[]
+): number => {
+  if (!node?.attributes) throw Error('Malformed Graph! Node has no attributes');
+  // console.log(paths);
+
+  if (!paths?.[currentIdx]) {
+    // console.log('new path');
+    paths.push([]);
+    if (node.attributes.type === QueryElementTypes.Relation) {
+      paths[currentIdx].push({ type: QueryElementTypes.Entity, x: node.attributes.x, y: node.attributes.y });
+    }
+  } else if (paths[currentIdx].length > 0) {
+    const lastNode = paths[currentIdx][paths[currentIdx].length - 1];
+    if (lastNode.type === node.attributes.type) {
+      if (lastNode.type === QueryElementTypes.Entity) {
+        paths[currentIdx].push({ type: QueryElementTypes.Relation, x: node.attributes.x, y: node.attributes.x });
+      } else {
+        paths[currentIdx].push({ type: QueryElementTypes.Entity, x: node.attributes.x, y: node.attributes.x });
+      }
+    }
+  }
+  paths[currentIdx].push(node.attributes);
+  // if (
+  //   (node.attributes.type === QueryElementTypes.Entity || node.attributes.type === QueryElementTypes.Relation) &&
+  //   paths[currentIdx].some((n) => n.type === QueryElementTypes.Logic)
+  // ) {
+  //   return 0;
+  // }
+
+  // const rightHandle =
+  //   node.type === QueryElementTypes.Entity
+  //     ? (node as EntityNodeAttributes)?.rightRelationHandleId
+  //     : (node as RelationNodeAttributes)?.rightEntityHandleId;
+
+  const edges = graph.edges.filter(
+    (n) =>
+      n?.attributes?.sourceHandleData.nodeType !== QueryElementTypes.Logic &&
+      n?.attributes?.targetHandleData.nodeType !== QueryElementTypes.Logic
+  );
+  let connections = edges.filter((e) => e.source === node.key);
+  if (connections.length === 0) {
+    if (node.attributes.type === QueryElementTypes.Relation) {
+      paths[currentIdx].push({ type: QueryElementTypes.Entity, x: node.attributes.x, y: node.attributes.x });
+    }
+    return 0;
+  }
+  // console.log('connections', connections);
+
+  const nodesToRight = connections
+    .map((c, i) => {
+      const rightNodeHandleData = c.attributes?.targetHandleData;
+      if (!rightNodeHandleData) throw Error('Malformed Graph! One or more edges of a relation node do not exist');
+
+      // console.log('nodesToRight', c);
+      // console.log('entities', entities);
+      if (paths[currentIdx].length > 10 || currentIdx > 10)
+        throw Error('Malformed Graph! One or more edges of a relation node do not exist');
+      const rightNode =
+        rightNodeHandleData.nodeType === QueryElementTypes.Entity
+          ? entities.find((r) => r.key === c.target)
+          : relations.find((r) => r.key === c.target);
+      return rightNode;
+    })
+    .filter((n) => n !== undefined) as SerializedNode<QueryGraphNodes>[];
+
+  let chunkOffset = 0;
+  let pathBeforeTraversing = [...paths[currentIdx]];
+  nodesToRight.forEach((rightNode, i) => {
+    if (i > 0) {
+      paths.push([...pathBeforeTraversing]); // clone previous path in case of branching
+    }
+    chunkOffset += traverseEntityRelationPaths(rightNode, paths, currentIdx + i + chunkOffset, graph, entities, relations);
+  });
+
+  return chunkOffset + nodesToRight.length - 1; // offset
+};
+
+export function calculateQueryLogic(
+  node: SerializedNode<LogicNodeAttributes>,
+  graph: QueryMultiGraph,
+  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);
+
+  let ret = [...node.attributes.logic.logic].map((l) => {
+    if (typeof l !== 'string') throw Error('Malformed Graph! Logic node has no logic');
+    if (!node.attributes) throw Error('Malformed Graph! Logic node has no attributes');
+
+    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');
+
+      const connectionToInputRef = connectionsToLeft.find((c) => c?.attributes?.targetHandleData.attributeName === inputRef.name);
+      if (!connectionToInputRef) {
+        // Not connected, search for set or default value
+        let val = node.attributes.inputs?.[inputRef.name] || inputRef.default;
+        if (val && inputRef.type === 'string') val = `\"${val}\"`;
+        console.log('val', val, inputRef, node);
+        return val;
+      } else if (connectionToInputRef.attributes?.sourceHandleData.nodeType === QueryElementTypes.Logic) {
+        // Is connected to another logic node
+        const leftLogic = logics.find((r) => r.key === connectionToInputRef.attributes?.sourceHandleData.nodeId);
+        if (!leftLogic) throw Error('Malformed Graph! Logic node is connected but has no logic data');
+        return calculateQueryLogic(leftLogic, graph, logics);
+      } else {
+        if (!connectionToInputRef.attributes?.sourceHandleData)
+          throw Error('Malformed Graph! Logic node is connected but has no sourceHandleData');
+        // Is connected to entity or relation node
+        return `@${connectionToInputRef.attributes.sourceHandleData.nodeId}.${connectionToInputRef.attributes.sourceHandleData.attributeName}`;
+      }
+    } else {
+      return l;
+    }
+  });
+  return ret as AllLogicStatement;
+}
+
+function queryLogicUnion(graphLogicChunks: AllLogicStatement[]): AllLogicStatement | undefined {
+  if (graphLogicChunks.length === 1) return graphLogicChunks[0];
+  else if (graphLogicChunks.length > 1) {
+    return ['And', graphLogicChunks[0], queryLogicUnion(graphLogicChunks.slice(1)) || '0'];
+  }
+  return undefined;
+}
+
+/**
+ * Converts the ReactFlow query to a json data structure to send the query to the backend.
+ * @returns {BackendQueryFormat} A JSON object in the `JSONFormat`.
+ */
+export function Query2BackendQuery(
+  databaseName: string,
+  graph: QueryMultiGraph,
+  options: Query2BackendQueryOptions = {}
+): BackendQueryFormat {
+  let query: BackendQueryFormat = {
+    databaseName: databaseName,
+    query: [],
+    limit: options.limit || 500,
+    return: ['*'], // TODO
+  };
+
+  let entities = graph.nodes.filter((n) => n?.attributes?.type === QueryElementTypes.Entity) as SerializedNode<EntityNodeAttributes>[];
+  let relations = graph.nodes.filter((n) => n?.attributes?.type === QueryElementTypes.Relation) as SerializedNode<RelationNodeAttributes>[];
+
+  const graphologyQuery = Graph.from(graph);
+  graphologyQuery
+    .filterNodes((n, att) => att?.type == QueryElementTypes.Logic)
+    .forEach((n) => {
+      graphologyQuery.dropNode(n);
+    }); // Remove all logic nodes from the graph for cycle test
+  if (hasCycle(graphologyQuery)) {
+    const cycles = entities.map((entity, i) => {
+      return allSimplePaths(graphologyQuery, entity.key, entity.key);
+    });
+    cycles.forEach((cycles_inner, i) => {
+      cycles_inner.forEach((cycle, 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);
+        // edgeAttributes.target = newOrigin;
+        graphologyQuery.addEdge(target, newOrigin, edgeAttributes);
+      });
+    });
+
+    console.log('graph', graph);
+    return Query2BackendQuery(databaseName, graphologyQuery.export());
+
+    //   if (
+    //     relations.some((entity, i) => {
+    //       return allSimplePaths(graphologyQuery, entity.id, entity.id);
+    //     })
+    //   )
+    //     throw Error('Cycles in query are not supported yet');
+    //   console.log('cycles', cycles);
+    //   return null;
+    // }
+    // return null;
+  }
+  // Chunk extraction: traverse graph to find all paths of logic between relations and entities
+  let graphSequenceChunks: QueryGraphNodes[][] = [];
+  let graphSequenceLogicChunks: QueryGraphNodes[][] = [];
+  let graphSequenceChunksIdMap: Record<string, [number, number]> = {};
+  let chunkOffset = 0;
+
+  let entitiesEmptyLeftHandle = entities.filter((n) => !graph.edges.some((e) => e.target === n.key));
+  let relationsEmptyLeftHandle = relations.filter((n) => !graph.edges.some((e) => e.target === n.key));
+  // let entitiesEmptyRightHandle = entities.filter((n) => !n?.attributes?.rightRelationHandleId);
+  entitiesEmptyLeftHandle.map((entity, i) => {
+    // start with all entities that have no left handle, which means it "starts" a logic
+    chunkOffset += traverseEntityRelationPaths(entity, graphSequenceChunks, i + chunkOffset, graph, entities, relations);
+  });
+  if (entitiesEmptyLeftHandle.length > 0) chunkOffset++;
+  relationsEmptyLeftHandle.map((entity, i) => {
+    // then, for all relations that have no left handle, since they weren't accounted by the loop above
+    chunkOffset += traverseEntityRelationPaths(entity, graphSequenceChunks, i + chunkOffset, graph, entities, relations);
+  });
+  graphSequenceChunks.forEach((chunkSequence, i) => {
+    chunkSequence.forEach((chunk, j) => {
+      graphSequenceChunksIdMap[chunk.id || chunk.name || ''] = [i, j];
+    });
+  });
+
+  // Logic pathways extraction: now traverse the graph again though the logic components to construct the logic chunks
+  // The traversal is done in reverse order, starting from the logic pill's right handle connected to an entity or relation, and going backwards
+  let logics = graph.nodes.filter((n) => n?.attributes?.type === QueryElementTypes.Logic) as SerializedNode<LogicNodeAttributes>[];
+  let logicsRightHandleConnectedOutside = logics.filter((n) => {
+    return graph.edges.some((e) => e.source === n.key && e.attributes?.targetHandleData.nodeType === QueryElementTypes.Entity);
+  });
+  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);
+
+  // Logic pathways extraction: now traverse the graph again though the logic components to construct the logic chunks
+
+  // console.log('logics', logics);
+  // console.log('graphSequenceChunks', graphSequenceChunks);
+  // console.log('graphLogicChunks', graphLogicChunks);
+  // console.log('logicsRightHandleConnectedOutside', logicsRightHandleConnectedOutside);
+  // console.log('logicsRightHandleFinal', logicsRightHandleFinal);
+  // console.log('graphSequenceChunksIdMap', graphSequenceChunksIdMap);
+  if (!graphSequenceChunks || graphSequenceChunks.length === 0 || graphSequenceChunks?.[0].length === 0) return query;
+
+  const processConnection = (chunk: QueryGraphNodes[], position: number): RelationStruct | NodeStruct => {
+    const currNode = chunk[position];
+
+    if (currNode.type === QueryElementTypes.Relation) {
+      const ret: RelationStruct = {
+        ID: currNode?.id,
+        label: currNode?.name || undefined,
+        // depth: QuerySearchDepthStruct;
+        direction: 'TO',
+        node: chunk.length === position + 1 ? undefined : (processConnection(chunk, position + 1) as NodeStruct),
+      };
+      return ret;
+    } else if (currNode.type === QueryElementTypes.Entity) {
+      const ret: NodeStruct = {
+        ID: currNode?.id,
+        label: currNode?.name,
+        // logic: LogicStruct[];
+        // subQuery?: QueryStruct;
+        // export: ExportNodeStruct[];
+        relation: chunk.length === position + 1 ? undefined : (processConnection(chunk, position + 1) as RelationStruct),
+      };
+      return ret;
+    }
+
+    throw Error('Malformed Chunks! ' + chunk + position);
+  };
+
+  query.query = graphSequenceChunks.map((chunk, i) => {
+    const ret: QueryStruct = {
+      ID: 'path_' + i, //TODO: chunk[0].id ||
+      node: processConnection(chunk, 0),
+    };
+    return ret;
+  });
+
+  console.debug('New query', graph, query);
+
+  return query;
+}
+
+// function processConnectionFromRelation(
+//   c: SerializedEdge,
+//   entities: EntityNodeAttributes[],
+//   relations: RelationNodeAttributes[]
+// ): NodeStruct {
+//   if (c.attributes?.targetHandle === null) throw Error('Malformed Graph! One or more edges of a relation node do not exist');
+//   const targetHandleData = fromHandleId(c.attributes?.targetHandle);
+
+//   if (targetHandleData.nodeType === QueryElementTypes.Entity) {
+//     const targetEntity = entities.find((r) => r.id === c.target);
+//   } else if (targetHandleData.nodeType === QueryElementTypes.Relation) {
+//     const targetRelation = relations.find((r) => r.id === c.target);
+//   } else if (targetHandleData.nodeType === QueryElementTypes.Logic) {
+//   }
+// }
diff --git a/libs/shared/lib/schema/model/reactflow.tsx b/libs/shared/lib/schema/model/reactflow.tsx
index da2e44015..88dc8dc55 100644
--- a/libs/shared/lib/schema/model/reactflow.tsx
+++ b/libs/shared/lib/schema/model/reactflow.tsx
@@ -34,6 +34,7 @@ export interface SchemaReactflowData {
   attributes: SchemaGraphologyNode[];
   nodeCount: number;
   summedNullAmount: number;
+  label: string;
 }
 
 export interface SchemaReactflowNode extends SchemaReactflowData {
diff --git a/libs/shared/lib/schema/panel/schema.tsx b/libs/shared/lib/schema/panel/schema.tsx
index 2c5ee5dee..846faaeea 100644
--- a/libs/shared/lib/schema/panel/schema.tsx
+++ b/libs/shared/lib/schema/panel/schema.tsx
@@ -127,8 +127,7 @@ export const Schema = (props: Props) => {
       return;
     }
     // console.log(schemaGraphology.export());
-
-    console.log(schemaLayout);
+    // console.log(schemaLayout);
 
     updateLayout();
     const expandedSchema = schemaExpandRelation(schemaGraphology);
diff --git a/libs/shared/lib/schema/panel/view-model/schemaViewModel.test.t b/libs/shared/lib/schema/panel/view-model/schemaViewModel.test.t
index 2460545f6..1eaaf31a0 100644
--- a/libs/shared/lib/schema/panel/view-model/schemaViewModel.test.t
+++ b/libs/shared/lib/schema/panel/view-model/schemaViewModel.test.t
@@ -1,1320 +1,1320 @@
-/**
- * This program has been developed by students from the bachelor Computer Science at
- * Utrecht University within the Software Project course.
- * © Copyright Utrecht University (Department of Information and Computing Sciences)
- */
-import {
-  schema,
-  schema2,
-} from '../../../data/mock-data/graph-schema/MockGraph';
-import {
-  mockAttributeDataNLEdge2,
-  mockAttributeDataNLEdge2IncorrectId,
-  mockAttributeDataNLNode1,
-  mockAttributeDataNLNode2,
-  mockAttributeDataNLNode2IncorrectId,
-} from '../../../data/mock-data/graph-schema/MockAttributeDataBatchedNL';
-import SecondChamberSchemaMock from '../../../data/mock-data/schema-result/2ndChamberSchemaMock';
-import GraphUseCase from '../../../domain/usecases/graph-schema/GraphUseCase';
-import SchemaViewModelImpl from './SchemaViewModel';
-import { Node, Edge, ArrowHeadType } from 'react-flow-renderer';
-import DrawOrderUseCase from '../../../domain/usecases/graph-schema/DrawOrderUseCase';
-import EdgeUseCase from '../../../domain/usecases/graph-schema/EdgeUseCase';
-import NodeUseCase from '../../../domain/usecases/graph-schema/NodeUseCase';
-import {
-  AttributeCategory,
-  BoundingBox,
-  NodeQualityDataForEntities,
-  NodeQualityDataForRelations,
-  NodeType,
-} from '../../../domain/entity/graph-schema/structures/Types';
-import {
-  Attribute,
-  AttributeData,
-} from '../../../domain/entity/graph-schema/structures/InputDataTypes';
-import mockQueryResult from '../../../data/mock-data/query-result/big2ndChamberQueryResult';
-import mockSchemaResult from '../../../data/mock-data/schema-result/2ndChamberSchemaMock';
-import Broker from '../../../domain/entity/broker/broker';
-
-jest.mock('../../view/graph-schema/SchemaStyleSheet');
-jest.mock('../../util/graph-schema/utils.tsx', () => {
-  return {
-    //TODO Is this already updated?
-    getWidthOfText: () => {
-      return 10;
-    },
-    calcWidthRelationNodeBox: () => {
-      return 75;
-    },
-    calcWidthEntityNodeBox: () => {
-      return 75;
-    },
-    makeBoundingBox: (x: number, y: number, width: number, height: number) => {
-      let boundingBox: BoundingBox;
-      boundingBox = {
-        topLeft: { x: x, y: y },
-        bottomRight: { x: x + width, y: y + height },
-      };
-      return boundingBox;
-    },
-    doBoxesOverlap: (firstBB: BoundingBox, secondBB: BoundingBox) => {
-      if (
-        firstBB.topLeft.x >= secondBB.bottomRight.x ||
-        secondBB.topLeft.x >= firstBB.bottomRight.x
-      )
-        return false;
-
-      if (
-        firstBB.topLeft.y >= secondBB.bottomRight.y ||
-        secondBB.topLeft.y >= firstBB.bottomRight.y
-      )
-        return false;
-
-      return true;
-    },
-  };
-});
-
-describe('schemaViewModelImpl', () => {
-  beforeEach(() => jest.resetModules());
-  const graphUseCase = new GraphUseCase();
-  const drawOrderUseCase = new DrawOrderUseCase();
-  const nodeUseCase = new NodeUseCase();
-  const edgeUseCase = new EdgeUseCase();
-  const attributesInQueryBuilder: any = [];
-  const addAttribute = (name: string, type: string) => {
-    attributesInQueryBuilder.push({ name: name, type: type });
-    return;
-  };
-
-  function anonymous(
-    attributes: Attribute[],
-    id: string,
-    hiddenAttributes: boolean
-  ): void {
-    //Empty methode.
-  }
-
-  it('should create a relation', () => {
-    const schemaViewModel = new SchemaViewModelImpl(
-      drawOrderUseCase,
-      nodeUseCase,
-      edgeUseCase,
-      graphUseCase,
-      addAttribute
-    );
-
-    const expectedrelationode = {
-      type: 'relation',
-      id: '5',
-      position: { x: -72.5, y: 20 },
-      data: {
-        width: 220,
-        height: 20,
-        collection: 'none',
-        attributes: [],
-        from: 'from:here',
-        to: 'to:here',
-        nodeCount: 0,
-        summedNullAmount: 0,
-        fromRatio: 0,
-        toRatio: 0,
-      },
-    };
-
-    schemaViewModel.createRelationNode(
-      '5',
-      [],
-      'none',
-      schemaViewModel.elements
-    );
-    schemaViewModel.setRelationNodePosition(
-      0,
-      0,
-      '5',
-      'from:here',
-      'to:here',
-      []
-    );
-    expect(JSON.stringify(schemaViewModel.elements.nodes[0])).toEqual(
-      JSON.stringify(expectedrelationode)
-    );
-
-    const expectedrelationode2 = {
-      type: 'relation',
-      id: '6',
-      position: { x: -72.5, y: 40 },
-      data: {
-        width: 220,
-        height: 20,
-        collection: 'none',
-        attributes: [],
-        from: 'from:hereagain',
-        to: 'to:hereagain',
-        nodeCount: 0,
-        summedNullAmount: 0,
-        fromRatio: 0,
-        toRatio: 0,
-      },
-    };
-    schemaViewModel.createRelationNode(
-      '6',
-      [],
-      'none',
-      schemaViewModel.elements
-    );
-    schemaViewModel.setRelationNodePosition(
-      0,
-      0,
-      '6',
-      'from:hereagain',
-      'to:hereagain',
-      []
-    );
-    expect(JSON.stringify(schemaViewModel.elements.nodes[1])).toEqual(
-      JSON.stringify(expectedrelationode2)
-    );
-  });
-
-  it('should console log that method is not implemented', () => {
-    const schemaViewModel = new SchemaViewModelImpl(
-      drawOrderUseCase,
-      nodeUseCase,
-      edgeUseCase,
-      graphUseCase,
-      addAttribute
-    );
-    const consoleSpy = jest.spyOn(console, 'log');
-    schemaViewModel.exportToPDF();
-    expect(consoleSpy).toHaveBeenCalledWith('Method not implemented.');
-  });
-
-  it('fitToView', () => {
-    const schemaViewModel = new SchemaViewModelImpl(
-      drawOrderUseCase,
-      nodeUseCase,
-      edgeUseCase,
-      graphUseCase,
-      addAttribute
-    );
-    const getWidthHeight = { width: 300, height: 700 };
-    const spy = jest.spyOn(schemaViewModel, 'getWidthHeight');
-    spy.mockReturnValue(getWidthHeight);
-
-    schemaViewModel.consumeMessageFromBackend(mockSchemaResult);
-    schemaViewModel.fitToView();
-
-    const consoleSpy = jest.spyOn(console, 'log');
-    expect(consoleSpy).toHaveBeenCalledWith(
-      'this.reactFlowInstance is undefined!'
-    );
-  });
-
-  /**
-   * These are the testcases for consuming messages from the backend
-   */
-  describe('consumeMessageFromBackend', () => {
-    it('should consume schema result only when subscribed to broker for schema_result', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      const mockConsumeMessages = jest.fn();
-      const message = 'test schema result';
-
-      schemaViewModel.consumeMessageFromBackend = mockConsumeMessages;
-
-      // should consume message for schema result when subscribed
-      schemaViewModel.subscribeToSchemaResult();
-      Broker.instance().publish(message, 'schema_result');
-      expect(mockConsumeMessages.mock.calls[0][0]).toEqual(message);
-      expect(mockConsumeMessages).toBeCalledTimes(1);
-
-      // should not consume message for query_result
-      Broker.instance().publish(message, 'query_result');
-      expect(mockConsumeMessages).toBeCalledTimes(1);
-
-      // should not consume message for schema result when unsubscribed
-      schemaViewModel.unSubscribeFromSchemaResult();
-      Broker.instance().publish(message, 'schema_result');
-      expect(mockConsumeMessages).toBeCalledTimes(1);
-    });
-
-    it('should consume attribute-data only when subscribed to broker for gsa_node_result & gsa_edge_result', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      const mockConsumeMessages = jest.fn();
-      const message = 'test analytics data';
-
-      schemaViewModel.consumeMessageFromBackend = mockConsumeMessages;
-
-      // should consume message for schema result when subscribed
-      schemaViewModel.subscribeToAnalyticsData();
-      Broker.instance().publish(message, 'gsa_node_result');
-      Broker.instance().publish(message, 'gsa_edge_result');
-      expect(mockConsumeMessages.mock.calls[0][0]).toEqual(message);
-      expect(mockConsumeMessages).toBeCalledTimes(2);
-
-      // should not consume message for schema result when unsubscribed
-      schemaViewModel.unSubscribeFromAnalyticsData();
-      Broker.instance().publish(message, 'schema_result');
-      Broker.instance().publish(message, 'gsa_node_result');
-      Broker.instance().publish(message, 'gsa_edge_result');
-      expect(mockConsumeMessages).toBeCalledTimes(2);
-    });
-
-    //TODO: also test the message for the analytics
-    it('should console log and should not change any elements when receiving unrelated messages', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      const consoleSpy = jest.spyOn(console, 'log');
-
-      expect(schemaViewModel.visible).toEqual(true);
-      schemaViewModel.consumeMessageFromBackend(mockQueryResult);
-      expect(consoleSpy).toHaveBeenCalledWith('This is no valid input!');
-
-      expect(schemaViewModel.visible).toEqual(true);
-      expect(schemaViewModel.elements.nodes).toEqual([]);
-      expect(schemaViewModel.elements.edges).toEqual([]);
-    });
-  });
-
-  /**
-   * These are the testcases for the attribute-analytics popup menu
-   */
-  describe('AttributeAnalyticsPopupMenu', () => {
-    it('should throw error that given id does not exist', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      schemaViewModel.consumeMessageFromBackend(schema);
-      // Nodes that are not in elements cannot have popups
-      const name = 'Edje';
-      expect(
-        schemaViewModel.elements.nodes.find((node) => node.id == name)
-      ).toEqual(undefined);
-      expect(() =>
-        schemaViewModel.toggleAttributeAnalyticsPopupMenu(name)
-      ).toThrowError(
-        'Node ' + name + ' does not exist therefore no popup menu can be shown.'
-      );
-    });
-
-    it('should show the attribute-analytics popup menu', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      const popup = schemaViewModel.attributeAnalyticsPopupMenu;
-      schemaViewModel.consumeMessageFromBackend(schema);
-
-      // test for entity node
-      const node = schemaViewModel.elements.nodes[0];
-      schemaViewModel.toggleAttributeAnalyticsPopupMenu(node.id);
-      expect(popup.isHidden).toEqual(false);
-      expect(popup.nodeID).toEqual(node.id);
-
-      // test for relation node
-      const edge = schemaViewModel.elements.edges[0];
-      schemaViewModel.setRelationNodePosition(
-        0,
-        0,
-        edge.id,
-        edge.source,
-        edge.target,
-        edge.data.attributes
-      );
-      schemaViewModel.toggleAttributeAnalyticsPopupMenu(edge.id);
-      expect(popup.isHidden).toEqual(false);
-      expect(popup.nodeID).toEqual(edge.id);
-    });
-
-    it('should hide the attribute-analytics popup menu as it was already open', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      schemaViewModel.consumeMessageFromBackend(schema);
-      schemaViewModel.attributeAnalyticsPopupMenu.nodeID = 'Thijs';
-      schemaViewModel.attributeAnalyticsPopupMenu.isHidden = false;
-      schemaViewModel.toggleAttributeAnalyticsPopupMenu('Thijs');
-      expect(schemaViewModel.attributeAnalyticsPopupMenu.isHidden).toEqual(
-        true
-      );
-    });
-
-    it('should close the attribute-analytics menu', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      schemaViewModel.consumeMessageFromBackend(schema);
-      schemaViewModel.attributeAnalyticsData['Thijs'].onClickCloseButton();
-      expect(schemaViewModel.attributeAnalyticsPopupMenu.isHidden).toEqual(
-        true
-      );
-    });
-
-    it('should make an empty attribute-analytics popupmenu', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      schemaViewModel.attributeAnalyticsPopupMenu =
-        schemaViewModel.emptyAttributeAnalyticsPopupMenuNode();
-      expect(schemaViewModel.attributeAnalyticsPopupMenu.id).toEqual(
-        'attributeAnalyticsPopupMenu'
-      );
-      expect(schemaViewModel.attributeAnalyticsPopupMenu.nodeID).toEqual('');
-      expect(schemaViewModel.attributeAnalyticsPopupMenu.data.nodeType).toEqual(
-        NodeType.relation
-      );
-      expect(
-        schemaViewModel.attributeAnalyticsPopupMenu.data.attributes
-      ).toEqual([]);
-      expect(
-        schemaViewModel.attributeAnalyticsPopupMenu.data.isAttributeDataIn
-      ).toEqual(false);
-    });
-
-    it('should place an attribute node in the querybuilder', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      schemaViewModel.consumeMessageFromBackend(schema);
-      const node = schemaViewModel.elements.nodes[0];
-      const attribute = node.data.attributes;
-      schemaViewModel.attributeAnalyticsData[
-        node.id
-      ].onClickPlaceInQueryBuilderButton(attribute.name, attribute.type);
-      expect(attributesInQueryBuilder[0]).toEqual({
-        name: attribute.name,
-        type: attribute.type,
-      });
-    });
-
-    it('should have a working searchbar', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      schemaViewModel.consumeMessageFromBackend(mockSchemaResult);
-      schemaViewModel.searchForAttributes('kamerleden', 'aa');
-
-      let attributes =
-        schemaViewModel.attributeAnalyticsPopupMenu.data.attributes;
-      expect(attributes.length).toEqual(2);
-
-      schemaViewModel.searchForAttributes('kamerleden', '');
-      attributes = schemaViewModel.attributeAnalyticsPopupMenu.data.attributes;
-      expect(attributes.length).toEqual(6);
-    });
-
-    it('should have working filters', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      schemaViewModel.consumeMessageFromBackend(mockSchemaResult);
-      schemaViewModel.consumeMessageFromBackend(mockAttributeDataNLNode1);
-
-      schemaViewModel.applyAttributeFilters(
-        'kamerleden',
-        AttributeCategory.categorical,
-        'Bigger',
-        -1
-      );
-      let attributes =
-        schemaViewModel.attributeAnalyticsPopupMenu.data.attributes;
-      expect(attributes.length).toEqual(1);
-
-      schemaViewModel.resetAttributeFilters('kamerleden');
-      attributes = schemaViewModel.attributeAnalyticsPopupMenu.data.attributes;
-      expect(attributes.length).toEqual(6);
-    });
-  });
-
-  /**
-   * These are the testcases for the node-quality popup menu
-   */
-  describe('nodeQualityPopup', () => {
-    it('should throw error that given id does not exist', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      schemaViewModel.consumeMessageFromBackend(schema);
-      expect(() => schemaViewModel.toggleNodeQualityPopup('Edje')).toThrowError(
-        'Node does not exist therefore no popup can be shown.'
-      );
-    });
-
-    it('should show the node-quality popup menu for an entity node', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      schemaViewModel.consumeMessageFromBackend(SecondChamberSchemaMock);
-      schemaViewModel.toggleNodeQualityPopup('commissies');
-      expect(schemaViewModel.nodeQualityPopup.isHidden).toEqual(false);
-      expect(schemaViewModel.nodeQualityPopup.type).toEqual(
-        'nodeQualityEntityPopup'
-      );
-      expect(schemaViewModel.nodeQualityPopup.nodeID).toEqual('commissies');
-    });
-
-    it('should show the node-quality popup menu for a relation node', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      schemaViewModel.createRelationNode(
-        '5',
-        [],
-        '5',
-        schemaViewModel.elements
-      );
-      schemaViewModel.toggleNodeQualityPopup('5');
-      expect(schemaViewModel.nodeQualityPopup.isHidden).toEqual(false);
-      expect(schemaViewModel.nodeQualityPopup.type).toEqual(
-        'nodeQualityRelationPopup'
-      );
-      expect(schemaViewModel.nodeQualityPopup.nodeID).toEqual('5');
-    });
-
-    it('should hide the node-quality popup menu as it was already open', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      schemaViewModel.consumeMessageFromBackend(schema);
-      schemaViewModel.nodeQualityPopup.nodeID = 'Thijs';
-      schemaViewModel.nodeQualityPopup.isHidden = false;
-      schemaViewModel.toggleNodeQualityPopup('Thijs');
-      expect(schemaViewModel.nodeQualityPopup.isHidden).toEqual(true);
-    });
-
-    it('should close the node-quality menu', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      schemaViewModel.consumeMessageFromBackend(schema);
-      schemaViewModel.nodeQualityData['Thijs'].onClickCloseButton();
-      expect(schemaViewModel.nodeQualityPopup.isHidden).toEqual(true);
-    });
-
-    it('should make an empty node-quality popupmenu', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      schemaViewModel.nodeQualityPopup =
-        schemaViewModel.emptyNodeQualityPopupNode();
-      expect(schemaViewModel.nodeQualityPopup.id).toEqual('nodeQualityPopup');
-      expect(schemaViewModel.nodeQualityPopup.nodeID).toEqual('');
-      expect(schemaViewModel.nodeQualityPopup.data.nodeCount).toEqual(0);
-      expect(schemaViewModel.nodeQualityPopup.data.attributeNullCount).toEqual(
-        0
-      );
-      expect(schemaViewModel.nodeQualityPopup.data.isAttributeDataIn).toEqual(
-        false
-      );
-    });
-  });
-
-  describe('AttributeData', () => {
-    it('should process the incoming data correctly for node-data', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      schemaViewModel.consumeMessageFromBackend(mockSchemaResult);
-      schemaViewModel.consumeMessageFromBackend(mockAttributeDataNLNode2);
-
-      const attributeAnalyticsData =
-        schemaViewModel.attributeAnalyticsData['commissies'];
-      const nodeQualityData = schemaViewModel.nodeQualityData[
-        'commissies'
-      ] as NodeQualityDataForEntities;
-      expect(attributeAnalyticsData.isAttributeDataIn).toEqual(true);
-      expect(attributeAnalyticsData.attributes[0].category).toEqual(
-        AttributeCategory.other
-      );
-      expect(attributeAnalyticsData.attributes[0].nullAmount).toEqual(1);
-      expect(nodeQualityData.nodeCount).toEqual(38);
-      expect(nodeQualityData.attributeNullCount).toEqual(1);
-      expect(nodeQualityData.notConnectedNodeCount).toEqual(0.03);
-    });
-
-    it('should process the incoming data correctly for edge-data', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      schemaViewModel.consumeMessageFromBackend(mockSchemaResult);
-      schemaViewModel.consumeMessageFromBackend(mockAttributeDataNLEdge2);
-
-      const attributeAnalyticsData =
-        schemaViewModel.attributeAnalyticsData['lid_van'];
-      const nodeQualityData = schemaViewModel.nodeQualityData[
-        'lid_van'
-      ] as NodeQualityDataForRelations;
-      expect(attributeAnalyticsData.isAttributeDataIn).toEqual(true);
-      expect(attributeAnalyticsData.attributes[0].category).toEqual(
-        AttributeCategory.categorical
-      );
-      expect(attributeAnalyticsData.attributes[0].nullAmount).toEqual(0);
-      expect(nodeQualityData.nodeCount).toEqual(149);
-      expect(nodeQualityData.attributeNullCount).toEqual(0);
-      expect(nodeQualityData.fromRatio).toEqual(1);
-      expect(nodeQualityData.toRatio).toEqual(1);
-    });
-
-    it('should console log when the given data has no corresponding id for entities', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      schemaViewModel.consumeMessageFromBackend(mockSchemaResult);
-
-      const consoleSpy = jest.spyOn(console, 'log');
-      schemaViewModel.consumeMessageFromBackend(
-        mockAttributeDataNLNode2IncorrectId
-      );
-
-      expect(consoleSpy).toBeCalledTimes(2);
-    });
-
-    it('should console log when the given data has no corresponding id for relations', () => {
-      const schemaViewModel = new SchemaViewModelImpl(
-        drawOrderUseCase,
-        nodeUseCase,
-        edgeUseCase,
-        graphUseCase,
-        addAttribute
-      );
-      schemaViewModel.consumeMessageFromBackend(mockSchemaResult);
-
-      const consoleSpy = jest.spyOn(console, 'log');
-      schemaViewModel.consumeMessageFromBackend(
-        mockAttributeDataNLEdge2IncorrectId
-      );
-
-      expect(consoleSpy).toBeCalledTimes(2);
-    });
-  });
-
-  /** expected results */
-
-  const expectedNodesInElements: Node[] = [
-    {
-      type: 'entity',
-      id: 'Airport',
-      position: { x: 0, y: 0 },
-      data: {
-        attributes: [
-          { name: 'city', type: 'string' },
-          { name: 'vip', type: 'bool' },
-          { name: 'state', type: 'string' },
-        ],
-        handles: [
-          'entityTargetBottom',
-          'entityTargetRight',
-          'entityTargetRight',
-          'entitySourceLeft',
-          'entitySourceLeft',
-          'entitySourceLeft',
-          'entityTargetRight',
-        ],
-        width: 165,
-        height: 20,
-        nodeCount: 0,
-        summedNullAmount: 0,
-        connectedRatio: 0,
-      },
-    },
-    {
-      type: 'entity',
-      id: 'Plane',
-      position: { x: 0, y: 150 },
-      data: {
-        attributes: [
-          { name: 'type', type: 'string' },
-          { name: 'maxFuelCapacity', type: 'int' },
-        ],
-        handles: ['entitySourceTop', 'entityTargetBottom', 'entityTargetRight'],
-        width: 165,
-        height: 20,
-        nodeCount: 0,
-        summedNullAmount: 0,
-        connectedRatio: 0,
-      },
-    },
-    {
-      type: 'entity',
-      id: 'Staff',
-      position: { x: 0, y: 300 },
-      data: {
-        attributes: [],
-        handles: ['entityTargetLeft', 'entitySourceTop', 'entitySourceBottom'],
-        width: 165,
-        height: 20,
-        nodeCount: 0,
-        summedNullAmount: 0,
-        connectedRatio: 0,
-      },
-    },
-    {
-      type: 'entity',
-      id: 'Airport2',
-      position: { x: 0, y: 450 },
-      data: {
-        attributes: [
-          { name: 'city', type: 'string' },
-          { name: 'vip', type: 'bool' },
-          { name: 'state', type: 'string' },
-        ],
-        handles: ['entitySourceRight', 'entitySourceRight', 'entityTargetTop'],
-        width: 165,
-        height: 20,
-        nodeCount: 0,
-        summedNullAmount: 0,
-        connectedRatio: 0,
-      },
-    },
-
-    {
-      type: 'entity',
-      id: 'Thijs',
-      position: { x: 0, y: 600 },
-      data: {
-        attributes: [],
-        handles: ['entitySourceRight', 'entityTargetLeft'],
-        width: 165,
-        height: 20,
-        nodeCount: 0,
-        summedNullAmount: 0,
-        connectedRatio: 0,
-      },
-    },
-
-    {
-      type: 'entity',
-      id: 'Unconnected',
-      position: { x: 0, y: 750 },
-      data: {
-        attributes: [],
-        handles: [],
-        width: 165,
-        height: 20,
-        nodeCount: 0,
-        summedNullAmount: 0,
-        connectedRatio: 0,
-      },
-    },
-    {
-      type: 'relation',
-      id: 'flights',
-      position: { x: 0, y: 0 },
-      data: {
-        width: 220,
-        height: 40,
-        collection: 'flights',
-        attributes: [
-          {
-            name: 'arrivalTime',
-            type: 'int',
-          },
-          {
-            name: 'departureTime',
-            type: 'int',
-          },
-        ],
-        from: '',
-        to: '',
-        nodeCount: 0,
-        summedNullAmount: 0,
-        fromRatio: 0,
-        toRatio: 0,
-      },
-    },
-    {
-      type: 'relation',
-      id: 'flights',
-      position: { x: 0, y: 0 },
-      data: {
-        width: 220,
-        height: 40,
-        collection: 'flights',
-        attributes: [
-          {
-            name: 'salary',
-            type: 'int',
-          },
-        ],
-        from: '',
-        to: '',
-        nodeCount: 0,
-        summedNullAmount: 0,
-        fromRatio: 0,
-        toRatio: 0,
-      },
-    },
-    {
-      type: 'relation',
-      id: 'flights',
-      position: { x: 0, y: 0 },
-      data: {
-        width: 220,
-        height: 40,
-        collection: 'flights',
-        attributes: [],
-        from: '',
-        to: '',
-        nodeCount: 0,
-        summedNullAmount: 0,
-        fromRatio: 0,
-        toRatio: 0,
-      },
-    },
-    {
-      type: 'relation',
-      id: 'flights',
-      position: { x: 0, y: 0 },
-      data: {
-        width: 220,
-        height: 40,
-        collection: 'flights',
-        attributes: [
-          {
-            name: 'hallo',
-            type: 'string',
-          },
-        ],
-        from: '',
-        to: '',
-        nodeCount: 0,
-        summedNullAmount: 0,
-        fromRatio: 0,
-        toRatio: 0,
-      },
-    },
-    {
-      type: 'relation',
-      id: 'flights',
-      position: { x: 0, y: 0 },
-      data: {
-        width: 220,
-        height: 40,
-        collection: 'flights',
-        attributes: [
-          {
-            name: 'hallo',
-            type: 'string',
-          },
-        ],
-        from: '',
-        to: '',
-        nodeCount: 0,
-        summedNullAmount: 0,
-        fromRatio: 0,
-        toRatio: 0,
-      },
-    },
-    {
-      type: 'relation',
-      id: 'flights',
-      position: { x: 0, y: 0 },
-      data: {
-        width: 220,
-        height: 40,
-        collection: 'flights',
-        attributes: [
-          {
-            name: 'hallo',
-            type: 'string',
-          },
-        ],
-        from: '',
-        to: '',
-        nodeCount: 0,
-        summedNullAmount: 0,
-        fromRatio: 0,
-        toRatio: 0,
-      },
-    },
-    {
-      type: 'relation',
-      id: 'flights',
-      position: { x: 0, y: 0 },
-      data: {
-        width: 220,
-        height: 40,
-        collection: 'flights',
-        attributes: [
-          {
-            name: 'hallo',
-            type: 'string',
-          },
-        ],
-        from: '',
-        to: '',
-        nodeCount: 0,
-        summedNullAmount: 0,
-        fromRatio: 0,
-        toRatio: 0,
-      },
-    },
-    {
-      type: 'relation',
-      id: 'flights',
-      position: { x: 0, y: 0 },
-      data: {
-        width: 220,
-        height: 40,
-        collection: 'flights',
-        attributes: [
-          {
-            name: 'hallo',
-            type: 'string',
-          },
-        ],
-        from: '',
-        to: '',
-        nodeCount: 0,
-        summedNullAmount: 0,
-        fromRatio: 0,
-        toRatio: 0,
-      },
-    },
-    {
-      type: 'relation',
-      id: 'flights',
-      position: { x: 0, y: 0 },
-      data: {
-        width: 220,
-        height: 40,
-        collection: 'flights',
-        attributes: [
-          {
-            name: 'test',
-            type: 'string',
-          },
-        ],
-        from: '',
-        to: '',
-        nodeCount: 0,
-        summedNullAmount: 0,
-        fromRatio: 0,
-        toRatio: 0,
-      },
-    },
-  ];
-
-  const expectedEdgesInElements: Edge[] = [
-    {
-      id: 'flights',
-      source: 'Plane',
-      target: 'Airport',
-      type: 'nodeEdge',
-      label: 'Plane:Airport',
-      data: {
-        attributes: [],
-        d: 0,
-        created: false,
-        collection: 'flights',
-        edgeCount: 0,
-        view: anonymous,
-      },
-      arrowHeadType: ArrowHeadType.Arrow,
-      sourceHandle: 'entitySourceTop',
-      targetHandle: 'entityTargetBottom',
-    },
-    {
-      id: 'flights',
-      source: 'Airport2',
-      target: 'Airport',
-      type: 'nodeEdge',
-      label: 'Airport2:Airport',
-      data: {
-        attributes: [
-          { name: 'arrivalTime', type: 'int' },
-          { name: 'departureTime', type: 'int' },
-        ],
-        d: 40,
-        created: false,
-        collection: 'flights',
-        edgeCount: 0,
-        view: anonymous,
-      },
-      arrowHeadType: ArrowHeadType.Arrow,
-      sourceHandle: 'entitySourceRight',
-      targetHandle: 'entityTargetRight',
-    },
-    {
-      id: 'flights',
-      source: 'Thijs',
-      target: 'Airport',
-      type: 'nodeEdge',
-      label: 'Thijs:Airport',
-      data: {
-        attributes: [{ name: 'hallo', type: 'string' }],
-        d: 80,
-        created: false,
-        collection: 'flights',
-        edgeCount: 0,
-        view: anonymous,
-      },
-      arrowHeadType: ArrowHeadType.Arrow,
-      sourceHandle: 'entitySourceRight',
-      targetHandle: 'entityTargetRight',
-    },
-    {
-      id: 'flights',
-      source: 'Airport',
-      target: 'Staff',
-      type: 'nodeEdge',
-      label: 'Airport:Staff',
-      data: {
-        attributes: [{ name: 'salary', type: 'int' }],
-        d: -40,
-        created: false,
-        collection: 'flights',
-        edgeCount: 0,
-        view: anonymous,
-      },
-      arrowHeadType: ArrowHeadType.Arrow,
-      sourceHandle: 'entitySourceLeft',
-      targetHandle: 'entityTargetLeft',
-    },
-    {
-      id: 'flights',
-      source: 'Airport',
-      target: 'Thijs',
-      type: 'nodeEdge',
-      label: 'Airport:Thijs',
-      data: {
-        attributes: [{ name: 'hallo', type: 'string' }],
-        d: -80,
-        created: false,
-        collection: 'flights',
-        edgeCount: 0,
-        view: anonymous,
-      },
-      arrowHeadType: ArrowHeadType.Arrow,
-      sourceHandle: 'entitySourceLeft',
-      targetHandle: 'entityTargetLeft',
-    },
-    {
-      id: 'flights',
-      source: 'Staff',
-      target: 'Plane',
-      type: 'nodeEdge',
-      label: 'Staff:Plane',
-      data: {
-        attributes: [{ name: 'hallo', type: 'string' }],
-        d: 0,
-        created: false,
-        collection: 'flights',
-        edgeCount: 0,
-        view: anonymous,
-      },
-      arrowHeadType: ArrowHeadType.Arrow,
-      sourceHandle: 'entitySourceTop',
-      targetHandle: 'entityTargetBottom',
-    },
-    {
-      id: 'flights',
-      source: 'Airport2',
-      target: 'Plane',
-      type: 'nodeEdge',
-      label: 'Airport2:Plane',
-      data: {
-        attributes: [{ name: 'hallo', type: 'string' }],
-        d: 120,
-        created: false,
-        collection: 'flights',
-        edgeCount: 0,
-        view: anonymous,
-      },
-      arrowHeadType: ArrowHeadType.Arrow,
-      sourceHandle: 'entitySourceRight',
-      targetHandle: 'entityTargetRight',
-    },
-    {
-      id: 'flights',
-      source: 'Staff',
-      target: 'Airport2',
-      type: 'nodeEdge',
-      label: 'Staff:Airport2',
-      data: {
-        attributes: [{ name: 'hallo', type: 'string' }],
-        d: 0,
-        created: false,
-        collection: 'flights',
-        edgeCount: 0,
-        view: anonymous,
-      },
-      arrowHeadType: ArrowHeadType.Arrow,
-      sourceHandle: 'entitySourceBottom',
-      targetHandle: 'entityTargetTop',
-    },
-    {
-      id: 'flights',
-      source: 'Airport',
-      target: 'Airport',
-      type: 'selfEdge',
-      label: 'Airport:Airport',
-      data: {
-        attributes: [{ name: 'test', type: 'string' }],
-        d: 58,
-        created: false,
-        collection: 'flights',
-        edgeCount: 0,
-        view: anonymous,
-      },
-      arrowHeadType: ArrowHeadType.Arrow,
-      sourceHandle: 'entitySourceLeft',
-      targetHandle: 'entityTargetRight',
-    },
-  ];
-
-  const expectedAttributes: Node[] = [
-    {
-      type: 'attribute',
-      id: 'Airport:city',
-      position: { x: 0, y: 21 },
-      data: { name: 'city', datatype: 'string' },
-      isHidden: true,
-    },
-    {
-      type: 'attribute',
-      id: 'Airport:vip',
-      position: { x: 0, y: 41 },
-      data: { name: 'vip', datatype: 'bool' },
-      isHidden: true,
-    },
-    {
-      type: 'attribute',
-      id: 'Airport:state',
-      position: { x: 0, y: 61 },
-      data: { name: 'state', datatype: 'string' },
-      isHidden: true,
-    },
-    {
-      type: 'attribute',
-      id: 'Plane:type',
-      position: { x: 0, y: 171 },
-      data: { name: 'type', datatype: 'string' },
-      isHidden: true,
-    },
-    {
-      type: 'attribute',
-      id: 'Plane:maxFuelCapacity',
-      position: { x: 0, y: 191 },
-      data: { name: 'maxFuelCapacity', datatype: 'int' },
-      isHidden: true,
-    },
-    {
-      type: 'attribute',
-      id: 'Airport2:city',
-      position: { x: 0, y: 471 },
-      data: { name: 'city', datatype: 'string' },
-      isHidden: true,
-    },
-    {
-      type: 'attribute',
-      id: 'Airport2:vip',
-      position: { x: 0, y: 491 },
-      data: { name: 'vip', datatype: 'bool' },
-      isHidden: true,
-    },
-    {
-      type: 'attribute',
-      id: 'Airport2:state',
-      position: { x: 0, y: 511 },
-      data: { name: 'state', datatype: 'string' },
-      isHidden: true,
-    },
-  ];
-});
-
-/** Result nodes. */
-const nodes: Node[] = [
-  {
-    type: 'entity',
-    id: 'Thijs',
-    position: { x: 0, y: 0 },
-    data: { attributes: [] },
-  },
-  {
-    type: 'entity',
-    id: 'Airport',
-    position: { x: 0, y: 0 },
-    data: { attributes: [] },
-  },
-  {
-    type: 'entity',
-    id: 'Airport2',
-    position: { x: 0, y: 0 },
-    data: { attributes: [] },
-  },
-  {
-    type: 'entity',
-    id: 'Plane',
-    position: { x: 0, y: 0 },
-    data: { attributes: [] },
-  },
-  {
-    type: 'entity',
-    id: 'Staff',
-    position: { x: 0, y: 0 },
-    data: { attributes: [] },
-  },
-];
-
-/** Result links. */
-const edges: Edge[] = [
-  {
-    id: 'Airport2:Airport',
-    label: 'Airport2:Airport',
-    type: 'nodeEdge',
-    source: 'Airport2',
-    target: 'Airport',
-    arrowHeadType: ArrowHeadType.Arrow,
-    data: {
-      d: '',
-      attributes: [],
-    },
-  },
-  {
-    id: 'Airport:Staff',
-    label: 'Airport:Staff',
-    type: 'nodeEdge',
-    source: 'Airport',
-    target: 'Staff',
-    arrowHeadType: ArrowHeadType.Arrow,
-    data: { d: '', attributes: [] },
-  },
-  {
-    id: 'Plane:Airport',
-    label: 'Plane:Airport',
-    type: 'nodeEdge',
-    source: 'Plane',
-    target: 'Airport',
-    arrowHeadType: ArrowHeadType.Arrow,
-    data: { d: '', attributes: [] },
-  },
-  {
-    id: 'Airport:Thijs',
-    label: 'Airport:Thijs',
-    type: 'nodeEdge',
-    source: 'Airport',
-    target: 'Thijs',
-    arrowHeadType: ArrowHeadType.Arrow,
-    data: { d: '', attributes: [] },
-  },
-  {
-    id: 'Thijs:Airport',
-    label: 'Thijs:Airport',
-    type: 'nodeEdge',
-    source: 'Thijs',
-    target: 'Airport',
-    arrowHeadType: ArrowHeadType.Arrow,
-    data: { d: '', attributes: [] },
-  },
-  {
-    id: 'Staff:Plane',
-    label: 'Staff:Plane',
-    type: 'nodeEdge',
-    source: 'Staff',
-    target: 'Plane',
-    arrowHeadType: ArrowHeadType.Arrow,
-    data: { d: '', attributes: [] },
-  },
-  {
-    id: 'Staff:Airport2',
-    label: 'Staff:Airport2',
-    type: 'nodeEdge',
-    source: 'Staff',
-    target: 'Airport2',
-    arrowHeadType: ArrowHeadType.Arrow,
-    data: { d: '', attributes: [] },
-  },
-  {
-    id: 'Airport2:Plane',
-    label: 'Airport2:Plane',
-    type: 'nodeEdge',
-    source: 'Airport2',
-    target: 'Plane',
-    arrowHeadType: ArrowHeadType.Arrow,
-    data: { d: '', attributes: [] },
-  },
-];
+/**
+ * This program has been developed by students from the bachelor Computer Science at
+ * Utrecht University within the Software Project course.
+ * © Copyright Utrecht University (Department of Information and Computing Sciences)
+ */
+import {
+  schema,
+  schema2,
+} from '../../../data/mock-data/graph-schema/MockGraph';
+import {
+  mockAttributeDataNLEdge2,
+  mockAttributeDataNLEdge2IncorrectId,
+  mockAttributeDataNLNode1,
+  mockAttributeDataNLNode2,
+  mockAttributeDataNLNode2IncorrectId,
+} from '../../../data/mock-data/graph-schema/MockAttributeDataBatchedNL';
+import SecondChamberSchemaMock from '../../../data/mock-data/schema-result/2ndChamberSchemaMock';
+import GraphUseCase from '../../../domain/usecases/graph-schema/GraphUseCase';
+import SchemaViewModelImpl from './SchemaViewModel';
+import { Node, Edge, ArrowHeadType } from 'react-flow-renderer';
+import DrawOrderUseCase from '../../../domain/usecases/graph-schema/DrawOrderUseCase';
+import EdgeUseCase from '../../../domain/usecases/graph-schema/EdgeUseCase';
+import NodeUseCase from '../../../domain/usecases/graph-schema/NodeUseCase';
+import {
+  AttributeCategory,
+  BoundingBox,
+  NodeQualityDataForEntities,
+  NodeQualityDataForRelations,
+  NodeType,
+} from '../../../domain/entity/graph-schema/structures/Types';
+import {
+  Attribute,
+  AttributeData,
+} from '../../../domain/entity/graph-schema/structures/InputDataTypes';
+import mockQueryResult from '../../../data/mock-data/query-result/big2ndChamberQueryResult';
+import mockSchemaResult from '../../../data/mock-data/schema-result/2ndChamberSchemaMock';
+import Broker from '../../../domain/entity/broker/broker';
+
+jest.mock('../../view/graph-schema/SchemaStyleSheet');
+jest.mock('../../util/graph-schema/utils.tsx', () => {
+  return {
+    //TODO Is this already updated?
+    getWidthOfText: () => {
+      return 10;
+    },
+    calcWidthRelationNodeBox: () => {
+      return 75;
+    },
+    calcWidthEntityNodeBox: () => {
+      return 75;
+    },
+    makeBoundingBox: (x: number, y: number, width: number, height: number) => {
+      let boundingBox: BoundingBox;
+      boundingBox = {
+        topLeft: { x: x, y: y },
+        bottomRight: { x: x + width, y: y + height },
+      };
+      return boundingBox;
+    },
+    doBoxesOverlap: (firstBB: BoundingBox, secondBB: BoundingBox) => {
+      if (
+        firstBB.topLeft.x >= secondBB.bottomRight.x ||
+        secondBB.topLeft.x >= firstBB.bottomRight.x
+      )
+        return false;
+
+      if (
+        firstBB.topLeft.y >= secondBB.bottomRight.y ||
+        secondBB.topLeft.y >= firstBB.bottomRight.y
+      )
+        return false;
+
+      return true;
+    },
+  };
+});
+
+describe('schemaViewModelImpl', () => {
+  beforeEach(() => jest.resetModules());
+  const graphUseCase = new GraphUseCase();
+  const drawOrderUseCase = new DrawOrderUseCase();
+  const nodeUseCase = new NodeUseCase();
+  const edgeUseCase = new EdgeUseCase();
+  const attributesInQueryBuilder: any = [];
+  const addAttribute = (name: string, type: string) => {
+    attributesInQueryBuilder.push({ name: name, type: type });
+    return;
+  };
+
+  function anonymous(
+    attributes: Attribute[],
+    id: string,
+    hiddenAttributes: boolean
+  ): void {
+    //Empty methode.
+  }
+
+  it('should create a relation', () => {
+    const schemaViewModel = new SchemaViewModelImpl(
+      drawOrderUseCase,
+      nodeUseCase,
+      edgeUseCase,
+      graphUseCase,
+      addAttribute
+    );
+
+    const expectedrelationode = {
+      type: 'relation',
+      id: '5',
+      position: { x: -72.5, y: 20 },
+      data: {
+        width: 220,
+        height: 20,
+        collection: 'none',
+        attributes: [],
+        from: 'from:here',
+        to: 'to:here',
+        nodeCount: 0,
+        summedNullAmount: 0,
+        fromRatio: 0,
+        toRatio: 0,
+      },
+    };
+
+    schemaViewModel.createRelationNode(
+      '5',
+      [],
+      'none',
+      schemaViewModel.elements
+    );
+    schemaViewModel.setRelationNodePosition(
+      0,
+      0,
+      '5',
+      'from:here',
+      'to:here',
+      []
+    );
+    expect(JSON.stringify(schemaViewModel.elements.nodes[0])).toEqual(
+      JSON.stringify(expectedrelationode)
+    );
+
+    const expectedrelationode2 = {
+      type: 'relation',
+      id: '6',
+      position: { x: -72.5, y: 40 },
+      data: {
+        width: 220,
+        height: 20,
+        collection: 'none',
+        attributes: [],
+        from: 'from:hereagain',
+        to: 'to:hereagain',
+        nodeCount: 0,
+        summedNullAmount: 0,
+        fromRatio: 0,
+        toRatio: 0,
+      },
+    };
+    schemaViewModel.createRelationNode(
+      '6',
+      [],
+      'none',
+      schemaViewModel.elements
+    );
+    schemaViewModel.setRelationNodePosition(
+      0,
+      0,
+      '6',
+      'from:hereagain',
+      'to:hereagain',
+      []
+    );
+    expect(JSON.stringify(schemaViewModel.elements.nodes[1])).toEqual(
+      JSON.stringify(expectedrelationode2)
+    );
+  });
+
+  it('should console log that method is not implemented', () => {
+    const schemaViewModel = new SchemaViewModelImpl(
+      drawOrderUseCase,
+      nodeUseCase,
+      edgeUseCase,
+      graphUseCase,
+      addAttribute
+    );
+    const consoleSpy = jest.spyOn(console, 'log');
+    schemaViewModel.exportToPDF();
+    expect(consoleSpy).toHaveBeenCalledWith('Method not implemented.');
+  });
+
+  it('fitToView', () => {
+    const schemaViewModel = new SchemaViewModelImpl(
+      drawOrderUseCase,
+      nodeUseCase,
+      edgeUseCase,
+      graphUseCase,
+      addAttribute
+    );
+    const getWidthHeight = { width: 300, height: 700 };
+    const spy = jest.spyOn(schemaViewModel, 'getWidthHeight');
+    spy.mockReturnValue(getWidthHeight);
+
+    schemaViewModel.consumeMessageFromBackend(mockSchemaResult);
+    schemaViewModel.fitToView();
+
+    const consoleSpy = jest.spyOn(console, 'log');
+    expect(consoleSpy).toHaveBeenCalledWith(
+      'this.reactFlowInstance is undefined!'
+    );
+  });
+
+  /**
+   * These are the testcases for consuming messages from the backend
+   */
+  describe('consumeMessageFromBackend', () => {
+    it('should consume schema result only when subscribed to broker for schema_result', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      const mockConsumeMessages = jest.fn();
+      const message = 'test schema result';
+
+      schemaViewModel.consumeMessageFromBackend = mockConsumeMessages;
+
+      // should consume message for schema result when subscribed
+      schemaViewModel.subscribeToSchemaResult();
+      Broker.instance().publish(message, 'schema_result');
+      expect(mockConsumeMessages.mock.calls[0][0]).toEqual(message);
+      expect(mockConsumeMessages).toBeCalledTimes(1);
+
+      // should not consume message for query_result
+      Broker.instance().publish(message, 'query_result');
+      expect(mockConsumeMessages).toBeCalledTimes(1);
+
+      // should not consume message for schema result when unsubscribed
+      schemaViewModel.unSubscribeFromSchemaResult();
+      Broker.instance().publish(message, 'schema_result');
+      expect(mockConsumeMessages).toBeCalledTimes(1);
+    });
+
+    it('should consume attribute-data only when subscribed to broker for gsa_node_result & gsa_edge_result', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      const mockConsumeMessages = jest.fn();
+      const message = 'test analytics data';
+
+      schemaViewModel.consumeMessageFromBackend = mockConsumeMessages;
+
+      // should consume message for schema result when subscribed
+      schemaViewModel.subscribeToAnalyticsData();
+      Broker.instance().publish(message, 'gsa_node_result');
+      Broker.instance().publish(message, 'gsa_edge_result');
+      expect(mockConsumeMessages.mock.calls[0][0]).toEqual(message);
+      expect(mockConsumeMessages).toBeCalledTimes(2);
+
+      // should not consume message for schema result when unsubscribed
+      schemaViewModel.unSubscribeFromAnalyticsData();
+      Broker.instance().publish(message, 'schema_result');
+      Broker.instance().publish(message, 'gsa_node_result');
+      Broker.instance().publish(message, 'gsa_edge_result');
+      expect(mockConsumeMessages).toBeCalledTimes(2);
+    });
+
+    //TODO: also test the message for the analytics
+    it('should console log and should not change any elements when receiving unrelated messages', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      const consoleSpy = jest.spyOn(console, 'log');
+
+      expect(schemaViewModel.visible).toEqual(true);
+      schemaViewModel.consumeMessageFromBackend(mockQueryResult);
+      expect(consoleSpy).toHaveBeenCalledWith('This is no valid input!');
+
+      expect(schemaViewModel.visible).toEqual(true);
+      expect(schemaViewModel.elements.nodes).toEqual([]);
+      expect(schemaViewModel.elements.edges).toEqual([]);
+    });
+  });
+
+  /**
+   * These are the testcases for the attribute-analytics popup menu
+   */
+  describe('AttributeAnalyticsPopupMenu', () => {
+    it('should throw error that given id does not exist', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      schemaViewModel.consumeMessageFromBackend(schema);
+      // Nodes that are not in elements cannot have popups
+      const name = 'Edje';
+      expect(
+        schemaViewModel.elements.nodes.find((node) => node.id == name)
+      ).toEqual(undefined);
+      expect(() =>
+        schemaViewModel.toggleAttributeAnalyticsPopupMenu(name)
+      ).toThrowError(
+        'Node ' + name + ' does not exist therefore no popup menu can be shown.'
+      );
+    });
+
+    it('should show the attribute-analytics popup menu', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      const popup = schemaViewModel.attributeAnalyticsPopupMenu;
+      schemaViewModel.consumeMessageFromBackend(schema);
+
+      // test for entity node
+      const node = schemaViewModel.elements.nodes[0];
+      schemaViewModel.toggleAttributeAnalyticsPopupMenu(node.id);
+      expect(popup.isHidden).toEqual(false);
+      expect(popup.nodeID).toEqual(node.id);
+
+      // test for relation node
+      const edge = schemaViewModel.elements.edges[0];
+      schemaViewModel.setRelationNodePosition(
+        0,
+        0,
+        edge.id,
+        edge.source,
+        edge.target,
+        edge.data.attributes
+      );
+      schemaViewModel.toggleAttributeAnalyticsPopupMenu(edge.id);
+      expect(popup.isHidden).toEqual(false);
+      expect(popup.nodeID).toEqual(edge.id);
+    });
+
+    it('should hide the attribute-analytics popup menu as it was already open', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      schemaViewModel.consumeMessageFromBackend(schema);
+      schemaViewModel.attributeAnalyticsPopupMenu.nodeID = 'Thijs';
+      schemaViewModel.attributeAnalyticsPopupMenu.isHidden = false;
+      schemaViewModel.toggleAttributeAnalyticsPopupMenu('Thijs');
+      expect(schemaViewModel.attributeAnalyticsPopupMenu.isHidden).toEqual(
+        true
+      );
+    });
+
+    it('should close the attribute-analytics menu', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      schemaViewModel.consumeMessageFromBackend(schema);
+      schemaViewModel.attributeAnalyticsData['Thijs'].onClickCloseButton();
+      expect(schemaViewModel.attributeAnalyticsPopupMenu.isHidden).toEqual(
+        true
+      );
+    });
+
+    it('should make an empty attribute-analytics popupmenu', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      schemaViewModel.attributeAnalyticsPopupMenu =
+        schemaViewModel.emptyAttributeAnalyticsPopupMenuNode();
+      expect(schemaViewModel.attributeAnalyticsPopupMenu.id).toEqual(
+        'attributeAnalyticsPopupMenu'
+      );
+      expect(schemaViewModel.attributeAnalyticsPopupMenu.nodeID).toEqual('');
+      expect(schemaViewModel.attributeAnalyticsPopupMenu.data.nodeType).toEqual(
+        NodeType.relation
+      );
+      expect(
+        schemaViewModel.attributeAnalyticsPopupMenu.data.attributes
+      ).toEqual([]);
+      expect(
+        schemaViewModel.attributeAnalyticsPopupMenu.data.isAttributeDataIn
+      ).toEqual(false);
+    });
+
+    it('should place an attribute node in the querybuilder', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      schemaViewModel.consumeMessageFromBackend(schema);
+      const node = schemaViewModel.elements.nodes[0];
+      const attribute = node.data.attributes;
+      schemaViewModel.attributeAnalyticsData[
+        node.id
+      ].onClickPlaceInQueryBuilderButton(attribute.name, attribute.type);
+      expect(attributesInQueryBuilder[0]).toEqual({
+        name: attribute.name,
+        type: attribute.type,
+      });
+    });
+
+    it('should have a working searchbar', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      schemaViewModel.consumeMessageFromBackend(mockSchemaResult);
+      schemaViewModel.searchForAttributes('kamerleden', 'aa');
+
+      let attributes =
+        schemaViewModel.attributeAnalyticsPopupMenu.data.attributes;
+      expect(attributes.length).toEqual(2);
+
+      schemaViewModel.searchForAttributes('kamerleden', '');
+      attributes = schemaViewModel.attributeAnalyticsPopupMenu.data.attributes;
+      expect(attributes.length).toEqual(6);
+    });
+
+    it('should have working filters', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      schemaViewModel.consumeMessageFromBackend(mockSchemaResult);
+      schemaViewModel.consumeMessageFromBackend(mockAttributeDataNLNode1);
+
+      schemaViewModel.applyAttributeFilters(
+        'kamerleden',
+        AttributeCategory.categorical,
+        'Bigger',
+        -1
+      );
+      let attributes =
+        schemaViewModel.attributeAnalyticsPopupMenu.data.attributes;
+      expect(attributes.length).toEqual(1);
+
+      schemaViewModel.resetAttributeFilters('kamerleden');
+      attributes = schemaViewModel.attributeAnalyticsPopupMenu.data.attributes;
+      expect(attributes.length).toEqual(6);
+    });
+  });
+
+  /**
+   * These are the testcases for the node-quality popup menu
+   */
+  describe('nodeQualityPopup', () => {
+    it('should throw error that given id does not exist', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      schemaViewModel.consumeMessageFromBackend(schema);
+      expect(() => schemaViewModel.toggleNodeQualityPopup('Edje')).toThrowError(
+        'Node does not exist therefore no popup can be shown.'
+      );
+    });
+
+    it('should show the node-quality popup menu for an entity node', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      schemaViewModel.consumeMessageFromBackend(SecondChamberSchemaMock);
+      schemaViewModel.toggleNodeQualityPopup('commissies');
+      expect(schemaViewModel.nodeQualityPopup.isHidden).toEqual(false);
+      expect(schemaViewModel.nodeQualityPopup.type).toEqual(
+        'nodeQualityEntityPopup'
+      );
+      expect(schemaViewModel.nodeQualityPopup.nodeID).toEqual('commissies');
+    });
+
+    it('should show the node-quality popup menu for a relation node', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      schemaViewModel.createRelationNode(
+        '5',
+        [],
+        '5',
+        schemaViewModel.elements
+      );
+      schemaViewModel.toggleNodeQualityPopup('5');
+      expect(schemaViewModel.nodeQualityPopup.isHidden).toEqual(false);
+      expect(schemaViewModel.nodeQualityPopup.type).toEqual(
+        'nodeQualityRelationPopup'
+      );
+      expect(schemaViewModel.nodeQualityPopup.nodeID).toEqual('5');
+    });
+
+    it('should hide the node-quality popup menu as it was already open', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      schemaViewModel.consumeMessageFromBackend(schema);
+      schemaViewModel.nodeQualityPopup.nodeID = 'Thijs';
+      schemaViewModel.nodeQualityPopup.isHidden = false;
+      schemaViewModel.toggleNodeQualityPopup('Thijs');
+      expect(schemaViewModel.nodeQualityPopup.isHidden).toEqual(true);
+    });
+
+    it('should close the node-quality menu', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      schemaViewModel.consumeMessageFromBackend(schema);
+      schemaViewModel.nodeQualityData['Thijs'].onClickCloseButton();
+      expect(schemaViewModel.nodeQualityPopup.isHidden).toEqual(true);
+    });
+
+    it('should make an empty node-quality popupmenu', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      schemaViewModel.nodeQualityPopup =
+        schemaViewModel.emptyNodeQualityPopupNode();
+      expect(schemaViewModel.nodeQualityPopup.id).toEqual('nodeQualityPopup');
+      expect(schemaViewModel.nodeQualityPopup.nodeID).toEqual('');
+      expect(schemaViewModel.nodeQualityPopup.data.nodeCount).toEqual(0);
+      expect(schemaViewModel.nodeQualityPopup.data.attributeNullCount).toEqual(
+        0
+      );
+      expect(schemaViewModel.nodeQualityPopup.data.isAttributeDataIn).toEqual(
+        false
+      );
+    });
+  });
+
+  describe('AttributeData', () => {
+    it('should process the incoming data correctly for node-data', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      schemaViewModel.consumeMessageFromBackend(mockSchemaResult);
+      schemaViewModel.consumeMessageFromBackend(mockAttributeDataNLNode2);
+
+      const attributeAnalyticsData =
+        schemaViewModel.attributeAnalyticsData['commissies'];
+      const nodeQualityData = schemaViewModel.nodeQualityData[
+        'commissies'
+      ] as NodeQualityDataForEntities;
+      expect(attributeAnalyticsData.isAttributeDataIn).toEqual(true);
+      expect(attributeAnalyticsData.attributes[0].category).toEqual(
+        AttributeCategory.other
+      );
+      expect(attributeAnalyticsData.attributes[0].nullAmount).toEqual(1);
+      expect(nodeQualityData.nodeCount).toEqual(38);
+      expect(nodeQualityData.attributeNullCount).toEqual(1);
+      expect(nodeQualityData.notConnectedNodeCount).toEqual(0.03);
+    });
+
+    it('should process the incoming data correctly for edge-data', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      schemaViewModel.consumeMessageFromBackend(mockSchemaResult);
+      schemaViewModel.consumeMessageFromBackend(mockAttributeDataNLEdge2);
+
+      const attributeAnalyticsData =
+        schemaViewModel.attributeAnalyticsData['lid_van'];
+      const nodeQualityData = schemaViewModel.nodeQualityData[
+        'lid_van'
+      ] as NodeQualityDataForRelations;
+      expect(attributeAnalyticsData.isAttributeDataIn).toEqual(true);
+      expect(attributeAnalyticsData.attributes[0].category).toEqual(
+        AttributeCategory.categorical
+      );
+      expect(attributeAnalyticsData.attributes[0].nullAmount).toEqual(0);
+      expect(nodeQualityData.nodeCount).toEqual(149);
+      expect(nodeQualityData.attributeNullCount).toEqual(0);
+      expect(nodeQualityData.fromRatio).toEqual(1);
+      expect(nodeQualityData.toRatio).toEqual(1);
+    });
+
+    it('should console log when the given data has no corresponding id for entities', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      schemaViewModel.consumeMessageFromBackend(mockSchemaResult);
+
+      const consoleSpy = jest.spyOn(console, 'log');
+      schemaViewModel.consumeMessageFromBackend(
+        mockAttributeDataNLNode2IncorrectId
+      );
+
+      expect(consoleSpy).toBeCalledTimes(2);
+    });
+
+    it('should console log when the given data has no corresponding id for relations', () => {
+      const schemaViewModel = new SchemaViewModelImpl(
+        drawOrderUseCase,
+        nodeUseCase,
+        edgeUseCase,
+        graphUseCase,
+        addAttribute
+      );
+      schemaViewModel.consumeMessageFromBackend(mockSchemaResult);
+
+      const consoleSpy = jest.spyOn(console, 'log');
+      schemaViewModel.consumeMessageFromBackend(
+        mockAttributeDataNLEdge2IncorrectId
+      );
+
+      expect(consoleSpy).toBeCalledTimes(2);
+    });
+  });
+
+  /** expected results */
+
+  const expectedNodesInElements: Node[] = [
+    {
+      type: QueryElementTypes.Entity,
+      id: 'Airport',
+      position: { x: 0, y: 0 },
+      data: {
+        attributes: [
+          { name: 'city', type: 'string' },
+          { name: 'vip', type: 'bool' },
+          { name: 'state', type: 'string' },
+        ],
+        handles: [
+          'entityTargetBottom',
+          'entityTargetRight',
+          'entityTargetRight',
+          'entitySourceLeft',
+          'entitySourceLeft',
+          'entitySourceLeft',
+          'entityTargetRight',
+        ],
+        width: 165,
+        height: 20,
+        nodeCount: 0,
+        summedNullAmount: 0,
+        connectedRatio: 0,
+      },
+    },
+    {
+      type: QueryElementTypes.Entity,
+      id: 'Plane',
+      position: { x: 0, y: 150 },
+      data: {
+        attributes: [
+          { name: 'type', type: 'string' },
+          { name: 'maxFuelCapacity', type: 'int' },
+        ],
+        handles: ['entitySourceTop', 'entityTargetBottom', 'entityTargetRight'],
+        width: 165,
+        height: 20,
+        nodeCount: 0,
+        summedNullAmount: 0,
+        connectedRatio: 0,
+      },
+    },
+    {
+      type: QueryElementTypes.Entity,
+      id: 'Staff',
+      position: { x: 0, y: 300 },
+      data: {
+        attributes: [],
+        handles: ['entityTargetLeft', 'entitySourceTop', 'entitySourceBottom'],
+        width: 165,
+        height: 20,
+        nodeCount: 0,
+        summedNullAmount: 0,
+        connectedRatio: 0,
+      },
+    },
+    {
+      type: QueryElementTypes.Entity,
+      id: 'Airport2',
+      position: { x: 0, y: 450 },
+      data: {
+        attributes: [
+          { name: 'city', type: 'string' },
+          { name: 'vip', type: 'bool' },
+          { name: 'state', type: 'string' },
+        ],
+        handles: ['entitySourceRight', 'entitySourceRight', 'entityTargetTop'],
+        width: 165,
+        height: 20,
+        nodeCount: 0,
+        summedNullAmount: 0,
+        connectedRatio: 0,
+      },
+    },
+
+    {
+      type: QueryElementTypes.Entity,
+      id: 'Thijs',
+      position: { x: 0, y: 600 },
+      data: {
+        attributes: [],
+        handles: ['entitySourceRight', 'entityTargetLeft'],
+        width: 165,
+        height: 20,
+        nodeCount: 0,
+        summedNullAmount: 0,
+        connectedRatio: 0,
+      },
+    },
+
+    {
+      type: QueryElementTypes.Entity,
+      id: 'Unconnected',
+      position: { x: 0, y: 750 },
+      data: {
+        attributes: [],
+        handles: [],
+        width: 165,
+        height: 20,
+        nodeCount: 0,
+        summedNullAmount: 0,
+        connectedRatio: 0,
+      },
+    },
+    {
+      type: 'relation',
+      id: 'flights',
+      position: { x: 0, y: 0 },
+      data: {
+        width: 220,
+        height: 40,
+        collection: 'flights',
+        attributes: [
+          {
+            name: 'arrivalTime',
+            type: 'int',
+          },
+          {
+            name: 'departureTime',
+            type: 'int',
+          },
+        ],
+        from: '',
+        to: '',
+        nodeCount: 0,
+        summedNullAmount: 0,
+        fromRatio: 0,
+        toRatio: 0,
+      },
+    },
+    {
+      type: 'relation',
+      id: 'flights',
+      position: { x: 0, y: 0 },
+      data: {
+        width: 220,
+        height: 40,
+        collection: 'flights',
+        attributes: [
+          {
+            name: 'salary',
+            type: 'int',
+          },
+        ],
+        from: '',
+        to: '',
+        nodeCount: 0,
+        summedNullAmount: 0,
+        fromRatio: 0,
+        toRatio: 0,
+      },
+    },
+    {
+      type: 'relation',
+      id: 'flights',
+      position: { x: 0, y: 0 },
+      data: {
+        width: 220,
+        height: 40,
+        collection: 'flights',
+        attributes: [],
+        from: '',
+        to: '',
+        nodeCount: 0,
+        summedNullAmount: 0,
+        fromRatio: 0,
+        toRatio: 0,
+      },
+    },
+    {
+      type: 'relation',
+      id: 'flights',
+      position: { x: 0, y: 0 },
+      data: {
+        width: 220,
+        height: 40,
+        collection: 'flights',
+        attributes: [
+          {
+            name: 'hallo',
+            type: 'string',
+          },
+        ],
+        from: '',
+        to: '',
+        nodeCount: 0,
+        summedNullAmount: 0,
+        fromRatio: 0,
+        toRatio: 0,
+      },
+    },
+    {
+      type: 'relation',
+      id: 'flights',
+      position: { x: 0, y: 0 },
+      data: {
+        width: 220,
+        height: 40,
+        collection: 'flights',
+        attributes: [
+          {
+            name: 'hallo',
+            type: 'string',
+          },
+        ],
+        from: '',
+        to: '',
+        nodeCount: 0,
+        summedNullAmount: 0,
+        fromRatio: 0,
+        toRatio: 0,
+      },
+    },
+    {
+      type: 'relation',
+      id: 'flights',
+      position: { x: 0, y: 0 },
+      data: {
+        width: 220,
+        height: 40,
+        collection: 'flights',
+        attributes: [
+          {
+            name: 'hallo',
+            type: 'string',
+          },
+        ],
+        from: '',
+        to: '',
+        nodeCount: 0,
+        summedNullAmount: 0,
+        fromRatio: 0,
+        toRatio: 0,
+      },
+    },
+    {
+      type: 'relation',
+      id: 'flights',
+      position: { x: 0, y: 0 },
+      data: {
+        width: 220,
+        height: 40,
+        collection: 'flights',
+        attributes: [
+          {
+            name: 'hallo',
+            type: 'string',
+          },
+        ],
+        from: '',
+        to: '',
+        nodeCount: 0,
+        summedNullAmount: 0,
+        fromRatio: 0,
+        toRatio: 0,
+      },
+    },
+    {
+      type: 'relation',
+      id: 'flights',
+      position: { x: 0, y: 0 },
+      data: {
+        width: 220,
+        height: 40,
+        collection: 'flights',
+        attributes: [
+          {
+            name: 'hallo',
+            type: 'string',
+          },
+        ],
+        from: '',
+        to: '',
+        nodeCount: 0,
+        summedNullAmount: 0,
+        fromRatio: 0,
+        toRatio: 0,
+      },
+    },
+    {
+      type: 'relation',
+      id: 'flights',
+      position: { x: 0, y: 0 },
+      data: {
+        width: 220,
+        height: 40,
+        collection: 'flights',
+        attributes: [
+          {
+            name: 'test',
+            type: 'string',
+          },
+        ],
+        from: '',
+        to: '',
+        nodeCount: 0,
+        summedNullAmount: 0,
+        fromRatio: 0,
+        toRatio: 0,
+      },
+    },
+  ];
+
+  const expectedEdgesInElements: Edge[] = [
+    {
+      id: 'flights',
+      source: 'Plane',
+      target: 'Airport',
+      type: 'nodeEdge',
+      label: 'Plane:Airport',
+      data: {
+        attributes: [],
+        d: 0,
+        created: false,
+        collection: 'flights',
+        edgeCount: 0,
+        view: anonymous,
+      },
+      arrowHeadType: ArrowHeadType.Arrow,
+      sourceHandle: 'entitySourceTop',
+      targetHandle: 'entityTargetBottom',
+    },
+    {
+      id: 'flights',
+      source: 'Airport2',
+      target: 'Airport',
+      type: 'nodeEdge',
+      label: 'Airport2:Airport',
+      data: {
+        attributes: [
+          { name: 'arrivalTime', type: 'int' },
+          { name: 'departureTime', type: 'int' },
+        ],
+        d: 40,
+        created: false,
+        collection: 'flights',
+        edgeCount: 0,
+        view: anonymous,
+      },
+      arrowHeadType: ArrowHeadType.Arrow,
+      sourceHandle: 'entitySourceRight',
+      targetHandle: 'entityTargetRight',
+    },
+    {
+      id: 'flights',
+      source: 'Thijs',
+      target: 'Airport',
+      type: 'nodeEdge',
+      label: 'Thijs:Airport',
+      data: {
+        attributes: [{ name: 'hallo', type: 'string' }],
+        d: 80,
+        created: false,
+        collection: 'flights',
+        edgeCount: 0,
+        view: anonymous,
+      },
+      arrowHeadType: ArrowHeadType.Arrow,
+      sourceHandle: 'entitySourceRight',
+      targetHandle: 'entityTargetRight',
+    },
+    {
+      id: 'flights',
+      source: 'Airport',
+      target: 'Staff',
+      type: 'nodeEdge',
+      label: 'Airport:Staff',
+      data: {
+        attributes: [{ name: 'salary', type: 'int' }],
+        d: -40,
+        created: false,
+        collection: 'flights',
+        edgeCount: 0,
+        view: anonymous,
+      },
+      arrowHeadType: ArrowHeadType.Arrow,
+      sourceHandle: 'entitySourceLeft',
+      targetHandle: 'entityTargetLeft',
+    },
+    {
+      id: 'flights',
+      source: 'Airport',
+      target: 'Thijs',
+      type: 'nodeEdge',
+      label: 'Airport:Thijs',
+      data: {
+        attributes: [{ name: 'hallo', type: 'string' }],
+        d: -80,
+        created: false,
+        collection: 'flights',
+        edgeCount: 0,
+        view: anonymous,
+      },
+      arrowHeadType: ArrowHeadType.Arrow,
+      sourceHandle: 'entitySourceLeft',
+      targetHandle: 'entityTargetLeft',
+    },
+    {
+      id: 'flights',
+      source: 'Staff',
+      target: 'Plane',
+      type: 'nodeEdge',
+      label: 'Staff:Plane',
+      data: {
+        attributes: [{ name: 'hallo', type: 'string' }],
+        d: 0,
+        created: false,
+        collection: 'flights',
+        edgeCount: 0,
+        view: anonymous,
+      },
+      arrowHeadType: ArrowHeadType.Arrow,
+      sourceHandle: 'entitySourceTop',
+      targetHandle: 'entityTargetBottom',
+    },
+    {
+      id: 'flights',
+      source: 'Airport2',
+      target: 'Plane',
+      type: 'nodeEdge',
+      label: 'Airport2:Plane',
+      data: {
+        attributes: [{ name: 'hallo', type: 'string' }],
+        d: 120,
+        created: false,
+        collection: 'flights',
+        edgeCount: 0,
+        view: anonymous,
+      },
+      arrowHeadType: ArrowHeadType.Arrow,
+      sourceHandle: 'entitySourceRight',
+      targetHandle: 'entityTargetRight',
+    },
+    {
+      id: 'flights',
+      source: 'Staff',
+      target: 'Airport2',
+      type: 'nodeEdge',
+      label: 'Staff:Airport2',
+      data: {
+        attributes: [{ name: 'hallo', type: 'string' }],
+        d: 0,
+        created: false,
+        collection: 'flights',
+        edgeCount: 0,
+        view: anonymous,
+      },
+      arrowHeadType: ArrowHeadType.Arrow,
+      sourceHandle: 'entitySourceBottom',
+      targetHandle: 'entityTargetTop',
+    },
+    {
+      id: 'flights',
+      source: 'Airport',
+      target: 'Airport',
+      type: 'selfEdge',
+      label: 'Airport:Airport',
+      data: {
+        attributes: [{ name: 'test', type: 'string' }],
+        d: 58,
+        created: false,
+        collection: 'flights',
+        edgeCount: 0,
+        view: anonymous,
+      },
+      arrowHeadType: ArrowHeadType.Arrow,
+      sourceHandle: 'entitySourceLeft',
+      targetHandle: 'entityTargetRight',
+    },
+  ];
+
+  const expectedAttributes: Node[] = [
+    {
+      type: 'attribute',
+      id: 'Airport:city',
+      position: { x: 0, y: 21 },
+      data: { name: 'city', datatype: 'string' },
+      isHidden: true,
+    },
+    {
+      type: 'attribute',
+      id: 'Airport:vip',
+      position: { x: 0, y: 41 },
+      data: { name: 'vip', datatype: 'bool' },
+      isHidden: true,
+    },
+    {
+      type: 'attribute',
+      id: 'Airport:state',
+      position: { x: 0, y: 61 },
+      data: { name: 'state', datatype: 'string' },
+      isHidden: true,
+    },
+    {
+      type: 'attribute',
+      id: 'Plane:type',
+      position: { x: 0, y: 171 },
+      data: { name: 'type', datatype: 'string' },
+      isHidden: true,
+    },
+    {
+      type: 'attribute',
+      id: 'Plane:maxFuelCapacity',
+      position: { x: 0, y: 191 },
+      data: { name: 'maxFuelCapacity', datatype: 'int' },
+      isHidden: true,
+    },
+    {
+      type: 'attribute',
+      id: 'Airport2:city',
+      position: { x: 0, y: 471 },
+      data: { name: 'city', datatype: 'string' },
+      isHidden: true,
+    },
+    {
+      type: 'attribute',
+      id: 'Airport2:vip',
+      position: { x: 0, y: 491 },
+      data: { name: 'vip', datatype: 'bool' },
+      isHidden: true,
+    },
+    {
+      type: 'attribute',
+      id: 'Airport2:state',
+      position: { x: 0, y: 511 },
+      data: { name: 'state', datatype: 'string' },
+      isHidden: true,
+    },
+  ];
+});
+
+/** Result nodes. */
+const nodes: Node[] = [
+  {
+    type: QueryElementTypes.Entity,
+    id: 'Thijs',
+    position: { x: 0, y: 0 },
+    data: { attributes: [] },
+  },
+  {
+    type: QueryElementTypes.Entity,
+    id: 'Airport',
+    position: { x: 0, y: 0 },
+    data: { attributes: [] },
+  },
+  {
+    type: QueryElementTypes.Entity,
+    id: 'Airport2',
+    position: { x: 0, y: 0 },
+    data: { attributes: [] },
+  },
+  {
+    type: QueryElementTypes.Entity,
+    id: 'Plane',
+    position: { x: 0, y: 0 },
+    data: { attributes: [] },
+  },
+  {
+    type: QueryElementTypes.Entity,
+    id: 'Staff',
+    position: { x: 0, y: 0 },
+    data: { attributes: [] },
+  },
+];
+
+/** Result links. */
+const edges: Edge[] = [
+  {
+    id: 'Airport2:Airport',
+    label: 'Airport2:Airport',
+    type: 'nodeEdge',
+    source: 'Airport2',
+    target: 'Airport',
+    arrowHeadType: ArrowHeadType.Arrow,
+    data: {
+      d: '',
+      attributes: [],
+    },
+  },
+  {
+    id: 'Airport:Staff',
+    label: 'Airport:Staff',
+    type: 'nodeEdge',
+    source: 'Airport',
+    target: 'Staff',
+    arrowHeadType: ArrowHeadType.Arrow,
+    data: { d: '', attributes: [] },
+  },
+  {
+    id: 'Plane:Airport',
+    label: 'Plane:Airport',
+    type: 'nodeEdge',
+    source: 'Plane',
+    target: 'Airport',
+    arrowHeadType: ArrowHeadType.Arrow,
+    data: { d: '', attributes: [] },
+  },
+  {
+    id: 'Airport:Thijs',
+    label: 'Airport:Thijs',
+    type: 'nodeEdge',
+    source: 'Airport',
+    target: 'Thijs',
+    arrowHeadType: ArrowHeadType.Arrow,
+    data: { d: '', attributes: [] },
+  },
+  {
+    id: 'Thijs:Airport',
+    label: 'Thijs:Airport',
+    type: 'nodeEdge',
+    source: 'Thijs',
+    target: 'Airport',
+    arrowHeadType: ArrowHeadType.Arrow,
+    data: { d: '', attributes: [] },
+  },
+  {
+    id: 'Staff:Plane',
+    label: 'Staff:Plane',
+    type: 'nodeEdge',
+    source: 'Staff',
+    target: 'Plane',
+    arrowHeadType: ArrowHeadType.Arrow,
+    data: { d: '', attributes: [] },
+  },
+  {
+    id: 'Staff:Airport2',
+    label: 'Staff:Airport2',
+    type: 'nodeEdge',
+    source: 'Staff',
+    target: 'Airport2',
+    arrowHeadType: ArrowHeadType.Arrow,
+    data: { d: '', attributes: [] },
+  },
+  {
+    id: 'Airport2:Plane',
+    label: 'Airport2:Plane',
+    type: 'nodeEdge',
+    source: 'Airport2',
+    target: 'Plane',
+    arrowHeadType: ArrowHeadType.Arrow,
+    data: { d: '', attributes: [] },
+  },
+];
diff --git a/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx b/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx
index 1bdbb7dba..49d6a6500 100644
--- a/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx
+++ b/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx
@@ -14,6 +14,7 @@ import styles from './entity.module.scss';
 import { calcWidthEntityNodeBox, calculateAttributeQuality, calculateEntityQuality } from '@graphpolaris/shared/lib/schema/schema-utils';
 import { useTheme } from '@mui/material';
 import { SchemaReactflowNodeWithFunctions } from '../../../model/reactflow';
+import { QueryElementTypes } from '@graphpolaris/shared/lib/querybuilder';
 
 /**
  * EntityNode is the node that represents the database entities.
@@ -32,8 +33,9 @@ export const EntityNode = React.memo(({ id, data }: NodeProps<SchemaReactflowNod
    * @param event React Mouse drag event
    */
   const onDragStart = (event: React.DragEvent<HTMLDivElement>) => {
+    // console.log('dragging entiry', id, data);
     // console.log(id, data);
-    event.dataTransfer.setData('application/reactflow', JSON.stringify({ type: 'entity', name: id }));
+    event.dataTransfer.setData('application/reactflow', JSON.stringify({ type: QueryElementTypes.Entity, name: id }));
     event.dataTransfer.effectAllowed = 'move';
   };
 
diff --git a/libs/shared/lib/schema/pills/nodes/relation/relation-node.tsx b/libs/shared/lib/schema/pills/nodes/relation/relation-node.tsx
index 2aafdd016..6eab676f8 100644
--- a/libs/shared/lib/schema/pills/nodes/relation/relation-node.tsx
+++ b/libs/shared/lib/schema/pills/nodes/relation/relation-node.tsx
@@ -20,6 +20,7 @@ import {
 } from '@graphpolaris/shared/lib/schema/schema-utils';
 import { useTheme } from '@mui/material';
 import { SchemaReactflowRelation, SchemaReactflowRelationWithFunctions } from '../../../model/reactflow';
+import { QueryElementTypes } from '@graphpolaris/shared/lib/querybuilder';
 
 /**
  * Relation node component that renders a relation node for the schema.
@@ -36,15 +37,16 @@ export const RelationNode = React.memo(({ id, data }: NodeProps<SchemaReactflowR
    * @param event React Mouse drag event.
    */
   const onDragStart = (event: React.DragEvent<HTMLDivElement>) => {
-    // console.log(id, data);
+    console.log('dragging relation', id, data);
     event.dataTransfer.setData(
       'application/reactflow',
       JSON.stringify({
-        type: 'relation',
+        type: QueryElementTypes.Relation,
         name: id, //TODO id?
         from: data.from,
         to: data.to,
         collection: data.collection,
+        label: data.label,
       })
     );
     event.dataTransfer.effectAllowed = 'move';
diff --git a/libs/shared/lib/schema/schema-utils/schema-usecases.spec.ts b/libs/shared/lib/schema/schema-utils/schema-usecases.spec.ts
index 42c767efd..15a3cbb72 100644
--- a/libs/shared/lib/schema/schema-utils/schema-usecases.spec.ts
+++ b/libs/shared/lib/schema/schema-utils/schema-usecases.spec.ts
@@ -1,7 +1,5 @@
-import { assert, describe, expect, it, test } from 'vitest';
+import { describe, expect, it, test } from 'vitest';
 import { SchemaUtils } from '@graphpolaris/shared/lib/schema/schema-utils';
-import { MultiGraph } from 'graphology';
-import { Attributes } from 'graphology-types';
 import { movieSchemaRaw, northwindSchemaRaw, simpleSchemaRaw, twitterSchemaRaw } from '@graphpolaris/shared/lib/mock-data';
 import { SchemaFromBackend, SchemaGraphology } from '../model';
 
diff --git a/libs/shared/lib/schema/schema-utils/schema-usecases.ts b/libs/shared/lib/schema/schema-utils/schema-usecases.ts
index 3bea392e2..780870b8b 100644
--- a/libs/shared/lib/schema/schema-utils/schema-usecases.ts
+++ b/libs/shared/lib/schema/schema-utils/schema-usecases.ts
@@ -3,6 +3,7 @@ import Graph from 'graphology';
 import { Attributes } from 'graphology-types';
 import { MarkerType, Edge, Node } from 'reactflow';
 import { SchemaReactflowNode } from '../model/reactflow';
+import { QueryElementTypes } from '../../querybuilder';
 
 //TODO does not belong here; maybe should go into the GraphPolarisThemeProvider
 const ANIMATEDEDGES = false;
@@ -12,7 +13,7 @@ export function schemaExpandRelation(graph: Graph): Graph {
 
   newGraph.forEachNode((node, attributes) => {
     // console.log(node, attributes);
-    newGraph.mergeNodeAttributes(node, { type: 'entity' });
+    newGraph.mergeNodeAttributes(node, { type: QueryElementTypes.Entity });
   });
 
   //makeNewRelationNodes
@@ -30,7 +31,7 @@ export function schemaExpandRelation(graph: Graph): Graph {
       ...attributes,
       x: 0,
       y: 0,
-      type: 'relation',
+      type: QueryElementTypes.Relation,
     });
 
     const id = 'RelationEdge' + source + '->' + newID;
@@ -117,7 +118,7 @@ export function createReactFlowEdges(graph: Graph): Array<Edge> {
 //         name: edge,
 //       },
 //       position: { x: attributes.x, y: attributes.y },
-//       type: 'relation',
+//       type: QueryElementTypes.Relation,
 //     };
 //     nodeElements.push(newRelationNode);
 //   });
diff --git a/libs/shared/lib/vis/nodelink/NodeLinkViewModel.tsx b/libs/shared/lib/vis/nodelink/NodeLinkViewModel.tsx
index c1548f3c0..fa2a2ffc8 100644
--- a/libs/shared/lib/vis/nodelink/NodeLinkViewModel.tsx
+++ b/libs/shared/lib/vis/nodelink/NodeLinkViewModel.tsx
@@ -130,7 +130,7 @@ export default class NodeLinkViewModel {
     this.graph = this.resultNodeLinkParserUseCase.parseQueryResult(jsonObject);
     this.SetNodeGraphics(this.graph, this.radius);
     this.simulation.restart();
-    console.log('simulation restarted', jsonObject, this.graph);
+    console.debug('simulation restarted', jsonObject, this.graph);
     // this.notifyViewAboutChanges();
 
     // if (isNodeLinkResult(jsonObject)) {
@@ -509,14 +509,6 @@ export default class NodeLinkViewModel {
     event.subject.fy = event.subject.y;
     this.dragOffset.x = event.subject.x;
     this.dragOffset.y = event.subject.y;
-
-    // Toggle display of the attributes of the node
-    const node = this.simulation.find(event.x, event.y, 15);
-
-    // Null check
-    if (node) {
-      this.ToggleInformationOnNode(node);
-    }
   };
 
   /**
@@ -527,9 +519,9 @@ export default class NodeLinkViewModel {
   public ToggleInformationOnNode(node: NodeType) {
     this.simulation.alphaTarget(0).restart(); // renderer will not always update without this line
     this.showAttributes(node);
-    this.highlightNode(node);
-    this.highlightLinks(node);
-    this.showShortestPath();
+    // this.highlightNode(node);
+    // this.highlightLinks(node);
+    // this.showShortestPath();
   }
 
   /**
@@ -638,6 +630,13 @@ export default class NodeLinkViewModel {
     }
     event.subject.fx = null;
     event.subject.fy = null;
+
+    // Toggle display of the attributes of the node
+    const node = this.simulation.find(event.x, event.y, 15);
+    // Null check
+    if (!!node) {
+      this.ToggleInformationOnNode(node);
+    }
   };
 
   /**
@@ -724,8 +723,8 @@ export default class NodeLinkViewModel {
   /** Ticked is the updater function of the simulation. Every 'tick' all the new positions of the nodes (and thus the edges) are recalculated and updated. */
   public ticked = () => {
     this.graph.nodes.forEach((node: any) => {
-      if (node === undefined) return;
-      if (node?.gfx === undefined) node.gfx = {};
+      if (!node || !node.x || !node.y) return;
+      // if (node?.gfx === undefined) node.gfx = {};
       node.gfx.position = new PIXI.Point(node.x, node.y);
       // Update attributes position if they exist
       if (node.gfxAttributes) {
diff --git a/libs/shared/lib/vis/nodelink/nodelinkvis.tsx b/libs/shared/lib/vis/nodelink/nodelinkvis.tsx
index 1c6d4b2c6..8a4b6aa11 100644
--- a/libs/shared/lib/vis/nodelink/nodelinkvis.tsx
+++ b/libs/shared/lib/vis/nodelink/nodelinkvis.tsx
@@ -64,14 +64,14 @@ export const NodeLinkVis = React.memo((props: Props) => {
   const theme = useTheme();
 
   useEffect(() => {
-    console.log('update nodelink useEffect', graphQueryResult);
+    console.debug('update nodelink useEffect', graphQueryResult);
     nodeLinkViewModelRef?.current?.consumeMessageFromBackend(graphQueryResult);
     nodeLinkViewModelRef?.current?.startSimulation();
     nodeLinkViewModelRef?.current?.selectD3Elements();
   }, [graphQueryResult]);
 
   useEffect(() => {
-    console.log('loaded NodeLinkVis');
+    console.debug('loaded NodeLinkVis');
     const resultNodeLinkParserUseCase = new ResultNodeLinkParserUseCase();
     const nodeLinkViewModel = new NodeLinkViewModel(
       resultNodeLinkParserUseCase,
diff --git a/libs/shared/package.json b/libs/shared/package.json
index 2fd83cbbb..221adab0f 100644
--- a/libs/shared/package.json
+++ b/libs/shared/package.json
@@ -38,9 +38,11 @@
     "d3": "^6.6",
     "deck.gl": "^8.9.19",
     "graphology": "^0.25.1",
+    "graphology-dag": "^0.3.0",
     "graphology-layout": "^0.6.1",
     "graphology-layout-forceatlas2": "^0.10.1",
     "graphology-layout-noverlap": "^0.4.2",
+    "graphology-simple-path": "^0.2.0",
     "graphology-types": "^0.24.7",
     "immer": "^10.0.2",
     "jspdf": "^2.5.1",
diff --git a/libs/shared/vite.config.ts b/libs/shared/vite.config.ts
index 6cd9dbf47..2f263c107 100644
--- a/libs/shared/vite.config.ts
+++ b/libs/shared/vite.config.ts
@@ -5,6 +5,7 @@ import { resolve } from 'path';
 import { defineConfig } from 'vite';
 import dts from 'vite-plugin-dts';
 import path from 'path';
+import tsconfigPaths from 'vite-tsconfig-paths';
 
 export default defineConfig({
   plugins: [
@@ -12,7 +13,9 @@ export default defineConfig({
     dts({
       insertTypesEntry: true,
     }),
+    tsconfigPaths(),
   ],
+  optimizeDeps: {},
   build: {
     lib: {
       entry: resolve(__dirname, './index.ts'),
@@ -20,6 +23,10 @@ export default defineConfig({
       formats: ['es', 'umd'],
       fileName: (format) => `@graphpolaris-shared.${format}.js`,
     },
+    commonjsOptions: {
+      include: [],
+      transformMixedEsModules: true,
+    },
     rollupOptions: {
       external: ['react', 'react-dom'],
       output: {
@@ -36,12 +43,9 @@ export default defineConfig({
     },
   },
   test: {
-    setupFiles: ['./vitest.setup.ts'],
     environment: 'happy-dom',
-    deps: {
-      // inline: ['vitest-canvas-mock'],
-    },
-    threads: false,
+    deps: {},
+    threads: true,
     environmentOptions: {
       jsdom: {
         resources: 'usable',
diff --git a/libs/shared/vitest.setup.ts b/libs/shared/vitest.setup.ts
deleted file mode 100644
index 721b8f9bb..000000000
--- a/libs/shared/vitest.setup.ts
+++ /dev/null
@@ -1 +0,0 @@
-// import 'vitest-canvas-mock'
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index abdb21d3a..73e36f4b9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -232,6 +232,9 @@ importers:
       graphology:
         specifier: ^0.25.1
         version: 0.25.1(graphology-types@0.24.7)
+      graphology-dag:
+        specifier: ^0.3.0
+        version: 0.3.0(graphology-types@0.24.7)
       graphology-layout:
         specifier: ^0.6.1
         version: 0.6.1(graphology-types@0.24.7)
@@ -241,6 +244,9 @@ importers:
       graphology-layout-noverlap:
         specifier: ^0.4.2
         version: 0.4.2(graphology-types@0.24.7)
+      graphology-simple-path:
+        specifier: ^0.2.0
+        version: 0.2.0(graphology-types@0.24.7)
       graphology-types:
         specifier: ^0.24.7
         version: 0.24.7
@@ -563,7 +569,7 @@ importers:
         version: 8.7.0(eslint@7.32.0)
       eslint-config-turbo:
         specifier: latest
-        version: 1.10.6(eslint@7.32.0)
+        version: 1.10.9(eslint@7.32.0)
       eslint-plugin-react:
         specifier: 7.31.8
         version: 7.31.8(eslint@7.32.0)
@@ -9235,15 +9241,6 @@ packages:
     dependencies:
       eslint: 7.32.0
 
-  /eslint-config-turbo@1.10.6(eslint@7.32.0):
-    resolution: {integrity: sha512-iZ63etePRUdEIDY5MxdUhU2ekV9TDbVdHg0BK00QqVFgQTXUYuJ7rsQj/wUKTsw3jwhbLfaY6H5sknAgYyWZ2g==}
-    peerDependencies:
-      eslint: '>6.6.0'
-    dependencies:
-      eslint: 7.32.0
-      eslint-plugin-turbo: 1.10.6(eslint@7.32.0)
-    dev: false
-
   /eslint-config-turbo@1.10.9(eslint@7.32.0):
     resolution: {integrity: sha512-YA5QWxWte/NiRJL0/Cv7aATfIvS5sUAuyD6ZuyTZEzwyU7E6FUXGo44amjf9INkyj96HrJ2nYWoFkCRx3vs6Ag==}
     peerDependencies:
@@ -9251,7 +9248,6 @@ packages:
     dependencies:
       eslint: 7.32.0
       eslint-plugin-turbo: 1.10.9(eslint@7.32.0)
-    dev: true
 
   /eslint-import-resolver-node@0.3.7:
     resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==}
@@ -9394,15 +9390,6 @@ packages:
       semver: 6.3.0
       string.prototype.matchall: 4.0.8
 
-  /eslint-plugin-turbo@1.10.6(eslint@7.32.0):
-    resolution: {integrity: sha512-jlzfxYaK8hcz1DTk8Glxxi1r0kgdy85191a4CbFOTiiBulmKHMLJgzhsyE9Ong796MA62n91KFpc20BiKjlHwg==}
-    peerDependencies:
-      eslint: '>6.6.0'
-    dependencies:
-      dotenv: 16.0.3
-      eslint: 7.32.0
-    dev: false
-
   /eslint-plugin-turbo@1.10.9(eslint@7.32.0):
     resolution: {integrity: sha512-o8Nga4WFMvzF0lo3d3UyjGli2JOUn/4SRtRdvcf4EA9/TPotU/NUHqO16Cp0SHZJG/tGYIy5LY1O/EO7Mxbd1A==}
     peerDependencies:
@@ -9410,7 +9397,6 @@ packages:
     dependencies:
       dotenv: 16.0.3
       eslint: 7.32.0
-    dev: true
 
   /eslint-scope@5.1.1:
     resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
@@ -10177,6 +10163,16 @@ packages:
       lodash: 4.17.21
     dev: true
 
+  /graphology-dag@0.3.0(graphology-types@0.24.7):
+    resolution: {integrity: sha512-dg4JPb+/LDEbDinZIj7ezWzlEXDRokshdpTL8oAuftE9Uy0uTKGOKSmYULY8p3j/vw0HB31Wog9T/kpqprUQpg==}
+    peerDependencies:
+      graphology-types: '>=0.19.0'
+    dependencies:
+      graphology-types: 0.24.7
+      graphology-utils: 2.5.2(graphology-types@0.24.7)
+      mnemonist: 0.39.5
+    dev: false
+
   /graphology-generators@0.11.2(graphology-types@0.24.7):
     resolution: {integrity: sha512-hx+F0OZRkVdoQ0B1tWrpxoakmHZNex0c6RAoR0PrqJ+6fz/gz6CQ88Qlw78C6yD9nlZVRgepIoDYhRTFV+bEHg==}
     peerDependencies:
@@ -10248,9 +10244,27 @@ packages:
       mnemonist: 0.39.5
     dev: true
 
+  /graphology-simple-path@0.2.0(graphology-types@0.24.7):
+    resolution: {integrity: sha512-4cGMWbVuJM0zlKDUx6dS6JGGLddizDPe8PsTokXVz2eTeHYg07qa5TgwIco15ta2RMM05+xy8N1mFnpS85y0kw==}
+    peerDependencies:
+      graphology-types: '>=0.20.0'
+    dependencies:
+      graphology-types: 0.24.7
+      graphology-utils: 1.8.0(graphology-types@0.24.7)
+      mnemonist: 0.39.5
+    dev: false
+
   /graphology-types@0.24.7:
     resolution: {integrity: sha512-tdcqOOpwArNjEr0gNQKCXwaNCWnQJrog14nJNQPeemcLnXQUUGrsCWpWkVKt46zLjcS6/KGoayeJfHHyPDlvwA==}
 
+  /graphology-utils@1.8.0(graphology-types@0.24.7):
+    resolution: {integrity: sha512-Pa7SW30OMm8fVtyH49b3GJ/uxlMHGfXly50wIhlcc7ZoX9ahZa7sPBz+obo4WZClrRV6wh3tIu0GJoI42eao1A==}
+    peerDependencies:
+      graphology-types: '>=0.19.0'
+    dependencies:
+      graphology-types: 0.24.7
+    dev: false
+
   /graphology-utils@2.5.2(graphology-types@0.24.7):
     resolution: {integrity: sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==}
     peerDependencies:
-- 
GitLab