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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
100 changes: 69 additions & 31 deletions main/src/components/ListResultTable/TemplatedListResultTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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}`);
Expand All @@ -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.
Expand All @@ -65,31 +69,43 @@ const TemplatedListResultTable = (props) => {
return false;
}

if (waitingForVariableOptionsError) {
// LOG console.log(`TemplatedListResultTable failure while waiting for variable options: ${waitingForVariableOptionsError}`);
return <ErrorDisplay errorMessage={waitingForVariableOptionsError} />;
}

if (waitingForVariableOptions) {
// LOG console.log('TemplatedListResultTable waiting for variable options.');
return <Loading sx={{ height: "auto" }} loadingSecondary={"The options for the variables in this query are loading. Just a moment please."} />;
if (variableOptionsError) {
// LOG console.log(`TemplatedListResultTable variable options error: ${variableOptionsError}`);
return <ErrorDisplay errorMessage={variableOptionsError} />;
}

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 <Loading sx={{ height: "auto" }} loadingSecondary={"The options for the variables in this query are loading. Just a moment please."} />;
}

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
Expand All @@ -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);
}
Expand Down Expand Up @@ -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;
120 changes: 0 additions & 120 deletions test/cypress/e2e/indirect-variables.cy.js

This file was deleted.

Loading