From 9de81de3c37f49607a50289b3d6c06fa8a545ad3 Mon Sep 17 00:00:00 2001 From: Vladislav Denisov Date: Wed, 12 Nov 2025 11:57:47 +0100 Subject: [PATCH 1/9] Enhance dashboard parameter handling: persist updated values and apply saved parameters --- client/app/pages/dashboards/DashboardPage.jsx | 2 +- .../pages/dashboards/hooks/useDashboard.js | 48 +++++++++++++++++-- client/app/services/dashboard.js | 6 ++- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/client/app/pages/dashboards/DashboardPage.jsx b/client/app/pages/dashboards/DashboardPage.jsx index 63c796ee4b..1d8b495c56 100644 --- a/client/app/pages/dashboards/DashboardPage.jsx +++ b/client/app/pages/dashboards/DashboardPage.jsx @@ -92,7 +92,7 @@ function DashboardComponent(props) { const [bottomPanelStyles, setBottomPanelStyles] = useState({}); const onParametersEdit = parameters => { const paramOrder = map(parameters, "name"); - updateDashboard({ options: { globalParamOrder: paramOrder } }); + updateDashboard({ options: { ...(dashboard.options || {}), globalParamOrder: paramOrder } }); }; useEffect(() => { diff --git a/client/app/pages/dashboards/hooks/useDashboard.js b/client/app/pages/dashboards/hooks/useDashboard.js index 43eeb336d9..ff86fa2f2a 100644 --- a/client/app/pages/dashboards/hooks/useDashboard.js +++ b/client/app/pages/dashboards/hooks/useDashboard.js @@ -1,5 +1,5 @@ import { useState, useEffect, useMemo, useCallback, useRef } from "react"; -import { isEmpty, includes, compact, map, has, pick, keys, extend, every, get } from "lodash"; +import { isEmpty, includes, compact, map, has, pick, keys, extend, every, get, isEqual } from "lodash"; import notification from "@/services/notification"; import location from "@/services/location"; import url from "@/services/url"; @@ -144,14 +144,52 @@ function useDashboard(dashboardData) { [loadWidget] ); + const persistParameterValues = useCallback( + updatedParameters => { + if (!canEditDashboard || isEmpty(updatedParameters)) { + return Promise.resolve(); + } + + const currentDashboard = dashboardRef.current; + const currentValues = get(currentDashboard, "options.parameterValues", {}); + const nextValues = extend({}, currentValues); + let hasChanges = false; + + updatedParameters.forEach(param => { + if (!param) { + return; + } + + if (!isEqual(nextValues[param.name], param.value)) { + nextValues[param.name] = param.value === undefined ? null : param.value; + hasChanges = true; + } + }); + + if (!hasChanges) { + return Promise.resolve(); + } + + return updateDashboard({ + options: extend({}, get(currentDashboard, "options", {}), { parameterValues: nextValues }), + }); + }, + [canEditDashboard, updateDashboard] + ); + const refreshDashboard = useCallback( updatedParameters => { - if (!refreshing) { - setRefreshing(true); - loadDashboard(true, updatedParameters).finally(() => setRefreshing(false)); + if (refreshing) { + return; } + + setRefreshing(true); + Promise.resolve(persistParameterValues(updatedParameters)) + .catch(() => {}) + .then(() => loadDashboard(true, updatedParameters)) + .finally(() => setRefreshing(false)); }, - [refreshing, loadDashboard] + [refreshing, loadDashboard, persistParameterValues] ); const archiveDashboard = useCallback(() => { diff --git a/client/app/services/dashboard.js b/client/app/services/dashboard.js index a4d3550ba5..24a752da1b 100644 --- a/client/app/services/dashboard.js +++ b/client/app/services/dashboard.js @@ -210,9 +210,13 @@ Dashboard.prototype.getParametersDefs = function getParametersDefs() { }); } }); + const savedParameterValues = _.get(this, "options.parameterValues", {}); const resultingGlobalParams = _.values( _.each(globalParams, param => { - param.setValue(param.value); // apply global param value to all locals + const valueToApply = _.has(savedParameterValues, param.name) + ? savedParameterValues[param.name] + : param.value; + param.setValue(valueToApply); // apply global param value (saved or default) to all locals param.fromUrlParams(queryParams); // try to initialize from url (may do nothing) }) ); From fd3eb539bb037071ea533bc3d4cf14127e586d5e Mon Sep 17 00:00:00 2001 From: Vladislav Denisov Date: Wed, 12 Nov 2025 12:12:50 +0100 Subject: [PATCH 2/9] Fix parameter value persistence: handle undefined values and add error notification --- client/app/pages/dashboards/hooks/useDashboard.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/app/pages/dashboards/hooks/useDashboard.js b/client/app/pages/dashboards/hooks/useDashboard.js index ff86fa2f2a..9a0e05b24a 100644 --- a/client/app/pages/dashboards/hooks/useDashboard.js +++ b/client/app/pages/dashboards/hooks/useDashboard.js @@ -161,7 +161,7 @@ function useDashboard(dashboardData) { } if (!isEqual(nextValues[param.name], param.value)) { - nextValues[param.name] = param.value === undefined ? null : param.value; + nextValues[param.name] = param.value; hasChanges = true; } }); @@ -185,7 +185,10 @@ function useDashboard(dashboardData) { setRefreshing(true); Promise.resolve(persistParameterValues(updatedParameters)) - .catch(() => {}) + .catch(error => { + console.error("Failed to persist parameter values:", error); + notification.error("Parameter values could not be saved. Your changes may not be persisted."); + }) .then(() => loadDashboard(true, updatedParameters)) .finally(() => setRefreshing(false)); }, From 35b89705014559342137ff0fdaf1afbf94cf95b1 Mon Sep 17 00:00:00 2001 From: Vladislav Denisov Date: Fri, 14 Nov 2025 09:50:51 +0100 Subject: [PATCH 3/9] Enhance parameter persistence: manage pending values during layout editing and improve value update logic --- .../pages/dashboards/hooks/useDashboard.js | 65 ++++++++++++++----- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/client/app/pages/dashboards/hooks/useDashboard.js b/client/app/pages/dashboards/hooks/useDashboard.js index 9a0e05b24a..73f35ddb22 100644 --- a/client/app/pages/dashboards/hooks/useDashboard.js +++ b/client/app/pages/dashboards/hooks/useDashboard.js @@ -127,6 +127,8 @@ function useDashboard(dashboardData) { const dashboardRef = useRef(); dashboardRef.current = dashboard; + const pendingParameterValuesRef = useRef({}); + const editingLayoutRef = useRef(false); const loadDashboard = useCallback( (forceRefresh = false, updatedParameters = []) => { @@ -145,8 +147,8 @@ function useDashboard(dashboardData) { ); const persistParameterValues = useCallback( - updatedParameters => { - if (!canEditDashboard || isEmpty(updatedParameters)) { + parameterValues => { + if (!canEditDashboard || !parameterValues || isEmpty(parameterValues)) { return Promise.resolve(); } @@ -155,13 +157,9 @@ function useDashboard(dashboardData) { const nextValues = extend({}, currentValues); let hasChanges = false; - updatedParameters.forEach(param => { - if (!param) { - return; - } - - if (!isEqual(nextValues[param.name], param.value)) { - nextValues[param.name] = param.value; + Object.entries(parameterValues).forEach(([name, value]) => { + if (!isEqual(nextValues[name], value)) { + nextValues[name] = value; hasChanges = true; } }); @@ -183,16 +181,25 @@ function useDashboard(dashboardData) { return; } + if (editingLayoutRef.current && !isEmpty(updatedParameters)) { + const queuedValues = {}; + updatedParameters.forEach(param => { + if (!param) { + return; + } + + queuedValues[param.name] = param.value === undefined ? null : param.value; + }); + + if (!isEmpty(queuedValues)) { + pendingParameterValuesRef.current = extend({}, pendingParameterValuesRef.current, queuedValues); + } + } + setRefreshing(true); - Promise.resolve(persistParameterValues(updatedParameters)) - .catch(error => { - console.error("Failed to persist parameter values:", error); - notification.error("Parameter values could not be saved. Your changes may not be persisted."); - }) - .then(() => loadDashboard(true, updatedParameters)) - .finally(() => setRefreshing(false)); + loadDashboard(true, updatedParameters).finally(() => setRefreshing(false)); }, - [refreshing, loadDashboard, persistParameterValues] + [refreshing, loadDashboard] ); const archiveDashboard = useCallback(() => { @@ -245,6 +252,30 @@ function useDashboard(dashboardData) { const [fullscreen, toggleFullscreen] = useFullscreenHandler(); const editModeHandler = useEditModeHandler(!gridDisabled && canEditDashboard, dashboard.widgets); + useEffect(() => { + const wasEditing = editingLayoutRef.current; + const isEditing = editModeHandler.editingLayout; + + if (!wasEditing && isEditing) { + pendingParameterValuesRef.current = {}; + } + + if (wasEditing && !isEditing) { + const pendingValues = pendingParameterValuesRef.current; + pendingParameterValuesRef.current = {}; + + if (!isEmpty(pendingValues)) { + Promise.resolve(persistParameterValues(pendingValues)).catch(error => { + console.error("Failed to persist parameter values:", error); + notification.error("Parameter values could not be saved. Your changes may not be persisted."); + pendingParameterValuesRef.current = pendingValues; + }); + } + } + + editingLayoutRef.current = isEditing; + }, [editModeHandler.editingLayout, persistParameterValues]); + useEffect(() => { setDashboard(dashboardData); loadDashboard(); From d25c4ee104498d4aac49724af0ef4acc85ecffea Mon Sep 17 00:00:00 2001 From: Vladislav Denisov Date: Mon, 17 Nov 2025 09:55:04 +0100 Subject: [PATCH 4/9] Refactor parameter value handling: streamline collection and persistence of current parameter values --- .../pages/dashboards/hooks/useDashboard.js | 47 +++++++------------ 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/client/app/pages/dashboards/hooks/useDashboard.js b/client/app/pages/dashboards/hooks/useDashboard.js index 73f35ddb22..e118ac3e4d 100644 --- a/client/app/pages/dashboards/hooks/useDashboard.js +++ b/client/app/pages/dashboards/hooks/useDashboard.js @@ -41,6 +41,16 @@ function useDashboard(dashboardData) { const [refreshing, setRefreshing] = useState(false); const [gridDisabled, setGridDisabled] = useState(false); const globalParameters = useMemo(() => dashboard.getParametersDefs(), [dashboard]); + const collectCurrentParameterValues = useCallback(() => { + const values = {}; + (globalParameters || []).forEach(param => { + if (!param) { + return; + } + values[param.name] = param.value === undefined ? null : param.value; + }); + return values; + }, [globalParameters]); const canEditDashboard = !dashboard.is_archived && policy.canEdit(dashboard); const isDashboardOwnerOrAdmin = useMemo( () => @@ -127,7 +137,6 @@ function useDashboard(dashboardData) { const dashboardRef = useRef(); dashboardRef.current = dashboard; - const pendingParameterValuesRef = useRef({}); const editingLayoutRef = useRef(false); const loadDashboard = useCallback( @@ -181,21 +190,6 @@ function useDashboard(dashboardData) { return; } - if (editingLayoutRef.current && !isEmpty(updatedParameters)) { - const queuedValues = {}; - updatedParameters.forEach(param => { - if (!param) { - return; - } - - queuedValues[param.name] = param.value === undefined ? null : param.value; - }); - - if (!isEmpty(queuedValues)) { - pendingParameterValuesRef.current = extend({}, pendingParameterValuesRef.current, queuedValues); - } - } - setRefreshing(true); loadDashboard(true, updatedParameters).finally(() => setRefreshing(false)); }, @@ -256,25 +250,16 @@ function useDashboard(dashboardData) { const wasEditing = editingLayoutRef.current; const isEditing = editModeHandler.editingLayout; - if (!wasEditing && isEditing) { - pendingParameterValuesRef.current = {}; - } - if (wasEditing && !isEditing) { - const pendingValues = pendingParameterValuesRef.current; - pendingParameterValuesRef.current = {}; - - if (!isEmpty(pendingValues)) { - Promise.resolve(persistParameterValues(pendingValues)).catch(error => { - console.error("Failed to persist parameter values:", error); - notification.error("Parameter values could not be saved. Your changes may not be persisted."); - pendingParameterValuesRef.current = pendingValues; - }); - } + const latestValues = collectCurrentParameterValues(); + Promise.resolve(persistParameterValues(latestValues)).catch(error => { + console.error("Failed to persist parameter values:", error); + notification.error("Parameter values could not be saved. Your changes may not be persisted."); + }); } editingLayoutRef.current = isEditing; - }, [editModeHandler.editingLayout, persistParameterValues]); + }, [collectCurrentParameterValues, editModeHandler.editingLayout, persistParameterValues]); useEffect(() => { setDashboard(dashboardData); From e67a83ac5da20bc34f3bad19a6aa1b40c06fddb3 Mon Sep 17 00:00:00 2001 From: Vladislav Denisov Date: Tue, 18 Nov 2025 10:05:33 +0100 Subject: [PATCH 5/9] Implement saveDashboardParameters function and integrate it into DashboardEditControl for improved parameter persistence --- .../dashboards/components/DashboardHeader.jsx | 8 ++++- .../pages/dashboards/hooks/useDashboard.js | 30 +++++++++---------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/client/app/pages/dashboards/components/DashboardHeader.jsx b/client/app/pages/dashboards/components/DashboardHeader.jsx index b8b27a3920..8d3ed95c64 100644 --- a/client/app/pages/dashboards/components/DashboardHeader.jsx +++ b/client/app/pages/dashboards/components/DashboardHeader.jsx @@ -252,7 +252,13 @@ function DashboardEditControl({ dashboardConfiguration, headerExtra }) { doneBtnClickedWhileSaving, dashboardStatus, retrySaveDashboardLayout, + saveDashboardParameters = () => Promise.resolve(), } = dashboardConfiguration; + const handleDoneEditing = () => { + Promise.resolve(saveDashboardParameters()) + .catch(() => {}) + .finally(() => setEditingLayout(false)); + }; let status; if (dashboardStatus === DashboardStatusEnum.SAVED) { status = Saved; @@ -277,7 +283,7 @@ function DashboardEditControl({ dashboardConfiguration, headerExtra }) { Retry ) : ( -