diff --git a/CHANGELOG.md b/CHANGELOG.md
index 663ba10..fb61bf1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Avoided (infinite) display of "The list is loading. Just a moment please." for queries that should show "The result list is empty.", in cases where the user does not have the right to read the involved source(s) (#209).
- Result list sorting works again and the behavior is improved - see the issue for details (#216).
- Loading values for variables ends with an error message if a required source is not available (#218).
+- When visiting a templated query with variable values defined in the URL search parameters,
+ the resolved query is executed immediately, avoiding the delay it takes to first retrieve all options for the variables (#211).
## [1.7.0] - 2025-04-09
diff --git a/main/src/components/ListResultTable/TemplatedListResultTable.jsx b/main/src/components/ListResultTable/TemplatedListResultTable.jsx
index c75706a..53245c6 100644
--- a/main/src/components/ListResultTable/TemplatedListResultTable.jsx
+++ b/main/src/components/ListResultTable/TemplatedListResultTable.jsx
@@ -21,8 +21,9 @@ const TemplatedListResultTable = (props) => {
const location = useLocation();
const navigate = useNavigate();
const query = configManager.getQueryWorkingCopyById(resource);
- const [waitingForVariableOptions, setWaitingForVariableOptions] = useState(!!(query.variables || query.indirectVariables));
- const [waitingForVariableOptionsError, setWaitingForVariableOptionsError] = useState("");
+ const [askingForVariableOptions, setAskingForVariableOptions] = useState(false);
+ const [waitingForVariableOptions, setWaitingForVariableOptions] = useState(false);
+ const [variableOptionsError, setVariableOptionsError] = useState("");
const [variableOptions, setVariableOptions] = useState({});
const [variableValues, setVariableValues] = useState({});
const [variablesSubmitted, setVariablesSubmitted] = useState(false);
@@ -32,8 +33,9 @@ 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(`askingForVariableOptions: ${askingForVariableOptions}`);
// LOG console.log(`waitingForVariableOptions: ${waitingForVariableOptions}`);
- // LOG console.log(`waitingForVariableOptionsError: ${waitingForVariableOptionsError}`);
+ // LOG console.log(`variableOptionsError: ${variableOptionsError}`);
// LOG console.log(`variableOptions: ${JSON.stringify(variableOptions, null, 2)}`);
// LOG console.log(`variableValues: ${JSON.stringify(variableValues, null, 2)}`);
// LOG console.log(`variablesSubmitted: ${variablesSubmitted}`);
@@ -42,21 +44,23 @@ const TemplatedListResultTable = (props) => {
useEffect(() => {
(async () => {
- if (query.variables || query.indirectVariables) {
+ if (askingForVariableOptions) {
+ setAskingForVariableOptions(false);
try {
- // LOG console.log('start waiting for variable options');
+ // LOG console.log('Start waiting for variable options');
+ setWaitingForVariableOptions(true);
// LOG const t1 = Date.now();
setVariableOptions(await dataProvider.getVariableOptions(query));
// LOG const t2 = Date.now();
- // LOG console.log(`done waiting for variable options after ${t2-t1} ms`);
+ // LOG console.log(`Done waiting for variable options after ${t2-t1} ms`);
setWaitingForVariableOptions(false);
} catch (error) {
- // LOG console.log(`error getting variable options: ${error.message}`);
- setWaitingForVariableOptionsError(error.message);
+ // LOG console.log(`Error getting variable options: ${error.message}`);
+ setVariableOptionsError(error.message);
}
}
})();
- }, [resource]);
+ }, [askingForVariableOptions]);
// Cover a transient state after creation of a new custom query. EventEmitter's event processing may still be in progress.
@@ -65,31 +69,43 @@ const TemplatedListResultTable = (props) => {
return false;
}
- if (waitingForVariableOptionsError) {
- // LOG console.log(`TemplatedListResultTable failure while waiting for variable options: ${waitingForVariableOptionsError}`);
- return ;
- }
-
- if (waitingForVariableOptions) {
- // LOG console.log('TemplatedListResultTable waiting for variable options.');
- return ;
+ if (variableOptionsError) {
+ // LOG console.log(`TemplatedListResultTable variable options error: ${variableOptionsError}`);
+ return ;
}
if (isTemplatedQuery) {
- // Check if an update of variable values is needed from user supplied url search parameters
- const possibleNewVariableValues = variableValuesFromUrlSearchParams(new URLSearchParams(location.search), variableOptions);
- // Protect against incomplete or omitted variable values, as is the case when changing pagination,
- // where List causes a revisit but does not include variable values in url search parameters
- if (Object.keys(possibleNewVariableValues).length == Object.keys(variableOptions).length) {
- if (Object.keys(variableOptions).some((v) => variableValues[v] != possibleNewVariableValues[v])) {
- // LOG console.log("Accepting new variable values from user supplied url search parameters.");
- setVariableValues(possibleNewVariableValues);
+ if (!Object.keys(variableOptions).length) {
+ // Check for initial visit with url search parameters
+ if (!askingForVariableOptions && !waitingForVariableOptions && !variablesSubmitted) {
+ const vv = initialVariableValuesFromUrlSearchParams(new URLSearchParams(location.search));
+ if (Object.keys(vv).length) {
+ // LOG console.log("Accepting initial variable values from url search parameters.");
+ setVariableValues(vv);
+ setVariablesSubmitted(true);
+ return false;
+ }
+ // LOG console.log("Trigger the search for variable options in body.");
+ setAskingForVariableOptions(true);
+ return false;
+ }
+ } else {
+ // Check for next visit with url search parameters
+ const vv = newVariableValuesFromUrlSearchParams(new URLSearchParams(location.search), variableValues);
+ if (vv && Object.keys(variableValues).some((v) => variableValues[v] != vv[v])) {
+ // LOG console.log("Accepting new variable values from url search parameters.");
+ setVariableValues(vv);
setVariablesSubmitted(true);
return false;
}
}
}
+ if (waitingForVariableOptions) {
+ // LOG console.log('TemplatedListResultTable waiting for variable options.');
+ return ;
+ }
+
const submitVariables = (formVariables) => {
// Create url search parameters from new variable values received from the TemplatedQueryForm fields
// Note: possible previous url search parameters involving pagination are discarded here on purpose
@@ -102,6 +118,12 @@ const TemplatedListResultTable = (props) => {
const changeVariables = () => {
setVariablesSubmitted(false);
+ if (!Object.keys(variableOptions).length) {
+ if (!askingForVariableOptions) {
+ // LOG console.log("Trigger the search for variable options in changeVariables.");
+ setAskingForVariableOptions(true);
+ }
+ }
// revisit with same search parameters
navigate(location.search);
}
@@ -129,19 +151,35 @@ function urlSearchParamsFromVariableValues(variableValues) {
}
/**
- * Make variableValues from urlSearchParams
+ * Make initial variableValues from urlSearchParams
* @param {URLSearchParams} urlSearchParams
- * @param {Object} variableOptions used to filter
* @returns {Object} variableValues
*/
-function variableValuesFromUrlSearchParams(urlSearchParams, variableOptions) {
+function initialVariableValuesFromUrlSearchParams(urlSearchParams) {
const variableValues = {};
- for (const variableName of Object.keys(variableOptions)) {
+ for (const [key, value] of urlSearchParams.entries()) {
+ variableValues[key] = value;
+ }
+ return variableValues;
+}
+
+/**
+ * Make new variableValues from urlSearchParams
+ * @param {URLSearchParams} urlSearchParams the input
+ * @param {Object} variableValues the current variableValues
+ * @returns {Object} new variableValues or undefined, if not all variable keys in the input
+ */
+function newVariableValuesFromUrlSearchParams(urlSearchParams, variableValues) {
+ const newVariableValues = {};
+ for (const variableName of Object.keys(variableValues)) {
if (urlSearchParams.has(variableName)) {
- variableValues[variableName] = urlSearchParams.get(variableName);
+ newVariableValues[variableName] = urlSearchParams.get(variableName);
}
}
- return variableValues;
+ if (Object.keys(newVariableValues).length == Object.keys(variableValues).length) {
+ return newVariableValues;
+ }
+ return undefined;
}
export default TemplatedListResultTable;
diff --git a/test/cypress/e2e/indirect-variables.cy.js b/test/cypress/e2e/indirect-variables.cy.js
deleted file mode 100644
index 3deeef2..0000000
--- a/test/cypress/e2e/indirect-variables.cy.js
+++ /dev/null
@@ -1,120 +0,0 @@
-describe("Indirect variable query", () => {
-
- it("Indirect with 1 variable", () => {
-
- cy.visit("/");
- cy.contains("Example queries").click();
- cy.contains("A templated query about musicians (indirect variables)").click();
-
- // Fill in the form
- cy.get('.ra-input-genre').click();
- cy.get('li').contains('Baroque').click();
-
- // Comfirm query
- cy.get('button').contains('Query').click();
-
- // Check that the page loaded and that we can see the correct data
- cy.contains("Finished in:");
- cy.get('.column-name').find('span').contains("Johann Sebastian Bach").should("exist");;
- cy.get('.column-name').find('span').contains("Marc-Antoine Charpentier").should("exist");;
- // Check that we don't see artists that don't belong here
- cy.get('.column-name').find('span').contains("Franz Schubert").should("not.exist");
- cy.get('.column-name').find('span').contains("Wolfgang Amadeus Mozart").should("not.exist");
-
- });
-
- it("Indirect with 2 variables", () => {
-
- cy.visit("/");
- cy.contains("For testing only").click();
- cy.contains("A templated query about musicians, two variables (indirect variables)").click();
-
- // Fill in the form
- cy.get('.ra-input-genre').click();
- cy.get('li').contains('Classical').click();
-
- cy.get('.ra-input-sameAsUrl').click();
- cy.get('li').contains('Mozart').click();
-
- cy.get('button').contains('Query').click();
-
- // Check that it is correctly loaded with and only the correct data appears
- cy.contains("Finished in:");
- cy.get('.column-name').find('span').contains("Wolfgang Amadeus Mozart").should("exist");;
-
- cy.get('.column-name').find('span').contains("Franz Schubert").should("not.exist");
- cy.get('.column-name').find('span').contains("Johann Sebastian Bach").should("not.exist");
- cy.get('.column-name').find('span').contains("Ludwig van Beethoven").should("not.exist");
-
- });
-
-
- it("Indirect with 1 variable and sources from indexfile", () => {
-
- cy.visit("/");
- cy.contains("For testing only").click();
- cy.contains("Component and materials - 1 variable (indirect source & indirect variables)").click();
-
- // Fill in the form
- cy.get('.ra-input-componentName').click();
- cy.get('li').contains('Component 1').click();
-
- // Comfirm query
- cy.get('button[type="submit"]').click();
-
- // Check that it is correctly loaded with and only the correct data appears
- cy.contains("Finished in:");
-
- cy.get('.column-componentName').find('span').contains("Component 1").should("exist");
- cy.get('.column-materialName').find('span').contains("Material 2").should("exist");
- cy.get('.column-materialName').find('span').contains("Material 1").should("exist");
-
- cy.get('.column-componentName').find('span').contains("Component 2").should("not.exist");
- cy.get('.column-componentName').find('span').contains("Component 3").should("not.exist");
- cy.get('.column-materialName').find('span').contains("Material 6").should("not.exist");
-
-
-
- });
-
- it("Indirect with 2 variables and sources from indexfile", () => {
- cy.visit("/");
- cy.contains("For testing only").click();
- cy.contains("Component and materials - 2 variables (indirect source & indirect variables)").click();
-
- // Fill in the form
- cy.get('.ra-input-componentName').click();
- cy.get('li').contains('Component 1').click();
-
- cy.get('.ra-input-materialName').click();
- cy.get('li').contains('Material 2').click();
-
- // Comfirm query
- cy.get('button[type="submit"]').click();
-
- // Check that it is correctly loaded with and only the correct data appears
- cy.contains("Finished in:");
-
- cy.get('.column-componentName').find('span').contains("Component 1").should("exist");
- cy.get('.column-materialName').find('span').contains("Material 2").should("exist");
-
- cy.get('.column-materialName').find('span').contains("Material 1").should("not.exist");
- cy.get('.column-componentName').find('span').contains("Component 2").should("not.exist");
- cy.get('.column-componentName').find('span').contains("Component 3").should("not.exist");
- cy.get('.column-materialName').find('span').contains("Material 6").should("not.exist");
-
-
- });
-
- it("Indirect with 1 variable but no sources available to get variables from", () => {
-
- cy.visit("/");
- cy.contains("For testing only").click();
- cy.contains("A templated query about musicians (indirect variables) - no sources").click();
-
- cy.contains("Error getting variable options...").should("exist");;
- });
-});
-
-
-
diff --git a/test/cypress/e2e/templated-query.cy.js b/test/cypress/e2e/templated-query.cy.js
index 2e1faa0..b7046b2 100644
--- a/test/cypress/e2e/templated-query.cy.js
+++ b/test/cypress/e2e/templated-query.cy.js
@@ -141,61 +141,6 @@ describe("Templated query", () => {
});
- it("With 2 variables; visit with variable values given in url search parameters", () => {
- // a first url
- cy.visit("/#/1100?genre=%22Classical%22&sameAsUrl=%3Chttps%3A%2F%2Fen.wikipedia.org%2Fwiki%2FWolfgang_Amadeus_Mozart%3E");
-
- // Check the display of the variable(s) and their value
- cy.contains("genre: \"Classical\"");
- cy.contains("sameAsUrl: ");
-
- // Check that the page loaded and that we can see the correct data
- cy.contains("Finished in:");
- cy.contains("1-1 of 1");
- cy.get('.column-name').find('span').contains("Wolfgang Amadeus Mozart").should("exist");;
-
- // Check if the button to make a new query exists and use it
- cy.get('button').contains("Change Variables").should("exist");
- cy.get('button').contains("Change Variables").click();
-
- // Making sure we get the form to enter new variables
- // and that the previously selected value(s) are still there
- cy.get('.ra-input-genre').find('input').should('have.value', '"Classical"');
- cy.get('.ra-input-sameAsUrl').find('input').should('have.value', '');
-
- // Change variables and make another existing combination
- cy.get('.ra-input-genre').click();
- cy.get('li').contains('Baroque').click();
-
- cy.get('.ra-input-sameAsUrl').click();
- cy.get('li').contains('Bach').click();
-
- cy.get('button[type="submit"]').click();
-
- cy.contains("Finished in:");
- cy.contains("1-1 of 1");
- cy.get('.column-name').find('span').contains("Johann Sebastian Bach").should("exist");
-
- // check if the url is reflecting the new variable values
- cy.url().should("have.string", "genre=%22Baroque%22");
- cy.url().should("have.string", "sameAsUrl=%3Chttps%3A%2F%2Fen.wikipedia.org%2Fwiki%2FJohann_Sebastian_Bach%3E");
-
- // a second url
- cy.visit("/#/1100?genre=%22Romantic%22&sameAsUrl=%3Chttps%3A%2F%2Fen.wikipedia.org%2Fwiki%2FFranz_Schubert%3E");
-
- // Check the display of the variable(s) and their value
- cy.contains("genre: \"Romantic\"");
- cy.contains("sameAsUrl: ");
-
- // Check that the page loaded and that we can see the correct data
- cy.contains("Finished in:");
- cy.contains("1-1 of 1");
- cy.get('.column-name').find('span').contains("Franz Schubert").should("exist");;
-
- // Check if the button to make a new query exists
- cy.get('button').contains("Change Variables").should("exist");
- });
-
it("Correct message displayed when no resulting data", () => {
cy.visit("/");
cy.contains("Example queries").click();
@@ -245,4 +190,170 @@ describe("Templated query", () => {
});
+ it("Indirect with 1 variable", () => {
+
+ cy.visit("/");
+ cy.contains("Example queries").click();
+ cy.contains("A templated query about musicians (indirect variables)").click();
+
+ // Fill in the form
+ cy.get('.ra-input-genre').click();
+ cy.get('li').contains('Baroque').click();
+
+ // Comfirm query
+ cy.get('button').contains('Query').click();
+
+ // Check that the page loaded and that we can see the correct data
+ cy.contains("Finished in:");
+ cy.get('.column-name').find('span').contains("Johann Sebastian Bach").should("exist");;
+ cy.get('.column-name').find('span').contains("Marc-Antoine Charpentier").should("exist");;
+ // Check that we don't see artists that don't belong here
+ cy.get('.column-name').find('span').contains("Franz Schubert").should("not.exist");
+ cy.get('.column-name').find('span').contains("Wolfgang Amadeus Mozart").should("not.exist");
+
+ });
+
+ it("Indirect with 2 variables", () => {
+
+ cy.visit("/");
+ cy.contains("For testing only").click();
+ cy.contains("A templated query about musicians, two variables (indirect variables)").click();
+
+ // Fill in the form
+ cy.get('.ra-input-genre').click();
+ cy.get('li').contains('Classical').click();
+
+ cy.get('.ra-input-sameAsUrl').click();
+ cy.get('li').contains('Mozart').click();
+
+ cy.get('button').contains('Query').click();
+
+ // Check that it is correctly loaded with and only the correct data appears
+ cy.contains("Finished in:");
+ cy.get('.column-name').find('span').contains("Wolfgang Amadeus Mozart").should("exist");;
+
+ cy.get('.column-name').find('span').contains("Franz Schubert").should("not.exist");
+ cy.get('.column-name').find('span').contains("Johann Sebastian Bach").should("not.exist");
+ cy.get('.column-name').find('span').contains("Ludwig van Beethoven").should("not.exist");
+
+ });
+
+
+ it("Indirect with 1 variable and sources from indexfile", () => {
+
+ cy.visit("/");
+ cy.contains("For testing only").click();
+ cy.contains("Component and materials - 1 variable (indirect source & indirect variables)").click();
+
+ // Fill in the form
+ cy.get('.ra-input-componentName').click();
+ cy.get('li').contains('Component 1').click();
+
+ // Comfirm query
+ cy.get('button[type="submit"]').click();
+
+ // Check that it is correctly loaded with and only the correct data appears
+ cy.contains("Finished in:");
+
+ cy.get('.column-componentName').find('span').contains("Component 1").should("exist");
+ cy.get('.column-materialName').find('span').contains("Material 2").should("exist");
+ cy.get('.column-materialName').find('span').contains("Material 1").should("exist");
+
+ cy.get('.column-componentName').find('span').contains("Component 2").should("not.exist");
+ cy.get('.column-componentName').find('span').contains("Component 3").should("not.exist");
+ cy.get('.column-materialName').find('span').contains("Material 6").should("not.exist");
+
+ });
+
+ it("Indirect with 2 variables and sources from indexfile", () => {
+ cy.visit("/");
+ cy.contains("For testing only").click();
+ cy.contains("Component and materials - 2 variables (indirect source & indirect variables)").click();
+
+ // Fill in the form
+ cy.get('.ra-input-componentName').click();
+ cy.get('li').contains('Component 1').click();
+
+ cy.get('.ra-input-materialName').click();
+ cy.get('li').contains('Material 2').click();
+
+ // Comfirm query
+ cy.get('button[type="submit"]').click();
+
+ // Check that it is correctly loaded with and only the correct data appears
+ cy.contains("Finished in:");
+
+ cy.get('.column-componentName').find('span').contains("Component 1").should("exist");
+ cy.get('.column-materialName').find('span').contains("Material 2").should("exist");
+
+ cy.get('.column-materialName').find('span').contains("Material 1").should("not.exist");
+ cy.get('.column-componentName').find('span').contains("Component 2").should("not.exist");
+ cy.get('.column-componentName').find('span').contains("Component 3").should("not.exist");
+ cy.get('.column-materialName').find('span').contains("Material 6").should("not.exist");
+
+ });
+
+ it("Indirect with 1 variable but no sources available to get variables from", () => {
+
+ cy.visit("/");
+ cy.contains("For testing only").click();
+ cy.contains("A templated query about musicians (indirect variables) - no sources").click();
+
+ cy.contains("Error getting variable options...").should("exist");;
+ });
+
+ it("Indirect with 2 variables - variable values from url search parameters", () => {
+
+ // a first url
+ cy.visit("/#/9040?genre=%22Classical%22&sameAsUrl=%3Chttps%3A%2F%2Fen.wikipedia.org%2Fwiki%2FWolfgang_Amadeus_Mozart%3E");
+
+ // Check the display of the variable(s) and their value
+ cy.contains("genre: \"Classical\"");
+ cy.contains("sameAsUrl: ");
+
+ // Check that the page loaded and that we can see the correct data
+ cy.contains("Finished in:");
+ cy.contains("1-1 of 1");
+ cy.get('.column-name').find('span').contains("Wolfgang Amadeus Mozart").should("exist");;
+
+ // Check if the button to make a new query exists and use it
+ cy.get('button').contains("Change Variables").should("exist");
+ cy.get('button').contains("Change Variables").click();
+
+ // Making sure we get the form to enter new variables
+ // and that the previously selected value(s) are still there
+ cy.get('.ra-input-genre').find('input').should('have.value', '"Classical"');
+ cy.get('.ra-input-sameAsUrl').find('input').should('have.value', '');
+
+ // Change variables and make another existing combination
+ cy.get('.ra-input-genre').click();
+ cy.get('li').contains('Baroque').click();
+
+ cy.get('.ra-input-sameAsUrl').click();
+ cy.get('li').contains('Bach').click();
+
+ cy.get('button[type="submit"]').click();
+
+ cy.contains("Finished in:");
+ cy.contains("1-1 of 1");
+ cy.get('.column-name').find('span').contains("Johann Sebastian Bach").should("exist");
+
+ // check if the url is reflecting the new variable values
+ cy.url().should("have.string", "genre=%22Baroque%22");
+ cy.url().should("have.string", "sameAsUrl=%3Chttps%3A%2F%2Fen.wikipedia.org%2Fwiki%2FJohann_Sebastian_Bach%3E");
+
+ // a second url
+ cy.visit("/#/9040?genre=%22Romantic%22&sameAsUrl=%3Chttps%3A%2F%2Fen.wikipedia.org%2Fwiki%2FFranz_Schubert%3E");
+
+ // Check the display of the variable(s) and their value
+ cy.contains("genre: \"Romantic\"");
+ cy.contains("sameAsUrl: ");
+
+ // Check that the page loaded and that we can see the correct data
+ cy.contains("Finished in:");
+ cy.contains("1-1 of 1");
+ cy.get('.column-name').find('span').contains("Franz Schubert").should("exist");;
+
+ });
+
});