From 2f7fe2942ed4fc99bfa26a332254d5efe6cb82d6 Mon Sep 17 00:00:00 2001 From: EmilioTR Date: Mon, 13 May 2024 13:55:19 +0200 Subject: [PATCH 01/47] added custom editor, not fully operational --- .../CustomQueryEditor/customEditor.jsx | 140 ++++++++++++++++++ src/components/Dashboard/Dashboard.css | 3 +- src/components/Dashboard/Dashboard.jsx | 19 ++- 3 files changed, 155 insertions(+), 7 deletions(-) create mode 100644 src/components/Dashboard/CustomQueryEditor/customEditor.jsx diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx new file mode 100644 index 00000000..f6051b8c --- /dev/null +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -0,0 +1,140 @@ +import React, { useState } from 'react'; +import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; + +import { QueryEngine } from "@comunica/query-sparql"; + +const myEngine = new QueryEngine(); +//import {SimpleForm, TextInput, required } from 'react-admin'; + + +export default function CustomEditor() { + + const [openEditor, setOpenEditor] = useState(false); + const [customQuery, setCustomQuery] = useState('') + const [resultList, setResultList] = useState([]) + + const closeEditor = () => { + setOpenEditor(false) + } + + const finalise = async () => { + //setResultList(await ResultsFromQuery(customQuery.source, customQuery.query)) + console.log(resultList) + } + + return ( + + + + + + { + event.preventDefault(); + const formData = new FormData(event.currentTarget); + const jsonData = Object.fromEntries(formData.entries()); + + setResultList(await ResultsFromQuery(jsonData.source , jsonData.query)); + + closeEditor(); + + finalise(); + }, + }} + > + Custom Query Editor + + + Get results from a custom query in SPARQL + + +
+ / +
+ +
+ +
+
+ + + + + +
+
+ ) + +} + +async function ResultsFromQuery (source, query){ + + // const a = `PREFIX rdf: PREFIX rdfs: PREFIX example: SELECT ?object WHERE { example:index-example rdfs:seeAlso ?object . }` + + // const b = `http://localhost:8080/example/index-example-texon-only` + + console.log(source , query) + + const results = []; +try{ + const bindingsStream = await myEngine.queryBindings(query, { + sources: [source], + }); + + await new Promise((resolve, reject) => { + bindingsStream.on('data', (binding) => { + const source = binding.get('object').value; + console.log(binding) + if (!results.includes(source)) { + results.push(source); + } + }); + bindingsStream.on('end', resolve); + bindingsStream.on('error', reject); + }); + + return results; + +}catch(error){ + console.log('nahhh brooo:' , error) +} +} + diff --git a/src/components/Dashboard/Dashboard.css b/src/components/Dashboard/Dashboard.css index d3efe624..d5c96543 100644 --- a/src/components/Dashboard/Dashboard.css +++ b/src/components/Dashboard/Dashboard.css @@ -1,3 +1,4 @@ #main-content { - margin-top: 30px; + margin: 30px; + } diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index 1508a031..c437025d 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -1,7 +1,8 @@ import Card from '@mui/material/Card'; import CardContent from '@mui/material/CardContent'; -import {Title} from 'react-admin'; +import { Title } from 'react-admin'; import PropTypes from 'prop-types'; +import CustomEditor from './CustomQueryEditor/customEditor'; import './Dashboard.css'; /** @@ -10,16 +11,22 @@ import './Dashboard.css'; * @returns {function(): *} - A function that creates a dashboard with an introduction for . */ function Dashboard(props) { - let {title, text} = props; + let { title, text } = props; title = title || 'You change this title via the config file.'; text = text || 'You change this text via the config file.'; return ( - - - <CardContent>{text}</CardContent> - </Card> + <div> + + <CustomEditor/> + <Card> + <Title title={title} /> + <CardContent>{text}</CardContent> + </Card> + + + </div> ); } From 0bdf196d53fa2f0301afae74a758dc8c435b11c7 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Mon, 13 May 2024 16:37:44 +0200 Subject: [PATCH 02/47] experimental implememtation, still not good --- .../CustomQueryEditor/customEditor.jsx | 122 ++++++++++++------ 1 file changed, 81 insertions(+), 41 deletions(-) diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index f6051b8c..79608a22 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -8,35 +8,30 @@ import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; import { QueryEngine } from "@comunica/query-sparql"; +import QueryResultList from "../../ListResultTable/QueryResultList/QueryResultList" +import ListResultTable from '../../ListResultTable/ListResultTable'; const myEngine = new QueryEngine(); -//import {SimpleForm, TextInput, required } from 'react-admin'; - export default function CustomEditor() { const [openEditor, setOpenEditor] = useState(false); - const [customQuery, setCustomQuery] = useState('') - const [resultList, setResultList] = useState([]) + const [customQueryData, setCustomQueryData] = useState(null) const closeEditor = () => { setOpenEditor(false) } - const finalise = async () => { - //setResultList(await ResultsFromQuery(customQuery.source, customQuery.query)) - console.log(resultList) - } - return ( <React.Fragment> <Button variant="contained" onClick={ - () => { setOpenEditor(true) }}> + () => { setOpenEditor(true) }} + sx={{ margin: '10px' }}> Custom query </Button> <Button variant="contained" onClick={ - () => { console.log(resultList, customQuery)}}> + () => {console.log(customQueryData)}}> Show data </Button> @@ -51,12 +46,13 @@ export default function CustomEditor() { event.preventDefault(); const formData = new FormData(event.currentTarget); const jsonData = Object.fromEntries(formData.entries()); - - setResultList(await ResultsFromQuery(jsonData.source , jsonData.query)); + + // setResultList(await ResultsFromQuery(jsonData.source , jsonData.query)); + + setCustomQueryData(await getData(jsonData.query, jsonData.source)); closeEditor(); - - finalise(); + }, }} > @@ -100,41 +96,85 @@ export default function CustomEditor() { <Button variant="contained" type="submit">Submit Query</Button> </DialogActions> </Dialog> + + {/* {customQueryData && <div> + this exists + {customQueryData.itemListElement.map((e) => { + console.log(e) + return( + <div key={e.name}> + {e.name} + </div> + ) + })} + </div>} */} + + {/* {customQueryData && <div> + this exists + {Object.keys(customQueryData).map((key) => { + console.log(key) + return ( + <div></div> + ) + })} + </div>} */} + </React.Fragment> ) } -async function ResultsFromQuery (source, query){ - - // const a = `PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX example: <http://localhost:8080/example/index-example-texon-only#> SELECT ?object WHERE { example:index-example rdfs:seeAlso ?object . }` - - // const b = `http://localhost:8080/example/index-example-texon-only` - - console.log(source , query) +async function executeSPARQLQuery(query, dataSource) { + const url = new URL(dataSource); + const params = new URLSearchParams(); + params.append('query', query); + url.search = params.toString(); + + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to execute SPARQL query. 333 Status: ${response.status}`); + } + + const data = await response.json(); + console.log(data) + + const adjustedData = { + results: { + bindings: data.ItemListElement.map(item => ({ + name: { value: item.name }, + genre: { value: item.genre }, + sameAs_url: { value: item.sameAs["@id"] } + })) + } + }; + + console.log(adjustedData) + + return adjustedData; + } catch (error) { + throw new Error(`Error executing SPARQL query 111: ${error.message}`); + } +} - const results = []; -try{ - const bindingsStream = await myEngine.queryBindings(query, { - sources: [source], - }); +async function getData(query, dataSource) { + const data = executeSPARQLQuery(query, dataSource) - await new Promise((resolve, reject) => { - bindingsStream.on('data', (binding) => { - const source = binding.get('object').value; - console.log(binding) - if (!results.includes(source)) { - results.push(source); - } + + try { + const result = await myEngine.queryBindings(query, { + sources: [{ value: data }], }); - bindingsStream.on('end', resolve); - bindingsStream.on('error', reject); - }); - return results; + console.log('Query results:', result); + -}catch(error){ - console.log('nahhh brooo:' , error) -} + // Process the query results directly + + + } catch (error) { + console.error('Error executing SPARQL query 222:', error); + } } + From 655a49aed57a6c0fd0307e879f8b9e6968c3c437 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Thu, 16 May 2024 11:15:37 +0200 Subject: [PATCH 03/47] working qury results --- .../CustomQueryEditor/customEditor.jsx | 72 +++++++------------ 1 file changed, 24 insertions(+), 48 deletions(-) diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index 79608a22..e0e8da9e 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -17,6 +17,7 @@ export default function CustomEditor() { const [openEditor, setOpenEditor] = useState(false); const [customQueryData, setCustomQueryData] = useState(null) + const [showError, setShowError] = useState(false) const closeEditor = () => { setOpenEditor(false) @@ -46,11 +47,10 @@ export default function CustomEditor() { event.preventDefault(); const formData = new FormData(event.currentTarget); const jsonData = Object.fromEntries(formData.entries()); + setShowError(false); - // setResultList(await ResultsFromQuery(jsonData.source , jsonData.query)); - - setCustomQueryData(await getData(jsonData.query, jsonData.source)); - + setCustomQueryData(await executeSPARQLQuery(jsonData.query, jsonData.source, showError, setShowError)); + closeEditor(); }, @@ -59,7 +59,7 @@ export default function CustomEditor() { <DialogTitle>Custom Query Editor</DialogTitle> <DialogContent> <DialogContentText> - Get results from a custom query in SPARQL + {showError? 'Something went wrong while querying, please review your query' : ''} </DialogContentText> <div> @@ -72,7 +72,7 @@ export default function CustomEditor() { placeholder="http://examplesource.org" helperText="Give the source Url for the query" variant='outlined' - />/ + /> </div> <div> @@ -124,57 +124,33 @@ export default function CustomEditor() { } -async function executeSPARQLQuery(query, dataSource) { - const url = new URL(dataSource); - const params = new URLSearchParams(); - params.append('query', query); - url.search = params.toString(); - - try { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`Failed to execute SPARQL query. 333 Status: ${response.status}`); - } - - const data = await response.json(); - console.log(data) - - const adjustedData = { - results: { - bindings: data.ItemListElement.map(item => ({ - name: { value: item.name }, - genre: { value: item.genre }, - sameAs_url: { value: item.sameAs["@id"] } - })) - } - }; +async function executeSPARQLQuery(query, dataSource, setShowError) { - console.log(adjustedData) - - return adjustedData; - } catch (error) { - throw new Error(`Error executing SPARQL query 111: ${error.message}`); - } -} - -async function getData(query, dataSource) { - const data = executeSPARQLQuery(query, dataSource) + const resultingObjects = [] - + console.log("query; " , query) + console.log("datasource: ", dataSource) try { - const result = await myEngine.queryBindings(query, { - sources: [{ value: data }], - }); + const bindingsStream = await myEngine.queryBindings(query, { + sources: [dataSource] + }); - console.log('Query results:', result); + bindingsStream.on('data', (binding) => { + //console.log(binding); + //console.log(binding.toString()); + + resultingObjects.push(JSON.parse(binding.toString())); + +}); - // Process the query results directly - } catch (error) { - console.error('Error executing SPARQL query 222:', error); + setShowError(true); + throw new Error(`Error executing SPARQL query: ${error.message}`); } + + return resultingObjects; } From 0e28667c130a2a7d7987b1df9e165d8024874899 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Thu, 16 May 2024 13:26:49 +0200 Subject: [PATCH 04/47] working query prompt and added error handling --- .../CustomQueryEditor/customEditor.jsx | 67 +++++-------------- 1 file changed, 17 insertions(+), 50 deletions(-) diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index e0e8da9e..1de7085d 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -18,9 +18,11 @@ export default function CustomEditor() { const [openEditor, setOpenEditor] = useState(false); const [customQueryData, setCustomQueryData] = useState(null) const [showError, setShowError] = useState(false) + const closeEditor = () => { - setOpenEditor(false) + setOpenEditor(false); + setShowError(false); } return ( @@ -32,7 +34,7 @@ export default function CustomEditor() { </Button> <Button variant="contained" onClick={ - () => {console.log(customQueryData)}}> + () => { console.log(customQueryData) }}> Show data </Button> @@ -47,19 +49,18 @@ export default function CustomEditor() { event.preventDefault(); const formData = new FormData(event.currentTarget); const jsonData = Object.fromEntries(formData.entries()); - setShowError(false); - - setCustomQueryData(await executeSPARQLQuery(jsonData.query, jsonData.source, showError, setShowError)); - + + setCustomQueryData(await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError)); closeEditor(); - }, }} > <DialogTitle>Custom Query Editor</DialogTitle> + + <DialogContent> - <DialogContentText> - {showError? 'Something went wrong while querying, please review your query' : ''} + <DialogContentText sx={{ color: 'red', mb: '10px' }}> + {showError ? 'Invalid Query. Check the URL and Query Syntax' : ''} </DialogContentText> <div> @@ -97,59 +98,25 @@ export default function CustomEditor() { </DialogActions> </Dialog> - {/* {customQueryData && <div> - this exists - {customQueryData.itemListElement.map((e) => { - console.log(e) - return( - <div key={e.name}> - {e.name} - </div> - ) - })} - </div>} */} - - {/* {customQueryData && <div> - this exists - {Object.keys(customQueryData).map((key) => { - console.log(key) - return ( - <div></div> - ) - })} - </div>} */} - </React.Fragment> ) } async function executeSPARQLQuery(query, dataSource, setShowError) { - - const resultingObjects = [] - - console.log("query; " , query) - console.log("datasource: ", dataSource) + const resultingObjects = []; try { const bindingsStream = await myEngine.queryBindings(query, { - sources: [dataSource] - }); - - - bindingsStream.on('data', (binding) => { - //console.log(binding); - //console.log(binding.toString()); - - resultingObjects.push(JSON.parse(binding.toString())); - -}); - + sources: [dataSource] + }); + bindingsStream.on('data', (binding) => { + resultingObjects.push(JSON.parse(binding.toString())); + }); } catch (error) { - setShowError(true); + setShowError(true); throw new Error(`Error executing SPARQL query: ${error.message}`); } - return resultingObjects; } From 4f2a5fc2c3519486e3af41d03a952cab6ace3979 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Thu, 16 May 2024 17:31:23 +0200 Subject: [PATCH 05/47] First visual representation, works completely --- .../CustomQueryEditor/customEditor.jsx | 39 ++++++++------ .../Dashboard/CustomQueryEditor/tableData.jsx | 51 +++++++++++++++++++ 2 files changed, 75 insertions(+), 15 deletions(-) create mode 100644 src/components/Dashboard/CustomQueryEditor/tableData.jsx diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index 1de7085d..b41debb0 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -6,19 +6,22 @@ import TextField from '@mui/material/TextField'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; - import { QueryEngine } from "@comunica/query-sparql"; -import QueryResultList from "../../ListResultTable/QueryResultList/QueryResultList" -import ListResultTable from '../../ListResultTable/ListResultTable'; +// import QueryResultList from "../../ListResultTable/QueryResultList/QueryResultList" +// import ListResultTable from '../../ListResultTable/ListResultTable'; + +import TableData from './tableData'; const myEngine = new QueryEngine(); export default function CustomEditor() { const [openEditor, setOpenEditor] = useState(false); - const [customQueryData, setCustomQueryData] = useState(null) + const [customQueryData, setCustomQueryData] = useState([]) const [showError, setShowError] = useState(false) - + const [isSubmitted, setIsSubmitted] = useState(false) + + const [showTable, setShowTable] = useState(false) const closeEditor = () => { setOpenEditor(false); @@ -31,12 +34,16 @@ export default function CustomEditor() { () => { setOpenEditor(true) }} sx={{ margin: '10px' }}> Custom query - </Button> - <Button variant="contained" onClick={ - () => { console.log(customQueryData) }}> - Show data </Button> + {isSubmitted && + <Button variant="outlined" color={showTable ? "error" : "primary"} onClick={ + () => { + setShowTable(!showTable); + }}> + {showTable ? "Hide Table" : "Show Table"} + </Button> + } <Dialog open={openEditor} @@ -49,20 +56,20 @@ export default function CustomEditor() { event.preventDefault(); const formData = new FormData(event.currentTarget); const jsonData = Object.fromEntries(formData.entries()); - - setCustomQueryData(await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError)); + const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError) + setIsSubmitted(false) + setShowTable(false) + setCustomQueryData(data); closeEditor(); + setIsSubmitted(true) }, }} > <DialogTitle>Custom Query Editor</DialogTitle> - - <DialogContent> <DialogContentText sx={{ color: 'red', mb: '10px' }}> {showError ? 'Invalid Query. Check the URL and Query Syntax' : ''} </DialogContentText> - <div> <TextField required @@ -98,11 +105,13 @@ export default function CustomEditor() { </DialogActions> </Dialog> + {showTable && <TableData data={customQueryData} />} + </React.Fragment> ) - } + async function executeSPARQLQuery(query, dataSource, setShowError) { const resultingObjects = []; try { diff --git a/src/components/Dashboard/CustomQueryEditor/tableData.jsx b/src/components/Dashboard/CustomQueryEditor/tableData.jsx new file mode 100644 index 00000000..91d70636 --- /dev/null +++ b/src/components/Dashboard/CustomQueryEditor/tableData.jsx @@ -0,0 +1,51 @@ +import * as React from 'react'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; + + +export default function TableData(data) { + const keys = [] + if (data.data === null || data.data === undefined || data.data.length === 0) { + return ( + <Typography variant="body1" component="div" sx={{ margin: '10px' }}> + There are no results for this custom query. + </Typography> + ) + } + Object.keys(data.data[0]).forEach((k) => { keys.push(k) }) + return ( + <TableContainer sx={{ marginBottom: '20px', marginTop: '10px' }} component={Paper}> + <Table sx={{ minWidth: 650 }} aria-label="simple table"> + <TableHead> + <TableRow> + {keys.map((label) => { + return ( + <TableCell key={label} align="left">{label}</TableCell> + ) + })} + </TableRow> + </TableHead> + <TableBody> + {data.data.map((row, i) => ( + <TableRow + key={i} + > + {keys.map((cat) => ( + <TableCell key={cat + i} > + {row[cat]} + </TableCell> + ))} + </TableRow> + ) + )} + </TableBody> + </Table> + </TableContainer> + ); +} From 1947e11be380afa6aa80d8b0261c1faee7b19912 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Tue, 28 May 2024 13:08:05 +0200 Subject: [PATCH 06/47] temporary attempt to save new queries and show title --- .../CustomQueryEditor/customEditor.jsx | 31 ++++++++++++++++++- .../savedQueries/customQueries.json | 8 +++++ .../Dashboard/CustomQueryEditor/tableData.jsx | 6 ++++ 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 src/components/Dashboard/CustomQueryEditor/savedQueries/customQueries.json diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index b41debb0..a425ca82 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -7,6 +7,7 @@ import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; import { QueryEngine } from "@comunica/query-sparql"; + // import QueryResultList from "../../ListResultTable/QueryResultList/QueryResultList" // import ListResultTable from '../../ListResultTable/ListResultTable'; @@ -20,6 +21,7 @@ export default function CustomEditor() { const [customQueryData, setCustomQueryData] = useState([]) const [showError, setShowError] = useState(false) const [isSubmitted, setIsSubmitted] = useState(false) + const [customQueryJSON, setCustomQueryJSON] = useState({}) const [showTable, setShowTable] = useState(false) @@ -45,6 +47,13 @@ export default function CustomEditor() { </Button> } +{/* <Button variant="contained" onClick={ + () => { console.log(customQueryJSON.title) }} + sx={{ margin: '10px' }}> + logging + + </Button> */} + <Dialog open={openEditor} onClose={closeEditor} @@ -56,6 +65,12 @@ export default function CustomEditor() { event.preventDefault(); const formData = new FormData(event.currentTarget); const jsonData = Object.fromEntries(formData.entries()); + console.log(jsonData) + setCustomQueryJSON(jsonData); + + console.log(customQueryJSON) + + const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError) setIsSubmitted(false) setShowTable(false) @@ -70,6 +85,20 @@ export default function CustomEditor() { <DialogContentText sx={{ color: 'red', mb: '10px' }}> {showError ? 'Invalid Query. Check the URL and Query Syntax' : ''} </DialogContentText> + + <div> + <TextField + required + fullWidth + name='title' + id="outlined-required" + label="Query title " + placeholder="Custom query name" + helperText="Give this custom query a name" + variant='outlined' + /> + </div> + <div> <TextField required @@ -105,7 +134,7 @@ export default function CustomEditor() { </DialogActions> </Dialog> - {showTable && <TableData data={customQueryData} />} + {showTable && <TableData data={customQueryData} title={customQueryJSON.title} />} </React.Fragment> ) diff --git a/src/components/Dashboard/CustomQueryEditor/savedQueries/customQueries.json b/src/components/Dashboard/CustomQueryEditor/savedQueries/customQueries.json new file mode 100644 index 00000000..fa70fb4b --- /dev/null +++ b/src/components/Dashboard/CustomQueryEditor/savedQueries/customQueries.json @@ -0,0 +1,8 @@ +{"savedQueries" : [ + + { + "title" : "customBooks", + "source": "http://localhost:8080/example/wish-list", + "query": "PREFIX schema: <http://schema.org/> \n\nSELECT * WHERE {\n ?list schema:name ?listTitle;\n schema:itemListElement [\n schema:name ?bookTitle;\n schema:creator [\n schema:name ?authorName\n ]\n ].\n}" + } +]} \ No newline at end of file diff --git a/src/components/Dashboard/CustomQueryEditor/tableData.jsx b/src/components/Dashboard/CustomQueryEditor/tableData.jsx index 91d70636..f912da99 100644 --- a/src/components/Dashboard/CustomQueryEditor/tableData.jsx +++ b/src/components/Dashboard/CustomQueryEditor/tableData.jsx @@ -10,6 +10,9 @@ import Typography from '@mui/material/Typography'; export default function TableData(data) { + + //console.log(title) + const keys = [] if (data.data === null || data.data === undefined || data.data.length === 0) { return ( @@ -21,6 +24,9 @@ export default function TableData(data) { Object.keys(data.data[0]).forEach((k) => { keys.push(k) }) return ( <TableContainer sx={{ marginBottom: '20px', marginTop: '10px' }} component={Paper}> + {/* <Typography variant="h6" component="div" style={{ padding: '16px' }}> + {title} + </Typography> */} <Table sx={{ minWidth: 650 }} aria-label="simple table"> <TableHead> <TableRow> From 32c088de8f0d696bafb216ab8455622ecab627ac Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Mon, 3 Jun 2024 10:17:08 +0200 Subject: [PATCH 07/47] adding custom query shows it as a loose query in the menu --- src/App.jsx | 20 ++- .../CustomQueryEditor/customEditor.jsx | 25 ++- src/components/Dashboard/Dashboard.jsx | 2 +- .../SelectionMenu/SelectionMenu.jsx | 170 ++++++++++-------- 4 files changed, 140 insertions(+), 77 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 384d1f48..c41b16c5 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -17,7 +17,6 @@ import TemplatedListResultTable from "./components/ListResultTable/TemplatedList import configManager from "./configManager/configManager.js"; -const config = configManager.getConfig(); const queryClient = new QueryClient({ defaultOptions: { @@ -33,12 +32,29 @@ const queryClient = new QueryClient({ function App() { const session = getDefaultSession(); const [loggedIn, setLoggedIn] = useState(); + const [config, setConfig] = useState(configManager.getConfig()); + const [queryLength, setQueryLength] = useState(config.queries.length) useEffect(() => { const root = document.documentElement; root.style.setProperty("--text-color", config.textColor); }, []); + useEffect(() => { + const handleConfigChange = (newConfig) => { + setConfig(newConfig); + setQueryLength(newConfig.queries.length) + }; + + // Listen for config changes + configManager.on('configChanged', handleConfigChange); + + // Clean up the event listener on component unmount + return () => { + configManager.off('configChanged', handleConfigChange); + }; + }, []); + useEffect(() => { session.onLogin(() => setLoggedIn(true)); session.onLogout(() => setLoggedIn(false)); @@ -68,7 +84,7 @@ function App() { return Dashboard({ title: config.title, text: config.introductionText }) }} > - {config.queries.map((query) => { + {queryLength && config.queries.map((query) => { return ( <Resource key={query.id} diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index a425ca82..3fbe3ae4 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -11,6 +11,8 @@ import { QueryEngine } from "@comunica/query-sparql"; // import QueryResultList from "../../ListResultTable/QueryResultList/QueryResultList" // import ListResultTable from '../../ListResultTable/ListResultTable'; +import configManager from '../../../configManager/configManager'; + import TableData from './tableData'; const myEngine = new QueryEngine(); @@ -43,7 +45,7 @@ export default function CustomEditor() { () => { setShowTable(!showTable); }}> - {showTable ? "Hide Table" : "Show Table"} + {showTable ? "Hide Results" : "Show Results"} </Button> } @@ -68,8 +70,9 @@ export default function CustomEditor() { console.log(jsonData) setCustomQueryJSON(jsonData); - console.log(customQueryJSON) - + addQuery(jsonData.title , jsonData.query) + // console.log(customQueryJSON) + // console.log(Date.now()) const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError) setIsSubmitted(false) @@ -158,4 +161,18 @@ async function executeSPARQLQuery(query, dataSource, setShowError) { return resultingObjects; } - +//Mock query +const addQuery = (title, queryString) => { + configManager.addQuery({ + id: Date.now().toString(), + queryString: queryString , + queryLocation: "components.rq", + name: title, + description: "Query components", + comunicaContext: { + sources: [ + "http://localhost:8080/example/components" + ] + } + }); +}; diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index c437025d..781c8de1 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -19,12 +19,12 @@ function Dashboard(props) { return ( <div> - <CustomEditor/> <Card> <Title title={title} /> <CardContent>{text}</CardContent> </Card> + <CustomEditor/> </div> ); diff --git a/src/components/InteractionLayout/SelectionMenu/SelectionMenu.jsx b/src/components/InteractionLayout/SelectionMenu/SelectionMenu.jsx index 6ff47e03..f146477e 100644 --- a/src/components/InteractionLayout/SelectionMenu/SelectionMenu.jsx +++ b/src/components/InteractionLayout/SelectionMenu/SelectionMenu.jsx @@ -1,4 +1,4 @@ -import React, { Component, useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { useResourceDefinitions } from "ra-core"; import { DashboardMenuItem } from "ra-ui-materialui"; import { Menu } from "react-admin"; @@ -10,55 +10,109 @@ import ListItemText from '@mui/material/ListItemText'; import Collapse from '@mui/material/Collapse'; import ExpandLess from '@mui/icons-material/ExpandLess'; import ExpandMore from '@mui/icons-material/ExpandMore'; -import IconProvider from "../../../IconProvider/IconProvider"; import ListAltIcon from '@mui/icons-material/ListAlt'; - +import IconProvider from "../../../IconProvider/IconProvider"; import configManager from '../../../configManager/configManager'; -const config = configManager.getConfig(); - -/** - * A custom menu as defined in React Admin for selecting the query the user whishes to execute. - * @returns {Component} the selection menu component - */ -function SelectionMenu() { +const SelectionMenu = () => { const resources = useResourceDefinitions(); - const queryGroups = config.queryGroups || []; + const [config, setConfig] = useState(configManager.getConfig()); + const [nrOfQueries, setNrOfQueries] = useState(config.queries.length); + const [displayMenu, setDisplayMenu] = useState({ + queryGroups: config.queryGroups || [], + looseQueries: [] + }); + useEffect(() => { + const handleConfigChange = (newConfig) => { + setConfig(newConfig); + + if(newConfig.queries.length > nrOfQueries){ + addCustomQuery(config.queries.at(-1)) + setNrOfQueries(newConfig.queries.length); + } + }; + + // Listen for config changes + configManager.on('configChanged', handleConfigChange); - // adding a list to the group that will contain all the queries for said group - queryGroups.forEach(group => group.queries = []) + // Clean up the event listener on component unmount + return () => { + configManager.off('configChanged', handleConfigChange); + }; + }, []); - // fill in the groups, and put the ones without a group inside the looseQueries list - const looseQueries = setUpQueryGroups(queryGroups, resources); + useEffect(() => { + setUpQueryGroups(displayMenu, setDisplayMenu, resources); + }, [resources]); + + const setUpQueryGroups = (displayMenu, setDisplayMenu, resources) => { + const looseQueries = []; + const updatedQueryGroups = displayMenu.queryGroups.map(group => ({ + ...group, + queries: [] + })); + + Object.keys(resources).forEach((id) => { + try { + const queryGroupId = resources[id].options.queryGroupId; + if (queryGroupId === undefined) { + looseQueries.push(id); + } else { + const queryGroup = updatedQueryGroups.find(group => group.id === queryGroupId); + if (queryGroup) { + queryGroup.queries.push(id); + } else { + looseQueries.push(id); + } + } + } catch (error) { + throw new Error(`Error adding queries to a group: ${error.message}`); + } + }); + + setDisplayMenu(prevState => ({ + ...prevState, + queryGroups: updatedQueryGroups, + looseQueries + })); + }; + + const addCustomQuery = (newQuery) => { + setDisplayMenu(prevState => { + return({ + ...prevState, + looseQueries: [...prevState.looseQueries, newQuery] + })}); + }; return ( <ThemeProvider theme={menuItemTheme}> <div style={{ height: '100%', overflowY: 'auto', backgroundColor: 'white' }}> <Menu> + {nrOfQueries} <List> <DashboardMenuItem /> - - {looseQueries.map(id => ( + {displayMenu.looseQueries.map(id => ( <Tooltip key={id} placement="right" title={ <TooltipContent - title={resources[id].options.label} - description={resources[id].options.descr} /> + title={resources[id] ? resources[id].options.label : 'tabonbonbon'} + description={resources[id] ? resources[id].options.descr : ' no desc'} /> } > - <div > + <div> <Menu.ResourceItem name={id} /> </div> </Tooltip> ))} </List> - {queryGroups.map((group) => { - const [open, setOpen] = useState(false) + {displayMenu.queryGroups.map((group) => { + const [open, setOpen] = useState(false); return ( - <List key={group.id} disablePadding > - <ListItemButton onClick={() => { setOpen(!open) }}> + <List key={group.id} disablePadding> + <ListItemButton onClick={() => setOpen(!open)}> <ListItemIcon> {getIconComponent(group.icon)} </ListItemIcon> @@ -67,7 +121,7 @@ function SelectionMenu() { </ListItemButton> <Collapse in={open} timeout="auto" unmountOnExit> <List component="div" disablePadding> - {group.queries.map((id) => ( + {group.queries && group.queries.map((id) => ( <Tooltip key={id} placement="right" @@ -77,7 +131,7 @@ function SelectionMenu() { description={resources[id].options.descr} /> } > - <ListItemText sx={{ overflow: 'hidden', ml: 1.5 }} > + <ListItemText sx={{ overflow: 'hidden', ml: 1.5 }}> <Menu.ResourceItem name={id} /> </ListItemText> </Tooltip> @@ -85,13 +139,13 @@ function SelectionMenu() { </List> </Collapse> </List> - ) + ); })} </Menu> </div> </ThemeProvider> ); -} +}; const menuItemTheme = createTheme({ components: { @@ -127,50 +181,26 @@ const getIconComponent = (iconKey) => { }; const TooltipContent = ({ title, description }) => ( - <React.Fragment> - <Box + <Box + sx={{ + width: 'fit-content', + backgroundColor: '#6d6d6d', + paddingX: 1, + marginX: -1, + }} + > + <Typography variant="h6" component="div"> + {title} + </Typography> + <Typography variant="body2" component="div" sx={{ - width: 'fit-content', - backgroundColor: '#6d6d6d', - paddingX: 1, - marginX: -1, + fontStyle: 'italic', + marginTop: 1, }} > - <Typography variant="h6" component="div"> - {title} - </Typography> - - <Typography variant="body2" component="div" - sx={{ - fontStyle: 'italic', - marginTop: 1, - }} - > - {description} - </Typography> - </Box> - </React.Fragment> -) - -const setUpQueryGroups = (queryGroups, resources) => { - const looseQueries = []; - Object.keys(resources).forEach((id) => { - try { - if (resources[id].options.queryGroupId === undefined) { - looseQueries.push(id) - } else { - const queryGroup = queryGroups.find(group => group.id === resources[id].options.queryGroupId); - if (queryGroup) { - queryGroup.queries.push(id); - } else { - looseQueries.push(id); - } - } - } catch (error) { - throw new Error(`Error adding queries to a group: ${error.message}`); - } - }) - return looseQueries; -} + {description} + </Typography> + </Box> +); export default SelectionMenu; From 4dc09ab6dc8b5d30447566101787c7b39300ad35 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Mon, 3 Jun 2024 17:01:51 +0200 Subject: [PATCH 08/47] Adding custom query now works with a custom group --- src/IconProvider/IconProvider.js | 4 +- .../CustomQueryEditor/customEditor.jsx | 28 ++- .../SelectionMenu/SelectionMenu.jsx | 190 +++++++----------- src/configManager/configManager.js | 12 ++ 4 files changed, 101 insertions(+), 133 deletions(-) diff --git a/src/IconProvider/IconProvider.js b/src/IconProvider/IconProvider.js index b20156c9..b13f6ea7 100644 --- a/src/IconProvider/IconProvider.js +++ b/src/IconProvider/IconProvider.js @@ -7,6 +7,7 @@ import ListAltIcon from '@mui/icons-material/ListAlt'; import FactoryIcon from '@mui/icons-material/Factory'; import BugReportIcon from '@mui/icons-material/BugReport'; import ConstructionIcon from '@mui/icons-material/Construction'; +import EditNoteIcon from '@mui/icons-material/EditNote'; export default { BrushIcon, @@ -17,5 +18,6 @@ export default { ListAltIcon, FactoryIcon, BugReportIcon, - ConstructionIcon + ConstructionIcon, + EditNoteIcon }; diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index 3fbe3ae4..31c65a8e 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -49,13 +49,6 @@ export default function CustomEditor() { </Button> } -{/* <Button variant="contained" onClick={ - () => { console.log(customQueryJSON.title) }} - sx={{ margin: '10px' }}> - logging - - </Button> */} - <Dialog open={openEditor} onClose={closeEditor} @@ -67,14 +60,16 @@ export default function CustomEditor() { event.preventDefault(); const formData = new FormData(event.currentTarget); const jsonData = Object.fromEntries(formData.entries()); - console.log(jsonData) + + makeCustomGroup('cstm' , 'Custom queries' , 'EditNoteIcon') + + //console.log(jsonData) setCustomQueryJSON(jsonData); addQuery(jsonData.title , jsonData.query) - // console.log(customQueryJSON) - // console.log(Date.now()) - + const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError) + setIsSubmitted(false) setShowTable(false) setCustomQueryData(data); @@ -159,12 +154,23 @@ async function executeSPARQLQuery(query, dataSource, setShowError) { throw new Error(`Error executing SPARQL query: ${error.message}`); } return resultingObjects; +}; + +const makeCustomGroup = (id, name, icon) => { + const config = configManager.getConfig() + const groupExists = config.queryGroups.find(group => group.id === id); + + if(groupExists === undefined){ + configManager.addNewQueryGroup(id, name, icon) + } + } //Mock query const addQuery = (title, queryString) => { configManager.addQuery({ id: Date.now().toString(), + queryGroupId: "cstm", queryString: queryString , queryLocation: "components.rq", name: title, diff --git a/src/components/InteractionLayout/SelectionMenu/SelectionMenu.jsx b/src/components/InteractionLayout/SelectionMenu/SelectionMenu.jsx index f146477e..520af054 100644 --- a/src/components/InteractionLayout/SelectionMenu/SelectionMenu.jsx +++ b/src/components/InteractionLayout/SelectionMenu/SelectionMenu.jsx @@ -14,93 +14,46 @@ import ListAltIcon from '@mui/icons-material/ListAlt'; import IconProvider from "../../../IconProvider/IconProvider"; import configManager from '../../../configManager/configManager'; + const SelectionMenu = () => { const resources = useResourceDefinitions(); const [config, setConfig] = useState(configManager.getConfig()); - const [nrOfQueries, setNrOfQueries] = useState(config.queries.length); - const [displayMenu, setDisplayMenu] = useState({ - queryGroups: config.queryGroups || [], - looseQueries: [] - }); + const [openGroups, setOpenGroups] = useState({}); + + let queryGroups = config.queryGroups || []; + queryGroups.forEach(group => group.queries = []); + const looseQueries = setUpQueryGroups(queryGroups, resources); + useEffect(() => { - const handleConfigChange = (newConfig) => { + const handleGroupChange = (newConfig) => { setConfig(newConfig); - - if(newConfig.queries.length > nrOfQueries){ - addCustomQuery(config.queries.at(-1)) - setNrOfQueries(newConfig.queries.length); - } }; - // Listen for config changes - configManager.on('configChanged', handleConfigChange); + configManager.on('configChanged', handleGroupChange); - // Clean up the event listener on component unmount return () => { - configManager.off('configChanged', handleConfigChange); + configManager.off('configChanged', handleGroupChange); }; }, []); - useEffect(() => { - setUpQueryGroups(displayMenu, setDisplayMenu, resources); - }, [resources]); - - const setUpQueryGroups = (displayMenu, setDisplayMenu, resources) => { - const looseQueries = []; - const updatedQueryGroups = displayMenu.queryGroups.map(group => ({ - ...group, - queries: [] + const handleGroupToggle = (groupId) => { + setOpenGroups(prevOpenGroups => ({ + ...prevOpenGroups, + [groupId]: !prevOpenGroups[groupId], })); - - Object.keys(resources).forEach((id) => { - try { - const queryGroupId = resources[id].options.queryGroupId; - if (queryGroupId === undefined) { - looseQueries.push(id); - } else { - const queryGroup = updatedQueryGroups.find(group => group.id === queryGroupId); - if (queryGroup) { - queryGroup.queries.push(id); - } else { - looseQueries.push(id); - } - } - } catch (error) { - throw new Error(`Error adding queries to a group: ${error.message}`); - } - }); - - setDisplayMenu(prevState => ({ - ...prevState, - queryGroups: updatedQueryGroups, - looseQueries - })); - }; - - const addCustomQuery = (newQuery) => { - setDisplayMenu(prevState => { - return({ - ...prevState, - looseQueries: [...prevState.looseQueries, newQuery] - })}); }; return ( <ThemeProvider theme={menuItemTheme}> <div style={{ height: '100%', overflowY: 'auto', backgroundColor: 'white' }}> <Menu> - {nrOfQueries} <List> <DashboardMenuItem /> - {displayMenu.looseQueries.map(id => ( + {looseQueries.map(id => ( <Tooltip key={id} placement="right" - title={ - <TooltipContent - title={resources[id] ? resources[id].options.label : 'tabonbonbon'} - description={resources[id] ? resources[id].options.descr : ' no desc'} /> - } + title={<TooltipContent title={resources[id].options.label} description={resources[id].options.descr} />} > <div> <Menu.ResourceItem name={id} /> @@ -108,39 +61,32 @@ const SelectionMenu = () => { </Tooltip> ))} </List> - {displayMenu.queryGroups.map((group) => { - const [open, setOpen] = useState(false); - return ( - <List key={group.id} disablePadding> - <ListItemButton onClick={() => setOpen(!open)}> - <ListItemIcon> - {getIconComponent(group.icon)} - </ListItemIcon> - <ListItemText primary={group.name} /> - {open ? <ExpandLess /> : <ExpandMore />} - </ListItemButton> - <Collapse in={open} timeout="auto" unmountOnExit> - <List component="div" disablePadding> - {group.queries && group.queries.map((id) => ( - <Tooltip - key={id} - placement="right" - title={ - <TooltipContent - title={resources[id].options.label} - description={resources[id].options.descr} /> - } - > - <ListItemText sx={{ overflow: 'hidden', ml: 1.5 }}> - <Menu.ResourceItem name={id} /> - </ListItemText> - </Tooltip> - ))} - </List> - </Collapse> - </List> - ); - })} + {queryGroups.map((group) => ( + <List key={group.id} disablePadding> + <ListItemButton onClick={() => handleGroupToggle(group.id)}> + <ListItemIcon> + {getIconComponent(group.icon)} + </ListItemIcon> + <ListItemText primary={group.name} /> + {openGroups[group.id] ? <ExpandLess /> : <ExpandMore />} + </ListItemButton> + <Collapse in={openGroups[group.id]} timeout="auto" unmountOnExit> + <List component="div" disablePadding> + {group.queries.map((id) => ( + <Tooltip + key={id} + placement="right" + title={<TooltipContent title={resources[id].options.label} description={resources[id].options.descr} />} + > + <ListItemText sx={{ overflow: 'hidden', ml: 1.5 }}> + <Menu.ResourceItem name={id} /> + </ListItemText> + </Tooltip> + ))} + </List> + </Collapse> + </List> + ))} </Menu> </div> </ThemeProvider> @@ -165,42 +111,44 @@ const menuItemTheme = createTheme({ display: "block", whiteSpace: "nowrap", textOverflow: "ellipsis", - } + }, }, }, - } + }, }, }); const getIconComponent = (iconKey) => { const IconComponent = IconProvider[iconKey]; - if (IconComponent) { - return <IconComponent />; - } - return <ListAltIcon />; + return IconComponent ? <IconComponent /> : <ListAltIcon />; }; const TooltipContent = ({ title, description }) => ( - <Box - sx={{ - width: 'fit-content', - backgroundColor: '#6d6d6d', - paddingX: 1, - marginX: -1, - }} - > - <Typography variant="h6" component="div"> - {title} - </Typography> - <Typography variant="body2" component="div" - sx={{ - fontStyle: 'italic', - marginTop: 1, - }} - > - {description} - </Typography> + <Box sx={{ width: 'fit-content', backgroundColor: '#6d6d6d', paddingX: 1, marginX: -1 }}> + <Typography variant="h6" component="div">{title}</Typography> + <Typography variant="body2" component="div" sx={{ fontStyle: 'italic', marginTop: 1 }}>{description}</Typography> </Box> ); +const setUpQueryGroups = (queryGroups, resources) => { + const looseQueries = []; + Object.keys(resources).forEach((id) => { + try { + if (resources[id].options.queryGroupId === undefined) { + looseQueries.push(id); + } else { + const queryGroup = queryGroups.find(group => group.id === resources[id].options.queryGroupId); + if (queryGroup) { + queryGroup.queries.push(id); + } else { + looseQueries.push(id); + } + } + } catch (error) { + throw new Error(`Error adding queries to a group: ${error.message}`); + } + }); + return looseQueries; +}; + export default SelectionMenu; diff --git a/src/configManager/configManager.js b/src/configManager/configManager.js index 98c622a3..5e741f59 100644 --- a/src/configManager/configManager.js +++ b/src/configManager/configManager.js @@ -47,6 +47,18 @@ class ConfigManager extends EventEmitter { this.emit('configChanged', this.config); } + addNewQueryGroup(id, name, icon=null) { + const newGroup = { id, name }; + if (icon) { + newGroup.icon = icon; + } + + + this.config.queryGroups = [...this.config.queryGroups, newGroup]; + + this.emit('configChanged', this.config); + } + /** * Adds as query element to the config.queries array in the configuration * From 7818a729c61cd6cc0de8b064125d82c9c48ce01b Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Mon, 3 Jun 2024 17:34:09 +0200 Subject: [PATCH 09/47] extracted group adding logic to the manager (more robust for extension) --- .../CustomQueryEditor/customEditor.jsx | 38 ++++++++----------- src/configManager/configManager.js | 16 ++++---- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index 31c65a8e..71421378 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import Dialog from '@mui/material/Dialog'; import DialogTitle from '@mui/material/DialogTitle'; import Button from '@mui/material/Button'; @@ -20,17 +20,19 @@ const myEngine = new QueryEngine(); export default function CustomEditor() { const [openEditor, setOpenEditor] = useState(false); - const [customQueryData, setCustomQueryData] = useState([]) - const [showError, setShowError] = useState(false) - const [isSubmitted, setIsSubmitted] = useState(false) - const [customQueryJSON, setCustomQueryJSON] = useState({}) + const [customQueryData, setCustomQueryData] = useState([]); + const [showError, setShowError] = useState(false); + const [isSubmitted, setIsSubmitted] = useState(false); + const [customQueryJSON, setCustomQueryJSON] = useState({}); - const [showTable, setShowTable] = useState(false) + const [showTable, setShowTable] = useState(false); const closeEditor = () => { setOpenEditor(false); setShowError(false); } + + //investigate state re-render? maybe send to dashboard instead of own component return ( <React.Fragment> @@ -39,7 +41,7 @@ export default function CustomEditor() { sx={{ margin: '10px' }}> Custom query - </Button> + </Button> {isSubmitted && <Button variant="outlined" color={showTable ? "error" : "primary"} onClick={ () => { @@ -61,16 +63,17 @@ export default function CustomEditor() { const formData = new FormData(event.currentTarget); const jsonData = Object.fromEntries(formData.entries()); - makeCustomGroup('cstm' , 'Custom queries' , 'EditNoteIcon') + // TODO: NEED A CHECK HERE TO SEE IF WE MAY SUBMIT (correct query) + const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError) + configManager.addNewQueryGroup('cstm' , 'Custom queries' , 'EditNoteIcon'); + addQuery(jsonData.title , jsonData.query) + //console.log(jsonData) setCustomQueryJSON(jsonData); - - addQuery(jsonData.title , jsonData.query) - - const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError) - setIsSubmitted(false) + + setIsSubmitted(false) setShowTable(false) setCustomQueryData(data); closeEditor(); @@ -156,15 +159,6 @@ async function executeSPARQLQuery(query, dataSource, setShowError) { return resultingObjects; }; -const makeCustomGroup = (id, name, icon) => { - const config = configManager.getConfig() - const groupExists = config.queryGroups.find(group => group.id === id); - - if(groupExists === undefined){ - configManager.addNewQueryGroup(id, name, icon) - } - -} //Mock query const addQuery = (title, queryString) => { diff --git a/src/configManager/configManager.js b/src/configManager/configManager.js index 5e741f59..206eea5f 100644 --- a/src/configManager/configManager.js +++ b/src/configManager/configManager.js @@ -47,16 +47,18 @@ class ConfigManager extends EventEmitter { this.emit('configChanged', this.config); } - addNewQueryGroup(id, name, icon=null) { - const newGroup = { id, name }; + addNewQueryGroup(id, name, icon = null) { + + const groupExists = this.config.queryGroups.find(group => group.id === id); + + if (groupExists === undefined) { + const newGroup = { id, name }; if (icon) { newGroup.icon = icon; } - - - this.config.queryGroups = [...this.config.queryGroups, newGroup]; - - this.emit('configChanged', this.config); + this.config.queryGroups = [...this.config.queryGroups, newGroup]; + this.emit('configChanged', this.config); + } } /** From 9e6671fc0de1eeb616cf3dd74b5fe441326294f3 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Tue, 4 Jun 2024 10:50:05 +0200 Subject: [PATCH 10/47] added description and multiple source option --- src/IconProvider/IconProvider.js | 4 +- .../CustomQueryEditor/customEditor.jsx | 63 ++++++++++++------- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/IconProvider/IconProvider.js b/src/IconProvider/IconProvider.js index b13f6ea7..0c8686ee 100644 --- a/src/IconProvider/IconProvider.js +++ b/src/IconProvider/IconProvider.js @@ -8,6 +8,7 @@ import FactoryIcon from '@mui/icons-material/Factory'; import BugReportIcon from '@mui/icons-material/BugReport'; import ConstructionIcon from '@mui/icons-material/Construction'; import EditNoteIcon from '@mui/icons-material/EditNote'; +import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome'; export default { BrushIcon, @@ -19,5 +20,6 @@ export default { FactoryIcon, BugReportIcon, ConstructionIcon, - EditNoteIcon + EditNoteIcon, + AutoAwesomeIcon }; diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index 71421378..833e96ab 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -31,7 +31,7 @@ export default function CustomEditor() { setOpenEditor(false); setShowError(false); } - + //investigate state re-render? maybe send to dashboard instead of own component return ( @@ -41,7 +41,7 @@ export default function CustomEditor() { sx={{ margin: '10px' }}> Custom query - </Button> + </Button> {isSubmitted && <Button variant="outlined" color={showTable ? "error" : "primary"} onClick={ () => { @@ -63,19 +63,20 @@ export default function CustomEditor() { const formData = new FormData(event.currentTarget); const jsonData = Object.fromEntries(formData.entries()); + console.log(jsonData) + // TODO: NEED A CHECK HERE TO SEE IF WE MAY SUBMIT (correct query) - const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError) + // const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError) + + configManager.addNewQueryGroup('cstm', 'Custom queries', 'EditNoteIcon'); + addQuery(jsonData) - configManager.addNewQueryGroup('cstm' , 'Custom queries' , 'EditNoteIcon'); - addQuery(jsonData.title , jsonData.query) - - //console.log(jsonData) setCustomQueryJSON(jsonData); - - - setIsSubmitted(false) + + + setIsSubmitted(false) setShowTable(false) - setCustomQueryData(data); + //setCustomQueryData(data); closeEditor(); setIsSubmitted(true) }, @@ -98,6 +99,19 @@ export default function CustomEditor() { helperText="Give this custom query a name" variant='outlined' /> + + <TextField + required + id="outlined-multiline-flexible" + label="Description" + name='description' + multiline + fullWidth + minRows={2} + variant='outlined' + helperText="Give a description for the query" + placeholder={`This is a custom query.`} + /> </div> <div> @@ -107,8 +121,8 @@ export default function CustomEditor() { name='source' id="outlined-required" label="Data Source " - placeholder="http://examplesource.org" - helperText="Give the source Url for the query" + placeholder="http://examplesource.org ; source2" + helperText="Give the source Url('s) for the query. You can add more than one source separated with ' ; '" variant='outlined' /> </div> @@ -141,12 +155,12 @@ export default function CustomEditor() { ) } - +// Temporary bindingstream async function executeSPARQLQuery(query, dataSource, setShowError) { const resultingObjects = []; try { const bindingsStream = await myEngine.queryBindings(query, { - sources: [dataSource] + sources: dataSource.split(';').map(source => source.trim()) }); bindingsStream.on('data', (binding) => { @@ -161,18 +175,19 @@ async function executeSPARQLQuery(query, dataSource, setShowError) { //Mock query -const addQuery = (title, queryString) => { +const addQuery = (formData) => { configManager.addQuery({ id: Date.now().toString(), queryGroupId: "cstm", - queryString: queryString , - queryLocation: "components.rq", - name: title, - description: "Query components", + icon: "AutoAwesomeIcon", + queryString: formData.queryString, + name: formData.title, + description: formData.description, comunicaContext: { - sources: [ - "http://localhost:8080/example/components" - ] - } + sources: formData.source.split(';').map(source => source.trim()) + }, + + // Location for testing purposes, delete after it works with the querystring + queryLocation: "components.rq" }); }; From d6cb6641533ef962ae8f200b8100af6507d0426e Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Tue, 4 Jun 2024 16:40:39 +0200 Subject: [PATCH 11/47] custom query editor operational --- .../CustomQueryEditor/customEditor.jsx | 16 +- .../customQueryEditButton.jsx | 156 ++++++++++++++++++ .../savedQueries/customQueries.json | 8 - .../Dashboard/CustomQueryEditor/tableData.jsx | 2 - .../QueryResultList/QueryResultList.jsx | 7 +- src/configManager/configManager.js | 42 ++++- src/dataProvider/SparqlDataProvider.js | 7 +- 7 files changed, 210 insertions(+), 28 deletions(-) create mode 100644 src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx delete mode 100644 src/components/Dashboard/CustomQueryEditor/savedQueries/customQueries.json diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index 833e96ab..fc6d4bd0 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -8,8 +8,8 @@ import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; import { QueryEngine } from "@comunica/query-sparql"; -// import QueryResultList from "../../ListResultTable/QueryResultList/QueryResultList" -// import ListResultTable from '../../ListResultTable/ListResultTable'; +//import { useLocation, useNavigate } from 'react-router-dom'; + import configManager from '../../../configManager/configManager'; @@ -20,10 +20,8 @@ const myEngine = new QueryEngine(); export default function CustomEditor() { const [openEditor, setOpenEditor] = useState(false); - const [customQueryData, setCustomQueryData] = useState([]); const [showError, setShowError] = useState(false); const [isSubmitted, setIsSubmitted] = useState(false); - const [customQueryJSON, setCustomQueryJSON] = useState({}); const [showTable, setShowTable] = useState(false); @@ -63,20 +61,16 @@ export default function CustomEditor() { const formData = new FormData(event.currentTarget); const jsonData = Object.fromEntries(formData.entries()); - console.log(jsonData) - // TODO: NEED A CHECK HERE TO SEE IF WE MAY SUBMIT (correct query) // const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError) configManager.addNewQueryGroup('cstm', 'Custom queries', 'EditNoteIcon'); addQuery(jsonData) - setCustomQueryJSON(jsonData); setIsSubmitted(false) setShowTable(false) - //setCustomQueryData(data); closeEditor(); setIsSubmitted(true) }, @@ -149,7 +143,7 @@ export default function CustomEditor() { </DialogActions> </Dialog> - {showTable && <TableData data={customQueryData} title={customQueryJSON.title} />} + {/* {showTable && <TableData data={customQueryData} title={customQueryJSON.title} />} */} </React.Fragment> ) @@ -180,7 +174,7 @@ const addQuery = (formData) => { id: Date.now().toString(), queryGroupId: "cstm", icon: "AutoAwesomeIcon", - queryString: formData.queryString, + queryString: formData.query, name: formData.title, description: formData.description, comunicaContext: { @@ -188,6 +182,6 @@ const addQuery = (formData) => { }, // Location for testing purposes, delete after it works with the querystring - queryLocation: "components.rq" + // queryLocation: "components.rq" }); }; diff --git a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx new file mode 100644 index 00000000..1b8fea4c --- /dev/null +++ b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx @@ -0,0 +1,156 @@ +import React, { useState, useEffect } from 'react'; +import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import { QueryEngine } from "@comunica/query-sparql"; +import { useLocation, useNavigate } from 'react-router-dom'; + +import configManager from '../../../configManager/configManager'; + + +const myEngine = new QueryEngine(); + +export default function CustomQueryEditButton({ queryID }) { + + + // GET QUERY BY ID -> not yet implemented + const customQuery = configManager.getQueryById(queryID); + const sourcesString = customQuery.comunicaContext.sources.join(' ; '); + + const [openEditor, setOpenEditor] = useState(false); + + const [showError, setShowError] = useState(false); + const navigate = useNavigate(); + + const closeEditor = () => { + setOpenEditor(false); + setShowError(false); + } + + // Need a solutin to reload the new query? + + return ( + <React.Fragment> + <Button variant="contained" onClick={ + () => { setOpenEditor(true) }} + sx={{ margin: '10px' }}> + Edit Query + + </Button> + + + <Dialog + open={openEditor} + onClose={closeEditor} + maxWidth={'md'} + fullWidth + PaperProps={{ + component: 'form', + onSubmit: async (event) => { + event.preventDefault(); + const formData = new FormData(event.currentTarget); + const jsonData = Object.fromEntries(formData.entries()); + + // TODO: NEED A CHECK HERE TO SEE IF WE MAY SUBMIT (correct query) + // const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError) + + updateQuery(jsonData, customQuery) + closeEditor(); + navigate('/#') + }, + }} + > + <DialogTitle> Edit: {customQuery.name}</DialogTitle> + + <DialogContent> + <DialogContentText sx={{ mb: '15px' }}> + {customQuery.description} + </DialogContentText> + + + {/* <div> + <TextField + required + disabled + fullWidth + name='title' + id="outlined-required" + label="Query title" + helperText="Edit name" + variant='outlined' + defaultValue={customQuery.name} + /> + + <TextField + required + disabled + id="outlined-multiline-flexible" + label="Description" + name='description' + multiline + fullWidth + minRows={2} + variant='outlined' + helperText="Edit description" + defaultValue={customQuery.description} + /> + </div> */} + + <div> + <TextField + required + fullWidth + name='source' + id="outlined-required" + label="Data Source " + defaultValue={sourcesString} + helperText="You can add more than one source separated with ' ; '" + variant='outlined' + /> + </div> + + <div> + <TextField + required + id="outlined-multiline-flexible" + label="Custom Query" + name='query' + multiline + fullWidth + minRows={5} + variant='outlined' + defaultValue={customQuery.queryString} + /> + </div> + <DialogContentText > + When confirming you will be redirected to the dashboard. + </DialogContentText> + </DialogContent> + + <DialogActions> + <Button onClick={closeEditor}>Cancel</Button> + <Button variant="contained" type="submit">Confirm changes</Button> + </DialogActions> + </Dialog> + + </React.Fragment> + ) +} + + +//Mock query +const updateQuery = (formData, customQuery) => { + const { query, source } = formData; + configManager.updateQuery({ + ...customQuery, + queryString: query, + comunicaContext: { + sources: source.split(';').map(src => src.trim()) + }, + // queryLocation: "components.rq" // Location for testing purposes, delete after it works with the querystring + }); +}; diff --git a/src/components/Dashboard/CustomQueryEditor/savedQueries/customQueries.json b/src/components/Dashboard/CustomQueryEditor/savedQueries/customQueries.json deleted file mode 100644 index fa70fb4b..00000000 --- a/src/components/Dashboard/CustomQueryEditor/savedQueries/customQueries.json +++ /dev/null @@ -1,8 +0,0 @@ -{"savedQueries" : [ - - { - "title" : "customBooks", - "source": "http://localhost:8080/example/wish-list", - "query": "PREFIX schema: <http://schema.org/> \n\nSELECT * WHERE {\n ?list schema:name ?listTitle;\n schema:itemListElement [\n schema:name ?bookTitle;\n schema:creator [\n schema:name ?authorName\n ]\n ].\n}" - } -]} \ No newline at end of file diff --git a/src/components/Dashboard/CustomQueryEditor/tableData.jsx b/src/components/Dashboard/CustomQueryEditor/tableData.jsx index f912da99..8c85d83e 100644 --- a/src/components/Dashboard/CustomQueryEditor/tableData.jsx +++ b/src/components/Dashboard/CustomQueryEditor/tableData.jsx @@ -11,8 +11,6 @@ import Typography from '@mui/material/Typography'; export default function TableData(data) { - //console.log(title) - const keys = [] if (data.data === null || data.data === undefined || data.data.length === 0) { return ( diff --git a/src/components/ListResultTable/QueryResultList/QueryResultList.jsx b/src/components/ListResultTable/QueryResultList/QueryResultList.jsx index f1d60646..4cfa5bc3 100644 --- a/src/components/ListResultTable/QueryResultList/QueryResultList.jsx +++ b/src/components/ListResultTable/QueryResultList/QueryResultList.jsx @@ -7,6 +7,7 @@ import TableHeader from "./TableHeader/TableHeader"; import Button from '@mui/material/Button'; import SearchOffIcon from '@mui/icons-material/SearchOff'; import { SvgIcon, Box, Typography } from "@mui/material"; +import CustomQueryEditButton from "../../Dashboard/CustomQueryEditor/customQueryEditButton"; import configManager from "../../../configManager/configManager"; const config = configManager.getConfig(); @@ -16,7 +17,9 @@ const config = configManager.getConfig(); * @returns {Component} custom ListViewer as defined by react-admin containing the results of the query with each variable its generic field. */ function QueryResultList(props) { - const QueryTitle = useResourceDefinition().options.label; + const resource = useResourceDefinition(); + + const QueryTitle = resource.options.label; const { data } = useListContext(props); const {changeVariables, submitted} = props; const [values, setValues] = useState(undefined); @@ -31,7 +34,7 @@ function QueryResultList(props) { return ( <div style={{ paddingLeft: '20px' , paddingRight: '10px' }}> <Title title={config.title} /> - + {resource.options.queryGroupId === 'cstm' && <CustomQueryEditButton queryID={resource.name}/>} {submitted && <Aside changeVariables={changeVariables}/> /* Adding button to make a new query - top left corner */ } <Typography fontSize={"2rem"} mt={2} > {QueryTitle} </Typography> {values ?( diff --git a/src/configManager/configManager.js b/src/configManager/configManager.js index 206eea5f..6b7b0745 100644 --- a/src/configManager/configManager.js +++ b/src/configManager/configManager.js @@ -62,7 +62,7 @@ class ConfigManager extends EventEmitter { } /** - * Adds as query element to the config.queries array in the configuration + * Adds a query element to the config.queries array in the configuration * * @param {object} newQuery - the query element to be added */ @@ -70,7 +70,45 @@ class ConfigManager extends EventEmitter { this.config.queries = [...this.config.queries, newQuery]; this.emit('configChanged', this.config); } -} + + + /** + * Updates a query element to the config.queries array in the configuration + * @param {Object} updatedQuery - the updated query element to replace + */ + updateQuery(updatedQuery) { + let index = this.config.queries.findIndex(query => query.id === updatedQuery.id); + if (index !== -1) { + this.config.queries[index] = updatedQuery; + } + this.emit('configChanged', this.config); + } + + + /** + * Gets the query with the given id in the config.queries array in the configuration + * @param {string} id - id property a query + * @returns {object} the query + */ + getQueryById(id) { + return this.config.queries.find((query) => query.id === id); + } + + /** + * Gets the query text from a query + * @param {object} query - the input query + * @returns {string} the query text + */ + async getQueryText(query) { + + if (query.queryLocation) { + const fetchResult = await fetch(`${this.config.queryFolder}${query.queryLocation}`); + return await fetchResult.text(); + } + return query.queryString; + } + +} const configManager = new ConfigManager(); export default configManager; diff --git a/src/dataProvider/SparqlDataProvider.js b/src/dataProvider/SparqlDataProvider.js index 6bc472ea..b7183218 100644 --- a/src/dataProvider/SparqlDataProvider.js +++ b/src/dataProvider/SparqlDataProvider.js @@ -112,10 +112,11 @@ function findQueryWithId(id) { */ async function fetchQuery(query) { try { - const result = await fetch(`${config.queryFolder}${query.queryLocation}`); const parser = new Parser(); - let rawText = await result.text(); - + // const result = await fetch(`${config.queryFolder}${query.queryLocation}`); + let rawText = await configManager.getQueryText(query) + + if (query.variableValues) { rawText = replaceVariables(rawText, query.variableValues); } From 21e4782e262ca0941fb716e0a68f8fa5cc476466 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Mon, 17 Jun 2024 11:47:05 +0200 Subject: [PATCH 12/47] update --- .../QueryResultList/QueryResultList.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/ListResultTable/QueryResultList/QueryResultList.jsx b/src/components/ListResultTable/QueryResultList/QueryResultList.jsx index c55de0d9..28a25a22 100644 --- a/src/components/ListResultTable/QueryResultList/QueryResultList.jsx +++ b/src/components/ListResultTable/QueryResultList/QueryResultList.jsx @@ -17,11 +17,11 @@ import configManager from "../../../configManager/configManager"; * @returns {Component} custom ListViewer as defined by react-admin containing the results of the query with each variable its generic field. */ function QueryResultList(props) { - const resource = useResourceDefinition(); + const { resource, changeVariables, submitted} = props; + const resourceDef = useResourceDefinition(); - const QueryTitle = resource.options.label; + const queryTitle = resourceDef.options.label; const { data } = useListContext(props); - const { resource, changeVariables, submitted} = props; const [values, setValues] = useState(undefined); useEffect(() => { if (data && data.length > 0) { @@ -37,8 +37,8 @@ function QueryResultList(props) { return ( <div style={{ paddingLeft: '20px' , paddingRight: '10px' }}> <Title title={config.title} /> - {resource.options.queryGroupId === 'cstm' && <CustomQueryEditButton queryID={resource.name}/>} - {submitted && <Aside changeVariables={changeVariables}/> /* Adding button to make a new query - top left corner */ } + {resourceDef.options.queryGroupId === 'cstm' && <CustomQueryEditButton queryID={resourceDef.name}/>} + {submitted && <Aside changeVariables={changeVariables}/> } <Typography fontSize={"2rem"} mt={2} > {queryTitle} </Typography> {values ?( <ListView title=" " actions={<ActionBar />} {...props} > From e4a1a0e6478e6293a4cea0d707d2a632beb6b6b1 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Mon, 17 Jun 2024 15:36:17 +0200 Subject: [PATCH 13/47] formcomponent with searchParams --- src/App.jsx | 7 +- src/IconProvider/IconProvider.js | 6 +- .../CustomQueryEditor/customEditor.jsx | 175 +++++++++--------- src/components/Dashboard/Dashboard.jsx | 5 - .../SelectionMenu/SelectionMenu.jsx | 1 + 5 files changed, 100 insertions(+), 94 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 41013474..81cdbf5b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,5 @@ import "./App.css"; -import { Admin, Resource } from "react-admin"; +import { Admin, Resource, CustomRoutes } from "react-admin"; import SparqlDataProvider from "./dataProvider/SparqlDataProvider"; import { Component, useEffect, useState } from "react"; import { @@ -14,6 +14,8 @@ import Dashboard from "./components/Dashboard/Dashboard"; import InteractionLayout from "./components/InteractionLayout/InteractionLayout"; import TemplatedListResultTable from "./components/ListResultTable/TemplatedListResultTable.jsx"; +import { Route } from "react-router-dom"; +import CustomEditor from "./components/Dashboard/CustomQueryEditor/customEditor.jsx"; import configManager from "./configManager/configManager.js"; @@ -93,6 +95,9 @@ function App() { /> ); })} + <CustomRoutes> + <Route key="customQuery" path="/customQuery" element={<CustomEditor/>}/> + </CustomRoutes> </Admin> ); } diff --git a/src/IconProvider/IconProvider.js b/src/IconProvider/IconProvider.js index 0c8686ee..f2103b00 100644 --- a/src/IconProvider/IconProvider.js +++ b/src/IconProvider/IconProvider.js @@ -9,6 +9,8 @@ import BugReportIcon from '@mui/icons-material/BugReport'; import ConstructionIcon from '@mui/icons-material/Construction'; import EditNoteIcon from '@mui/icons-material/EditNote'; import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome'; +import AddIcon from '@mui/icons-material/Add'; +import DashboardCustomizeIcon from '@mui/icons-material/DashboardCustomize'; export default { BrushIcon, @@ -21,5 +23,7 @@ export default { BugReportIcon, ConstructionIcon, EditNoteIcon, - AutoAwesomeIcon + AutoAwesomeIcon, + AddIcon, + DashboardCustomizeIcon }; diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index fc6d4bd0..aff61710 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -1,110 +1,110 @@ import React, { useState, useEffect } from 'react'; -import Dialog from '@mui/material/Dialog'; -import DialogTitle from '@mui/material/DialogTitle'; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; import Button from '@mui/material/Button'; import TextField from '@mui/material/TextField'; -import DialogActions from '@mui/material/DialogActions'; -import DialogContent from '@mui/material/DialogContent'; -import DialogContentText from '@mui/material/DialogContentText'; + import { QueryEngine } from "@comunica/query-sparql"; +import { CardActions, Typography } from '@mui/material'; +import { useLocation, useNavigate } from 'react-router-dom'; -//import { useLocation, useNavigate } from 'react-router-dom'; import configManager from '../../../configManager/configManager'; - -import TableData from './tableData'; - const myEngine = new QueryEngine(); export default function CustomEditor() { - const [openEditor, setOpenEditor] = useState(false); + const location = useLocation(); + const navigate = useNavigate(); + const [formData, setFormData] = useState({ + title: '', + description: '', + source: '', + query: '' + }); const [showError, setShowError] = useState(false); - const [isSubmitted, setIsSubmitted] = useState(false); - - const [showTable, setShowTable] = useState(false); - const closeEditor = () => { - setOpenEditor(false); - setShowError(false); - } - - //investigate state re-render? maybe send to dashboard instead of own component + //delete tabledata when everything is finished + + useEffect(() => { + const searchParams = new URLSearchParams(location.search); + const title = searchParams.get('title') || ''; + const description = searchParams.get('description') || ''; + const source = searchParams.get('source') || ''; + const query = searchParams.get('query') || ''; + + setFormData({ title, description, source, query }); + }, [location.search]); + + const handleSubmit = async (event) => { + event.preventDefault(); + const formData = new FormData(event.currentTarget); + const jsonData = Object.fromEntries(formData.entries()); + + const searchParams = new URLSearchParams(jsonData); + navigate({ search: searchParams.toString() }); + + // TODO: NEED A CHECK HERE TO SEE IF WE MAY SUBMIT (correct query) + // const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError); + + configManager.addNewQueryGroup('cstm', 'Custom queries', 'EditNoteIcon'); + addQuery(jsonData); + }; + + const handleChange = (event) => { + const { name, value } = event.target; + setFormData((prevFormData) => ({ + ...prevFormData, + [name]: value, + })); + }; return ( <React.Fragment> - <Button variant="contained" onClick={ - () => { setOpenEditor(true) }} - sx={{ margin: '10px' }}> - Custom query - - </Button> - {isSubmitted && - <Button variant="outlined" color={showTable ? "error" : "primary"} onClick={ - () => { - setShowTable(!showTable); - }}> - {showTable ? "Hide Results" : "Show Results"} - </Button> - } - - <Dialog - open={openEditor} - onClose={closeEditor} - maxWidth={'md'} - fullWidth - PaperProps={{ - component: 'form', - onSubmit: async (event) => { - event.preventDefault(); - const formData = new FormData(event.currentTarget); - const jsonData = Object.fromEntries(formData.entries()); - - // TODO: NEED A CHECK HERE TO SEE IF WE MAY SUBMIT (correct query) - // const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError) - - configManager.addNewQueryGroup('cstm', 'Custom queries', 'EditNoteIcon'); - addQuery(jsonData) - - - - setIsSubmitted(false) - setShowTable(false) - closeEditor(); - setIsSubmitted(true) - }, - }} + + <Card + component="form" + onSubmit={handleSubmit} + sx={{ padding: '16px', marginTop: '16px', width: '100%' }} > - <DialogTitle>Custom Query Editor</DialogTitle> - <DialogContent> - <DialogContentText sx={{ color: 'red', mb: '10px' }}> - {showError ? 'Invalid Query. Check the URL and Query Syntax' : ''} - </DialogContentText> + <CardContent> + <Typography variant="h6">Custom Query Editor</Typography> + {showError && ( + <Typography variant="body2" sx={{ color: 'red', mb: '10px' }}> + Invalid Query. Check the URL and Query Syntax + </Typography> + )} <div> <TextField required fullWidth - name='title' + name="title" id="outlined-required" - label="Query title " + label="Query title" placeholder="Custom query name" helperText="Give this custom query a name" - variant='outlined' + variant="outlined" + value={formData.title} + onChange={handleChange} + sx={{ marginBottom: '16px' }} /> <TextField required id="outlined-multiline-flexible" label="Description" - name='description' + name="description" multiline fullWidth minRows={2} - variant='outlined' + variant="outlined" helperText="Give a description for the query" - placeholder={`This is a custom query.`} + placeholder="This is a custom query." + value={formData.description} + onChange={handleChange} + sx={{ marginBottom: '16px' }} /> </div> @@ -112,12 +112,15 @@ export default function CustomEditor() { <TextField required fullWidth - name='source' + name="source" id="outlined-required" - label="Data Source " + label="Data Source" placeholder="http://examplesource.org ; source2" - helperText="Give the source Url('s) for the query. You can add more than one source separated with ' ; '" - variant='outlined' + helperText="Give the source Url(s) for the query. You can add more than one source separated with ' ; '" + variant="outlined" + value={formData.source} + onChange={handleChange} + sx={{ marginBottom: '16px' }} /> </div> @@ -126,23 +129,24 @@ export default function CustomEditor() { required id="outlined-multiline-flexible" label="Custom Query" - name='query' + name="query" multiline fullWidth minRows={5} - variant='outlined' + variant="outlined" helperText="Give the SPARQL query" placeholder={`SELECT ?s ?p ?o \nWHERE { \n\t?s ?p ?o \n}`} + value={formData.query} + onChange={handleChange} + sx={{ marginBottom: '16px' }} /> </div> - </DialogContent> + </CardContent> - <DialogActions> - <Button onClick={closeEditor}>Cancel</Button> + <CardActions> <Button variant="contained" type="submit">Submit Query</Button> - </DialogActions> - </Dialog> - + </CardActions> + </Card> {/* {showTable && <TableData data={customQueryData} title={customQueryJSON.title} />} */} </React.Fragment> @@ -168,7 +172,7 @@ async function executeSPARQLQuery(query, dataSource, setShowError) { }; -//Mock query + const addQuery = (formData) => { configManager.addQuery({ id: Date.now().toString(), @@ -180,8 +184,5 @@ const addQuery = (formData) => { comunicaContext: { sources: formData.source.split(';').map(source => source.trim()) }, - - // Location for testing purposes, delete after it works with the querystring - // queryLocation: "components.rq" }); }; diff --git a/src/components/Dashboard/Dashboard.jsx b/src/components/Dashboard/Dashboard.jsx index 366b3c1e..ca362d4d 100644 --- a/src/components/Dashboard/Dashboard.jsx +++ b/src/components/Dashboard/Dashboard.jsx @@ -1,7 +1,6 @@ import Card from '@mui/material/Card'; import CardContent from '@mui/material/CardContent'; import { Title } from 'react-admin'; -import CustomEditor from './CustomQueryEditor/customEditor'; import './Dashboard.css'; import configManager from '../../configManager/configManager'; @@ -17,14 +16,10 @@ function Dashboard() { return ( <div> - <Card> <Title title={title} /> <CardContent>{introductionText}</CardContent> </Card> - - <CustomEditor/> - </div> ); } diff --git a/src/components/InteractionLayout/SelectionMenu/SelectionMenu.jsx b/src/components/InteractionLayout/SelectionMenu/SelectionMenu.jsx index 3c5740f5..c9d67427 100644 --- a/src/components/InteractionLayout/SelectionMenu/SelectionMenu.jsx +++ b/src/components/InteractionLayout/SelectionMenu/SelectionMenu.jsx @@ -50,6 +50,7 @@ const SelectionMenu = () => { <Menu> <List> <DashboardMenuItem /> + <Menu.Item to="/customQuery" primaryText="Custom Query Editor" leftIcon={<IconProvider.DashboardCustomizeIcon/>}/> {looseQueries.map(id => ( <Tooltip key={id} From d35e1973aa0568740776e90c07d105558bd54e23 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Tue, 18 Jun 2024 14:51:45 +0200 Subject: [PATCH 14/47] Made edit operational and updated structure --- src/App.jsx | 57 ++++---- .../CustomQueryEditor/customEditor.jsx | 114 ++++++++++----- .../customQueryEditButton.jsx | 138 ++++-------------- src/configManager/configManager.js | 1 + 4 files changed, 139 insertions(+), 171 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 81cdbf5b..dabc1c44 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -35,7 +35,7 @@ function App() { const session = getDefaultSession(); const [loggedIn, setLoggedIn] = useState(); const [config, setConfig] = useState(configManager.getConfig()); - const [queryLength, setQueryLength] = useState(config.queries.length) + const [configChangeTrigger, setConfigChangeTrigger] = useState(config.queries.length) useEffect(() => { const root = document.documentElement; @@ -45,7 +45,7 @@ function App() { useEffect(() => { const handleConfigChange = (newConfig) => { setConfig(newConfig); - setQueryLength(newConfig.queries.length) + setConfigChangeTrigger(Date.now().toString()) }; // Listen for config changes @@ -75,30 +75,37 @@ function App() { }); return ( - <Admin - queryClient={queryClient} - dataProvider={SparqlDataProvider} - layout={InteractionLayout} - authProvider={authenticationProvider} - loginPage={SolidLoginForm} - requireAuth={false} - dashboard={Dashboard} - > - {queryLength && config.queries.map((query) => { - return ( - <Resource - key={query.id} - name={query.id} - options={{ label: query.name, descr: query.description, queryGroupId: query.queryGroupId }} - icon={IconProvider[query.icon]} - list={TemplatedListResultTable} - /> - ); + <Admin + queryClient={queryClient} + dataProvider={SparqlDataProvider} + layout={InteractionLayout} + authProvider={authenticationProvider} + loginPage={SolidLoginForm} + requireAuth={false} + dashboard={Dashboard} + > + {configChangeTrigger && config.queries.map((query) => { + return ( + <Resource + key={query.id} + name={query.id} + options={{ label: query.name, descr: query.description, queryGroupId: query.queryGroupId }} + icon={IconProvider[query.icon]} + list={TemplatedListResultTable} + /> + ); + })} + <CustomRoutes> + <Route key="customQuery" path="/customQuery" element={<CustomEditor newQuery={true} />} /> + {config.queries.map((query) => { + if (query.queryGroupId === 'cstm') { + return ( + <Route key={`edit${query.id}`} path={`/${query.id}/editCustom`} element={<CustomEditor newQuery={false} id={query.id} />} /> + ); + } })} - <CustomRoutes> - <Route key="customQuery" path="/customQuery" element={<CustomEditor/>}/> - </CustomRoutes> - </Admin> + </CustomRoutes> + </Admin> ); } diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index aff61710..ca4536ff 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -5,15 +5,19 @@ import Button from '@mui/material/Button'; import TextField from '@mui/material/TextField'; import { QueryEngine } from "@comunica/query-sparql"; -import { CardActions, Typography } from '@mui/material'; +import { CardActions, Typography } from '@mui/material'; import { useLocation, useNavigate } from 'react-router-dom'; +import configManager from '../../../configManager/configManager'; + -import configManager from '../../../configManager/configManager'; const myEngine = new QueryEngine(); -export default function CustomEditor() { +export default function CustomEditor(props) { + + //console.log(props) + const location = useLocation(); const navigate = useNavigate(); @@ -25,31 +29,52 @@ export default function CustomEditor() { }); const [showError, setShowError] = useState(false); + //delete tabledata when everything is finished useEffect(() => { - const searchParams = new URLSearchParams(location.search); - const title = searchParams.get('title') || ''; - const description = searchParams.get('description') || ''; - const source = searchParams.get('source') || ''; - const query = searchParams.get('query') || ''; - - setFormData({ title, description, source, query }); + if (props.newQuery) { + const searchParams = new URLSearchParams(location.search); + const title = searchParams.get('title') || ''; + const description = searchParams.get('description') || ''; + const source = searchParams.get('source') || ''; + const query = searchParams.get('query') || ''; + setFormData({ title, description, source, query }); + + } else{ + const edittingQuery = configManager.getQueryById(props.id); + setFormData({ + title: edittingQuery.name, + description: edittingQuery.description, + source: edittingQuery.comunicaContext.sources.join(' ; '), + query: edittingQuery.queryString }); + } }, [location.search]); const handleSubmit = async (event) => { event.preventDefault(); const formData = new FormData(event.currentTarget); const jsonData = Object.fromEntries(formData.entries()); - - const searchParams = new URLSearchParams(jsonData); - navigate({ search: searchParams.toString() }); - - // TODO: NEED A CHECK HERE TO SEE IF WE MAY SUBMIT (correct query) - // const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError); - - configManager.addNewQueryGroup('cstm', 'Custom queries', 'EditNoteIcon'); - addQuery(jsonData); + if (props.newQuery) { + + const searchParams = new URLSearchParams(jsonData); + navigate({ search: searchParams.toString() }); + + // TODO: NEED A CHECK HERE TO SEE IF WE MAY SUBMIT (correct query) + // const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError); + + configManager.addNewQueryGroup('cstm', 'Custom queries', 'EditNoteIcon'); + + //const savedUrl = `http://localhost:5173/#${location.pathname}?${searchParams.toString()}` + // jsonData.savedUrl = savedUrl; + // console.log(jsonData); + addQuery(jsonData); + } + else{ + + const customQuery = configManager.getQueryById(props.id); + updateQuery(jsonData, customQuery); + } }; const handleChange = (event) => { @@ -60,16 +85,49 @@ export default function CustomEditor() { })); }; + const addQuery = (formData) => { + configManager.addQuery({ + id: Date.now().toString(), + queryGroupId: "cstm", + icon: "AutoAwesomeIcon", + queryString: formData.query, + name: formData.title, + description: formData.description, + comunicaContext: { + sources: formData.source.split(';').map(source => source.trim()) + }, + }); + }; + + const updateQuery = (formData, customQuery) => { + console.log(formData , customQuery) + const { title, description, query, source } = formData; + + // NAAM EN BESCHRIJVING WILLEN visueel NIET MEE UPDATEIN IN DE RESOURCES + configManager.updateQuery({ + ...customQuery, + name: title, + description: description, + queryString: query, + comunicaContext: { + sources: source.split(';').map(src => src.trim()) + }, + // queryLocation: "components.rq" // Location for testing purposes, delete after it works with the querystring + }); + + navigate(`/${customQuery.id}`) + }; + return ( <React.Fragment> - + <Card component="form" onSubmit={handleSubmit} sx={{ padding: '16px', marginTop: '16px', width: '100%' }} > <CardContent> - <Typography variant="h6">Custom Query Editor</Typography> + <Typography variant="h6">{props.newQuery?'Custom Query Editor':'Edit'}</Typography> {showError && ( <Typography variant="body2" sx={{ color: 'red', mb: '10px' }}> Invalid Query. Check the URL and Query Syntax @@ -173,16 +231,4 @@ async function executeSPARQLQuery(query, dataSource, setShowError) { -const addQuery = (formData) => { - configManager.addQuery({ - id: Date.now().toString(), - queryGroupId: "cstm", - icon: "AutoAwesomeIcon", - queryString: formData.query, - name: formData.title, - description: formData.description, - comunicaContext: { - sources: formData.source.split(';').map(source => source.trim()) - }, - }); -}; + diff --git a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx index 1b8fea4c..39844be4 100644 --- a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx @@ -1,141 +1,55 @@ import React, { useState, useEffect } from 'react'; -import Dialog from '@mui/material/Dialog'; -import DialogTitle from '@mui/material/DialogTitle'; import Button from '@mui/material/Button'; -import TextField from '@mui/material/TextField'; -import DialogActions from '@mui/material/DialogActions'; -import DialogContent from '@mui/material/DialogContent'; -import DialogContentText from '@mui/material/DialogContentText'; -import { QueryEngine } from "@comunica/query-sparql"; + import { useLocation, useNavigate } from 'react-router-dom'; import configManager from '../../../configManager/configManager'; -const myEngine = new QueryEngine(); export default function CustomQueryEditButton({ queryID }) { - - // GET QUERY BY ID -> not yet implemented + const customQuery = configManager.getQueryById(queryID); const sourcesString = customQuery.comunicaContext.sources.join(' ; '); - const [openEditor, setOpenEditor] = useState(false); - - const [showError, setShowError] = useState(false); const navigate = useNavigate(); + const location = useLocation(); - const closeEditor = () => { - setOpenEditor(false); - setShowError(false); + const handleEditClick = () => { + navigate(`/${queryID}/editCustom`) } - // Need a solutin to reload the new query? + const handleSave = () => { + // const searchParams = new URLSearchParams(customQuery); + + //newSearchaparam(geconverteerd object voor form) + //const savedUrl = `http://localhost:5173/#${location.pathname}?${searchParams.toString()}` + + // const savedUrl = `http://localhost:5173/#/customQuery?${searchParams.toString()}` + // console.log(customQuery) + // console.log(savedUrl) + + } return ( <React.Fragment> <Button variant="contained" onClick={ - () => { setOpenEditor(true) }} + () => { + handleEditClick() + }} sx={{ margin: '10px' }}> Edit Query </Button> - - <Dialog - open={openEditor} - onClose={closeEditor} - maxWidth={'md'} - fullWidth - PaperProps={{ - component: 'form', - onSubmit: async (event) => { - event.preventDefault(); - const formData = new FormData(event.currentTarget); - const jsonData = Object.fromEntries(formData.entries()); - - // TODO: NEED A CHECK HERE TO SEE IF WE MAY SUBMIT (correct query) - // const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError) - - updateQuery(jsonData, customQuery) - closeEditor(); - navigate('/#') - }, + <Button variant="contained" onClick={ + () => { + handleSave() }} - > - <DialogTitle> Edit: {customQuery.name}</DialogTitle> - - <DialogContent> - <DialogContentText sx={{ mb: '15px' }}> - {customQuery.description} - </DialogContentText> - - - {/* <div> - <TextField - required - disabled - fullWidth - name='title' - id="outlined-required" - label="Query title" - helperText="Edit name" - variant='outlined' - defaultValue={customQuery.name} - /> - - <TextField - required - disabled - id="outlined-multiline-flexible" - label="Description" - name='description' - multiline - fullWidth - minRows={2} - variant='outlined' - helperText="Edit description" - defaultValue={customQuery.description} - /> - </div> */} - - <div> - <TextField - required - fullWidth - name='source' - id="outlined-required" - label="Data Source " - defaultValue={sourcesString} - helperText="You can add more than one source separated with ' ; '" - variant='outlined' - /> - </div> - - <div> - <TextField - required - id="outlined-multiline-flexible" - label="Custom Query" - name='query' - multiline - fullWidth - minRows={5} - variant='outlined' - defaultValue={customQuery.queryString} - /> - </div> - <DialogContentText > - When confirming you will be redirected to the dashboard. - </DialogContentText> - </DialogContent> - - <DialogActions> - <Button onClick={closeEditor}>Cancel</Button> - <Button variant="contained" type="submit">Confirm changes</Button> - </DialogActions> - </Dialog> + sx={{ margin: '10px' }}> + Save Query + </Button> </React.Fragment> ) @@ -151,6 +65,6 @@ const updateQuery = (formData, customQuery) => { comunicaContext: { sources: source.split(';').map(src => src.trim()) }, - // queryLocation: "components.rq" // Location for testing purposes, delete after it works with the querystring + // queryLocation: "components.rq" // Location for testing purposes, delete after it works with the querystring }); }; diff --git a/src/configManager/configManager.js b/src/configManager/configManager.js index 9b181f5d..b014e771 100644 --- a/src/configManager/configManager.js +++ b/src/configManager/configManager.js @@ -125,6 +125,7 @@ class ConfigManager extends EventEmitter { if (index !== -1) { this.config.queries[index] = updatedQuery; } + this.queryWorkingCopies = {}; this.emit('configChanged', this.config); } From f5c547261dea4447c527c7b27ce27dcc824f5da1 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Thu, 20 Jun 2024 15:29:13 +0200 Subject: [PATCH 15/47] update for a custom query using an index file --- src/dataProvider/SparqlDataProvider.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/dataProvider/SparqlDataProvider.js b/src/dataProvider/SparqlDataProvider.js index 3892f786..ff18633c 100644 --- a/src/dataProvider/SparqlDataProvider.js +++ b/src/dataProvider/SparqlDataProvider.js @@ -343,8 +343,13 @@ async function configureBindingStream(bindingStream, variables) { const addComunicaContextSourcesFromSourcesIndex = async (sourcesIndex) => { const sourcesList = []; try { - const result = await fetch(`${config.queryFolder}${sourcesIndex.queryLocation}`); - const queryStringIndexSource = await result.text(); + let queryStringIndexSource; + if (sourcesIndex.queryLocation){ + const result = await fetch(`${config.queryFolder}${sourcesIndex.queryLocation}`); + queryStringIndexSource = await result.text(); + }else{ + queryStringIndexSource = sourcesIndex.queryString; + } const bindingsStream = await myEngine.queryBindings(queryStringIndexSource, { sources: [sourcesIndex.url], From 3cf244e7c45a19e3f295726258fea9cfa0e57a11 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Thu, 20 Jun 2024 15:29:29 +0200 Subject: [PATCH 16/47] custom query creation is operational --- .../CustomQueryEditor/customEditor.jsx | 477 ++++++++++++++---- .../customQueryEditButton.jsx | 18 +- 2 files changed, 390 insertions(+), 105 deletions(-) diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index ca4536ff..46b8d88b 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -8,6 +8,10 @@ import { QueryEngine } from "@comunica/query-sparql"; import { CardActions, Typography } from '@mui/material'; import { useLocation, useNavigate } from 'react-router-dom'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; + import configManager from '../../../configManager/configManager'; @@ -22,32 +26,62 @@ export default function CustomEditor(props) { const location = useLocation(); const navigate = useNavigate(); const [formData, setFormData] = useState({ - title: '', + name: '', description: '', source: '', - query: '' + queryString: '', + comunicaContext: '', + + comunicaContextCheck: false, + sourceIndexCheck: false, + askQueryCheck: false, + templatedQueryCheck: false, + + }); const [showError, setShowError] = useState(false); + //const [sourceIndexCheck, setSourceIndexCheck] = useState(false); + // const [comunicaContextCheck, setComunicaContextCheck] = useState(false); + // const [askQueryCheck, setAskQueryCheck] = useState(false); + // const [templatedQueryCheck, setTemplatedQueryCheck] = useState(false); + + const [parsingErrorComunica, setParsingErrorComunica] = useState(false); + const [parsingErrorAsk, setParsingErrorAsk] = useState(false); + const [parsingErrorTemplate, setParsingErrorTemplate] = useState(false); //delete tabledata when everything is finished useEffect(() => { if (props.newQuery) { const searchParams = new URLSearchParams(location.search); - const title = searchParams.get('title') || ''; - const description = searchParams.get('description') || ''; - const source = searchParams.get('source') || ''; - const query = searchParams.get('query') || ''; - setFormData({ title, description, source, query }); - - } else{ + const obj = {} + //if (searchParams.size > 0 ) + searchParams.forEach((value, key) => { + // console.log(key, value); + obj[key] = value + }) + + //console.log(obj) + // const name = searchParams.get('name') || ''; + // const description = searchParams.get('description') || ''; + // const source = searchParams.get('source') || ''; + // const queryString = searchParams.get('queryString') || ''; + // const sourceIndexCheck = searchParams.get('sourceIndexCheck') || false; + // const indexSource = searchParams.get('indexSource') || ''; + // setFormData({ name, description, source, queryString, sourceIndexCheck, indexSource }); + + setFormData(obj) + + } else { const edittingQuery = configManager.getQueryById(props.id); - setFormData({ - title: edittingQuery.name, - description: edittingQuery.description, - source: edittingQuery.comunicaContext.sources.join(' ; '), - query: edittingQuery.queryString }); + setFormData({ + name: edittingQuery.name, + description: edittingQuery.description, + source: edittingQuery.comunicaContext.sources.join(' ; '), + queryString: edittingQuery.queryString, + sourceIndexCheck: edittingQuery.sourceIndexCheck + }); } }, [location.search]); @@ -56,22 +90,23 @@ export default function CustomEditor(props) { const formData = new FormData(event.currentTarget); const jsonData = Object.fromEntries(formData.entries()); if (props.newQuery) { - + const searchParams = new URLSearchParams(jsonData); + jsonData.searchParams = searchParams; navigate({ search: searchParams.toString() }); // TODO: NEED A CHECK HERE TO SEE IF WE MAY SUBMIT (correct query) // const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError); - + configManager.addNewQueryGroup('cstm', 'Custom queries', 'EditNoteIcon'); - + //const savedUrl = `http://localhost:5173/#${location.pathname}?${searchParams.toString()}` // jsonData.savedUrl = savedUrl; // console.log(jsonData); addQuery(jsonData); } - else{ - + else { + const customQuery = configManager.getQueryById(props.id); updateQuery(jsonData, customQuery); } @@ -85,90 +120,188 @@ export default function CustomEditor(props) { })); }; + const handleJSONparsing = (event, errorSetter) => { + const { name, value } = event.target; + errorSetter(false) + + let parsedValue; + try { + parsedValue = JSON.parse(value); + } catch (error) { + errorSetter(true) + parsedValue = value; + } + + setFormData((prevFormData) => ({ + ...prevFormData, + [name]: parsedValue, + })); + }; + const ensureBoolean = (value) => value === 'on' || value === true; + + + const parseAllObjectsToJSON = (dataWithStrings) => { + + const parsedObject = dataWithStrings; + + if (ensureBoolean(dataWithStrings.comunicaContextCheck)) { + parsedObject.comunicaContext = JSON.parse(dataWithStrings.comunicaContext); + + if (!!dataWithStrings.source && dataWithStrings.source.trim() !== '') + parsedObject.comunicaContext.sources = dataWithStrings.source.split(';').map(source => source.trim()); + + } else if (!!dataWithStrings.source && dataWithStrings.source.trim() !== '') { + parsedObject.comunicaContext = { + sources: formData.source.split(';').map(source => source.trim()) + } + } + + if (ensureBoolean(dataWithStrings.sourceIndexCheck)) { + parsedObject.sourcesIndex = { + url: parsedObject.indexSourceUrl, + queryString: parsedObject.indexSourceQuery + } + } + + if (ensureBoolean(dataWithStrings.askQueryCheck)) { + parsedObject.askQuery = JSON.parse(dataWithStrings.askQuery); + } + if (ensureBoolean(dataWithStrings.templatedQueryCheck)) { + parsedObject.variables = JSON.parse(dataWithStrings.variables); + } + return parsedObject; + } + const addQuery = (formData) => { + formData = parseAllObjectsToJSON(formData); + console.log('addfunctie: ', formData) + configManager.addQuery({ + ...formData, id: Date.now().toString(), queryGroupId: "cstm", icon: "AutoAwesomeIcon", - queryString: formData.query, - name: formData.title, - description: formData.description, - comunicaContext: { - sources: formData.source.split(';').map(source => source.trim()) - }, }); }; - - const updateQuery = (formData, customQuery) => { - console.log(formData , customQuery) - const { title, description, query, source } = formData; - // NAAM EN BESCHRIJVING WILLEN visueel NIET MEE UPDATEIN IN DE RESOURCES + const updateQuery = (formData, customQuery) => { + console.log(formData, customQuery) + const { name, description, queryString, source } = formData; + + configManager.updateQuery({ - ...customQuery, - name: title, - description: description, - queryString: query, - comunicaContext: { - sources: source.split(';').map(src => src.trim()) - }, - // queryLocation: "components.rq" // Location for testing purposes, delete after it works with the querystring + ...customQuery, + name: name, + description: description, + queryString: queryString, + // comunicaContext: { + // sources: source.split(';').map(src => src.trim()) + // }, + // queryLocation: "components.rq" // Location for testing purposes, delete after it works with the querystring }); - + navigate(`/${customQuery.id}`) }; + return ( <React.Fragment> - + <Button variant="contained" onClick={ + () => { + console.log(formData) + }} + sx={{ margin: '10px' }}> + ShowData + + </Button> <Card component="form" onSubmit={handleSubmit} sx={{ padding: '16px', marginTop: '16px', width: '100%' }} > <CardContent> - <Typography variant="h6">{props.newQuery?'Custom Query Editor':'Edit'}</Typography> + <Typography variant="h6">{props.newQuery ? 'Custom Query Editor' : 'Edit'}</Typography> {showError && ( <Typography variant="body2" sx={{ color: 'red', mb: '10px' }}> Invalid Query. Check the URL and Query Syntax </Typography> )} - - <div> - <TextField - required - fullWidth - name="title" - id="outlined-required" - label="Query title" - placeholder="Custom query name" - helperText="Give this custom query a name" - variant="outlined" - value={formData.title} - onChange={handleChange} - sx={{ marginBottom: '16px' }} - /> - - <TextField - required - id="outlined-multiline-flexible" - label="Description" - name="description" - multiline - fullWidth - minRows={2} - variant="outlined" - helperText="Give a description for the query" - placeholder="This is a custom query." - value={formData.description} - onChange={handleChange} - sx={{ marginBottom: '16px' }} - /> - </div> - - <div> + {/* {console.log(!!formData.indexSourceQuery ? formData.indexSourceQuery : false)} */} + <Card sx={{ px: '10px', my: 2 }}> + <div> + + <TextField + required + fullWidth + name="name" + id="outlined-required" + label="Query name" + placeholder="Custom query name" + helperText="Give this custom query a name" + variant="outlined" + value={!!formData.name ? formData.name : ''} + onChange={handleChange} + sx={{ marginBottom: '16px' }} + /> + + <TextField + required + id="outlined-multiline-flexible" + label="Description" + name="description" + multiline + fullWidth + minRows={2} + variant="outlined" + helperText="Give a description for the query" + placeholder="This is a custom query." + value={!!formData.description ? formData.description : ''} + onChange={handleChange} + sx={{ marginBottom: '16px' }} + /> + + <TextField + required + id="outlined-multiline-flexible" + label="Custom Query" + name="queryString" + multiline + fullWidth + minRows={5} + variant="outlined" + helperText="Give the SPARQL query" + placeholder={`SELECT ?s ?p ?o \nWHERE { \n\t?s ?p ?o \n}`} + value={!!formData.queryString ? formData.queryString : ''} + //value={formData.queryString} + onChange={handleChange} + sx={{ marginBottom: '16px' }} + /> + </div> + </Card> + + <Card sx={{ px: '10px', my: 2 }}> + + <Typography variant="h5" sx={{ mt: 2 }}> Comunica Context</Typography> + <div> + <FormControlLabel + control={<Checkbox + name='comunicaContextCheck' + checked={!!formData.comunicaContextCheck} + onChange={ + () => { + setFormData((prevFormData) => ({ + ...prevFormData, + 'comunicaContextCheck': !formData.comunicaContextCheck, + })) + } + // () => { setComunicaContextCheck(!comunicaContextCheck) } + } + + />} label="Advanced Comunica Context Settings" /> + + </div> <TextField - required + required={!formData.sourceIndexCheck} fullWidth name="source" id="outlined-required" @@ -176,42 +309,192 @@ export default function CustomEditor(props) { placeholder="http://examplesource.org ; source2" helperText="Give the source Url(s) for the query. You can add more than one source separated with ' ; '" variant="outlined" - value={formData.source} + value={!!formData.source ? formData.source : ''} + // value={formData.source} onChange={handleChange} sx={{ marginBottom: '16px' }} /> - </div> - <div> - <TextField - required - id="outlined-multiline-flexible" - label="Custom Query" - name="query" - multiline - fullWidth - minRows={5} - variant="outlined" - helperText="Give the SPARQL query" - placeholder={`SELECT ?s ?p ?o \nWHERE { \n\t?s ?p ?o \n}`} - value={formData.query} - onChange={handleChange} - sx={{ marginBottom: '16px' }} - /> - </div> + + {formData.comunicaContextCheck && + <div> + + <TextField + required={ensureBoolean(formData.comunicaContextCheck)} + id="outlined-multiline-flexible" + label="Comunica Context Configuration" + name="comunicaContext" + multiline + fullWidth + error={parsingErrorComunica} + minRows={5} + variant="outlined" + helperText={`Write the extra configurations in JSON-format ${parsingErrorComunica ? ' (Invalid Syntax)' : ''}`} + value={!!formData.comunicaContext ? typeof formData.comunicaContext === 'object' ? JSON.stringify(formData.comunicaContext) : formData.comunicaContext : ''} + placeholder={`{\n\t"lenient" : true,\n\t"other" : "some other options"\n}`} + // value={formData.query} + onChange={(e) => handleJSONparsing(e, setParsingErrorComunica)} + sx={{ marginBottom: '16px' }} + /> + </div> + } + </Card> + + <Card sx={{ px: '10px', my: 2 }}> + + <div> + + <FormControlLabel + control={<Checkbox + name='sourceIndexCheck' + checked={!!formData.sourceIndexCheck} + onChange={ + () => { + setFormData((prevFormData) => ({ + ...prevFormData, + 'sourceIndexCheck': !formData.sourceIndexCheck, + })) + } + } + //() => setSourceIndexCheck(!sourceIndexCheck)} + + />} label="Source from index file" /> + + {formData.sourceIndexCheck && + <div> + <TextField + required={ensureBoolean(formData.sourceIndexCheck)} + fullWidth + name="indexSourceUrl" + id="outlined-required" + label="Index File url" + placeholder="http://examplesource.org ; source2" + helperText="Give the index file to use as IndexSource." + variant="outlined" + value={!!formData.indexSourceUrl ? formData.indexSourceUrl : ''} + // value={formData.indexSource} + onChange={handleChange} + sx={{ marginBottom: '16px' }} + /> + + <TextField + required={ensureBoolean(formData.sourceIndexCheck)} + id="outlined-multiline-flexible" + label="Query to get the source from index file" + name="indexSourceQuery" + multiline + fullWidth + minRows={5} + variant="outlined" + helperText="Give the SPARQL query to retrieve the sources" + placeholder={`SELECT ?s ?p ?o \nWHERE { \n\t?s ?p ?o \n}`} + value={!!formData.indexSourceQuery ? formData.indexSourceQuery : ''} + // value={formData.query} + onChange={handleChange} + sx={{ marginBottom: '16px' }} + /> + </div> + } + + + + + + <FormControlLabel + control={<Checkbox + name='askQueryCheck' + checked={!!formData.askQueryCheck} + onChange={ + + () => { + setFormData((prevFormData) => ({ + ...prevFormData, + 'askQueryCheck': !formData.askQueryCheck, + })) + } + // () => { setAskQueryCheck(!askQueryCheck) } + } + + />} label="add an askQuery" /> + + {formData.askQueryCheck && + <div> + + <TextField + id="outlined-multiline-flexible" + label="Creating an ask query" + name="askQuery" + error={parsingErrorAsk} + multiline + fullWidth + minRows={5} + variant="outlined" + helperText={`Write contents of the askQuery in JSON-format ${parsingErrorAsk ? ' (Invalid Syntax)' : ''}`} + // defaultValue={`{\n\t"trueText" : " ",\n\t"falseText" : " " \n}`} + //value={!!formData.askQuery? formData.askQuery : ''} + value={!!formData.askQuery ? typeof formData.askQuery === 'object' ? JSON.stringify(formData.askQuery) : formData.askQuery : `{\n\t"trueText" : " ",\n\t"falseText" : " " \n}`} + placeholder={`{\n\t"trueText" : "this displays when true.",\n\t"falseText" : "this displays when false." \n}`} + // value={formData.query} + onChange={(e) => handleJSONparsing(e, setParsingErrorAsk)} + sx={{ marginBottom: '16px' }} + /> + </div> + } + <FormControlLabel + control={<Checkbox + name='templatedQueryCheck' + checked={!!formData.templatedQueryCheck} + onChange={ + () => { + setFormData((prevFormData) => ({ + ...prevFormData, + 'templatedQueryCheck': !formData.templatedQueryCheck, + })) + } + //() => { setTemplatedQueryCheck(!templatedQueryCheck) } + } + + + />} label="Templated Query" /> + + {formData.templatedQueryCheck && + <div> + + <TextField + id="outlined-multiline-flexible" + label="Variables for the templated query" + name="variables" + error={parsingErrorTemplate} + multiline + fullWidth + minRows={5} + variant="outlined" + helperText={`Write the variables in JSON-format ${parsingErrorTemplate ? ' (Invalid Syntax)' : ''}`} + value={!!formData.variables ? typeof formData.variables === 'object' ? JSON.stringify(formData.variables) : formData.variables : ''} + placeholder={`{\n\tvariableOne : ["option1","option2","option3"],\n\tvariableTwo : ["option1","option2","option3"], \n\t...\n}`} + // value={formData.query} + onChange={(e) => handleJSONparsing(e, setParsingErrorTemplate)} + sx={{ marginBottom: '16px' }} + /> + </div> + } + </div> + </Card> </CardContent> <CardActions> <Button variant="contained" type="submit">Submit Query</Button> </CardActions> </Card> - {/* {showTable && <TableData data={customQueryData} title={customQueryJSON.title} />} */} + </React.Fragment> ) } // Temporary bindingstream + +/* async function executeSPARQLQuery(query, dataSource, setShowError) { const resultingObjects = []; try { @@ -229,6 +512,8 @@ async function executeSPARQLQuery(query, dataSource, setShowError) { return resultingObjects; }; +*/ + diff --git a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx index 39844be4..ba56b500 100644 --- a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx @@ -11,24 +11,24 @@ export default function CustomQueryEditButton({ queryID }) { const customQuery = configManager.getQueryById(queryID); - const sourcesString = customQuery.comunicaContext.sources.join(' ; '); + // const sourcesString = customQuery.comunicaContext.sources.join(' ; '); const navigate = useNavigate(); - const location = useLocation(); + // const location = useLocation(); const handleEditClick = () => { navigate(`/${queryID}/editCustom`) } const handleSave = () => { - // const searchParams = new URLSearchParams(customQuery); - //newSearchaparam(geconverteerd object voor form) - //const savedUrl = `http://localhost:5173/#${location.pathname}?${searchParams.toString()}` - - // const savedUrl = `http://localhost:5173/#/customQuery?${searchParams.toString()}` - // console.log(customQuery) - // console.log(savedUrl) + + const url = new URL( window.location.href); + const serverURL = `${url.protocol}//${url.hostname}${url.port ? ':' + url.port : ''}`; + + const savedUrl = `${serverURL}/#/customQuery?${customQuery.searchParams.toString()}` + console.log(savedUrl) + } From 45d4f02f9587f9f75fec2ad0064b2727e071beb6 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Thu, 20 Jun 2024 16:43:13 +0200 Subject: [PATCH 17/47] added a little check to prevent a crash when redirecting --- .../ListResultTable/QueryResultList/TableHeader/TableHeader.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ListResultTable/QueryResultList/TableHeader/TableHeader.jsx b/src/components/ListResultTable/QueryResultList/TableHeader/TableHeader.jsx index aff92d7b..ad2ba020 100644 --- a/src/components/ListResultTable/QueryResultList/TableHeader/TableHeader.jsx +++ b/src/components/ListResultTable/QueryResultList/TableHeader/TableHeader.jsx @@ -54,7 +54,7 @@ function TableHeader({ children }) { > {child.props.label} </span> - {variableOntology[child.props.source] && ( + {!!variableOntology && variableOntology[child.props.source] && ( <Link target="_blank" href={variableOntology[child.props.source]} From 7d0b29273f30ff5eead074b2257f5972b5bff240 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Thu, 20 Jun 2024 16:43:24 +0200 Subject: [PATCH 18/47] added a delete function --- src/configManager/configManager.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/configManager/configManager.js b/src/configManager/configManager.js index b014e771..2f3f719d 100644 --- a/src/configManager/configManager.js +++ b/src/configManager/configManager.js @@ -123,7 +123,20 @@ class ConfigManager extends EventEmitter { updateQuery(updatedQuery) { let index = this.config.queries.findIndex(query => query.id === updatedQuery.id); if (index !== -1) { - this.config.queries[index] = updatedQuery; + this.config.queries[index] = updatedQuery; + } + this.queryWorkingCopies = {}; + this.emit('configChanged', this.config); + } + + /** + * Deletes the query with the given id in the config.queries array in the configuration + * @param {string} id - id property of the query to delete + */ + deleteQueryById(id) { + let index = this.config.queries.findIndex(query => query.id === id); + if (index !== -1) { + this.config.queries.splice(index, 1); } this.queryWorkingCopies = {}; this.emit('configChanged', this.config); @@ -145,15 +158,15 @@ class ConfigManager extends EventEmitter { * @returns {string} the query text */ async getQueryText(query) { - + if (query.queryLocation) { const fetchResult = await fetch(`${this.config.queryFolder}${query.queryLocation}`); return await fetchResult.text(); } - return query.queryString; + return query.queryString; } -} +} const configManager = new ConfigManager(); export default configManager; From de22b497f677a5e4e5d3c59e55cbe872a361f6c9 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Thu, 20 Jun 2024 17:09:08 +0200 Subject: [PATCH 19/47] added icons --- src/IconProvider/IconProvider.js | 14 +- .../CustomQueryEditor/customEditor.jsx | 127 +++--------------- .../customQueryEditButton.jsx | 71 +++++++--- .../Dashboard/CustomQueryEditor/tableData.jsx | 55 -------- .../QueryResultList/QueryResultList.jsx | 73 +++++----- 5 files changed, 121 insertions(+), 219 deletions(-) delete mode 100644 src/components/Dashboard/CustomQueryEditor/tableData.jsx diff --git a/src/IconProvider/IconProvider.js b/src/IconProvider/IconProvider.js index f2103b00..972f2719 100644 --- a/src/IconProvider/IconProvider.js +++ b/src/IconProvider/IconProvider.js @@ -11,6 +11,12 @@ import EditNoteIcon from '@mui/icons-material/EditNote'; import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome'; import AddIcon from '@mui/icons-material/Add'; import DashboardCustomizeIcon from '@mui/icons-material/DashboardCustomize'; +import DeleteIcon from '@mui/icons-material/Delete'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; +import SaveIcon from '@mui/icons-material/Save'; +import ModeEditIcon from '@mui/icons-material/ModeEdit'; +import TuneIcon from '@mui/icons-material/Tune'; +import SaveAsIcon from '@mui/icons-material/SaveAs'; export default { BrushIcon, @@ -25,5 +31,11 @@ export default { EditNoteIcon, AutoAwesomeIcon, AddIcon, - DashboardCustomizeIcon + DashboardCustomizeIcon, + DeleteIcon, + ContentCopyIcon, + SaveIcon, + ModeEditIcon, + TuneIcon, + SaveAsIcon }; diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index 46b8d88b..95c3a3c4 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -3,26 +3,18 @@ import Card from '@mui/material/Card'; import CardContent from '@mui/material/CardContent'; import Button from '@mui/material/Button'; import TextField from '@mui/material/TextField'; - -import { QueryEngine } from "@comunica/query-sparql"; import { CardActions, Typography } from '@mui/material'; import { useLocation, useNavigate } from 'react-router-dom'; - -import FormGroup from '@mui/material/FormGroup'; import FormControlLabel from '@mui/material/FormControlLabel'; import Checkbox from '@mui/material/Checkbox'; - import configManager from '../../../configManager/configManager'; +import IconProvider from '../../../IconProvider/IconProvider'; - - -const myEngine = new QueryEngine(); +//import { QueryEngine } from "@comunica/query-sparql"; +//const myEngine = new QueryEngine(); export default function CustomEditor(props) { - //console.log(props) - - const location = useLocation(); const navigate = useNavigate(); const [formData, setFormData] = useState({ @@ -37,76 +29,48 @@ export default function CustomEditor(props) { askQueryCheck: false, templatedQueryCheck: false, - }); - const [showError, setShowError] = useState(false); - //const [sourceIndexCheck, setSourceIndexCheck] = useState(false); - // const [comunicaContextCheck, setComunicaContextCheck] = useState(false); - // const [askQueryCheck, setAskQueryCheck] = useState(false); - // const [templatedQueryCheck, setTemplatedQueryCheck] = useState(false); + const [showError, setShowError] = useState(false); const [parsingErrorComunica, setParsingErrorComunica] = useState(false); const [parsingErrorAsk, setParsingErrorAsk] = useState(false); const [parsingErrorTemplate, setParsingErrorTemplate] = useState(false); - //delete tabledata when everything is finished - useEffect(() => { + let searchParams if (props.newQuery) { - const searchParams = new URLSearchParams(location.search); + searchParams = new URLSearchParams(location.search); + } else{ + const edittingQuery = configManager.getQueryById(props.id); + searchParams = edittingQuery.searchParams; + } const obj = {} - //if (searchParams.size > 0 ) searchParams.forEach((value, key) => { - // console.log(key, value); obj[key] = value }) - - //console.log(obj) - // const name = searchParams.get('name') || ''; - // const description = searchParams.get('description') || ''; - // const source = searchParams.get('source') || ''; - // const queryString = searchParams.get('queryString') || ''; - // const sourceIndexCheck = searchParams.get('sourceIndexCheck') || false; - // const indexSource = searchParams.get('indexSource') || ''; - // setFormData({ name, description, source, queryString, sourceIndexCheck, indexSource }); - setFormData(obj) - - } else { - const edittingQuery = configManager.getQueryById(props.id); - setFormData({ - name: edittingQuery.name, - description: edittingQuery.description, - source: edittingQuery.comunicaContext.sources.join(' ; '), - queryString: edittingQuery.queryString, - sourceIndexCheck: edittingQuery.sourceIndexCheck - }); - } }, [location.search]); const handleSubmit = async (event) => { event.preventDefault(); const formData = new FormData(event.currentTarget); const jsonData = Object.fromEntries(formData.entries()); - if (props.newQuery) { - const searchParams = new URLSearchParams(jsonData); - jsonData.searchParams = searchParams; + const searchParams = new URLSearchParams(jsonData); + jsonData.searchParams = searchParams; + + if (props.newQuery) { navigate({ search: searchParams.toString() }); // TODO: NEED A CHECK HERE TO SEE IF WE MAY SUBMIT (correct query) // const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError); configManager.addNewQueryGroup('cstm', 'Custom queries', 'EditNoteIcon'); - - //const savedUrl = `http://localhost:5173/#${location.pathname}?${searchParams.toString()}` - // jsonData.savedUrl = savedUrl; - // console.log(jsonData); addQuery(jsonData); } else { - + console.log(jsonData) const customQuery = configManager.getQueryById(props.id); updateQuery(jsonData, customQuery); } @@ -139,7 +103,6 @@ export default function CustomEditor(props) { }; const ensureBoolean = (value) => value === 'on' || value === true; - const parseAllObjectsToJSON = (dataWithStrings) => { const parsedObject = dataWithStrings; @@ -174,8 +137,6 @@ export default function CustomEditor(props) { const addQuery = (formData) => { formData = parseAllObjectsToJSON(formData); - console.log('addfunctie: ', formData) - configManager.addQuery({ ...formData, id: Date.now().toString(), @@ -185,35 +146,18 @@ export default function CustomEditor(props) { }; const updateQuery = (formData, customQuery) => { - console.log(formData, customQuery) - const { name, description, queryString, source } = formData; - - + formData = parseAllObjectsToJSON(formData); configManager.updateQuery({ ...customQuery, - name: name, - description: description, - queryString: queryString, - // comunicaContext: { - // sources: source.split(';').map(src => src.trim()) - // }, - // queryLocation: "components.rq" // Location for testing purposes, delete after it works with the querystring + ...formData }); - navigate(`/${customQuery.id}`) + navigate(`/${customQuery.id}`) }; return ( <React.Fragment> - <Button variant="contained" onClick={ - () => { - console.log(formData) - }} - sx={{ margin: '10px' }}> - ShowData - - </Button> <Card component="form" onSubmit={handleSubmit} @@ -226,10 +170,9 @@ export default function CustomEditor(props) { Invalid Query. Check the URL and Query Syntax </Typography> )} - {/* {console.log(!!formData.indexSourceQuery ? formData.indexSourceQuery : false)} */} + <Card sx={{ px: '10px', my: 2 }}> <div> - <TextField required fullWidth @@ -272,7 +215,6 @@ export default function CustomEditor(props) { helperText="Give the SPARQL query" placeholder={`SELECT ?s ?p ?o \nWHERE { \n\t?s ?p ?o \n}`} value={!!formData.queryString ? formData.queryString : ''} - //value={formData.queryString} onChange={handleChange} sx={{ marginBottom: '16px' }} /> @@ -294,7 +236,6 @@ export default function CustomEditor(props) { 'comunicaContextCheck': !formData.comunicaContextCheck, })) } - // () => { setComunicaContextCheck(!comunicaContextCheck) } } />} label="Advanced Comunica Context Settings" /> @@ -310,7 +251,6 @@ export default function CustomEditor(props) { helperText="Give the source Url(s) for the query. You can add more than one source separated with ' ; '" variant="outlined" value={!!formData.source ? formData.source : ''} - // value={formData.source} onChange={handleChange} sx={{ marginBottom: '16px' }} /> @@ -318,7 +258,6 @@ export default function CustomEditor(props) { {formData.comunicaContextCheck && <div> - <TextField required={ensureBoolean(formData.comunicaContextCheck)} id="outlined-multiline-flexible" @@ -332,7 +271,6 @@ export default function CustomEditor(props) { helperText={`Write the extra configurations in JSON-format ${parsingErrorComunica ? ' (Invalid Syntax)' : ''}`} value={!!formData.comunicaContext ? typeof formData.comunicaContext === 'object' ? JSON.stringify(formData.comunicaContext) : formData.comunicaContext : ''} placeholder={`{\n\t"lenient" : true,\n\t"other" : "some other options"\n}`} - // value={formData.query} onChange={(e) => handleJSONparsing(e, setParsingErrorComunica)} sx={{ marginBottom: '16px' }} /> @@ -343,7 +281,6 @@ export default function CustomEditor(props) { <Card sx={{ px: '10px', my: 2 }}> <div> - <FormControlLabel control={<Checkbox name='sourceIndexCheck' @@ -356,8 +293,6 @@ export default function CustomEditor(props) { })) } } - //() => setSourceIndexCheck(!sourceIndexCheck)} - />} label="Source from index file" /> {formData.sourceIndexCheck && @@ -372,7 +307,6 @@ export default function CustomEditor(props) { helperText="Give the index file to use as IndexSource." variant="outlined" value={!!formData.indexSourceUrl ? formData.indexSourceUrl : ''} - // value={formData.indexSource} onChange={handleChange} sx={{ marginBottom: '16px' }} /> @@ -389,37 +323,28 @@ export default function CustomEditor(props) { helperText="Give the SPARQL query to retrieve the sources" placeholder={`SELECT ?s ?p ?o \nWHERE { \n\t?s ?p ?o \n}`} value={!!formData.indexSourceQuery ? formData.indexSourceQuery : ''} - // value={formData.query} onChange={handleChange} sx={{ marginBottom: '16px' }} /> </div> } - - - - <FormControlLabel control={<Checkbox name='askQueryCheck' checked={!!formData.askQueryCheck} onChange={ - () => { setFormData((prevFormData) => ({ ...prevFormData, 'askQueryCheck': !formData.askQueryCheck, })) } - // () => { setAskQueryCheck(!askQueryCheck) } } - />} label="add an askQuery" /> {formData.askQueryCheck && <div> - <TextField id="outlined-multiline-flexible" label="Creating an ask query" @@ -430,11 +355,8 @@ export default function CustomEditor(props) { minRows={5} variant="outlined" helperText={`Write contents of the askQuery in JSON-format ${parsingErrorAsk ? ' (Invalid Syntax)' : ''}`} - // defaultValue={`{\n\t"trueText" : " ",\n\t"falseText" : " " \n}`} - //value={!!formData.askQuery? formData.askQuery : ''} value={!!formData.askQuery ? typeof formData.askQuery === 'object' ? JSON.stringify(formData.askQuery) : formData.askQuery : `{\n\t"trueText" : " ",\n\t"falseText" : " " \n}`} placeholder={`{\n\t"trueText" : "this displays when true.",\n\t"falseText" : "this displays when false." \n}`} - // value={formData.query} onChange={(e) => handleJSONparsing(e, setParsingErrorAsk)} sx={{ marginBottom: '16px' }} /> @@ -451,15 +373,11 @@ export default function CustomEditor(props) { 'templatedQueryCheck': !formData.templatedQueryCheck, })) } - //() => { setTemplatedQueryCheck(!templatedQueryCheck) } } - - />} label="Templated Query" /> {formData.templatedQueryCheck && <div> - <TextField id="outlined-multiline-flexible" label="Variables for the templated query" @@ -472,7 +390,6 @@ export default function CustomEditor(props) { helperText={`Write the variables in JSON-format ${parsingErrorTemplate ? ' (Invalid Syntax)' : ''}`} value={!!formData.variables ? typeof formData.variables === 'object' ? JSON.stringify(formData.variables) : formData.variables : ''} placeholder={`{\n\tvariableOne : ["option1","option2","option3"],\n\tvariableTwo : ["option1","option2","option3"], \n\t...\n}`} - // value={formData.query} onChange={(e) => handleJSONparsing(e, setParsingErrorTemplate)} sx={{ marginBottom: '16px' }} /> @@ -483,17 +400,15 @@ export default function CustomEditor(props) { </CardContent> <CardActions> - <Button variant="contained" type="submit">Submit Query</Button> + <Button variant="contained" type="submit" startIcon={props.newQuery ?<IconProvider.AddIcon/>: <IconProvider.SaveAsIcon/>}>{props.newQuery ? 'Create Query' : 'Save Changes'}</Button> </CardActions> </Card> - </React.Fragment> ) } -// Temporary bindingstream - +// Temporary bindingstream this is if you want a check on the simple queries before submitting /* async function executeSPARQLQuery(query, dataSource, setShowError) { const resultingObjects = []; diff --git a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx index ba56b500..d5622fa0 100644 --- a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx @@ -1,7 +1,13 @@ import React, { useState, useEffect } from 'react'; import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; import { useLocation, useNavigate } from 'react-router-dom'; +import IconProvider from '../../../IconProvider/IconProvider'; import configManager from '../../../configManager/configManager'; @@ -9,32 +15,38 @@ import configManager from '../../../configManager/configManager'; export default function CustomQueryEditButton({ queryID }) { + const [open, setOpen] = useState(false); const customQuery = configManager.getQueryById(queryID); - // const sourcesString = customQuery.comunicaContext.sources.join(' ; '); + // const sourcesString = customQuery.comunicaContext.sources.join(' ; '); const navigate = useNavigate(); - // const location = useLocation(); + // const location = useLocation(); const handleEditClick = () => { navigate(`/${queryID}/editCustom`) } + const handleDelete = () => { + setOpen(false) + navigate(`/`) + configManager.deleteQueryById(queryID) + } + const handleSave = () => { - - const url = new URL( window.location.href); + + const url = new URL(window.location.href); const serverURL = `${url.protocol}//${url.hostname}${url.port ? ':' + url.port : ''}`; - + const savedUrl = `${serverURL}/#/customQuery?${customQuery.searchParams.toString()}` console.log(savedUrl) - } return ( <React.Fragment> - <Button variant="contained" onClick={ + <Button variant="contained" startIcon={<IconProvider.ModeEditIcon/>} onClick={ () => { handleEditClick() }} @@ -43,7 +55,7 @@ export default function CustomQueryEditButton({ queryID }) { </Button> - <Button variant="contained" onClick={ + <Button variant="outlined" color="success" startIcon={<IconProvider.SaveIcon/>} onClick={ () => { handleSave() }} @@ -51,20 +63,35 @@ export default function CustomQueryEditButton({ queryID }) { Save Query </Button> + <Button variant="outlined" color="error" startIcon={<IconProvider.DeleteIcon/>} onClick={ + () => { + setOpen(true) + }} + sx={{ margin: '10px' }}> + Delete Query + </Button> + + <Dialog + open={open} + onClose={() => { setOpen(false) }} + + > + <DialogTitle> + Delete custom query + </DialogTitle> + <DialogContent> + <DialogContentText > + Are you sure you want to delete this query? + </DialogContentText> + </DialogContent> + <DialogActions> + <Button variant="outlined" onClick={() => { setOpen(false) }}>Cancel</Button> + <Button variant="contained" color="error" onClick={handleDelete} autoFocus> + Delete + </Button> + </DialogActions> + </Dialog> + </React.Fragment> ) } - - -//Mock query -const updateQuery = (formData, customQuery) => { - const { query, source } = formData; - configManager.updateQuery({ - ...customQuery, - queryString: query, - comunicaContext: { - sources: source.split(';').map(src => src.trim()) - }, - // queryLocation: "components.rq" // Location for testing purposes, delete after it works with the querystring - }); -}; diff --git a/src/components/Dashboard/CustomQueryEditor/tableData.jsx b/src/components/Dashboard/CustomQueryEditor/tableData.jsx deleted file mode 100644 index 8c85d83e..00000000 --- a/src/components/Dashboard/CustomQueryEditor/tableData.jsx +++ /dev/null @@ -1,55 +0,0 @@ -import * as React from 'react'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableCell from '@mui/material/TableCell'; -import TableContainer from '@mui/material/TableContainer'; -import TableHead from '@mui/material/TableHead'; -import TableRow from '@mui/material/TableRow'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; - - -export default function TableData(data) { - - const keys = [] - if (data.data === null || data.data === undefined || data.data.length === 0) { - return ( - <Typography variant="body1" component="div" sx={{ margin: '10px' }}> - There are no results for this custom query. - </Typography> - ) - } - Object.keys(data.data[0]).forEach((k) => { keys.push(k) }) - return ( - <TableContainer sx={{ marginBottom: '20px', marginTop: '10px' }} component={Paper}> - {/* <Typography variant="h6" component="div" style={{ padding: '16px' }}> - {title} - </Typography> */} - <Table sx={{ minWidth: 650 }} aria-label="simple table"> - <TableHead> - <TableRow> - {keys.map((label) => { - return ( - <TableCell key={label} align="left">{label}</TableCell> - ) - })} - </TableRow> - </TableHead> - <TableBody> - {data.data.map((row, i) => ( - <TableRow - key={i} - > - {keys.map((cat) => ( - <TableCell key={cat + i} > - {row[cat]} - </TableCell> - ))} - </TableRow> - ) - )} - </TableBody> - </Table> - </TableContainer> - ); -} diff --git a/src/components/ListResultTable/QueryResultList/QueryResultList.jsx b/src/components/ListResultTable/QueryResultList/QueryResultList.jsx index 28a25a22..13a90db6 100644 --- a/src/components/ListResultTable/QueryResultList/QueryResultList.jsx +++ b/src/components/ListResultTable/QueryResultList/QueryResultList.jsx @@ -9,7 +9,7 @@ import SearchOffIcon from '@mui/icons-material/SearchOff'; import { SvgIcon, Box, Typography } from "@mui/material"; import PropTypes from "prop-types"; import CustomQueryEditButton from "../../Dashboard/CustomQueryEditor/customQueryEditButton"; - +import IconProvider from "../../../IconProvider/IconProvider"; import configManager from "../../../configManager/configManager"; /** @@ -17,9 +17,9 @@ import configManager from "../../../configManager/configManager"; * @returns {Component} custom ListViewer as defined by react-admin containing the results of the query with each variable its generic field. */ function QueryResultList(props) { - const { resource, changeVariables, submitted} = props; + const { resource, changeVariables, submitted } = props; const resourceDef = useResourceDefinition(); - + const queryTitle = resourceDef.options.label; const { data } = useListContext(props); const [values, setValues] = useState(undefined); @@ -35,27 +35,29 @@ function QueryResultList(props) { const query = configManager.getQueryWorkingCopyById(resource); return ( - <div style={{ paddingLeft: '20px' , paddingRight: '10px' }}> + <div style={{ paddingLeft: '20px', paddingRight: '10px' }}> <Title title={config.title} /> - {resourceDef.options.queryGroupId === 'cstm' && <CustomQueryEditButton queryID={resourceDef.name}/>} - {submitted && <Aside changeVariables={changeVariables}/> } + <div style={{ display: 'flex', flexDirection: 'row' }}> + {submitted && <Aside changeVariables={changeVariables} />} + {resourceDef.options.queryGroupId === 'cstm' && <CustomQueryEditButton queryID={resourceDef.name} />} + </div> <Typography fontSize={"2rem"} mt={2} > {queryTitle} </Typography> - {values ?( + {values ? ( <ListView title=" " actions={<ActionBar />} {...props} > - <Datagrid header={<TableHeader query={query}/>} bulkActionButtons={false}> - {Object.keys(values).map((key) => { - return ( - <GenericField - key={key} - source={key} - label={key.split("_")[0]} - /> - ); - })} - </Datagrid> - </ListView> - ): - <NoValuesDisplay/> + <Datagrid header={<TableHeader query={query} />} bulkActionButtons={false}> + {Object.keys(values).map((key) => { + return ( + <GenericField + key={key} + source={key} + label={key.split("_")[0]} + /> + ); + })} + </Datagrid> + </ListView> + ) : + <NoValuesDisplay /> } </div> ); @@ -86,23 +88,24 @@ function reduceDataToObject(data) { } const Aside = (props) => { - const {changeVariables} = props; - return( - <div> - <Button variant="contained" onClick={changeVariables}>Change Variables</Button> - </div> -)} + const { changeVariables } = props; + return ( + + <Button variant="contained" onClick={changeVariables} startIcon={<IconProvider.TuneIcon/>} sx={{ margin: '10px' }}>Change Variables</Button> + + ) +} const NoValuesDisplay = () => { - return( - <div> - <Box display="flex" alignItems="center" sx={{m:3}}> - <SvgIcon component={SearchOffIcon} /> - <span>The result list is empty.</span> - </Box> + return ( +<div> + <Box display="flex" alignItems="center" sx={{ m: 3 }}> + <SvgIcon component={SearchOffIcon} /> + <span>The result list is empty.</span> + </Box> </div> - + ) } - + export default QueryResultList; From dbf502973983473f9b01124386d9e972845d0a66 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Mon, 24 Jun 2024 08:58:56 +0200 Subject: [PATCH 20/47] UI updates --- .../customQueryEditButton.jsx | 63 ++++++++++--------- .../QueryResultList/QueryResultList.jsx | 3 +- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx index d5622fa0..bad3e3f3 100644 --- a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx @@ -1,18 +1,15 @@ import React, { useState, useEffect } from 'react'; import Button from '@mui/material/Button'; - import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; import DialogTitle from '@mui/material/DialogTitle'; -import { useLocation, useNavigate } from 'react-router-dom'; +import Box from '@mui/material/Box'; +import { useNavigate } from 'react-router-dom'; import IconProvider from '../../../IconProvider/IconProvider'; - import configManager from '../../../configManager/configManager'; - - export default function CustomQueryEditButton({ queryID }) { const [open, setOpen] = useState(false); @@ -35,7 +32,6 @@ export default function CustomQueryEditButton({ queryID }) { const handleSave = () => { - const url = new URL(window.location.href); const serverURL = `${url.protocol}//${url.hostname}${url.port ? ':' + url.port : ''}`; @@ -46,30 +42,37 @@ export default function CustomQueryEditButton({ queryID }) { return ( <React.Fragment> - <Button variant="contained" startIcon={<IconProvider.ModeEditIcon/>} onClick={ - () => { - handleEditClick() - }} - sx={{ margin: '10px' }}> - Edit Query - - </Button> - - <Button variant="outlined" color="success" startIcon={<IconProvider.SaveIcon/>} onClick={ - () => { - handleSave() - }} - sx={{ margin: '10px' }}> - Save Query - </Button> - - <Button variant="outlined" color="error" startIcon={<IconProvider.DeleteIcon/>} onClick={ - () => { - setOpen(true) - }} - sx={{ margin: '10px' }}> - Delete Query - </Button> + <Box display="flex" justifyContent="space-between" width="80%" > + <Box> + <Button variant="contained" startIcon={<IconProvider.ModeEditIcon />} onClick={ + () => { + handleEditClick() + }} + sx={{ margin: '10px' }}> + Edit Query + </Button> + </Box> + + {/* // TODO NOW PROVIDE AN OPTION TO SAVE BY COPYING TO CLIP BOARD !! */} + <Box> + + <Button variant="outlined" color="success" startIcon={<IconProvider.SaveIcon />} onClick={ + () => { + handleSave() + }} + sx={{ margin: '10px' }}> + Save Query + </Button> + + <Button variant="outlined" color="error" startIcon={<IconProvider.DeleteIcon />} onClick={ + () => { + setOpen(true) + }} + sx={{ margin: '10px' }}> + Delete Query + </Button> + </Box> + </Box> <Dialog open={open} diff --git a/src/components/ListResultTable/QueryResultList/QueryResultList.jsx b/src/components/ListResultTable/QueryResultList/QueryResultList.jsx index 13a90db6..deac4181 100644 --- a/src/components/ListResultTable/QueryResultList/QueryResultList.jsx +++ b/src/components/ListResultTable/QueryResultList/QueryResultList.jsx @@ -90,8 +90,9 @@ function reduceDataToObject(data) { const Aside = (props) => { const { changeVariables } = props; return ( - + <Box> <Button variant="contained" onClick={changeVariables} startIcon={<IconProvider.TuneIcon/>} sx={{ margin: '10px' }}>Change Variables</Button> + </Box> ) } From 6830fa83c7b4d4fa89cb8e85fbd393aeb086d218 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Mon, 24 Jun 2024 11:07:40 +0200 Subject: [PATCH 21/47] Can now also save queries --- .../CustomQueryEditor/customEditor.jsx | 1 - .../customQueryEditButton.jsx | 88 +++++++++++++++---- .../QueryResultList/QueryResultList.jsx | 2 +- 3 files changed, 72 insertions(+), 19 deletions(-) diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index 95c3a3c4..61c56ea8 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -70,7 +70,6 @@ export default function CustomEditor(props) { addQuery(jsonData); } else { - console.log(jsonData) const customQuery = configManager.getQueryById(props.id); updateQuery(jsonData, customQuery); } diff --git a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx index bad3e3f3..7409d4f3 100644 --- a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx @@ -10,22 +10,26 @@ import { useNavigate } from 'react-router-dom'; import IconProvider from '../../../IconProvider/IconProvider'; import configManager from '../../../configManager/configManager'; -export default function CustomQueryEditButton({ queryID }) { +import TextField from '@mui/material/TextField'; - const [open, setOpen] = useState(false); +export default function CustomQueryEditButton({ queryID, submitted }) { const customQuery = configManager.getQueryById(queryID); - // const sourcesString = customQuery.comunicaContext.sources.join(' ; '); - const navigate = useNavigate(); - // const location = useLocation(); + + const [deleteOpen, setDeleteOpen] = useState(false); + const [saveOpen, setSaveOpen] = useState(false); + + const [copyURL, setCopyUrl] = useState(''); + const [feedback, setFeedback] = useState(''); + const handleEditClick = () => { navigate(`/${queryID}/editCustom`) } const handleDelete = () => { - setOpen(false) + setDeleteOpen(false) navigate(`/`) configManager.deleteQueryById(queryID) } @@ -35,14 +39,28 @@ export default function CustomQueryEditButton({ queryID }) { const url = new URL(window.location.href); const serverURL = `${url.protocol}//${url.hostname}${url.port ? ':' + url.port : ''}`; - const savedUrl = `${serverURL}/#/customQuery?${customQuery.searchParams.toString()}` - console.log(savedUrl) + const savedUrl = `${serverURL}/#/customQuery?${customQuery.searchParams.toString()}`; + setCopyUrl(savedUrl); + } + const handleSaveClose = () => { + setSaveOpen(false) + setFeedback('') } + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(copyURL); + setFeedback('Text successfully copied to clipboard!'); + } catch (err) { + console.error('Failed to copy text: ', err); + setFeedback('Failed to copy text.'); + } + }; + return ( <React.Fragment> - <Box display="flex" justifyContent="space-between" width="80%" > + <Box display="flex" justifyContent="space-between" width={submitted ? '80%' : '100%'} > <Box> <Button variant="contained" startIcon={<IconProvider.ModeEditIcon />} onClick={ () => { @@ -53,12 +71,11 @@ export default function CustomQueryEditButton({ queryID }) { </Button> </Box> - {/* // TODO NOW PROVIDE AN OPTION TO SAVE BY COPYING TO CLIP BOARD !! */} <Box> - <Button variant="outlined" color="success" startIcon={<IconProvider.SaveIcon />} onClick={ () => { handleSave() + setSaveOpen(true) }} sx={{ margin: '10px' }}> Save Query @@ -66,7 +83,7 @@ export default function CustomQueryEditButton({ queryID }) { <Button variant="outlined" color="error" startIcon={<IconProvider.DeleteIcon />} onClick={ () => { - setOpen(true) + setDeleteOpen(true) }} sx={{ margin: '10px' }}> Delete Query @@ -75,9 +92,8 @@ export default function CustomQueryEditButton({ queryID }) { </Box> <Dialog - open={open} - onClose={() => { setOpen(false) }} - + open={deleteOpen} + onClose={() => { setDeleteOpen(false) }} > <DialogTitle> Delete custom query @@ -88,13 +104,51 @@ export default function CustomQueryEditButton({ queryID }) { </DialogContentText> </DialogContent> <DialogActions> - <Button variant="outlined" onClick={() => { setOpen(false) }}>Cancel</Button> + <Button variant="text" onClick={() => { setDeleteOpen(false) }}>Cancel</Button> <Button variant="contained" color="error" onClick={handleDelete} autoFocus> Delete </Button> </DialogActions> </Dialog> + <Dialog + open={saveOpen} + onClose={() => { setSaveOpen(false) }} + + > + <DialogTitle> + Save custom query + </DialogTitle> + + <DialogContent> + <DialogContentText > + Copy the url to save the query. This is the link to the full creation form. + </DialogContentText> + + <DialogContentText style={{ color: feedback.includes('successfully') ? 'green' : 'red' }} > + {feedback} + </DialogContentText> + + <Box width={500}> + <TextField + label='Query URL' + fullWidth + multiline + minRows={5} + value={copyURL} + variant="filled" + /> + </Box> + </DialogContent> + + <DialogActions > + <Button variant="text" onClick={handleSaveClose}>Cancel</Button> + <Button variant="contained" onClick={handleCopy} autoFocus startIcon={<IconProvider.ContentCopyIcon />}> + Copy + </Button> + </DialogActions> + </Dialog> + </React.Fragment> ) -} +} \ No newline at end of file diff --git a/src/components/ListResultTable/QueryResultList/QueryResultList.jsx b/src/components/ListResultTable/QueryResultList/QueryResultList.jsx index deac4181..f483a17e 100644 --- a/src/components/ListResultTable/QueryResultList/QueryResultList.jsx +++ b/src/components/ListResultTable/QueryResultList/QueryResultList.jsx @@ -39,7 +39,7 @@ function QueryResultList(props) { <Title title={config.title} /> <div style={{ display: 'flex', flexDirection: 'row' }}> {submitted && <Aside changeVariables={changeVariables} />} - {resourceDef.options.queryGroupId === 'cstm' && <CustomQueryEditButton queryID={resourceDef.name} />} + {resourceDef.options.queryGroupId === 'cstm' && <CustomQueryEditButton queryID={resourceDef.name} submitted={submitted} />} </div> <Typography fontSize={"2rem"} mt={2} > {queryTitle} </Typography> {values ? ( From fce29f21ec47588f94e221f7ef2ba60c61c56e3e Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Mon, 24 Jun 2024 11:23:52 +0200 Subject: [PATCH 22/47] small UI update --- .../Dashboard/CustomQueryEditor/customEditor.jsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index 61c56ea8..26b7aff2 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -163,14 +163,15 @@ export default function CustomEditor(props) { sx={{ padding: '16px', marginTop: '16px', width: '100%' }} > <CardContent> - <Typography variant="h6">{props.newQuery ? 'Custom Query Editor' : 'Edit'}</Typography> + <Typography variant="h5" sx={{ fontWeight: 'bold' }}>{props.newQuery ? 'Custom Query Editor' : 'Edit'}</Typography> {showError && ( <Typography variant="body2" sx={{ color: 'red', mb: '10px' }}> Invalid Query. Check the URL and Query Syntax </Typography> )} - <Card sx={{ px: '10px', my: 2 }}> + <Card sx={{ py: '10px', px: '20px' , my: 2 }}> + <Typography variant="h5" sx={{ mt: 2 }}> Basic Information</Typography> <div> <TextField required @@ -205,7 +206,7 @@ export default function CustomEditor(props) { <TextField required id="outlined-multiline-flexible" - label="Custom Query" + label="SPARQL Query Text" name="queryString" multiline fullWidth @@ -220,7 +221,7 @@ export default function CustomEditor(props) { </div> </Card> - <Card sx={{ px: '10px', my: 2 }}> + <Card sx={{ py: '10px', px: '20px' , my: 2 }}> <Typography variant="h5" sx={{ mt: 2 }}> Comunica Context</Typography> <div> @@ -277,8 +278,8 @@ export default function CustomEditor(props) { } </Card> - <Card sx={{ px: '10px', my: 2 }}> - + <Card sx={{ py: '10px', px: '20px' , my: 2 }}> + <Typography variant="h5" sx={{ mt: 2 }}> Extra Options</Typography> <div> <FormControlLabel control={<Checkbox From b1a4079e426b7eb0965a1d32fa5a146093943cb0 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Mon, 24 Jun 2024 15:07:44 +0200 Subject: [PATCH 23/47] small update --- .../Dashboard/CustomQueryEditor/customQueryEditButton.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx index 7409d4f3..2c6e08b9 100644 --- a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx @@ -132,6 +132,7 @@ export default function CustomQueryEditButton({ queryID, submitted }) { <Box width={500}> <TextField label='Query URL' + name='queryURL' fullWidth multiline minRows={5} From f088ba28a1399c6e576657c2d5baf4ed047bd48c Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Mon, 24 Jun 2024 15:08:07 +0200 Subject: [PATCH 24/47] Added e2e tests for the custom edittor --- cypress/e2e/custom-query-editor.cy.js | 184 ++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 cypress/e2e/custom-query-editor.cy.js diff --git a/cypress/e2e/custom-query-editor.cy.js b/cypress/e2e/custom-query-editor.cy.js new file mode 100644 index 00000000..12a186d0 --- /dev/null +++ b/cypress/e2e/custom-query-editor.cy.js @@ -0,0 +1,184 @@ + +describe("Simple Custom Query Editor tests", () => { + + it("Create a new query and successfully create the URLparams", () => { + + cy.visit("/#/customQuery"); + + cy.get('input[name="name"]').type("new query"); + cy.get('textarea[name="description"]').type("new description"); + cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> + +SELECT * WHERE { + ?list schema:name ?listTitle; + schema:itemListElement [ + schema:name ?bookTitle; + schema:creator [ + schema:name ?authorName + ] + ]. +}`); + cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list"); + cy.get('button[type="submit"]').click(); + + // Search Params were correctly created + cy.url().should('include', '?name=new+query&description=new+description&queryString=PREFIX+schema%3A+%3Chttp%3A%2F%2Fschema.org%2F%3E+%0A%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') + + cy.contains("Custom queries").click(); + cy.contains("new query").click(); + + // Checking if the book query works + cy.contains("Colleen Hoover").should('exist'); + }); + + it("Create a new query, with multiple sources", () => { + + cy.visit("/#/customQuery"); + + cy.get('input[name="name"]').type("material query"); + cy.get('textarea[name="description"]').type("this query has 3 sources"); + cy.get('textarea[name="queryString"]').type(`# Query Texon's components +# Datasources: https://css5.onto-deside.ilabt.imec.be/texon/data/dt/out/components.ttl + +PREFIX oo: <http://purl.org/openorg/> +PREFIX ao: <http://purl.org/ontology/ao/core#> +PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> +PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> +PREFIX d: <http://www/example.com/data/> +PREFIX o: <http://www/example.com/ont/> + +SELECT DISTINCT ?component ?componentName ?recycledContentPercentage +WHERE { + ?component + a o:Component ; + o:name ?componentName ; + o:recycled-content-percentage ?recycledContentPercentage ; + . +} +ORDER BY ?componentName +`); + 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"); + cy.get('button[type="submit"]').click(); + + cy.contains("Custom queries").click(); + cy.contains("material query").click(); + + // Checking if the query works + cy.contains("http://www/example.com/data/component-c01").should('exist'); + }); + + it("Check if all possible parameters are filled in with parameterized URL", () => { + + // Navigate to the URL of a saved query with completely filled-in form + 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&variables=%7B%22firstvariables%22%3A%5B%22only+one%22%5D%7D") + + // Verify that every field is correctly filled-in + cy.get('input[name="name"]').should('have.value', 'Query Name'); + cy.get('textarea[name="description"]').should('have.value','Query Description'); + cy.get('textarea[name="queryString"]').should('have.value','Sparql query text'); + + cy.get('input[name="source"]').should('have.value',"The Comunica Source"); + cy.get('textarea[name="comunicaContext"]').should('have.value',`{"Advanced Comunica Context":true}`); + + cy.get('input[name="indexSourceUrl"]').should('have.value',"Index Source"); + cy.get('textarea[name="indexSourceQuery"]').should('have.value',"Index Query "); + + cy.get('textarea[name="askQuery"]').should('have.value',`{"trueText":" filled in","falseText":"not filled in"}`); + + cy.get('textarea[name="variables"]').should('have.value',`{"firstvariables":["only one"]}`); + + }) + + it( "Successfully edit a query to make it work" , () => { + + cy.visit("/#/customQuery"); + + // First create a wrong query + cy.get('input[name="name"]').type("broken query"); + cy.get('textarea[name="description"]').type("just a description"); + + cy.get('textarea[name="queryString"]').type("this is faultive querytext") + + cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list"); + + //Submit the faultive query + cy.get('button[type="submit"]').click(); + + cy.contains("Custom queries").click(); + cy.contains("broken query").click(); + + // Verify that there are no results + cy.contains("The result list is empty.").should('exist'); + + // Edit the query + cy.get('button').contains("Edit Query").click(); + + // Give the query a new name and a correct query text + cy.get('input[name="name"]').clear(); + cy.get('input[name="name"]').type("Fixed query"); + + cy.get('textarea[name="queryString"]').clear(); + cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> + + SELECT * WHERE { + ?list schema:name ?listTitle; + schema:itemListElement [ + schema:name ?bookTitle; + schema:creator [ + schema:name ?authorName + ] + ]. + }`); + + // Submit the correct query + cy.get('button[type="submit"]').click(); + + // Now we should be on the page of the fixed query + cy.contains("Fixed query").should('exist'); + + // Check if the resulting list appears + cy.contains("Colleen Hoover").should('exist'); + + }) + + it("Saves the correct URL", ()=>{ + + cy.visit("/#/customQuery"); + + // First create a simple query + cy.get('input[name="name"]').type("new query"); + cy.get('textarea[name="description"]').type("new description"); + cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> + +SELECT * WHERE { + ?list schema:name ?listTitle; + schema:itemListElement [ + schema:name ?bookTitle; + schema:creator [ + schema:name ?authorName + ] + ]. +}`); + cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list"); + cy.get('button[type="submit"]').click(); + + // Search Params were correctly created + cy.url().should('include', '?name=new+query&description=new+description&queryString=PREFIX+schema%3A+%3Chttp%3A%2F%2Fschema.org%2F%3E+%0A%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') + + // Remember the URL + cy.url().then((url) => { + cy.wrap(url).as('createdUrl'); + }); + + // Now go to the query and save it + cy.contains("Custom queries").click(); + cy.contains("new query").click(); + cy.get('button').contains("Save Query").click(); + + // The given URL is correct + cy.get('@createdUrl').then((createdUrl) => { + cy.get('textarea[name="queryURL"]').should('have.value', createdUrl); + }); + }) + +}) \ No newline at end of file From b4a0fdd5a8e1beff68713eebf18b9d0288312af7 Mon Sep 17 00:00:00 2001 From: Martin Vanbrabant <martin.vanbrabant@ugent.be> Date: Tue, 25 Jun 2024 08:27:48 +0200 Subject: [PATCH 25/47] Some text updates --- .../CustomQueryEditor/customEditor.jsx | 36 +++++++++---------- .../customQueryEditButton.jsx | 8 ++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index 26b7aff2..7db46689 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -180,7 +180,7 @@ export default function CustomEditor(props) { id="outlined-required" label="Query name" placeholder="Custom query name" - helperText="Give this custom query a name" + helperText="Give this custom query a name." variant="outlined" value={!!formData.name ? formData.name : ''} onChange={handleChange} @@ -196,7 +196,7 @@ export default function CustomEditor(props) { fullWidth minRows={2} variant="outlined" - helperText="Give a description for the query" + helperText="Give a description for the query." placeholder="This is a custom query." value={!!formData.description ? formData.description : ''} onChange={handleChange} @@ -206,13 +206,13 @@ export default function CustomEditor(props) { <TextField required id="outlined-multiline-flexible" - label="SPARQL Query Text" + label="SPARQL query" name="queryString" multiline fullWidth minRows={5} variant="outlined" - helperText="Give the SPARQL query" + helperText="Enter your SPARQL query here." placeholder={`SELECT ?s ?p ?o \nWHERE { \n\t?s ?p ?o \n}`} value={!!formData.queryString ? formData.queryString : ''} onChange={handleChange} @@ -246,9 +246,9 @@ export default function CustomEditor(props) { fullWidth name="source" id="outlined-required" - label="Data Source" + label="Data source(s)" placeholder="http://examplesource.org ; source2" - helperText="Give the source Url(s) for the query. You can add more than one source separated with ' ; '" + helperText="Give the source URL(s) for the query. Separate URLs with with ';'." variant="outlined" value={!!formData.source ? formData.source : ''} onChange={handleChange} @@ -261,14 +261,14 @@ export default function CustomEditor(props) { <TextField required={ensureBoolean(formData.comunicaContextCheck)} id="outlined-multiline-flexible" - label="Comunica Context Configuration" + label="Comunica context configuration" name="comunicaContext" multiline fullWidth error={parsingErrorComunica} minRows={5} variant="outlined" - helperText={`Write the extra configurations in JSON-format ${parsingErrorComunica ? ' (Invalid Syntax)' : ''}`} + helperText={`Write the extra configurations in JSON-format${parsingErrorComunica ? ' (Invalid Syntax)' : '.'}`} value={!!formData.comunicaContext ? typeof formData.comunicaContext === 'object' ? JSON.stringify(formData.comunicaContext) : formData.comunicaContext : ''} placeholder={`{\n\t"lenient" : true,\n\t"other" : "some other options"\n}`} onChange={(e) => handleJSONparsing(e, setParsingErrorComunica)} @@ -293,7 +293,7 @@ export default function CustomEditor(props) { })) } } - />} label="Source from index file" /> + />} label="Sources from index file" /> {formData.sourceIndexCheck && <div> @@ -302,9 +302,9 @@ export default function CustomEditor(props) { fullWidth name="indexSourceUrl" id="outlined-required" - label="Index File url" + label="Index file URL" placeholder="http://examplesource.org ; source2" - helperText="Give the index file to use as IndexSource." + helperText="Give the URL of the index file." variant="outlined" value={!!formData.indexSourceUrl ? formData.indexSourceUrl : ''} onChange={handleChange} @@ -314,13 +314,13 @@ export default function CustomEditor(props) { <TextField required={ensureBoolean(formData.sourceIndexCheck)} id="outlined-multiline-flexible" - label="Query to get the source from index file" + label="SPARQL query" name="indexSourceQuery" multiline fullWidth minRows={5} variant="outlined" - helperText="Give the SPARQL query to retrieve the sources" + helperText="Give the SPARQL query to get the sources from the index file." placeholder={`SELECT ?s ?p ?o \nWHERE { \n\t?s ?p ?o \n}`} value={!!formData.indexSourceQuery ? formData.indexSourceQuery : ''} onChange={handleChange} @@ -341,7 +341,7 @@ export default function CustomEditor(props) { })) } } - />} label="add an askQuery" /> + />} label="ASK query" /> {formData.askQueryCheck && <div> @@ -354,7 +354,7 @@ export default function CustomEditor(props) { fullWidth minRows={5} variant="outlined" - helperText={`Write contents of the askQuery in JSON-format ${parsingErrorAsk ? ' (Invalid Syntax)' : ''}`} + helperText={`Write askQuery details in JSON-format${parsingErrorAsk ? ' (Invalid Syntax)' : '.'}`} value={!!formData.askQuery ? typeof formData.askQuery === 'object' ? JSON.stringify(formData.askQuery) : formData.askQuery : `{\n\t"trueText" : " ",\n\t"falseText" : " " \n}`} placeholder={`{\n\t"trueText" : "this displays when true.",\n\t"falseText" : "this displays when false." \n}`} onChange={(e) => handleJSONparsing(e, setParsingErrorAsk)} @@ -374,20 +374,20 @@ export default function CustomEditor(props) { })) } } - />} label="Templated Query" /> + />} label="Templated query" /> {formData.templatedQueryCheck && <div> <TextField id="outlined-multiline-flexible" - label="Variables for the templated query" + label="Variables specification" name="variables" error={parsingErrorTemplate} multiline fullWidth minRows={5} variant="outlined" - helperText={`Write the variables in JSON-format ${parsingErrorTemplate ? ' (Invalid Syntax)' : ''}`} + helperText={`Write the variables specification in JSON-format${parsingErrorTemplate ? ' (Invalid Syntax)' : '.'}`} value={!!formData.variables ? typeof formData.variables === 'object' ? JSON.stringify(formData.variables) : formData.variables : ''} placeholder={`{\n\tvariableOne : ["option1","option2","option3"],\n\tvariableTwo : ["option1","option2","option3"], \n\t...\n}`} onChange={(e) => handleJSONparsing(e, setParsingErrorTemplate)} diff --git a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx index 2c6e08b9..9b2b0951 100644 --- a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx @@ -78,7 +78,7 @@ export default function CustomQueryEditButton({ queryID, submitted }) { setSaveOpen(true) }} sx={{ margin: '10px' }}> - Save Query + Save Query Link </Button> <Button variant="outlined" color="error" startIcon={<IconProvider.DeleteIcon />} onClick={ @@ -117,12 +117,12 @@ export default function CustomQueryEditButton({ queryID, submitted }) { > <DialogTitle> - Save custom query + Save custom query link </DialogTitle> <DialogContent> <DialogContentText > - Copy the url to save the query. This is the link to the full creation form. + Use this link ro recreate this custom query later. </DialogContentText> <DialogContentText style={{ color: feedback.includes('successfully') ? 'green' : 'red' }} > @@ -145,7 +145,7 @@ export default function CustomQueryEditButton({ queryID, submitted }) { <DialogActions > <Button variant="text" onClick={handleSaveClose}>Cancel</Button> <Button variant="contained" onClick={handleCopy} autoFocus startIcon={<IconProvider.ContentCopyIcon />}> - Copy + Copy to clipboard </Button> </DialogActions> </Dialog> From a5a5e7b9cad29cc1ade87540660e2ddd72d45fa4 Mon Sep 17 00:00:00 2001 From: Martin Vanbrabant <martin.vanbrabant@ugent.be> Date: Tue, 25 Jun 2024 08:36:31 +0200 Subject: [PATCH 26/47] Another text mod --- src/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.json b/src/config.json index 088c6d2e..6aaece59 100644 --- a/src/config.json +++ b/src/config.json @@ -11,7 +11,7 @@ "showMilliseconds": false, "defaultIDP": "http://localhost:8080", "footer": "<p><a href='https://idlab.technology/'>IDLab</a> - <a href='https://www.imec.be/nl'>imec</a> - <a href='https://www.ugent.be/'>UGent</a></p>", - "introductionText": "Please select a query from the menu on the left.", + "introductionText": "Please log in as the appropriate actor and make your choice in the menu on the left.", "queryGroups" : [ { "id": "a-ex", From 9855a4be0cbb337d2e25b25f30b44babd85de383 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Tue, 25 Jun 2024 09:10:49 +0200 Subject: [PATCH 27/47] extra user experience --- cypress/e2e/custom-query-editor.cy.js | 4 + .../CustomQueryEditor/customEditor.jsx | 83 +++++++++++-------- .../customQueryEditButton.jsx | 2 +- .../ListResultTable/TemplatedQueryForm.jsx | 7 +- 4 files changed, 57 insertions(+), 39 deletions(-) diff --git a/cypress/e2e/custom-query-editor.cy.js b/cypress/e2e/custom-query-editor.cy.js index 12a186d0..6e2a1f51 100644 --- a/cypress/e2e/custom-query-editor.cy.js +++ b/cypress/e2e/custom-query-editor.cy.js @@ -181,4 +181,8 @@ SELECT * WHERE { }); }) + // NOG EEN TEMPLATED TEST + + + // NOG EEN INDEX FILE TEST }) \ No newline at end of file diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index 26b7aff2..ae59987c 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -33,6 +33,9 @@ export default function CustomEditor(props) { const [showError, setShowError] = useState(false); + + // ADD A CONTROLE TO NOT SUBMIT WHEN PARSING ERRORS!!!!!!!!!!!!!!!!!!!!! + const [parsingErrorComunica, setParsingErrorComunica] = useState(false); const [parsingErrorAsk, setParsingErrorAsk] = useState(false); const [parsingErrorTemplate, setParsingErrorTemplate] = useState(false); @@ -40,38 +43,44 @@ export default function CustomEditor(props) { useEffect(() => { let searchParams if (props.newQuery) { - searchParams = new URLSearchParams(location.search); - } else{ + searchParams = new URLSearchParams(location.search); + } else { const edittingQuery = configManager.getQueryById(props.id); searchParams = edittingQuery.searchParams; } - const obj = {} - searchParams.forEach((value, key) => { - obj[key] = value - }) - setFormData(obj) + const obj = {} + searchParams.forEach((value, key) => { + obj[key] = value + }) + setFormData(obj) }, [location.search]); const handleSubmit = async (event) => { event.preventDefault(); - const formData = new FormData(event.currentTarget); - const jsonData = Object.fromEntries(formData.entries()); - const searchParams = new URLSearchParams(jsonData); - jsonData.searchParams = searchParams; + if (!parsingErrorComunica && !parsingErrorAsk && !parsingErrorTemplate) { + setShowError(false) + const formData = new FormData(event.currentTarget); + const jsonData = Object.fromEntries(formData.entries()); - if (props.newQuery) { - navigate({ search: searchParams.toString() }); + const searchParams = new URLSearchParams(jsonData); + jsonData.searchParams = searchParams; - // TODO: NEED A CHECK HERE TO SEE IF WE MAY SUBMIT (correct query) - // const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError); + if (props.newQuery) { + navigate({ search: searchParams.toString() }); - configManager.addNewQueryGroup('cstm', 'Custom queries', 'EditNoteIcon'); - addQuery(jsonData); - } - else { - const customQuery = configManager.getQueryById(props.id); - updateQuery(jsonData, customQuery); + // TODO: NEED A CHECK HERE TO SEE IF WE MAY SUBMIT (correct query) + // const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError); + + configManager.addNewQueryGroup('cstm', 'Custom queries', 'EditNoteIcon'); + addQuery(jsonData); + } + else { + const customQuery = configManager.getQueryById(props.id); + updateQuery(jsonData, customQuery); + } + }else{ + setShowError(true) } }; @@ -151,7 +160,7 @@ export default function CustomEditor(props) { ...formData }); - navigate(`/${customQuery.id}`) + navigate(`/${customQuery.id}`) }; @@ -164,14 +173,9 @@ export default function CustomEditor(props) { > <CardContent> <Typography variant="h5" sx={{ fontWeight: 'bold' }}>{props.newQuery ? 'Custom Query Editor' : 'Edit'}</Typography> - {showError && ( - <Typography variant="body2" sx={{ color: 'red', mb: '10px' }}> - Invalid Query. Check the URL and Query Syntax - </Typography> - )} - - <Card sx={{ py: '10px', px: '20px' , my: 2 }}> - <Typography variant="h5" sx={{ mt: 2 }}> Basic Information</Typography> + + <Card sx={{ py: '10px', px: '20px', my: 2 }}> + <Typography variant="h5" sx={{ mt: 2 }}> Basic Information</Typography> <div> <TextField required @@ -204,7 +208,7 @@ export default function CustomEditor(props) { /> <TextField - required + required id="outlined-multiline-flexible" label="SPARQL Query Text" name="queryString" @@ -221,7 +225,7 @@ export default function CustomEditor(props) { </div> </Card> - <Card sx={{ py: '10px', px: '20px' , my: 2 }}> + <Card sx={{ py: '10px', px: '20px', my: 2 }}> <Typography variant="h5" sx={{ mt: 2 }}> Comunica Context</Typography> <div> @@ -231,6 +235,7 @@ export default function CustomEditor(props) { checked={!!formData.comunicaContextCheck} onChange={ () => { + setParsingErrorComunica(false); setFormData((prevFormData) => ({ ...prevFormData, 'comunicaContextCheck': !formData.comunicaContextCheck, @@ -278,8 +283,8 @@ export default function CustomEditor(props) { } </Card> - <Card sx={{ py: '10px', px: '20px' , my: 2 }}> - <Typography variant="h5" sx={{ mt: 2 }}> Extra Options</Typography> + <Card sx={{ py: '10px', px: '20px', my: 2 }}> + <Typography variant="h5" sx={{ mt: 2 }}> Extra Options</Typography> <div> <FormControlLabel control={<Checkbox @@ -335,6 +340,7 @@ export default function CustomEditor(props) { checked={!!formData.askQueryCheck} onChange={ () => { + setParsingErrorAsk(false); setFormData((prevFormData) => ({ ...prevFormData, 'askQueryCheck': !formData.askQueryCheck, @@ -368,6 +374,7 @@ export default function CustomEditor(props) { checked={!!formData.templatedQueryCheck} onChange={ () => { + setParsingErrorTemplate(false); setFormData((prevFormData) => ({ ...prevFormData, 'templatedQueryCheck': !formData.templatedQueryCheck, @@ -398,9 +405,13 @@ export default function CustomEditor(props) { </div> </Card> </CardContent> - + {showError && ( + <Typography variant="body2" sx={{ color: 'red', mb: '10px' }}> + Invalid Query. Check the JSON-Syntax + </Typography> + )} <CardActions> - <Button variant="contained" type="submit" startIcon={props.newQuery ?<IconProvider.AddIcon/>: <IconProvider.SaveAsIcon/>}>{props.newQuery ? 'Create Query' : 'Save Changes'}</Button> + <Button variant="contained" type="submit" startIcon={props.newQuery ? <IconProvider.AddIcon /> : <IconProvider.SaveAsIcon />}>{props.newQuery ? 'Create Query' : 'Save Changes'}</Button> </CardActions> </Card> diff --git a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx index 2c6e08b9..a8701ed9 100644 --- a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx @@ -12,7 +12,7 @@ import configManager from '../../../configManager/configManager'; import TextField from '@mui/material/TextField'; -export default function CustomQueryEditButton({ queryID, submitted }) { +export default function CustomQueryEditButton({ queryID, submitted=false }) { const customQuery = configManager.getQueryById(queryID); const navigate = useNavigate(); diff --git a/src/components/ListResultTable/TemplatedQueryForm.jsx b/src/components/ListResultTable/TemplatedQueryForm.jsx index aff4b8b1..67b0e39d 100644 --- a/src/components/ListResultTable/TemplatedQueryForm.jsx +++ b/src/components/ListResultTable/TemplatedQueryForm.jsx @@ -1,7 +1,8 @@ -import {Toolbar, SaveButton, SelectInput, SimpleForm, required} from "react-admin"; +import {Toolbar, SaveButton, SelectInput, SimpleForm, required, useResourceDefinition} from "react-admin"; import DoneIcon from '@mui/icons-material/Done'; import {Component, useEffect} from "react"; import PropTypes from "prop-types"; +import CustomQueryEditButton from "../Dashboard/CustomQueryEditor/customQueryEditButton"; const MyToolbar = () => ( <Toolbar> @@ -22,15 +23,17 @@ const TemplatedQueryForm = (props) => { searchPar, } = props; + const resourceDef = useResourceDefinition(); + useEffect(() => { if (submitted){ onSubmit(searchPar); } }, [submitted]) - return ( <SimpleForm toolbar={<MyToolbar />} onSubmit={onSubmit}> + {resourceDef.options.queryGroupId === 'cstm' && <CustomQueryEditButton queryID={resourceDef.name}/>} {Object.entries(variableOptions).map(([name, options]) => ( <SelectInput key={name} source={name} name={name} label={name} validate={required()} choices={ options.map((option) => ({ From 655c00c08e3c4e8ffc9d6947537a06911f4ecf0e Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Tue, 25 Jun 2024 09:29:32 +0200 Subject: [PATCH 28/47] added a check, to prevent wrong inputs --- src/components/Dashboard/CustomQueryEditor/customEditor.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index 8658e5b6..c5fb75c2 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -352,6 +352,7 @@ export default function CustomEditor(props) { {formData.askQueryCheck && <div> <TextField + required={ensureBoolean(formData.askQueryCheck)} id="outlined-multiline-flexible" label="Creating an ask query" name="askQuery" @@ -386,6 +387,7 @@ export default function CustomEditor(props) { {formData.templatedQueryCheck && <div> <TextField + required={ensureBoolean(formData.templatedQueryCheck)} id="outlined-multiline-flexible" label="Variables specification" name="variables" From f13b9fdd97536ed66caf0115e659402efece0f05 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Tue, 25 Jun 2024 09:29:43 +0200 Subject: [PATCH 29/47] updated the buttons UI --- .../CustomQueryEditor/customQueryEditButton.jsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx index 90f26c75..88037f05 100644 --- a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx @@ -60,18 +60,16 @@ export default function CustomQueryEditButton({ queryID, submitted=false }) { return ( <React.Fragment> - <Box display="flex" justifyContent="space-between" width={submitted ? '80%' : '100%'} > - <Box> - <Button variant="contained" startIcon={<IconProvider.ModeEditIcon />} onClick={ + <Box display="flex" justifyContent="flex-end" width={submitted ? '80%' : '100%'} > + + <Button variant="outlined" startIcon={<IconProvider.ModeEditIcon />} onClick={ () => { handleEditClick() }} sx={{ margin: '10px' }}> Edit Query </Button> - </Box> - - <Box> + <Button variant="outlined" color="success" startIcon={<IconProvider.SaveIcon />} onClick={ () => { handleSave() @@ -88,7 +86,7 @@ export default function CustomQueryEditButton({ queryID, submitted=false }) { sx={{ margin: '10px' }}> Delete Query </Button> - </Box> + </Box> <Dialog From a86c981465f6ca31c618593357c83d5417b5900c Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Tue, 25 Jun 2024 12:55:10 +0200 Subject: [PATCH 30/47] added navigate on creation --- .../Dashboard/CustomQueryEditor/customEditor.jsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index c5fb75c2..ded1c4a5 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -33,9 +33,6 @@ export default function CustomEditor(props) { const [showError, setShowError] = useState(false); - - // ADD A CONTROLE TO NOT SUBMIT WHEN PARSING ERRORS!!!!!!!!!!!!!!!!!!!!! - const [parsingErrorComunica, setParsingErrorComunica] = useState(false); const [parsingErrorAsk, setParsingErrorAsk] = useState(false); const [parsingErrorTemplate, setParsingErrorTemplate] = useState(false); @@ -144,13 +141,15 @@ export default function CustomEditor(props) { } const addQuery = (formData) => { + const creationID = Date.now().toString(); formData = parseAllObjectsToJSON(formData); configManager.addQuery({ ...formData, - id: Date.now().toString(), + id: creationID, queryGroupId: "cstm", icon: "AutoAwesomeIcon", }); + navigate(`/${creationID}`) }; const updateQuery = (formData, customQuery) => { @@ -362,8 +361,9 @@ export default function CustomEditor(props) { minRows={5} variant="outlined" helperText={`Write askQuery details in JSON-format${parsingErrorAsk ? ' (Invalid Syntax)' : '.'}`} - value={!!formData.askQuery ? typeof formData.askQuery === 'object' ? JSON.stringify(formData.askQuery) : formData.askQuery : `{\n\t"trueText" : " ",\n\t"falseText" : " " \n}`} + value={!!formData.askQuery ? typeof formData.askQuery === 'object' ? JSON.stringify(formData.askQuery) : formData.askQuery : formData.askQuery === '' ? '' : `{\n\t"trueText" : " ",\n\t"falseText" : " " \n}`} placeholder={`{\n\t"trueText" : "this displays when true.",\n\t"falseText" : "this displays when false." \n}`} + onClick={(e) => handleJSONparsing(e, setParsingErrorAsk)} onChange={(e) => handleJSONparsing(e, setParsingErrorAsk)} sx={{ marginBottom: '16px' }} /> @@ -397,8 +397,9 @@ export default function CustomEditor(props) { minRows={5} variant="outlined" helperText={`Write the variables specification in JSON-format${parsingErrorTemplate ? ' (Invalid Syntax)' : '.'}`} - value={!!formData.variables ? typeof formData.variables === 'object' ? JSON.stringify(formData.variables) : formData.variables : ''} + value={!!formData.variables ? typeof formData.variables === 'object' ? JSON.stringify(formData.variables) : formData.variables : formData.variables === '' ? '' : `{\n\t"variableOne" : ["option1", "option2", "option3"],\n\t"variableTwo" : ["option1", "option2", "option3"]\n}`} placeholder={`{\n\tvariableOne : ["option1","option2","option3"],\n\tvariableTwo : ["option1","option2","option3"], \n\t...\n}`} + onClick={(e) => handleJSONparsing(e, setParsingErrorTemplate)} onChange={(e) => handleJSONparsing(e, setParsingErrorTemplate)} sx={{ marginBottom: '16px' }} /> From 0a55354f796bc061789a6636ae9be6c59827e2af Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Tue, 25 Jun 2024 14:49:03 +0200 Subject: [PATCH 31/47] meeting feedback added --- .../Dashboard/CustomQueryEditor/customEditor.jsx | 9 +++++---- src/components/ListResultTable/TemplatedQueryForm.jsx | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index ded1c4a5..f60cbc8d 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -273,8 +273,9 @@ export default function CustomEditor(props) { minRows={5} variant="outlined" helperText={`Write the extra configurations in JSON-format${parsingErrorComunica ? ' (Invalid Syntax)' : '.'}`} - value={!!formData.comunicaContext ? typeof formData.comunicaContext === 'object' ? JSON.stringify(formData.comunicaContext) : formData.comunicaContext : ''} + value={!!formData.comunicaContext ? typeof formData.comunicaContext === 'object' ? JSON.stringify(formData.comunicaContext,null,2) : formData.comunicaContext : formData.comunicaContext === '' ? '' : `{\n\t"lenient" : true \n}`} placeholder={`{\n\t"lenient" : true,\n\t"other" : "some other options"\n}`} + onClick={(e) => handleJSONparsing(e, setParsingErrorComunica)} onChange={(e) => handleJSONparsing(e, setParsingErrorComunica)} sx={{ marginBottom: '16px' }} /> @@ -307,7 +308,7 @@ export default function CustomEditor(props) { name="indexSourceUrl" id="outlined-required" label="Index file URL" - placeholder="http://examplesource.org ; source2" + placeholder="http://examplesource.org" helperText="Give the URL of the index file." variant="outlined" value={!!formData.indexSourceUrl ? formData.indexSourceUrl : ''} @@ -361,7 +362,7 @@ export default function CustomEditor(props) { minRows={5} variant="outlined" helperText={`Write askQuery details in JSON-format${parsingErrorAsk ? ' (Invalid Syntax)' : '.'}`} - value={!!formData.askQuery ? typeof formData.askQuery === 'object' ? JSON.stringify(formData.askQuery) : formData.askQuery : formData.askQuery === '' ? '' : `{\n\t"trueText" : " ",\n\t"falseText" : " " \n}`} + value={!!formData.askQuery ? typeof formData.askQuery === 'object' ? JSON.stringify(formData.askQuery,null,2) : formData.askQuery : formData.askQuery === '' ? '' : `{\n\t"trueText" : " ",\n\t"falseText" : " " \n}`} placeholder={`{\n\t"trueText" : "this displays when true.",\n\t"falseText" : "this displays when false." \n}`} onClick={(e) => handleJSONparsing(e, setParsingErrorAsk)} onChange={(e) => handleJSONparsing(e, setParsingErrorAsk)} @@ -397,7 +398,7 @@ export default function CustomEditor(props) { minRows={5} variant="outlined" helperText={`Write the variables specification in JSON-format${parsingErrorTemplate ? ' (Invalid Syntax)' : '.'}`} - value={!!formData.variables ? typeof formData.variables === 'object' ? JSON.stringify(formData.variables) : formData.variables : formData.variables === '' ? '' : `{\n\t"variableOne" : ["option1", "option2", "option3"],\n\t"variableTwo" : ["option1", "option2", "option3"]\n}`} + value={!!formData.variables ? typeof formData.variables === 'object' ? JSON.stringify(formData.variables,null,2) : formData.variables : formData.variables === '' ? '' : `{\n\t"variableOne" : ["option1", "option2", "option3"],\n\t"variableTwo" : ["option1", "option2", "option3"]\n}`} placeholder={`{\n\tvariableOne : ["option1","option2","option3"],\n\tvariableTwo : ["option1","option2","option3"], \n\t...\n}`} onClick={(e) => handleJSONparsing(e, setParsingErrorTemplate)} onChange={(e) => handleJSONparsing(e, setParsingErrorTemplate)} diff --git a/src/components/ListResultTable/TemplatedQueryForm.jsx b/src/components/ListResultTable/TemplatedQueryForm.jsx index 67b0e39d..24135424 100644 --- a/src/components/ListResultTable/TemplatedQueryForm.jsx +++ b/src/components/ListResultTable/TemplatedQueryForm.jsx @@ -33,7 +33,7 @@ const TemplatedQueryForm = (props) => { return ( <SimpleForm toolbar={<MyToolbar />} onSubmit={onSubmit}> - {resourceDef.options.queryGroupId === 'cstm' && <CustomQueryEditButton queryID={resourceDef.name}/>} + {!!resourceDef.options && resourceDef.options.queryGroupId === 'cstm' && <CustomQueryEditButton queryID={resourceDef.name}/>} {Object.entries(variableOptions).map(([name, options]) => ( <SelectInput key={name} source={name} name={name} label={name} validate={required()} choices={ options.map((option) => ({ From 19b60e06e1f0df4704b8aef39017c25fd8bccd24 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Tue, 25 Jun 2024 14:49:16 +0200 Subject: [PATCH 32/47] finalised custom query tests --- cypress/e2e/custom-query-editor.cy.js | 277 +++++++++++++++++--------- 1 file changed, 178 insertions(+), 99 deletions(-) diff --git a/cypress/e2e/custom-query-editor.cy.js b/cypress/e2e/custom-query-editor.cy.js index 6e2a1f51..8c81ab62 100644 --- a/cypress/e2e/custom-query-editor.cy.js +++ b/cypress/e2e/custom-query-editor.cy.js @@ -1,13 +1,13 @@ describe("Simple Custom Query Editor tests", () => { - it("Create a new query and successfully create the URLparams", () => { + it("Create a new query", () => { - cy.visit("/#/customQuery"); + cy.visit("/#/customQuery"); - cy.get('input[name="name"]').type("new query"); - cy.get('textarea[name="description"]').type("new description"); - cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> + cy.get('input[name="name"]').type("new query"); + cy.get('textarea[name="description"]').type("new description"); + cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> SELECT * WHERE { ?list schema:name ?listTitle; @@ -18,26 +18,21 @@ SELECT * WHERE { ] ]. }`); - cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list"); - cy.get('button[type="submit"]').click(); + cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list"); + cy.get('button[type="submit"]').click(); - // Search Params were correctly created - cy.url().should('include', '?name=new+query&description=new+description&queryString=PREFIX+schema%3A+%3Chttp%3A%2F%2Fschema.org%2F%3E+%0A%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') - cy.contains("Custom queries").click(); - cy.contains("new query").click(); + // Checking if the book query works + cy.contains("Colleen Hoover").should('exist'); + }); - // Checking if the book query works - cy.contains("Colleen Hoover").should('exist'); - }); - - it("Create a new query, with multiple sources", () => { + it("Create a new query, with multiple sources", () => { - cy.visit("/#/customQuery"); + cy.visit("/#/customQuery"); - cy.get('input[name="name"]').type("material query"); - cy.get('textarea[name="description"]').type("this query has 3 sources"); - cy.get('textarea[name="queryString"]').type(`# Query Texon's components + cy.get('input[name="name"]').type("material query"); + cy.get('textarea[name="description"]').type("this query has 3 sources"); + cy.get('textarea[name="queryString"]').type(`# Query Texon's components # Datasources: https://css5.onto-deside.ilabt.imec.be/texon/data/dt/out/components.ttl PREFIX oo: <http://purl.org/openorg/> @@ -57,68 +52,65 @@ WHERE { } ORDER BY ?componentName `); - 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"); - cy.get('button[type="submit"]').click(); - - cy.contains("Custom queries").click(); - cy.contains("material query").click(); + 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"); + cy.get('button[type="submit"]').click(); - // Checking if the query works - cy.contains("http://www/example.com/data/component-c01").should('exist'); - }); + // Checking if the query works + cy.contains("http://www/example.com/data/component-c01").should('exist'); + }); + + it("Check if all possible parameters are filled in with parameterized URL", () => { + + // Navigate to the URL of a saved query with completely filled-in form + 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&variables=%7B%22firstvariables%22%3A%5B%22only+one%22%5D%7D") + + // Verify that every field is correctly filled-in + cy.get('input[name="name"]').should('have.value', 'Query Name'); + cy.get('textarea[name="description"]').should('have.value', 'Query Description'); + cy.get('textarea[name="queryString"]').should('have.value', 'Sparql query text'); + + cy.get('input[name="source"]').should('have.value', "The Comunica Source"); + cy.get('textarea[name="comunicaContext"]').should('have.value', `{"Advanced Comunica Context":true}`); - it("Check if all possible parameters are filled in with parameterized URL", () => { + cy.get('input[name="indexSourceUrl"]').should('have.value', "Index Source"); + cy.get('textarea[name="indexSourceQuery"]').should('have.value', "Index Query "); - // Navigate to the URL of a saved query with completely filled-in form - 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&variables=%7B%22firstvariables%22%3A%5B%22only+one%22%5D%7D") - - // Verify that every field is correctly filled-in - cy.get('input[name="name"]').should('have.value', 'Query Name'); - cy.get('textarea[name="description"]').should('have.value','Query Description'); - cy.get('textarea[name="queryString"]').should('have.value','Sparql query text'); + cy.get('textarea[name="askQuery"]').should('have.value', `{"trueText":" filled in","falseText":"not filled in"}`); - cy.get('input[name="source"]').should('have.value',"The Comunica Source"); - cy.get('textarea[name="comunicaContext"]').should('have.value',`{"Advanced Comunica Context":true}`); + cy.get('textarea[name="variables"]').should('have.value', `{"firstvariables":["only one"]}`); - cy.get('input[name="indexSourceUrl"]').should('have.value',"Index Source"); - cy.get('textarea[name="indexSourceQuery"]').should('have.value',"Index Query "); + }) - cy.get('textarea[name="askQuery"]').should('have.value',`{"trueText":" filled in","falseText":"not filled in"}`); + it("Successfully edit a query to make it work", () => { - cy.get('textarea[name="variables"]').should('have.value',`{"firstvariables":["only one"]}`); + cy.visit("/#/customQuery"); - }) + // First create a wrong query + cy.get('input[name="name"]').type("broken query"); + cy.get('textarea[name="description"]').type("just a description"); - it( "Successfully edit a query to make it work" , () => { + cy.get('textarea[name="queryString"]').type("this is faultive querytext") - cy.visit("/#/customQuery"); - - // First create a wrong query - cy.get('input[name="name"]').type("broken query"); - cy.get('textarea[name="description"]').type("just a description"); + cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list"); - cy.get('textarea[name="queryString"]').type("this is faultive querytext") - - cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list"); + //Submit the faultive query + cy.get('button[type="submit"]').click(); - //Submit the faultive query - cy.get('button[type="submit"]').click(); + cy.contains("Custom queries").click(); + cy.contains("broken query").click(); - cy.contains("Custom queries").click(); - cy.contains("broken query").click(); + // Verify that there are no results + cy.contains("The result list is empty.").should('exist'); - // Verify that there are no results - cy.contains("The result list is empty.").should('exist'); - - // Edit the query - cy.get('button').contains("Edit Query").click(); + // Edit the query + cy.get('button').contains("Edit Query").click(); - // Give the query a new name and a correct query text - cy.get('input[name="name"]').clear(); - cy.get('input[name="name"]').type("Fixed query"); + // Give the query a new name and a correct query text + cy.get('input[name="name"]').clear(); + cy.get('input[name="name"]').type("Fixed query"); - cy.get('textarea[name="queryString"]').clear(); - cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> + cy.get('textarea[name="queryString"]').clear(); + cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> SELECT * WHERE { ?list schema:name ?listTitle; @@ -130,25 +122,25 @@ ORDER BY ?componentName ]. }`); - // Submit the correct query - cy.get('button[type="submit"]').click(); + // Submit the correct query + cy.get('button[type="submit"]').click(); - // Now we should be on the page of the fixed query - cy.contains("Fixed query").should('exist'); + // Now we should be on the page of the fixed query + cy.contains("Fixed query").should('exist'); - // Check if the resulting list appears - cy.contains("Colleen Hoover").should('exist'); + // Check if the resulting list appears + cy.contains("Colleen Hoover").should('exist'); - }) + }) - it("Saves the correct URL", ()=>{ + it("Saves the correct URL", () => { - cy.visit("/#/customQuery"); + cy.visit("/#/customQuery"); - // First create a simple query - cy.get('input[name="name"]').type("new query"); - cy.get('textarea[name="description"]').type("new description"); - cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> + // First create a simple query + cy.get('input[name="name"]').type("new query"); + cy.get('textarea[name="description"]').type("new description"); + cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> SELECT * WHERE { ?list schema:name ?listTitle; @@ -159,30 +151,117 @@ SELECT * WHERE { ] ]. }`); - cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list"); - cy.get('button[type="submit"]').click(); + cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list"); + cy.get('button[type="submit"]').click(); - // Search Params were correctly created - cy.url().should('include', '?name=new+query&description=new+description&queryString=PREFIX+schema%3A+%3Chttp%3A%2F%2Fschema.org%2F%3E+%0A%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') - - // Remember the URL - cy.url().then((url) => { - cy.wrap(url).as('createdUrl'); - }); + cy.get('button').contains("Save Query").click(); - // Now go to the query and save it - cy.contains("Custom queries").click(); - cy.contains("new query").click(); - cy.get('button').contains("Save Query").click(); + cy.get('textarea[name="queryURL"]').invoke('val').then((val) => { + expect(val).to.include('?name=new+query&description=new+description&queryString=PREFIX+schema%3A+%3Chttp%3A%2F%2Fschema.org%2F%3E+%0A%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'); + }); + + }) + + it("Custom templated query", () => { + + cy.visit("/#/customQuery"); + + cy.get('input[name="name"]').type("custom template"); + cy.get('textarea[name="description"]').type("description for template"); - // The given URL is correct - cy.get('@createdUrl').then((createdUrl) => { - cy.get('textarea[name="queryURL"]').should('have.value', createdUrl); - }); - }) + // Query handeling a variable + cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> + SELECT ?name ?sameAs_url WHERE { + ?list schema:name ?listTitle; + schema:name ?name; + schema:genre $genre; + schema:sameAs ?sameAs_url; + }` + ); - // NOG EEN TEMPLATED TEST + cy.get('input[name="source"]').type("http://localhost:8080/example/favourite-musicians"); + cy.get('input[name="templatedQueryCheck"]').click() + cy.get('textarea[name="variables"]').clear() + cy.get('textarea[name="variables"]').type(`{ + "genre": [ + "\\"Romantic\\"", + "\\"Baroque\\"", + "\\"Classical\\"" + ] + }`) + cy.get('button[type="submit"]').click(); + + + cy.get('form').within(() => { + cy.get('#genre').click(); + }); + cy.get('li').contains('Baroque').click(); + + // Comfirm query + cy.get('button[type="submit"]').click(); + + cy.get('.column-name').find('span').contains("Antonio Caldara").should('exist'); + }) + + // NOG EEN INDEX FILE TEST + + it("Custom Query With Index File", () => { + + cy.visit("/#/customQuery"); + + cy.get('input[name="name"]').type("custom with index file"); + cy.get('textarea[name="description"]').type("description for index"); + + // Query handeling a variable + cy.get('textarea[name="queryString"]').type(` + # Query Texon's components and their materials + # 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 + + PREFIX oo: <http://purl.org/openorg/> + PREFIX ao: <http://purl.org/ontology/ao/core#> + PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> + PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> + PREFIX d: <http://www/example.com/data/> + PREFIX o: <http://www/example.com/ont/> + + SELECT ?component ?componentName ?material ?materialName ?percentage + WHERE { + ?component + a o:Component ; + o:name ?componentName ; + o:has-component-bom [ + o:has-component-material-assoc [ + o:percentage ?percentage ; + o:has-material ?material ; + ]; + ]; + . + ?material o:name ?materialName ; + } + ORDER BY ?componentName` + ); + + // No Comunica Sources Required + + cy.get('input[name="sourceIndexCheck"]').click() + + cy.get('input[name="indexSourceUrl"]').type("http://localhost:8080/example/index-example-texon-only") + + cy.get('textarea[name="indexSourceQuery"]').type(` + PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> + PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> + PREFIX example: <http://localhost:8080/example/index-example-texon-only#> + + SELECT ?object + WHERE { + example:index-example rdfs:seeAlso ?object . + }` + ) + cy.get('button[type="submit"]').click(); + + cy.contains("http://www/example.com/data/component-c01").should('exist'); + + }) - // NOG EEN INDEX FILE TEST }) \ No newline at end of file From c633accabc2a3a9bc11b2b28bd8b452847f26bc1 Mon Sep 17 00:00:00 2001 From: Martin Vanbrabant <martin.vanbrabant@ugent.be> Date: Tue, 25 Jun 2024 17:17:24 +0200 Subject: [PATCH 33/47] Added custom queries to the README --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index c06fb393..0890eebf 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Table of contents: * [Adding variable type](#adding-variable-type) * [Templated queries](#templated-queries) * [Query icons](#query-icons) +* [Custom queries](#custom-queries) * [Representation Mapper](#representation-mapper) * [Using the local pods](#using-the-local-pods) * [Testing](#testing) @@ -178,6 +179,20 @@ For this to work you need to add the icon to the exports in [IconProvider.js](./ 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. Nevertheless, you can use any React component you want, just make sure it's a functional component. +## Custom queries + +Besides the prepared queries in the configuration file, a user can edit custom queries: + +- To create a custom query, open "Custom Query Editor" from the menu on the left. +- Complete the custom query editor form and click the "CREATE QUERY" button when ready. +- Your new query is added to the "Custom queries" group and you are redirected to the query's result view. +- If not satisfied with the query result, you can click "EDIT QUERY" to further edit your query. + When saving changes, the result is recalculated. +- Because the custom query only lives as long as your browser remembers it, a "SAVE QUERY LINK" button is provided. + Use it to generate a unique URL for this custom query. Copy that URL to your clipboard and save it. + You can then visit that URL any time later, to recreate this query. +- To clean up an unwanted custom query, there is always a button "DELETE QUERY"... + ## Representation Mapper If you want to add your own type representations From e9a1703bf4b179ed9ecc15a3bc00530cfebf4c2d Mon Sep 17 00:00:00 2001 From: Martin Vanbrabant <martin.vanbrabant@ugent.be> Date: Tue, 25 Jun 2024 17:20:42 +0200 Subject: [PATCH 34/47] CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae291bc4..53bb2496 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- It is now possible to add and edit custom queries (#54). + ### Changed - For logged in users not having a username, the webID is displayed (#133). From c872eaf295dcf05b334c99f1e4fd84aec5b1c060 Mon Sep 17 00:00:00 2001 From: Martin Vanbrabant <martin.vanbrabant@ugent.be> Date: Tue, 25 Jun 2024 19:54:22 +0200 Subject: [PATCH 35/47] changed placeholders for sources --- src/components/Dashboard/CustomQueryEditor/customEditor.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index f60cbc8d..751fbf7d 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -251,8 +251,8 @@ export default function CustomEditor(props) { name="source" id="outlined-required" label="Data source(s)" - placeholder="http://examplesource.org ; source2" - helperText="Give the source URL(s) for the query. Separate URLs with with ';'." + placeholder="http://example.com/source1; http://example.com/source2" + helperText="Give the source URL(s) for the query. Separate URLs with with '; '." variant="outlined" value={!!formData.source ? formData.source : ''} onChange={handleChange} @@ -308,7 +308,7 @@ export default function CustomEditor(props) { name="indexSourceUrl" id="outlined-required" label="Index file URL" - placeholder="http://examplesource.org" + placeholder="http://example.com/index" helperText="Give the URL of the index file." variant="outlined" value={!!formData.indexSourceUrl ? formData.indexSourceUrl : ''} From 54b9f91a2570ac6e64c955d821127f163818befd Mon Sep 17 00:00:00 2001 From: Martin Vanbrabant <martin.vanbrabant@ugent.be> Date: Wed, 26 Jun 2024 08:27:23 +0200 Subject: [PATCH 36/47] JSON placeholder and initial contents format --- .../Dashboard/CustomQueryEditor/customEditor.jsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index 751fbf7d..dfbf6c21 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -162,6 +162,8 @@ export default function CustomEditor(props) { navigate(`/${customQuery.id}`) }; + const defaultExtraComunicaContext = JSON.stringify({ "lenient": true }, null, 2); + const defaultAskQueryDetails = JSON.stringify({"trueText": "this displays when true.", "falseText": "this displays when false."}, null, 2); return ( <React.Fragment> @@ -273,8 +275,8 @@ export default function CustomEditor(props) { minRows={5} variant="outlined" helperText={`Write the extra configurations in JSON-format${parsingErrorComunica ? ' (Invalid Syntax)' : '.'}`} - value={!!formData.comunicaContext ? typeof formData.comunicaContext === 'object' ? JSON.stringify(formData.comunicaContext,null,2) : formData.comunicaContext : formData.comunicaContext === '' ? '' : `{\n\t"lenient" : true \n}`} - placeholder={`{\n\t"lenient" : true,\n\t"other" : "some other options"\n}`} + value={!!formData.comunicaContext ? typeof formData.comunicaContext === 'object' ? JSON.stringify(formData.comunicaContext, null, 2) : formData.comunicaContext : formData.comunicaContext === '' ? '' : defaultExtraComunicaContext} + placeholder={defaultExtraComunicaContext} onClick={(e) => handleJSONparsing(e, setParsingErrorComunica)} onChange={(e) => handleJSONparsing(e, setParsingErrorComunica)} sx={{ marginBottom: '16px' }} @@ -362,8 +364,8 @@ export default function CustomEditor(props) { minRows={5} variant="outlined" helperText={`Write askQuery details in JSON-format${parsingErrorAsk ? ' (Invalid Syntax)' : '.'}`} - value={!!formData.askQuery ? typeof formData.askQuery === 'object' ? JSON.stringify(formData.askQuery,null,2) : formData.askQuery : formData.askQuery === '' ? '' : `{\n\t"trueText" : " ",\n\t"falseText" : " " \n}`} - placeholder={`{\n\t"trueText" : "this displays when true.",\n\t"falseText" : "this displays when false." \n}`} + value={!!formData.askQuery ? typeof formData.askQuery === 'object' ? JSON.stringify(formData.askQuery, null, 2) : formData.askQuery : formData.askQuery === '' ? '' : defaultAskQueryDetails} + placeholder={defaultAskQueryDetails} onClick={(e) => handleJSONparsing(e, setParsingErrorAsk)} onChange={(e) => handleJSONparsing(e, setParsingErrorAsk)} sx={{ marginBottom: '16px' }} From 19719e694aef4b0294c2a0f510080fb23ae51501 Mon Sep 17 00:00:00 2001 From: Martin Vanbrabant <martin.vanbrabant@ugent.be> Date: Wed, 26 Jun 2024 09:27:03 +0200 Subject: [PATCH 37/47] default SPARQL queries --- cypress/e2e/custom-query-editor.cy.js | 6 ++++++ .../CustomQueryEditor/customEditor.jsx | 17 +++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/cypress/e2e/custom-query-editor.cy.js b/cypress/e2e/custom-query-editor.cy.js index 8c81ab62..1f3f123c 100644 --- a/cypress/e2e/custom-query-editor.cy.js +++ b/cypress/e2e/custom-query-editor.cy.js @@ -7,6 +7,7 @@ describe("Simple Custom Query Editor tests", () => { cy.get('input[name="name"]').type("new query"); cy.get('textarea[name="description"]').type("new description"); + cy.get('textarea[name="queryString"]').clear(); cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> SELECT * WHERE { @@ -32,6 +33,7 @@ SELECT * WHERE { cy.get('input[name="name"]').type("material query"); cy.get('textarea[name="description"]').type("this query has 3 sources"); + cy.get('textarea[name="queryString"]').clear(); cy.get('textarea[name="queryString"]').type(`# Query Texon's components # Datasources: https://css5.onto-deside.ilabt.imec.be/texon/data/dt/out/components.ttl @@ -89,6 +91,7 @@ ORDER BY ?componentName cy.get('input[name="name"]').type("broken query"); cy.get('textarea[name="description"]').type("just a description"); + cy.get('textarea[name="queryString"]').clear(); cy.get('textarea[name="queryString"]').type("this is faultive querytext") cy.get('input[name="source"]').type("http://localhost:8080/example/wish-list"); @@ -170,6 +173,7 @@ SELECT * WHERE { cy.get('textarea[name="description"]').type("description for template"); // Query handeling a variable + cy.get('textarea[name="queryString"]').clear(); cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> SELECT ?name ?sameAs_url WHERE { ?list schema:name ?listTitle; @@ -214,6 +218,7 @@ SELECT * WHERE { cy.get('textarea[name="description"]').type("description for index"); // Query handeling a variable + cy.get('textarea[name="queryString"]').clear(); cy.get('textarea[name="queryString"]').type(` # Query Texon's components and their materials # 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 @@ -248,6 +253,7 @@ SELECT * WHERE { cy.get('input[name="indexSourceUrl"]').type("http://localhost:8080/example/index-example-texon-only") + cy.get('textarea[name="indexSourceQuery"]').clear(); cy.get('textarea[name="indexSourceQuery"]').type(` PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index dfbf6c21..36302ac6 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -162,6 +162,15 @@ export default function CustomEditor(props) { navigate(`/${customQuery.id}`) }; + const defaultSparqlQuery = `SELECT ?s ?p ?o +WHERE { + ?s ?p ?o +}`; + const defaultSparqlQueryIndexSources = `PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> +SELECT ?object +WHERE { + ?s rdfs:seeAlso ?object +}`; const defaultExtraComunicaContext = JSON.stringify({ "lenient": true }, null, 2); const defaultAskQueryDetails = JSON.stringify({"trueText": "this displays when true.", "falseText": "this displays when false."}, null, 2); @@ -218,8 +227,8 @@ export default function CustomEditor(props) { minRows={5} variant="outlined" helperText="Enter your SPARQL query here." - placeholder={`SELECT ?s ?p ?o \nWHERE { \n\t?s ?p ?o \n}`} - value={!!formData.queryString ? formData.queryString : ''} + placeholder={defaultSparqlQuery} + value={!!formData.queryString ? formData.queryString : formData.queryString === '' ? '' : defaultSparqlQuery} onChange={handleChange} sx={{ marginBottom: '16px' }} /> @@ -328,8 +337,8 @@ export default function CustomEditor(props) { minRows={5} variant="outlined" helperText="Give the SPARQL query to get the sources from the index file." - placeholder={`SELECT ?s ?p ?o \nWHERE { \n\t?s ?p ?o \n}`} - value={!!formData.indexSourceQuery ? formData.indexSourceQuery : ''} + placeholder={defaultSparqlQueryIndexSources} + value={!!formData.indexSourceQuery ? formData.indexSourceQuery : formData.indexSourceQuery === '' ? '' : defaultSparqlQueryIndexSources} onChange={handleChange} sx={{ marginBottom: '16px' }} /> From c0163f85d84ffc6bfb729a6853619f38770f26e2 Mon Sep 17 00:00:00 2001 From: Martin Vanbrabant <martin.vanbrabant@ugent.be> Date: Wed, 26 Jun 2024 09:45:37 +0200 Subject: [PATCH 38/47] custom editor tests work again --- cypress/e2e/custom-query-editor.cy.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/custom-query-editor.cy.js b/cypress/e2e/custom-query-editor.cy.js index 1f3f123c..d6493d9b 100644 --- a/cypress/e2e/custom-query-editor.cy.js +++ b/cypress/e2e/custom-query-editor.cy.js @@ -7,6 +7,7 @@ describe("Simple Custom Query Editor tests", () => { cy.get('input[name="name"]').type("new query"); cy.get('textarea[name="description"]').type("new description"); + cy.get('textarea[name="queryString"]').clear(); cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> @@ -33,6 +34,7 @@ SELECT * WHERE { cy.get('input[name="name"]').type("material query"); cy.get('textarea[name="description"]').type("this query has 3 sources"); + cy.get('textarea[name="queryString"]').clear(); cy.get('textarea[name="queryString"]').type(`# Query Texon's components # Datasources: https://css5.onto-deside.ilabt.imec.be/texon/data/dt/out/components.ttl @@ -143,6 +145,8 @@ ORDER BY ?componentName // First create a simple query cy.get('input[name="name"]').type("new query"); cy.get('textarea[name="description"]').type("new description"); + + cy.get('textarea[name="queryString"]').clear(); cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> SELECT * WHERE { @@ -172,7 +176,7 @@ SELECT * WHERE { cy.get('input[name="name"]').type("custom template"); cy.get('textarea[name="description"]').type("description for template"); - // Query handeling a variable + // Query handling a variable cy.get('textarea[name="queryString"]').clear(); cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> SELECT ?name ?sameAs_url WHERE { @@ -217,7 +221,7 @@ SELECT * WHERE { cy.get('input[name="name"]').type("custom with index file"); cy.get('textarea[name="description"]').type("description for index"); - // Query handeling a variable + // Query handling a variable cy.get('textarea[name="queryString"]').clear(); cy.get('textarea[name="queryString"]').type(` # Query Texon's components and their materials From c4216c78e34eae53d0925cb0ea7a132aa8864e00 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Wed, 26 Jun 2024 11:05:01 +0200 Subject: [PATCH 39/47] open query group when a custom query is created/deleted/editted --- .../InteractionLayout/SelectionMenu/SelectionMenu.jsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/InteractionLayout/SelectionMenu/SelectionMenu.jsx b/src/components/InteractionLayout/SelectionMenu/SelectionMenu.jsx index c9d67427..82dd579a 100644 --- a/src/components/InteractionLayout/SelectionMenu/SelectionMenu.jsx +++ b/src/components/InteractionLayout/SelectionMenu/SelectionMenu.jsx @@ -28,6 +28,14 @@ const SelectionMenu = () => { useEffect(() => { const handleGroupChange = (newConfig) => { setConfig(newConfig); + + // Open the cstm group when a new custom query is created + if(newConfig.queryGroups.find(group => group.id === 'cstm')){ + setOpenGroups(prevOpenGroups => ({ + ...prevOpenGroups, + ['cstm']: true, + })); + } }; configManager.on('configChanged', handleGroupChange); From 60059d2be6501ef256796649889cc9efcb396b09 Mon Sep 17 00:00:00 2001 From: Martin Vanbrabant <martin.vanbrabant@ugent.be> Date: Wed, 26 Jun 2024 11:53:01 +0200 Subject: [PATCH 40/47] source index query can use any variable name --- .../CustomQueryEditor/customEditor.jsx | 4 ++-- src/dataProvider/SparqlDataProvider.js | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index 36302ac6..9266e5e0 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -167,9 +167,9 @@ WHERE { ?s ?p ?o }`; const defaultSparqlQueryIndexSources = `PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> -SELECT ?object +SELECT ?source WHERE { - ?s rdfs:seeAlso ?object + ?s rdfs:seeAlso ?source }`; const defaultExtraComunicaContext = JSON.stringify({ "lenient": true }, null, 2); const defaultAskQueryDetails = JSON.stringify({"trueText": "this displays when true.", "falseText": "this displays when false."}, null, 2); diff --git a/src/dataProvider/SparqlDataProvider.js b/src/dataProvider/SparqlDataProvider.js index a5b79097..99b8060b 100644 --- a/src/dataProvider/SparqlDataProvider.js +++ b/src/dataProvider/SparqlDataProvider.js @@ -356,10 +356,15 @@ const addComunicaContextSourcesFromSourcesIndex = async (sourcesIndex) => { }); await new Promise((resolve, reject) => { - bindingsStream.on('data', (binding) => { - const source = binding.get('object').value; - if (!sourcesList.includes(source)) { - sourcesList.push(source); + bindingsStream.on('data', (bindings) => { + // the bindings should have exactly one key (any name is allowed) and we accept the value as a source + if (bindings.size == 1) { + for (const term of bindings.values()) { + const source = term.value; + if (!sourcesList.includes(source)) { + sourcesList.push(source); + } + } } }); bindingsStream.on('end', resolve); @@ -370,6 +375,10 @@ const addComunicaContextSourcesFromSourcesIndex = async (sourcesIndex) => { throw new Error(`Error adding sources from index: ${error.message}`); } + if (sourcesList.length == 0) { + throw new Error(`The resulting list of sources is empty`); + } + return sourcesList; }; From 29e879c489db95ba8ae3dfaca03ce1f65febbf21 Mon Sep 17 00:00:00 2001 From: Martin Vanbrabant <martin.vanbrabant@ugent.be> Date: Wed, 26 Jun 2024 12:00:32 +0200 Subject: [PATCH 41/47] doc on source URLs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0890eebf..40362eae 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ The set of sources over which a query will be executed is derived from two *opti If both inputs are present, the query will be executed over the superset of sources. -The (auxiliary) query provided in `sourceIndex.queryLocation` is executed on `sourceIndex.url` and must result in the list of sources. +The (auxiliary) query provided in `sourceIndex.queryLocation` is executed on `sourceIndex.url` and must result in the list of source URLs. If `sourceIndex` is used and there is no `comunicaContext.lenient` property found, one will be created with value `true`. This makes sure that the (main) query can succeed if not all obtained sources are accessible. From 041421b42d369d62c50cd3f2ea17dab20c4552ea Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Wed, 26 Jun 2024 12:14:27 +0200 Subject: [PATCH 42/47] changed the scope of templated options to add more features --- .../CustomQueryEditor/customEditor.jsx | 62 ++++++++++++------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index 36302ac6..f905a078 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -14,7 +14,7 @@ import IconProvider from '../../../IconProvider/IconProvider'; //const myEngine = new QueryEngine(); export default function CustomEditor(props) { - + const location = useLocation(); const navigate = useNavigate(); const [formData, setFormData] = useState({ @@ -23,20 +23,33 @@ export default function CustomEditor(props) { source: '', queryString: '', comunicaContext: '', - + comunicaContextCheck: false, sourceIndexCheck: false, askQueryCheck: false, templatedQueryCheck: false, - + }); - + const [showError, setShowError] = useState(false); - + const [parsingErrorComunica, setParsingErrorComunica] = useState(false); const [parsingErrorAsk, setParsingErrorAsk] = useState(false); const [parsingErrorTemplate, setParsingErrorTemplate] = useState(false); + const defaultSparqlQuery = `SELECT ?s ?p ?o +WHERE { + ?s ?p ?o +}`; + const defaultSparqlQueryIndexSources = `PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> +SELECT ?object +WHERE { + ?s rdfs:seeAlso ?object +}`; + const defaultExtraComunicaContext = JSON.stringify({ "lenient": true }, null, 2); + const defaultAskQueryDetails = JSON.stringify({"trueText": "this displays when true.", "falseText": "this displays when false."}, null, 2); + const defaultTemplateOptions = JSON.stringify({"variables" : {"variableOne" : ["option1", "option2", "option3"],"variableTwo" : ["option1", "option2", "option3"]}}, null, 5) + useEffect(() => { let searchParams if (props.newQuery) { @@ -135,7 +148,25 @@ export default function CustomEditor(props) { parsedObject.askQuery = JSON.parse(dataWithStrings.askQuery); } if (ensureBoolean(dataWithStrings.templatedQueryCheck)) { - parsedObject.variables = JSON.parse(dataWithStrings.variables); + //parsedObject.variables = JSON.parse(dataWithStrings.variables); + + // hier de logica voor .variables en .querystring voor latere updates + // form name variables gaan hernoemen naar templateOptions + + const options = JSON.parse(dataWithStrings.templateOptions); + + if (options.variables){ + console.log('het werkt') + console.log(options) + parsedObject.variables = options.variables; + } + + // This will serve for the extention of customizable query variables + if (options.templatedVarSourceQueryString){ + parsedObject.templatedVarSourceQueryString = options.templatedVarSourceQueryString; + } + + } return parsedObject; } @@ -162,17 +193,6 @@ export default function CustomEditor(props) { navigate(`/${customQuery.id}`) }; - const defaultSparqlQuery = `SELECT ?s ?p ?o -WHERE { - ?s ?p ?o -}`; - const defaultSparqlQueryIndexSources = `PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> -SELECT ?object -WHERE { - ?s rdfs:seeAlso ?object -}`; - const defaultExtraComunicaContext = JSON.stringify({ "lenient": true }, null, 2); - const defaultAskQueryDetails = JSON.stringify({"trueText": "this displays when true.", "falseText": "this displays when false."}, null, 2); return ( <React.Fragment> @@ -401,16 +421,16 @@ WHERE { <TextField required={ensureBoolean(formData.templatedQueryCheck)} id="outlined-multiline-flexible" - label="Variables specification" - name="variables" + label="Templated query options" + name="templateOptions" error={parsingErrorTemplate} multiline fullWidth minRows={5} variant="outlined" helperText={`Write the variables specification in JSON-format${parsingErrorTemplate ? ' (Invalid Syntax)' : '.'}`} - value={!!formData.variables ? typeof formData.variables === 'object' ? JSON.stringify(formData.variables,null,2) : formData.variables : formData.variables === '' ? '' : `{\n\t"variableOne" : ["option1", "option2", "option3"],\n\t"variableTwo" : ["option1", "option2", "option3"]\n}`} - placeholder={`{\n\tvariableOne : ["option1","option2","option3"],\n\tvariableTwo : ["option1","option2","option3"], \n\t...\n}`} + value={!!formData.templateOptions ? typeof formData.templateOptions === 'object' ? JSON.stringify(formData.templateOptions,null,5) : formData.templateOptions : formData.templateOptions === '' ? '' : defaultTemplateOptions} + placeholder={defaultTemplateOptions} onClick={(e) => handleJSONparsing(e, setParsingErrorTemplate)} onChange={(e) => handleJSONparsing(e, setParsingErrorTemplate)} sx={{ marginBottom: '16px' }} From 92de0dda4d3a541477f5d8257dfb854f14ec9f6d Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Wed, 26 Jun 2024 12:22:21 +0200 Subject: [PATCH 43/47] updated tests --- cypress/e2e/custom-query-editor.cy.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cypress/e2e/custom-query-editor.cy.js b/cypress/e2e/custom-query-editor.cy.js index d6493d9b..6d156357 100644 --- a/cypress/e2e/custom-query-editor.cy.js +++ b/cypress/e2e/custom-query-editor.cy.js @@ -66,7 +66,7 @@ ORDER BY ?componentName it("Check if all possible parameters are filled in with parameterized URL", () => { // Navigate to the URL of a saved query with completely filled-in form - 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&variables=%7B%22firstvariables%22%3A%5B%22only+one%22%5D%7D") + 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") // Verify that every field is correctly filled-in cy.get('input[name="name"]').should('have.value', 'Query Name'); @@ -81,7 +81,7 @@ ORDER BY ?componentName cy.get('textarea[name="askQuery"]').should('have.value', `{"trueText":" filled in","falseText":"not filled in"}`); - cy.get('textarea[name="variables"]').should('have.value', `{"firstvariables":["only one"]}`); + cy.get('textarea[name="templateOptions"]').should('have.value', `{"firstvariables":["only one"]}`); }) @@ -190,14 +190,14 @@ SELECT * WHERE { cy.get('input[name="source"]').type("http://localhost:8080/example/favourite-musicians"); cy.get('input[name="templatedQueryCheck"]').click() - cy.get('textarea[name="variables"]').clear() - cy.get('textarea[name="variables"]').type(`{ + cy.get('textarea[name="templateOptions"]').clear() + cy.get('textarea[name="templateOptions"]').type(`{"variables" : { "genre": [ "\\"Romantic\\"", "\\"Baroque\\"", "\\"Classical\\"" ] - }`) + }}`) cy.get('button[type="submit"]').click(); From aa7cfe7637a637d6b9c33af0d0c8d0e3011bc1f3 Mon Sep 17 00:00:00 2001 From: Martin Vanbrabant <martin.vanbrabant@ugent.be> Date: Wed, 26 Jun 2024 12:58:22 +0200 Subject: [PATCH 44/47] ?source in SPARQL query for sources index file --- src/components/Dashboard/CustomQueryEditor/customEditor.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index f905a078..38a64ddc 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -42,9 +42,9 @@ WHERE { ?s ?p ?o }`; const defaultSparqlQueryIndexSources = `PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> -SELECT ?object +SELECT ?source WHERE { - ?s rdfs:seeAlso ?object + ?s rdfs:seeAlso ?source }`; const defaultExtraComunicaContext = JSON.stringify({ "lenient": true }, null, 2); const defaultAskQueryDetails = JSON.stringify({"trueText": "this displays when true.", "falseText": "this displays when false."}, null, 2); From 4df496c78a3a57e948714fc1059ab919a5bd17f7 Mon Sep 17 00:00:00 2001 From: Martin Vanbrabant <martin.vanbrabant@ugent.be> Date: Wed, 26 Jun 2024 13:09:24 +0200 Subject: [PATCH 45/47] CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53bb2496..d4065b1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Forced CSS's to not return content type application/ld+json, which induced a CORS error on some CSS server versions (#131). +- Queries based on index file now work for any variable, not just ?object (#136). ## [1.2.1] - 2024-06-17 From 56117ea2d54d96f3ed86566b9214757647461d79 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Wed, 26 Jun 2024 14:12:04 +0200 Subject: [PATCH 46/47] test fixes --- cypress/e2e/custom-query-editor.cy.js | 111 +++++++++++++------------- 1 file changed, 54 insertions(+), 57 deletions(-) diff --git a/cypress/e2e/custom-query-editor.cy.js b/cypress/e2e/custom-query-editor.cy.js index 6d156357..e2e249e1 100644 --- a/cypress/e2e/custom-query-editor.cy.js +++ b/cypress/e2e/custom-query-editor.cy.js @@ -1,5 +1,5 @@ -describe("Simple Custom Query Editor tests", () => { +describe("Custom Query Editor tests", () => { it("Create a new query", () => { @@ -116,16 +116,15 @@ ORDER BY ?componentName cy.get('textarea[name="queryString"]').clear(); cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> - - SELECT * WHERE { - ?list schema:name ?listTitle; - schema:itemListElement [ - schema:name ?bookTitle; - schema:creator [ - schema:name ?authorName - ] - ]. - }`); +SELECT * WHERE { + ?list schema:name ?listTitle; + schema:itemListElement [ + schema:name ?bookTitle; + schema:creator [ + schema:name ?authorName + ] + ]. +}`); // Submit the correct query cy.get('button[type="submit"]').click(); @@ -148,7 +147,6 @@ ORDER BY ?componentName cy.get('textarea[name="queryString"]').clear(); cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> - SELECT * WHERE { ?list schema:name ?listTitle; schema:itemListElement [ @@ -164,8 +162,9 @@ SELECT * WHERE { cy.get('button').contains("Save Query").click(); cy.get('textarea[name="queryURL"]').invoke('val').then((val) => { - expect(val).to.include('?name=new+query&description=new+description&queryString=PREFIX+schema%3A+%3Chttp%3A%2F%2Fschema.org%2F%3E+%0A%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'); + 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'); }); + }) @@ -179,13 +178,13 @@ SELECT * WHERE { // Query handling a variable cy.get('textarea[name="queryString"]').clear(); cy.get('textarea[name="queryString"]').type(`PREFIX schema: <http://schema.org/> - SELECT ?name ?sameAs_url WHERE { - ?list schema:name ?listTitle; - schema:name ?name; - schema:genre $genre; - schema:sameAs ?sameAs_url; - }` - ); +SELECT ?name ?sameAs_url WHERE { + ?list schema:name ?listTitle; + schema:name ?name; + schema:genre $genre; + schema:sameAs ?sameAs_url; +}` +); cy.get('input[name="source"]').type("http://localhost:8080/example/favourite-musicians"); cy.get('input[name="templatedQueryCheck"]').click() @@ -223,33 +222,32 @@ SELECT * WHERE { // Query handling a variable cy.get('textarea[name="queryString"]').clear(); - cy.get('textarea[name="queryString"]').type(` - # Query Texon's components and their materials - # 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 - - PREFIX oo: <http://purl.org/openorg/> - PREFIX ao: <http://purl.org/ontology/ao/core#> - PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> - PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> - PREFIX d: <http://www/example.com/data/> - PREFIX o: <http://www/example.com/ont/> - - SELECT ?component ?componentName ?material ?materialName ?percentage - WHERE { - ?component - a o:Component ; - o:name ?componentName ; - o:has-component-bom [ - o:has-component-material-assoc [ - o:percentage ?percentage ; - o:has-material ?material ; - ]; - ]; - . - ?material o:name ?materialName ; - } - ORDER BY ?componentName` - ); + cy.get('textarea[name="queryString"]').type(`# Query Texon's components and their materials +# 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 + +PREFIX oo: <http://purl.org/openorg/> +PREFIX ao: <http://purl.org/ontology/ao/core#> +PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> +PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> +PREFIX d: <http://www/example.com/data/> +PREFIX o: <http://www/example.com/ont/> + +SELECT ?component ?componentName ?material ?materialName ?percentage +WHERE { + ?component + a o:Component ; + o:name ?componentName ; + o:has-component-bom [ + o:has-component-material-assoc [ + o:percentage ?percentage ; + o:has-material ?material ; + ]; + ]; + . + ?material o:name ?materialName ; +} +ORDER BY ?componentName` +); // No Comunica Sources Required @@ -258,16 +256,15 @@ SELECT * WHERE { cy.get('input[name="indexSourceUrl"]').type("http://localhost:8080/example/index-example-texon-only") cy.get('textarea[name="indexSourceQuery"]').clear(); - cy.get('textarea[name="indexSourceQuery"]').type(` - PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> - PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> - PREFIX example: <http://localhost:8080/example/index-example-texon-only#> - - SELECT ?object - WHERE { - example:index-example rdfs:seeAlso ?object . - }` - ) + cy.get('textarea[name="indexSourceQuery"]').type(`PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> +PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> +PREFIX example: <http://localhost:8080/example/index-example-texon-only#> + +SELECT ?object +WHERE { + example:index-example rdfs:seeAlso ?object . +}` +) cy.get('button[type="submit"]').click(); cy.contains("http://www/example.com/data/component-c01").should('exist'); From 17f52cc25d84d3e54b8ad6aa2a35226e1e2ea289 Mon Sep 17 00:00:00 2001 From: EmilioTR <Emilio.TenaRomero@hotmail.com> Date: Wed, 26 Jun 2024 14:25:56 +0200 Subject: [PATCH 47/47] Code clean up --- cypress/e2e/custom-query-editor.cy.js | 4 --- .../CustomQueryEditor/customEditor.jsx | 31 ------------------- .../customQueryEditButton.jsx | 2 +- 3 files changed, 1 insertion(+), 36 deletions(-) diff --git a/cypress/e2e/custom-query-editor.cy.js b/cypress/e2e/custom-query-editor.cy.js index e2e249e1..831f326b 100644 --- a/cypress/e2e/custom-query-editor.cy.js +++ b/cypress/e2e/custom-query-editor.cy.js @@ -211,8 +211,6 @@ SELECT ?name ?sameAs_url WHERE { cy.get('.column-name').find('span').contains("Antonio Caldara").should('exist'); }) - // NOG EEN INDEX FILE TEST - it("Custom Query With Index File", () => { cy.visit("/#/customQuery"); @@ -250,9 +248,7 @@ ORDER BY ?componentName` ); // No Comunica Sources Required - cy.get('input[name="sourceIndexCheck"]').click() - cy.get('input[name="indexSourceUrl"]').type("http://localhost:8080/example/index-example-texon-only") cy.get('textarea[name="indexSourceQuery"]').clear(); diff --git a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx index 38a64ddc..8e04693a 100644 --- a/src/components/Dashboard/CustomQueryEditor/customEditor.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customEditor.jsx @@ -10,8 +10,6 @@ import Checkbox from '@mui/material/Checkbox'; import configManager from '../../../configManager/configManager'; import IconProvider from '../../../IconProvider/IconProvider'; -//import { QueryEngine } from "@comunica/query-sparql"; -//const myEngine = new QueryEngine(); export default function CustomEditor(props) { @@ -79,9 +77,6 @@ WHERE { if (props.newQuery) { navigate({ search: searchParams.toString() }); - // TODO: NEED A CHECK HERE TO SEE IF WE MAY SUBMIT (correct query) - // const data = await executeSPARQLQuery(jsonData.query, jsonData.source, setShowError); - configManager.addNewQueryGroup('cstm', 'Custom queries', 'EditNoteIcon'); addQuery(jsonData); } @@ -148,16 +143,10 @@ WHERE { parsedObject.askQuery = JSON.parse(dataWithStrings.askQuery); } if (ensureBoolean(dataWithStrings.templatedQueryCheck)) { - //parsedObject.variables = JSON.parse(dataWithStrings.variables); - // hier de logica voor .variables en .querystring voor latere updates - // form name variables gaan hernoemen naar templateOptions - const options = JSON.parse(dataWithStrings.templateOptions); if (options.variables){ - console.log('het werkt') - console.log(options) parsedObject.variables = options.variables; } @@ -454,26 +443,6 @@ WHERE { ) } -// Temporary bindingstream this is if you want a check on the simple queries before submitting -/* -async function executeSPARQLQuery(query, dataSource, setShowError) { - const resultingObjects = []; - try { - const bindingsStream = await myEngine.queryBindings(query, { - sources: dataSource.split(';').map(source => source.trim()) - }); - - bindingsStream.on('data', (binding) => { - resultingObjects.push(JSON.parse(binding.toString())); - }); - } catch (error) { - setShowError(true); - throw new Error(`Error executing SPARQL query: ${error.message}`); - } - return resultingObjects; -}; - -*/ diff --git a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx index 88037f05..905c4848 100644 --- a/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx +++ b/src/components/Dashboard/CustomQueryEditor/customQueryEditButton.jsx @@ -120,7 +120,7 @@ export default function CustomQueryEditButton({ queryID, submitted=false }) { <DialogContent> <DialogContentText > - Use this link ro recreate this custom query later. + Use this link to recreate this custom query later. </DialogContentText> <DialogContentText style={{ color: feedback.includes('successfully') ? 'green' : 'red' }} >