Skip to content

Commit b1d505c

Browse files
authored
Fix/231 variable options, fix/232 fetch status (#233)
1 parent 2f21bd8 commit b1d505c

File tree

8 files changed

+184
-68
lines changed

8 files changed

+184
-68
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Screencast for the Onto-DESIDE use case (#228).
1313

14+
### Fixed
15+
16+
- Avoided "Error getting variable options..." in templated queries with indirect sources to which the user has no read access (#231).
17+
- Corrected fetch status in templated queries with indirect sources to which the user has no read access (#232).
18+
1419
## [2.0.0] - 2025-05-29
1520

1621
### Added

main/configs/test/config.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,38 @@
444444
"queryLocation": "/sourceQueries/index_example_common_lt.rq"
445445
}
446446
},
447+
{
448+
"id": "9081",
449+
"queryGroupId": "gr-test",
450+
"queryLocation": "component_material_one_variable.rq",
451+
"name": "Component and materials - 1 variable (indirect source & indirect variables; one unauthorized source)",
452+
"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.",
453+
"indirectVariables": {
454+
"queryLocations": [
455+
"variableQueries/components_name_variable.rq"
456+
]
457+
},
458+
"sourcesIndex": {
459+
"url": "http://localhost:8080/example/index-example-with-unauthorized-source-lt",
460+
"queryLocation": "/sourceQueries/index_example_common_lt.rq"
461+
}
462+
},
463+
{
464+
"id": "9082",
465+
"queryGroupId": "gr-test",
466+
"queryLocation": "component_material_one_variable.rq",
467+
"name": "Component and materials - 1 variable (indirect source & indirect variables; no indirect sources found)",
468+
"description": "Query components (including details about materials) with zero sources obtained from index files and variables from the sources.",
469+
"indirectVariables": {
470+
"queryLocations": [
471+
"variableQueries/components_name_variable.rq"
472+
]
473+
},
474+
"sourcesIndex": {
475+
"url": "http://localhost:8080/example/index-example-texon-only-lt",
476+
"queryLocation": "/sourceQueries/index_example_common_lt_bad.rq"
477+
}
478+
},
447479
{
448480
"id": "9090",
449481
"queryGroupId": "gr-test",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
2+
3+
SELECT DISTINCT ?source WHERE {
4+
?s rdfs:seeAlso_hihihahahoho ?source.
5+
}

main/src/components/ActionBar/SourceFetchStatusIcon/SourceFetchStatusIcon.jsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import PendingIcon from '@mui/icons-material/Pending';
12
import CheckIcon from '@mui/icons-material/Check';
23
import CancelIcon from "@mui/icons-material/Cancel";
34
import RemoveCircleOutlineIcon from '@mui/icons-material/RemoveCircleOutline';
@@ -14,7 +15,13 @@ import comunicaEngineWrapper from '../../../comunicaEngineWrapper/comunicaEngine
1415
function SourceFetchStatusIcon({ source }) {
1516
const status = comunicaEngineWrapper.getFetchStatusNumber(source);
1617

17-
if (comunicaEngineWrapper.getFetchSuccess(source)) {
18+
if (comunicaEngineWrapper.getFetchSuccess(source) === undefined) {
19+
return (
20+
<Tooltip title="Not fetched">
21+
<PendingIcon size="small" />
22+
</Tooltip>
23+
);
24+
} else if (comunicaEngineWrapper.getFetchSuccess(source)) {
1825
return (
1926
<Tooltip title="Fetch was successful">
2027
<CheckIcon size="small" />

main/src/comunicaEngineWrapper/comunicaEngineWrapper.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class ComunicaEngineWrapper {
3636
this._fetchSuccess = {};
3737
this._fetchStatusNumber = {};
3838
this._underlyingFetchFunction = undefined;
39+
// LOG console.log(`Comunica engines reset`);
3940
}
4041

4142
getFetchSuccess(arg) {
@@ -168,13 +169,14 @@ class ComunicaEngineWrapper {
168169
* @param {array} httpProxies - array of httpProxy definitions
169170
*/
170171
_prepareQuery(context, httpProxies) {
171-
// avoid faulty fetch status for sources cached in Comunica
172-
for (const source of context.sources) {
173-
this._fetchSuccess[source] = true;
174-
}
175-
this._underlyingFetchFunction = fetch;
172+
// note: there is no need to preset this._fetchSuccess[source] here;
173+
// if Comunica caches, we still have the previous value
176174
if (getDefaultSession().info.isLoggedIn) {
177175
this._underlyingFetchFunction = authFetch;
176+
// LOG console.log(`Using authFetch as underlying fetch function`);
177+
} else {
178+
this._underlyingFetchFunction = fetch;
179+
// LOG console.log(`Using fetch as underlying fetch function`);
178180
}
179181
context.fetch = ComunicaEngineWrapper._getWrappedFetchFunction(this._underlyingFetchFunction, httpProxies, this);
180182
}

main/src/dataProvider/SparqlDataProvider.js

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -293,22 +293,19 @@ async function getSourcesFromSourcesIndex(sourcesIndex, httpProxies) {
293293

294294
const bindingsStream = await comunicaEngineWrapper.queryBindings(queryStringIndexSource,
295295
{ lenient: true, sources: [sourcesIndex.url] }, httpProxies, { engine: "link-traversal" });
296-
await new Promise((resolve, reject) => {
297-
bindingsStream.on('data', (bindings) => {
298-
// LOG console.log(`getSourcesFromSourcesIndex bindings: ${bindings.toString()}`);
299-
for (const term of bindings.values()) { // check for 1st value
300-
const source = term.value;
301-
if (!sourcesList.includes(source)) {
302-
// LOG console.log(`getSourcesFromSourcesIndex adding source: ${source}`);
303-
sourcesList.push(source);
304-
}
305-
// we only want the first term, whatever the variable's name is (note: a for ... of loop seems the only way to access it)
306-
break;
296+
const bindingsArray = await bindingsStream.toArray();
297+
for (const bindings of bindingsArray) {
298+
// LOG console.log(`getSourcesFromSourcesIndex bindings: ${bindings.toString()}`);
299+
for (const term of bindings.values()) { // check for 1st value
300+
const source = term.value;
301+
if (!sourcesList.includes(source)) {
302+
// LOG console.log(`getSourcesFromSourcesIndex adding source: ${source}`);
303+
sourcesList.push(source);
307304
}
308-
});
309-
bindingsStream.on('end', resolve);
310-
bindingsStream.on('error', reject);
311-
});
305+
// we only want the first term, whatever the variable's name is (note: a for ... of loop seems the only way to access it)
306+
break;
307+
}
308+
}
312309
}
313310
catch (error) {
314311
throw new Error(`Error adding sources from index: ${error.message}`);
@@ -380,6 +377,9 @@ async function getVariableOptions(query) {
380377
}
381378
// END duplicated chunk of code
382379

380+
if (query.comunicaContext.sources.length === 0) {
381+
throw new Error(`Error getting variable options... No sources found.`);
382+
}
383383

384384
let variableOptions;
385385
let queryStringList = [];
@@ -417,35 +417,35 @@ async function getVariableOptions(query) {
417417

418418
try {
419419
for (const queryString of queryStringList) {
420+
// queryBindings with lenient true to avoid errors with unauthorized sources
420421
const bindingsStream = await comunicaEngineWrapper.queryBindings(queryString,
421-
{ sources: query.comunicaContext.sources }, query.httpProxies);
422-
await new Promise((resolve, reject) => {
423-
bindingsStream.on('data', (bindings) => {
424-
// LOG console.log(`getVariableOptions bindings: ${bindings.toString()}`);
425-
for (const [variable, term] of bindings) {
426-
const name = variable.value;
427-
if (!variableOptions[name]) {
428-
variableOptions[name] = [];
429-
}
430-
const variableValue = termToSparqlCompatibleString(term);
431-
if (variableValue && !variableOptions[name].includes(variableValue)) {
432-
// LOG console.log(`getVariableOptions adding variable option for '${name}': ${variableValue}`);
433-
variableOptions[name].push(variableValue);
434-
}
422+
{ lenient: true, sources: query.comunicaContext.sources }, query.httpProxies);
423+
// convert stream to array (works when no bindings found - handling events 'data', 'end' and 'error' does not work when no bindints found)
424+
const bindingsArray = await bindingsStream.toArray();
425+
for (const bindings of bindingsArray) {
426+
// LOG console.log(`getVariableOptions bindings: ${bindings.toString()}`);
427+
for (const [variable, term] of bindings) {
428+
const name = variable.value;
429+
if (!variableOptions[name]) {
430+
variableOptions[name] = [];
435431
}
436-
});
437-
bindingsStream.on('end', resolve);
438-
bindingsStream.on('error', reject);
439-
});
432+
const variableValue = termToSparqlCompatibleString(term);
433+
if (variableValue && !variableOptions[name].includes(variableValue)) {
434+
// LOG console.log(`getVariableOptions adding variable option for '${name}': ${variableValue}`);
435+
variableOptions[name].push(variableValue);
436+
}
437+
}
438+
}
440439
}
441440
}
442441
catch (error) {
443442
throw new Error(`Error getting variable options... ${error.message}`);
444443
}
445444

446-
if (variableOptions == {}) {
447-
throw new Error(`Error getting variable options... The variable options are empty`);
445+
if (Object.keys(variableOptions).length === 0) {
446+
throw new Error(`Error getting variable options... No variable options found.`);
448447
}
448+
449449
return variableOptions;
450450
}
451451

test/cypress/e2e/fetch-status.cy.js

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@ describe("Fetch Status", () => {
1111
// Check if the public and restricted sources appear
1212
cy.get('[aria-label="Sources info"]').click();
1313

14-
cy.contains("http://localhost:8080/example/favourite-books");
15-
cy.contains("http://localhost:8080/example/wish-list");
16-
1714
// Check if the correct icons appear
18-
cy.get('[aria-label="Authentication required"]').should("exist");
19-
cy.get('[aria-label="Unauthorized"]').should("exist");
20-
21-
cy.get('[aria-label="No authentication required"]').should("exist");
22-
cy.get('[aria-label="Fetch was successful"]').should("exist");
23-
15+
cy.contains("http://localhost:8080/example/wish-list").parent().within(() => {
16+
cy.get('[aria-label="No authentication required"]').should("exist");
17+
cy.get('[aria-label="Fetch was successful"]').should("exist");
18+
});
19+
cy.contains("http://localhost:8080/example/favourite-books").parent().within(() => {
20+
cy.get('[aria-label="Authentication required"]').should("exist");
21+
cy.get('[aria-label="Unauthorized"]').should("exist");
22+
});
23+
cy.get('[aria-label="Not fetched"]').should("not.exist");
2424

2525
// Checking that a non-authorized book is not appearing
2626
cy.contains("It Ends With Us").should("not.exist");
@@ -58,16 +58,16 @@ describe("Fetch Status", () => {
5858
// Check if the public and restricted sources appear
5959
cy.get('[aria-label="Sources info"]').click();
6060

61-
cy.contains("http://localhost:8080/example/favourite-books");
62-
cy.contains("http://localhost:8080/example/wish-list");
63-
6461
// Check if the correct icons appear
65-
cy.get('[aria-label="Authentication required"]').should("exist");
66-
cy.get('[aria-label="Fetch Failed"]').should("not.exist");
67-
cy.get('[aria-label="Unauthorized"]').should("not.exist");
68-
69-
cy.get('[aria-label="No authentication required"]').should("exist");
70-
cy.get('[aria-label="Fetch was successful"]').should("exist");
62+
cy.contains("http://localhost:8080/example/wish-list").parent().within(() => {
63+
cy.get('[aria-label="No authentication required"]').should("exist");
64+
cy.get('[aria-label="Fetch was successful"]').should("exist");
65+
});
66+
cy.contains("http://localhost:8080/example/favourite-books").parent().within(() => {
67+
cy.get('[aria-label="Authentication required"]').should("exist");
68+
cy.get('[aria-label="Fetch was successful"]').should("exist");
69+
});
70+
cy.get('[aria-label="Not fetched"]').should("not.exist");
7171

7272
// Checking that you see authorized books
7373
cy.contains("It Ends With Us");
@@ -85,15 +85,45 @@ describe("Fetch Status", () => {
8585
// Check if the good and bad sources appear
8686
cy.get('[aria-label="Sources info"]').click();
8787

88-
// First fetch should be a success
89-
cy.contains("http://localhost:8080/example/favourite-musicians");
90-
cy.get('[aria-label="No authentication required"]').should("exist");
91-
cy.get('[aria-label="Unauthorized"]').should("not.exist");
92-
cy.get('[aria-label="Fetch was successful"]').should("exist");
93-
94-
// the bad source should fail to fetch
95-
cy.contains("http://www.example.com/fetch-failure-but-query-success");
96-
cy.get('[aria-label="Fetch failed"]').should("exist");
88+
// Check if the correct icons appear
89+
cy.contains("http://localhost:8080/example/favourite-musicians").parent().within(() => {
90+
cy.get('[aria-label="No authentication required"]').should("exist");
91+
cy.get('[aria-label="Fetch was successful"]').should("exist");
92+
});
93+
cy.contains("http://www.example.com/fetch-failure-but-query-success").parent().within(() => {
94+
cy.get('[aria-label="Uncertain if authentication is required"]').should("exist");
95+
cy.get('[aria-label="Fetch failed"]').should("exist");
96+
});
97+
cy.get('[aria-label="Not fetched"]').should("not.exist");
98+
99+
});
100+
101+
it("Fetch data with no authenticated user, indirect source & indirect variables and one unauthorized source", () => {
102+
103+
cy.visit("/");
104+
cy.contains("For testing only").click();
105+
cy.contains("Component and materials - 1 variable (indirect source & indirect variables; one unauthorized source)").click();
106+
107+
// Fill in the form
108+
cy.get('.ra-input-componentName').click();
109+
cy.get('li').contains('Component 1').click();
110+
111+
// Comfirm query
112+
cy.get('button[type="submit"]').click();
113+
114+
// Check if the public and restricted sources appear
115+
cy.get('[aria-label="Sources info"]').click();
116+
117+
// Check if the correct icons appear
118+
cy.contains("http://localhost:8080/example/boms").parent().within(() => {
119+
cy.get('[aria-label="No authentication required"]').should("exist");
120+
cy.get('[aria-label="Fetch was successful"]').should("exist");
121+
});
122+
cy.contains("http://localhost:8080/example/favourite-books").parent().within(() => {
123+
cy.get('[aria-label="Authentication required"]').should("exist");
124+
cy.get('[aria-label="Unauthorized"]').should("exist");
125+
});
126+
cy.get('[aria-label="Not fetched"]').should("not.exist");
97127

98128
});
99129

test/cypress/e2e/templated-query.cy.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,41 @@ describe("Templated query", () => {
265265

266266
});
267267

268+
it("Indirect with 1 variable and sources from indexfile, with one unauthorized source", () => {
269+
270+
cy.visit("/");
271+
cy.contains("For testing only").click();
272+
cy.contains("Component and materials - 1 variable (indirect source & indirect variables; one unauthorized source)").click();
273+
274+
// Fill in the form
275+
cy.get('.ra-input-componentName').click();
276+
cy.get('li').contains('Component 1').click();
277+
278+
// Comfirm query
279+
cy.get('button[type="submit"]').click();
280+
281+
// Check that it is correctly loaded with and only the correct data appears
282+
cy.contains("Finished in:");
283+
284+
cy.get('.column-componentName').find('span').contains("Component 1").should("exist");
285+
cy.get('.column-materialName').find('span').contains("Material 2").should("exist");
286+
cy.get('.column-materialName').find('span').contains("Material 1").should("exist");
287+
288+
cy.get('.column-componentName').find('span').contains("Component 2").should("not.exist");
289+
cy.get('.column-componentName').find('span').contains("Component 3").should("not.exist");
290+
cy.get('.column-materialName').find('span').contains("Material 6").should("not.exist");
291+
292+
});
293+
294+
it("Indirect with 1 variable and sources from indexfile, no indirect sources found", () => {
295+
296+
cy.visit("/");
297+
cy.contains("For testing only").click();
298+
cy.contains("Component and materials - 1 variable (indirect source & indirect variables; no indirect sources found)").click();
299+
300+
cy.contains("Error getting variable options...").should("exist");;
301+
});
302+
268303
it("Indirect with 2 variables and sources from indexfile", () => {
269304
cy.visit("/");
270305
cy.contains("For testing only").click();

0 commit comments

Comments
 (0)