diff --git a/CHANGELOG.md b/CHANGELOG.md
index c801b520..2644cc1d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [2.1.0] - 2025-09-25
+
+### Added
+
+- Screencast for the Onto-DESIDE use case (#228).
+- Presentation as presented during the SemDev Workhop co-located with SEMANTiCS 2025 (#234).
+
+### Fixed
+
+- Link in table result header is no longer arbitrary if more than one predicate has the same object (#230.)
+- Avoided "Error getting variable options..." in templated queries with indirect sources to which the user has no read access (#231).
+- Corrected fetch status in templated queries with indirect sources to which the user has no read access (#232).
+
## [2.0.0] - 2025-05-29
### Added
@@ -270,4 +283,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[1.6.0]: https://github.com/SolidLabResearch/miravi-a-linked-data-viewer/releases/tag/v1.6.0
[1.7.0]: https://github.com/SolidLabResearch/miravi-a-linked-data-viewer/releases/tag/v1.7.0
[2.0.0]: https://github.com/SolidLabResearch/miravi-a-linked-data-viewer/releases/tag/v2.0.0
-[Unreleased]: https://github.com/SolidLabResearch/miravi-a-linked-data-viewer/compare/v2.0.0...HEAD
+[2.1.0]: https://github.com/SolidLabResearch/miravi-a-linked-data-viewer/releases/tag/v2.1.0
+[Unreleased]: https://github.com/SolidLabResearch/miravi-a-linked-data-viewer/compare/v2.1.0...HEAD
diff --git a/README.md b/README.md
index ea66a7e5..acd04a81 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,13 @@ Rooted in the Latin *mirari* ("to look with wonder"), it turns fragmented knowle
+Miravi is fully configurable. In the illustration below, [this configuration](./main/configs/onto-deside/config.json) is at work.
+
+
+
+For a more complete presentation of Miravi and description of the design choices, please have a look at our
+[presentation as presented during the SemDev Workhop co-located with SEMANTiCS 2025](./doc/slides-miravi-semdev-2025.pdf).
+
Table of contents:
* [Preface](#preface)
@@ -27,9 +34,10 @@ Table of contents:
* [Custom queries](#custom-queries)
* [Representation Mapper](#representation-mapper)
* [Advanced topics](#advanced-topics)
+ * [Adding your own configuration](#adding-your-own-configuration)
* [Converting custom queries into common queries](#converting-custom-queries-into-common-queries)
+* [Illustrations](#illustrations)
* [For developers](#for-developers)
- * [Adding your own configuration](#adding-your-own-configuration)
* [Testing](#testing)
* [Additional prerequisites](#additional-prerequisites)
* [Testing the production version](#testing-the-production-version)
@@ -366,6 +374,18 @@ They've already got styling matching that of `react-admin` and are easy to use.
## Advanced topics
+### Adding your own configuration
+
+The easiest way to add your own configuration is:
+
+1. Get inspired by the configuration in `main/configs/demo`.
+2. Choose your ``: a string obeying regex `[a-z0-9-]+`; directory `main/configs/` should not yet be in use.
+3. Add your own queries in the `main/configs//public/queries` directory and in general, your own resources in the `main/configs//public` directory.
+4. Add your own additional resources in `main/configs//public`, if the defaults you'll get from `main/config-defaults/public` are not satisfactory for you.
+5. Write your own `main/configs//config.json` file, following the [configuration file documentation above](#configuration-file).
+6. Run or build as documented above for the `demo` configuration, of course now using ``.
+7. Consider a pull request to add your configuration to this repo.
+
### Converting custom queries into common queries
Once you have your basic configuration working, you may extend it with custom queries interactively with the query editor
@@ -384,19 +404,12 @@ Follow these steps to get started:
5. **Adapt any other properties** according to your preferences.
6. **Save `main/configs//config.json`**, rerun or rebuild and refresh your browser to test.
-## For developers
+## Illustrations
-### Adding your own configuration
+* [A screencast about configs/onto-deside](doc/screencast-onto-deside.gif)
+* [Presentation as presented during the SemDev Workhop co-located with SEMANTiCS 2025](./doc/slides-miravi-semdev-2025.pdf)
-The easiest way to add your own configuration is:
-
-1. Get inspired by the configuration in `main/configs/demo`.
-2. Choose your ``: a string obeying regex `[a-z0-9-]+`; directory `main/configs/` should not yet be in use.
-3. Add your own queries in the `main/configs//public/queries` directory and in general, your own resources in the `main/configs//public` directory.
-4. Add your own additional resources in `main/configs//public`, if the defaults you'll get from `main/config-defaults/public` are not satisfactory for you.
-5. Write your own `main/configs//config.json` file, following the [configuration file documentation above](#configuration-file).
-6. Run or build as documented above for the `demo` configuration, of course now using ``.
-7. Consider a pull request to add your configuration to this repo.
+## For developers
### Testing
diff --git a/doc/screencast-onto-deside.gif b/doc/screencast-onto-deside.gif
new file mode 100644
index 00000000..15e2ee41
Binary files /dev/null and b/doc/screencast-onto-deside.gif differ
diff --git a/doc/slides-miravi-semdev-2025.pdf b/doc/slides-miravi-semdev-2025.pdf
new file mode 100644
index 00000000..938a7bf2
Binary files /dev/null and b/doc/slides-miravi-semdev-2025.pdf differ
diff --git a/main/configs/test/config.json b/main/configs/test/config.json
index 286171ef..4fd5b2ce 100644
--- a/main/configs/test/config.json
+++ b/main/configs/test/config.json
@@ -444,6 +444,38 @@
"queryLocation": "/sourceQueries/index_example_common_lt.rq"
}
},
+ {
+ "id": "9081",
+ "queryGroupId": "gr-test",
+ "queryLocation": "component_material_one_variable.rq",
+ "name": "Component and materials - 1 variable (indirect source & indirect variables; one unauthorized source)",
+ "description": "Query components (including details about materials) with the sources obtained from index files and variables from the sources. One unauthorized indirect source, to check lenient while getting variable options.",
+ "indirectVariables": {
+ "queryLocations": [
+ "variableQueries/components_name_variable.rq"
+ ]
+ },
+ "sourcesIndex": {
+ "url": "http://localhost:8080/example/index-example-with-unauthorized-source-lt",
+ "queryLocation": "/sourceQueries/index_example_common_lt.rq"
+ }
+ },
+ {
+ "id": "9082",
+ "queryGroupId": "gr-test",
+ "queryLocation": "component_material_one_variable.rq",
+ "name": "Component and materials - 1 variable (indirect source & indirect variables; no indirect sources found)",
+ "description": "Query components (including details about materials) with zero sources obtained from index files and variables from the sources.",
+ "indirectVariables": {
+ "queryLocations": [
+ "variableQueries/components_name_variable.rq"
+ ]
+ },
+ "sourcesIndex": {
+ "url": "http://localhost:8080/example/index-example-texon-only-lt",
+ "queryLocation": "/sourceQueries/index_example_common_lt_bad.rq"
+ }
+ },
{
"id": "9090",
"queryGroupId": "gr-test",
@@ -549,6 +581,32 @@
"http://localhost:8080/example/favourite-musicians-file-does-not-exist"
]
}
+ },
+ {
+ "id": "9200",
+ "queryGroupId": "gr-test",
+ "queryLocation": "schema_name.rq",
+ "name": "A query that looks for names that are the object of predicate schema:name",
+ "description": "Tests a single link in the 'name' column header.",
+ "comunicaContext": {
+ "sources": [
+ "http://localhost:8080/example/names-labels"
+ ],
+ "lenient": true
+ }
+ },
+ {
+ "id": "9201",
+ "queryGroupId": "gr-test",
+ "queryLocation": "schema_name_rdfs_label.rq",
+ "name": "A query that looks for names that are both the objects of predicates schema:name and rdfs:label",
+ "description": "Tests two links in the 'name' column header.",
+ "comunicaContext": {
+ "sources": [
+ "http://localhost:8080/example/names-labels"
+ ],
+ "lenient": true
+ }
}
]
}
\ No newline at end of file
diff --git a/main/configs/test/public/queries/schema_name.rq b/main/configs/test/public/queries/schema_name.rq
new file mode 100644
index 00000000..2f393ab0
--- /dev/null
+++ b/main/configs/test/public/queries/schema_name.rq
@@ -0,0 +1,6 @@
+PREFIX schema:
+PREFIX rdfs:
+
+SELECT ?name WHERE {
+ ?s schema:name ?name.
+}
\ No newline at end of file
diff --git a/main/configs/test/public/queries/schema_name_rdfs_label.rq b/main/configs/test/public/queries/schema_name_rdfs_label.rq
new file mode 100644
index 00000000..1cf2ce2b
--- /dev/null
+++ b/main/configs/test/public/queries/schema_name_rdfs_label.rq
@@ -0,0 +1,7 @@
+PREFIX schema:
+PREFIX rdfs:
+
+SELECT ?name WHERE {
+ ?s schema:name ?name.
+ ?s rdfs:label ?name.
+}
\ No newline at end of file
diff --git a/main/configs/test/public/queries/sourceQueries/index_example_common_lt_bad.rq b/main/configs/test/public/queries/sourceQueries/index_example_common_lt_bad.rq
new file mode 100644
index 00000000..fee1195e
--- /dev/null
+++ b/main/configs/test/public/queries/sourceQueries/index_example_common_lt_bad.rq
@@ -0,0 +1,5 @@
+PREFIX rdfs:
+
+SELECT DISTINCT ?source WHERE {
+ ?s rdfs:seeAlso_hihihahahoho ?source.
+}
diff --git a/main/src/components/ActionBar/SourceFetchStatusIcon/SourceFetchStatusIcon.jsx b/main/src/components/ActionBar/SourceFetchStatusIcon/SourceFetchStatusIcon.jsx
index 1cfd3786..82278640 100644
--- a/main/src/components/ActionBar/SourceFetchStatusIcon/SourceFetchStatusIcon.jsx
+++ b/main/src/components/ActionBar/SourceFetchStatusIcon/SourceFetchStatusIcon.jsx
@@ -1,3 +1,4 @@
+import PendingIcon from '@mui/icons-material/Pending';
import CheckIcon from '@mui/icons-material/Check';
import CancelIcon from "@mui/icons-material/Cancel";
import RemoveCircleOutlineIcon from '@mui/icons-material/RemoveCircleOutline';
@@ -14,7 +15,13 @@ import comunicaEngineWrapper from '../../../comunicaEngineWrapper/comunicaEngine
function SourceFetchStatusIcon({ source }) {
const status = comunicaEngineWrapper.getFetchStatusNumber(source);
- if (comunicaEngineWrapper.getFetchSuccess(source)) {
+ if (comunicaEngineWrapper.getFetchSuccess(source) === undefined) {
+ return (
+
+
+
+ );
+ } else if (comunicaEngineWrapper.getFetchSuccess(source)) {
return (
diff --git a/main/src/components/ListResultTable/QueryResultList/TableHeader/TableHeader.jsx b/main/src/components/ListResultTable/QueryResultList/TableHeader/TableHeader.jsx
index 75fff512..c8fb5acf 100644
--- a/main/src/components/ListResultTable/QueryResultList/TableHeader/TableHeader.jsx
+++ b/main/src/components/ListResultTable/QueryResultList/TableHeader/TableHeader.jsx
@@ -60,16 +60,18 @@ function TableHeader({ children }) {
}
{!!variableOntology && variableOntology[child.props.source] && (
- *": { verticalAlign: "middle" } }}
- >
-
-
+ variableOntology[child.props.source].map((link) => (
+ *": { verticalAlign: "middle" } }}
+ >
+
+
+ ))
)}
{sort.field === child.props.source && (
<>
diff --git a/main/src/comunicaEngineWrapper/comunicaEngineWrapper.js b/main/src/comunicaEngineWrapper/comunicaEngineWrapper.js
index 9c7686a4..1bd45173 100644
--- a/main/src/comunicaEngineWrapper/comunicaEngineWrapper.js
+++ b/main/src/comunicaEngineWrapper/comunicaEngineWrapper.js
@@ -36,6 +36,7 @@ class ComunicaEngineWrapper {
this._fetchSuccess = {};
this._fetchStatusNumber = {};
this._underlyingFetchFunction = undefined;
+ // LOG console.log(`Comunica engines reset`);
}
getFetchSuccess(arg) {
@@ -168,13 +169,14 @@ class ComunicaEngineWrapper {
* @param {array} httpProxies - array of httpProxy definitions
*/
_prepareQuery(context, httpProxies) {
- // avoid faulty fetch status for sources cached in Comunica
- for (const source of context.sources) {
- this._fetchSuccess[source] = true;
- }
- this._underlyingFetchFunction = fetch;
+ // note: there is no need to preset this._fetchSuccess[source] here;
+ // if Comunica caches, we still have the previous value
if (getDefaultSession().info.isLoggedIn) {
this._underlyingFetchFunction = authFetch;
+ // LOG console.log(`Using authFetch as underlying fetch function`);
+ } else {
+ this._underlyingFetchFunction = fetch;
+ // LOG console.log(`Using fetch as underlying fetch function`);
}
context.fetch = ComunicaEngineWrapper._getWrappedFetchFunction(this._underlyingFetchFunction, httpProxies, this);
}
diff --git a/main/src/dataProvider/SparqlDataProvider.js b/main/src/dataProvider/SparqlDataProvider.js
index 5af53290..00e54ffa 100644
--- a/main/src/dataProvider/SparqlDataProvider.js
+++ b/main/src/dataProvider/SparqlDataProvider.js
@@ -189,10 +189,10 @@ function replaceVariables(rawText, variableValues) {
}
/**
- * Given a query and an object, this function returns the predicate of the object in the query.
+ * Given a query and an object, this function returns the predicates of the object in the query.
*
* @param {object} query - the parsed query in which the predicate is to be looked for.
- * @returns {object} an object with the variable as key and the predicate as value.
+ * @returns {object} an object with the variable as key and as value an array of predicates.
*/
function findPredicates(query) {
const ontologyMapper = {};
@@ -204,7 +204,11 @@ function findPredicates(query) {
if (part.triples) {
for (const triple of part.triples) {
if (triple.predicate.termType !== "Variable") {
- ontologyMapper[triple.object.value] = triple.predicate.value;
+ if (!ontologyMapper[triple.object.value]) {
+ ontologyMapper[triple.object.value] = [triple.predicate.value];
+ } else if (!ontologyMapper[triple.object.value].includes(triple.predicate.value)) {
+ ontologyMapper[triple.object.value].push(triple.predicate.value);
+ }
}
}
}
@@ -293,22 +297,19 @@ async function getSourcesFromSourcesIndex(sourcesIndex, httpProxies) {
const bindingsStream = await comunicaEngineWrapper.queryBindings(queryStringIndexSource,
{ lenient: true, sources: [sourcesIndex.url] }, httpProxies, { engine: "link-traversal" });
- await new Promise((resolve, reject) => {
- bindingsStream.on('data', (bindings) => {
- // LOG console.log(`getSourcesFromSourcesIndex bindings: ${bindings.toString()}`);
- for (const term of bindings.values()) { // check for 1st value
- const source = term.value;
- if (!sourcesList.includes(source)) {
- // LOG console.log(`getSourcesFromSourcesIndex adding source: ${source}`);
- sourcesList.push(source);
- }
- // we only want the first term, whatever the variable's name is (note: a for ... of loop seems the only way to access it)
- break;
+ const bindingsArray = await bindingsStream.toArray();
+ for (const bindings of bindingsArray) {
+ // LOG console.log(`getSourcesFromSourcesIndex bindings: ${bindings.toString()}`);
+ for (const term of bindings.values()) { // check for 1st value
+ const source = term.value;
+ if (!sourcesList.includes(source)) {
+ // LOG console.log(`getSourcesFromSourcesIndex adding source: ${source}`);
+ sourcesList.push(source);
}
- });
- bindingsStream.on('end', resolve);
- bindingsStream.on('error', reject);
- });
+ // we only want the first term, whatever the variable's name is (note: a for ... of loop seems the only way to access it)
+ break;
+ }
+ }
}
catch (error) {
throw new Error(`Error adding sources from index: ${error.message}`);
@@ -380,6 +381,9 @@ async function getVariableOptions(query) {
}
// END duplicated chunk of code
+ if (query.comunicaContext.sources.length === 0) {
+ throw new Error(`Error getting variable options... No sources found.`);
+ }
let variableOptions;
let queryStringList = [];
@@ -417,35 +421,35 @@ async function getVariableOptions(query) {
try {
for (const queryString of queryStringList) {
+ // queryBindings with lenient true to avoid errors with unauthorized sources
const bindingsStream = await comunicaEngineWrapper.queryBindings(queryString,
- { sources: query.comunicaContext.sources }, query.httpProxies);
- await new Promise((resolve, reject) => {
- bindingsStream.on('data', (bindings) => {
- // LOG console.log(`getVariableOptions bindings: ${bindings.toString()}`);
- for (const [variable, term] of bindings) {
- const name = variable.value;
- if (!variableOptions[name]) {
- variableOptions[name] = [];
- }
- const variableValue = termToSparqlCompatibleString(term);
- if (variableValue && !variableOptions[name].includes(variableValue)) {
- // LOG console.log(`getVariableOptions adding variable option for '${name}': ${variableValue}`);
- variableOptions[name].push(variableValue);
- }
+ { lenient: true, sources: query.comunicaContext.sources }, query.httpProxies);
+ // convert stream to array (works when no bindings found - handling events 'data', 'end' and 'error' does not work when no bindints found)
+ const bindingsArray = await bindingsStream.toArray();
+ for (const bindings of bindingsArray) {
+ // LOG console.log(`getVariableOptions bindings: ${bindings.toString()}`);
+ for (const [variable, term] of bindings) {
+ const name = variable.value;
+ if (!variableOptions[name]) {
+ variableOptions[name] = [];
}
- });
- bindingsStream.on('end', resolve);
- bindingsStream.on('error', reject);
- });
+ const variableValue = termToSparqlCompatibleString(term);
+ if (variableValue && !variableOptions[name].includes(variableValue)) {
+ // LOG console.log(`getVariableOptions adding variable option for '${name}': ${variableValue}`);
+ variableOptions[name].push(variableValue);
+ }
+ }
+ }
}
}
catch (error) {
throw new Error(`Error getting variable options... ${error.message}`);
}
- if (variableOptions == {}) {
- throw new Error(`Error getting variable options... The variable options are empty`);
+ if (Object.keys(variableOptions).length === 0) {
+ throw new Error(`Error getting variable options... No variable options found.`);
}
+
return variableOptions;
}
diff --git a/main/src/version.js b/main/src/version.js
index c76f51c2..1eb7321a 100644
--- a/main/src/version.js
+++ b/main/src/version.js
@@ -1,5 +1,5 @@
// Maintain the version string on the development branch:
// - during development: set to unreleased;
// - right before merging to the main branch for making a new release: set to the new release tag (see https://github.com/SolidLabResearch/miravi-a-linked-data-viewer/tags).
-const version = "v2.0.0";
+const version = "v2.1.0";
export default version;
diff --git a/test/cypress/e2e/column-header.cy.js b/test/cypress/e2e/column-header.cy.js
index c1d41d91..048488db 100644
--- a/test/cypress/e2e/column-header.cy.js
+++ b/test/cypress/e2e/column-header.cy.js
@@ -1,9 +1,23 @@
describe("Column header", () => {
- it("Variables link to ontology", () => {
+ it("One link to ontology", () => {
cy.visit("/");
- cy.contains("Example queries").click();
- cy.contains("A query about musicians").click();
+ cy.contains("For testing only").click();
+ cy.contains("A query that looks for names that are the object of predicate schema:name").click();
cy.contains("Finished in:");
- cy.get('a[href="http://schema.org/name"]');
- })
+ cy.get('th').contains("name").parent().within(() => {
+ cy.get('a[href="http://schema.org/name"]');
+ });
+ });
+
+ it("Two links to ontology", () => {
+ cy.visit("/");
+ cy.contains("For testing only").click();
+ cy.contains("A query that looks for names that are both the objects of predicates schema:name and rdfs:label").click();
+ cy.contains("Finished in:");
+ cy.get('th').contains("name").parent().within(() => {
+ cy.get('a[href="http://schema.org/name"]');
+ cy.get('a[href="http://www.w3.org/2000/01/rdf-schema#label"]');
+ });
+ });
+
});
diff --git a/test/cypress/e2e/fetch-status.cy.js b/test/cypress/e2e/fetch-status.cy.js
index b30edeca..c29422bb 100644
--- a/test/cypress/e2e/fetch-status.cy.js
+++ b/test/cypress/e2e/fetch-status.cy.js
@@ -11,16 +11,16 @@ describe("Fetch Status", () => {
// Check if the public and restricted sources appear
cy.get('[aria-label="Sources info"]').click();
- cy.contains("http://localhost:8080/example/favourite-books");
- cy.contains("http://localhost:8080/example/wish-list");
-
// Check if the correct icons appear
- cy.get('[aria-label="Authentication required"]').should("exist");
- cy.get('[aria-label="Unauthorized"]').should("exist");
-
- cy.get('[aria-label="No authentication required"]').should("exist");
- cy.get('[aria-label="Fetch was successful"]').should("exist");
-
+ cy.contains("http://localhost:8080/example/wish-list").parent().within(() => {
+ cy.get('[aria-label="No authentication required"]').should("exist");
+ cy.get('[aria-label="Fetch was successful"]').should("exist");
+ });
+ cy.contains("http://localhost:8080/example/favourite-books").parent().within(() => {
+ cy.get('[aria-label="Authentication required"]').should("exist");
+ cy.get('[aria-label="Unauthorized"]').should("exist");
+ });
+ cy.get('[aria-label="Not fetched"]').should("not.exist");
// Checking that a non-authorized book is not appearing
cy.contains("It Ends With Us").should("not.exist");
@@ -58,16 +58,16 @@ describe("Fetch Status", () => {
// Check if the public and restricted sources appear
cy.get('[aria-label="Sources info"]').click();
- cy.contains("http://localhost:8080/example/favourite-books");
- cy.contains("http://localhost:8080/example/wish-list");
-
// Check if the correct icons appear
- cy.get('[aria-label="Authentication required"]').should("exist");
- cy.get('[aria-label="Fetch Failed"]').should("not.exist");
- cy.get('[aria-label="Unauthorized"]').should("not.exist");
-
- cy.get('[aria-label="No authentication required"]').should("exist");
- cy.get('[aria-label="Fetch was successful"]').should("exist");
+ cy.contains("http://localhost:8080/example/wish-list").parent().within(() => {
+ cy.get('[aria-label="No authentication required"]').should("exist");
+ cy.get('[aria-label="Fetch was successful"]').should("exist");
+ });
+ cy.contains("http://localhost:8080/example/favourite-books").parent().within(() => {
+ cy.get('[aria-label="Authentication required"]').should("exist");
+ cy.get('[aria-label="Fetch was successful"]').should("exist");
+ });
+ cy.get('[aria-label="Not fetched"]').should("not.exist");
// Checking that you see authorized books
cy.contains("It Ends With Us");
@@ -85,15 +85,45 @@ describe("Fetch Status", () => {
// Check if the good and bad sources appear
cy.get('[aria-label="Sources info"]').click();
- // First fetch should be a success
- cy.contains("http://localhost:8080/example/favourite-musicians");
- cy.get('[aria-label="No authentication required"]').should("exist");
- cy.get('[aria-label="Unauthorized"]').should("not.exist");
- cy.get('[aria-label="Fetch was successful"]').should("exist");
-
- // the bad source should fail to fetch
- cy.contains("http://www.example.com/fetch-failure-but-query-success");
- cy.get('[aria-label="Fetch failed"]').should("exist");
+ // Check if the correct icons appear
+ cy.contains("http://localhost:8080/example/favourite-musicians").parent().within(() => {
+ cy.get('[aria-label="No authentication required"]').should("exist");
+ cy.get('[aria-label="Fetch was successful"]').should("exist");
+ });
+ cy.contains("http://www.example.com/fetch-failure-but-query-success").parent().within(() => {
+ cy.get('[aria-label="Uncertain if authentication is required"]').should("exist");
+ cy.get('[aria-label="Fetch failed"]').should("exist");
+ });
+ cy.get('[aria-label="Not fetched"]').should("not.exist");
+
+ });
+
+ it("Fetch data with no authenticated user, indirect source & indirect variables and one unauthorized source", () => {
+
+ cy.visit("/");
+ cy.contains("For testing only").click();
+ cy.contains("Component and materials - 1 variable (indirect source & indirect variables; one unauthorized source)").click();
+
+ // Fill in the form
+ cy.get('.ra-input-componentName').click();
+ cy.get('li').contains('Component 1').click();
+
+ // Comfirm query
+ cy.get('button[type="submit"]').click();
+
+ // Check if the public and restricted sources appear
+ cy.get('[aria-label="Sources info"]').click();
+
+ // Check if the correct icons appear
+ cy.contains("http://localhost:8080/example/boms").parent().within(() => {
+ cy.get('[aria-label="No authentication required"]').should("exist");
+ cy.get('[aria-label="Fetch was successful"]').should("exist");
+ });
+ cy.contains("http://localhost:8080/example/favourite-books").parent().within(() => {
+ cy.get('[aria-label="Authentication required"]').should("exist");
+ cy.get('[aria-label="Unauthorized"]').should("exist");
+ });
+ cy.get('[aria-label="Not fetched"]').should("not.exist");
});
diff --git a/test/cypress/e2e/templated-query.cy.js b/test/cypress/e2e/templated-query.cy.js
index b7046b26..0ffd443b 100644
--- a/test/cypress/e2e/templated-query.cy.js
+++ b/test/cypress/e2e/templated-query.cy.js
@@ -265,6 +265,41 @@ describe("Templated query", () => {
});
+ it("Indirect with 1 variable and sources from indexfile, with one unauthorized source", () => {
+
+ cy.visit("/");
+ cy.contains("For testing only").click();
+ cy.contains("Component and materials - 1 variable (indirect source & indirect variables; one unauthorized source)").click();
+
+ // Fill in the form
+ cy.get('.ra-input-componentName').click();
+ cy.get('li').contains('Component 1').click();
+
+ // Comfirm query
+ cy.get('button[type="submit"]').click();
+
+ // Check that it is correctly loaded with and only the correct data appears
+ cy.contains("Finished in:");
+
+ cy.get('.column-componentName').find('span').contains("Component 1").should("exist");
+ cy.get('.column-materialName').find('span').contains("Material 2").should("exist");
+ cy.get('.column-materialName').find('span').contains("Material 1").should("exist");
+
+ cy.get('.column-componentName').find('span').contains("Component 2").should("not.exist");
+ cy.get('.column-componentName').find('span').contains("Component 3").should("not.exist");
+ cy.get('.column-materialName').find('span').contains("Material 6").should("not.exist");
+
+ });
+
+ it("Indirect with 1 variable and sources from indexfile, no indirect sources found", () => {
+
+ cy.visit("/");
+ cy.contains("For testing only").click();
+ cy.contains("Component and materials - 1 variable (indirect source & indirect variables; no indirect sources found)").click();
+
+ cy.contains("Error getting variable options...").should("exist");;
+ });
+
it("Indirect with 2 variables and sources from indexfile", () => {
cy.visit("/");
cy.contains("For testing only").click();
diff --git a/test/initial-pod-data/names-labels$.ttl b/test/initial-pod-data/names-labels$.ttl
new file mode 100644
index 00000000..c84ca716
--- /dev/null
+++ b/test/initial-pod-data/names-labels$.ttl
@@ -0,0 +1,8 @@
+PREFIX schema:
+PREFIX rdfs:
+PREFIX ex:
+
+ex:1 schema:name "A name, given with predicates schema:name and rdfs:label" ;
+ rdfs:label "A name, given with predicates schema:name and rdfs:label" .
+ex:2 schema:name "A name, given with predicate schema:name" ;
+ rdfs:label "A name, given with predicate rdfs:label" .
diff --git a/test/initial-pod-data/names-labels.acl b/test/initial-pod-data/names-labels.acl
new file mode 100644
index 00000000..bd3221fe
--- /dev/null
+++ b/test/initial-pod-data/names-labels.acl
@@ -0,0 +1,14 @@
+@prefix acl: .
+@prefix foaf: .
+
+<#public>
+ a acl:Authorization;
+ acl:accessTo <./names-labels>;
+ acl:agentClass foaf:Agent;
+ acl:mode acl:Read.
+
+<#owner>
+ a acl:Authorization;
+ acl:accessTo <./names-labels>;
+ acl:agent ;
+ acl:mode acl:Read, acl:Write, acl:Control.