From 987882706835b3073115479109fd7605177eb8a3 Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Fri, 21 Mar 2025 17:33:49 -0400 Subject: [PATCH 1/4] Attempt to fix explicit input removal --- .../public/dashboard_api/get_dashboard_api.ts | 3 +- .../public/dashboard_api/panels_manager.ts | 24 ++++++++++++-- .../dashboard_api/unsaved_changes_manager.ts | 21 ++++++++++++- .../services/dashboard_backup_service.ts | 31 ++++++++++++++++++- 4 files changed, 74 insertions(+), 5 deletions(-) diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_api/get_dashboard_api.ts b/src/platform/plugins/shared/dashboard/public/dashboard_api/get_dashboard_api.ts index c5c1b4b8ea378..9ce395da303e1 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_api/get_dashboard_api.ts +++ b/src/platform/plugins/shared/dashboard/public/dashboard_api/get_dashboard_api.ts @@ -90,7 +90,8 @@ export function getDashboardApi({ initialPanelsRuntimeState ?? {}, trackPanel, getPanelReferences, - pushPanelReferences + pushPanelReferences, + savedObjectId ); const dataLoadingManager = initializeDataLoadingManager(panelsManager.api.children$); const dataViewsManager = initializeDataViewsManager( diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_api/panels_manager.ts b/src/platform/plugins/shared/dashboard/public/dashboard_api/panels_manager.ts index 80545b1a432b4..31e02a7b898e8 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_api/panels_manager.ts +++ b/src/platform/plugins/shared/dashboard/public/dashboard_api/panels_manager.ts @@ -26,6 +26,7 @@ import { apiPublishesUnsavedChanges, apiHasSerializableState, getTitle, + SerializedPanelState, } from '@kbn/presentation-publishing'; import { i18n } from '@kbn/i18n'; import { coreServices, usageCollectionService } from '../services/kibana_services'; @@ -41,6 +42,7 @@ import { arePanelLayoutsEqual } from './are_panel_layouts_equal'; import { dashboardClonePanelActionStrings } from '../dashboard_actions/_dashboard_actions_strings'; import { placeClonePanel } from '../panel_placement/place_clone_panel_strategy'; import { PanelPlacementStrategy } from '../plugin_constants'; +import { getDashboardBackupService } from '../services/dashboard_backup_service'; export function initializePanelsManager( incomingEmbeddable: EmbeddablePackageState | undefined, @@ -48,7 +50,8 @@ export function initializePanelsManager( initialPanelsRuntimeState: UnsavedPanelState, trackPanel: ReturnType, getReferencesForPanelId: (id: string) => Reference[], - pushReferences: (references: Reference[]) => void + pushReferences: (references: Reference[]) => void, + dashboardId?: string ) { const children$ = new BehaviorSubject<{ [key: string]: unknown; @@ -382,11 +385,28 @@ export function initializePanelsManager( } => { const references: Reference[] = []; + const getApiMissingFallback = (id: string): SerializedPanelState => { + // If the Dashboard has been saved with this panel before, we should be able to grab it from the current value of panels$ + if (Object.keys(panels$.value[id].explicitInput ?? {}).length > 0) { + return { + rawState: panels$.value[id].explicitInput, + references: getReferencesForPanelId(id), + }; + } else { + // otherwise, this panel may be new, and therefore will have its unsaved changes backed up in the dashboard backup service + return ( + getDashboardBackupService().getSerializedPanelBackup(id, dashboardId) ?? { + rawState: {}, + } + ); + } + }; + const panels = Object.keys(panels$.value).reduce((acc, id) => { const childApi = children$.value[id]; const serializeResult = apiHasSerializableState(childApi) ? childApi.serializeState() - : { rawState: {} }; + : getApiMissingFallback(id); acc[id] = { ...panels$.value[id], explicitInput: { ...serializeResult.rawState, id } }; references.push(...prefixReferencesFromPanel(id, serializeResult.references ?? [])); diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_api/unsaved_changes_manager.ts b/src/platform/plugins/shared/dashboard/public/dashboard_api/unsaved_changes_manager.ts index 337e7d86893fe..1367a426762a2 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_api/unsaved_changes_manager.ts +++ b/src/platform/plugins/shared/dashboard/public/dashboard_api/unsaved_changes_manager.ts @@ -12,7 +12,9 @@ import { childrenUnsavedChanges$, initializeUnsavedChanges } from '@kbn/presenta import { PublishesSavedObjectId, PublishingSubject, + SerializedPanelState, StateComparators, + apiHasSerializableState, } from '@kbn/presentation-publishing'; import { omit } from 'lodash'; import { BehaviorSubject, Subject, combineLatest, debounceTime, skipWhile, switchMap } from 'rxjs'; @@ -93,17 +95,34 @@ export function initializeUnsavedChangesManager({ // backup unsaved changes if configured to do so if (creationOptions?.useSessionStorageIntegration) { + const dashboardBackupService = getDashboardBackupService(); + // Current behaviour expects time range not to be backed up. Revisit this? const dashboardStateToBackup = omit(dashboardChanges ?? {}, [ 'timeRange', 'refreshInterval', ]); + + // TEMPORARY - back up serialized state for all panels with changes + if (unsavedPanelState) { + const serializedPanelBackup: { [key: string]: SerializedPanelState } = {}; + for (const uuid of Object.keys(unsavedPanelState)) { + const childApi = panelsManager.api.children$.value[uuid]; + if (!apiHasSerializableState(childApi)) continue; + serializedPanelBackup[uuid] = childApi.serializeState(); + } + dashboardBackupService.setSerializedPanelsBackups( + serializedPanelBackup, + savedObjectId$.value + ); + } + const reactEmbeddableChanges = unsavedPanelState ? { ...unsavedPanelState } : {}; if (controlGroupChanges) { reactEmbeddableChanges[PANELS_CONTROL_GROUP_KEY] = controlGroupChanges; } - getDashboardBackupService().setState( + dashboardBackupService.setState( savedObjectId$.value, dashboardStateToBackup, reactEmbeddableChanges diff --git a/src/platform/plugins/shared/dashboard/public/services/dashboard_backup_service.ts b/src/platform/plugins/shared/dashboard/public/services/dashboard_backup_service.ts index 52068d881e9ac..0d2fa83dab27b 100644 --- a/src/platform/plugins/shared/dashboard/public/services/dashboard_backup_service.ts +++ b/src/platform/plugins/shared/dashboard/public/services/dashboard_backup_service.ts @@ -14,7 +14,7 @@ import { i18n } from '@kbn/i18n'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { set } from '@kbn/safer-lodash-set'; -import { ViewMode } from '@kbn/presentation-publishing'; +import { SerializedPanelState, ViewMode } from '@kbn/presentation-publishing'; import { coreServices, spacesService } from './kibana_services'; import { DashboardState, UnsavedPanelState } from '../dashboard_api/types'; import { DEFAULT_DASHBOARD_STATE } from '../dashboard_api/default_dashboard_state'; @@ -27,6 +27,9 @@ const DASHBOARD_VIEWMODE_LOCAL_KEY = 'dashboardViewMode'; // this key is named `panels` for BWC reasons, but actually contains the entire dashboard state const DASHBOARD_STATE_SESSION_KEY = 'dashboardStateManagerPanels'; +// Temporary key for Dashboard to back up serialized state of all its panels +const DASHBOARD_SERIALIZED_PANEL_BACKUP_KEY = 'dashboardSerializedPanelBackup'; + const getPanelsGetError = (message: string) => i18n.translate('dashboard.panelStorageError.getError', { defaultMessage: 'Error encountered while fetching unsaved changes: {message}', @@ -119,6 +122,32 @@ class DashboardBackupService implements DashboardBackupServiceType { } } + public getSerializedPanelBackup(panelId: string, dashboardId = DASHBOARD_PANELS_UNSAVED_ID) { + return this.sessionStorage.get(DASHBOARD_SERIALIZED_PANEL_BACKUP_KEY)?.[this.activeSpaceId]?.[ + dashboardId + ]?.[panelId] as SerializedPanelState | undefined; + } + + public setSerializedPanelsBackups( + serializedPanels: { [key: string]: SerializedPanelState }, + dashboardId = DASHBOARD_PANELS_UNSAVED_ID + ) { + try { + const serializedPanelsBackup = + this.sessionStorage.get(DASHBOARD_SERIALIZED_PANEL_BACKUP_KEY) ?? {}; + set(serializedPanelsBackup, [this.activeSpaceId, dashboardId], serializedPanels); + this.sessionStorage.set(DASHBOARD_SERIALIZED_PANEL_BACKUP_KEY, serializedPanelsBackup); + } catch (e) { + coreServices.notifications.toasts.addDanger({ + title: i18n.translate('dashboard.panelStorageError.setError', { + defaultMessage: 'Error encountered while setting unsaved changes: {message}', + values: { message: e.message }, + }), + 'data-test-subj': 'dashboardPanelsSetFailure', + }); + } + } + public getState(id = DASHBOARD_PANELS_UNSAVED_ID) { try { const dashboardState = this.sessionStorage.get(DASHBOARD_STATE_SESSION_KEY)?.[ From 535279b40bd75401dc6a6a05f992aee6283124c0 Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Thu, 27 Mar 2025 11:31:24 -0400 Subject: [PATCH 2/4] always fall back to serialized state backup --- .../public/dashboard_api/panels_manager.ts | 21 +++---------------- .../services/dashboard_backup_service.ts | 9 ++++++++ 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_api/panels_manager.ts b/src/platform/plugins/shared/dashboard/public/dashboard_api/panels_manager.ts index 31e02a7b898e8..59fb1a24e9e78 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_api/panels_manager.ts +++ b/src/platform/plugins/shared/dashboard/public/dashboard_api/panels_manager.ts @@ -385,28 +385,13 @@ export function initializePanelsManager( } => { const references: Reference[] = []; - const getApiMissingFallback = (id: string): SerializedPanelState => { - // If the Dashboard has been saved with this panel before, we should be able to grab it from the current value of panels$ - if (Object.keys(panels$.value[id].explicitInput ?? {}).length > 0) { - return { - rawState: panels$.value[id].explicitInput, - references: getReferencesForPanelId(id), - }; - } else { - // otherwise, this panel may be new, and therefore will have its unsaved changes backed up in the dashboard backup service - return ( - getDashboardBackupService().getSerializedPanelBackup(id, dashboardId) ?? { - rawState: {}, - } - ); - } - }; - const panels = Object.keys(panels$.value).reduce((acc, id) => { const childApi = children$.value[id]; const serializeResult = apiHasSerializableState(childApi) ? childApi.serializeState() - : getApiMissingFallback(id); + : getDashboardBackupService().getSerializedPanelBackup(id, dashboardId) ?? { + rawState: {}, + }; acc[id] = { ...panels$.value[id], explicitInput: { ...serializeResult.rawState, id } }; references.push(...prefixReferencesFromPanel(id, serializeResult.references ?? [])); diff --git a/src/platform/plugins/shared/dashboard/public/services/dashboard_backup_service.ts b/src/platform/plugins/shared/dashboard/public/services/dashboard_backup_service.ts index 0d2fa83dab27b..d0700b0c6718d 100644 --- a/src/platform/plugins/shared/dashboard/public/services/dashboard_backup_service.ts +++ b/src/platform/plugins/shared/dashboard/public/services/dashboard_backup_service.ts @@ -111,6 +111,15 @@ class DashboardBackupService implements DashboardBackupServiceType { [this.activeSpaceId]: panelsStorage, }); } + + const serializedBackups = + this.sessionStorage.get(DASHBOARD_SERIALIZED_PANEL_BACKUP_KEY)?.[this.activeSpaceId] ?? {}; + if (serializedBackups[id]) { + delete serializedBackups[id]; + this.sessionStorage.set(DASHBOARD_SERIALIZED_PANEL_BACKUP_KEY, { + [this.activeSpaceId]: serializedBackups, + }); + } } catch (e) { coreServices.notifications.toasts.addDanger({ title: i18n.translate('dashboard.panelStorageError.clearError', { From 33916f3bf726d62181e5b88899bfdf1e651a5ada Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Thu, 27 Mar 2025 11:31:47 -0400 Subject: [PATCH 3/4] fix type --- .../shared/dashboard/public/dashboard_api/panels_manager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_api/panels_manager.ts b/src/platform/plugins/shared/dashboard/public/dashboard_api/panels_manager.ts index 59fb1a24e9e78..b2b4e7921f8e7 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_api/panels_manager.ts +++ b/src/platform/plugins/shared/dashboard/public/dashboard_api/panels_manager.ts @@ -26,7 +26,6 @@ import { apiPublishesUnsavedChanges, apiHasSerializableState, getTitle, - SerializedPanelState, } from '@kbn/presentation-publishing'; import { i18n } from '@kbn/i18n'; import { coreServices, usageCollectionService } from '../services/kibana_services'; From d18d50eac47b14877704b2faa3f86053d6d75e39 Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Thu, 27 Mar 2025 13:53:23 -0400 Subject: [PATCH 4/4] fallback to existing panels state. --- .../shared/dashboard/public/dashboard_api/panels_manager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_api/panels_manager.ts b/src/platform/plugins/shared/dashboard/public/dashboard_api/panels_manager.ts index b2b4e7921f8e7..b88b15f438379 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_api/panels_manager.ts +++ b/src/platform/plugins/shared/dashboard/public/dashboard_api/panels_manager.ts @@ -389,7 +389,8 @@ export function initializePanelsManager( const serializeResult = apiHasSerializableState(childApi) ? childApi.serializeState() : getDashboardBackupService().getSerializedPanelBackup(id, dashboardId) ?? { - rawState: {}, + rawState: panels$.value[id].explicitInput ?? {}, + references: getReferencesForPanelId(id), }; acc[id] = { ...panels$.value[id], explicitInput: { ...serializeResult.rawState, id } };