Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
the resolved query is executed immediately, avoiding the delay it takes to first retrieve all options for the variables (#211).
- CONSTRUCT queries work again (#222).
- Config field "logoRedirectURL" is working (#17).
- After modifying an existing custom query, the previous result table is updated as expected now (#137).

## [1.7.0] - 2025-04-09

Expand Down
205 changes: 102 additions & 103 deletions main/src/components/CustomQueryEditor/customEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,8 @@ export default function CustomEditor(props) {
icon: customQuery.icon
});

navigate(`/${customQuery.id}`);
// force a re-render with the updateTimestamp
navigate(`/${customQuery.id}`, {state: { updateTimestamp: Date.now() } });
}
};

Expand Down Expand Up @@ -353,6 +354,18 @@ export default function CustomEditor(props) {
<Card sx={{ py: '10px', px: '20px', my: 2 }}>

<Typography variant="h5" sx={{ mt: 2 }}> Comunica Context &amp; Sources </Typography>
<TextField
required={!isChecked(formData.sourceIndexCheck)}
fullWidth
name="source"
label="Fixed data source(s)"
placeholder="http://example.com/source1; http://example.com/source2"
helperText="Give the source URL(s) for the query. Separate URLs with '; '. (These are the Comunica context sources)"
variant="outlined"
value={!!formData.source ? formData.source : ''}
onChange={handleChange}
sx={{ marginBottom: '16px' }}
/>
<div>
<FormControlLabel
control={<Checkbox
Expand All @@ -368,22 +381,7 @@ export default function CustomEditor(props) {
}

/>} label="Advanced Comunica Context Settings" />

</div>
<TextField
required={!isChecked(formData.sourceIndexCheck)}
fullWidth
name="source"
label="Fixed data source(s)"
placeholder="http://example.com/source1; http://example.com/source2"
helperText="Give the source URL(s) for the query. Separate URLs with '; '. (These are the Comunica context sources)"
variant="outlined"
value={!!formData.source ? formData.source : ''}
onChange={handleChange}
sx={{ marginBottom: '16px' }}
/>


{isChecked(formData.comunicaContextCheck) &&
<div>
<JsonEditField
Expand All @@ -397,19 +395,21 @@ export default function CustomEditor(props) {
</div>
}

<FormControlLabel
control={<Checkbox
name='sourceIndexCheck'
checked={isChecked(formData.sourceIndexCheck)}
onChange={
() => {
setFormData((prevFormData) => ({
...prevFormData,
'sourceIndexCheck': !isChecked(formData.sourceIndexCheck),
}));
<div>
<FormControlLabel
control={<Checkbox
name='sourceIndexCheck'
checked={isChecked(formData.sourceIndexCheck)}
onChange={
() => {
setFormData((prevFormData) => ({
...prevFormData,
'sourceIndexCheck': !isChecked(formData.sourceIndexCheck),
}));
}
}
}
/>} label="Indirect sources" />
/>} label="Indirect sources" />
</div>

{isChecked(formData.sourceIndexCheck) &&
<div>
Expand Down Expand Up @@ -455,21 +455,23 @@ export default function CustomEditor(props) {
}
}
/>} label="Fixed Variables" />
</div>

{isChecked(formData.directVariablesCheck) &&
<div>
<Typography variant="base" sx={{ mt: 2, color: 'darkgrey' }}> Give the variable names and options for this templated query.</Typography>
<JsonEditField
required
label="Fixed templated query variables"
name="variables"
helperText="Enter your fixed templated variables specification in JSON-format."
value={formData.variables === '' ? '' : formData.variables || defaultTemplateOptions}
onChange={handleChange}
/>
</div>
}
{isChecked(formData.directVariablesCheck) &&
<div>
<Typography variant="base" sx={{ mt: 2, color: 'darkgrey' }}> Give the variable names and options for this templated query.</Typography>
<JsonEditField
required
label="Fixed templated query variables"
name="variables"
helperText="Enter your fixed templated variables specification in JSON-format."
value={formData.variables === '' ? '' : formData.variables || defaultTemplateOptions}
onChange={handleChange}
/>
</div>
}

<div>
<FormControlLabel
control={<Checkbox
name='indirectVariablesCheck'
Expand All @@ -483,47 +485,46 @@ export default function CustomEditor(props) {
}
}
/>} label="Indirect Variables" />
</div>

{isChecked(formData.indirectVariablesCheck) &&
<div>
<div style={{ marginBottom: '20px' }}>
<Typography variant="base" sx={{ color: '#777' }}> Give one or more SPARQL queries to retrieve variable(s) from source(s).</Typography>
</div>
{
indirectVariablesQueryList.map((ivQuery, index) => (
<div key={index} style={{ position: 'relative' }}>
<SparqlEditField
required
label={`SPARQL query ${index + 1} for indirect variable(s)`}
name={`indirectVariablesQuery-${index}`}
helperText={`Enter a ${index === 0 ? "1st" : index === 1 ? "2nd" : index + 1 + "th"} SPARQL query to retrieve variables.`}
value={ivQuery}
onChange={handleChange}
/>

<Button
variant="outlined"
color='error' onClick={() => handleRemoveIndirectVariablesQuery(index)}
type="button" disabled={indirectVariablesQueryList.length <= 1}
style={{ zIndex: '2', position: 'absolute', top: '30px', right: '17px', padding: '8px', minWidth: 'auto', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
>
<IconProvider.DeleteIcon />
</Button>
</div>
))
}
<Button variant="outlined" onClick={handleAddIndirectVariablesQuery} type="button" startIcon={<IconProvider.AddIcon />}>
Add another query
</Button>
{isChecked(formData.indirectVariablesCheck) &&
<div>
<div style={{ marginBottom: '20px' }}>
<Typography variant="base" sx={{ color: '#777' }}> Give one or more SPARQL queries to retrieve variable(s) from source(s).</Typography>
</div>
}
</div>
{
indirectVariablesQueryList.map((ivQuery, index) => (
<div key={index} style={{ position: 'relative' }}>
<SparqlEditField
required
label={`SPARQL query ${index + 1} for indirect variable(s)`}
name={`indirectVariablesQuery-${index}`}
helperText={`Enter a ${index === 0 ? "1st" : index === 1 ? "2nd" : index + 1 + "th"} SPARQL query to retrieve variables.`}
value={ivQuery}
onChange={handleChange}
/>

<Button
variant="outlined"
color='error' onClick={() => handleRemoveIndirectVariablesQuery(index)}
type="button" disabled={indirectVariablesQueryList.length <= 1}
style={{ zIndex: '2', position: 'absolute', top: '30px', right: '17px', padding: '8px', minWidth: 'auto', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
>
<IconProvider.DeleteIcon />
</Button>
</div>
))
}
<Button variant="outlined" onClick={handleAddIndirectVariablesQuery} type="button" startIcon={<IconProvider.AddIcon />}>
Add another query
</Button>
</div>
}
</Card>

<Card sx={{ py: '10px', px: '20px', my: 2 }}>
<Typography variant="h5" sx={{ mt: 2 }}> Extra Options</Typography>
<div>

<FormControlLabel
control={<Checkbox
name='askQueryCheck'
Expand All @@ -537,20 +538,20 @@ export default function CustomEditor(props) {
}
}
/>} label="ASK query" />

{isChecked(formData.askQueryCheck) &&
<div>
<JsonEditField
required
label="Creating an ask query"
name="askQuery"
helperText="Enter your ASK query specification in JSON-format."
value={formData.askQuery === '' ? '' : formData.askQuery || defaultAskQueryDetails}
onChange={handleChange}
/>
</div>
}

</div>
{isChecked(formData.askQueryCheck) &&
<div>
<JsonEditField
required
label="Creating an ask query"
name="askQuery"
helperText="Enter your ASK query specification in JSON-format."
value={formData.askQuery === '' ? '' : formData.askQuery || defaultAskQueryDetails}
onChange={handleChange}
/>
</div>
}
<div>
<FormControlLabel
control={<Checkbox
name='httpProxiesCheck'
Expand All @@ -564,21 +565,19 @@ export default function CustomEditor(props) {
}
}
/>} label="Http proxies" />

{isChecked(formData.httpProxiesCheck) &&
<div>
<JsonEditField
required
label="Specifying HTTP proxies"
name="httpProxies"
helperText="Enter your HTTP proxies specification JSON-format."
value={formData.httpProxies === '' ? '' : formData.httpProxies || defaultHttpProxiesDetails}
onChange={handleChange}
/>
</div>
}

</div>
{isChecked(formData.httpProxiesCheck) &&
<div>
<JsonEditField
required
label="Specifying HTTP proxies"
name="httpProxies"
helperText="Enter your HTTP proxies specification JSON-format."
value={formData.httpProxies === '' ? '' : formData.httpProxies || defaultHttpProxiesDetails}
onChange={handleChange}
/>
</div>
}
</Card>

</CardContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import CustomConversionButton from "../../CustomQueryEditor/customConversionButt
* @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, variableValues, changeVariables, submitted } = props;
const { updateTimestamp, resource, variableValues, changeVariables, submitted } = props;
const resourceDef = useResourceDefinition();
const queryTitle = resourceDef?.options?.label;
const config = configManager.getConfig();
Expand Down Expand Up @@ -54,7 +54,8 @@ function QueryResultList(props) {
empty={false}
queryOptions={{
meta: {
variableValues: variableValues
variableValues,
updateTimestamp // force the dataProvider to refetch the data when the updateTimestamp changes
}}}>
<MyDatagrid {...props} query={query} />
</List>
Expand All @@ -63,6 +64,7 @@ function QueryResultList(props) {
}

QueryResultList.propTypes = {
updateTimestamp: PropTypes.number.isRequired,
resource: PropTypes.string.isRequired,
variableValues: PropTypes.object.isRequired,
changeVariables: PropTypes.func.isRequired,
Expand Down
24 changes: 21 additions & 3 deletions main/src/components/ListResultTable/TemplatedListResultTable.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useState, useEffect, Component } from 'react';
import { useState, useEffect, Component } from 'react';
import { useResourceContext, Loading, useDataProvider, useResourceDefinition } from "react-admin";
import { useLocation, useNavigate } from 'react-router-dom';
import TemplatedQueryForm from "./TemplatedQueryForm.jsx";
import QueryResultList from "./QueryResultList/QueryResultList";
import ErrorDisplay from "../../components/ErrorDisplay/ErrorDisplay";

import configManager from '../../configManager/configManager.js';
import comunicaEngineWrapper from '../../comunicaEngineWrapper/comunicaEngineWrapper.js';

// LOG let templatedListResultTableCounter = 0;

Expand All @@ -21,6 +22,7 @@ const TemplatedListResultTable = (props) => {
const location = useLocation();
const navigate = useNavigate();
const query = configManager.getQueryWorkingCopyById(resource);
const [updateTimestamp, setUpdateTimestamp] = useState(0);
const [askingForVariableOptions, setAskingForVariableOptions] = useState(false);
const [waitingForVariableOptions, setWaitingForVariableOptions] = useState(false);
const [variableOptionsError, setVariableOptionsError] = useState("");
Expand All @@ -33,6 +35,7 @@ const TemplatedListResultTable = (props) => {
// LOG console.log(`--- TemplatedListResultTable #${++templatedListResultTableCounter}`);
// LOG console.log(`props: ${JSON.stringify(props, null, 2)}`);
// LOG console.log(`resource: ${resource}`);
// LOG console.log(`updateTimestamp: ${updateTimestamp}`);
// LOG console.log(`askingForVariableOptions: ${askingForVariableOptions}`);
// LOG console.log(`waitingForVariableOptions: ${waitingForVariableOptions}`);
// LOG console.log(`variableOptionsError: ${variableOptionsError}`);
Expand All @@ -42,6 +45,22 @@ const TemplatedListResultTable = (props) => {
// LOG console.log(`isTemplatedQuery: ${isTemplatedQuery}`);
// LOG console.log(`templatedQueryFormEnabled: ${templatedQueryFormEnabled}`);

useEffect(() => {
const t = location.state?.updateTimestamp;
if (t && t != updateTimestamp) {
setUpdateTimestamp(location.state.updateTimestamp);
// LOG console.log(`New updateTimestamp: ${t}`);
setAskingForVariableOptions(false);
setWaitingForVariableOptions(false);
setVariableOptionsError("");
setVariableOptions({});
setVariableValues({});
setVariablesSubmitted(false);
// we need next because comunica would use its cache even if some of its context parameters have changed
comunicaEngineWrapper.reset();
}
}, [location.state]);

useEffect(() => {
(async () => {
if (askingForVariableOptions) {
Expand All @@ -62,7 +81,6 @@ const TemplatedListResultTable = (props) => {
})();
}, [askingForVariableOptions]);


// Cover a transient state after creation of a new custom query. EventEmitter's event processing may still be in progress.
if (!resourceDef.options) {
// LOG console.log('TemplatedListResultTable waiting for custom query creation to complete.');
Expand Down Expand Up @@ -131,7 +149,7 @@ const TemplatedListResultTable = (props) => {
return (
templatedQueryFormEnabled
? <TemplatedQueryForm variableOptions={variableOptions} defaultFormVariables={variableValues} onSubmit={submitVariables} />
: <QueryResultList {...props} resource={resource} variableValues={variableValues} changeVariables={changeVariables} submitted={variablesSubmitted} />
: <QueryResultList updateTimestamp={updateTimestamp}{...props} resource={resource} variableValues={variableValues} changeVariables={changeVariables} submitted={variablesSubmitted} />
)
}

Expand Down
Loading