Skip to content
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

PKG visualization - frontend #98

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
6 changes: 5 additions & 1 deletion pkg_api/pkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"""

import io
import os
import logging
import uuid
from collections import defaultdict
Expand All @@ -29,7 +30,10 @@
from pkg_api.core.pkg_types import URI
from pkg_api.mapping_vocab import MappingVocab

DEFAULT_VISUALIZATION_PATH = "data/pkg_visualizations"
ROOT_DIR = os.path.dirname(
os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
)
DEFAULT_VISUALIZATION_PATH = ROOT_DIR + "/data/pkg_visualizations"


class PKG:
Expand Down
2 changes: 1 addition & 1 deletion pkg_api/server/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

# TODO: Retrieve namespace from the mapping class
# See issue: https://github.com/iai-group/pkg-api/issues/13
NS = "http://example.org/pkg/"
NS = "http://example.com#"


def create_user_uri(username: str) -> str:
Expand Down
22 changes: 11 additions & 11 deletions pkg_api/server/pkg_exploration.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
"""PKG Exploration Resource."""
from typing import Any, Dict, Tuple
from typing import Any, Dict, Tuple, Union

from flask import request
import flask
from flask import Response, request
from flask_restful import Resource

from pkg_api.server.utils import open_pkg, parse_query_request_data


class PKGExplorationResource(Resource):
def get(self) -> Tuple[Dict[str, Any], int]:
def get(self) -> Union[Response, Tuple[Dict[str, Any], int]]:
"""Returns the PKG visualization.

Returns:
A dictionary with the path to PKG visualization and the status code.
A response containing the image of the PKG graph.
"""
data = request.json
data = dict(request.args)
try:
pkg = open_pkg(data)
except Exception as e:
Expand All @@ -23,10 +24,7 @@ def get(self) -> Tuple[Dict[str, Any], int]:
graph_img_path = pkg.visualize_graph()
pkg.close()

return {
"message": "PKG visualized successfully.",
"img_path": graph_img_path,
}, 200
return flask.send_file(graph_img_path, mimetype="image/png")

def post(self) -> Tuple[Dict[str, Any], int]:
"""Executes the SPARQL query.
Expand All @@ -44,7 +42,9 @@ def post(self) -> Tuple[Dict[str, Any], int]:
sparql_query = parse_query_request_data(data)

if "SELECT" in sparql_query:
result = str(pkg.execute_sparql_query(sparql_query))
result = pkg.execute_sparql_query(sparql_query)
# TODO: Update pkg.visualize_graph() to return partial graph based
# on the query result
Comment on lines +46 to +47
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: missing dot at the end.
Can you create a follow-up issue and link it here?

else:
return {
"message": (
Expand All @@ -57,5 +57,5 @@ def post(self) -> Tuple[Dict[str, Any], int]:

return {
"message": "SPARQL query executed successfully.",
"data": result,
"result": str(result.bindings),
WerLaj marked this conversation as resolved.
Show resolved Hide resolved
}, 200
4 changes: 3 additions & 1 deletion pkg_client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"react": "^18.2.0",
"react-bootstrap": "^2.9.1",
"react-dom": "^18.2.0",
"react-icons": "^4.12.0",
"react-router-dom": "^6.21.0",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
Expand Down Expand Up @@ -43,4 +45,4 @@
"last 1 safari version"
]
}
}
}
7 changes: 2 additions & 5 deletions pkg_client/src/App.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
.App {
text-align: center;
}

.App-logo {
height: 40vmin;
pointer-events: none;
Expand Down Expand Up @@ -32,7 +28,8 @@
from {
transform: rotate(0deg);
}

to {
transform: rotate(360deg);
}
}
}
6 changes: 2 additions & 4 deletions pkg_client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useContext } from "react";
import "./App.css";
import APIHandler from "./components/APIHandler";
import LoginForm from "./components/LoginForm";
import Container from 'react-bootstrap/Container'
import Container from 'react-bootstrap/Container';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: use double quotes as other imports.

import { UserContext } from "./contexts/UserContext";

function App() {
Expand All @@ -13,9 +13,7 @@ function App() {
return (
<Container className="p-3">
<Container className="p-3 mb-4 bg-light rounded-3">
<div className="App">
{content}
</div>
<div className="App">{content}</div>
</Container>
</Container>
);
Expand Down
66 changes: 27 additions & 39 deletions pkg_client/src/components/APIHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,38 @@
import React, { useContext, useEffect, useState } from "react";
import axios from "axios";
import React, { useContext } from "react";
import { UserContext } from "../contexts/UserContext";
import Container from "react-bootstrap/Container";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Layout from "./Layout";
import PKGVisualization from "./PKGVisualization";

const APIHandler: React.FC = () => {
const { user } = useContext(UserContext);
// State tracker for service management data.
const [serviceData, setServiceData] = useState(null);
// State tracker for personal facts data.
const [factsData, setFactsData] = useState(null);
// State tracker for PKG exploration data. Data presentation, graphs, etc.
const [exploreData, setExploreData] = useState(null);

useEffect(() => {
const baseURL =
(window as any)["PKG_API_BASE_URL"] || "http://localhost:5000";

axios
.get(`${baseURL}/service`)
.then((response) => setServiceData(response.data));
axios
.get(`${baseURL}/facts`)
.then((response) => setFactsData(response.data));
axios
.get(`${baseURL}/explore`)
.then((response) => setExploreData(response.data));
}, []);

return (
<Container>
<h1>Personal Knowledge Graph API</h1>
<div>
<p>Welcome {JSON.stringify(user, null, 2)}</p>
</div>
<div>
<h2>Service Management Data</h2>
<pre>{JSON.stringify(serviceData, null, 2)}</pre>
</div>
<div>
<h2>Personal Facts Data</h2>
<pre>{JSON.stringify(factsData, null, 2)}</pre>
</div>
<div>
<h2>PKG Exploration Data</h2>
<pre>{JSON.stringify(exploreData, null, 2)}</pre>
</div>
<h1>Personal Knowledge Graph</h1>

<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route
index
element={
<div>
<div>Welcome {user?.username}.</div>
</div>
}
/>
<Route path="service" element={<div>Service Management</div>} />
<Route path="population" element={<div>Population form</div>} />
<Route
path="preferences"
element={<div>Personal Preferences</div>}
/>
<Route path="explore" element={<PKGVisualization />} />
Comment on lines +26 to +31
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to remove these, they can be added in a PR related to the implementatio of their view.

</Route>
</Routes>
</BrowserRouter>
</Container>
);
};
Expand Down
47 changes: 47 additions & 0 deletions pkg_client/src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Layout component for the application includes a navigation bar and content
// of the current tab.
import { Outlet, Link } from "react-router-dom";
import Nav from "react-bootstrap/Nav";
import { useState } from "react";

const Layout = () => {
const [activeKey, setActiveKey] = useState("/");

const handleSelect = (eventKey: string | null) => {
if (eventKey !== null) {
setActiveKey(eventKey);
}
};

return (
<>
<Nav variant="underline" activeKey={activeKey} onSelect={handleSelect}>
<Nav.Item>
<Nav.Link eventKey="/" as={Link} to="/">
Home
</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey="/service" as={Link} to="/service">
Service Management
</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey="/population" as={Link} to="/population">
Populate PKG
</Nav.Link>
</Nav.Item>
Comment on lines +24 to +33
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as before, we may want to include these routes in related PRs.

<Nav.Item>
<Nav.Link eventKey="/explore" as={Link} to="/explore">
Explore PKG
</Nav.Link>
</Nav.Item>
</Nav>

<br />
<Outlet />
</>
);
};

export default Layout;
83 changes: 83 additions & 0 deletions pkg_client/src/components/PKGVisualization.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Natural language to PKG component

import { Container } from "react-bootstrap";
import { UserContext } from "../contexts/UserContext";
import { useContext, useEffect, useState } from "react";
import axios from "axios";
import QueryForm from "./QueryForm";
import Button from "react-bootstrap/Button";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: remove unused import.


const PKGVisualization = () => {
const { user } = useContext(UserContext);
const [error, setError] = useState("");
const [image_path, setImagePath] = useState("");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: usually in javascript, the variable name uses a capital letter instead of _, e.g., imagePath.

const [query_info, setQueryInfo] = useState("");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: same comment as for image_path.

const [result, setResult] = useState("");
const baseURL =
(window as any)["PKG_API_BASE_URL"] || "http://127.0.0.1:5000";

useEffect(() => {
getImage();
}, []);

const getImage = () => {
axios
.get(`${baseURL}/explore`, {
params: {
owner_username: user?.username,
owner_uri: user?.uri,
},
responseType: "blob",
})
.then((response) => {
setError("");
const imageURL = URL.createObjectURL(
new Blob([response.data], {
type: "image/png",
})
);
setImagePath(imageURL);
})
.catch((error) => {
setError(error.message);
throw error;
});
};

const executeQuery = (query: string) => {
return axios
.post(`${baseURL}/explore`, {
sparql_query: query,
owner_username: user?.username,
owner_uri: user?.uri,
})
.then((response) => {
setError("");
console.log(response.data);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this can be removed.

setQueryInfo(response.data.message);
setResult(response.data.result);
})
.catch((error) => {
setError(error.message);
throw error;
});
};

return (
<Container>
<div>
<b>Here you can execute your own SPARQL queries to manage your PKG.</b>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be queries to select statements within the PKG? From my understanding only SELECT queries are accepted.

</div>
<QueryForm handleSubmit={executeQuery} error={error} />
<div>Query execution status: {query_info}</div>
<div>Query execution result: {result}</div>
<div>
<b>This is your current PKG:</b>
</div>
{/* <div>[Only for testing] Local path to the image: {image_path}</div> */}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this can be removed.

<img src={image_path} alt="PKG" style={{ width: "100%" }} />
</Container>
);
};

export default PKGVisualization;
Loading
Loading