Skip to content

Commit

Permalink
[7.15] [Dashboard] Retain Viewmode State in Session (#112302) (#112708)
Browse files Browse the repository at this point in the history
* [Dashboard] Retain Viewmode State in Session (#112302)

* Made dashboard retain viewmode state in session. This means filters and query will be kept over reloads and navigations

* clear filters to avoid maps but #64861

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
ThomThomson and kibanamachine authored Sep 27, 2021
1 parent ec0fbef commit 3b9a381
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -229,27 +229,25 @@ export const useDashboardAppState = ({
.pipe(debounceTime(DashboardConstants.CHANGE_CHECK_DEBOUNCE))
.subscribe((states) => {
const [lastSaved, current] = states;
const unsavedChanges =
current.viewMode === ViewMode.EDIT ? diffDashboardState(lastSaved, current) : {};
const unsavedChanges = diffDashboardState(lastSaved, current);

let savedTimeChanged = false;
const savedTimeChanged =
lastSaved.timeRestore &&
!areTimeRangesEqual(
{
from: savedDashboard?.timeFrom,
to: savedDashboard?.timeTo,
},
timefilter.getTime()
);

/**
* changes to the time filter should only be considered 'unsaved changes' when
* changes to the dashboard should only be considered 'unsaved changes' when
* editing the dashboard
*/
if (current.viewMode === ViewMode.EDIT) {
savedTimeChanged =
lastSaved.timeRestore &&
!areTimeRangesEqual(
{
from: savedDashboard?.timeFrom,
to: savedDashboard?.timeTo,
},
timefilter.getTime()
);
}
const hasUnsavedChanges = Object.keys(unsavedChanges).length > 0 || savedTimeChanged;
const hasUnsavedChanges =
current.viewMode === ViewMode.EDIT &&
(Object.keys(unsavedChanges).length > 0 || savedTimeChanged);
setDashboardAppState((s) => ({ ...s, hasUnsavedChanges }));

unsavedChanges.viewMode = current.viewMode; // always push view mode into session store.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Storage } from '../../services/kibana_utils';
import { NotificationsStart } from '../../services/core';
import { panelStorageErrorStrings } from '../../dashboard_strings';
import { DashboardState } from '../../types';
import { ViewMode } from '../../services/embeddable';

export const DASHBOARD_PANELS_UNSAVED_ID = 'unsavedDashboard';
const DASHBOARD_PANELS_SESSION_KEY = 'dashboardStateManagerPanels';
Expand Down Expand Up @@ -69,6 +70,7 @@ export class DashboardSessionStorage {
const dashboardsWithUnsavedChanges: string[] = [];
Object.keys(dashboardStatesInSpace).map((dashboardId) => {
if (
dashboardStatesInSpace[dashboardId].viewMode === ViewMode.EDIT &&
Object.keys(dashboardStatesInSpace[dashboardId]).some(
(stateKey) => stateKey !== 'viewMode'
)
Expand Down
169 changes: 108 additions & 61 deletions test/functional/apps/dashboard/dashboard_unsaved_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@ import { FtrProviderContext } from '../../ftr_provider_context';

export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['dashboard', 'header', 'visualize', 'settings', 'common']);
const browser = getService('browser');
const queryBar = getService('queryBar');
const filterBar = getService('filterBar');
const esArchiver = getService('esArchiver');
const testSubjects = getService('testSubjects');
const kibanaServer = getService('kibanaServer');
const dashboardAddPanel = getService('dashboardAddPanel');

let originalPanelCount = 0;
let unsavedPanelCount = 0;
const testQuery = 'Test Query';

// FLAKY: https://github.com/elastic/kibana/issues/91191
describe.skip('dashboard unsaved panels', () => {
describe('dashboard unsaved state', () => {
before(async () => {
await esArchiver.load('test/functional/fixtures/es_archiver/dashboard/current/kibana');
await kibanaServer.uiSettings.replace({
Expand All @@ -31,79 +34,123 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.dashboard.preserveCrossAppState();
await PageObjects.dashboard.loadSavedDashboard('few panels');
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.dashboard.waitForRenderComplete();
originalPanelCount = await PageObjects.dashboard.getPanelCount();
});

it('does not show unsaved changes badge when there are no unsaved changes', async () => {
await testSubjects.missingOrFail('dashboardUnsavedChangesBadge');
});
describe('view mode state', () => {
before(async () => {
await queryBar.setQuery(testQuery);
await filterBar.addFilter('bytes', 'exists');
await queryBar.submitQuery();
});

it('shows the unsaved changes badge after adding panels', async () => {
await PageObjects.dashboard.switchToEditMode();
// add an area chart by value
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickAggBasedVisualizations();
await PageObjects.visualize.clickAreaChart();
await PageObjects.visualize.clickNewSearch();
await PageObjects.visualize.saveVisualizationAndReturn();
const validateQueryAndFilter = async () => {
const query = await queryBar.getQueryString();
expect(query).to.eql(testQuery);
const filterCount = await filterBar.getFilterCount();
expect(filterCount).to.eql(1);
};

it('persists after navigating to the listing page and back', async () => {
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.dashboard.gotoDashboardLandingPage();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.dashboard.loadSavedDashboard('few panels');
await PageObjects.dashboard.waitForRenderComplete();
await validateQueryAndFilter();
});

// add a metric by reference
await dashboardAddPanel.addVisualization('Rendering-Test: metric');
it('persists after navigating to Visualize and back', async () => {
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.gotoVisualizationLandingPage();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.common.navigateToApp('dashboards');
await PageObjects.dashboard.loadSavedDashboard('few panels');
await PageObjects.dashboard.waitForRenderComplete();
await validateQueryAndFilter();
});

await PageObjects.header.waitUntilLoadingHasFinished();
await testSubjects.existOrFail('dashboardUnsavedChangesBadge');
});
it('persists after a hard refresh', async () => {
await browser.refresh();
const alert = await browser.getAlert();
await alert?.accept();
await PageObjects.dashboard.waitForRenderComplete();
await validateQueryAndFilter();
});

it('has correct number of panels', async () => {
unsavedPanelCount = await PageObjects.dashboard.getPanelCount();
expect(unsavedPanelCount).to.eql(originalPanelCount + 2);
after(async () => {
// discard changes made in view mode
await PageObjects.dashboard.switchToEditMode();
await PageObjects.dashboard.clickCancelOutOfEditMode();
});
});

it('retains unsaved panel count after navigating to listing page and back', async () => {
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.dashboard.gotoDashboardLandingPage();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.dashboard.loadSavedDashboard('few panels');
await PageObjects.dashboard.switchToEditMode();
const currentPanelCount = await PageObjects.dashboard.getPanelCount();
expect(currentPanelCount).to.eql(unsavedPanelCount);
});
describe('edit mode state', () => {
const addPanels = async () => {
// add an area chart by value
await dashboardAddPanel.clickEditorMenuButton();
await dashboardAddPanel.clickAggBasedVisualizations();
await PageObjects.visualize.clickAreaChart();
await PageObjects.visualize.clickNewSearch();
await PageObjects.visualize.saveVisualizationAndReturn();

// add a metric by reference
await dashboardAddPanel.addVisualization('Rendering-Test: metric');
};

it('does not show unsaved changes badge when there are no unsaved changes', async () => {
await testSubjects.missingOrFail('dashboardUnsavedChangesBadge');
});

it('retains unsaved panel count after navigating to another app and back', async () => {
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.gotoVisualizationLandingPage();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.common.navigateToApp('dashboards');
await PageObjects.dashboard.loadSavedDashboard('few panels');
await PageObjects.dashboard.switchToEditMode();
const currentPanelCount = await PageObjects.dashboard.getPanelCount();
expect(currentPanelCount).to.eql(unsavedPanelCount);
});
it('shows the unsaved changes badge after adding panels', async () => {
await PageObjects.dashboard.switchToEditMode();
await addPanels();
await PageObjects.header.waitUntilLoadingHasFinished();
await testSubjects.existOrFail('dashboardUnsavedChangesBadge');
});

it('resets to original panel count upon entering view mode', async () => {
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.dashboard.clickCancelOutOfEditMode();
await PageObjects.header.waitUntilLoadingHasFinished();
const currentPanelCount = await PageObjects.dashboard.getPanelCount();
expect(currentPanelCount).to.eql(originalPanelCount);
});
it('has correct number of panels', async () => {
unsavedPanelCount = await PageObjects.dashboard.getPanelCount();
expect(unsavedPanelCount).to.eql(originalPanelCount + 2);
});

it('shows unsaved changes badge in view mode if changes have not been discarded', async () => {
await testSubjects.existOrFail('dashboardUnsavedChangesBadge');
});
it('retains unsaved panel count after navigating to listing page and back', async () => {
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.dashboard.gotoDashboardLandingPage();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.dashboard.loadSavedDashboard('few panels');
const currentPanelCount = await PageObjects.dashboard.getPanelCount();
expect(currentPanelCount).to.eql(unsavedPanelCount);
});

it('retains unsaved panel count after returning to edit mode', async () => {
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.dashboard.switchToEditMode();
await PageObjects.header.waitUntilLoadingHasFinished();
const currentPanelCount = await PageObjects.dashboard.getPanelCount();
expect(currentPanelCount).to.eql(unsavedPanelCount);
});
it('retains unsaved panel count after navigating to another app and back', async () => {
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.gotoVisualizationLandingPage();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.common.navigateToApp('dashboards');
await PageObjects.dashboard.loadSavedDashboard('few panels');
const currentPanelCount = await PageObjects.dashboard.getPanelCount();
expect(currentPanelCount).to.eql(unsavedPanelCount);
});

it('does not show unsaved changes badge after saving', async () => {
await PageObjects.dashboard.saveDashboard('Unsaved State Test');
await PageObjects.header.waitUntilLoadingHasFinished();
await testSubjects.missingOrFail('dashboardUnsavedChangesBadge');
it('resets to original panel count after discarding changes', async () => {
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.dashboard.clickCancelOutOfEditMode();
await PageObjects.header.waitUntilLoadingHasFinished();
const currentPanelCount = await PageObjects.dashboard.getPanelCount();
expect(currentPanelCount).to.eql(originalPanelCount);
expect(PageObjects.dashboard.getIsInViewMode()).to.eql(true);
});

it('does not show unsaved changes badge after saving', async () => {
await PageObjects.dashboard.switchToEditMode();
await addPanels();
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.dashboard.saveDashboard('Unsaved State Test');
await PageObjects.header.waitUntilLoadingHasFinished();
await testSubjects.missingOrFail('dashboardUnsavedChangesBadge');
});
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ export default function ({ getPageObjects, getService }) {
const hasJoinFilter = await filterBar.hasFilter('runtime_shape_name', 'charlie');
expect(hasJoinFilter).to.be(true);
});

after(async () => {
// Remove all filters so that when loading the dashboard again, the geo shape is still present. Related to #64861
await filterBar.removeAllFilters();
});
});

describe('panel actions', () => {
Expand Down

0 comments on commit 3b9a381

Please sign in to comment.