diff --git a/Makefile b/Makefile index 81447c9574b4cb9972b255df57106e1ab008a0a6..04b9b607cbd7d4f0416efbee3942c5f7f8f0cc6d 100644 --- a/Makefile +++ b/Makefile @@ -11,3 +11,8 @@ docker: login @docker build -t harbor.graphpolaris.com/graphpolaris/frontend:latest . @docker push harbor.graphpolaris.com/graphpolaris/frontend\:latest + +push: + @pnpm lint + @pnpm test + @pnpm build diff --git a/libs/shared/lib/components/Popup.tsx b/libs/shared/lib/components/Popup.tsx index 13141bde35daacaae748891aca0c55ce085ba963..9066287af25880719faf27c5f053a41fb81d1d8e 100644 --- a/libs/shared/lib/components/Popup.tsx +++ b/libs/shared/lib/components/Popup.tsx @@ -1,10 +1,23 @@ -export const Popup = (props: { children: React.ReactNode; open: boolean; hAnchor: 'left' | 'right' }) => { +import { useRef } from 'react'; + +export const Popup = (props: { + children: React.ReactNode; + open: boolean; + hAnchor: 'left' | 'right'; + className?: string; + offset?: string; +}) => { + const ref = useRef<HTMLDivElement>(null); + return ( <> {props.open && ( <div - className="absolute z-10 max-w-[20rem] bg-white flex flex-col gap-2 animate-openmenu p-0 m-0" - style={props.hAnchor === 'left' ? { left: '5rem' } : { right: '5rem' }} + ref={ref} + className={ + 'absolute z-50 max-w-[20rem] bg-white flex flex-col gap-2 animate-openmenu p-0 m-0 ' + (props.className ? props.className : '') + } + style={props.hAnchor === 'right' ? { left: props.offset || 0 } : { right: props.offset || 0 }} > {props.children} </div> diff --git a/libs/shared/lib/components/forms/index.tsx b/libs/shared/lib/components/forms/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..561852353bc47599c1c5b1d46e553e401ebd8393 --- /dev/null +++ b/libs/shared/lib/components/forms/index.tsx @@ -0,0 +1,51 @@ +import React, { PropsWithChildren, useEffect, useLayoutEffect, useRef, useState } from 'react'; +import CloseIcon from '@mui/icons-material/Close'; + +export const FormDiv = React.forwardRef<HTMLDivElement, PropsWithChildren<{ className?: string; hAnchor?: string; offset?: string }>>( + (props, ref) => { + return ( + <div + className={'absolute opacity-100 transition-opacity group-hover:opacity-100 z-50 ' + (props.className ? props.className : '')} + ref={ref} + style={props.hAnchor === 'left' ? { left: props.offset || 0 } : { right: props.offset || '5rem' }} + > + {props.children} + </div> + ); + } +); +export const FormCard = (props: PropsWithChildren<{ className?: string }>) => ( + <div className={'card card-bordered bg-white rounded-none ' + (props.className ? props.className : '')}>{props.children}</div> +); +export const FormBody = ({ + children, + ...props +}: PropsWithChildren<React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>>) => ( + <form className="card-body px-0 w-72 py-5" {...props}> + {children} + </form> +); +export const FormTitle = ({ children, title, onClose }: PropsWithChildren<{ title: string; onClose: () => void }>) => ( + <div className="card-title p-5 py-0 flex w-full"> + <h2 className="w-full">{title}</h2> + <button className="btn btn-circle btn-sm btn-ghost" onClick={() => onClose()}> + <CloseIcon fontSize="small" /> + </button> + </div> +); +export const FormHBar = () => <div className="divider m-0"></div>; +export const FormControl = ({ children }: PropsWithChildren) => <div className="form-control px-5">{children}</div>; +export const FormActions = (props: { onClose: () => void }) => ( + <div className="card-actions mt-1 w-full px-5 flex flex-row"> + <button + className="btn btn-secondary flex-grow" + onClick={(e) => { + e.preventDefault(); + props.onClose(); + }} + > + Cancel + </button> + <button className="btn btn-primary flex-grow">Apply</button> + </div> +); diff --git a/libs/shared/lib/querybuilder/panel/querySettingsDialog.tsx b/libs/shared/lib/querybuilder/panel/querySettingsDialog.tsx index ccd4ffda4ac0e3f47209e558692dfd83c491b92a..d5ccff8e3107de3f13a0725f3ff6c680fb6476bd 100644 --- a/libs/shared/lib/querybuilder/panel/querySettingsDialog.tsx +++ b/libs/shared/lib/querybuilder/panel/querySettingsDialog.tsx @@ -5,6 +5,7 @@ import CloseIcon from '@mui/icons-material/Close'; import { useAppDispatch, useQuerybuilderSettings } from '../../data-access'; import { QueryBuilderSettings, setQuerybuilderSettings } from '../../data-access/store/querybuilderSlice'; import { addWarning } from '../../data-access/store/configSlice'; +import { FormBody, FormCard, FormDiv, FormHBar, FormTitle } from '../../components/forms'; export const QuerySettingsDialog = React.forwardRef<HTMLDivElement, DialogProps>((props, ref) => { const qb = useQuerybuilderSettings(); @@ -31,22 +32,16 @@ export const QuerySettingsDialog = React.forwardRef<HTMLDivElement, DialogProps> return ( <> {props.open && ( - <div className="absolute right-20 bottom-5 opacity-100 transition-opacity group-hover:opacity-100 z-50 " ref={ref}> - <div className="card card-bordered bg-white rounded-none"> - <form - className="card-body px-0 w-72 py-5" + <FormDiv ref={ref} className="" hAnchor="right"> + <FormCard> + <FormBody onSubmit={(e) => { e.preventDefault(); submit(); }} > - <div className="card-title p-5 py-0 flex w-full"> - <h2 className="w-full">Query Settings</h2> - <button className="btn btn-circle btn-sm btn-ghost" onClick={() => props.onClose()}> - <CloseIcon fontSize="small" /> - </button> - </div> - <div className="divider m-0"></div> + <FormTitle title="Query Settings" onClose={props.onClose} /> + <FormHBar /> <div className="form-control px-5"> <label className="label"> <span className="label-text">Limit - Max number of results</span> @@ -59,7 +54,7 @@ export const QuerySettingsDialog = React.forwardRef<HTMLDivElement, DialogProps> onChange={(e) => setState({ ...state, limit: parseInt(e.target.value) })} /> </div> - <div className="divider m-0"></div> + <FormHBar /> <div className="form-control px-5 flex flex-row gap-3"> <div className=""> <label className="label"> @@ -100,7 +95,7 @@ export const QuerySettingsDialog = React.forwardRef<HTMLDivElement, DialogProps> /> </div> </div> - <div className="divider m-0"></div> + <FormHBar /> <div className="card-actions mt-1 w-full px-5 flex flex-row"> <button className="btn btn-secondary flex-grow" @@ -113,9 +108,9 @@ export const QuerySettingsDialog = React.forwardRef<HTMLDivElement, DialogProps> </button> <button className="btn btn-primary flex-grow">Apply</button> </div> - </form> - </div> - </div> + </FormBody> + </FormCard> + </FormDiv> )} </> ); diff --git a/libs/shared/lib/schema/model/reactflow.tsx b/libs/shared/lib/schema/model/reactflow.tsx index 88dc8dc5512e9753b27e36e604de38c177fd2c5e..cecfd06a28715ad7c9567d2d685766ee6eaf0ff2 100644 --- a/libs/shared/lib/schema/model/reactflow.tsx +++ b/libs/shared/lib/schema/model/reactflow.tsx @@ -35,9 +35,10 @@ export interface SchemaReactflowData { nodeCount: number; summedNullAmount: number; label: string; + type: string; } -export interface SchemaReactflowNode extends SchemaReactflowData { +export interface SchemaReactflowEntity extends SchemaReactflowData { // handles: string[]; connectedRatio: number; name: string; @@ -51,7 +52,7 @@ export interface SchemaReactflowRelation extends SchemaReactflowData { toRatio: number; } -export interface SchemaReactflowNodeWithFunctions extends SchemaReactflowNode { +export interface SchemaReactflowNodeWithFunctions extends SchemaReactflowEntity { toggleNodeQualityPopup: (id: string) => void; toggleAttributeAnalyticsPopupMenu: (id: string) => void; } diff --git a/libs/shared/lib/schema/panel/schema.tsx b/libs/shared/lib/schema/panel/schema.tsx index 9a0c2c5b2878f40bd99426328c4579c292b4b385..a7ea87e3a3b703c581141d1d61534efeaf3c3a17 100644 --- a/libs/shared/lib/schema/panel/schema.tsx +++ b/libs/shared/lib/schema/panel/schema.tsx @@ -102,8 +102,8 @@ export const Schema = (props: Props) => { if (schemaGraphology == undefined || schemaGraphology.order == 0) { return; } - // console.log(schemaGraphology.export()); - // console.log(schemaLayout); + + console.log(schemaGraph); updateLayout(); const expandedSchema = schemaExpandRelation(schemaGraphology); diff --git a/libs/shared/lib/schema/panel/schemaDialog.tsx b/libs/shared/lib/schema/panel/schemaDialog.tsx index 17a986178bd1f8bc75a091ef93ab78cfa5d817c8..46c10a721dbfb4b8370bd71605131177fee740f4 100644 --- a/libs/shared/lib/schema/panel/schemaDialog.tsx +++ b/libs/shared/lib/schema/panel/schemaDialog.tsx @@ -4,6 +4,7 @@ import React from 'react'; import CloseIcon from '@mui/icons-material/Close'; import { useAppDispatch, useSchemaSettings } from '../../data-access'; import { SchemaSettings, setSchemaSettings } from '../../data-access/store/schemaSlice'; +import { FormActions, FormBody, FormCard, FormControl, FormHBar, FormTitle, FormDiv } from '../../components/forms'; export const SchemaDialog = React.forwardRef<HTMLDivElement, DialogProps>((props, ref) => { const settings = useSchemaSettings(); @@ -23,23 +24,17 @@ export const SchemaDialog = React.forwardRef<HTMLDivElement, DialogProps>((props return ( <> {props.open && ( - <div className="absolute opacity-100 transition-opacity group-hover:opacity-100 z-50 " ref={ref}> - <div className="card absolute card-bordered bg-white rounded-none"> - <form - className="card-body px-0 w-72 py-5" + <FormDiv ref={ref}> + <FormCard> + <FormBody onSubmit={(e) => { e.preventDefault(); submit(); }} > - <div className="card-title p-5 py-0 flex w-full"> - <h2 className="w-full">Quick Settings</h2> - <button className="btn btn-circle btn-sm btn-ghost" onClick={() => props.onClose()}> - <CloseIcon fontSize="small" /> - </button> - </div> - <div className="divider m-0"></div> - <div className="form-control px-5"> + <FormTitle title="Quick Settings" onClose={props.onClose} /> + <FormHBar /> + <FormControl> <label className="label cursor-pointer w-fit gap-2 px-0 py-1"> <input type="checkbox" checked={true} onChange={(e) => {}} className="checkbox checkbox-xs" /> <span className="label-text">Points</span> @@ -52,23 +47,23 @@ export const SchemaDialog = React.forwardRef<HTMLDivElement, DialogProps>((props <input type="checkbox" checked={true} onChange={(e) => {}} className="checkbox checkbox-xs" /> <span className="label-text">Line</span> </label> - </div> - <div className="divider m-0"></div> - <div className="form-control px-5"> + </FormControl> + <FormHBar /> + <FormControl> <label className="label"> <span className="label-text">Opacity</span> </label> <input type="range" min={0} max="100" value="40" onChange={(e) => {}} className="range range-sm" /> - </div> - <div className="divider m-0"></div> - <div className="form-control px-5"> + </FormControl> + <FormHBar /> + <FormControl> <label className="label"> <span className="label-text">Histogram</span> </label> ... - </div> - <div className="divider m-0"></div> - <div className="form-control px-5"> + </FormControl> + <FormHBar /> + <FormControl> <label className="label"> <span className="label-text">Type of Connection</span> </label> @@ -92,24 +87,13 @@ export const SchemaDialog = React.forwardRef<HTMLDivElement, DialogProps>((props Bezier </option> </select> - </div> - <div className="divider m-0"></div> + </FormControl> + <FormHBar /> - <div className="card-actions mt-1 w-full px-5 flex flex-row"> - <button - className="btn btn-secondary flex-grow" - onClick={(e) => { - e.preventDefault(); - props.onClose(); - }} - > - Cancel - </button> - <button className="btn btn-primary flex-grow">Apply</button> - </div> - </form> - </div> - </div> + <FormActions onClose={props.onClose} /> + </FormBody> + </FormCard> + </FormDiv> )} </> ); diff --git a/libs/shared/lib/schema/panel/view-model/SchemaViewModel.t b/libs/shared/lib/schema/panel/view-model/SchemaViewModel.t deleted file mode 100644 index f3a434ca5a7b5cffbe1479041ce816f9a62e94c3..0000000000000000000000000000000000000000 --- a/libs/shared/lib/schema/panel/view-model/SchemaViewModel.t +++ /dev/null @@ -1,928 +0,0 @@ -/** - * 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 SchemaViewModel from './SchemaViewModel'; -import { Node, ReactFlowInstance } from 'reactflow'; -import { - doBoxesOverlap, - makeBoundingBox, - calcWidthRelationNodeBox, - calcWidthEntityNodeBox, - numberPredicates, -} from '../../schema-utils/utils'; -import { - AttributeAnalyticsData, - AttributeAnalyticsPopupMenuNode, - AttributeCategory, - AttributeWithData, - NodeQualityDataForEntities, - NodeQualityDataForRelations, - NodeQualityPopupNode, - NodeType, -} from '../../schema-utils/Types'; -import React from 'react'; -import '../../view/graph-schema/flow-components/nodes/popupmenus/NodeQualityPopupNode.scss'; - -import '../../view/graph-schema/flow-components/nodes/popupmenus/AttributeAnalyticsPopupMenuNode.scss'; - -// import { -// exportComponentAsPNG, -// Params, -// PDFOptions, -// } from 'react-component-export-image'; - -import { getTextOfJSDocComment } from 'typescript'; - -/** This class is responsible for updating and creating the graph schema. */ -export default class SchemaViewModelImpl - extends AbstractBaseViewModelImpl - implements SchemaViewModel -{ - private reactFlowInstance: any; - private relationCounter: number; - private drawOrderUseCase: DrawOrderUseCase; - private nodeUseCase: NodeUseCase; - private edgeUseCase: EdgeUseCase; - private graphUseCase: GraphUseCase; - private placeInQueryBuilder: (name: string, type: string) => void; - public elements: SchemaElements = { nodes: [], edges: [], selfEdges: [] }; - public zoom: number; - public visible: boolean; - - public nodeQualityPopup: NodeQualityPopupNode; - public attributeAnalyticsPopupMenu: AttributeAnalyticsPopupMenuNode; - - public nodeQualityData: Record< - string, - NodeQualityDataForEntities | NodeQualityDataForRelations - >; - public attributeAnalyticsData: Record<string, AttributeAnalyticsData>; - - private entityPopupOffsets = { - nodeQualityOffset: { - x: SchemaThemeHolder.entity.width, - y: SchemaThemeHolder.entity.height - 3, - }, - attributeQualityOffset: { - x: SchemaThemeHolder.entity.width, - y: -SchemaThemeHolder.entity.height + 12, - }, - }; - - private relationPopupOffsets = { - nodeQualityOffset: { - x: SchemaThemeHolder.relation.width + 50, - y: SchemaThemeHolder.relation.height + 2, - }, - attributeQualityOffset: { - x: SchemaThemeHolder.relation.width + 50, - y: -SchemaThemeHolder.relation.height + 17, - }, - }; - // React flow reference for positioning on drop. - public myRef: React.RefObject<HTMLDivElement>; - - public nodeTypes = { - entity: EntityNode, - relation: RelationNode, - nodeQualityEntityPopup: NodeQualityEntityPopupNode, - nodeQualityRelationPopup: NodeQualityRelationPopupNode, - attributeAnalyticsPopupMenu: AttributeAnalyticsPopupMenu, - }; - - public edgeTypes = { - nodeEdge: NodeEdge, - selfEdge: SelfEdge, - }; - - public constructor( - drawOrderUseCase: DrawOrderUseCase, - nodeUseCase: NodeUseCase, - edgeUseCase: EdgeUseCase, - graphUseCase: GraphUseCase, - addAttribute: (name: string, type: string) => void - ) { - super(); - - this.myRef = React.createRef(); - // TODO: These values need to not be hardcoded. - this.zoom = 1.3; - this.relationCounter = 0; - this.reactFlowInstance = 0; - this.visible = true; - this.edgeUseCase = edgeUseCase; - this.nodeUseCase = nodeUseCase; - this.drawOrderUseCase = drawOrderUseCase; - this.graphUseCase = graphUseCase; - this.placeInQueryBuilder = addAttribute; - - this.nodeQualityPopup = this.emptyNodeQualityPopupNode(); - this.attributeAnalyticsPopupMenu = - this.emptyAttributeAnalyticsPopupMenuNode(); - - this.nodeQualityData = {}; - this.attributeAnalyticsData = {}; - } - - /** - * Containts all function calls to create the graph schema. - * Notifies the view about the changes at the end. - */ - public createSchema = (elements: SchemaElements): SchemaElements => { - let drawOrder: Node[]; - - drawOrder = this.drawOrderUseCase.createDrawOrder(elements); - // Create nodes with start position. - elements.nodes = this.nodeUseCase.setEntityNodePosition( - drawOrder, - { - x: 0, - y: 0, - }, - this.toggleNodeQualityPopup, - this.toggleAttributeAnalyticsPopupMenu - ); - - // Create the relation-nodes. - elements.edges.forEach((relation) => { - this.createRelationNode( - relation.id, - relation.data.attributes, - relation.data.collection, - elements - ); - }); - - elements.selfEdges.forEach((relation) => { - this.createRelationNode( - relation.id, - relation.data.attributes, - relation.data.collection, - elements - ); - }); - - // Complement the relation-nodes with extra data that is now accessible. - elements.edges = this.edgeUseCase.positionEdges( - drawOrder, - elements, - this.setRelationNodePosition - ); - - this.visible = false; - this.notifyViewAboutChanges(); - return elements; - }; - - /** - * consumes the schema send from the backend and uses it to create the SchemaElements for the schema - * @param jsonObject - */ - public consumeMessageFromBackend(jsonObject: unknown): void { - if (isSchemaResult(jsonObject)) { - // This is always the first message to receive, so reset the global variables. - this.visible = false; - this.createSchema({ nodes: [], edges: [], selfEdges: [] }); - - this.relationCounter = 0; - - this.nodeQualityPopup.isHidden = true; - this.attributeAnalyticsPopupMenu.isHidden = true; - - /* Create the graph-schema and add the popup-menu's for as far as possible. - * Runs underlying useCase trice to fix a bug with lingering selfEdges. - TODO: clean this up.*/ - this.elements = this.createSchema({ - nodes: [], - edges: [], - selfEdges: [], - }); - let schemaElements = - this.graphUseCase.createGraphFromInputData(jsonObject); - this.elements = this.createSchema(schemaElements); - this.notifyViewAboutChanges(); - //End weird fix - - this.visible = true; - schemaElements = this.graphUseCase.createGraphFromInputData(jsonObject); - this.elements = this.createSchema(schemaElements); - this.notifyViewAboutChanges(); - - this.addAttributesToAnalyticsPopupMenus(jsonObject); - } else if (isAttributeDataEntity(jsonObject)) { - // Add all information from the received message to the popup-menu's. - this.addAttributeDataToPopupMenusAndElements( - jsonObject, - 'gsa_node_result', - this.elements - ); - } else if (isAttributeDataRelation(jsonObject)) { - // Add all information from the received message to the popup-menu's. - this.addAttributeDataToPopupMenusAndElements( - jsonObject, - 'gsa_edge_result', - this.elements - ); - } else { - // TODO: This should be an error screen eventually. - console.log('This is no valid input!'); - } - this.notifyViewAboutChanges(); - } - - /** - * Create a reference to the reactflow schema component on load - * @param reactFlowInstance reactflow instance - */ - public onInit = (reactFlowInstance: ReactFlowInstance<any>): void => { - this.reactFlowInstance = reactFlowInstance; - }; - - /** - * Complements the relation-nodes with data that had default values before. - * It determines the position of the relation-node and the connected entity-nodes. - * @param centerX Used to determine the center of the edge. - * @param centerY Used to determine the center of the edge. - * @param id The id of the relation node. - * @param from The id of the entity where the edge should connect from. - * @param to The id of the entity where the edge should connect to. - * @param attributes The attributes of the relation node. - */ - public setRelationNodePosition = ( - centerX: number, - centerY: number, - id: string, - from: string, - to: string, - attributes: Attribute[] - ): void => { - let width: number; - let overlap: boolean; - let y: number; - - // Check if the relation-node is in the list of nodes. - let relation = this.elements.nodes.find((node) => node.id == id); - if (relation == undefined) - throw new Error('Relation ' + id + ' does not exist.'); - - // Height of relation/entity + external buttons. - const height = 20; - - width = - SchemaThemeHolder.relation.width + - calcWidthRelationNodeBox(attributes.length, 0); - let x = centerX - SchemaThemeHolder.relation.width / 2; - y = centerY - height / 2; - - while (this.CheckForOverlap(x, y, width, height)) { - y = y + 1; - } - - // Replace the default values for the correct values. - relation.position = { x: x, y: y }; - relation.data.from = from; - relation.data.to = to; - - this.relationCounter++; - if (this.relationCounter == this.elements.edges.length) { - this.fitToView(); - } - this.notifyViewAboutChanges(); - }; - - /** - * Creates a new relation-node with some default values. - * @param id The id of the relation node. - * @param attributes The list of attributes that this relation-node has. - * @param collection The collection this relation-node is in. - */ - public createRelationNode = ( - id: string, - attributes: Attribute[], - collection: string, - schemaElements: SchemaElements - ): void => { - // Height of relation/entity + external buttons. - const height = 20; - const width = - SchemaThemeHolder.relation.width + - calcWidthRelationNodeBox(attributes.length, 0); - - schemaElements.nodes.push({ - type: NodeType.relation, - id: id, - position: { x: 0, y: 0 }, - data: { - width: width, - height: height, - collection: collection, - attributes: attributes, - from: '', - to: '', - nodeCount: 0, - summedNullAmount: 0, - fromRatio: 0, - toRatio: 0, - toggleNodeQualityPopup: this.toggleNodeQualityPopup, - toggleAttributeAnalyticsPopupMenu: - this.toggleAttributeAnalyticsPopupMenu, - }, - }); - }; - - /** - * Calculates the width and height of the graph-schema-panel. - * @returns { number; number } The width and the height of the graph-schema-panel. - */ - public getWidthHeight = (): { width: number; height: number } => { - const reactFlow = this.myRef.current as HTMLDivElement; - const reactFlowBounds = reactFlow.getBoundingClientRect(); - const width = reactFlowBounds.right - reactFlowBounds.left; - const height = reactFlowBounds.bottom - reactFlowBounds.top; - return { width, height }; - }; - - /** Placeholder function for fitting the schema into the view. */ - public fitToView = (): void => { - let minX = Infinity; - let maxX = 0; - let minY = Infinity; - let maxY = 0; - let xZoom = 0; - let yZoom = 0; - - let setY = 0; - let setX = 0; - let setZoom = 0; - - this.elements.nodes.forEach((node) => { - const attributeCount: number = node.data.attributes.length; - const nodeCount: number = node.data.nodeCount; - const nodeWidth: number = - node.type == NodeType.entity - ? SchemaThemeHolder.entity.width + - calcWidthEntityNodeBox(attributeCount, nodeCount) - : SchemaThemeHolder.relation.width + - calcWidthRelationNodeBox(attributeCount, nodeCount); - - if (node.position.x < minX) minX = node.position.x; - if (node.position.x + nodeWidth > maxX) - maxX = node.position.x + nodeWidth; - if (node.position.y < minY) minY = node.position.y; - if (node.position.y + node.data.height > maxY) - maxY = node.position.y + node.data.height; - }); - - minX -= 10; - maxX += 90; - - const { width, height } = this.getWidthHeight(); - - // Correct for X and Y position with width and height. - let nodeWidth = Math.abs(maxX - minX); - let nodeHeight = Math.abs(maxY - minY); - - setX = minX * -1; - xZoom = width / nodeWidth; - setY = minY * -1; - yZoom = height / nodeHeight; - - // TODO: Correct position and zoom for selfEdges. - - if (xZoom >= yZoom) { - setZoom = yZoom; - setX = setX + width / 2 - nodeWidth / 2; - } else { - setZoom = xZoom; - setY = setY + height / 2 - nodeHeight / 2; - } - - try { - this.reactFlowInstance.setTransform({ x: setX, y: setY, zoom: setZoom }); - } catch { - console.log('this.reactFlowInstance is undefined!'); - } - }; - - /** - * this function check for a relation node if it overlaps with any of the other nodes in the schema. - * It creates boundingbox for the node and checks with all the other nodes if the boxes overlap. - * @param x Top left x of the node. - * @param y Top left y of the node. - * @param width Width of the node. - * @param height Height of the node. - * @returns {bool} whether it overlaps.*/ - public CheckForOverlap = ( - x: number, - y: number, - width: number, - height: number - ): boolean => { - const boundingBox = makeBoundingBox(x, y, width, height); - let elements = this.elements; - - let boundingTemporary: BoundingBox; - for (let i = 0; i < elements.nodes.length; i++) { - boundingTemporary = makeBoundingBox( - elements.nodes[i].position.x, - elements.nodes[i].position.y, - elements.nodes[i].data.width, - elements.nodes[i].data.height - ); - if (doBoxesOverlap(boundingBox, boundingTemporary)) { - return true; - } - } - return false; - }; - - /** Exports the schema builder to a beautiful png file */ - public exportToPNG(): void { - const { width, height } = this.getWidthHeight(); - exportComponentAsPNG(this.myRef, { - fileName: 'schemaBuilder', - pdfOptions: { - x: 0, - y: 0, - w: width, - h: height, - unit: 'px', - } as Partial<PDFOptions>, - } as Params); - } - - /** Not implemented method for exporting the schema builder visualisation to PDF. */ - public exportToPDF(): void { - console.log('Method not implemented.'); - } - - /** Attach the listener to the broker. */ - public subscribeToSchemaResult(): void { - Broker.instance().subscribe(this, 'schema_result'); - } - - /** Detach the listener to the broker. */ - public unSubscribeFromSchemaResult(): void { - Broker.instance().unSubscribe(this, 'schema_result'); - } - - /** Attach the listeners to the broker. */ - public subscribeToAnalyticsData(): void { - Broker.instance().subscribe(this, 'gsa_node_result'); - Broker.instance().subscribe(this, 'gsa_edge_result'); - } - - /** Detach the listeners to the broker. */ - public unSubscribeFromAnalyticsData(): void { - Broker.instance().unSubscribe(this, 'gsa_node_result'); - Broker.instance().unSubscribe(this, 'gsa_edge_result'); - } - - /** - * This function is used by relation and entity nodes to hide or show the node-quality popup of that node. - * @param id of the node for which the new popup is. - */ - public toggleNodeQualityPopup = (id: string): void => { - const popup = this.nodeQualityPopup; - - // Hide the popup if the current popup is visible and if the popup belongs to the same node as the given id. - if (popup.nodeID == id && !popup.isHidden) popup.isHidden = true; - // Else make and show a new popup for the node with the given id. - else this.updateNodeQualityPopup(id, this.elements); - - this.notifyViewAboutChanges(); - }; - - /** - * This function shows and updates the node-quality popup for the node which has the given id. - * @param id of the node for which the new popup is. - */ - private updateNodeQualityPopup(id: string, schemaElements: SchemaElements) { - let node = schemaElements.nodes.find((node) => node.id == id); - if (node == undefined) { - throw new Error('Node does not exist therefore no popup can be shown.'); - } - - const popup = this.nodeQualityPopup; - popup.nodeID = id; - popup.isHidden = false; - popup.data = this.nodeQualityData[id]; - - if (node.type == 'entity') { - // Make changes to the popup, to make it a popup for entities. - this.updateToNodeQualityEntityPopup(node); - } else { - // Make changes to the popup, to make it a popup for relations. - this.updateToNodeQualityRelationPopup(node); - } - - // Hide the attributeAnalyticsPopupMenu so that only one popup is displayed. - this.attributeAnalyticsPopupMenu.isHidden = true; - - this.notifyViewAboutChanges(); - this.relationCounter++; - if (this.relationCounter == schemaElements.edges.length) { - this.fitToView(); - } - } - - /** - * This displays the new node-quality popup for the given entity. - * @param node This is the entity of which you want to display the popup. - */ - private updateToNodeQualityEntityPopup(node: Node) { - const popup = this.nodeQualityPopup; - const offset = this.entityPopupOffsets.nodeQualityOffset; - - popup.position = { - x: node.position.x + offset.x, - y: node.position.y + offset.y, - }; - - popup.type = 'nodeQualityEntityPopup'; - } - - /** - * This displays the new node-quality popup for the given relation. - * @param node This is the relation of which you want to display the popup. - */ - private updateToNodeQualityRelationPopup(node: Node) { - const popup = this.nodeQualityPopup; - const offset = this.relationPopupOffsets.nodeQualityOffset; - - popup.position = { - x: node.position.x + offset.x, - y: node.position.y + offset.y, - }; - - popup.type = 'nodeQualityRelationPopup'; - } - - /** - * This function is used by relation and entity nodes to hide or show the attribute analyics popup menu of that node. - * @param id of the node for which the popup is. - */ - public toggleAttributeAnalyticsPopupMenu = (id: string): void => { - const popupMenu = this.attributeAnalyticsPopupMenu; - - // Hide the popup menu if the current popup menu is visible and if the popup menu belongs to the same node as the given id. - if (popupMenu.nodeID == id && !popupMenu.isHidden) - popupMenu.isHidden = true; - // Else make and show a new popup menu for the node with the given id. - else this.updateAttributeAnalyticsPopupMenu(id, this.elements); - - this.notifyViewAboutChanges(); - }; - - /** - * This displays the attribute-analytics popup menu for the given node (entity or relation). - * It removes the other menus from the screen. - * @param id This is the id of the node (entity or relation) of which you want to display the menu. - */ - public updateAttributeAnalyticsPopupMenu = ( - id: string, - schemaElements: SchemaElements - ): void => { - const node = schemaElements.nodes.find((node) => node.id == id); - - if (node == undefined) - throw new Error( - 'Node ' + id + ' does not exist therefore no popup menu can be shown.' - ); - - const popupMenu = this.attributeAnalyticsPopupMenu; - // Make new popup menu for the node. - popupMenu.nodeID = id; - popupMenu.isHidden = false; - popupMenu.data = { ...this.attributeAnalyticsData[id] }; - - if (node.type == NodeType.entity) { - const offset = this.entityPopupOffsets.attributeQualityOffset; - popupMenu.position = { - x: node.position.x + offset.x, - y: node.position.y + offset.y, - }; - } else { - const offset = this.relationPopupOffsets.attributeQualityOffset; - popupMenu.position = { - x: node.position.x + offset.x, - y: node.position.y + offset.y, - }; - } - - // Hide the nodeQualityPopup so that only one popup is displayed. - this.nodeQualityPopup.isHidden = true; - - this.notifyViewAboutChanges(); - }; - - /** This removes the node quality popup from the screen. */ - public hideNodeQualityPopup = (): void => { - this.nodeQualityPopup.isHidden = true; - this.notifyViewAboutChanges(); - }; - - /** This removes the attribute-analytics popup menu from the screen. */ - public hideAttributeAnalyticsPopupMenu = (): void => { - this.attributeAnalyticsPopupMenu.isHidden = true; - this.notifyViewAboutChanges(); - }; - - /** - * This sets all the data for the attributesPopupMenu without the attribute data. - * @param schemaResult This is the schema result that you get (so no attribute data yet). - */ - addAttributesToAnalyticsPopupMenus = (schemaResult: Schema): void => { - this.nodeQualityData = {}; - this.attributeAnalyticsData = {}; - - // Firstly, loop over all entities and add the quality-data (as far as possible). - // Then add the attribute-data (as far as possible). - schemaResult.nodes.forEach((node) => { - this.nodeQualityData[node.name] = { - nodeCount: 0, - notConnectedNodeCount: 0, - attributeNullCount: 0, - isAttributeDataIn: false, - onClickCloseButton: this.hideNodeQualityPopup, - }; - let attributes: any = []; - node.attributes.forEach((attribute) => { - attributes.push({ - attribute: attribute, - category: AttributeCategory.undefined, - nullAmount: 0, - }); - }); - this.attributeAnalyticsData[node.name] = { - nodeID: node.name, - nodeType: NodeType.entity, - attributes: attributes, - isAttributeDataIn: false, - onClickCloseButton: this.hideAttributeAnalyticsPopupMenu, - onClickPlaceInQueryBuilderButton: this.placeInQueryBuilder, - searchForAttributes: this.searchForAttributes, - resetAttributeFilters: this.resetAttributeFilters, - applyAttributeFilters: this.applyAttributeFilters, - }; - }); - // Secondly, loop over all relations and add the quality-data (as far as possible). - // Then add the attribute-data (as far as possible). - schemaResult.edges.forEach((edge) => { - this.nodeQualityData[edge.collection] = { - nodeCount: 0, - fromRatio: 0, - toRatio: 0, - attributeNullCount: 0, - notConnectedNodeCount: 0, - isAttributeDataIn: false, - onClickCloseButton: this.hideNodeQualityPopup, - }; - let attributes: any = []; - edge.attributes.forEach((attribute) => { - attributes.push({ - attribute: attribute, - category: AttributeCategory.undefined, - nullAmount: 0, - }); - }); - this.attributeAnalyticsData[edge.collection] = { - nodeID: edge.collection, - nodeType: NodeType.relation, - attributes: attributes, - isAttributeDataIn: false, - onClickCloseButton: this.hideAttributeAnalyticsPopupMenu, - onClickPlaceInQueryBuilderButton: this.placeInQueryBuilder, - searchForAttributes: this.searchForAttributes, - resetAttributeFilters: this.resetAttributeFilters, - applyAttributeFilters: this.applyAttributeFilters, - }; - }); - }; - - /** Returns an empty quality popup node for react-flow. */ - public emptyAttributeAnalyticsPopupMenuNode(): AttributeAnalyticsPopupMenuNode { - return { - id: 'attributeAnalyticsPopupMenu', - nodeID: '', - position: { x: 0, y: 0 }, - data: { - nodeID: '', - nodeType: NodeType.relation, - attributes: [], - isAttributeDataIn: false, - onClickCloseButton: this.hideAttributeAnalyticsPopupMenu, - onClickPlaceInQueryBuilderButton: this.placeInQueryBuilder, - searchForAttributes: this.searchForAttributes, - resetAttributeFilters: this.resetAttributeFilters, - applyAttributeFilters: this.applyAttributeFilters, - }, - type: 'attributeAnalyticsPopupMenu', - isHidden: true, - className: 'attributeAnalyticsPopupMenu', - }; - } - /** Returns an empty quality popup node for react-flow. */ - public emptyNodeQualityPopupNode(): NodeQualityPopupNode { - return { - id: 'nodeQualityPopup', - position: { x: 0, y: 0 }, - data: { - nodeCount: 0, - notConnectedNodeCount: 0, - attributeNullCount: 0, - isAttributeDataIn: false, - onClickCloseButton: this.hideNodeQualityPopup, - }, - type: 'nodeQualityEntityPopup', - isHidden: true, - nodeID: '', - className: 'nodeQualityPopup', - }; - } - - /** - * This function adjusts the values from the new attribute data into - * the attribute-analytics data and the node-quality data. - * @param attributeData The data that comes from the backend with (some) data about the attributes. - */ - public addAttributeDataToPopupMenusAndElements = ( - attributeData: AttributeData, - attributeDataType: string, - schemaElements: SchemaElements - ): void => { - // Check if attributeData is a node/entity. - if (attributeDataType === 'gsa_node_result') { - const entity = attributeData as NodeAttributeData; - // If it is a entity then add the data for the corresponding entity. - if (entity.id in this.attributeAnalyticsData) { - const attributeDataOfEntity = this.attributeAnalyticsData[entity.id]; - attributeDataOfEntity.isAttributeDataIn = true; - - entity.attributes.forEach((attribute) => { - // Check if attribute is in the list with attributes from the correct entity. - const attributeFound = attributeDataOfEntity.attributes.find( - (attribute_) => attribute_.attribute.name == attribute.name - ); - if (attributeFound !== undefined) { - attributeFound.category = attribute.type; - attributeFound.nullAmount = attribute.nullAmount; - } - }); - } // Not throw new error, because it should not crash, a message is enough and not all data will be shown. - else - console.log( - 'entity ' + entity.id + ' is not in attributeAnalyticsData' - ); - - if (entity.id in this.nodeQualityData) { - const qualityDataOfEntity = this.nodeQualityData[ - entity.id - ] as NodeQualityDataForEntities; - qualityDataOfEntity.nodeCount = entity.length; - qualityDataOfEntity.attributeNullCount = entity.summedNullAmount; - qualityDataOfEntity.notConnectedNodeCount = Number( - (1 - entity.connectedRatio).toFixed(2) - ); - qualityDataOfEntity.isAttributeDataIn = true; - } // Not throw new error, because it should not crash, a message is enough and not all data will be shown. - else console.log('entity ' + entity.id + ' is not in nodeQualityData'); - - // Check also if the entity exists in the this.elements-list. - // If so, add the new data to it. - const elementsNode = schemaElements.nodes.find( - (node_) => node_.id == entity.id - ); - if (elementsNode !== undefined) { - elementsNode.data.nodeCount = entity.length; - elementsNode.data.summedNullAmount = entity.summedNullAmount; - elementsNode.data.connectedRatio = entity.connectedRatio; - } - } - - // Check if attributeData is an edge/relation. - else if (attributeDataType === 'gsa_edge_result') { - const relation = attributeData as EdgeAttributeData; - // If it is a relation then add the data for the corresponding relation. - if (relation.id in this.attributeAnalyticsData) { - const attributeDataOfRelation = - this.attributeAnalyticsData[relation.id]; - attributeDataOfRelation.isAttributeDataIn = true; - - relation.attributes.forEach((attribute) => { - // Check if attribute is in the list with attributes from the correct relation. - const attributeFound = attributeDataOfRelation.attributes.find( - (attribute_) => attribute_.attribute.name == attribute.name - ); - if (attributeFound !== undefined) { - attributeFound.category = attribute.type; - attributeFound.nullAmount = attribute.nullAmount; - } - }); - } // Not throw new error, because it should not crash, a message is enough and not all data will be shown. - else - console.log( - 'relation ' + relation.id + ' is not in attributeAnalyticsData' - ); - - if (relation.id in this.nodeQualityData) { - const qualityDataOfRelation = this.nodeQualityData[ - relation.id - ] as NodeQualityDataForRelations; - qualityDataOfRelation.nodeCount = relation.length; - qualityDataOfRelation.attributeNullCount = relation.summedNullAmount; - qualityDataOfRelation.fromRatio = Number(relation.fromRatio.toFixed(2)); - qualityDataOfRelation.toRatio = Number(relation.toRatio.toFixed(2)); - qualityDataOfRelation.isAttributeDataIn = true; - } // Not throw new error, because it should not crash, a message is enough and not all data will be shown. - else - console.log('relation ' + relation.id + ' is not in nodeQualityData'); - - // Check also if the entity exists in the this.elements-list. - // If so, add the new data to it. - const elementsNode = schemaElements.nodes.find( - (node_) => node_.id == relation.id - ); - if (elementsNode !== undefined) { - elementsNode.data.nodeCount = relation.length; - elementsNode.data.summedNullAmount = relation.summedNullAmount; - elementsNode.data.fromRatio = relation.fromRatio; - elementsNode.data.toRatio = relation.toRatio; - } - } else throw new Error('This data is not valid!'); - }; - - /** - * Filter out attributes that do not contain the given searchbar-value. - * @param id The id of the node the attributes are from. - * @param searchbarValue The value of the searchbar. - */ - public searchForAttributes = (id: string, searchbarValue: string): void => { - const data = this.attributeAnalyticsData[id]; - // Check if there is data available. - if (data !== undefined) { - let passedAttributes: AttributeWithData[] = []; - data.attributes.forEach((attribute) => { - if ( - attribute.attribute.name - .toLowerCase() - .includes(searchbarValue.toLowerCase()) - ) - passedAttributes.push(attribute); - }); - this.attributeAnalyticsPopupMenu.data.attributes = passedAttributes; - this.notifyViewAboutChanges(); - } - }; - - /** - * Reset the current used filters for the attribute-list. - * @param id The id of the node the attributes are from. - */ - public resetAttributeFilters = (id: string): void => { - const data = this.attributeAnalyticsData[id]; - // Check if there is data available. - if (data !== undefined) { - this.attributeAnalyticsPopupMenu.data.attributes = data.attributes; - this.notifyViewAboutChanges(); - } - }; - - /** - * Applies the chosen filters on the list of attributes of the particular node. - * @param id The id of the node the attributes are from. - * @param dataType The given type of the data you want to filter on (numerical, categorical, other). - * @param predicate The given predicate. - * @param percentage The given percentage you want to compare the null-values on. - */ - public applyAttributeFilters = ( - id: string, - dataType: AttributeCategory, - predicate: string, - percentage: number - ): void => { - const data = this.attributeAnalyticsData[id]; - // Check if there is data available. - if (data !== undefined) { - let passedAttributes: AttributeWithData[] = []; - data.attributes.forEach((attribute) => { - // If the value is undefined it means that this filter is not chosen, so that must not be taken into account for further filtering. - if ( - attribute.category == dataType || - dataType == AttributeCategory.undefined - ) - if (predicate == '' || percentage == -1) - // If the string is empty it means that this filter is not chosen, so that must not be taken into account for filtering. - passedAttributes.push(attribute); - else if ( - numberPredicates[predicate](attribute.nullAmount, percentage) - ) - passedAttributes.push(attribute); - }); - this.attributeAnalyticsPopupMenu.data.attributes = passedAttributes; - this.notifyViewAboutChanges(); - } - }; -} diff --git a/libs/shared/lib/schema/panel/view-model/schemaViewModel.test.t b/libs/shared/lib/schema/panel/view-model/schemaViewModel.test.t deleted file mode 100644 index 1eaaf31a0e5c3078ae3cad44b2d3b08a1a1a8672..0000000000000000000000000000000000000000 --- a/libs/shared/lib/schema/panel/view-model/schemaViewModel.test.t +++ /dev/null @@ -1,1320 +0,0 @@ -/** - * 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/SchemaEntityPopup.tsx b/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPopup.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1c96996cd23ba117c4bd2a3578953ee9f225cf56 --- /dev/null +++ b/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPopup.tsx @@ -0,0 +1,61 @@ +/** + * 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) + */ + +/* istanbul ignore file */ +/* 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 { SchemaReactflowEntity } from '@graphpolaris/shared/lib/schema/model'; + +export type SchemaEntityPopupProps = { + data: SchemaReactflowEntity; + onClose: () => void; +}; + +/** + * NodeQualityEntityPopupNode is the node that represents the popup that shows the node quality for an entity + * @param data Input data of type NodeQualityDataForEntities, which is for the popup. + */ +export const SchemaEntityPopup = (props: SchemaEntityPopupProps) => { + return ( + <div className="card card-bordered rounded-none text-[0.9rem] min-w-[10rem]"> + <div className="card-body p-0"> + <span className="px-2.5 pt-2"> + <span>Nodes</span> + <span className="float-right">TBD</span> + </span> + <div className="h-[1px] w-full bg-offwhite-300"></div> + <div className="px-2.5 text-[0.8rem]"> + <p> + Null Values: <span className="float-right">TBD</span> + </p> + <p> + Not connected: <span className="float-right">TBD</span> + </p> + </div> + <div className="h-[1px] w-full bg-offwhite-300"></div> + {/* <span>Attributes:</span> + <div className="text-xs"> + {data.attributes.map((attribute) => { + return ( + <div className="flex flex-row" key={attribute.name}> + <span>{attribute.name}</span> + </div> + ); + })} + </div> */} + <button + className="btn btn-outline btn-accent border-0 btn-sm p-0 m-0 text-[0.8rem] mb-2 mx-2.5 min-h-0 h-5" + onClick={() => props.onClose()} + > + Close + </button> + </div> + </div> + ); +}; 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 aca2c74571f7f5e8286ee1aed60f02294c5d3d45..b6b8c9a3f90e4dd87dc606f6ac496dccb7b34e8a 100644 --- a/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx +++ b/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx @@ -11,9 +11,10 @@ import React, { useState } from 'react'; import { Node, Handle, Position, NodeProps } from 'reactflow'; import styles from './entity.module.scss'; -import { calcWidthEntityNodeBox, calculateAttributeQuality, calculateEntityQuality } from '@graphpolaris/shared/lib/schema/schema-utils'; import { SchemaReactflowNodeWithFunctions } from '../../../model/reactflow'; import { QueryElementTypes } from '@graphpolaris/shared/lib/querybuilder'; +import { SchemaEntityPopup } from './SchemaEntityPopup'; +import { Popup } from '@graphpolaris/shared/lib/components/Popup'; /** * EntityNode is the node that represents the database entities. @@ -22,18 +23,13 @@ import { QueryElementTypes } from '@graphpolaris/shared/lib/querybuilder'; * @param {NodeProps} param0 The data of an entity flow element. */ export const EntityNode = React.memo(({ id, data }: NodeProps<SchemaReactflowNodeWithFunctions>) => { - // console.log(data); - - const [hidden, setHidden] = useState<boolean>(true); + const [openPopup, setOpenPopup] = useState(false); /** * adds drag functionality in order to be able to drag the entityNode to the schema * @param event React Mouse drag event */ const onDragStart = (event: React.DragEvent<HTMLDivElement>) => { - console.log('dragging entiry', id, data); - // console.log('dragging entiry', id, data); - // console.log(id, data); event.dataTransfer.setData('application/reactflow', JSON.stringify({ type: QueryElementTypes.Entity, name: id })); event.dataTransfer.effectAllowed = 'move'; }; @@ -53,16 +49,25 @@ export const EntityNode = React.memo(({ id, data }: NodeProps<SchemaReactflowNod }; return ( - <div - className="border-l-2 bg-offwhite-200 border-l-entity-600 min-w-[8rem] text-[0.8rem]" - onDragStart={(event) => onDragStart(event)} - onDragStartCapture={(event) => onDragStart(event)} - onMouseDownCapture={(event) => { - if (!event.shiftKey) event.stopPropagation(); - }} - draggable - > - {/* <div + <> + {openPopup && ( + <Popup open={openPopup} hAnchor="left" className="-top-10" offset="-9rem"> + <SchemaEntityPopup data={data} onClose={() => setOpenPopup(false)} /> + </Popup> + )} + <div + className="border-l-2 bg-offwhite-200 border-l-entity-600 min-w-[8rem] text-[0.8rem]" + onDragStart={(event) => onDragStart(event)} + onDragStartCapture={(event) => onDragStart(event)} + onMouseDownCapture={(event) => { + if (!event.shiftKey) event.stopPropagation(); + }} + onClickCapture={(event) => { + setOpenPopup(!openPopup); + }} + draggable + > + {/* <div className={styles.entityNodeAttributesBox} onClick={() => onClickToggleAttributeAnalyticsPopupMenu()} style={{ @@ -84,7 +89,7 @@ export const EntityNode = React.memo(({ id, data }: NodeProps<SchemaReactflowNod </span> <span className={styles.nodeSpan}>{data.attributes.length}</span> </div> */} - {/* <div + {/* <div className={styles.entityNodeNodesBox} onClick={() => onClickToggleNodeQualityPopup()} style={{ @@ -106,7 +111,7 @@ export const EntityNode = React.memo(({ id, data }: NodeProps<SchemaReactflowNod </span> <span className={styles.nodeSpan}>{data.nodeCount}</span> </div> */} - {/* <Handle + {/* <Handle style={{ pointerEvents: 'none' }} id="entitySourceLeft" position={Position.Left} @@ -114,30 +119,30 @@ export const EntityNode = React.memo(({ id, data }: NodeProps<SchemaReactflowNod type="source" // hidden={Array.from(data.handles).includes('entitySourceLeft') ? false : true} ></Handle> */} - <Handle - style={{ pointerEvents: 'none' }} - id="entityTargetLeft" - position={Position.Left} - className={styles.handleTriangleLeft} - type="target" - // hidden={Array.from(data.handles).includes('entityTargetLeft') ? false : true} - ></Handle> - <Handle - style={{ pointerEvents: 'none' }} - id="entitySourceRight" - position={Position.Right} - className={styles.handleTriangleRight} - type="source" - // hidden={Array.from(data.handles).includes('entitySourceRight') ? false : true} - ></Handle> - {/* <Handle + <Handle + style={{ pointerEvents: 'none' }} + id="entityTargetLeft" + position={Position.Left} + className={styles.handleTriangleLeft} + type="target" + // hidden={Array.from(data.handles).includes('entityTargetLeft') ? false : true} + ></Handle> + <Handle + style={{ pointerEvents: 'none' }} + id="entitySourceRight" + position={Position.Right} + className={styles.handleTriangleRight} + type="source" + // hidden={Array.from(data.handles).includes('entitySourceRight') ? false : true} + ></Handle> + {/* <Handle style={{ pointerEvents: 'none' }} id="entityTargetRight" position={Position.Right} type="target" // hidden={Array.from(data.handles).includes('entityTargetRight') ? false : true} ></Handle> */} - {/* <Handle + {/* <Handle style={{ pointerEvents: 'none' }} id="entitySourceTop" position={Position.Top} @@ -151,7 +156,7 @@ export const EntityNode = React.memo(({ id, data }: NodeProps<SchemaReactflowNod type="target" // hidden={Array.from(data.handles).includes('entityTargetTop') ? false : true} ></Handle> */} - {/* <Handle + {/* <Handle style={{ pointerEvents: 'none' }} id="entitySourceBottom" position={Position.Bottom} @@ -165,10 +170,11 @@ export const EntityNode = React.memo(({ id, data }: NodeProps<SchemaReactflowNod type="target" // hidden={Array.from(data.handles).includes('entityTargetBottom') ? false : true} ></Handle> */} - <div className="p-2 py-1"> - <span className="">{id}</span> + <div className="p-2 py-1"> + <span className="">{id}</span> + </div> </div> - </div> + </> ); }); diff --git a/libs/shared/lib/schema/pills/nodes/popup/node-quality-entity-popup.stories.tsx b/libs/shared/lib/schema/pills/nodes/popup/node-quality-entity-popup.stories.tsx deleted file mode 100644 index 2009a5a307129fb1dcf42ee0233d31862798cad3..0000000000000000000000000000000000000000 --- a/libs/shared/lib/schema/pills/nodes/popup/node-quality-entity-popup.stories.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { Meta } from '@storybook/react'; -import { configureStore } from '@reduxjs/toolkit'; -import { Provider } from 'react-redux'; - -import { querybuilderSlice, schemaSlice } from '@graphpolaris/shared/lib/data-access/store'; -import { ReactFlowProvider } from 'reactflow'; -import { NodeQualityEntityPopupNode } from './node-quality-entity-popup'; - -const Component: Meta<typeof NodeQualityEntityPopupNode> = { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'Schema/Pills/Popups/NodeQualityEntityPopupNode', - component: NodeQualityEntityPopupNode, - decorators: [ - (story) => ( - <Provider store={Mockstore}> - <ReactFlowProvider>{story()}</ReactFlowProvider> - </Provider> - ), - ], -}; - -export default Component; - -// A super-simple mock of a redux store -const Mockstore = configureStore({ - reducer: { - querybuilder: querybuilderSlice.reducer, - // schema: schemaSlice.reducer, - }, -}); - -export const Default = { - args: { - data: { - name: 'TestEntity', - attributes: [{ id: 'a' }], - handles: [], - nodeCount: 10, - from: 1, - to: 2, - }, - }, -}; diff --git a/libs/shared/lib/schema/pills/nodes/popup/node-quality-entity-popup.tsx b/libs/shared/lib/schema/pills/nodes/popup/node-quality-entity-popup.tsx deleted file mode 100644 index cab64e2dfeab631536de783333d98a66bad9d1c2..0000000000000000000000000000000000000000 --- a/libs/shared/lib/schema/pills/nodes/popup/node-quality-entity-popup.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/** - * 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) - */ - -/* istanbul ignore file */ -/* 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 { NodeProps } from 'reactflow'; -import { NodeQualityDataForEntities } from '../../../model/reactflow'; - -/** - * NodeQualityEntityPopupNode is the node that represents the popup that shows the node quality for an entity - * @param data Input data of type NodeQualityDataForEntities, which is for the popup. - */ -export const NodeQualityEntityPopupNode = ({ data }: NodeProps<NodeQualityDataForEntities>) => { - if (data == undefined) throw new Error('No node quality data is available for this node.'); - - if (data.isAttributeDataIn) - return ( - <div> - <div className="title"> - <span id="name">Nodes</span> - <span className="rightSideValue">{data.nodeCount}</span> - </div> - <div className="information"> - <div> - <span>Null attributes</span> - <span className="rightSideValue">{data.attributeNullCount}</span> - </div> - <div> - <span>Not connected</span> - <span className="rightSideValue">{data.notConnectedNodeCount}</span> - </div> - </div> - <div className="closeButtonWrapper"> - <button onClick={() => data.onClickCloseButton()} id="closeButton"> - Close - </button> - </div> - </div> - ); - else - return ( - <div> - <div className="title"> - <span id="name">Nodes</span> - <span className="rightSideValue">{data.nodeCount}</span> - </div> - <div className="information"></div> - <div className="closeButtonWrapper"> - <button onClick={() => data.onClickCloseButton()} id="closeButton"> - Close - </button> - </div> - </div> - ); -}; diff --git a/libs/shared/lib/schema/pills/nodes/relation/SchemaRelationshipPopup.tsx b/libs/shared/lib/schema/pills/nodes/relation/SchemaRelationshipPopup.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b61517f3618382df8ebfdd14b9ae32d269faaefd --- /dev/null +++ b/libs/shared/lib/schema/pills/nodes/relation/SchemaRelationshipPopup.tsx @@ -0,0 +1,63 @@ +/** + * 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) + */ + +/* istanbul ignore file */ +/* 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 { FormBody, FormDiv, FormCard, FormHBar } from '@graphpolaris/shared/lib/components/forms'; +import { SchemaReactflowRelation } from '@graphpolaris/shared/lib/schema/model'; +import React from 'react'; +import { NodeProps } from 'reactflow'; + +export type SchemaRelationshipPopupProps = { + data: SchemaReactflowRelation; + onClose: () => void; +}; + +/** + * NodeQualityEntityPopupNode is the node that represents the popup that shows the node quality for an entity + * @param data Input data of type NodeQualityDataForEntities, which is for the popup. + */ +export const SchemaRelationshipPopup = (props: SchemaRelationshipPopupProps) => { + return ( + <div className="card card-bordered rounded-none text-[0.9rem] min-w-[10rem]"> + <div className="card-body p-0"> + <span className="px-2.5 pt-2"> + <span>Relationships</span> + <span className="float-right">TBD</span> + </span> + <div className="h-[1px] w-full bg-offwhite-300"></div> + <div className="px-2.5 text-[0.8rem]"> + <p> + Null Values: <span className="float-right">TBD</span> + </p> + <p> + Not connected: <span className="float-right">TBD</span> + </p> + </div> + <div className="h-[1px] w-full bg-offwhite-300"></div> + {/* <span>Attributes:</span> + <div className="text-xs"> + {data.attributes.map((attribute) => { + return ( + <div className="flex flex-row" key={attribute.name}> + <span>{attribute.name}</span> + </div> + ); + })} + </div> */} + <button + className="btn btn-outline btn-primary border-0 btn-sm p-0 m-0 text-[0.8rem] mb-2 mx-2.5 min-h-0 h-5" + onClick={() => props.onClose()} + > + Close + </button> + </div> + </div> + ); +}; 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 7dbffdf3efd9cce72d821347c62a470907c9d537..9fdb24c358ab14b5663c7953a34e35ce1136551b 100644 --- a/libs/shared/lib/schema/pills/nodes/relation/relation-node.tsx +++ b/libs/shared/lib/schema/pills/nodes/relation/relation-node.tsx @@ -13,6 +13,8 @@ import { Node, Handle, Position, NodeProps } from 'reactflow'; import styles from './relation.module.scss'; import { SchemaReactflowRelation, SchemaReactflowRelationWithFunctions } from '../../../model/reactflow'; import { QueryElementTypes } from '@graphpolaris/shared/lib/querybuilder'; +import { Popup } from '@graphpolaris/shared/lib/components/Popup'; +import { SchemaRelationshipPopup } from './SchemaRelationshipPopup'; /** * Relation node component that renders a relation node for the schema. @@ -20,7 +22,7 @@ import { QueryElementTypes } from '@graphpolaris/shared/lib/querybuilder'; * @param {NodeProps} param0 The data of an entity flow element. */ export const RelationNode = React.memo(({ id, data }: NodeProps<SchemaReactflowRelationWithFunctions>) => { - const [hidden, setHidden] = useState<boolean>(true); + const [openPopup, setOpenPopup] = useState(false); /** * Adds drag functionality in order to be able to drag the relationNode to the schema. @@ -57,24 +59,33 @@ export const RelationNode = React.memo(({ id, data }: NodeProps<SchemaReactflowR }; return ( - <div - onDragStart={(event) => onDragStart(event)} - onDragStartCapture={(event) => onDragStart(event)} - onMouseDownCapture={(event) => { - if (!event.shiftKey) event.stopPropagation(); - }} - draggable - // style={{ width: 100, height: 100 }} - > - <div className="text-[0.8rem] border-l-2 bg-offwhite-200 border-l-relation-600 min-w-[8rem]"> - <Handle - style={{ pointerEvents: 'none' }} - className={styles.handleTriangleTop} - id="entitySourceLeft" - position={Position.Top} - type="target" - ></Handle> - {/* <div + <> + {openPopup && ( + <Popup open={openPopup} hAnchor="left" className="-top-10" offset="-9rem"> + <SchemaRelationshipPopup data={data} onClose={() => setOpenPopup(false)} /> + </Popup> + )} + <div + onDragStart={(event) => onDragStart(event)} + onDragStartCapture={(event) => onDragStart(event)} + onMouseDownCapture={(event) => { + if (!event.shiftKey) event.stopPropagation(); + }} + onClickCapture={(event) => { + setOpenPopup(!openPopup); + }} + draggable + // style={{ width: 100, height: 100 }} + > + <div className="text-[0.8rem] border-l-2 bg-offwhite-200 border-l-relation-600 min-w-[8rem]"> + <Handle + style={{ pointerEvents: 'none' }} + className={styles.handleTriangleTop} + id="entitySourceLeft" + position={Position.Top} + type="target" + ></Handle> + {/* <div className={styles.relationNodeAttributesBox} onClick={() => onClickToggleAttributeAnalyticsPopupMenu()} style={{ @@ -95,7 +106,7 @@ export const RelationNode = React.memo(({ id, data }: NodeProps<SchemaReactflowR </span> <span className={styles.nodeSpan}>{data.attributes.length}</span> </div> */} - {/* <div + {/* <div className={styles.relationNodeNodesBox} onClick={() => onClickToggleNodeQualityPopup()} style={{ @@ -117,13 +128,19 @@ export const RelationNode = React.memo(({ id, data }: NodeProps<SchemaReactflowR <span className={styles.nodeSpan}>{data.nodeCount}</span> </div> */} - <div className="p-2 py-1"> - <span className="">{data.collection}</span> - </div> + <div className="p-2 py-1"> + <span className="">{data.collection}</span> + </div> - <Handle className={styles.handleTriangleBottom} style={{ pointerEvents: 'none' }} position={Position.Bottom} type="source"></Handle> + <Handle + className={styles.handleTriangleBottom} + style={{ pointerEvents: 'none' }} + position={Position.Bottom} + type="source" + ></Handle> + </div> </div> - </div> + </> ); }); diff --git a/libs/shared/lib/schema/schema-utils/schema-usecases.ts b/libs/shared/lib/schema/schema-utils/schema-usecases.ts index 3d5ae3482277281cb77ab01374196a921d7e99c5..810e2e337d6b0f900298751faffe5fef6fcc0ef8 100644 --- a/libs/shared/lib/schema/schema-utils/schema-usecases.ts +++ b/libs/shared/lib/schema/schema-utils/schema-usecases.ts @@ -2,7 +2,7 @@ import { SchemaReactflowNodeWithFunctions, SchemaReactflowRelation, SchemaReactf import Graph from 'graphology'; import { Attributes } from 'graphology-types'; import { MarkerType, Edge, Node } from 'reactflow'; -import { SchemaReactflowNode } from '../model/reactflow'; +import { SchemaReactflowEntity } from '../model/reactflow'; import { QueryElementTypes } from '../../querybuilder'; //TODO does not belong here; maybe should go into the GraphPolarisThemeProvider @@ -64,9 +64,6 @@ export function schemaGraphology2Reactflow( initialElements.nodes = createReactFlowNodes(graph); initialElements.edges = createReactFlowEdges(graph, defaultEdgeType); - // initialElements.push(...createReactFlowRelationNodes(graph)); - // initialElements.push(...createReactFlowRelationEdges(graph)); - // console.log(initialElements); return initialElements; } diff --git a/libs/shared/lib/schema/schema-utils/schema-utils.ts b/libs/shared/lib/schema/schema-utils/schema-utils.ts index d816090981a95e6cc896fdbdd04a3adf4a385047..86cebcb2807d183385f518cae2193a5201d4a9d3 100644 --- a/libs/shared/lib/schema/schema-utils/schema-utils.ts +++ b/libs/shared/lib/schema/schema-utils/schema-utils.ts @@ -11,6 +11,8 @@ export class SchemaUtils { if (!nodes || !edges) return schemaGraphology; nodes.forEach((node) => { + console.log(node); + const attributes: SchemaGraphologyNode = { ...node, name: node.name,