mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* Added unsaved changes badge to dashboards. Removed (unsaved) from the dashboard title
This commit is contained in:
parent
d41df11804
commit
f81cdc3ff7
15 changed files with 148 additions and 49 deletions
|
@ -67,7 +67,13 @@ export function DashboardApp({
|
|||
savedDashboard,
|
||||
history
|
||||
);
|
||||
const dashboardContainer = useDashboardContainer(dashboardStateManager, history, false);
|
||||
const [unsavedChanges, setUnsavedChanges] = useState(false);
|
||||
const dashboardContainer = useDashboardContainer({
|
||||
timeFilter: data.query.timefilter.timefilter,
|
||||
dashboardStateManager,
|
||||
setUnsavedChanges,
|
||||
history,
|
||||
});
|
||||
const searchSessionIdQuery$ = useMemo(
|
||||
() => createQueryParamObservable(history, DashboardConstants.SEARCH_SESSION_ID),
|
||||
[history]
|
||||
|
@ -200,6 +206,7 @@ export function DashboardApp({
|
|||
);
|
||||
|
||||
dashboardStateManager.registerChangeListener(() => {
|
||||
setUnsavedChanges(dashboardStateManager?.hasUnsavedPanelState());
|
||||
// we aren't checking dirty state because there are changes the container needs to know about
|
||||
// that won't make the dashboard "dirty" - like a view mode change.
|
||||
triggerRefresh$.next();
|
||||
|
@ -281,6 +288,7 @@ export function DashboardApp({
|
|||
embedSettings,
|
||||
indexPatterns,
|
||||
savedDashboard,
|
||||
unsavedChanges,
|
||||
dashboardContainer,
|
||||
dashboardStateManager,
|
||||
}}
|
||||
|
|
|
@ -17,6 +17,7 @@ import { createKbnUrlStateStorage } from '../services/kibana_utils';
|
|||
import { InputTimeRange, TimefilterContract, TimeRange } from '../services/data';
|
||||
|
||||
import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks';
|
||||
import { coreMock } from '../../../../core/public/mocks';
|
||||
|
||||
describe('DashboardState', function () {
|
||||
let dashboardState: DashboardStateManager;
|
||||
|
@ -45,6 +46,7 @@ describe('DashboardState', function () {
|
|||
kibanaVersion: '7.0.0',
|
||||
kbnUrlStateStorage: createKbnUrlStateStorage(),
|
||||
history: createBrowserHistory(),
|
||||
toasts: coreMock.createStart().notifications.toasts,
|
||||
hasTaggingCapabilities: mockHasTaggingCapabilities,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -43,6 +43,8 @@ import {
|
|||
syncState,
|
||||
} from '../services/kibana_utils';
|
||||
import { STATE_STORAGE_KEY } from '../url_generator';
|
||||
import { NotificationsStart } from '../services/core';
|
||||
import { getMigratedToastText } from '../dashboard_strings';
|
||||
|
||||
/**
|
||||
* Dashboard state manager handles connecting angular and redux state between the angular and react portions of the
|
||||
|
@ -59,10 +61,12 @@ export class DashboardStateManager {
|
|||
query: Query;
|
||||
};
|
||||
private stateDefaults: DashboardAppStateDefaults;
|
||||
private toasts: NotificationsStart['toasts'];
|
||||
private hideWriteControls: boolean;
|
||||
private kibanaVersion: string;
|
||||
public isDirty: boolean;
|
||||
private changeListeners: Array<(status: { dirty: boolean }) => void>;
|
||||
private hasShownMigrationToast = false;
|
||||
|
||||
public get appState(): DashboardAppState {
|
||||
return this.stateContainer.get();
|
||||
|
@ -93,6 +97,7 @@ export class DashboardStateManager {
|
|||
* @param
|
||||
*/
|
||||
constructor({
|
||||
toasts,
|
||||
history,
|
||||
kibanaVersion,
|
||||
savedDashboard,
|
||||
|
@ -108,11 +113,13 @@ export class DashboardStateManager {
|
|||
hideWriteControls: boolean;
|
||||
allowByValueEmbeddables: boolean;
|
||||
savedDashboard: DashboardSavedObject;
|
||||
toasts: NotificationsStart['toasts'];
|
||||
usageCollection?: UsageCollectionSetup;
|
||||
kbnUrlStateStorage: IKbnUrlStateStorage;
|
||||
dashboardPanelStorage?: DashboardPanelStorage;
|
||||
hasTaggingCapabilities: SavedObjectTagDecoratorTypeGuard;
|
||||
}) {
|
||||
this.toasts = toasts;
|
||||
this.kibanaVersion = kibanaVersion;
|
||||
this.savedDashboard = savedDashboard;
|
||||
this.hideWriteControls = hideWriteControls;
|
||||
|
@ -283,6 +290,10 @@ export class DashboardStateManager {
|
|||
if (dirty) {
|
||||
this.stateContainer.transitions.set('panels', Object.values(convertedPanelStateMap));
|
||||
if (dirtyBecauseOfInitialStateMigration) {
|
||||
if (this.getIsEditMode() && !this.hasShownMigrationToast) {
|
||||
this.toasts.addSuccess(getMigratedToastText());
|
||||
this.hasShownMigrationToast = true;
|
||||
}
|
||||
this.saveState({ replace: true });
|
||||
}
|
||||
|
||||
|
@ -693,6 +704,11 @@ export class DashboardStateManager {
|
|||
this.dashboardPanelStorage.clearPanels(this.savedDashboard?.id);
|
||||
}
|
||||
|
||||
public hasUnsavedPanelState(): boolean {
|
||||
const panels = this.dashboardPanelStorage?.getPanels(this.savedDashboard?.id);
|
||||
return panels !== undefined && panels.length > 0;
|
||||
}
|
||||
|
||||
private getUnsavedPanelState(): { panels?: SavedDashboardPanel[] } {
|
||||
if (!this.allowByValueEmbeddables || this.getIsViewMode() || !this.dashboardPanelStorage) {
|
||||
return {};
|
||||
|
|
|
@ -45,7 +45,6 @@ export const useDashboardBreadcrumbs = (
|
|||
text: getDashboardTitle(
|
||||
dashboardStateManager.getTitle(),
|
||||
dashboardStateManager.getViewMode(),
|
||||
dashboardStateManager.getIsDirty(timefilter),
|
||||
dashboardStateManager.isNew()
|
||||
),
|
||||
},
|
||||
|
|
|
@ -20,6 +20,7 @@ import { DashboardCapabilities } from '../types';
|
|||
import { EmbeddableFactory } from '../../../../embeddable/public';
|
||||
import { HelloWorldEmbeddable } from '../../../../embeddable/public/tests/fixtures';
|
||||
import { DashboardContainer } from '../embeddable';
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
|
||||
const savedDashboard = getSavedDashboardMock();
|
||||
|
||||
|
@ -32,12 +33,13 @@ const history = createBrowserHistory();
|
|||
const createDashboardState = () =>
|
||||
new DashboardStateManager({
|
||||
savedDashboard,
|
||||
kibanaVersion: '7.0.0',
|
||||
hideWriteControls: false,
|
||||
allowByValueEmbeddables: false,
|
||||
kibanaVersion: '7.0.0',
|
||||
kbnUrlStateStorage: createKbnUrlStateStorage(),
|
||||
history: createBrowserHistory(),
|
||||
kbnUrlStateStorage: createKbnUrlStateStorage(),
|
||||
hasTaggingCapabilities: mockHasTaggingCapabilities,
|
||||
toasts: coreMock.createStart().notifications.toasts,
|
||||
});
|
||||
|
||||
const defaultCapabilities: DashboardCapabilities = {
|
||||
|
@ -83,9 +85,9 @@ const setupEmbeddableFactory = () => {
|
|||
test('container is destroyed on unmount', async () => {
|
||||
const { createEmbeddable, destroySpy, embeddable } = setupEmbeddableFactory();
|
||||
|
||||
const state = createDashboardState();
|
||||
const dashboardStateManager = createDashboardState();
|
||||
const { result, unmount, waitForNextUpdate } = renderHook(
|
||||
() => useDashboardContainer(state, history, false),
|
||||
() => useDashboardContainer({ dashboardStateManager, history }),
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<KibanaContextProvider services={services}>{children}</KibanaContextProvider>
|
||||
|
@ -113,7 +115,7 @@ test('old container is destroyed on new dashboardStateManager', async () => {
|
|||
const { result, waitForNextUpdate, rerender } = renderHook<
|
||||
DashboardStateManager,
|
||||
DashboardContainer | null
|
||||
>((dashboardState) => useDashboardContainer(dashboardState, history, false), {
|
||||
>((dashboardStateManager) => useDashboardContainer({ dashboardStateManager, history }), {
|
||||
wrapper: ({ children }) => (
|
||||
<KibanaContextProvider services={services}>{children}</KibanaContextProvider>
|
||||
),
|
||||
|
@ -148,7 +150,7 @@ test('destroyed if rerendered before resolved', async () => {
|
|||
const { result, waitForNextUpdate, rerender } = renderHook<
|
||||
DashboardStateManager,
|
||||
DashboardContainer | null
|
||||
>((dashboardState) => useDashboardContainer(dashboardState, history, false), {
|
||||
>((dashboardStateManager) => useDashboardContainer({ dashboardStateManager, history }), {
|
||||
wrapper: ({ children }) => (
|
||||
<KibanaContextProvider services={services}>{children}</KibanaContextProvider>
|
||||
),
|
||||
|
|
|
@ -24,12 +24,21 @@ import { getDashboardContainerInput, getSearchSessionIdFromURL } from '../dashbo
|
|||
import { DashboardConstants, DashboardContainer, DashboardContainerInput } from '../..';
|
||||
import { DashboardAppServices } from '../types';
|
||||
import { DASHBOARD_CONTAINER_TYPE } from '..';
|
||||
import { TimefilterContract } from '../../services/data';
|
||||
|
||||
export const useDashboardContainer = (
|
||||
dashboardStateManager: DashboardStateManager | null,
|
||||
history: History,
|
||||
isEmbeddedExternally: boolean
|
||||
) => {
|
||||
export const useDashboardContainer = ({
|
||||
history,
|
||||
timeFilter,
|
||||
setUnsavedChanges,
|
||||
dashboardStateManager,
|
||||
isEmbeddedExternally,
|
||||
}: {
|
||||
history: History;
|
||||
isEmbeddedExternally?: boolean;
|
||||
timeFilter?: TimefilterContract;
|
||||
setUnsavedChanges?: (dirty: boolean) => void;
|
||||
dashboardStateManager: DashboardStateManager | null;
|
||||
}) => {
|
||||
const {
|
||||
dashboardCapabilities,
|
||||
data,
|
||||
|
@ -72,15 +81,20 @@ export const useDashboardContainer = (
|
|||
.getStateTransfer()
|
||||
.getIncomingEmbeddablePackage(DashboardConstants.DASHBOARDS_ID, true);
|
||||
|
||||
// when dashboard state manager initially loads, determine whether or not there are unsaved changes
|
||||
setUnsavedChanges?.(
|
||||
Boolean(incomingEmbeddable) || dashboardStateManager.hasUnsavedPanelState()
|
||||
);
|
||||
|
||||
let canceled = false;
|
||||
let pendingContainer: DashboardContainer | ErrorEmbeddable | null | undefined;
|
||||
(async function createContainer() {
|
||||
pendingContainer = await dashboardFactory.create(
|
||||
getDashboardContainerInput({
|
||||
isEmbeddedExternally: Boolean(isEmbeddedExternally),
|
||||
dashboardCapabilities,
|
||||
dashboardStateManager,
|
||||
incomingEmbeddable,
|
||||
isEmbeddedExternally,
|
||||
query,
|
||||
searchSessionId: searchSessionIdFromURL ?? searchSession.start(),
|
||||
})
|
||||
|
@ -141,8 +155,10 @@ export const useDashboardContainer = (
|
|||
dashboardCapabilities,
|
||||
dashboardStateManager,
|
||||
isEmbeddedExternally,
|
||||
setUnsavedChanges,
|
||||
searchSession,
|
||||
scopedHistory,
|
||||
timeFilter,
|
||||
embeddable,
|
||||
history,
|
||||
query,
|
||||
|
|
|
@ -87,6 +87,7 @@ export const useDashboardStateManager = (
|
|||
});
|
||||
|
||||
const stateManager = new DashboardStateManager({
|
||||
toasts: core.notifications.toasts,
|
||||
hasTaggingCapabilities,
|
||||
dashboardPanelStorage,
|
||||
hideWriteControls,
|
||||
|
@ -160,7 +161,6 @@ export const useDashboardStateManager = (
|
|||
const dashboardTitle = getDashboardTitle(
|
||||
stateManager.getTitle(),
|
||||
stateManager.getViewMode(),
|
||||
stateManager.getIsDirty(timefilter),
|
||||
stateManager.isNew()
|
||||
);
|
||||
|
||||
|
@ -213,6 +213,7 @@ export const useDashboardStateManager = (
|
|||
uiSettings,
|
||||
usageCollection,
|
||||
allowByValueEmbeddables,
|
||||
core.notifications.toasts,
|
||||
dashboardCapabilities.storeSearchSession,
|
||||
]);
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ import { ShowShareModal } from './show_share_modal';
|
|||
import { PanelToolbar } from './panel_toolbar';
|
||||
import { confirmDiscardOrKeepUnsavedChanges } from '../listing/confirm_overlays';
|
||||
import { OverlayRef } from '../../../../../core/public';
|
||||
import { getNewDashboardTitle } from '../../dashboard_strings';
|
||||
import { getNewDashboardTitle, unsavedChangesBadge } from '../../dashboard_strings';
|
||||
import { DASHBOARD_PANELS_UNSAVED_ID } from '../lib/dashboard_panel_storage';
|
||||
import { DashboardContainer } from '..';
|
||||
|
||||
|
@ -64,6 +64,7 @@ export interface DashboardTopNavProps {
|
|||
timefilter: TimefilterContract;
|
||||
indexPatterns: IndexPattern[];
|
||||
redirectTo: DashboardRedirect;
|
||||
unsavedChanges?: boolean;
|
||||
lastDashboardId?: string;
|
||||
viewMode: ViewMode;
|
||||
}
|
||||
|
@ -72,6 +73,7 @@ export function DashboardTopNav({
|
|||
dashboardStateManager,
|
||||
dashboardContainer,
|
||||
lastDashboardId,
|
||||
unsavedChanges,
|
||||
savedDashboard,
|
||||
onQuerySubmit,
|
||||
embedSettings,
|
||||
|
@ -467,7 +469,18 @@ export function DashboardTopNav({
|
|||
isDirty: dashboardStateManager.isDirty,
|
||||
});
|
||||
|
||||
const badges = unsavedChanges
|
||||
? [
|
||||
{
|
||||
'data-test-subj': 'dashboardUnsavedChangesBadge',
|
||||
badgeText: unsavedChangesBadge.getUnsavedChangedBadgeText(),
|
||||
color: 'secondary',
|
||||
},
|
||||
]
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
badges,
|
||||
appName: 'dashboard',
|
||||
config: showTopNavMenu ? topNav : undefined,
|
||||
className: isFullScreenMode ? 'kbnTopNavMenu-isFullScreen' : undefined,
|
||||
|
|
|
@ -12,37 +12,31 @@ import { ViewMode } from './services/embeddable';
|
|||
/**
|
||||
* @param title {string} the current title of the dashboard
|
||||
* @param viewMode {DashboardViewMode} the current mode. If in editing state, prepends 'Editing ' to the title.
|
||||
* @param isDirty {boolean} if the dashboard is in a dirty state. If in dirty state, adds (unsaved) to the
|
||||
* end of the title.
|
||||
* @returns {string} A title to display to the user based on the above parameters.
|
||||
*/
|
||||
export function getDashboardTitle(
|
||||
title: string,
|
||||
viewMode: ViewMode,
|
||||
isDirty: boolean,
|
||||
isNew: boolean
|
||||
): string {
|
||||
export function getDashboardTitle(title: string, viewMode: ViewMode, isNew: boolean): string {
|
||||
const isEditMode = viewMode === ViewMode.EDIT;
|
||||
let displayTitle: string;
|
||||
const dashboardTitle = isNew ? getNewDashboardTitle() : title;
|
||||
|
||||
if (isEditMode && isDirty) {
|
||||
displayTitle = i18n.translate('dashboard.strings.dashboardUnsavedEditTitle', {
|
||||
defaultMessage: 'Editing {title} (unsaved)',
|
||||
values: { title: dashboardTitle },
|
||||
});
|
||||
} else if (isEditMode) {
|
||||
displayTitle = i18n.translate('dashboard.strings.dashboardEditTitle', {
|
||||
defaultMessage: 'Editing {title}',
|
||||
values: { title: dashboardTitle },
|
||||
});
|
||||
} else {
|
||||
displayTitle = dashboardTitle;
|
||||
}
|
||||
|
||||
return displayTitle;
|
||||
return isEditMode
|
||||
? i18n.translate('dashboard.strings.dashboardEditTitle', {
|
||||
defaultMessage: 'Editing {title}',
|
||||
values: { title: dashboardTitle },
|
||||
})
|
||||
: dashboardTitle;
|
||||
}
|
||||
|
||||
export const unsavedChangesBadge = {
|
||||
getUnsavedChangedBadgeText: () =>
|
||||
i18n.translate('dashboard.unsavedChangesBadge', {
|
||||
defaultMessage: 'Unsaved changes',
|
||||
}),
|
||||
};
|
||||
|
||||
export const getMigratedToastText = () =>
|
||||
i18n.translate('dashboard.migratedChanges', {
|
||||
defaultMessage: 'Some panels have been successfully updated to the latest version.',
|
||||
});
|
||||
|
||||
/*
|
||||
Plugin
|
||||
*/
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
.kbnTopNavMenu {
|
||||
margin-right: $euiSizeXS;
|
||||
}
|
||||
|
||||
.kbnTopNavMenu__badgeWrapper {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.kbnTopNavMenu__badgeGroup {
|
||||
margin-right: $euiSizeM;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import React, { ReactElement } from 'react';
|
||||
import { EuiHeaderLinks } from '@elastic/eui';
|
||||
import { EuiBadge, EuiBadgeGroup, EuiBadgeProps, EuiHeaderLinks } from '@elastic/eui';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { MountPoint } from '../../../../core/public';
|
||||
|
@ -23,6 +23,7 @@ import { TopNavMenuItem } from './top_nav_menu_item';
|
|||
export type TopNavMenuProps = StatefulSearchBarProps &
|
||||
Omit<SearchBarProps, 'kibana' | 'intl' | 'timeHistory'> & {
|
||||
config?: TopNavMenuData[];
|
||||
badges?: Array<EuiBadgeProps & { badgeText: string }>;
|
||||
showSearchBar?: boolean;
|
||||
showQueryBar?: boolean;
|
||||
showQueryInput?: boolean;
|
||||
|
@ -61,12 +62,28 @@ export type TopNavMenuProps = StatefulSearchBarProps &
|
|||
**/
|
||||
|
||||
export function TopNavMenu(props: TopNavMenuProps): ReactElement | null {
|
||||
const { config, showSearchBar, ...searchBarProps } = props;
|
||||
const { config, badges, showSearchBar, ...searchBarProps } = props;
|
||||
|
||||
if ((!config || config.length === 0) && (!showSearchBar || !props.data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderBadges(): ReactElement | null {
|
||||
if (!badges || badges.length === 0) return null;
|
||||
return (
|
||||
<EuiBadgeGroup className={'kbnTopNavMenu__badgeGroup'}>
|
||||
{badges.map((badge: EuiBadgeProps & { badgeText: string }, i: number) => {
|
||||
const { badgeText, ...badgeProps } = badge;
|
||||
return (
|
||||
<EuiBadge key={`nav-menu-badge-${i}`} {...badgeProps}>
|
||||
{badgeText}
|
||||
</EuiBadge>
|
||||
);
|
||||
})}
|
||||
</EuiBadgeGroup>
|
||||
);
|
||||
}
|
||||
|
||||
function renderItems(): ReactElement[] | null {
|
||||
if (!config || config.length === 0) return null;
|
||||
return config.map((menuItem: TopNavMenuData, i: number) => {
|
||||
|
@ -98,7 +115,10 @@ export function TopNavMenu(props: TopNavMenuProps): ReactElement | null {
|
|||
return (
|
||||
<>
|
||||
<MountPointPortal setMountPoint={setMenuMountPoint}>
|
||||
<span className={wrapperClassName}>{renderMenu(menuClassName)}</span>
|
||||
<span className={`${wrapperClassName} kbnTopNavMenu__badgeWrapper`}>
|
||||
{renderBadges()}
|
||||
{renderMenu(menuClassName)}
|
||||
</span>
|
||||
</MountPointPortal>
|
||||
<span className={wrapperClassName}>{renderSearchBar()}</span>
|
||||
</>
|
||||
|
|
|
@ -115,7 +115,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await testSubjects.click('confirmCopyToButton');
|
||||
|
||||
await PageObjects.dashboard.waitForRenderComplete();
|
||||
await PageObjects.dashboard.expectOnDashboard(`Editing New Dashboard (unsaved)`);
|
||||
await PageObjects.dashboard.expectOnDashboard(`Editing New Dashboard`);
|
||||
});
|
||||
|
||||
it('it always appends new panels instead of overwriting', async () => {
|
||||
|
|
|
@ -13,6 +13,7 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const PageObjects = getPageObjects(['dashboard', 'header', 'visualize', 'settings', 'common']);
|
||||
const esArchiver = getService('esArchiver');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const dashboardAddPanel = getService('dashboardAddPanel');
|
||||
|
||||
|
@ -29,10 +30,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.common.navigateToApp('dashboard');
|
||||
await PageObjects.dashboard.preserveCrossAppState();
|
||||
await PageObjects.dashboard.loadSavedDashboard('few panels');
|
||||
await PageObjects.dashboard.switchToEditMode();
|
||||
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
originalPanelCount = await PageObjects.dashboard.getPanelCount();
|
||||
});
|
||||
|
||||
it('does not show unsaved changes badge when there are no unsaved changes', async () => {
|
||||
await testSubjects.missingOrFail('dashboardUnsavedChangesBadge');
|
||||
});
|
||||
|
||||
it('shows the unsaved changes badge after adding panels', async () => {
|
||||
await PageObjects.dashboard.switchToEditMode();
|
||||
// add an area chart by value
|
||||
await dashboardAddPanel.clickCreateNewLink();
|
||||
await PageObjects.visualize.clickAggBasedVisualizations();
|
||||
|
@ -42,6 +49,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
// add a metric by reference
|
||||
await dashboardAddPanel.addVisualization('Rendering-Test: metric');
|
||||
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await testSubjects.existOrFail('dashboardUnsavedChangesBadge');
|
||||
});
|
||||
|
||||
it('has correct number of panels', async () => {
|
||||
|
@ -73,10 +83,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
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('shows unsaved changes badge in view mode if changes have not been discarded', async () => {
|
||||
await testSubjects.existOrFail('dashboardUnsavedChangesBadge');
|
||||
});
|
||||
|
||||
it('retains unsaved panel count after returning to edit mode', async () => {
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.dashboard.switchToEditMode();
|
||||
|
@ -84,5 +99,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
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');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -646,7 +646,6 @@
|
|||
"dashboard.savedDashboard.newDashboardTitle": "新規ダッシュボード",
|
||||
"dashboard.stateManager.timeNotSavedWithDashboardErrorMessage": "このダッシュボードに時刻が保存されていないため、同期できません。",
|
||||
"dashboard.strings.dashboardEditTitle": "{title}を編集中",
|
||||
"dashboard.strings.dashboardUnsavedEditTitle": "{title}を編集中(未保存)",
|
||||
"dashboard.topNav.cloneModal.cancelButtonLabel": "キャンセル",
|
||||
"dashboard.topNav.cloneModal.cloneDashboardModalHeaderTitle": "ダッシュボードのクローンを作成",
|
||||
"dashboard.topNav.cloneModal.confirmButtonLabel": "クローンの確認",
|
||||
|
|
|
@ -646,7 +646,6 @@
|
|||
"dashboard.savedDashboard.newDashboardTitle": "新建仪表板",
|
||||
"dashboard.stateManager.timeNotSavedWithDashboardErrorMessage": "时间未随此仪表板保存,因此无法同步。",
|
||||
"dashboard.strings.dashboardEditTitle": "正在编辑 {title}",
|
||||
"dashboard.strings.dashboardUnsavedEditTitle": "正在编辑 {title}(未保存)",
|
||||
"dashboard.topNav.cloneModal.cancelButtonLabel": "取消",
|
||||
"dashboard.topNav.cloneModal.cloneDashboardModalHeaderTitle": "克隆仪表板",
|
||||
"dashboard.topNav.cloneModal.confirmButtonLabel": "确认克隆",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue