Skip to content

Commit

Permalink
[8.8] [Dashboard] Fast Navigation Between Dashboards (#157437) (#159827)
Browse files Browse the repository at this point in the history
# Backport

This will backport the following commits from `main` to `8.8`:
- [[Dashboard] Fast Navigation Between Dashboards
(#157437)](#157437)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Devon
Thomson","email":"[email protected]"},"sourceCommit":{"committedDate":"2023-05-25T18:40:48Z","message":"[Dashboard]
Fast Navigation Between Dashboards (#157437)\n\n## Summary\r\nMakes all
navigation from one Dashboard to another feel
snappier.","sha":"5342563a22a54223180a727a542a7b6c99caea7f","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement","Feature:Dashboard","Team:Presentation","loe:days","impact:medium","backport:skip","v8.8.0","v8.9.0"],"number":157437,"url":"https://github.com/elastic/kibana/pull/157437","mergeCommit":{"message":"[Dashboard]
Fast Navigation Between Dashboards (#157437)\n\n## Summary\r\nMakes all
navigation from one Dashboard to another feel
snappier.","sha":"5342563a22a54223180a727a542a7b6c99caea7f"}},"sourceBranch":"main","suggestedTargetBranches":["8.8"],"targetPullRequestStates":[{"branch":"8.8","label":"v8.8.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/157437","number":157437,"mergeCommit":{"message":"[Dashboard]
Fast Navigation Between Dashboards (#157437)\n\n## Summary\r\nMakes all
navigation from one Dashboard to another feel
snappier.","sha":"5342563a22a54223180a727a542a7b6c99caea7f"}}]}]
BACKPORT-->

Co-authored-by: Kibana Machine <[email protected]>
Co-authored-by: Patryk Kopyciński <[email protected]>
  • Loading branch information
3 people authored Jun 16, 2023
1 parent 1bdfeba commit 9e48a57
Show file tree
Hide file tree
Showing 24 changed files with 299 additions and 230 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import { EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
import { controlGroupInputBuilder } from '@kbn/controls-plugin/public';
import { getDefaultControlGroupInput } from '@kbn/controls-plugin/common';
import { FILTER_DEBUGGER_EMBEDDABLE } from '@kbn/embeddable-examples-plugin/public';
import { AwaitingDashboardAPI, DashboardRenderer } from '@kbn/dashboard-plugin/public';
import {
AwaitingDashboardAPI,
DashboardRenderer,
DashboardCreationOptions,
} from '@kbn/dashboard-plugin/public';

export const DashboardWithControlsExample = ({ dataView }: { dataView: DataView }) => {
const [dashboard, setDashboard] = useState<AwaitingDashboardAPI>();
Expand Down Expand Up @@ -48,7 +52,7 @@ export const DashboardWithControlsExample = ({ dataView }: { dataView: DataView
<EuiSpacer size="m" />
<EuiPanel hasBorder={true}>
<DashboardRenderer
getCreationOptions={async () => {
getCreationOptions={async (): Promise<DashboardCreationOptions> => {
const builder = controlGroupInputBuilder;
const controlGroupInput = getDefaultControlGroupInput();
await builder.addDataControlFromField(controlGroupInput, {
Expand All @@ -68,11 +72,11 @@ export const DashboardWithControlsExample = ({ dataView }: { dataView: DataView

return {
useControlGroupIntegration: true,
initialInput: {
getInitialInput: () => ({
timeRange: { from: 'now-30d', to: 'now' },
viewMode: ViewMode.VIEW,
controlGroupInput,
},
}),
};
}}
ref={setDashboard}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,10 @@ export const DynamicByReferenceExample = () => {
getCreationOptions={async () => {
const persistedInput = getPersistableInput();
return {
initialInput: {
getInitialInput: () => ({
...persistedInput,
timeRange: { from: 'now-30d', to: 'now' }, // need to set the time range for the by value vis
},
}),
};
}}
ref={setdashboard}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,15 @@ export const StaticByReferenceExample = ({
const field = dataView.getFieldByName('machine.os.keyword');
let filter: Filter;
let creationOptions: DashboardCreationOptions = {
initialInput: { viewMode: ViewMode.VIEW },
getInitialInput: () => ({ viewMode: ViewMode.VIEW }),
};
if (field) {
filter = buildPhraseFilter(field, 'win xp', dataView);
filter.meta.negate = true;
creationOptions = { ...creationOptions, initialInput: { filters: [filter] } };
creationOptions = {
...creationOptions,
getInitialInput: () => ({ filters: [filter] }),
};
}
return creationOptions; // if can't find the field, then just return no special creation options
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ export const StaticByValueExample = () => {
<DashboardRenderer
getCreationOptions={async () => {
return {
initialInput: {
getInitialInput: () => ({
timeRange: { from: 'now-30d', to: 'now' },
viewMode: ViewMode.VIEW,
panels: panelsJson as DashboardPanelMap,
},
}),
};
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,19 @@ export class ControlGroupContainer extends Container<
);
};

public updateInputAndReinitialize = (newInput: Partial<ControlGroupInput>) => {
this.subscriptions.unsubscribe();
this.subscriptions = new Subscription();
this.initialized$.next(false);
this.updateInput(newInput);
this.untilAllChildrenReady().then(() => {
this.recalculateDataViews();
this.recalculateFilters();
this.setupSubscriptions();
this.initialized$.next(true);
});
};

public setLastUsedDataViewId = (lastUsedDataViewId: string) => {
this.lastUsedDataViewId = lastUsedDataViewId;
};
Expand Down
48 changes: 23 additions & 25 deletions src/plugins/dashboard/public/dashboard_app/dashboard_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,6 @@ export function DashboardApp({
customBranding,
} = pluginServices.getServices();
const showPlainSpinner = useObservable(customBranding.hasCustomBranding$, false);

const incomingEmbeddable = getStateTransfer().getIncomingEmbeddablePackage(
DASHBOARD_APP_ID,
true
);
const { scopedHistory: getScopedHistory } = useDashboardMountContext();

useExecutionContext(executionContext, {
Expand Down Expand Up @@ -125,13 +120,28 @@ export function DashboardApp({
/**
* Create options to pass into the dashboard renderer
*/
const stateFromLocator = loadDashboardHistoryLocationState(getScopedHistory);
const getCreationOptions = useCallback((): Promise<DashboardCreationOptions> => {
const initialUrlState = loadAndRemoveDashboardState(kbnUrlStateStorage);
const searchSessionIdFromURL = getSearchSessionIdFromURL(history);
const getInitialInput = () => {
const stateFromLocator = loadDashboardHistoryLocationState(getScopedHistory);
const initialUrlState = loadAndRemoveDashboardState(kbnUrlStateStorage);

// Override all state with URL + Locator input
return {
// State loaded from the dashboard app URL and from the locator overrides all other dashboard state.
...initialUrlState,
...stateFromLocator,

// if print mode is active, force viewMode.PRINT
...(isScreenshotMode() && getScreenshotContext('layout') === 'print'
? { viewMode: ViewMode.PRINT }
: {}),
};
};

return Promise.resolve({
incomingEmbeddable,
return Promise.resolve<DashboardCreationOptions>({
getIncomingEmbeddable: () =>
getStateTransfer().getIncomingEmbeddablePackage(DASHBOARD_APP_ID, true),

// integrations
useControlGroupIntegration: true,
Expand All @@ -148,28 +158,16 @@ export function DashboardApp({
getSearchSessionIdFromURL: () => getSearchSessionIdFromURL(history),
removeSessionIdFromUrl: () => removeSearchSessionIdFromURL(kbnUrlStateStorage),
},

// Override all state with URL + Locator input
initialInput: {
// State loaded from the dashboard app URL and from the locator overrides all other dashboard state.
...initialUrlState,
...stateFromLocator,

// if print mode is active, force viewMode.PRINT
...(isScreenshotMode() && getScreenshotContext('layout') === 'print'
? { viewMode: ViewMode.PRINT }
: {}),
},

getInitialInput,
validateLoadedSavedObject: validateOutcome,
});
}, [
history,
validateOutcome,
stateFromLocator,
getScopedHistory,
isScreenshotMode,
getStateTransfer,
kbnUrlStateStorage,
incomingEmbeddable,
getScreenshotContext,
]);

Expand All @@ -183,7 +181,7 @@ export function DashboardApp({
dashboardAPI,
});
return () => stopWatchingAppStateInUrl();
}, [dashboardAPI, kbnUrlStateStorage]);
}, [dashboardAPI, kbnUrlStateStorage, savedDashboardId]);

return (
<div className="dshAppWrapper">
Expand Down
26 changes: 10 additions & 16 deletions src/plugins/dashboard/public/dashboard_app/dashboard_router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
import './_dashboard_app.scss';

import React from 'react';
import { History } from 'history';
import { parse, ParsedQuery } from 'query-string';
import { render, unmountComponentAtNode } from 'react-dom';
import { Switch, RouteComponentProps, HashRouter, Redirect } from 'react-router-dom';
import { Route } from '@kbn/shared-ux-router';

import { I18nProvider } from '@kbn/i18n-react';
import { ViewMode } from '@kbn/embeddable-plugin/public';
import { AppMountParameters, CoreSetup } from '@kbn/core/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { createKbnUrlStateStorage, withNotifyOnErrors } from '@kbn/kibana-utils-plugin/public';
Expand Down Expand Up @@ -56,14 +56,14 @@ export async function mountApp({ core, element, appUnMounted, mountContext }: Da
chrome: { setBadge, docTitle, setHelpExtension },
dashboardCapabilities: { showWriteControls },
documentationLinks: { dashboardDocLink },
application: { navigateToApp },
settings: { uiSettings },
data: dataStart,
notifications,
embeddable,
} = pluginServices.getServices();

let globalEmbedSettings: DashboardEmbedSettings | undefined;
let routerHistory: History;

const getUrlStateStorage = (history: RouteComponentProps['history']) =>
createKbnUrlStateStorage({
Expand All @@ -73,17 +73,17 @@ export async function mountApp({ core, element, appUnMounted, mountContext }: Da
});

const redirect = (redirectTo: RedirectToProps) => {
if (!routerHistory) return;
const historyFunction = redirectTo.useReplace ? routerHistory.replace : routerHistory.push;
let destination;
let path;
let state;
if (redirectTo.destination === 'dashboard') {
destination = redirectTo.id
? createDashboardEditUrl(redirectTo.id, redirectTo.editMode)
: CREATE_NEW_DASHBOARD_URL;
path = redirectTo.id ? createDashboardEditUrl(redirectTo.id) : CREATE_NEW_DASHBOARD_URL;
if (redirectTo.editMode) {
state = { viewMode: ViewMode.EDIT };
}
} else {
destination = createDashboardListingFilterUrl(redirectTo.filter);
path = createDashboardListingFilterUrl(redirectTo.filter);
}
historyFunction(destination);
navigateToApp(DASHBOARD_APP_ID, { path: `#/${path}`, state, replace: redirectTo.useReplace });
};

const getDashboardEmbedSettings = (
Expand All @@ -102,9 +102,6 @@ export async function mountApp({ core, element, appUnMounted, mountContext }: Da
if (routeParams.embed && !globalEmbedSettings) {
globalEmbedSettings = getDashboardEmbedSettings(routeParams);
}
if (!routerHistory) {
routerHistory = routeProps.history;
}
return (
<DashboardApp
history={routeProps.history}
Expand All @@ -120,9 +117,6 @@ export async function mountApp({ core, element, appUnMounted, mountContext }: Da
const routeParams = parse(routeProps.history.location.search);
const title = (routeParams.title as string) || undefined;
const filter = (routeParams.filter as string) || undefined;
if (!routerHistory) {
routerHistory = routeProps.history;
}
return (
<DashboardListingPage
initialFilter={filter}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ export function DashboardTopNav({ embedSettings, redirectTo }: DashboardTopNavPr
const title = dashboard.select((state) => state.explicitInput.title);

// store data views in state & subscribe to dashboard data view changes.
const [allDataViews, setAllDataViews] = useState<DataView[]>(dashboard.getAllDataViews());
const [allDataViews, setAllDataViews] = useState<DataView[]>([]);
useEffect(() => {
setAllDataViews(dashboard.getAllDataViews());
const subscription = dashboard.onDataViewsUpdate$.subscribe((dataViews) =>
setAllDataViews(dataViews)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import 'react-grid-layout/css/styles.css';

import { pick } from 'lodash';
import classNames from 'classnames';
import { useEffectOnce } from 'react-use/lib';
import React, { useState, useMemo, useCallback, useEffect } from 'react';
import { Layout, Responsive as ResponsiveReactGridLayout } from 'react-grid-layout';

Expand All @@ -31,19 +30,20 @@ export const DashboardGrid = ({ viewportWidth }: { viewportWidth: number }) => {
const viewMode = dashboard.select((state) => state.explicitInput.viewMode);
const useMargins = dashboard.select((state) => state.explicitInput.useMargins);
const expandedPanelId = dashboard.select((state) => state.componentState.expandedPanelId);
const animatePanelTransforms = dashboard.select(
(state) => state.componentState.animatePanelTransforms
);

// turn off panel transform animations for the first 500ms so that the dashboard doesn't animate on its first render.
const [animatePanelTransforms, setAnimatePanelTransforms] = useState(false);
useEffectOnce(() => {
setTimeout(() => setAnimatePanelTransforms(true), 500);
});

/**
* Track panel maximized state delayed by one tick and use it to prevent
* panel sliding animations on maximize and minimize.
*/
const [delayedIsPanelExpanded, setDelayedIsPanelMaximized] = useState(false);
useEffect(() => {
if (expandedPanelId) {
setAnimatePanelTransforms(false);
setDelayedIsPanelMaximized(true);
} else {
// delaying enabling CSS transforms to the next tick prevents a panel slide animation on minimize
setTimeout(() => setAnimatePanelTransforms(true), 0);
setTimeout(() => setDelayedIsPanelMaximized(false), 0);
}
}, [expandedPanelId]);

Expand Down Expand Up @@ -107,7 +107,7 @@ export const DashboardGrid = ({ viewportWidth }: { viewportWidth: number }) => {
'dshLayout-withoutMargins': !useMargins,
'dshLayout--viewing': viewMode === ViewMode.VIEW,
'dshLayout--editing': viewMode !== ViewMode.VIEW,
'dshLayout--noAnimation': !animatePanelTransforms || expandedPanelId,
'dshLayout--noAnimation': !animatePanelTransforms || delayedIsPanelExpanded,
'dshLayout-isMaximizedPanel': expandedPanelId !== undefined,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ export function runSaveAs(this: DashboardContainer) {
this.dispatch.setLastSavedInput(stateToSave);
});
}
if (newCopyOnSave || !lastSavedId) this.expectIdChange();
resolve(saveResult);
return saveResult;
};
Expand Down Expand Up @@ -175,10 +174,7 @@ export async function runClone(this: DashboardContainer) {
saveOptions: { saveAsCopy: true },
currentState: { ...currentState, title: newTitle },
});

this.dispatch.setTitle(newTitle);
resolve(saveResult);
this.expectIdChange();
return saveResult.id ? { id: saveResult.id } : { error: saveResult.error };
};
showCloneModal({ onClone, title: currentState.title, onClose: () => resolve(undefined) });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function startSyncingDashboardControlGroup(this: DashboardContainer) {
chainingSystem: deepEqual,
ignoreParentSettings: deepEqual,
};
this.subscriptions.add(
this.integrationSubscriptions.add(
this.controlGroup
.getInput$()
.pipe(
Expand Down Expand Up @@ -83,7 +83,7 @@ export function startSyncingDashboardControlGroup(this: DashboardContainer) {
};

// pass down any pieces of input needed to refetch or force refetch data for the controls
this.subscriptions.add(
this.integrationSubscriptions.add(
(this.getInput$() as Readonly<Observable<DashboardContainerInput>>)
.pipe(
distinctUntilChanged((a, b) =>
Expand All @@ -106,7 +106,7 @@ export function startSyncingDashboardControlGroup(this: DashboardContainer) {
);

// when control group outputs filters, force a refresh!
this.subscriptions.add(
this.integrationSubscriptions.add(
this.controlGroup
.getOutput$()
.pipe(
Expand All @@ -118,7 +118,7 @@ export function startSyncingDashboardControlGroup(this: DashboardContainer) {
.subscribe(() => this.forceRefresh(false)) // we should not reload the control group when the control group output changes - otherwise, performance is severely impacted
);

this.subscriptions.add(
this.integrationSubscriptions.add(
this.controlGroup
.getOutput$()
.pipe(
Expand All @@ -134,7 +134,7 @@ export function startSyncingDashboardControlGroup(this: DashboardContainer) {
);

// the Control Group needs to know when any dashboard children are loading in order to know when to move on to the next time slice when playing.
this.subscriptions.add(
this.integrationSubscriptions.add(
this.getAnyChildOutputChange$().subscribe(() => {
if (!this.controlGroup) {
return;
Expand Down
Loading

0 comments on commit 9e48a57

Please sign in to comment.