Skip to content
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
8 changes: 8 additions & 0 deletions cypress/e2e/spec.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ describe("Web app", () => {
);
});

it("Variables in column header contain link to ontology", () => {
cy.visit("/");

cy.contains("My favourite musicians").click();
cy.contains("Finished in:");
cy.get('a[href="http://schema.org/name"]');
})

it("When one source throws an error, the results of other sources are still shown", () => {
cy.visit("/");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ActionBar from "../../ActionBar/ActionBar";
import GenericField from "../../../representationProvider/GenericField";
import { Term } from "sparqljs";
import config from "../../../config";
import TableHeader from "./TableHeader/TableHeader";

/**
* @param {object} props - the props passed down to the component
Expand All @@ -25,7 +26,7 @@ function QueryResultList(props) {
<Title title={config.title} />
<ListView title=" " actions={<ActionBar />} {...props}>
{values && (
<Datagrid>
<Datagrid header={<TableHeader config={config}/>}>
{Object.keys(values).map((key) => {
return (
<GenericField
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

.header-button{
height: 100%;
vertical-align: middle;
}

.header-button:hover{
cursor: pointer;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { Link, TableCell, TableHead, TableRow } from "@mui/material";
import React from "react";
import { useListContext } from "react-admin";
import "./TableHeader.css";
import NorthIcon from "@mui/icons-material/North";
import SouthIcon from "@mui/icons-material/South";
import LinkIcon from "@mui/icons-material/Link";
import PropTypes from "prop-types";
import { Component } from "react";

/**
*
* @param {object} props - the props passed down to the component
* @param {Array<Component>} props.children - the children of the component
* @param {object} props.config - the config object of the application
* @returns {Component} the header of the table containing the column names, the sort icons and ontology links
*/
function TableHeader({ children, config }) {
const { sort, setSort, resource } = useListContext();
const { variableOntology } = config.queries.filter(
(query) => query.id === resource
)[0];

/**
* Handles the click on a header and sets the sort state accordingly
* @param {string} target - the source of the column that was clicked
*/
function handleHeaderClick(target) {
const newSort = { field: target, order: "DESC" };
if (sort) {
if (sort.order === "ASC") {
newSort.order = "DESC";
} else {
newSort.order = "ASC";
}
}
setSort(newSort);
}

return (
<TableHead>
<TableRow>
<TableCell> </TableCell>
{React.Children.map(children, (child) => (
<>
<TableCell
key={child.props.source}
sx={{ height: "100%", "& > *": { verticalAlign: "middle" } }}
>
<span
role="button"
className="header-button"
onClick={() => handleHeaderClick(child.props.source)}
>
{child.props.label}
</span>
{variableOntology[child.props.source] && (
<Link
target="_blank"
href={variableOntology[child.props.source]}
sx={{ height: "100%", margin: "0 5px", "& > *": { verticalAlign: "middle" } }}
>
<LinkIcon
fontSize="small"
sx={{ height: "100%", color: "gray" }}
/>
</Link>
)}
{sort.field === child.props.source && (
<>
{sort && sort.order === "DESC" && (
<NorthIcon
fontSize="small"
sx={{ height: "100%", color: "gray" }}
/>
)}
{sort && sort.order === "ASC" && (
<SouthIcon
fontSize="small"
sx={{ height: "100%", color: "gray" }}
/>
)}
</>
)}
</TableCell>
</>
))}
</TableRow>
</TableHead>
);
}

TableHeader.propTypes = {
children: PropTypes.node,
config: PropTypes.object.isRequired,

}
export default TableHeader;
32 changes: 26 additions & 6 deletions src/dataProvider/SparqlDataProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ async function fetchQuery(query) {
const rawText = await result.text();
query.rawText = rawText;
const parsedQuery = parser.parse(rawText);
if (!query.variableOntology) {
query.variableOntology = findPredicates(parsedQuery);
}
if (!parsedQuery.limit) {
parsedQuery.limit = query.limit;
}
Expand All @@ -116,6 +119,26 @@ async function fetchQuery(query) {
}
}

/**
* Given a query and an object, this function returns the predicate of the object in the query.
* @param {object} query - the paresed query in which the predicate is to be looked for.
* @returns {object} an object with the variable as key and the predicate as value.
*/
function findPredicates(query) {
const ontologyMapper = {};
if (!query.variables) {
return query;
}
for (const part of query.where) {
for (const triple of part.triples) {
if(triple.predicate.termType !== "Variable"){
ontologyMapper[triple.object.value] = triple.predicate.value;
}
}
}
return ontologyMapper;
}

/**
* A function that executes a given query and processes every result.
* @param {object} query - the query which is to be executed and additional information about the query.
Expand All @@ -125,12 +148,9 @@ async function executeQuery(query) {
try {
query.queryText = await fetchQuery(query);
return handleQueryExecution(
await myEngine.query(
query.queryText,
{
...generateContext(query.comunicaContext)
}
),
await myEngine.query(query.queryText, {
...generateContext(query.comunicaContext),
}),
query
);
} catch (error) {
Expand Down