Skip to content

Commit

Permalink
Merge branch '7.12' into backport/7.12/pr-92667
Browse files Browse the repository at this point in the history
  • Loading branch information
kibanamachine committed Mar 2, 2021
2 parents 68fd37c + afa6da7 commit 7d03af6
Show file tree
Hide file tree
Showing 44 changed files with 479 additions and 241 deletions.
14 changes: 13 additions & 1 deletion src/plugins/dashboard/public/application/dashboard_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,26 @@ export function DashboardApp({
const [indexPatterns, setIndexPatterns] = useState<IndexPattern[]>([]);

const savedDashboard = useSavedDashboard(savedDashboardId, history);

const getIncomingEmbeddable = useCallback(
(removeAfterFetch?: boolean) => {
return embeddable
.getStateTransfer()
.getIncomingEmbeddablePackage(DashboardConstants.DASHBOARDS_ID, removeAfterFetch);
},
[embeddable]
);

const { dashboardStateManager, viewMode, setViewMode } = useDashboardStateManager(
savedDashboard,
history
history,
getIncomingEmbeddable
);
const [unsavedChanges, setUnsavedChanges] = useState(false);
const dashboardContainer = useDashboardContainer({
timeFilter: data.query.timefilter.timefilter,
dashboardStateManager,
getIncomingEmbeddable,
setUnsavedChanges,
history,
});
Expand Down
70 changes: 70 additions & 0 deletions src/plugins/dashboard/public/application/dashboard_state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ describe('DashboardState', function () {
savedDashboard,
hideWriteControls: false,
allowByValueEmbeddables: false,
hasPendingEmbeddable: () => false,
kibanaVersion: '7.0.0',
kbnUrlStateStorage: createKbnUrlStateStorage(),
history: createBrowserHistory(),
Expand Down Expand Up @@ -199,4 +200,73 @@ describe('DashboardState', function () {
expect(dashboardState.getIsDirty()).toBeFalsy();
});
});

describe('initial view mode', () => {
test('initial view mode set to view when hideWriteControls is true', () => {
const initialViewModeDashboardState = new DashboardStateManager({
savedDashboard,
hideWriteControls: true,
allowByValueEmbeddables: false,
hasPendingEmbeddable: () => false,
kibanaVersion: '7.0.0',
kbnUrlStateStorage: createKbnUrlStateStorage(),
history: createBrowserHistory(),
toasts: coreMock.createStart().notifications.toasts,
hasTaggingCapabilities: mockHasTaggingCapabilities,
});
expect(initialViewModeDashboardState.getViewMode()).toBe(ViewMode.VIEW);
});

test('initial view mode set to edit if edit mode specified in URL', () => {
const kbnUrlStateStorage = createKbnUrlStateStorage();
kbnUrlStateStorage.set('_a', { viewMode: ViewMode.EDIT });

const initialViewModeDashboardState = new DashboardStateManager({
savedDashboard,
kbnUrlStateStorage,
kibanaVersion: '7.0.0',
hideWriteControls: false,
allowByValueEmbeddables: false,
history: createBrowserHistory(),
hasPendingEmbeddable: () => false,
toasts: coreMock.createStart().notifications.toasts,
hasTaggingCapabilities: mockHasTaggingCapabilities,
});
expect(initialViewModeDashboardState.getViewMode()).toBe(ViewMode.EDIT);
});

test('initial view mode set to edit if the dashboard is new', () => {
const newDashboard = getSavedDashboardMock();
newDashboard.id = undefined;
const initialViewModeDashboardState = new DashboardStateManager({
savedDashboard: newDashboard,
kibanaVersion: '7.0.0',
hideWriteControls: false,
allowByValueEmbeddables: false,
history: createBrowserHistory(),
hasPendingEmbeddable: () => false,
kbnUrlStateStorage: createKbnUrlStateStorage(),
toasts: coreMock.createStart().notifications.toasts,
hasTaggingCapabilities: mockHasTaggingCapabilities,
});
expect(initialViewModeDashboardState.getViewMode()).toBe(ViewMode.EDIT);
});

test('initial view mode set to edit if there is a pending embeddable', () => {
const newDashboard = getSavedDashboardMock();
newDashboard.id = undefined;
const initialViewModeDashboardState = new DashboardStateManager({
savedDashboard: newDashboard,
kibanaVersion: '7.0.0',
hideWriteControls: false,
allowByValueEmbeddables: false,
history: createBrowserHistory(),
hasPendingEmbeddable: () => true,
kbnUrlStateStorage: createKbnUrlStateStorage(),
toasts: coreMock.createStart().notifications.toasts,
hasTaggingCapabilities: mockHasTaggingCapabilities,
});
expect(initialViewModeDashboardState.getViewMode()).toBe(ViewMode.EDIT);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export class DashboardStateManager {

private readonly usageCollection: UsageCollectionSetup | undefined;
public readonly hasTaggingCapabilities: SavedObjectTagDecoratorTypeGuard;
private hasPendingEmbeddable: () => boolean;

/**
*
Expand All @@ -104,13 +105,15 @@ export class DashboardStateManager {
usageCollection,
hideWriteControls,
kbnUrlStateStorage,
hasPendingEmbeddable,
dashboardPanelStorage,
hasTaggingCapabilities,
allowByValueEmbeddables,
}: {
history: History;
kibanaVersion: string;
hideWriteControls: boolean;
hasPendingEmbeddable: () => boolean;
allowByValueEmbeddables: boolean;
savedDashboard: DashboardSavedObject;
toasts: NotificationsStart['toasts'];
Expand All @@ -126,15 +129,17 @@ export class DashboardStateManager {
this.usageCollection = usageCollection;
this.hasTaggingCapabilities = hasTaggingCapabilities;
this.allowByValueEmbeddables = allowByValueEmbeddables;
this.hasPendingEmbeddable = hasPendingEmbeddable;
this.dashboardPanelStorage = dashboardPanelStorage;
this.kbnUrlStateStorage = kbnUrlStateStorage;

// get state defaults from saved dashboard, make sure it is migrated
const viewMode = this.getInitialViewMode();
this.stateDefaults = migrateAppState(
getAppStateDefaults(this.savedDashboard, this.hideWriteControls, this.hasTaggingCapabilities),
getAppStateDefaults(viewMode, this.savedDashboard, this.hasTaggingCapabilities),
kibanaVersion,
usageCollection
);
this.dashboardPanelStorage = dashboardPanelStorage;
this.kbnUrlStateStorage = kbnUrlStateStorage;

// setup initial state by merging defaults with state from url & panels storage
// also run migration, as state in url could be of older version
Expand Down Expand Up @@ -357,8 +362,9 @@ export class DashboardStateManager {
// The right way to fix this might be to ensure the defaults object stored on state is a deep
// clone, but given how much code uses the state object, I determined that to be too risky of a change for
// now. TODO: revisit this!
const currentViewMode = this.stateContainer.get().viewMode;
this.stateDefaults = migrateAppState(
getAppStateDefaults(this.savedDashboard, this.hideWriteControls, this.hasTaggingCapabilities),
getAppStateDefaults(currentViewMode, this.savedDashboard, this.hasTaggingCapabilities),
this.kibanaVersion,
this.usageCollection
);
Expand All @@ -369,8 +375,7 @@ export class DashboardStateManager {
this.stateDefaults.filters = [...this.getLastSavedFilterBars()];
this.isDirty = false;

const currentViewMode = this.stateContainer.get().viewMode;
this.stateContainer.set({ ...this.stateDefaults, viewMode: currentViewMode });
this.stateContainer.set(this.stateDefaults);
}

/**
Expand Down Expand Up @@ -534,9 +539,7 @@ export class DashboardStateManager {
return this.appState.viewMode;
}
// get viewMode should work properly even before the state container is created
return this.savedDashboard.id
? this.kbnUrlStateStorage.get<DashboardAppState>(STATE_STORAGE_KEY)?.viewMode ?? ViewMode.VIEW
: ViewMode.EDIT;
return this.getInitialViewMode();
}

public getIsViewMode() {
Expand Down Expand Up @@ -668,6 +671,7 @@ export class DashboardStateManager {

public switchViewMode(newMode: ViewMode) {
this.stateContainer.transitions.set('viewMode', newMode);
this.restorePanels();
}

/**
Expand All @@ -692,6 +696,7 @@ export class DashboardStateManager {
...this.stateDefaults,
...unsavedState,
...this.kbnUrlStateStorage.get<DashboardAppState>(STATE_STORAGE_KEY),
viewMode: this.getViewMode(),
},
this.kibanaVersion,
this.usageCollection
Expand Down Expand Up @@ -739,6 +744,18 @@ export class DashboardStateManager {
return stateWithoutPanels;
}

private getInitialViewMode() {
if (this.hideWriteControls) {
return ViewMode.VIEW;
}
const viewModeFromUrl = this.kbnUrlStateStorage.get<DashboardAppState>(STATE_STORAGE_KEY)
?.viewMode;
if (viewModeFromUrl) {
return viewModeFromUrl;
}
return !this.savedDashboard.id || this.hasPendingEmbeddable() ? ViewMode.EDIT : ViewMode.VIEW;
}

private checkIsDirty() {
// Filters need to be compared manually because they sometimes have a $$hashkey stored on the object.
// Query needs to be compared manually because saved legacy queries get migrated in app state automatically
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const createDashboardState = () =>
hideWriteControls: false,
allowByValueEmbeddables: false,
history: createBrowserHistory(),
hasPendingEmbeddable: () => false,
kbnUrlStateStorage: createKbnUrlStateStorage(),
hasTaggingCapabilities: mockHasTaggingCapabilities,
toasts: coreMock.createStart().notifications.toasts,
Expand All @@ -53,6 +54,8 @@ const defaultCapabilities: DashboardCapabilities = {
storeSearchSession: true,
};

const getIncomingEmbeddable = () => undefined;

const services = {
dashboardCapabilities: defaultCapabilities,
data: dataPluginMock.createStartContract(),
Expand Down Expand Up @@ -87,7 +90,12 @@ test('container is destroyed on unmount', async () => {

const dashboardStateManager = createDashboardState();
const { result, unmount, waitForNextUpdate } = renderHook(
() => useDashboardContainer({ dashboardStateManager, history }),
() =>
useDashboardContainer({
getIncomingEmbeddable,
dashboardStateManager,
history,
}),
{
wrapper: ({ children }) => (
<KibanaContextProvider services={services}>{children}</KibanaContextProvider>
Expand Down Expand Up @@ -115,12 +123,20 @@ test('old container is destroyed on new dashboardStateManager', async () => {
const { result, waitForNextUpdate, rerender } = renderHook<
DashboardStateManager,
DashboardContainer | null
>((dashboardStateManager) => useDashboardContainer({ dashboardStateManager, history }), {
wrapper: ({ children }) => (
<KibanaContextProvider services={services}>{children}</KibanaContextProvider>
),
initialProps: createDashboardState(),
});
>(
(dashboardStateManager) =>
useDashboardContainer({
getIncomingEmbeddable,
dashboardStateManager,
history,
}),
{
wrapper: ({ children }) => (
<KibanaContextProvider services={services}>{children}</KibanaContextProvider>
),
initialProps: createDashboardState(),
}
);

expect(result.current).toBeNull(); // null on initial render

Expand Down Expand Up @@ -150,12 +166,20 @@ test('destroyed if rerendered before resolved', async () => {
const { result, waitForNextUpdate, rerender } = renderHook<
DashboardStateManager,
DashboardContainer | null
>((dashboardStateManager) => useDashboardContainer({ dashboardStateManager, history }), {
wrapper: ({ children }) => (
<KibanaContextProvider services={services}>{children}</KibanaContextProvider>
),
initialProps: createDashboardState(),
});
>(
(dashboardStateManager) =>
useDashboardContainer({
getIncomingEmbeddable,
dashboardStateManager,
history,
}),
{
wrapper: ({ children }) => (
<KibanaContextProvider services={services}>{children}</KibanaContextProvider>
),
initialProps: createDashboardState(),
}
);

expect(result.current).toBeNull(); // null on initial render

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ import {
ContainerOutput,
EmbeddableFactoryNotFoundError,
EmbeddableInput,
EmbeddablePackageState,
ErrorEmbeddable,
isErrorEmbeddable,
ViewMode,
} from '../../services/embeddable';

import { DashboardStateManager } from '../dashboard_state_manager';
import { getDashboardContainerInput, getSearchSessionIdFromURL } from '../dashboard_app_functions';
import { DashboardConstants, DashboardContainer, DashboardContainerInput } from '../..';
import { DashboardContainer, DashboardContainerInput } from '../..';
import { DashboardAppServices } from '../types';
import { DASHBOARD_CONTAINER_TYPE } from '..';
import { TimefilterContract } from '../../services/data';
Expand All @@ -30,6 +31,7 @@ export const useDashboardContainer = ({
history,
timeFilter,
setUnsavedChanges,
getIncomingEmbeddable,
dashboardStateManager,
isEmbeddedExternally,
}: {
Expand All @@ -38,6 +40,7 @@ export const useDashboardContainer = ({
timeFilter?: TimefilterContract;
setUnsavedChanges?: (dirty: boolean) => void;
dashboardStateManager: DashboardStateManager | null;
getIncomingEmbeddable: (removeAfterFetch?: boolean) => EmbeddablePackageState | undefined;
}) => {
const {
dashboardCapabilities,
Expand Down Expand Up @@ -77,11 +80,8 @@ export const useDashboardContainer = ({
searchSession.restore(searchSessionIdFromURL);
}

const incomingEmbeddable = embeddable
.getStateTransfer()
.getIncomingEmbeddablePackage(DashboardConstants.DASHBOARDS_ID, true);

// when dashboard state manager initially loads, determine whether or not there are unsaved changes
const incomingEmbeddable = getIncomingEmbeddable(true);
setUnsavedChanges?.(
Boolean(incomingEmbeddable) || dashboardStateManager.hasUnsavedPanelState()
);
Expand Down Expand Up @@ -131,7 +131,6 @@ export const useDashboardContainer = ({
(incomingEmbeddable.embeddableId &&
!pendingContainer.getInput().panels[incomingEmbeddable.embeddableId]))
) {
dashboardStateManager.switchViewMode(ViewMode.EDIT);
pendingContainer.addNewEmbeddable<EmbeddableInput>(
incomingEmbeddable.type,
incomingEmbeddable.input
Expand All @@ -154,6 +153,7 @@ export const useDashboardContainer = ({
}, [
dashboardCapabilities,
dashboardStateManager,
getIncomingEmbeddable,
isEmbeddedExternally,
setUnsavedChanges,
searchSession,
Expand Down
Loading

0 comments on commit 7d03af6

Please sign in to comment.