Skip to content

Commit

Permalink
rml gen
Browse files Browse the repository at this point in the history
  • Loading branch information
ensaremirerol committed Jan 4, 2025
1 parent 0718c11 commit bf5b066
Show file tree
Hide file tree
Showing 24 changed files with 2,010 additions and 176 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -415,4 +415,6 @@ $RECYCLE.BIN/
public/
!server/services/local/
!test/services/local/
!app/src/lib/
!app/src/lib/

bin/
3 changes: 2 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"name": "Python Debugger: FastAPI",
"type": "debugpy",
"request": "launch",
"module": "main"
"module": "main",
"justMyCode": false
},
{
"name": "Launch Frontend",
Expand Down
1,178 changes: 1,119 additions & 59 deletions app/package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
"@blueprintjs/select": "5.3.6",
"@blueprintjs/table": "5.3.0",
"@monaco-editor/react": "^4.6.0",
"@rmlio/yarrrml-parser": "^1.7.2",
"@xyflow/react": "^12.3.6",
"axios": "1.7.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.468.0",
"n3": "^1.23.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.4.0",
Expand All @@ -30,6 +32,7 @@
},
"devDependencies": {
"@eslint/js": "9.16.0",
"@types/n3": "^1.21.1",
"@types/node": "^22.10.2",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
Expand All @@ -47,4 +50,4 @@
"typescript-eslint": "8.18.0",
"vite": "6.0.3"
}
}
}
67 changes: 67 additions & 0 deletions app/src/lib/api/yarrrml_service/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import RMLGenerator from '@rmlio/yarrrml-parser/lib/rml-generator';
import { Writer } from 'n3';
import ApiService from '../../services/api_service';

class YARRRMLService {
private static getApiClient(): ApiService {
return ApiService.getInstance('default');
}

public static async getYARRRMLMapping(
workspaceUuid: string,
mappingUuid: string,
): Promise<string> {
const result = await this.getApiClient().callApi<string>(
`/workspaces/${workspaceUuid}/mapping/${mappingUuid}/yarrrml`,
{
method: 'GET',
parser: data => data as string,
},
);

if (result.type === 'success') {
return result.data;
}

throw new Error(
`Failed to get YARRRML mapping: ${result.message} (status: ${result.status})`,
);
}

public static async yarrrmlToRML(yarrrml: string): Promise<string> {
const y2r = new RMLGenerator();
const quads = y2r.convert(yarrrml);
const writer = new Writer();
writer.addQuads(quads);
return new Promise((resolve, reject) => {
writer.end((error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}

public static async rmlToTTL(rml: string): Promise<string> {
const result = await this.getApiClient().callApi<string>(
'/rml/run-rml-mapping',
{
method: 'POST',
body: rml,
parser: data => data as string,
},
);

if (result.type === 'success') {
return result.data;
}

throw new Error(
`Failed to convert RML to TTL: ${result.message} (status: ${result.status})`,
);
}
}

export default YARRRMLService;
95 changes: 95 additions & 0 deletions app/src/pages/mapping_page/components/MappingDialog/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {
Button,
Dialog,
DialogBody,
DialogFooter,
Tab,
TabPanel,
Tabs,
} from '@blueprintjs/core';
import { Editor } from '@monaco-editor/react';
import { useState } from 'react';

type MappingDialogProps = {
open: boolean;
yarrrml: string;
rml: string;
ttl: string;
onClose: () => void;
};

const MappingDialog = ({
open,
yarrrml,
rml,
ttl,
onClose,
}: MappingDialogProps) => {
const [activeTab, setActiveTab] = useState('yarrrml');

return (
<Dialog
className='bp5-dark'
isOpen={open}
onClose={onClose}
title='Mapping'
style={{ width: '80vw', height: '80vh' }}
>
<DialogBody>
<Tabs
id='Tabs'
onChange={newTabId => setActiveTab(String(newTabId))}
selectedTabId={activeTab}
>
<Tab
id='yarrrml'
title='YARRRML'
panel={
<Editor
height='60vh'
theme='vs-dark'
defaultLanguage='yaml'
defaultValue={yarrrml}
options={{
readOnly: true,
}}
/>
}
/>
<Tab
id='rml'
title='RML'
panel={
<Editor
height='60vh'
theme='vs-dark'
defaultLanguage='yaml'
defaultValue={rml}
options={{ readOnly: true }}
/>
}
/>
<Tab
id='ttl'
title='TTL'
panel={
<Editor
height='60vh'
theme='vs-dark'
defaultLanguage='turtle'
defaultValue={ttl}
options={{ readOnly: true }}
/>
}
/>
</Tabs>
<TabPanel id='yarrrml' parentId='Tabs' selectedTabId={activeTab} />
</DialogBody>
<DialogFooter>
<Button onClick={onClose}>Close</Button>
</DialogFooter>
</Dialog>
);
};

export default MappingDialog;
7 changes: 6 additions & 1 deletion app/src/pages/mapping_page/components/Navbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ type NavbarProps = {
mapping_uuid: string | undefined;
name: string | undefined;
isLoading: string | null;
onCompleteMapping: () => void;
};

const Navbar = ({
workspace_uuid,
mapping_uuid,
name,
isLoading,
onCompleteMapping,
}: NavbarProps) => {
const navigation = useNavigate();
const saveMapping = useMappingPage(state => state.saveMapping);

const isSaved = useMappingPage(state => state.isSaved);
const mapping = useMappingPage(state => state.mapping);

Expand Down Expand Up @@ -60,7 +63,9 @@ const Navbar = ({
>
Save
</Button>
<Button icon='rocket'>Map</Button>
<Button icon='rocket' onClick={onCompleteMapping}>
Map
</Button>
</ButtonGroup>
</BPNavbar.Group>
</BPNavbar>
Expand Down
29 changes: 28 additions & 1 deletion app/src/pages/mapping_page/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import MappingDialog from '@/pages/mapping_page/components/MappingDialog';
import { ReactFlowProvider } from '@xyflow/react';
import { languages } from 'monaco-editor';
import { useEffect, useRef } from 'react';
import { useEffect, useRef, useState } from 'react';
import {
ImperativePanelHandle,
Panel,
Expand Down Expand Up @@ -38,7 +39,9 @@ const MappingPage = () => {
const loadMapping = useMappingPage(state => state.loadMapping);
const setSelectedTab = useMappingPage(state => state.setSelectedTab);
const setIsCollapsed = useMappingPage(state => state.setIsSidePanelCollapsed);
const completeMapping = useMappingPage(state => state.completeMapping);

const [openDialog, setOpenDialog] = useState<'complete_mapping' | null>(null);
useRegisterTheme('mapping-theme', mapping_theme);
useRegisterLanguage('mapping_language', mapping_language, {});
useRegisterCompletionItemProvider('mapping_language', [
Expand Down Expand Up @@ -106,14 +109,38 @@ const MappingPage = () => {
setIsCollapsed(false);
};

const [completeMappingResult, setCompleteMappingResult] = useState<{
yarrrml: string;
rml: string;
ttl: string;
} | null>(null);

const onCompleteMapping = async () => {
if (!props.uuid || !props.mapping_uuid) return;
const { yarrrml, rml, ttl } = await completeMapping(
props.uuid,
props.mapping_uuid,
);
setCompleteMappingResult({ yarrrml, rml, ttl });
setOpenDialog('complete_mapping');
};

return (
<ReactFlowProvider>
<div className='mapping-page'>
<MappingDialog
open={openDialog === 'complete_mapping'}
yarrrml={completeMappingResult?.yarrrml ?? ''}
rml={completeMappingResult?.rml ?? ''}
ttl={completeMappingResult?.ttl ?? ''}
onClose={() => setOpenDialog(null)}
/>
<Navbar
workspace_uuid={props.uuid}
mapping_uuid={props.mapping_uuid}
name={mapping?.name}
isLoading={isLoading}
onCompleteMapping={onCompleteMapping}
/>
<div className='mapping-page-content'>
<PanelGroup direction='horizontal' style={{ height: '100%' }}>
Expand Down
27 changes: 27 additions & 0 deletions app/src/pages/mapping_page/state.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import YARRRMLService from '@/lib/api/yarrrml_service';
import {
XYEdgeType,
XYNodeTypes,
Expand Down Expand Up @@ -37,6 +38,10 @@ interface MappingPageStateActions {
nodes: XYNodeTypes[],
edges: XYEdgeType[],
) => Promise<void>;
completeMapping: (
workspaceUuid: string,
mappingUuid: string,
) => Promise<{ yarrrml: string; rml: string; ttl: string }>;
setIsSaved: (isSaved: boolean) => void;
setSelectedTab: (
selectedTab: 'properties' | 'ai' | 'references' | 'search',
Expand Down Expand Up @@ -126,6 +131,28 @@ const functions: ZustandActions<MappingPageStateActions, MappingPageState> = (
set({ isLoading: null });
}
},
completeMapping: async (workspaceUuid: string, mappingUuid: string) => {
set({ isLoading: 'Creating YARRRML' });
try {
const yarrrml = await YARRRMLService.getYARRRMLMapping(
workspaceUuid,
mappingUuid,
);
set({ isLoading: 'Creating RML' });
const rml = await YARRRMLService.yarrrmlToRML(yarrrml);
set({ isLoading: 'Creating TTL' });
const ttl = await YARRRMLService.rmlToTTL(rml);
set({ error: null, isLoading: null });
return { yarrrml, rml, ttl };
} catch (error) {
if (error instanceof Error) {
set({ error: error.message });
}
throw error;
} finally {
set({ isLoading: null });
}
},
setIsSaved(isSaved: boolean) {
set({ isSaved });
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ const CreateMappingDialog = (props: CreateMappingDialogProps) => {
setError('Please fill all fields');
return;
}
// json_path must end with '[*]'
if (sourceType === 'json' && json_path.value.slice(-3) !== '[*]') {
setError('JSON Path must end with "[*]"');
return;
}

const extra = sourceType === 'json' ? { json_path: json_path.value } : {};

Expand Down
Loading

0 comments on commit bf5b066

Please sign in to comment.