Skip to content

Commit 6c5a5e6

Browse files
data importer with format fixes (#1301)
* data importer with format fixes * Update frontend/src/components/Layout/PageLayout.tsx Co-authored-by: Copilot <[email protected]> * fomrat issues * heading name change * Update frontend/src/components/Layout/PageLayout.tsx Co-authored-by: Copilot <[email protected]> * name correction --------- Co-authored-by: Copilot <[email protected]>
1 parent eac85ef commit 6c5a5e6

File tree

13 files changed

+568
-64
lines changed

13 files changed

+568
-64
lines changed

frontend/src/components/Layout/PageLayout.tsx

Lines changed: 77 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ import { envConnectionAPI } from '../../services/ConnectAPI';
1616
import { healthStatus } from '../../services/HealthStatus';
1717
import { useAuth0 } from '@auth0/auth0-react';
1818
import { showErrorToast } from '../../utils/Toasts';
19-
import { APP_SOURCES, LOCAL_KEYS } from '../../utils/Constants';
19+
import { APP_SOURCES } from '../../utils/Constants';
2020
import { createDefaultFormData } from '../../API/Index';
2121
import LoadDBSchemaDialog from '../Popups/GraphEnhancementDialog/EnitityExtraction/LoadExistingSchema';
2222
import PredefinedSchemaDialog from '../Popups/GraphEnhancementDialog/EnitityExtraction/PredefinedSchemaDialog';
2323
import { SKIP_AUTH } from '../../utils/Constants';
2424
import { useNavigate } from 'react-router';
2525
import { deduplicateByFullPattern, deduplicateNodeByValue } from '../../utils/Utils';
26+
import DataImporterSchemaDialog from '../Popups/GraphEnhancementDialog/EnitityExtraction/DataImporter';
2627

2728
const GCSModal = lazy(() => import('../DataSources/GCS/GCSModal'));
2829
const S3Modal = lazy(() => import('../DataSources/AWS/S3Modal'));
@@ -186,13 +187,20 @@ const PageLayout: React.FC = () => {
186187
setSchemaValRels,
187188
setDbNodes,
188189
setDbRels,
189-
setSchemaView,
190190
setPreDefinedNodes,
191191
setPreDefinedRels,
192192
setPreDefinedPattern,
193193
allPatterns,
194194
selectedNodes,
195195
selectedRels,
196+
dataImporterSchemaDialog,
197+
setDataImporterSchemaDialog,
198+
setImporterPattern,
199+
setImporterNodes,
200+
setImporterRels,
201+
setSourceOptions,
202+
setTargetOptions,
203+
setTypeOptions,
196204
} = useFileContext();
197205
const navigate = useNavigate();
198206
const { user, isAuthenticated } = useAuth0();
@@ -380,10 +388,9 @@ const PageLayout: React.FC = () => {
380388
const combined = [...rels, ...prevRels];
381389
return deduplicateByFullPattern(combined);
382390
});
383-
setSchemaView('text');
384-
localStorage.setItem(LOCAL_KEYS.source, JSON.stringify(updatedSource));
385-
localStorage.setItem(LOCAL_KEYS.type, JSON.stringify(updatedType));
386-
localStorage.setItem(LOCAL_KEYS.target, JSON.stringify(updatedTarget));
391+
setSourceOptions((prev) => [...prev, ...updatedSource]);
392+
setTargetOptions((prev) => [...prev, ...updatedTarget]);
393+
setTypeOptions((prev) => [...prev, ...updatedType]);
387394
},
388395
[]
389396
);
@@ -409,7 +416,6 @@ const PageLayout: React.FC = () => {
409416
triggeredFrom: 'loadExistingSchemaApply',
410417
show: true,
411418
});
412-
setSchemaView('db');
413419
setDbNodes(nodes);
414420
setCombinedNodesVal((prevNodes: OptionType[]) => {
415421
const combined = [...nodes, ...prevNodes];
@@ -420,9 +426,9 @@ const PageLayout: React.FC = () => {
420426
const combined = [...rels, ...prevRels];
421427
return deduplicateByFullPattern(combined);
422428
});
423-
localStorage.setItem(LOCAL_KEYS.source, JSON.stringify(updatedSource));
424-
localStorage.setItem(LOCAL_KEYS.type, JSON.stringify(updatedType));
425-
localStorage.setItem(LOCAL_KEYS.target, JSON.stringify(updatedTarget));
429+
setSourceOptions((prev) => [...prev, ...updatedSource]);
430+
setTargetOptions((prev) => [...prev, ...updatedTarget]);
431+
setTypeOptions((prev) => [...prev, ...updatedType]);
426432
},
427433
[]
428434
);
@@ -447,7 +453,6 @@ const PageLayout: React.FC = () => {
447453
triggeredFrom: 'predefinedSchemaApply',
448454
show: true,
449455
});
450-
setSchemaView('preDefined');
451456
setPreDefinedNodes(nodes);
452457
setCombinedNodesVal((prevNodes: OptionType[]) => {
453458
const combined = [...nodes, ...prevNodes];
@@ -458,9 +463,47 @@ const PageLayout: React.FC = () => {
458463
const combined = [...rels, ...prevRels];
459464
return deduplicateByFullPattern(combined);
460465
});
461-
localStorage.setItem(LOCAL_KEYS.source, JSON.stringify(updatedSource));
462-
localStorage.setItem(LOCAL_KEYS.type, JSON.stringify(updatedType));
463-
localStorage.setItem(LOCAL_KEYS.target, JSON.stringify(updatedTarget));
466+
setSourceOptions((prev) => [...prev, ...updatedSource]);
467+
setTargetOptions((prev) => [...prev, ...updatedTarget]);
468+
setTypeOptions((prev) => [...prev, ...updatedType]);
469+
},
470+
[]
471+
);
472+
473+
const handleImporterApply = useCallback(
474+
(
475+
newPatterns: string[],
476+
nodes: OptionType[],
477+
rels: OptionType[],
478+
updatedSource: OptionType[],
479+
updatedTarget: OptionType[],
480+
updatedType: OptionType[]
481+
) => {
482+
setImporterPattern((prevPatterns: string[]) => {
483+
const uniquePatterns = Array.from(new Set([...newPatterns, ...prevPatterns]));
484+
return uniquePatterns;
485+
});
486+
setCombinedPatternsVal((prevPatterns: string[]) => {
487+
const uniquePatterns = Array.from(new Set([...newPatterns, ...prevPatterns]));
488+
return uniquePatterns;
489+
});
490+
setDataImporterSchemaDialog({
491+
triggeredFrom: 'importerSchemaApply',
492+
show: true,
493+
});
494+
setImporterNodes(nodes);
495+
setCombinedNodesVal((prevNodes: OptionType[]) => {
496+
const combined = [...nodes, ...prevNodes];
497+
return deduplicateNodeByValue(combined);
498+
});
499+
setImporterRels(rels);
500+
setCombinedRelsVal((prevRels: OptionType[]) => {
501+
const combined = [...rels, ...prevRels];
502+
return deduplicateByFullPattern(combined);
503+
});
504+
setSourceOptions((prev) => [...prev, ...updatedSource]);
505+
setTargetOptions((prev) => [...prev, ...updatedTarget]);
506+
setTypeOptions((prev) => [...prev, ...updatedType]);
464507
},
465508
[]
466509
);
@@ -477,6 +520,10 @@ const PageLayout: React.FC = () => {
477520
setShowTextFromSchemaDialog({ triggeredFrom: 'schemadialog', show: true });
478521
}, []);
479522

523+
const openDataImporterSchema = useCallback(() => {
524+
setDataImporterSchemaDialog({ triggeredFrom: 'schemadialog', show: true });
525+
}, []);
526+
480527
const openChatBot = useCallback(() => setShowChatBot(true), []);
481528

482529
return (
@@ -565,6 +612,20 @@ const PageLayout: React.FC = () => {
565612
}}
566613
onApply={handlePredinedApply}
567614
></PredefinedSchemaDialog>
615+
<DataImporterSchemaDialog
616+
open={dataImporterSchemaDialog.show}
617+
onClose={() => {
618+
setDataImporterSchemaDialog({ triggeredFrom: '', show: false });
619+
switch (dataImporterSchemaDialog.triggeredFrom) {
620+
case 'enhancementtab':
621+
toggleEnhancementDialog();
622+
break;
623+
default:
624+
break;
625+
}
626+
}}
627+
onApply={handleImporterApply}
628+
></DataImporterSchemaDialog>
568629
{isLargeDesktop ? (
569630
<div
570631
className={`layout-wrapper ${!isLeftExpanded ? 'drawerdropzoneclosed' : ''} ${
@@ -596,6 +657,7 @@ const PageLayout: React.FC = () => {
596657
openTextSchema={openTextSchema}
597658
openLoadSchema={openLoadSchema}
598659
openPredefinedSchema={openPredefinedSchema}
660+
openDataImporterSchema={openDataImporterSchema}
599661
showEnhancementDialog={showEnhancementDialog}
600662
toggleEnhancementDialog={toggleEnhancementDialog}
601663
setOpenConnection={setOpenConnection}
@@ -670,6 +732,7 @@ const PageLayout: React.FC = () => {
670732
openTextSchema={openTextSchema}
671733
openLoadSchema={openLoadSchema}
672734
openPredefinedSchema={openPredefinedSchema}
735+
openDataImporterSchema={openDataImporterSchema}
673736
showEnhancementDialog={showEnhancementDialog}
674737
toggleEnhancementDialog={toggleEnhancementDialog}
675738
setOpenConnection={setOpenConnection}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import { Button, Dialog } from '@neo4j-ndl/react';
2+
import { useState } from 'react';
3+
import { OptionType, TupleType } from '../../../../types';
4+
import { extractOptions, updateSourceTargetTypeOptions } from '../../../../utils/Utils';
5+
import { useFileContext } from '../../../../context/UsersFiles';
6+
import ImporterInput from './ImporterInput';
7+
import SchemaViz from '../../../Graph/SchemaViz';
8+
import PatternContainer from './PatternContainer';
9+
import UploadJsonData from './UploadJsonData';
10+
11+
interface DataImporterDialogProps {
12+
open: boolean;
13+
onClose: () => void;
14+
onApply: (
15+
patterns: string[],
16+
nodeLabels: OptionType[],
17+
relationshipLabels: OptionType[],
18+
updatedSource: OptionType[],
19+
updatedTarget: OptionType[],
20+
updatedType: OptionType[]
21+
) => void;
22+
}
23+
24+
const DataImporterSchemaDialog = ({ open, onClose, onApply }: DataImporterDialogProps) => {
25+
const {
26+
importerPattern,
27+
setImporterPattern,
28+
importerNodes,
29+
setImporterNodes,
30+
importerRels,
31+
setImporterRels,
32+
sourceOptions,
33+
setSourceOptions,
34+
targetOptions,
35+
setTargetOptions,
36+
typeOptions,
37+
setTypeOptions,
38+
} = useFileContext();
39+
40+
const [openGraphView, setOpenGraphView] = useState<boolean>(false);
41+
const [viewPoint, setViewPoint] = useState<string>('');
42+
const handleCancel = () => {
43+
onClose();
44+
setImporterPattern([]);
45+
setImporterNodes([]);
46+
setImporterRels([]);
47+
};
48+
49+
const handleImporterCheck = async () => {
50+
const [newSourceOptions, newTargetOptions, newTypeOptions] = await updateSourceTargetTypeOptions({
51+
patterns: importerPattern.map((label) => ({ label, value: label })),
52+
currentSourceOptions: sourceOptions,
53+
currentTargetOptions: targetOptions,
54+
currentTypeOptions: typeOptions,
55+
setSourceOptions,
56+
setTargetOptions,
57+
setTypeOptions,
58+
});
59+
onApply(importerPattern, importerNodes, importerRels, newSourceOptions, newTargetOptions, newTypeOptions);
60+
onClose();
61+
};
62+
63+
const handleRemovePattern = (patternToRemove: string) => {
64+
const updatedPatterns = importerPattern.filter((p) => p !== patternToRemove);
65+
if (updatedPatterns.length === 0) {
66+
setImporterPattern([]);
67+
setImporterNodes([]);
68+
setImporterRels([]);
69+
return;
70+
}
71+
const updatedTuples: TupleType[] = updatedPatterns
72+
.map((item: string) => {
73+
const matchResult = item.match(/^(.+?)-\[:([A-Z_]+)\]->(.+)$/);
74+
if (matchResult) {
75+
const [source, rel, target] = matchResult.slice(1).map((s) => s.trim());
76+
return {
77+
value: `${source},${rel},${target}`,
78+
label: `${source} -[:${rel}]-> ${target}`,
79+
source,
80+
target,
81+
type: rel,
82+
};
83+
}
84+
return null;
85+
})
86+
.filter(Boolean) as TupleType[];
87+
const { nodeLabelOptions, relationshipTypeOptions } = extractOptions(updatedTuples);
88+
setImporterPattern(updatedPatterns);
89+
setImporterNodes(nodeLabelOptions);
90+
setImporterRels(relationshipTypeOptions);
91+
};
92+
93+
const handleSchemaView = () => {
94+
setOpenGraphView(true);
95+
setViewPoint('showSchemaView');
96+
};
97+
98+
return (
99+
<>
100+
<Dialog isOpen={open} onClose={handleCancel}>
101+
<Dialog.Header>JSON Data Graph Extraction Settings</Dialog.Header>
102+
<Dialog.Content className='n-flex n-flex-col n-gap-token-6 p-6'>
103+
<ImporterInput />
104+
<UploadJsonData
105+
onSchemaExtracted={({ nodeLabels, relationshipTypes, relationshipObjectTypes, nodeObjectTypes }) => {
106+
const nodeLabelMap = Object.fromEntries(nodeLabels.map((n) => [n.$id, n.token]));
107+
const relTypeMap = Object.fromEntries(relationshipTypes.map((r) => [r.$id, r.token]));
108+
const nodeIdToLabel: Record<string, string> = {};
109+
nodeObjectTypes.forEach((nodeObj: any) => {
110+
const labelRef = nodeObj.labels?.[0]?.$ref;
111+
if (labelRef && nodeLabelMap[labelRef.slice(1)]) {
112+
nodeIdToLabel[nodeObj.$id] = nodeLabelMap[labelRef.slice(1)];
113+
}
114+
});
115+
116+
const patterns = relationshipObjectTypes.map((relObj) => {
117+
const fromId = relObj.from.$ref.slice(1);
118+
const toId = relObj.to.$ref.slice(1);
119+
const relId = relObj.type.$ref.slice(1);
120+
const fromLabel = nodeIdToLabel[fromId] || 'source';
121+
const toLabel = nodeIdToLabel[toId] || 'target';
122+
const relLabel = relTypeMap[relId] || 'type';
123+
const pattern = `${fromLabel} -[:${relLabel}]-> ${toLabel}`;
124+
return pattern;
125+
});
126+
127+
const importerTuples = patterns
128+
.map((p) => {
129+
const match = p.match(/^(.+?) -\[:(.+?)\]-> (.+)$/);
130+
if (!match) {
131+
return null;
132+
}
133+
const [_, source, type, target] = match;
134+
return {
135+
label: `${source} -[:${type}]-> ${target}`,
136+
value: `${source},${type},${target}`,
137+
source,
138+
target,
139+
type,
140+
};
141+
})
142+
.filter(Boolean) as TupleType[];
143+
const { nodeLabelOptions, relationshipTypeOptions } = extractOptions(importerTuples);
144+
setImporterNodes(nodeLabelOptions);
145+
setImporterRels(relationshipTypeOptions);
146+
setImporterPattern(patterns);
147+
}}
148+
/>
149+
<PatternContainer
150+
pattern={importerPattern}
151+
handleRemove={handleRemovePattern}
152+
handleSchemaView={handleSchemaView}
153+
nodes={importerNodes}
154+
rels={importerRels}
155+
/>
156+
<Dialog.Actions className='n-flex n-justify-end n-gap-token-4 pt-4'>
157+
<Button onClick={handleCancel} isDisabled={importerPattern.length === 0}>
158+
Cancel
159+
</Button>
160+
<Button onClick={handleImporterCheck} isDisabled={importerPattern.length === 0}>
161+
Apply
162+
</Button>
163+
</Dialog.Actions>
164+
</Dialog.Content>
165+
</Dialog>
166+
{openGraphView && (
167+
<SchemaViz
168+
open={openGraphView}
169+
setGraphViewOpen={setOpenGraphView}
170+
viewPoint={viewPoint}
171+
nodeValues={importerNodes ?? []}
172+
relationshipValues={importerRels ?? []}
173+
/>
174+
)}
175+
</>
176+
);
177+
};
178+
179+
export default DataImporterSchemaDialog;

0 commit comments

Comments
 (0)