Skip to content

Schema render #1235

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 10 additions & 59 deletions frontend/src/components/Graph/GraphViewModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
GraphViewModalProps,
OptionType,
Scheme,
TupleType
} from '../../types';
import { InteractiveNvlWrapper } from '@neo4j-nvl/react';
import NVL from '@neo4j-nvl/base';
Expand All @@ -34,11 +33,8 @@ import CheckboxSelection from './CheckboxSelection';
import ResultOverview from './ResultOverview';
import { ResizePanelDetails } from './ResizePanel';
import GraphPropertiesPanel from './GraphPropertiesPanel';
import { useFileContext } from '../../context/UsersFiles';
import SchemaViz from '../Graph/SchemaViz';
import { getNodeLabelsAndRelTypesFromText } from '../../services/SchemaFromTextAPI';
import { showNormalToast } from '../../utils/Toasts';
import { extractOptions } from '../../utils/Utils';
import { extractGraphSchemaFromRawData } from '../../utils/Utils';

const GraphViewModal: React.FunctionComponent<GraphViewModalProps> = ({
open,
Expand Down Expand Up @@ -67,11 +63,10 @@ const GraphViewModal: React.FunctionComponent<GraphViewModalProps> = ({
const [selected, setSelected] = useState<{ type: EntityType; id: string } | undefined>(undefined);
const [mode, setMode] = useState<boolean>(false);
const graphQueryAbortControllerRef = useRef<AbortController>();
const { model } = useFileContext();
const [openGraphView, setOpenGraphView] = useState<boolean>(false);
const [schemaNodes, setSchemaNodes] = useState<OptionType[]>([]);
const [schemaRels, setSchemaRels] = useState<OptionType[]>([]);
const [schemaLoading, setSchemaLoading] = useState<boolean>(false);
const [viewCheck, setViewcheck] = useState<string>('enhancement');

const graphQuery: string =
graphType.includes('DocumentChunk') && graphType.includes('Entities')
Expand Down Expand Up @@ -360,60 +355,16 @@ const GraphViewModal: React.FunctionComponent<GraphViewModalProps> = ({
setSearchQuery('');
setSelected(undefined);
};

const handleSchemaView = async (
nodeSchema: OptionType[] | ExtendedNode[],
relSchema: OptionType[] | ExtendedRelationship[]
rawNodes: any[],
rawRelationships: any[]
) => {
const { nodes, relationships } = extractGraphSchemaFromRawData(rawNodes, rawRelationships);
setSchemaNodes(nodes as any);
setSchemaRels(relationships as any);
setViewcheck('viz');
setOpenGraphView(true);
setSchemaLoading(true);
try {
const response = await getNodeLabelsAndRelTypesFromText(
model,
JSON.stringify({ nodes: nodeSchema, rels: relSchema }),
false,
false
);
const { status, message, data } = response.data;
if (status !== 'Success') {
showNormalToast(message as string || 'Failed to fetch schema data.');
return;
}
if (!data?.triplets?.length) {
showNormalToast('No nodes or relationships found.');
return;
}
const schemaTuples: TupleType[] = data.triplets
.map((item: string) => {
const matchResult = item.match(/^(.+?)-([A-Z_]+)->(.+)$/);
if (matchResult) {
const [source, rel, target] = matchResult.slice(1).map((s) => s.trim());
return {
value: `${source},${rel},${target}`,
label: `${source} -[:${rel}]-> ${target}`,
source,
target,
type: rel,
};
}
return null;
})
.filter(Boolean) as TupleType[];
const { nodeLabelOptions, relationshipTypeOptions } = extractOptions(schemaTuples);
if (!nodeLabelOptions.length || !relationshipTypeOptions.length) {
showNormalToast('No nodes or relationships found in the response.');
return;
}
setSchemaNodes(nodeLabelOptions);
setSchemaRels(relationshipTypeOptions);
} catch (error: any) {
setSchemaLoading(false);
console.error('Error processing schema:', error);
showNormalToast(error?.message || 'Unexpected error occurred.');
return { success: false, message: error?.message };
}
finally {
setSchemaLoading(false);
}
};


Expand Down Expand Up @@ -551,12 +502,12 @@ const GraphViewModal: React.FunctionComponent<GraphViewModalProps> = ({
</Dialog>
{openGraphView && (
<SchemaViz
schemaLoading={schemaLoading}
open={openGraphView}
setGraphViewOpen={setOpenGraphView}
viewPoint={viewPoint}
nodeValues={schemaNodes}
relationshipValues={schemaRels}
view={viewCheck}
/>
)}
</>
Expand Down
4 changes: 0 additions & 4 deletions frontend/src/components/Graph/SchemaDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,14 @@ const SchemaDropdown: React.FunctionComponent<SchemaDropdownProps> = ({ isDisabl
const [isOpen, setIsOpen] = useState(false);
const btnRef = useRef(null);
const {
userDefinedPattern,
userDefinedNodes,
userDefinedRels,
dbPattern,
dbNodes,
dbRels,
schemaTextPattern,
schemaValNodes,
schemaValRels,
preDefinedNodes,
preDefinedRels,
preDefinedPattern,
} = useFileContext();
const handleSelect = (source: string, nodes: any[], rels: any[]) => {
setIsOpen(false);
Expand Down
30 changes: 21 additions & 9 deletions frontend/src/components/Graph/SchemaViz.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
MagnifyingGlassPlusIconOutline,
} from '@neo4j-ndl/react/icons';
import { IconButtonWithToolTip } from '../UI/IconButtonToolTip';
import { userDefinedGraphSchema } from '../../utils/Utils';
import { userDefinedGraphSchema, generateGraphFromNodeAndRelVals } from '../../utils/Utils';
import { graphLabels, nvlOptions } from '../../utils/Constants';
import ResultOverview from './ResultOverview';
import { ResizePanelDetails } from './ResizePanel';
Expand All @@ -31,6 +31,7 @@ const SchemaViz: React.FunctionComponent<SchemaViewModalProps> = ({
nodeValues,
relationshipValues,
schemaLoading,
view
}) => {
const nvlRef = useRef<NVL>(null);
const [nodes, setNodes] = useState<ExtendedNode[]>([]);
Expand Down Expand Up @@ -69,15 +70,26 @@ const SchemaViz: React.FunctionComponent<SchemaViewModalProps> = ({

useEffect(() => {
if (open) {
if (view !== 'viz') {
setLoading(true);
const { nodes, relationships, scheme } = userDefinedGraphSchema(
(nodeValues as OptionType[]) ?? [],
(relationshipValues as OptionType[]) ?? []
);
setNodes(nodes);
setRelationships(relationships);
setNewScheme(scheme);
setLoading(false);
const { nodes, relationships, scheme } = userDefinedGraphSchema(
(nodeValues as OptionType[]) ?? [],
(relationshipValues as OptionType[]) ?? []
);

setNodes(nodes);
setRelationships(relationships);
setNewScheme(scheme);
setLoading(false);
}
else {
const { nodes, relationships, scheme } =
generateGraphFromNodeAndRelVals(nodeValues as any, relationshipValues as any);

setNodes(nodes);
setRelationships(relationships);
setNewScheme(scheme);
}
}
}, [open]);

Expand Down
1 change: 1 addition & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,7 @@ export interface SchemaViewModalProps {
relationshipValues?: ExtendedRelationship[] | string[] | OptionType[];
selectedRows?: CustomFile[] | undefined;
schemaLoading?:boolean;
view?: string
}

export type UserDefinedGraphSchema = {
Expand Down
128 changes: 123 additions & 5 deletions frontend/src/utils/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,6 @@ export const updateLocalStorage = (userCredentials: UserCredentials, key: string
export const userDefinedGraphSchema = (nodes: OptionType[], relationships: OptionType[]): UserDefinedGraphSchema => {
const schemeVal: Scheme = {};
let iterator = 0;
// Transform nodes and assign colors
const transformedNodes: ExtendedNode[] = nodes.map((node, index) => {
const { label } = node;
if (schemeVal[label] === undefined) {
Expand All @@ -575,23 +574,22 @@ export const userDefinedGraphSchema = (nodes: OptionType[], relationships: Optio
},
};
});
// Create a map of nodes for quick lookup

const nodeMap: Record<string, string> = transformedNodes.reduce((acc, node) => {
acc[node.labels[0]] = node.id;
return acc;
}, {} as Record<string, string>);
// Transform relationships with validation
const transformedRelationships: ExtendedRelationship[] = relationships
.map((rel, index) => {
const parts = rel.value.split(',');
if (parts.length !== 3) {
console.warn(`Invalid relationship format: ${rel}`);
return null; // Skip invalid relationships
return null;
}
const [start, type, end] = parts.map((part) => part.trim());
if (!nodeMap[start] || !nodeMap[end]) {
console.warn(`Missing node(s) for relationship: ${start} -[:${type}]-> ${end}`);
return null; // Skip relationships with missing nodes
return null;
}
return {
id: `rel-${index + 100}`,
Expand Down Expand Up @@ -678,3 +676,123 @@ export const extractOptions = (schemaTuples: TupleType[]) => {
}));
return { nodeLabelOptions, relationshipTypeOptions };
};

type RawNode = {
id: string;
labels: string[];
properties: Record<string, any>;
};
type RawRelationship = {
id: string;
caption: string;
from: string;
to: string;
};

export const extractGraphSchemaFromRawData = (
nodes: RawNode[],
relationships: RawRelationship[]
): {
nodes: OptionType[],
relationships: OptionType[]
} => {
const uniqueLabels = new Set<string>();
const nodeList: OptionType[] = [];
for (const node of nodes) {
for (const label of node.labels) {
if (!uniqueLabels.has(label)) {
uniqueLabels.add(label);
nodeList.push({ label, value: label });
}
}
}
const relList: OptionType[] = [];
for (const rel of relationships) {
const startNodes = nodes.filter((n) => n.id === rel.from);
const endNodes = nodes.filter((n) => n.id === rel.to);
const relType = rel.caption;
for (const startNode of startNodes) {
for (const endNode of endNodes) {
const startLabel = startNode.labels[0];
const endLabel = endNode.labels[0];
relList.push({
label: `${startLabel} -[:${relType}]-> ${endLabel}`,
value: `${startLabel}, ${relType}, ${endLabel}`,
});
}
}
}
return {
nodes: nodeList,
relationships: relList
};
};

export const generateGraphFromNodeAndRelVals = (
nodeVals: OptionType[],
relVals: OptionType[]
): UserDefinedGraphSchema => {
const schemeVal: Scheme = {};
const uniqueNodesMap = new Map<string, ExtendedNode>();
console.log('first rels', relVals)
let nodeIdCounter = 0;
nodeVals.forEach((node) => {
const key = `${node.label}-${node.value}`;
if (!uniqueNodesMap.has(key)) {
if (!schemeVal[node.label]) {
schemeVal[node.label] = calcWordColor(node.label);
}
uniqueNodesMap.set(key, {
id: `node-${nodeIdCounter}`,
color: schemeVal[node.label],
caption: node.label,
labels: [node.label],
properties: {
name: node.value,
indexes: node.label === 'Chunk' ? ['text', 'embedding'] : [],
constraints: [],
},
});
nodeIdCounter++;
}
});
const transformedNodes = Array.from(uniqueNodesMap.values());
const nodeValueToIdMap: Record<string, string> = {};
transformedNodes.forEach((node) => {
// @ts-ignore
nodeValueToIdMap[node.caption] = node.id;
});
const seenRelTypes = new Set<string>();
const transformedRelationships: ExtendedRelationship[] = [];
relVals.forEach((rel) => {
const parts = rel.value.split(',');
if (parts.length !== 3) {
console.warn(`Invalid relationship format: ${rel.value}`);
return;
}
const [start, type, end] = parts.map((part) => part.trim());
if (seenRelTypes.has(type)) {
return;
}
seenRelTypes.add(type);
const fromId = nodeValueToIdMap[start];
const toId = nodeValueToIdMap[end];
if (!fromId || !toId) {
console.warn(`Missing node(s) for relationship: ${start} -[:${type}]-> ${end}`);
return;
}
transformedRelationships.push({
id: `rel-${transformedRelationships.length + 100}`,
from: fromId,
to: toId,
caption: type,
type,
});
});
console.log('new rels', transformedRelationships);
return {
nodes: transformedNodes,
relationships: transformedRelationships,
scheme: schemeVal,
};
};