Skip to content

Commit d936dbc

Browse files
authored
Merge pull request #60 from SolidLabResearch/feature/templated-queries
Templated queries
2 parents f534acf + ea79af2 commit d936dbc

File tree

14 files changed

+353
-85
lines changed

14 files changed

+353
-85
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111
- Configurable query icons (#9).
1212
- "Username not given" when logged in, but user's name not known (#51, #65).
13+
- Templated query functionality (#52).
1314

1415
### Changed
1516
- Changed loading message to "The page is loading. Just a moment please." (#26).
1617
- Enabled no bad blocks and indentation for eslint/jsdoc (#14).
1718

1819
### Fixed
1920
- Configured title is now also visible before first query (#46).
21+
- "Unknown User" when not logged in (#51).
2022

2123
### Removed
2224
- Bulk action checkboxes are removed (#44).

README.md

Lines changed: 83 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,21 @@
33
This Web app allows users to easily execute queries over multiple data sources (including Solid pods) and
44
inspect the corresponding results.
55

6+
Table of contents:
7+
<!-- TOC -->
8+
* [Getting Started](#getting-started)
9+
* [Static build](#static-build)
10+
* [Logging in](#logging-in)
11+
* [Configuration file](#configuration-file)
12+
* [Adding variable type](#adding-variable-type)
13+
* [Templated queries](#templated-queries)
14+
* [Query icons](#query-icons)
15+
* [Representation Mapper](#representation-mapper)
16+
* [Testing with local pods](#testing-with-local-pods)
17+
* [Using a local http proxy](#using-a-local-http-proxy)
18+
* [Testing](#testing)
19+
<!-- TOC -->
20+
621
## Getting Started
722

823
After installing, the following steps suffice to install the application:
@@ -17,63 +32,70 @@ after this you can execute
1732
npm run dev
1833
```
1934

20-
Which will start the web application
35+
which will start the web application. Now you can browse the displayed URL.
36+
37+
If you want to test the default configuration however, you first need to complete the steps
38+
described in [testing with local pods](#testing-with-local-pods) and [using a local http proxy](#using-a-local-http-proxy).
39+
Do each of these in their own separate terminal window.
2140

2241
## Static build
2342

24-
If you want a static build of the application execute
43+
If you want a static build of the application, execute:
2544

2645
```bash
2746
npm run build
2847
```
2948

3049
This will create a static build in the `dist` folder.
3150

32-
### Logging in
51+
## Logging in
3352

34-
To log in you need to provide an Identity Provider or a WebID.
53+
Some queries access data sources that are only readable by authenticated users. This requires you to log in.
54+
To log in, you need to provide an Identity Provider or a WebID.
3555
The application will detect which one you use and redirect you to the login page of your Identity Provider.
3656
If you use your WebID, the first OIDC issuer on your WebID is used when there are multiple.
3757

38-
### Configuration file
58+
## Configuration file
3959

4060
The configuration file follows a simple structure.
4161

42-
```js
62+
```json
4363
{
44-
"title": "Title shown at the top of the app.",
45-
"logoLocation": "Image location of the logo shown at the top of the app (relative to public folder.).",
46-
"logoRedirectURL": "The URL the Web application redirects to when a user clicks on the logo.",
47-
"mainAppColor": "The main colors used in the app, can be any CSS color.",
48-
"backgroundColor": "Background color of the app, can be any CSS color.",
49-
"titleColor": "The color of the title, can be any CSS color",
50-
"textColor": "The color of all the text in teh app body, this means all text except header and footer.",
51-
"footer": "HTML components or text that will function as the footer (will be placed in the footer div.)",
52-
"defaultIDP": "The default value used for IDP when logging in, this IDP can be manually changed in the Web app as well. ",
53-
"queryFolder": "The base location of the queries, all query locations will start from this folder (relative to public folder.)",
54-
"httpProxy": "The http proxy through which the requests will be rerouted. When left empty, the Comunica query engine will handle it. This is useful when CORS headers are not set (correctly) on the queried source.",
55-
"introductionText": "The text that the app shows on the dashboard, which the app also shows when you first open it.",
56-
"queries": [
57-
{
58-
"queryLocation": "path to the query location, relative to "queryFolder"",
59-
"name": "A name for the query",
60-
"description": "Description of the query",
61-
"id": "A unique ID for the query",
62-
"icon": "The key to the icon for to represent the query (see Icon Provider below). This is optional and a default menu icon will be used when left empty.",
63-
"comunicaContext": {
64-
"sources": "Sources over which the query should be executed",
65-
"useProxy": "True or false, whether the query should be executed through the proxy or not. This field is optional and defaults to false.",
66-
...{"any other field that can be used in the Comunica query engine https://comunica.dev/docs/query/advanced/context/"}
67-
},
68-
},
69-
"askQuery": {
70-
"trueText": "The text that is to be shown when the query result is true, only useful for ASK queries.",
71-
"falseText": "The text that is to be shown when the query result is true, only useful for ASK queries."
72-
}
73-
74-
}
75-
...
76-
]
64+
"title": "Title shown at the top of the app.",
65+
"logoLocation": "Image location of the logo shown at the top of the app (relative to public folder.).",
66+
"logoRedirectURL": "The URL the Web application redirects to when a user clicks on the logo.",
67+
"mainAppColor": "The main colors used in the app, can be any CSS color.",
68+
"backgroundColor": "Background color of the app, can be any CSS color.",
69+
"titleColor": "The color of the title, can be any CSS color",
70+
"textColor": "The color of all the text in teh app body, this means all text except header and footer.",
71+
"footer": "HTML components or text that will function as the footer (will be placed in the footer div.)",
72+
"defaultIDP": "The default value used for IDP when logging in, this IDP can be manually changed in the Web app as well. ",
73+
"queryFolder": "The base location of the queries, all query locations will start from this folder (relative to public folder.)",
74+
"httpProxy": "The http proxy through which the requests will be rerouted. When left empty, the Comunica query engine will handle it. This is useful when CORS headers are not set (correctly) on the queried source.",
75+
"introductionText": "The text that the app shows on the dashboard, which the app also shows when you first open it.",
76+
"queries": [
77+
{
78+
"queryLocation": "path to the query location, relative to "queryFolder"",
79+
"name": "A name for the query",
80+
"description": "Description of the query",
81+
"id": "A unique ID for the query",
82+
"icon": "The key to the icon for the query . This is optional and a default menu icon will be used when left empty.",
83+
"comunicaContext": {
84+
"sources": "Sources over which the query should be executed",
85+
"useProxy": "True or false, whether the query should be executed through the proxy or not. This field is optional and defaults to false.",
86+
...{"any other field that can be used in the Comunica query engine https://comunica.dev/docs/query/advanced/context/"}
87+
},
88+
"variables": {
89+
"variableExampleString": ["\"String1\"", "\"String2\""],
90+
"variableExampleUri": ["<https://example.com/uri1>", "<https://example.com/uri2>"]
91+
},
92+
"askQuery": {
93+
"trueText": "The text that is to be shown when the query result is true, only useful for ASK queries.",
94+
"falseText": "The text that is to be shown when the query result is true, only useful for ASK queries."
95+
}
96+
}
97+
...
98+
]
7799
}
78100
```
79101

@@ -88,25 +110,39 @@ we can fully interpret how we can display and represent the result.
88110
You can specify the type of a variable by extending its name with the type in the query as such: `variableName_variableType`.
89111
The underscore `_` here is crucial to make a clear distinction between name and type.
90112

91-
### Query Icons
113+
### Templated queries
114+
115+
This application supports queries whose contents are not completely fixed upfront: they contain variables whose value can be set interactively.
116+
117+
To change a query into a templated query:
118+
- replace the fixed portion(s) of the query with (a) variable(s); a variable is an identifier preceded by a `$` sign, e.g. `$genre`
119+
- add a `variables` object in the query's entry in the configuration file
120+
- in the `variables` object, for each variable, add a property with name equal to the variable's identifier
121+
- set each such property's value to an array of possible values for the corresponding variable
122+
123+
Note that variables' values are not restricted to strings: URIs for example are alo possible.
124+
As a consequence, for strings the surround double quotes `"` must be added to the values in the list.
125+
This is shown in the configuration structure above.
126+
127+
### Query icons
92128

93129
In the selection menu the name of the query is proceeded by an icon.
94130
You configure this icon per query in the configuration file.
95131
For this to work you need to add the icon to the exports in [IconProvider.js](./src/IconProvider/IconProvider.js).
96132
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.
97133
Nevertheless, you can use any React component you want, just make sure it's a functional component.
98134

99-
### Representation Mapper
135+
## Representation Mapper
100136

101137
If you want to add your own type representations
102-
you can do this by adding your representation to the [representationProvider.jsx](./src/representationProvider/representationProvider.jsx) file.
138+
you can do this by adding your representation to the [representationProvider.js](./src/representationProvider/representationProvider.js) file.
103139
This can be useful for example when querying images.
104140
The result of the query is a reference to the image.
105141
By mapping a representation we can show the actual image instead of the reference.
106142

107143
The mapper follows a structure:
108144

109-
```js
145+
```json
110146
{
111147
"typeName": mapperComponent,
112148
...
@@ -117,7 +153,7 @@ With `typeName` being the name of the variable as defined in the `query`
117153
which is defined in [the configuration file](#configuration-file).
118154
The function `mapperComponent` takes the query result for the corresponding variable and
119155
returns either a [React](https://react.dev/) component (see below).
120-
Examples of how you can do this can already be found in the [representationProvider/components folder](./src/representationProvider/components/).
156+
Examples of how you can do this can already be found in the [representationProvider components folder](./src/representationProvider/components/).
121157

122158
The components get the following props:
123159

@@ -136,13 +172,12 @@ To create a local pod with which you can test for example authentication you can
136172

137173
- Add your data and `.acl` files in the `initial-pod-data` folder.
138174
These files will be available in the pod relative to `http://localhost:8080/example/`.
139-
We already added files for the resource `favourite-books`.
175+
We already added files to support the example queries in the configuration file.
140176
- Prepare the pods by executing `npm run prepare:pods`.
141177
- Start the pods by executing `npm run start:pods`.
142178
- Add your query as described in [the configuration file section](#configuration-file).
143-
We already added a query to list books based on the resource `favourite-books` to `src/config.json`.
144-
- Log in with the IDP `http://localhost:8080` and
145-
the credentials in the file `seeded-pod-config.json`.
179+
We already added some example queries in the default configuration file `src/config.json`.
180+
- Log in with the IDP `http://localhost:8080` and the credentials for the user owning the pod named `example` in the file `seeded-pod-config.json`.
146181

147182
## Using a local http proxy
148183

cypress.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { defineConfig } from "cypress";
22

33
export default defineConfig({
44
e2e: {
5+
defaultCommandTimeout: 10000, /* is OK for very slow computers */
56
baseUrl: 'http://localhost:5173',
67
supportFile: false,
78
video: false
89
},
9-
});
10+
});

cypress/e2e/spec.cy.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ describe("Web app", () => {
66
it("Custom icon per query is displayed", () => {
77
cy.visit("/");
88

9-
cy.get('[data-testid="BiotechIcon"]').should("exist");
9+
cy.get('[data-testid="PhotoIcon"]').should("exist");
1010
cy.get('[data-testid="BrushIcon"]').should("exist");
1111
})
1212

@@ -30,6 +30,26 @@ describe("Web app", () => {
3030
cy.get('[aria-label="Query was succesful"]').should("exist");
3131
});
3232

33+
it("Fetch status source info on templated query success", () => {
34+
cy.visit("/");
35+
36+
cy.contains("Templated query #2 about my favourite musicians").click();
37+
38+
cy.get('form').within(() => {
39+
cy.get('#genre').click();
40+
});
41+
cy.get('li').contains('Classical').click();
42+
cy.get('form').within(() => {
43+
cy.get('#sameAsUrl').click();
44+
});
45+
cy.get('li').contains('Mozart').click();
46+
47+
cy.get('button').contains('Query').click();
48+
cy.contains("Finished in:");
49+
cy.get('[aria-label="Sources info"]').click();
50+
cy.get('[aria-label="Query was succesful"]').should("exist");
51+
});
52+
3353
it("Authentication needed source info for query on public data", () => {
3454
cy.visit("/");
3555

@@ -186,7 +206,7 @@ describe("Web app", () => {
186206
it("Querying variable ending on _img should return an image as result", () => {
187207
cy.visit("/");
188208

189-
cy.contains("A Test For Images").click();
209+
cy.contains("A test for images").click();
190210
cy.contains("Finished in:");
191211
cy.get("td").find("img");
192212
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
PREFIX schema: <http://schema.org/>
2+
3+
SELECT ?name ?sameAs_url WHERE {
4+
?list schema:name ?listTitle;
5+
schema:name ?name;
6+
schema:genre $genre;
7+
schema:sameAs ?sameAs_url;
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
PREFIX schema: <http://schema.org/>
2+
3+
SELECT ?name WHERE {
4+
?list schema:name ?listTitle;
5+
schema:name ?name;
6+
schema:genre $genre;
7+
schema:sameAs $sameAsUrl;
8+
}

src/App.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import {
77
getDefaultSession,
88
handleIncomingRedirect,
99
} from "@inrupt/solid-client-authn-browser";
10-
import ListResultTable from "./components/ListResultTable/ListResultTable";
1110
import IconProvider from "./IconProvider/IconProvider";
1211
import authenticationProvider from "./authenticationProvider/authenticationProvider";
1312
import SolidLoginForm from "./components/LoginPage/LoginPage";
1413
import {QueryClient} from "react-query";
1514
import Dashboard from "./components/Dashboard/Dashboard";
1615
import InteractionLayout from "./components/InteractionLayout/InteractionLayout";
16+
import TemplatedListResultTable from "./components/ListResultTable/TemplatedListResultTable.jsx";
1717

1818
const queryClient = new QueryClient({
1919
defaultOptions: {
@@ -70,7 +70,7 @@ function App() {
7070
name={query.id}
7171
options={{ label: query.name }}
7272
icon={IconProvider[query.icon]}
73-
list={ListResultTable}
73+
list={TemplatedListResultTable}
7474
/>
7575
);
7676
})}

src/components/ListResultTable/ListResultTable.jsx

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
import {
2-
useListController,
3-
Loading,
4-
ListBase,
5-
} from "react-admin";
1+
import {ListBase, Loading, useListController,} from "react-admin";
62
import PropTypes from "prop-types";
7-
import { Component } from "react";
3+
import {Component} from "react";
84
import QueryResultList from "./QueryResultList/QueryResultList";
95

10-
/**
6+
/**
117
* @param {object} props - the props passed down to the component
128
* @returns {Component} custom List as defined by react-admin which either shows a loading indicator or the query results
139
*/
@@ -21,9 +17,18 @@ function ListResultTable(props) {
2117
perPage,
2218
resource,
2319
sort,
20+
variables,
2421
...rest
2522
} = props;
26-
const { isLoading } = useListController();
23+
24+
const {isLoading} = useListController({
25+
queryOptions: {
26+
meta: {
27+
variables: variables
28+
}
29+
}
30+
});
31+
2732
return (
2833
<ListBase
2934
debounce={debounce}
@@ -33,7 +38,7 @@ function ListResultTable(props) {
3338
filter={filter}
3439
filterDefaultValues={filterDefaultValues}
3540
perPage={perPage}
36-
queryOptions={{ keepPreviousData: false }}
41+
queryOptions={{keepPreviousData: false}}
3742
resource={resource}
3843
sort={sort}
3944
>
@@ -54,6 +59,7 @@ ListResultTable.propTypes = {
5459
queryOptions: PropTypes.object,
5560
resource: PropTypes.string,
5661
sort: PropTypes.object,
62+
variables: PropTypes.object,
5763
};
5864

59-
export default ListResultTable;
65+
export default ListResultTable;

0 commit comments

Comments
 (0)