[Dashboard] Store view mode in local storage (#166523)

Moves the Dashboard view mode from session storage to local storage. This means that users will only need to enter edit mode **once** if they are an editor, and any subsequent Dashboards they open will already be in edit mode.
This commit is contained in:
Devon Thomson 2023-09-29 19:46:18 -04:00 committed by GitHub
parent 7e32fc8432
commit 8ffa8d8ee4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 223 additions and 110 deletions

View file

@ -70,9 +70,7 @@ describe('ShowShareModal', () => {
const getPropsAndShare = ( const getPropsAndShare = (
unsavedState?: Partial<DashboardContainerInput> unsavedState?: Partial<DashboardContainerInput>
): ShowShareModalProps => { ): ShowShareModalProps => {
pluginServices.getServices().dashboardSessionStorage.getState = jest pluginServices.getServices().dashboardBackup.getState = jest.fn().mockReturnValue(unsavedState);
.fn()
.mockReturnValue(unsavedState);
return { return {
isDirty: true, isDirty: true,
anchorElement: document.createElement('div'), anchorElement: document.createElement('div'),

View file

@ -50,7 +50,7 @@ export function ShowShareModal({
}: ShowShareModalProps) { }: ShowShareModalProps) {
const { const {
dashboardCapabilities: { createShortUrl: allowShortUrl }, dashboardCapabilities: { createShortUrl: allowShortUrl },
dashboardSessionStorage, dashboardBackup,
data: { data: {
query: { query: {
timefilter: { timefilter: {
@ -121,7 +121,7 @@ export function ShowShareModal({
}; };
let unsavedStateForLocator: DashboardAppLocatorParams = {}; let unsavedStateForLocator: DashboardAppLocatorParams = {};
const unsavedDashboardState = dashboardSessionStorage.getState(savedObjectId); const unsavedDashboardState = dashboardBackup.getState(savedObjectId);
if (unsavedDashboardState) { if (unsavedDashboardState) {
unsavedStateForLocator = { unsavedStateForLocator = {

View file

@ -38,6 +38,7 @@ export const useDashboardMenuItems = ({
*/ */
const { const {
share, share,
dashboardBackup,
settings: { uiSettings }, settings: { uiSettings },
dashboardCapabilities: { showWriteControls }, dashboardCapabilities: { showWriteControls },
} = pluginServices.getServices(); } = pluginServices.getServices();
@ -127,18 +128,24 @@ export const useDashboardMenuItems = ({
const resetChanges = useCallback( const resetChanges = useCallback(
(switchToViewMode: boolean = false) => { (switchToViewMode: boolean = false) => {
dashboard.clearOverlays(); dashboard.clearOverlays();
if (hasUnsavedChanges) { const switchModes = switchToViewMode
confirmDiscardUnsavedChanges(() => { ? () => {
batch(() => { dashboard.dispatch.setViewMode(ViewMode.VIEW);
dashboard.resetToLastSavedState(); dashboardBackup.storeViewMode(ViewMode.VIEW);
if (switchToViewMode) dashboard.dispatch.setViewMode(ViewMode.VIEW); }
}); : undefined;
}, viewMode); if (!hasUnsavedChanges) {
} else { switchModes?.();
if (switchToViewMode) dashboard.dispatch.setViewMode(ViewMode.VIEW); return;
} }
confirmDiscardUnsavedChanges(() => {
batch(() => {
dashboard.resetToLastSavedState();
switchModes?.();
});
}, viewMode);
}, },
[dashboard, hasUnsavedChanges, viewMode] [dashboard, dashboardBackup, hasUnsavedChanges, viewMode]
); );
/** /**
@ -170,6 +177,7 @@ export const useDashboardMenuItems = ({
testId: 'dashboardEditMode', testId: 'dashboardEditMode',
className: 'eui-hideFor--s eui-hideFor--xs', // hide for small screens - editing doesn't work in mobile mode. className: 'eui-hideFor--s eui-hideFor--xs', // hide for small screens - editing doesn't work in mobile mode.
run: () => { run: () => {
dashboardBackup.storeViewMode(ViewMode.EDIT);
dashboard.dispatch.setViewMode(ViewMode.EDIT); dashboard.dispatch.setViewMode(ViewMode.EDIT);
dashboard.clearOverlays(); dashboard.clearOverlays();
}, },
@ -231,18 +239,19 @@ export const useDashboardMenuItems = ({
} as TopNavMenuData, } as TopNavMenuData,
}; };
}, [ }, [
disableTopNav, quickSaveDashboard,
isSaveInProgress, isSaveInProgress,
hasRunMigrations, hasRunMigrations,
hasUnsavedChanges, hasUnsavedChanges,
dashboardBackup,
saveDashboardAs,
setIsLabsShown,
disableTopNav,
resetChanges,
isLabsShown,
lastSavedId, lastSavedId,
showShare, showShare,
dashboard, dashboard,
setIsLabsShown,
isLabsShown,
quickSaveDashboard,
saveDashboardAs,
resetChanges,
clone, clone,
]); ]);

View file

@ -77,7 +77,7 @@ export const DASHBOARD_CACHE_TTL = 1000 * 60 * 5; // time to live = 5 minutes
// Default State // Default State
// ------------------------------------------------------------------ // ------------------------------------------------------------------
export const DEFAULT_DASHBOARD_INPUT: Omit<DashboardContainerInput, 'id'> = { export const DEFAULT_DASHBOARD_INPUT: Omit<DashboardContainerInput, 'id'> = {
viewMode: ViewMode.EDIT, // new dashboards start in edit mode. viewMode: ViewMode.VIEW,
timeRestore: false, timeRestore: false,
query: { query: '', language: 'kuery' }, query: { query: '', language: 'kuery' },
description: '', description: '',

View file

@ -83,7 +83,12 @@ export const dashboardSavedObjectErrorStrings = {
}), }),
}; };
export const panelStorageErrorStrings = { export const backupServiceStrings = {
viewModeStorageError: (message: string) =>
i18n.translate('dashboard.viewmodeBackup.error', {
defaultMessage: 'Error encountered while backing up view mode: {message}',
values: { message },
}),
getPanelsGetError: (message: string) => getPanelsGetError: (message: string) =>
i18n.translate('dashboard.panelStorageError.getError', { i18n.translate('dashboard.panelStorageError.getError', {
defaultMessage: 'Error encountered while fetching unsaved changes: {message}', defaultMessage: 'Error encountered while fetching unsaved changes: {message}',

View file

@ -21,7 +21,7 @@ import {
ControlGroupContainerFactory, ControlGroupContainerFactory,
} from '@kbn/controls-plugin/public'; } from '@kbn/controls-plugin/public';
import { Filter } from '@kbn/es-query'; import { Filter } from '@kbn/es-query';
import { EmbeddablePackageState } from '@kbn/embeddable-plugin/public'; import { EmbeddablePackageState, ViewMode } from '@kbn/embeddable-plugin/public';
import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
import { createDashboard } from './create_dashboard'; import { createDashboard } from './create_dashboard';
@ -109,7 +109,55 @@ test('passes managed state from the saved object into the Dashboard component st
expect(dashboard!.getState().componentState.managed).toBe(true); expect(dashboard!.getState().componentState.managed).toBe(true);
}); });
test('pulls state from session storage which overrides state from saved object', async () => { test('pulls view mode from dashboard backup', async () => {
pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest
.fn()
.mockResolvedValue({
dashboardInput: DEFAULT_DASHBOARD_INPUT,
});
pluginServices.getServices().dashboardBackup.getViewMode = jest
.fn()
.mockReturnValue(ViewMode.EDIT);
const dashboard = await createDashboard({ useSessionStorageIntegration: true }, 0, 'what-an-id');
expect(dashboard).toBeDefined();
expect(dashboard!.getState().explicitInput.viewMode).toBe(ViewMode.EDIT);
});
test('new dashboards start in edit mode', async () => {
pluginServices.getServices().dashboardBackup.getViewMode = jest
.fn()
.mockReturnValue(ViewMode.VIEW);
pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest
.fn()
.mockResolvedValue({
newDashboardCreated: true,
dashboardInput: {
...DEFAULT_DASHBOARD_INPUT,
description: 'wow this description is okay',
},
});
const dashboard = await createDashboard({ useSessionStorageIntegration: true }, 0, 'wow-such-id');
expect(dashboard).toBeDefined();
expect(dashboard!.getState().explicitInput.viewMode).toBe(ViewMode.EDIT);
});
test('managed dashboards start in view mode', async () => {
pluginServices.getServices().dashboardBackup.getViewMode = jest
.fn()
.mockReturnValue(ViewMode.EDIT);
pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest
.fn()
.mockResolvedValue({
dashboardInput: DEFAULT_DASHBOARD_INPUT,
managed: true,
});
const dashboard = await createDashboard({}, 0, 'what-an-id');
expect(dashboard).toBeDefined();
expect(dashboard!.getState().componentState.managed).toBe(true);
expect(dashboard!.getState().explicitInput.viewMode).toBe(ViewMode.VIEW);
});
test('pulls state from backup which overrides state from saved object', async () => {
pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest pluginServices.getServices().dashboardContentManagement.loadDashboardState = jest
.fn() .fn()
.mockResolvedValue({ .mockResolvedValue({
@ -118,7 +166,7 @@ test('pulls state from session storage which overrides state from saved object',
description: 'wow this description is okay', description: 'wow this description is okay',
}, },
}); });
pluginServices.getServices().dashboardSessionStorage.getState = jest pluginServices.getServices().dashboardBackup.getState = jest
.fn() .fn()
.mockReturnValue({ description: 'wow this description marginally better' }); .mockReturnValue({ description: 'wow this description marginally better' });
const dashboard = await createDashboard({ useSessionStorageIntegration: true }, 0, 'wow-such-id'); const dashboard = await createDashboard({ useSessionStorageIntegration: true }, 0, 'wow-such-id');
@ -137,7 +185,7 @@ test('pulls state from creation options initial input which overrides all other
description: 'wow this description is okay', description: 'wow this description is okay',
}, },
}); });
pluginServices.getServices().dashboardSessionStorage.getState = jest pluginServices.getServices().dashboardBackup.getState = jest
.fn() .fn()
.mockReturnValue({ description: 'wow this description marginally better' }); .mockReturnValue({ description: 'wow this description marginally better' });
const dashboard = await createDashboard( const dashboard = await createDashboard(

View file

@ -135,8 +135,9 @@ export const initializeDashboard = async ({
controlGroup?: ControlGroupContainer; controlGroup?: ControlGroupContainer;
}) => { }) => {
const { const {
dashboardSessionStorage, dashboardBackup,
embeddable: { getEmbeddableFactory }, embeddable: { getEmbeddableFactory },
dashboardCapabilities: { showWriteControls },
data: { data: {
query: queryService, query: queryService,
search: { session }, search: { session },
@ -170,24 +171,43 @@ export const initializeDashboard = async ({
} }
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// Gather input from session storage if integration is used. // Gather input from session storage and local storage if integration is used.
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
const sessionStorageInput = ((): Partial<DashboardContainerInput> | undefined => { const sessionStorageInput = ((): Partial<DashboardContainerInput> | undefined => {
if (!useSessionStorageIntegration) return; if (!useSessionStorageIntegration) return;
return dashboardSessionStorage.getState(loadDashboardReturn.dashboardId); return dashboardBackup.getState(loadDashboardReturn.dashboardId);
})(); })();
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// Combine input from saved object, session storage, & passed input to create initial input. // Combine input from saved object, session storage, & passed input to create initial input.
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
const initialViewMode = (() => {
if (loadDashboardReturn.managed || !showWriteControls) return ViewMode.VIEW;
if (
loadDashboardReturn.newDashboardCreated ||
dashboardBackup.dashboardHasUnsavedEdits(loadDashboardReturn.dashboardId)
) {
return ViewMode.EDIT;
}
return dashboardBackup.getViewMode();
})();
const overrideInput = getInitialInput?.(); const overrideInput = getInitialInput?.();
const initialInput: DashboardContainerInput = cloneDeep({ const initialInput: DashboardContainerInput = cloneDeep({
...DEFAULT_DASHBOARD_INPUT, ...DEFAULT_DASHBOARD_INPUT,
...(loadDashboardReturn?.dashboardInput ?? {}), ...(loadDashboardReturn?.dashboardInput ?? {}),
...sessionStorageInput, ...sessionStorageInput,
...(initialViewMode ? { viewMode: initialViewMode } : {}),
...overrideInput, ...overrideInput,
}); });
// Back up any view mode passed in explicitly.
if (overrideInput?.viewMode) {
dashboardBackup.storeViewMode(overrideInput?.viewMode);
}
initialInput.executionContext = { initialInput.executionContext = {
type: 'dashboard', type: 'dashboard',
description: initialInput.title, description: initialInput.title,

View file

@ -402,13 +402,13 @@ export class DashboardContainer extends Container<InheritedChildInput, Dashboard
this.searchSessionId = searchSessionId; this.searchSessionId = searchSessionId;
this.updateInput(newInput);
batch(() => { batch(() => {
this.dispatch.setLastSavedInput(loadDashboardReturn?.dashboardInput); this.dispatch.setLastSavedInput(loadDashboardReturn?.dashboardInput);
this.dispatch.setManaged(loadDashboardReturn?.managed); this.dispatch.setManaged(loadDashboardReturn?.managed);
this.dispatch.setAnimatePanelTransforms(false); // prevents panels from animating on navigate. this.dispatch.setAnimatePanelTransforms(false); // prevents panels from animating on navigate.
this.dispatch.setLastSavedId(newSavedObjectId); this.dispatch.setLastSavedId(newSavedObjectId);
}); });
this.updateInput(newInput);
dashboardContainerReady$.next(this); dashboardContainerReady$.next(this);
}; };

View file

@ -211,8 +211,8 @@ function backupUnsavedChanges(
this: DashboardContainer, this: DashboardContainer,
unsavedChanges: Partial<DashboardContainerInput> unsavedChanges: Partial<DashboardContainerInput>
) { ) {
const { dashboardSessionStorage } = pluginServices.getServices(); const { dashboardBackup } = pluginServices.getServices();
dashboardSessionStorage.setState( dashboardBackup.setState(
this.getDashboardSavedObjectId(), this.getDashboardSavedObjectId(),
omit(unsavedChanges, keysToOmitFromSessionStorage) omit(unsavedChanges, keysToOmitFromSessionStorage)
); );

View file

@ -24,7 +24,7 @@ import {
} from './_dashboard_listing_strings'; } from './_dashboard_listing_strings';
import { pluginServices } from '../services/plugin_services'; import { pluginServices } from '../services/plugin_services';
import { confirmDiscardUnsavedChanges } from './confirm_overlays'; import { confirmDiscardUnsavedChanges } from './confirm_overlays';
import { DASHBOARD_PANELS_UNSAVED_ID } from '../services/dashboard_session_storage/dashboard_session_storage_service'; import { DASHBOARD_PANELS_UNSAVED_ID } from '../services/dashboard_backup/dashboard_backup_service';
import { DashboardListingProps } from './types'; import { DashboardListingProps } from './types';
export interface DashboardListingEmptyPromptProps { export interface DashboardListingEmptyPromptProps {
@ -46,7 +46,7 @@ export const DashboardListingEmptyPrompt = ({
}: DashboardListingEmptyPromptProps) => { }: DashboardListingEmptyPromptProps) => {
const { const {
application, application,
dashboardSessionStorage, dashboardBackup,
dashboardCapabilities: { showWriteControls }, dashboardCapabilities: { showWriteControls },
} = pluginServices.getServices(); } = pluginServices.getServices();
@ -77,8 +77,8 @@ export const DashboardListingEmptyPrompt = ({
color="danger" color="danger"
onClick={() => onClick={() =>
confirmDiscardUnsavedChanges(() => { confirmDiscardUnsavedChanges(() => {
dashboardSessionStorage.clearState(DASHBOARD_PANELS_UNSAVED_ID); dashboardBackup.clearState(DASHBOARD_PANELS_UNSAVED_ID);
setUnsavedDashboardIds(dashboardSessionStorage.getDashboardIdsWithUnsavedChanges()); setUnsavedDashboardIds(dashboardBackup.getDashboardIdsWithUnsavedChanges());
}) })
} }
data-test-subj="discardDashboardPromptButton" data-test-subj="discardDashboardPromptButton"
@ -105,7 +105,7 @@ export const DashboardListingEmptyPrompt = ({
isEditingFirstDashboard, isEditingFirstDashboard,
createItem, createItem,
disableCreateDashboardButton, disableCreateDashboardButton,
dashboardSessionStorage, dashboardBackup,
setUnsavedDashboardIds, setUnsavedDashboardIds,
goToDashboard, goToDashboard,
]); ]);

View file

@ -14,7 +14,7 @@ import { findTestSubject } from '@elastic/eui/lib/test';
import { pluginServices } from '../services/plugin_services'; import { pluginServices } from '../services/plugin_services';
import { DashboardUnsavedListing, DashboardUnsavedListingProps } from './dashboard_unsaved_listing'; import { DashboardUnsavedListing, DashboardUnsavedListingProps } from './dashboard_unsaved_listing';
import { DASHBOARD_PANELS_UNSAVED_ID } from '../services/dashboard_session_storage/dashboard_session_storage_service'; import { DASHBOARD_PANELS_UNSAVED_ID } from '../services/dashboard_backup/dashboard_backup_service';
import { ViewMode } from '@kbn/embeddable-plugin/public'; import { ViewMode } from '@kbn/embeddable-plugin/public';
const makeDefaultProps = (): DashboardUnsavedListingProps => ({ const makeDefaultProps = (): DashboardUnsavedListingProps => ({
@ -91,7 +91,7 @@ describe('Unsaved listing', () => {
waitFor(() => { waitFor(() => {
component.update(); component.update();
expect(pluginServices.getServices().overlays.openConfirm).toHaveBeenCalled(); expect(pluginServices.getServices().overlays.openConfirm).toHaveBeenCalled();
expect(pluginServices.getServices().dashboardSessionStorage.clearState).toHaveBeenCalledWith( expect(pluginServices.getServices().dashboardBackup.clearState).toHaveBeenCalledWith(
'dashboardUnsavedOne' 'dashboardUnsavedOne'
); );
}); });
@ -125,16 +125,16 @@ describe('Unsaved listing', () => {
const { component } = mountWith({ props }); const { component } = mountWith({ props });
waitFor(() => { waitFor(() => {
component.update(); component.update();
expect(pluginServices.getServices().dashboardSessionStorage.clearState).toHaveBeenCalledWith( expect(pluginServices.getServices().dashboardBackup.clearState).toHaveBeenCalledWith(
'failCase1' 'failCase1'
); );
expect(pluginServices.getServices().dashboardSessionStorage.clearState).toHaveBeenCalledWith( expect(pluginServices.getServices().dashboardBackup.clearState).toHaveBeenCalledWith(
'failCase2' 'failCase2'
); );
// clearing panels from dashboard with errors should cause getDashboardIdsWithUnsavedChanges to be called again. // clearing panels from dashboard with errors should cause getDashboardIdsWithUnsavedChanges to be called again.
expect( expect(
pluginServices.getServices().dashboardSessionStorage.getDashboardIdsWithUnsavedChanges pluginServices.getServices().dashboardBackup.getDashboardIdsWithUnsavedChanges
).toHaveBeenCalledTimes(2); ).toHaveBeenCalledTimes(2);
}); });
}); });

View file

@ -23,7 +23,7 @@ import { pluginServices } from '../services/plugin_services';
import { confirmDiscardUnsavedChanges } from './confirm_overlays'; import { confirmDiscardUnsavedChanges } from './confirm_overlays';
import { DashboardAttributes } from '../../common/content_management'; import { DashboardAttributes } from '../../common/content_management';
import { dashboardUnsavedListingStrings, getNewDashboardTitle } from './_dashboard_listing_strings'; import { dashboardUnsavedListingStrings, getNewDashboardTitle } from './_dashboard_listing_strings';
import { DASHBOARD_PANELS_UNSAVED_ID } from '../services/dashboard_session_storage/dashboard_session_storage_service'; import { DASHBOARD_PANELS_UNSAVED_ID } from '../services/dashboard_backup/dashboard_backup_service';
const DashboardUnsavedItem = ({ const DashboardUnsavedItem = ({
id, id,
@ -116,7 +116,7 @@ export const DashboardUnsavedListing = ({
refreshUnsavedDashboards, refreshUnsavedDashboards,
}: DashboardUnsavedListingProps) => { }: DashboardUnsavedListingProps) => {
const { const {
dashboardSessionStorage, dashboardBackup,
dashboardContentManagement: { findDashboards }, dashboardContentManagement: { findDashboards },
} = pluginServices.getServices(); } = pluginServices.getServices();
@ -132,11 +132,11 @@ export const DashboardUnsavedListing = ({
const onDiscard = useCallback( const onDiscard = useCallback(
(id?: string) => { (id?: string) => {
confirmDiscardUnsavedChanges(() => { confirmDiscardUnsavedChanges(() => {
dashboardSessionStorage.clearState(id); dashboardBackup.clearState(id);
refreshUnsavedDashboards(); refreshUnsavedDashboards();
}); });
}, },
[refreshUnsavedDashboards, dashboardSessionStorage] [refreshUnsavedDashboards, dashboardBackup]
); );
useEffect(() => { useEffect(() => {
@ -156,7 +156,7 @@ export const DashboardUnsavedListing = ({
const newItems = results.reduce((map, result) => { const newItems = results.reduce((map, result) => {
if (result.status === 'error') { if (result.status === 'error') {
hasError = true; hasError = true;
dashboardSessionStorage.clearState(result.id); dashboardBackup.clearState(result.id);
return map; return map;
} }
return { return {
@ -173,7 +173,7 @@ export const DashboardUnsavedListing = ({
return () => { return () => {
canceled = true; canceled = true;
}; };
}, [refreshUnsavedDashboards, dashboardSessionStorage, unsavedDashboardIds, findDashboards]); }, [refreshUnsavedDashboards, dashboardBackup, unsavedDashboardIds, findDashboards]);
return unsavedDashboardIds.length === 0 ? null : ( return unsavedDashboardIds.length === 0 ? null : (
<> <>

View file

@ -46,15 +46,13 @@ describe('useDashboardListingTable', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
getPluginServices.dashboardSessionStorage.dashboardHasUnsavedEdits = jest getPluginServices.dashboardBackup.dashboardHasUnsavedEdits = jest.fn().mockReturnValue(true);
.fn()
.mockReturnValue(true);
getPluginServices.dashboardSessionStorage.getDashboardIdsWithUnsavedChanges = jest getPluginServices.dashboardBackup.getDashboardIdsWithUnsavedChanges = jest
.fn() .fn()
.mockReturnValue([]); .mockReturnValue([]);
getPluginServices.dashboardSessionStorage.clearState = clearStateMock; getPluginServices.dashboardBackup.clearState = clearStateMock;
getPluginServices.dashboardCapabilities.showWriteControls = true; getPluginServices.dashboardCapabilities.showWriteControls = true;
getPluginServices.dashboardContentManagement.deleteDashboards = deleteDashboards; getPluginServices.dashboardContentManagement.deleteDashboards = deleteDashboards;
getPluginServices.settings.uiSettings.get = getUiSettingsMock; getPluginServices.settings.uiSettings.get = getUiSettingsMock;

View file

@ -87,7 +87,7 @@ export const useDashboardListingTable = ({
showCreateDashboardButton?: boolean; showCreateDashboardButton?: boolean;
}): UseDashboardListingTableReturnType => { }): UseDashboardListingTableReturnType => {
const { const {
dashboardSessionStorage, dashboardBackup,
dashboardCapabilities: { showWriteControls }, dashboardCapabilities: { showWriteControls },
settings: { uiSettings }, settings: { uiSettings },
dashboardContentManagement: { dashboardContentManagement: {
@ -106,30 +106,30 @@ export const useDashboardListingTable = ({
const [pageDataTestSubject, setPageDataTestSubject] = useState<string>(); const [pageDataTestSubject, setPageDataTestSubject] = useState<string>();
const [hasInitialFetchReturned, setHasInitialFetchReturned] = useState(false); const [hasInitialFetchReturned, setHasInitialFetchReturned] = useState(false);
const [unsavedDashboardIds, setUnsavedDashboardIds] = useState<string[]>( const [unsavedDashboardIds, setUnsavedDashboardIds] = useState<string[]>(
dashboardSessionStorage.getDashboardIdsWithUnsavedChanges() dashboardBackup.getDashboardIdsWithUnsavedChanges()
); );
const listingLimit = uiSettings.get(SAVED_OBJECTS_LIMIT_SETTING); const listingLimit = uiSettings.get(SAVED_OBJECTS_LIMIT_SETTING);
const initialPageSize = uiSettings.get(SAVED_OBJECTS_PER_PAGE_SETTING); const initialPageSize = uiSettings.get(SAVED_OBJECTS_PER_PAGE_SETTING);
const createItem = useCallback(() => { const createItem = useCallback(() => {
if (useSessionStorageIntegration && dashboardSessionStorage.dashboardHasUnsavedEdits()) { if (useSessionStorageIntegration && dashboardBackup.dashboardHasUnsavedEdits()) {
confirmCreateWithUnsaved(() => { confirmCreateWithUnsaved(() => {
dashboardSessionStorage.clearState(); dashboardBackup.clearState();
goToDashboard(); goToDashboard();
}, goToDashboard); }, goToDashboard);
return; return;
} }
goToDashboard(); goToDashboard();
}, [dashboardSessionStorage, goToDashboard, useSessionStorageIntegration]); }, [dashboardBackup, goToDashboard, useSessionStorageIntegration]);
const updateItemMeta = useCallback( const updateItemMeta = useCallback(
async (props: Pick<DashboardContainerInput, 'id' | 'title' | 'description' | 'tags'>) => { async (props: Pick<DashboardContainerInput, 'id' | 'title' | 'description' | 'tags'>) => {
await updateDashboardMeta(props); await updateDashboardMeta(props);
setUnsavedDashboardIds(dashboardSessionStorage.getDashboardIdsWithUnsavedChanges()); setUnsavedDashboardIds(dashboardBackup.getDashboardIdsWithUnsavedChanges());
}, },
[dashboardSessionStorage, updateDashboardMeta] [dashboardBackup, updateDashboardMeta]
); );
const contentEditorValidators: OpenContentEditorParams['customValidators'] = useMemo( const contentEditorValidators: OpenContentEditorParams['customValidators'] = useMemo(
@ -232,7 +232,7 @@ export const useDashboardListingTable = ({
await deleteDashboards( await deleteDashboards(
dashboardsToDelete.map(({ id }) => { dashboardsToDelete.map(({ id }) => {
dashboardSessionStorage.clearState(id); dashboardBackup.clearState(id);
return id; return id;
}) })
); );
@ -252,9 +252,9 @@ export const useDashboardListingTable = ({
}); });
} }
setUnsavedDashboardIds(dashboardSessionStorage.getDashboardIdsWithUnsavedChanges()); setUnsavedDashboardIds(dashboardBackup.getDashboardIdsWithUnsavedChanges());
}, },
[dashboardSessionStorage, deleteDashboards, toasts] [dashboardBackup, deleteDashboards, toasts]
); );
const editItem = useCallback( const editItem = useCallback(
@ -324,8 +324,8 @@ export const useDashboardListingTable = ({
); );
const refreshUnsavedDashboards = useCallback( const refreshUnsavedDashboards = useCallback(
() => setUnsavedDashboardIds(dashboardSessionStorage.getDashboardIdsWithUnsavedChanges()), () => setUnsavedDashboardIds(dashboardBackup.getDashboardIdsWithUnsavedChanges()),
[dashboardSessionStorage] [dashboardBackup]
); );
return { return {

View file

@ -7,16 +7,17 @@
*/ */
import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; import { PluginServiceFactory } from '@kbn/presentation-util-plugin/public';
import { DashboardSessionStorageServiceType } from './types'; import { DashboardBackupServiceType } from './types';
type DashboardSessionStorageServiceFactory = type DashboardBackupServiceFactory = PluginServiceFactory<DashboardBackupServiceType>;
PluginServiceFactory<DashboardSessionStorageServiceType>;
export const dashboardSessionStorageServiceFactory: DashboardSessionStorageServiceFactory = () => { export const dashboardBackupServiceFactory: DashboardBackupServiceFactory = () => {
return { return {
clearState: jest.fn(), clearState: jest.fn(),
getState: jest.fn().mockReturnValue(undefined), getState: jest.fn().mockReturnValue(undefined),
setState: jest.fn(), setState: jest.fn(),
getViewMode: jest.fn(),
storeViewMode: jest.fn(),
getDashboardIdsWithUnsavedChanges: jest getDashboardIdsWithUnsavedChanges: jest
.fn() .fn()
.mockReturnValue(['dashboardUnsavedOne', 'dashboardUnsavedTwo']), .mockReturnValue(['dashboardUnsavedOne', 'dashboardUnsavedTwo']),

View file

@ -15,34 +15,37 @@ import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/p
import { DashboardSpacesService } from '../spaces/types'; import { DashboardSpacesService } from '../spaces/types';
import type { DashboardStartDependencies } from '../../plugin'; import type { DashboardStartDependencies } from '../../plugin';
import type { DashboardSessionStorageServiceType } from './types'; import type { DashboardBackupServiceType } from './types';
import type { DashboardContainerInput } from '../../../common'; import type { DashboardContainerInput } from '../../../common';
import { DashboardNotificationsService } from '../notifications/types'; import { DashboardNotificationsService } from '../notifications/types';
import { panelStorageErrorStrings } from '../../dashboard_container/_dashboard_container_strings'; import { backupServiceStrings } from '../../dashboard_container/_dashboard_container_strings';
export const DASHBOARD_PANELS_UNSAVED_ID = 'unsavedDashboard'; export const DASHBOARD_PANELS_UNSAVED_ID = 'unsavedDashboard';
const DASHBOARD_PANELS_SESSION_KEY = 'dashboardStateManagerPanels'; const DASHBOARD_PANELS_SESSION_KEY = 'dashboardStateManagerPanels';
const DASHBOARD_VIEWMODE_LOCAL_KEY = 'dashboardViewMode';
interface DashboardSessionStorageRequiredServices { interface DashboardBackupRequiredServices {
notifications: DashboardNotificationsService; notifications: DashboardNotificationsService;
spaces: DashboardSpacesService; spaces: DashboardSpacesService;
} }
export type DashboardSessionStorageServiceFactory = KibanaPluginServiceFactory< export type DashboardBackupServiceFactory = KibanaPluginServiceFactory<
DashboardSessionStorageServiceType, DashboardBackupServiceType,
DashboardStartDependencies, DashboardStartDependencies,
DashboardSessionStorageRequiredServices DashboardBackupRequiredServices
>; >;
class DashboardSessionStorageService implements DashboardSessionStorageServiceType { class DashboardBackupService implements DashboardBackupServiceType {
private activeSpaceId: string; private activeSpaceId: string;
private sessionStorage: Storage; private sessionStorage: Storage;
private localStorage: Storage;
private notifications: DashboardNotificationsService; private notifications: DashboardNotificationsService;
private spaces: DashboardSpacesService; private spaces: DashboardSpacesService;
constructor(requiredServices: DashboardSessionStorageRequiredServices) { constructor(requiredServices: DashboardBackupRequiredServices) {
({ notifications: this.notifications, spaces: this.spaces } = requiredServices); ({ notifications: this.notifications, spaces: this.spaces } = requiredServices);
this.sessionStorage = new Storage(sessionStorage); this.sessionStorage = new Storage(sessionStorage);
this.localStorage = new Storage(localStorage);
this.activeSpaceId = 'default'; this.activeSpaceId = 'default';
if (this.spaces.getActiveSpace$) { if (this.spaces.getActiveSpace$) {
@ -52,6 +55,21 @@ class DashboardSessionStorageService implements DashboardSessionStorageServiceTy
} }
} }
public getViewMode = (): ViewMode => {
return this.localStorage.get(DASHBOARD_VIEWMODE_LOCAL_KEY);
};
public storeViewMode = (viewMode: ViewMode) => {
try {
this.localStorage.set(DASHBOARD_VIEWMODE_LOCAL_KEY, viewMode);
} catch (e) {
this.notifications.toasts.addDanger({
title: backupServiceStrings.viewModeStorageError(e.message),
'data-test-subj': 'dashboardViewmodeBackupFailure',
});
}
};
public clearState(id = DASHBOARD_PANELS_UNSAVED_ID) { public clearState(id = DASHBOARD_PANELS_UNSAVED_ID) {
try { try {
const sessionStorage = this.sessionStorage.get(DASHBOARD_PANELS_SESSION_KEY); const sessionStorage = this.sessionStorage.get(DASHBOARD_PANELS_SESSION_KEY);
@ -62,7 +80,7 @@ class DashboardSessionStorageService implements DashboardSessionStorageServiceTy
} }
} catch (e) { } catch (e) {
this.notifications.toasts.addDanger({ this.notifications.toasts.addDanger({
title: panelStorageErrorStrings.getPanelsClearError(e.message), title: backupServiceStrings.getPanelsClearError(e.message),
'data-test-subj': 'dashboardPanelsClearFailure', 'data-test-subj': 'dashboardPanelsClearFailure',
}); });
} }
@ -73,7 +91,7 @@ class DashboardSessionStorageService implements DashboardSessionStorageServiceTy
return this.sessionStorage.get(DASHBOARD_PANELS_SESSION_KEY)?.[this.activeSpaceId]?.[id]; return this.sessionStorage.get(DASHBOARD_PANELS_SESSION_KEY)?.[this.activeSpaceId]?.[id];
} catch (e) { } catch (e) {
this.notifications.toasts.addDanger({ this.notifications.toasts.addDanger({
title: panelStorageErrorStrings.getPanelsGetError(e.message), title: backupServiceStrings.getPanelsGetError(e.message),
'data-test-subj': 'dashboardPanelsGetFailure', 'data-test-subj': 'dashboardPanelsGetFailure',
}); });
} }
@ -86,7 +104,7 @@ class DashboardSessionStorageService implements DashboardSessionStorageServiceTy
this.sessionStorage.set(DASHBOARD_PANELS_SESSION_KEY, sessionStateStorage); this.sessionStorage.set(DASHBOARD_PANELS_SESSION_KEY, sessionStateStorage);
} catch (e) { } catch (e) {
this.notifications.toasts.addDanger({ this.notifications.toasts.addDanger({
title: panelStorageErrorStrings.getPanelsSetError(e.message), title: backupServiceStrings.getPanelsSetError(e.message),
'data-test-subj': 'dashboardPanelsSetFailure', 'data-test-subj': 'dashboardPanelsSetFailure',
}); });
} }
@ -110,7 +128,7 @@ class DashboardSessionStorageService implements DashboardSessionStorageServiceTy
return dashboardsWithUnsavedChanges; return dashboardsWithUnsavedChanges;
} catch (e) { } catch (e) {
this.notifications.toasts.addDanger({ this.notifications.toasts.addDanger({
title: panelStorageErrorStrings.getPanelsGetError(e.message), title: backupServiceStrings.getPanelsGetError(e.message),
'data-test-subj': 'dashboardPanelsGetFailure', 'data-test-subj': 'dashboardPanelsGetFailure',
}); });
return []; return [];
@ -122,9 +140,9 @@ class DashboardSessionStorageService implements DashboardSessionStorageServiceTy
} }
} }
export const dashboardSessionStorageServiceFactory: DashboardSessionStorageServiceFactory = ( export const dashboardBackupServiceFactory: DashboardBackupServiceFactory = (
core, core,
requiredServices requiredServices
) => { ) => {
return new DashboardSessionStorageService(requiredServices); return new DashboardBackupService(requiredServices);
}; };

View file

@ -6,12 +6,15 @@
* Side Public License, v 1. * Side Public License, v 1.
*/ */
import { ViewMode } from '@kbn/embeddable-plugin/public';
import type { DashboardContainerInput } from '../../../common'; import type { DashboardContainerInput } from '../../../common';
export interface DashboardSessionStorageServiceType { export interface DashboardBackupServiceType {
clearState: (id?: string) => void; clearState: (id?: string) => void;
getState: (id: string | undefined) => Partial<DashboardContainerInput> | undefined; getState: (id: string | undefined) => Partial<DashboardContainerInput> | undefined;
setState: (id: string | undefined, newState: Partial<DashboardContainerInput>) => void; setState: (id: string | undefined, newState: Partial<DashboardContainerInput>) => void;
getViewMode: () => ViewMode;
storeViewMode: (viewMode: ViewMode) => void;
getDashboardIdsWithUnsavedChanges: () => string[]; getDashboardIdsWithUnsavedChanges: () => string[];
dashboardHasUnsavedEdits: (id?: string) => boolean; dashboardHasUnsavedEdits: (id?: string) => boolean;
} }

View file

@ -43,9 +43,9 @@ export const dashboardContentManagementServiceFactory: DashboardContentManagemen
data, data,
embeddable, embeddable,
notifications, notifications,
dashboardBackup,
initializerContext, initializerContext,
savedObjectsTagging, savedObjectsTagging,
dashboardSessionStorage,
} = requiredServices; } = requiredServices;
return { return {
loadDashboardState: ({ id }) => loadDashboardState: ({ id }) =>
@ -64,10 +64,10 @@ export const dashboardContentManagementServiceFactory: DashboardContentManagemen
lastSavedId, lastSavedId,
currentState, currentState,
notifications, notifications,
dashboardBackup,
contentManagement, contentManagement,
initializerContext, initializerContext,
savedObjectsTagging, savedObjectsTagging,
dashboardSessionStorage,
}), }),
findDashboards: { findDashboards: {
search: ({ hasReference, hasNoReference, search, size, options }) => search: ({ hasReference, hasNoReference, search, size, options }) =>

View file

@ -56,7 +56,9 @@ export const loadDashboardState = async ({
/** /**
* This is a newly created dashboard, so there is no saved object state to load. * This is a newly created dashboard, so there is no saved object state to load.
*/ */
if (!savedObjectId) return { dashboardInput: newDashboardState, dashboardFound: true }; if (!savedObjectId) {
return { dashboardInput: newDashboardState, dashboardFound: true, newDashboardCreated: true };
}
/** /**
* Load the saved object from Content Management * Load the saved object from Content Management

View file

@ -63,9 +63,9 @@ type SaveDashboardStateProps = SaveDashboardProps & {
contentManagement: DashboardStartDependencies['contentManagement']; contentManagement: DashboardStartDependencies['contentManagement'];
embeddable: DashboardContentManagementRequiredServices['embeddable']; embeddable: DashboardContentManagementRequiredServices['embeddable'];
notifications: DashboardContentManagementRequiredServices['notifications']; notifications: DashboardContentManagementRequiredServices['notifications'];
dashboardBackup: DashboardContentManagementRequiredServices['dashboardBackup'];
initializerContext: DashboardContentManagementRequiredServices['initializerContext']; initializerContext: DashboardContentManagementRequiredServices['initializerContext'];
savedObjectsTagging: DashboardContentManagementRequiredServices['savedObjectsTagging']; savedObjectsTagging: DashboardContentManagementRequiredServices['savedObjectsTagging'];
dashboardSessionStorage: DashboardContentManagementRequiredServices['dashboardSessionStorage'];
}; };
export const saveDashboardState = async ({ export const saveDashboardState = async ({
@ -74,9 +74,9 @@ export const saveDashboardState = async ({
lastSavedId, lastSavedId,
saveOptions, saveOptions,
currentState, currentState,
dashboardBackup,
contentManagement, contentManagement,
savedObjectsTagging, savedObjectsTagging,
dashboardSessionStorage,
notifications: { toasts }, notifications: { toasts },
}: SaveDashboardStateProps): Promise<SaveDashboardReturn> => { }: SaveDashboardStateProps): Promise<SaveDashboardReturn> => {
const { const {
@ -202,7 +202,7 @@ export const saveDashboardState = async ({
* If the dashboard id has been changed, redirect to the new ID to keep the url param in sync. * If the dashboard id has been changed, redirect to the new ID to keep the url param in sync.
*/ */
if (newId !== lastSavedId) { if (newId !== lastSavedId) {
dashboardSessionStorage.clearState(lastSavedId); dashboardBackup.clearState(lastSavedId);
return { redirectRequired: true, id: newId }; return { redirectRequired: true, id: newId };
} else { } else {
dashboardContentManagementCache.deleteDashboard(newId); // something changed in an existing dashboard, so delete it from the cache so that it can be re-fetched dashboardContentManagementCache.deleteDashboard(newId); // something changed in an existing dashboard, so delete it from the cache so that it can be re-fetched

View file

@ -23,7 +23,7 @@ import { DashboardCrudTypes } from '../../../common/content_management';
import { DashboardScreenshotModeService } from '../screenshot_mode/types'; import { DashboardScreenshotModeService } from '../screenshot_mode/types';
import { DashboardInitializerContextService } from '../initializer_context/types'; import { DashboardInitializerContextService } from '../initializer_context/types';
import { DashboardSavedObjectsTaggingService } from '../saved_objects_tagging/types'; import { DashboardSavedObjectsTaggingService } from '../saved_objects_tagging/types';
import { DashboardSessionStorageServiceType } from '../dashboard_session_storage/types'; import { DashboardBackupServiceType } from '../dashboard_backup/types';
import { DashboardDuplicateTitleCheckProps } from './lib/check_for_duplicate_dashboard_title'; import { DashboardDuplicateTitleCheckProps } from './lib/check_for_duplicate_dashboard_title';
export interface DashboardContentManagementRequiredServices { export interface DashboardContentManagementRequiredServices {
@ -31,10 +31,10 @@ export interface DashboardContentManagementRequiredServices {
spaces: DashboardSpacesService; spaces: DashboardSpacesService;
embeddable: DashboardEmbeddableService; embeddable: DashboardEmbeddableService;
notifications: DashboardNotificationsService; notifications: DashboardNotificationsService;
dashboardBackup: DashboardBackupServiceType;
screenshotMode: DashboardScreenshotModeService; screenshotMode: DashboardScreenshotModeService;
initializerContext: DashboardInitializerContextService; initializerContext: DashboardInitializerContextService;
savedObjectsTagging: DashboardSavedObjectsTaggingService; savedObjectsTagging: DashboardSavedObjectsTaggingService;
dashboardSessionStorage: DashboardSessionStorageServiceType;
} }
export interface DashboardContentManagementService { export interface DashboardContentManagementService {
@ -63,6 +63,7 @@ type DashboardResolveMeta = DashboardCrudTypes['GetOut']['meta'];
export interface LoadDashboardReturn { export interface LoadDashboardReturn {
dashboardFound: boolean; dashboardFound: boolean;
newDashboardCreated?: boolean;
dashboardId?: string; dashboardId?: string;
managed?: boolean; managed?: boolean;
resolveMeta?: DashboardResolveMeta; resolveMeta?: DashboardResolveMeta;

View file

@ -19,7 +19,7 @@ import { applicationServiceFactory } from './application/application.stub';
import { chromeServiceFactory } from './chrome/chrome.stub'; import { chromeServiceFactory } from './chrome/chrome.stub';
import { coreContextServiceFactory } from './core_context/core_context.stub'; import { coreContextServiceFactory } from './core_context/core_context.stub';
import { dashboardCapabilitiesServiceFactory } from './dashboard_capabilities/dashboard_capabilities.stub'; import { dashboardCapabilitiesServiceFactory } from './dashboard_capabilities/dashboard_capabilities.stub';
import { dashboardSessionStorageServiceFactory } from './dashboard_session_storage/dashboard_session_storage.stub'; import { dashboardBackupServiceFactory } from './dashboard_backup/dashboard_backup.stub';
import { dataServiceFactory } from './data/data.stub'; import { dataServiceFactory } from './data/data.stub';
import { dataViewEditorServiceFactory } from './data_view_editor/data_view_editor.stub'; import { dataViewEditorServiceFactory } from './data_view_editor/data_view_editor.stub';
import { documentationLinksServiceFactory } from './documentation_links/documentation_links.stub'; import { documentationLinksServiceFactory } from './documentation_links/documentation_links.stub';
@ -51,7 +51,7 @@ export const providers: PluginServiceProviders<DashboardServices> = {
chrome: new PluginServiceProvider(chromeServiceFactory), chrome: new PluginServiceProvider(chromeServiceFactory),
coreContext: new PluginServiceProvider(coreContextServiceFactory), coreContext: new PluginServiceProvider(coreContextServiceFactory),
dashboardCapabilities: new PluginServiceProvider(dashboardCapabilitiesServiceFactory), dashboardCapabilities: new PluginServiceProvider(dashboardCapabilitiesServiceFactory),
dashboardSessionStorage: new PluginServiceProvider(dashboardSessionStorageServiceFactory), dashboardBackup: new PluginServiceProvider(dashboardBackupServiceFactory),
data: new PluginServiceProvider(dataServiceFactory), data: new PluginServiceProvider(dataServiceFactory),
dataViewEditor: new PluginServiceProvider(dataViewEditorServiceFactory), dataViewEditor: new PluginServiceProvider(dataViewEditorServiceFactory),
documentationLinks: new PluginServiceProvider(documentationLinksServiceFactory), documentationLinks: new PluginServiceProvider(documentationLinksServiceFactory),

View file

@ -19,7 +19,7 @@ import { applicationServiceFactory } from './application/application_service';
import { chromeServiceFactory } from './chrome/chrome_service'; import { chromeServiceFactory } from './chrome/chrome_service';
import { coreContextServiceFactory } from './core_context/core_context_service'; import { coreContextServiceFactory } from './core_context/core_context_service';
import { dashboardCapabilitiesServiceFactory } from './dashboard_capabilities/dashboard_capabilities_service'; import { dashboardCapabilitiesServiceFactory } from './dashboard_capabilities/dashboard_capabilities_service';
import { dashboardSessionStorageServiceFactory } from './dashboard_session_storage/dashboard_session_storage_service'; import { dashboardBackupServiceFactory } from './dashboard_backup/dashboard_backup_service';
import { dataServiceFactory } from './data/data_service'; import { dataServiceFactory } from './data/data_service';
import { dataViewEditorServiceFactory } from './data_view_editor/data_view_editor_service'; import { dataViewEditorServiceFactory } from './data_view_editor/data_view_editor_service';
import { documentationLinksServiceFactory } from './documentation_links/documentation_links_service'; import { documentationLinksServiceFactory } from './documentation_links/documentation_links_service';
@ -47,16 +47,16 @@ import { noDataPageServiceFactory } from './no_data_page/no_data_page_service';
const providers: PluginServiceProviders<DashboardServices, DashboardPluginServiceParams> = { const providers: PluginServiceProviders<DashboardServices, DashboardPluginServiceParams> = {
dashboardContentManagement: new PluginServiceProvider(dashboardContentManagementServiceFactory, [ dashboardContentManagement: new PluginServiceProvider(dashboardContentManagementServiceFactory, [
'dashboardSessionStorage',
'savedObjectsTagging', 'savedObjectsTagging',
'initializerContext', 'initializerContext',
'dashboardBackup',
'screenshotMode', 'screenshotMode',
'notifications', 'notifications',
'embeddable', 'embeddable',
'spaces', 'spaces',
'data', 'data',
]), ]),
dashboardSessionStorage: new PluginServiceProvider(dashboardSessionStorageServiceFactory, [ dashboardBackup: new PluginServiceProvider(dashboardBackupServiceFactory, [
'notifications', 'notifications',
'spaces', 'spaces',
]), ]),

View file

@ -19,7 +19,7 @@ import { DashboardCoreContextService } from './core_context/types';
import { DashboardCustomBrandingService } from './custom_branding/types'; import { DashboardCustomBrandingService } from './custom_branding/types';
import { DashboardCapabilitiesService } from './dashboard_capabilities/types'; import { DashboardCapabilitiesService } from './dashboard_capabilities/types';
import { DashboardContentManagementService } from './dashboard_content_management/types'; import { DashboardContentManagementService } from './dashboard_content_management/types';
import { DashboardSessionStorageServiceType } from './dashboard_session_storage/types'; import { DashboardBackupServiceType } from './dashboard_backup/types';
import { DashboardDataService } from './data/types'; import { DashboardDataService } from './data/types';
import { DashboardDataViewEditorService } from './data_view_editor/types'; import { DashboardDataViewEditorService } from './data_view_editor/types';
import { DashboardDocumentationLinksService } from './documentation_links/types'; import { DashboardDocumentationLinksService } from './documentation_links/types';
@ -44,7 +44,7 @@ export type DashboardPluginServiceParams = KibanaPluginServiceParams<DashboardSt
initContext: PluginInitializerContext; // need a custom type so that initContext is a required parameter for initializerContext initContext: PluginInitializerContext; // need a custom type so that initContext is a required parameter for initializerContext
}; };
export interface DashboardServices { export interface DashboardServices {
dashboardSessionStorage: DashboardSessionStorageServiceType; dashboardBackup: DashboardBackupServiceType;
dashboardContentManagement: DashboardContentManagementService; dashboardContentManagement: DashboardContentManagementService;
analytics: DashboardAnalyticsService; analytics: DashboardAnalyticsService;

View file

@ -38,6 +38,24 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await kibanaServer.savedObjects.cleanStandardList(); await kibanaServer.savedObjects.cleanStandardList();
}); });
it('existing dashboard opens in last used view mode', async function () {
await PageObjects.dashboard.gotoDashboardLandingPage();
await PageObjects.dashboard.loadSavedDashboard(dashboardName);
expect(await PageObjects.dashboard.getIsInViewMode()).to.equal(true);
await PageObjects.dashboard.switchToEditMode();
await PageObjects.dashboard.gotoDashboardLandingPage();
await PageObjects.dashboard.loadSavedDashboard(dashboardName);
expect(await PageObjects.dashboard.getIsInViewMode()).to.equal(false);
await PageObjects.dashboard.gotoDashboardLandingPage();
await PageObjects.dashboard.loadSavedDashboard('few panels');
expect(await PageObjects.dashboard.getIsInViewMode()).to.equal(false);
await PageObjects.dashboard.clickCancelOutOfEditMode();
});
it('create new dashboard opens in edit mode', async function () { it('create new dashboard opens in edit mode', async function () {
await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.gotoDashboardLandingPage();
await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.clickNewDashboard();
@ -45,14 +63,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(isInViewMode).to.be(false); expect(isInViewMode).to.be(false);
}); });
it('existing dashboard opens in view mode', async function () {
await PageObjects.dashboard.gotoDashboardLandingPage();
await PageObjects.dashboard.loadSavedDashboard(dashboardName);
const inViewMode = await PageObjects.dashboard.getIsInViewMode();
expect(inViewMode).to.equal(true);
});
describe('save', function () { describe('save', function () {
it('auto exits out of edit mode', async function () { it('auto exits out of edit mode', async function () {
await PageObjects.dashboard.gotoDashboardEditMode(dashboardName); await PageObjects.dashboard.gotoDashboardEditMode(dashboardName);

View file

@ -182,11 +182,11 @@ export class DashboardPageObject extends FtrService {
public async expectOnDashboard(expectedTitle: string) { public async expectOnDashboard(expectedTitle: string) {
await this.retry.waitFor( await this.retry.waitFor(
`last breadcrumb to have dashboard title: ${expectedTitle}`, `last breadcrumb to have dashboard title: ${expectedTitle} OR Editing ${expectedTitle}`,
async () => { async () => {
const actualTitle = await this.globalNav.getLastBreadcrumb(); const actualTitle = await this.globalNav.getLastBreadcrumb();
this.log.debug(`Expected dashboard title ${expectedTitle}, actual: ${actualTitle}`); this.log.debug(`Expected dashboard title ${expectedTitle}, actual: ${actualTitle}`);
return actualTitle === expectedTitle; return actualTitle === expectedTitle || actualTitle === `Editing ${expectedTitle}`;
} }
); );
} }