From 390ba3b707261fc7cb12db0c71f38e1dccdd7fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Garc=C3=ADa=20Parrondo?= <31973472+AlexGPlay@users.noreply.github.com> Date: Fri, 31 Oct 2025 10:35:46 +0100 Subject: [PATCH] [Background search] Restore unsaved dashboards (#240067) ## Summary Closes https://github.com/elastic/kibana/issues/227311 Right now if we create a background search of an unsaved dashboard it doesn't store the panels/references in the saved object, that menas that we can't really restore it. With this we store them even if the dashboard isn't saved but I don't have any context on why it wasn't done like this before so happy to get input on it. --- #### Background an unsaved dashboard and just open it again Before https://github.com/user-attachments/assets/fd282c28-53ea-49dc-b58b-1441e25dfbb1 After https://github.com/user-attachments/assets/d39fbef0-d9b2-4763-bd10-1db98b4fc667 --- #### Background an unsaved dashboard, remove it and open it again Before https://github.com/user-attachments/assets/9c1ba24d-a55f-4176-a409-5070d282110d After https://github.com/user-attachments/assets/7d0a4e3e-e213-40f4-9c41-50783a08b2df --- ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [x] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. (cherry picked from commit 968633561559bfb774bd6e38ffd4c56c859317b8) --- .../url/search_sessions_integration.ts | 11 +- .../apps/dashboard/async_search/index.ts | 1 + .../async_search/unsaved_dashboard.ts | 123 ++++++++++++++++++ 3 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 x-pack/platform/test/search_sessions_integration/tests/apps/dashboard/async_search/unsaved_dashboard.ts diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/url/search_sessions_integration.ts b/src/platform/plugins/shared/dashboard/public/dashboard_app/url/search_sessions_integration.ts index 95c522a9211b1..7cbc89ec9889b 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_app/url/search_sessions_integration.ts +++ b/src/platform/plugins/shared/dashboard/public/dashboard_app/url/search_sessions_integration.ts @@ -78,12 +78,11 @@ function getLocatorParams({ shouldRestoreSearchSession: boolean; }): DashboardLocatorParams { const savedObjectId = dashboardApi.savedObjectId$.value; - const panels = savedObjectId - ? (dashboardInternalApi.serializeLayout() as Pick< - DashboardLocatorParams, - 'panels' | 'references' - >) - : undefined; + + const panels = dashboardInternalApi.serializeLayout() as Pick< + DashboardLocatorParams, + 'panels' | 'references' + >; const { controlGroupInput, controlGroupReferences } = dashboardInternalApi.serializeControls(); diff --git a/x-pack/platform/test/search_sessions_integration/tests/apps/dashboard/async_search/index.ts b/x-pack/platform/test/search_sessions_integration/tests/apps/dashboard/async_search/index.ts index e446e72fb1890..46a24088400d8 100644 --- a/x-pack/platform/test/search_sessions_integration/tests/apps/dashboard/async_search/index.ts +++ b/x-pack/platform/test/search_sessions_integration/tests/apps/dashboard/async_search/index.ts @@ -38,5 +38,6 @@ export default function ({ loadTestFile, getService, getPageObjects }: FtrProvid loadTestFile(require.resolve('./save_search_session')); loadTestFile(require.resolve('./save_search_session_relative_time')); loadTestFile(require.resolve('./sessions_in_space')); + loadTestFile(require.resolve('./unsaved_dashboard')); }); } diff --git a/x-pack/platform/test/search_sessions_integration/tests/apps/dashboard/async_search/unsaved_dashboard.ts b/x-pack/platform/test/search_sessions_integration/tests/apps/dashboard/async_search/unsaved_dashboard.ts new file mode 100644 index 0000000000000..4abba685d6e81 --- /dev/null +++ b/x-pack/platform/test/search_sessions_integration/tests/apps/dashboard/async_search/unsaved_dashboard.ts @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import type { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const log = getService('log'); + const searchSessions = getService('searchSessions'); + const kibanaServer = getService('kibanaServer'); + const retry = getService('retry'); + const monacoEditor = getService('monacoEditor'); + + const { common, home, timePicker, header, discover, dashboard, searchSessionsManagement } = + getPageObjects([ + 'common', + 'timePicker', + 'header', + 'home', + 'discover', + 'dashboard', + 'dashboardControls', + 'searchSessionsManagement', + ]); + + async function addFromLibrary() { + await testSubjects.click('dashboardAddTopNavButton'); + await testSubjects.click('dashboardAddFromLibraryButton'); + await testSubjects.setValue('savedObjectFinderSearchInput', 'Unsaved dashboard slow query'); + await testSubjects.click('savedObjectTitleUnsaved-dashboard-slow-query'); + } + + async function openBackgroundSearchWhenReady() { + await retry.waitFor('the background search to be completed', async () => { + const _list = await searchSessionsManagement.getList(); + return _list[0].status === 'complete'; + }); + + const list = await searchSessionsManagement.getList(); + await list[0].view(); + } + + describe('saves a search session for unsaved dashboard', () => { + describe('with a discover session', () => { + // Add the sample dataset + before(async () => { + await kibanaServer.uiSettings.replace({ + enableESQL: true, + }); + + await common.navigateToUrl('home', '/tutorial_directory/sampleData', { + useActualUrl: true, + }); + await retry.try(async () => { + await home.addSampleDataSet('flights'); + const isInstalled = await home.isSampleDataSetInstalled('flights'); + expect(isInstalled).to.be(true); + }); + log.debug('Sample data installed'); + }); + + // Save a slow query to a discover session + before(async () => { + await common.navigateToApp('discover'); + await header.waitUntilLoadingHasFinished(); + + await timePicker.setCommonlyUsedTime('This_week'); + await discover.selectTextBaseLang(); + await monacoEditor.setCodeEditorValue( + 'FROM kibana_sample_data_flights | LIMIT 1 | WHERE DELAY(1500ms)' + ); + await testSubjects.clickWhenNotDisabledWithoutRetry('querySubmitButton'); + await discover.waitUntilSearchingHasFinished(); + await discover.saveSearch('Unsaved dashboard slow query'); + }); + + afterEach(async () => { + await common.navigateToApp('dashboard'); + await testSubjects.click('discard-unsaved-New-Dashboard'); + await testSubjects.click('confirmModalConfirmButton'); + }); + + it('should be restored when opening from the background search', async () => { + await common.navigateToApp('dashboard'); + await dashboard.clickCreateDashboardPrompt(); + + await addFromLibrary(); + await searchSessions.save({ isSubmitButton: true }); + await dashboard.waitForRenderComplete(); + + await searchSessions.openFlyoutFromToast(); + await openBackgroundSearchWhenReady(); + + await dashboard.verifyNoRenderErrors(); + }); + + describe('when the dashboard is also discarded', async () => { + it('should still restore the session', async () => { + await common.navigateToApp('dashboard'); + await dashboard.clickCreateDashboardPrompt(); + + await addFromLibrary(); + await searchSessions.save({ isSubmitButton: true }); + await dashboard.waitForRenderComplete(); + + await common.navigateToApp('dashboard'); + await testSubjects.click('discard-unsaved-New-Dashboard'); + await testSubjects.click('confirmModalConfirmButton'); + + await searchSessionsManagement.goTo(); + await openBackgroundSearchWhenReady(); + + await dashboard.verifyNoRenderErrors(); + }); + }); + }); + }); +}