Skip to content

Commit fb444e0

Browse files
authored
Merge pull request #138 from SolidLabResearch/fix/54-CE-bis
custom query editor
2 parents 1a1a294 + 17f52cc commit fb444e0

File tree

16 files changed

+1183
-163
lines changed

16 files changed

+1183
-163
lines changed

CHANGELOG.md

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

1010
### Added
1111

12+
- It is now possible to add and edit custom queries (#54).
13+
1214
### Changed
1315

1416
- For logged in users not having a username, the webID is displayed (#133).
@@ -17,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1719
### Fixed
1820

1921
- Forced CSS's to not return content type application/ld+json, which induced a CORS error on some CSS server versions (#131).
22+
- Queries based on index file now work for any variable, not just ?object (#136).
2023

2124
## [1.2.1] - 2024-06-17
2225

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Table of contents:
1212
* [Adding variable type](#adding-variable-type)
1313
* [Templated queries](#templated-queries)
1414
* [Query icons](#query-icons)
15+
* [Custom queries](#custom-queries)
1516
* [Representation Mapper](#representation-mapper)
1617
* [Using the local pods](#using-the-local-pods)
1718
* [Testing](#testing)
@@ -140,7 +141,7 @@ The set of sources over which a query will be executed is derived from two *opti
140141

141142
If both inputs are present, the query will be executed over the superset of sources.
142143

143-
The (auxiliary) query provided in `sourceIndex.queryLocation` is executed on `sourceIndex.url` and must result in the list of sources.
144+
The (auxiliary) query provided in `sourceIndex.queryLocation` is executed on `sourceIndex.url` and must result in the list of source URLs.
144145

145146
If `sourceIndex` is used and there is no `comunicaContext.lenient` property found, one will be created with value `true`.
146147
This makes sure that the (main) query can succeed if not all obtained sources are accessible.
@@ -178,6 +179,20 @@ For this to work you need to add the icon to the exports in [IconProvider.js](./
178179
We advise to use the [Material UI icons](https://material-ui.com/components/material-icons/) as this is what's used internally in `react-admin` and it is also included in the dependencies.
179180
Nevertheless, you can use any React component you want, just make sure it's a functional component.
180181

182+
## Custom queries
183+
184+
Besides the prepared queries in the configuration file, a user can edit custom queries:
185+
186+
- To create a custom query, open "Custom Query Editor" from the menu on the left.
187+
- Complete the custom query editor form and click the "CREATE QUERY" button when ready.
188+
- Your new query is added to the "Custom queries" group and you are redirected to the query's result view.
189+
- If not satisfied with the query result, you can click "EDIT QUERY" to further edit your query.
190+
When saving changes, the result is recalculated.
191+
- Because the custom query only lives as long as your browser remembers it, a "SAVE QUERY LINK" button is provided.
192+
Use it to generate a unique URL for this custom query. Copy that URL to your clipboard and save it.
193+
You can then visit that URL any time later, to recreate this query.
194+
- To clean up an unwanted custom query, there is always a button "DELETE QUERY"...
195+
181196
## Representation Mapper
182197

183198
If you want to add your own type representations
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
2+
describe("Custom Query Editor tests", () => {
3+
4+
it("Create a new query", () => {
5+
6+
cy.visit("/#/customQuery");
7+
8+
cy.get('input[name="name"]').type("new query");
9+
cy.get('textarea[name="description"]').type("new description");
10+
11+
cy.get('textarea[name="queryString"]').clear();
12+
cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/>
13+
14+
SELECT * WHERE {
15+
?list schema:name ?listTitle;
16+
schema:itemListElement [
17+
schema:name ?bookTitle;
18+
schema:creator [
19+
schema:name ?authorName
20+
]
21+
].
22+
}`);
23+
cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list");
24+
cy.get('button[type="submit"]').click();
25+
26+
27+
// Checking if the book query works
28+
cy.contains("Colleen Hoover").should('exist');
29+
});
30+
31+
it("Create a new query, with multiple sources", () => {
32+
33+
cy.visit("/#/customQuery");
34+
35+
cy.get('input[name="name"]').type("material query");
36+
cy.get('textarea[name="description"]').type("this query has 3 sources");
37+
38+
cy.get('textarea[name="queryString"]').clear();
39+
cy.get('textarea[name="queryString"]').type(`# Query Texon's components
40+
# Datasources: https://css5.onto-deside.ilabt.imec.be/texon/data/dt/out/components.ttl
41+
42+
PREFIX oo: <http://purl.org/openorg/>
43+
PREFIX ao: <http://purl.org/ontology/ao/core#>
44+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
45+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
46+
PREFIX d: <http://www/example.com/data/>
47+
PREFIX o: <http://www/example.com/ont/>
48+
49+
SELECT DISTINCT ?component ?componentName ?recycledContentPercentage
50+
WHERE {
51+
?component
52+
a o:Component ;
53+
o:name ?componentName ;
54+
o:recycled-content-percentage ?recycledContentPercentage ;
55+
.
56+
}
57+
ORDER BY ?componentName
58+
`);
59+
cy.get('input[name="source"]').type("http://localhost:8080/verifiable-example/components-vc ; http://localhost:8080/verifiable-example/components-vc-incorrect-proof ; http://localhost:8080/example/components");
60+
cy.get('button[type="submit"]').click();
61+
62+
// Checking if the query works
63+
cy.contains("http://www/example.com/data/component-c01").should('exist');
64+
});
65+
66+
it("Check if all possible parameters are filled in with parameterized URL", () => {
67+
68+
// Navigate to the URL of a saved query with completely filled-in form
69+
cy.visit("/#/customQuery?name=Query+Name&description=Query+Description&queryString=Sparql+query+text&comunicaContextCheck=on&source=The+Comunica+Source&comunicaContext=%7B%22Advanced+Comunica+Context%22%3Atrue%7D&sourceIndexCheck=on&indexSourceUrl=Index+Source&indexSourceQuery=Index+Query+&askQueryCheck=on&askQuery=%7B%22trueText%22%3A%22+filled+in%22%2C%22falseText%22%3A%22not+filled+in%22%7D&templatedQueryCheck=on&templateOptions=%7B%22firstvariables%22%3A%5B%22only+one%22%5D%7D")
70+
71+
// Verify that every field is correctly filled-in
72+
cy.get('input[name="name"]').should('have.value', 'Query Name');
73+
cy.get('textarea[name="description"]').should('have.value', 'Query Description');
74+
cy.get('textarea[name="queryString"]').should('have.value', 'Sparql query text');
75+
76+
cy.get('input[name="source"]').should('have.value', "The Comunica Source");
77+
cy.get('textarea[name="comunicaContext"]').should('have.value', `{"Advanced Comunica Context":true}`);
78+
79+
cy.get('input[name="indexSourceUrl"]').should('have.value', "Index Source");
80+
cy.get('textarea[name="indexSourceQuery"]').should('have.value', "Index Query ");
81+
82+
cy.get('textarea[name="askQuery"]').should('have.value', `{"trueText":" filled in","falseText":"not filled in"}`);
83+
84+
cy.get('textarea[name="templateOptions"]').should('have.value', `{"firstvariables":["only one"]}`);
85+
86+
})
87+
88+
it("Successfully edit a query to make it work", () => {
89+
90+
cy.visit("/#/customQuery");
91+
92+
// First create a wrong query
93+
cy.get('input[name="name"]').type("broken query");
94+
cy.get('textarea[name="description"]').type("just a description");
95+
96+
cy.get('textarea[name="queryString"]').clear();
97+
cy.get('textarea[name="queryString"]').type("this is faultive querytext")
98+
99+
cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list");
100+
101+
//Submit the faultive query
102+
cy.get('button[type="submit"]').click();
103+
104+
cy.contains("Custom queries").click();
105+
cy.contains("broken query").click();
106+
107+
// Verify that there are no results
108+
cy.contains("The result list is empty.").should('exist');
109+
110+
// Edit the query
111+
cy.get('button').contains("Edit Query").click();
112+
113+
// Give the query a new name and a correct query text
114+
cy.get('input[name="name"]').clear();
115+
cy.get('input[name="name"]').type("Fixed query");
116+
117+
cy.get('textarea[name="queryString"]').clear();
118+
cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/>
119+
SELECT * WHERE {
120+
?list schema:name ?listTitle;
121+
schema:itemListElement [
122+
schema:name ?bookTitle;
123+
schema:creator [
124+
schema:name ?authorName
125+
]
126+
].
127+
}`);
128+
129+
// Submit the correct query
130+
cy.get('button[type="submit"]').click();
131+
132+
// Now we should be on the page of the fixed query
133+
cy.contains("Fixed query").should('exist');
134+
135+
// Check if the resulting list appears
136+
cy.contains("Colleen Hoover").should('exist');
137+
138+
})
139+
140+
it("Saves the correct URL", () => {
141+
142+
cy.visit("/#/customQuery");
143+
144+
// First create a simple query
145+
cy.get('input[name="name"]').type("new query");
146+
cy.get('textarea[name="description"]').type("new description");
147+
148+
cy.get('textarea[name="queryString"]').clear();
149+
cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/>
150+
SELECT * WHERE {
151+
?list schema:name ?listTitle;
152+
schema:itemListElement [
153+
schema:name ?bookTitle;
154+
schema:creator [
155+
schema:name ?authorName
156+
]
157+
].
158+
}`);
159+
cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list");
160+
cy.get('button[type="submit"]').click();
161+
162+
cy.get('button').contains("Save Query").click();
163+
164+
cy.get('textarea[name="queryURL"]').invoke('val').then((val) => {
165+
expect(val).to.include('?name=new+query&description=new+description&queryString=PREFIX+schema%3A+%3Chttp%3A%2F%2Fschema.org%2F%3E+%0ASELECT+*+WHERE+%7B%0A++++%3Flist+schema%3Aname+%3FlistTitle%3B%0A++++++schema%3AitemListElement+%5B%0A++++++schema%3Aname+%3FbookTitle%3B%0A++++++schema%3Acreator+%5B%0A++++++++schema%3Aname+%3FauthorName%0A++++++%5D%0A++++%5D.%0A%7D&source=http%3A%2F%2Flocalhost%3A8080%2Fexample%2Fwish-list');
166+
});
167+
168+
169+
})
170+
171+
it("Custom templated query", () => {
172+
173+
cy.visit("/#/customQuery");
174+
175+
cy.get('input[name="name"]').type("custom template");
176+
cy.get('textarea[name="description"]').type("description for template");
177+
178+
// Query handling a variable
179+
cy.get('textarea[name="queryString"]').clear();
180+
cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/>
181+
SELECT ?name ?sameAs_url WHERE {
182+
?list schema:name ?listTitle;
183+
schema:name ?name;
184+
schema:genre $genre;
185+
schema:sameAs ?sameAs_url;
186+
}`
187+
);
188+
189+
cy.get('input[name="source"]').type("http://localhost:8080/example/favourite-musicians");
190+
cy.get('input[name="templatedQueryCheck"]').click()
191+
192+
cy.get('textarea[name="templateOptions"]').clear()
193+
cy.get('textarea[name="templateOptions"]').type(`{"variables" : {
194+
"genre": [
195+
"\\"Romantic\\"",
196+
"\\"Baroque\\"",
197+
"\\"Classical\\""
198+
]
199+
}}`)
200+
cy.get('button[type="submit"]').click();
201+
202+
203+
cy.get('form').within(() => {
204+
cy.get('#genre').click();
205+
});
206+
cy.get('li').contains('Baroque').click();
207+
208+
// Comfirm query
209+
cy.get('button[type="submit"]').click();
210+
211+
cy.get('.column-name').find('span').contains("Antonio Caldara").should('exist');
212+
})
213+
214+
it("Custom Query With Index File", () => {
215+
216+
cy.visit("/#/customQuery");
217+
218+
cy.get('input[name="name"]').type("custom with index file");
219+
cy.get('textarea[name="description"]').type("description for index");
220+
221+
// Query handling a variable
222+
cy.get('textarea[name="queryString"]').clear();
223+
cy.get('textarea[name="queryString"]').type(`# Query Texon's components and their materials
224+
# Datasources: https://css5.onto-deside.ilabt.imec.be/texon/data/dt/out/components.ttl https://css5.onto-deside.ilabt.imec.be/texon/data/dt/out/boms.ttl https://css5.onto-deside.ilabt.imec.be/texon/data/dt/out/materials.ttl
225+
226+
PREFIX oo: <http://purl.org/openorg/>
227+
PREFIX ao: <http://purl.org/ontology/ao/core#>
228+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
229+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
230+
PREFIX d: <http://www/example.com/data/>
231+
PREFIX o: <http://www/example.com/ont/>
232+
233+
SELECT ?component ?componentName ?material ?materialName ?percentage
234+
WHERE {
235+
?component
236+
a o:Component ;
237+
o:name ?componentName ;
238+
o:has-component-bom [
239+
o:has-component-material-assoc [
240+
o:percentage ?percentage ;
241+
o:has-material ?material ;
242+
];
243+
];
244+
.
245+
?material o:name ?materialName ;
246+
}
247+
ORDER BY ?componentName`
248+
);
249+
250+
// No Comunica Sources Required
251+
cy.get('input[name="sourceIndexCheck"]').click()
252+
cy.get('input[name="indexSourceUrl"]').type("http://localhost:8080/example/index-example-texon-only")
253+
254+
cy.get('textarea[name="indexSourceQuery"]').clear();
255+
cy.get('textarea[name="indexSourceQuery"]').type(`PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
256+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
257+
PREFIX example: <http://localhost:8080/example/index-example-texon-only#>
258+
259+
SELECT ?object
260+
WHERE {
261+
example:index-example rdfs:seeAlso ?object .
262+
}`
263+
)
264+
cy.get('button[type="submit"]').click();
265+
266+
cy.contains("http://www/example.com/data/component-c01").should('exist');
267+
268+
})
269+
270+
})

0 commit comments

Comments
 (0)