Skip to content

Commit 8f62057

Browse files
authored
staging changes to main
1 parent 4156647 commit 8f62057

File tree

8 files changed

+522
-231
lines changed

8 files changed

+522
-231
lines changed

backend/src/document_sources/gcs_bucket.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ def delete_file_from_gcs(bucket_name,folder_name, file_name):
138138
blob.delete()
139139
logging.info('File deleted from GCS successfully')
140140
except Exception as e:
141+
raise Exception(e)
141142

142143
def copy_failed_file(source_bucket_name,dest_bucket_name,folder_name, file_name):
143144
try:
Lines changed: 387 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,387 @@
1+
import { Banner, Dialog, Flex, IconButtonArray, LoadingSpinner } from '@neo4j-ndl/react';
2+
import { useCallback, useEffect, useRef, useState } from 'react';
3+
import { GraphType, GraphViewModalProps, OptionType, Scheme, UserCredentials } from '../../types';
4+
import { InteractiveNvlWrapper } from '@neo4j-nvl/react';
5+
import NVL from '@neo4j-nvl/base';
6+
import type { Node, Relationship } from '@neo4j-nvl/base';
7+
import { Resizable } from 're-resizable';
8+
import {
9+
ArrowPathIconOutline,
10+
DragIcon,
11+
FitToScreenIcon,
12+
MagnifyingGlassMinusIconOutline,
13+
MagnifyingGlassPlusIconOutline,
14+
} from '@neo4j-ndl/react/icons';
15+
import IconButtonWithToolTip from '../UI/IconButtonToolTip';
16+
import { filterData, processGraphData } from '../../utils/Utils';
17+
import { useCredentials } from '../../context/UserCredentials';
18+
import { LegendsChip } from './LegendsChip';
19+
import graphQueryAPI from '../../services/GraphQuery';
20+
import {
21+
entityGraph,
22+
graphQuery,
23+
graphView,
24+
intitalGraphType,
25+
knowledgeGraph,
26+
lexicalGraph,
27+
mouseEventCallbacks,
28+
nvlOptions,
29+
queryMap,
30+
} from '../../utils/Constants';
31+
// import CheckboxSelection from './CheckboxSelection';
32+
import DropdownComponent from '../Dropdown';
33+
const GraphViewModal: React.FunctionComponent<GraphViewModalProps> = ({
34+
open,
35+
inspectedName,
36+
setGraphViewOpen,
37+
viewPoint,
38+
nodeValues,
39+
relationshipValues,
40+
selectedRows,
41+
}) => {
42+
const nvlRef = useRef<NVL>(null);
43+
const [nodes, setNodes] = useState<Node[]>([]);
44+
const [relationships, setRelationships] = useState<Relationship[]>([]);
45+
const [graphType, setGraphType] = useState<GraphType[]>(intitalGraphType);
46+
const [allNodes, setAllNodes] = useState<Node[]>([]);
47+
const [allRelationships, setAllRelationships] = useState<Relationship[]>([]);
48+
const [loading, setLoading] = useState<boolean>(false);
49+
const [status, setStatus] = useState<'unknown' | 'success' | 'danger'>('unknown');
50+
const [statusMessage, setStatusMessage] = useState<string>('');
51+
const { userCredentials } = useCredentials();
52+
const [scheme, setScheme] = useState<Scheme>({});
53+
const [newScheme, setNewScheme] = useState<Scheme>({});
54+
const [dropdownVal, setDropdownVal] = useState<OptionType>({
55+
label: 'Knowledge Graph',
56+
value: queryMap.DocChunkEntities,
57+
});
58+
59+
// const handleCheckboxChange = (graph: GraphType) => {
60+
// const currentIndex = graphType.indexOf(graph);
61+
// const newGraphSelected = [...graphType];
62+
// if (currentIndex === -1) {
63+
// newGraphSelected.push(graph);
64+
// } else {
65+
// newGraphSelected.splice(currentIndex, 1);
66+
// }
67+
// setGraphType(newGraphSelected);
68+
// };
69+
70+
const handleZoomToFit = () => {
71+
nvlRef.current?.fit(
72+
allNodes.map((node) => node.id),
73+
{}
74+
);
75+
};
76+
77+
// Destroy the component
78+
useEffect(() => {
79+
const timeoutId = setTimeout(() => {
80+
handleZoomToFit();
81+
}, 10);
82+
return () => {
83+
nvlRef.current?.destroy();
84+
setGraphType(intitalGraphType);
85+
clearTimeout(timeoutId);
86+
setScheme({});
87+
setNodes([]);
88+
setRelationships([]);
89+
setAllNodes([]);
90+
setAllRelationships([]);
91+
setDropdownVal({ label: 'Knowledge Graph', value: queryMap.DocChunkEntities });
92+
};
93+
}, []);
94+
95+
// To get nodes and relations on basis of view
96+
const fetchData = useCallback(async () => {
97+
try {
98+
const nodeRelationshipData =
99+
viewPoint === 'showGraphView'
100+
? await graphQueryAPI(
101+
userCredentials as UserCredentials,
102+
graphQuery,
103+
selectedRows?.map((f) => f.name)
104+
)
105+
: await graphQueryAPI(userCredentials as UserCredentials, graphQuery, [inspectedName ?? '']);
106+
return nodeRelationshipData;
107+
} catch (error: any) {
108+
console.log(error);
109+
}
110+
}, [viewPoint, selectedRows, graphQuery, inspectedName, userCredentials]);
111+
112+
// Api call to get the nodes and relations
113+
const graphApi = async () => {
114+
try {
115+
const result = await fetchData();
116+
if (result && result.data.data.nodes.length > 0) {
117+
const neoNodes = result.data.data.nodes.map((f: Node) => f);
118+
const neoRels = result.data.data.relationships.map((f: Relationship) => f);
119+
const { finalNodes, finalRels, schemeVal } = processGraphData(neoNodes, neoRels);
120+
setAllNodes(finalNodes);
121+
setAllRelationships(finalRels);
122+
setScheme(schemeVal);
123+
setNodes(finalNodes);
124+
setRelationships(finalRels);
125+
setNewScheme(schemeVal);
126+
setLoading(false);
127+
} else {
128+
setLoading(false);
129+
setStatus('danger');
130+
setStatusMessage(`No Nodes and Relations for the ${inspectedName} file`);
131+
}
132+
} catch (error: any) {
133+
setLoading(false);
134+
setStatus('danger');
135+
setStatusMessage(error.message);
136+
}
137+
};
138+
139+
useEffect(() => {
140+
if (open) {
141+
setLoading(true);
142+
if (viewPoint !== 'chatInfoView') {
143+
graphApi();
144+
} else {
145+
const { finalNodes, finalRels, schemeVal } = processGraphData(nodeValues ?? [], relationshipValues ?? []);
146+
setAllNodes(finalNodes);
147+
setAllRelationships(finalRels);
148+
setScheme(schemeVal);
149+
setNodes(finalNodes);
150+
setRelationships(finalRels);
151+
setNewScheme(schemeVal);
152+
setLoading(false);
153+
}
154+
}
155+
}, [open]);
156+
157+
if (!open) {
158+
return <></>;
159+
}
160+
161+
const headerTitle =
162+
viewPoint === 'showGraphView' || viewPoint === 'chatInfoView'
163+
? 'Generated Graph'
164+
: `Inspect Generated Graph from ${inspectedName}`;
165+
166+
const dropDownView = viewPoint !== 'chatInfoView';
167+
168+
const nvlCallbacks = {
169+
onLayoutComputing(isComputing: boolean) {
170+
if (!isComputing) {
171+
handleZoomToFit();
172+
}
173+
},
174+
};
175+
176+
// To handle the current zoom in function of graph
177+
const handleZoomIn = () => {
178+
nvlRef.current?.setZoom(nvlRef.current.getScale() * 1.3);
179+
};
180+
181+
// To handle the current zoom out function of graph
182+
const handleZoomOut = () => {
183+
nvlRef.current?.setZoom(nvlRef.current.getScale() * 0.7);
184+
};
185+
186+
// Refresh the graph with nodes and relations if file is processing
187+
const handleRefresh = () => {
188+
graphApi();
189+
setGraphType(intitalGraphType);
190+
setDropdownVal({ label: 'Knowledge Graph', value: queryMap.DocChunkEntities });
191+
};
192+
193+
// when modal closes reset all states to default
194+
const onClose = () => {
195+
setStatus('unknown');
196+
setStatusMessage('');
197+
setGraphViewOpen(false);
198+
setScheme({});
199+
setGraphType(intitalGraphType);
200+
setNodes([]);
201+
setRelationships([]);
202+
setDropdownVal({ label: 'Knowledge Graph', value: queryMap.DocChunkEntities });
203+
};
204+
205+
// sort the legends in with Chunk and Document always the first two values
206+
const legendCheck = Object.keys(newScheme).sort((a, b) => {
207+
if (a === 'Document' || a === 'Chunk') {
208+
return -1;
209+
} else if (b === 'Document' || b === 'Chunk') {
210+
return 1;
211+
}
212+
return a.localeCompare(b);
213+
});
214+
215+
// setting the default dropdown values
216+
const getDropdownDefaultValue = () => {
217+
if (graphType.includes('Document') && graphType.includes('Chunk') && graphType.includes('Entities')) {
218+
return knowledgeGraph;
219+
}
220+
if (graphType.includes('Document') && graphType.includes('Chunk')) {
221+
return lexicalGraph;
222+
}
223+
if (graphType.includes('Entities')) {
224+
return entityGraph;
225+
}
226+
return '';
227+
};
228+
229+
// Make a function call to store the nodes and relations in their states
230+
const initGraph = (graphType: GraphType[], finalNodes: Node[], finalRels: Relationship[], schemeVal: Scheme) => {
231+
if (allNodes.length > 0 && allRelationships.length > 0) {
232+
const { filteredNodes, filteredRelations, filteredScheme } = filterData(
233+
graphType,
234+
finalNodes ?? [],
235+
finalRels ?? [],
236+
schemeVal
237+
);
238+
setNodes(filteredNodes);
239+
setRelationships(filteredRelations);
240+
setNewScheme(filteredScheme);
241+
}
242+
};
243+
244+
// handle dropdown value change and call the init graph method
245+
const handleDropdownChange = (selectedOption: OptionType | null | void) => {
246+
if (selectedOption?.value) {
247+
const selectedValue = selectedOption.value;
248+
let newGraphType: GraphType[] = [];
249+
if (selectedValue === 'entities') {
250+
newGraphType = ['Entities'];
251+
} else if (selectedValue === queryMap.DocChunks) {
252+
newGraphType = ['Document', 'Chunk'];
253+
} else if (selectedValue === queryMap.DocChunkEntities) {
254+
newGraphType = ['Document', 'Entities', 'Chunk'];
255+
}
256+
setGraphType(newGraphType);
257+
setDropdownVal(selectedOption);
258+
initGraph(newGraphType, allNodes, allRelationships, scheme);
259+
}
260+
};
261+
return (
262+
<>
263+
<Dialog
264+
modalProps={{
265+
className: 'h-[90%]',
266+
id: 'default-menu',
267+
}}
268+
size='unset'
269+
open={open}
270+
aria-labelledby='form-dialog-title'
271+
disableCloseButton={false}
272+
onClose={onClose}
273+
>
274+
<Dialog.Header id='graph-title'>
275+
{headerTitle}
276+
<Flex className='w-full' alignItems='center' justifyContent='flex-end' flexDirection='row'>
277+
{/* {checkBoxView && (
278+
<CheckboxSelection graphType={graphType} loading={loading} handleChange={handleCheckboxChange} />
279+
)} */}
280+
{dropDownView && (
281+
<DropdownComponent
282+
onSelect={handleDropdownChange}
283+
options={graphView}
284+
placeholder='Select Graph Type'
285+
defaultValue={getDropdownDefaultValue()}
286+
view='GraphView'
287+
isDisabled={loading}
288+
value={dropdownVal}
289+
/>
290+
)}
291+
</Flex>
292+
</Dialog.Header>
293+
<Dialog.Content className='flex flex-col n-gap-token-4 w-full grow overflow-auto border border-palette-neutral-border-weak'>
294+
<div className='bg-white relative w-full h-full max-h-full'>
295+
{loading ? (
296+
<div className='my-40 flex items-center justify-center'>
297+
<LoadingSpinner size='large' />
298+
</div>
299+
) : status !== 'unknown' ? (
300+
<div className='my-40 flex items-center justify-center'>
301+
<Banner name='graph banner' description={statusMessage} type={status} />
302+
</div>
303+
) : nodes.length === 0 || relationships.length === 0 ? (
304+
<div className='my-40 flex items-center justify-center'>
305+
<Banner name='graph banner' description='No Entities Found' type='danger' />
306+
</div>
307+
) : (
308+
<>
309+
<div className='flex' style={{ height: '100%' }}>
310+
<div className='bg-palette-neutral-bg-default relative' style={{ width: '100%', flex: '1' }}>
311+
<InteractiveNvlWrapper
312+
nodes={nodes}
313+
rels={relationships}
314+
nvlOptions={nvlOptions}
315+
ref={nvlRef}
316+
mouseEventCallbacks={{ ...mouseEventCallbacks }}
317+
interactionOptions={{
318+
selectOnClick: true,
319+
}}
320+
nvlCallbacks={nvlCallbacks}
321+
/>
322+
<IconButtonArray orientation='vertical' floating className='absolute bottom-4 right-4'>
323+
{viewPoint !== 'chatInfoView' && (
324+
<IconButtonWithToolTip
325+
label='Refresh'
326+
text='Refresh graph'
327+
onClick={handleRefresh}
328+
placement='left'
329+
>
330+
<ArrowPathIconOutline />
331+
</IconButtonWithToolTip>
332+
)}
333+
<IconButtonWithToolTip label='Zoomin' text='Zoom in' onClick={handleZoomIn} placement='left'>
334+
<MagnifyingGlassPlusIconOutline />
335+
</IconButtonWithToolTip>
336+
<IconButtonWithToolTip label='Zoom out' text='Zoom out' onClick={handleZoomOut} placement='left'>
337+
<MagnifyingGlassMinusIconOutline />
338+
</IconButtonWithToolTip>
339+
<IconButtonWithToolTip
340+
label='Zoom to fit'
341+
text='Zoom to fit'
342+
onClick={handleZoomToFit}
343+
placement='left'
344+
>
345+
<FitToScreenIcon />
346+
</IconButtonWithToolTip>
347+
</IconButtonArray>
348+
</div>
349+
<Resizable
350+
defaultSize={{
351+
width: 400,
352+
height: '100%',
353+
}}
354+
minWidth={230}
355+
maxWidth='72%'
356+
enable={{
357+
top: false,
358+
right: false,
359+
bottom: false,
360+
left: true,
361+
topRight: false,
362+
bottomRight: false,
363+
bottomLeft: false,
364+
topLeft: false,
365+
}}
366+
handleComponent={{ left: <DragIcon className='absolute top-1/2 h-6 w-6' /> }}
367+
handleClasses={{ left: 'ml-1' }}
368+
>
369+
<div className='legend_div'>
370+
<h4 className='py-4 pt-3 ml-2'>Result Overview</h4>
371+
<div className='flex gap-2 flex-wrap ml-2'>
372+
{legendCheck.map((key, index) => (
373+
<LegendsChip key={index} title={key} scheme={newScheme} nodes={nodes} />
374+
))}
375+
</div>
376+
</div>
377+
</Resizable>
378+
</div>
379+
</>
380+
)}
381+
</div>
382+
</Dialog.Content>
383+
</Dialog>
384+
</>
385+
);
386+
};
387+
export default GraphViewModal;

0 commit comments

Comments
 (0)