Skip to content

Commit f66e714

Browse files
authored
Merge pull request #161 from SolidLabResearch/fix-140-save-on-pods
Fix 140 save on pods
2 parents 29a8632 + 5b1fa7f commit f66e714

File tree

12 files changed

+667
-40
lines changed

12 files changed

+667
-40
lines changed

CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- The possibility to save and load custom queries to and from a pod (#140).
13+
14+
### Changed
15+
16+
### Fixed
17+
18+
1019
## [1.3.1] - 2024-08-27
1120

1221
### Added

README.md

+44-7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ Table of contents:
2020
* [Custom queries](#custom-queries)
2121
* [Representation Mapper](#representation-mapper)
2222
* [Using the local pods](#using-the-local-pods)
23+
* [Advanced topics](#advanced-topics)
24+
* [Adapting this project to your needs](#adapting-this-project-to-your-needs)
25+
* [Converting custom queries into common queries](#converting-custom-queries-into-common-queries)
2326
* [Testing](#testing)
2427
* [Testing the production version](#testing-the-production-version)
2528
* [Testing the development version](#testing-the-development-version)
@@ -137,7 +140,7 @@ The configuration file `main/src/config.json` follows a simple structure.
137140
"description": "Description of the query",
138141
"icon": "The key to the icon for the query. This is optional and a default menu icon will be used when left empty.",
139142
"comunicaContext": {
140-
"sources": "Initial list of sources over which the query should be executed",
143+
"sources": "Initial array of sources over which the query should be executed",
141144
"useProxy": "True or false, whether the query should be executed through the proxy or not. This field is optional and defaults to false.",
142145
... any other field that can be used in the Comunica query engine https://comunica.dev/docs/query/advanced/context/
143146
},
@@ -207,10 +210,10 @@ If all possible values for the template variables are fixed and hence can be wri
207210
* In the config file:
208211
* Add a `variables` object in the query's entry in the configuration file.
209212
* In the `variables` object, for each template variable, add a property with name equal to the template variable's identifier.
210-
* Set each such property's value to an array strings, where each string is a possible value for the corresponding template variable.
213+
* Set each such property's value to an array of strings, where each string is a possible value for the corresponding template variable.
211214

212215
Note that template variables' values are not restricted to strings: URIs for example are also possible.
213-
As a consequence, for strings the surround double quotes `"` must be added to the values in the list.
216+
As a consequence, for strings the surround double quotes `"` must be added to the values in the array.
214217
For URIs you must add surrounding angle brackets `<>`.
215218
Other literals (integers for example) don't have to be surrounded with extra delimiters.
216219
This is shown in the configuration structure above.
@@ -264,12 +267,16 @@ In addition, a user can create and edit custom queries, either from scratch or b
264267
* Click "CLONE AS CUSTOM QUERY" (in a normal query) or "CLONE" (in a custom query).
265268
* Make the desired changes in the form and click the "CREATE QUERY" button when ready. The new custom query behaves as if it were created from scratch.
266269

267-
* To reproduce a custom query later, a "SAVE QUERY LINK" button is provided.
270+
* To share a custom query, a "SHARE QUERY" button is provided.
268271
Use it to generate a unique URL for this custom query.
269272
Visiting that URL any time later, recreates a custom query with the same specifications.
270-
This may be useful to forward a custom query to another user.
273+
This may be useful to share a custom query to another user or to save it for yourself.
271274

272-
* To clean up an unwanted custom query, there is always a button "DELETE QUERY"...
275+
* To clean up an unwanted custom query, there is always a button "DELETE QUERY"...
276+
277+
**Warning**: custom queries are stored in your browser's memory and will disappear if the browser page is refreshed or when switching logins.
278+
279+
Logged in users however have the possibility to save/load their custom queries to/from a selectable location in their Solid pod, via the buttons in the Dashboard.
273280

274281
## Representation Mapper
275282

@@ -314,9 +321,39 @@ You can make use of these for your own tests. Follow these steps:
314321
These files will be available in the pod relative to `http://localhost:8080/example/`.
315322
* Prepare the pods by executing `npm run reset:pods` in directory `test`.
316323

324+
## Advanced topics
325+
326+
### Adapting this project to your needs
327+
328+
The easiest way to adapt this project to your needs is:
329+
330+
1. Make your own fork on github.
331+
2. Concentrate on the files in the `main` subdirectory.
332+
3. Add your own queries in the `main/public/queries` directory and in general, your own resources in the `main/public` directory.
333+
4. Write your own `main/src/config.json` file, following the [configuration file documentation above](#configuration-file).
334+
5. Run or build as documented above.
335+
336+
### Converting custom queries into common queries
337+
338+
Once you have your basic configuration working, you may extend it with custom queries interactively with the query editor
339+
and save these to a file in a pod.
340+
You can convert such custom queries into common queries, by adding them to `main/src/config.json`.
341+
Follow these steps to get started:
342+
343+
1. **Open and view the file with custom queries** using a tool, such as [Penny](https://penny.vincenttunru.com/). The file has JSON syntax and contains an array of query objects.
344+
2. **Copy the query objects of interest** to the `"queries"` array in `main/src/config.json`.
345+
Note that the various queries that were documented in the [configuration file documentation above](#configuration-file) in `"queryLocation"` properties,
346+
appear here as `"queryString"` variants, with inline contents rather than references to query files (`*.rq`).
347+
Leave as is or convert to query files as you like.
348+
Inline queries may be hard to read due to the difficult newline coding in JSON syntax.
349+
3. **Update the `"queryGroupId"` property** in all these queries, to separate them from the custom queries. Ensure the group exists in the `"queryGroups"` array, or create a new group if you prefer.
350+
4. **Update the `"id"` property**, to avoid conflicts with remaining custom queries: the id must be unique and it also defines the position in the query group.
351+
5. **Adapt any other properties** according to your preferences.
352+
6. **Save `main/src/config.json`**, rerun or rebuild and refresh your browser to test.
353+
317354
## Testing
318355

319-
For testing we use [Cypress](https://www.cypress.io/).
356+
For testing with the provided configuration file, we use [Cypress](https://www.cypress.io/).
320357

321358
> It is important to test the production version at least at the end of a development cycle.
322359

main/src/IconProvider/IconProvider.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ import CloseIcon from '@mui/icons-material/Close';
2424
import SettingsSuggestIcon from '@mui/icons-material/SettingsSuggest';
2525
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
2626
import FilterNoneIcon from '@mui/icons-material/FilterNone';
27+
import CloudDownloadIcon from '@mui/icons-material/CloudDownload';
28+
import CloudUploadIcon from '@mui/icons-material/CloudUpload';
29+
import HourglassTopIcon from '@mui/icons-material/HourglassTop';
30+
import CreateNewFolderIcon from '@mui/icons-material/CreateNewFolder';
31+
import FolderOffIcon from '@mui/icons-material/FolderOff';
2732

2833
export default {
2934
BrushIcon,
@@ -51,5 +56,10 @@ export default {
5156
CloseIcon,
5257
SettingsSuggestIcon,
5358
ChevronLeftIcon,
54-
FilterNoneIcon
59+
FilterNoneIcon,
60+
CloudDownloadIcon,
61+
CloudUploadIcon,
62+
HourglassTopIcon,
63+
CreateNewFolderIcon,
64+
FolderOffIcon
5565
};

main/src/authenticationProvider/authenticationProvider.js

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export default {
4646
await queryEngine.invalidateHttpCache();
4747
const session = getDefaultSession();
4848
await session.logout();
49+
window.location.reload();
4950
return false;
5051
},
5152
checkAuth: async function checkAuth() {

main/src/components/CustomQueryEditor/customEditor.jsx

+37-26
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ import Checkbox from '@mui/material/Checkbox';
1111
import configManager from '../../configManager/configManager';
1212
import IconProvider from '../../IconProvider/IconProvider';
1313

14+
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
15+
1416

1517
export default function CustomEditor(props) {
18+
const session = getDefaultSession();
19+
const loggedIn = session.info.isLoggedIn;
1620

1721
const location = useLocation();
1822
const navigate = useNavigate();
@@ -30,7 +34,7 @@ export default function CustomEditor(props) {
3034
});
3135

3236
const [showError, setShowError] = useState(false);
33-
const [editError, setEditError] = useState(false)
37+
const [editError, setEditError] = useState(false);
3438
const [parsingErrorComunica, setParsingErrorComunica] = useState(false);
3539
const [parsingErrorAsk, setParsingErrorAsk] = useState(false);
3640
const [parsingErrorTemplate, setParsingErrorTemplate] = useState(false);
@@ -64,12 +68,12 @@ ORDER BY ?genre`;
6468
"(etc...)"
6569
],
6670
"(etc...)": []
67-
}, null, 5)
71+
}, null, 5);
6872

6973

7074
useEffect(() => {
7175
try {
72-
let searchParams
76+
let searchParams;
7377
if (props.newQuery) {
7478
searchParams = new URLSearchParams(location.search);
7579
} else {
@@ -78,16 +82,16 @@ ORDER BY ?genre`;
7882
}
7983
const obj = {}
8084
searchParams.forEach((value, key) => {
81-
obj[key] = value
85+
obj[key] = value;
8286
})
8387

8488
if (obj.indirectQueries) {
85-
setIndirectVariableSourceList(JSON.parse(obj.indirectQueries))
89+
setIndirectVariableSourceList(JSON.parse(obj.indirectQueries));
8690
}
87-
setFormData(obj)
91+
setFormData(obj);
8892

8993
} catch (error) {
90-
setEditError(true)
94+
setEditError(true);
9195
}
9296
}, [location.search]);
9397

@@ -97,12 +101,12 @@ ORDER BY ?genre`;
97101
event.preventDefault();
98102

99103
if (!parsingErrorComunica && !parsingErrorAsk && !parsingErrorTemplate) {
100-
setShowError(false)
104+
setShowError(false);
101105
const formData = new FormData(event.currentTarget);
102106
const jsonData = Object.fromEntries(formData.entries());
103107

104108
if (jsonData.indirectVariablesCheck) {
105-
jsonData.indirectQueries = JSON.stringify(indirectVariableSourceList)
109+
jsonData.indirectQueries = JSON.stringify(indirectVariableSourceList);
106110
}
107111

108112
const searchParams = new URLSearchParams(jsonData);
@@ -119,7 +123,7 @@ ORDER BY ?genre`;
119123
updateQuery(jsonData, customQuery);
120124
}
121125
} else {
122-
setShowError(true)
126+
setShowError(true);
123127
}
124128
};
125129

@@ -133,20 +137,20 @@ ORDER BY ?genre`;
133137
};
134138

135139
const handleIndirectVariablesChange = (event, index) => {
136-
const newList = [...indirectVariableSourceList]
137-
newList[index] = event.target.value
138-
setIndirectVariableSourceList(newList)
140+
const newList = [...indirectVariableSourceList];
141+
newList[index] = event.target.value;
142+
setIndirectVariableSourceList(newList);
139143
}
140144

141145
const handleJSONparsing = (event, errorSetter) => {
142146
const { name, value } = event.target;
143-
errorSetter(false)
147+
errorSetter(false);
144148

145149
let parsedValue;
146150
try {
147151
parsedValue = JSON.parse(value);
148152
} catch (error) {
149-
errorSetter(true)
153+
errorSetter(true);
150154
parsedValue = value;
151155
}
152156

@@ -191,20 +195,20 @@ ORDER BY ?genre`;
191195
}
192196

193197
if (ensureBoolean(dataWithStrings.indirectVariablesCheck)) {
194-
parsedObject.indirectVariables = { queryStrings: JSON.parse(dataWithStrings.indirectQueries) }
198+
parsedObject.indirectVariables = { queryStrings: JSON.parse(dataWithStrings.indirectQueries) };
195199
}
196200

197201
return parsedObject;
198202
}
199203

200204
// These are the functions for the addition and removal of indirect variable input fields
201205
const handleIndirectVariableSource = () => {
202-
setIndirectVariableSourceList([...indirectVariableSourceList, ""])
206+
setIndirectVariableSourceList([...indirectVariableSourceList, ""]);
203207
}
204208
const handleIndirectVariableSourceRemove = (index) => {
205209
const newList = [...indirectVariableSourceList];
206210
newList.splice(index, 1);
207-
setIndirectVariableSourceList(newList)
211+
setIndirectVariableSourceList(newList);
208212
}
209213

210214
// These Functions are the submit functions for whether the creation or edit of a custom query
@@ -218,7 +222,7 @@ ORDER BY ?genre`;
218222
queryGroupId: "cstm",
219223
icon: "AutoAwesomeIcon",
220224
});
221-
navigate(`/${creationID}`)
225+
navigate(`/${creationID}`);
222226
};
223227

224228
const updateQuery = (formData, customQuery) => {
@@ -230,11 +234,18 @@ ORDER BY ?genre`;
230234
icon: customQuery.icon
231235
});
232236

233-
navigate(`/${customQuery.id}`)
237+
navigate(`/${customQuery.id}`);
234238
};
235239

236240
return (
237241
<React.Fragment>
242+
{!loggedIn &&
243+
<Card sx={{ backgroundColor: "#edaa15", padding: '16px', width: '100%' }}>
244+
<b>Warning!</b> You are not logged in, so custom queries cannot be saved to a pod.
245+
The SHARE QUERY button is a solution to save a custom query (as a link).
246+
</Card>
247+
}
248+
238249
<Card
239250
component="form"
240251
onSubmit={handleSubmit}
@@ -307,7 +318,7 @@ ORDER BY ?genre`;
307318
setFormData((prevFormData) => ({
308319
...prevFormData,
309320
'comunicaContextCheck': !formData.comunicaContextCheck,
310-
}))
321+
}));
311322
}
312323
}
313324

@@ -358,7 +369,7 @@ ORDER BY ?genre`;
358369
setFormData((prevFormData) => ({
359370
...prevFormData,
360371
'sourceIndexCheck': !formData.sourceIndexCheck,
361-
}))
372+
}));
362373
}
363374
}
364375
/>} label="Indirect sources" />
@@ -410,7 +421,7 @@ ORDER BY ?genre`;
410421
setFormData((prevFormData) => ({
411422
...prevFormData,
412423
'directVariablesCheck': !formData.directVariablesCheck,
413-
}))
424+
}));
414425
}
415426
}
416427
/>} label="Fixed Variables" />
@@ -447,7 +458,7 @@ ORDER BY ?genre`;
447458
setFormData((prevFormData) => ({
448459
...prevFormData,
449460
'indirectVariablesCheck': !formData.indirectVariablesCheck,
450-
}))
461+
}));
451462
}
452463
}
453464
/>} label="Indirect Variables" />
@@ -508,7 +519,7 @@ ORDER BY ?genre`;
508519
setFormData((prevFormData) => ({
509520
...prevFormData,
510521
'askQueryCheck': !formData.askQueryCheck,
511-
}))
522+
}));
512523
}
513524
}
514525
/>} label="ASK query" />
@@ -563,7 +574,7 @@ ORDER BY ?genre`;
563574
</Button>
564575

565576
:
566-
577+
567578
<Button
568579
variant="outlined"
569580
color='error'

main/src/components/CustomQueryEditor/customQueryEditButton.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export default function CustomQueryEditButton({ queryID, submitted = false }) {
8686
setSaveOpen(true)
8787
}}
8888
sx={{ margin: '10px' }}>
89-
Save Query Link
89+
Share Query
9090
</Button>
9191

9292
<Button

main/src/components/Dashboard/Dashboard.jsx

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import CardContent from '@mui/material/CardContent';
33
import { Title } from 'react-admin';
44
import './Dashboard.css';
55

6+
import SaveCustomToPod from './saveCustomQueriesToPod/saveCustomToPod';
7+
68
import configManager from '../../configManager/configManager';
79

810
/**
@@ -20,6 +22,8 @@ function Dashboard() {
2022
<Title title={title} />
2123
<CardContent>{introductionText}</CardContent>
2224
</Card>
25+
26+
<SaveCustomToPod/>
2327
</div>
2428
);
2529
}

0 commit comments

Comments
 (0)