Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Dashboard] Retain Viewmode State in Session #112302

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -237,27 +237,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) : {};

let savedTimeChanged = false;
const unsavedChanges = diffDashboardState(lastSaved, current);

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');
});
});
});
}