diff --git a/src/plugins/dashboard/public/dashboard_api/types.ts b/src/plugins/dashboard/public/dashboard_api/types.ts index 4760d2fd953015..01d12c27ce4437 100644 --- a/src/plugins/dashboard/public/dashboard_api/types.ts +++ b/src/plugins/dashboard/public/dashboard_api/types.ts @@ -9,13 +9,17 @@ import { CanExpandPanels, + HasRuntimeChildState, + HasSerializedChildState, PresentationContainer, + SerializedPanelState, TracksOverlays, } from '@kbn/presentation-containers'; import { HasAppContext, HasType, PublishesDataViews, + PublishesPanelDescription, PublishesPanelTitle, PublishesSavedObjectId, PublishesUnifiedSearch, @@ -23,41 +27,64 @@ import { PublishingSubject, ViewMode, } from '@kbn/presentation-publishing'; -import { ControlGroupApi } from '@kbn/controls-plugin/public'; +import { ControlGroupApi, ControlGroupSerializedState } from '@kbn/controls-plugin/public'; import { Filter, Query, TimeRange } from '@kbn/es-query'; +import { DefaultEmbeddableApi, ErrorEmbeddable, IEmbeddable } from '@kbn/embeddable-plugin/public'; import { DashboardPanelMap, DashboardPanelState } from '../../common'; import { SaveDashboardReturn } from '../services/dashboard_content_management/types'; +import { DashboardStateFromSettingsFlyout, UnsavedPanelState } from '../dashboard_container/types'; export type DashboardApi = CanExpandPanels & HasAppContext & + HasRuntimeChildState & + HasSerializedChildState & HasType<'dashboard'> & PresentationContainer & PublishesDataViews & + PublishesPanelDescription & Pick & PublishesSavedObjectId & PublishesUnifiedSearch & PublishesViewMode & TracksOverlays & { addFromLibrary: () => void; + animatePanelTransforms$: PublishingSubject; asyncResetToLastSavedState: () => Promise; controlGroupApi$: PublishingSubject; + embeddedExternally$: PublishingSubject; fullScreenMode$: PublishingSubject; focusedPanelId$: PublishingSubject; forceRefresh: () => void; + getRuntimeStateForControlGroup: () => UnsavedPanelState | undefined; + getSerializedStateForControlGroup: () => SerializedPanelState; + getSettings: () => DashboardStateFromSettingsFlyout; getDashboardPanelFromId: (id: string) => Promise; - getPanelsState: () => DashboardPanelMap; hasOverlays$: PublishingSubject; hasRunMigrations$: PublishingSubject; hasUnsavedChanges$: PublishingSubject; + highlightPanel: (panelRef: HTMLDivElement) => void; + highlightPanelId$: PublishingSubject; managed$: PublishingSubject; + panels$: PublishingSubject; + registerChildApi: (api: DefaultEmbeddableApi) => void; runInteractiveSave: (interactionMode: ViewMode) => Promise; runQuickSave: () => Promise; + scrollToPanel: (panelRef: HTMLDivElement) => void; + scrollToPanelId$: PublishingSubject; scrollToTop: () => void; + setControlGroupApi: (controlGroupApi: ControlGroupApi) => void; + setSettings: (settings: DashboardStateFromSettingsFlyout) => void; setFilters: (filters?: Filter[] | undefined) => void; setFullScreenMode: (fullScreenMode: boolean) => void; + setPanels: (panels: DashboardPanelMap) => void; setQuery: (query?: Query | undefined) => void; setTags: (tags: string[]) => void; setTimeRange: (timeRange?: TimeRange | undefined) => void; setViewMode: (viewMode: ViewMode) => void; - openSettingsFlyout: () => void; + useMargins$: PublishingSubject; + // TODO replace with HasUniqueId once dashboard is refactored and navigateToDashboard is removed + uuid$: PublishingSubject; + + // TODO remove types below this line - from legacy embeddable system + untilEmbeddableLoaded: (id: string) => Promise; }; diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx index 57bf640a59e20e..41ddee24a93300 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/use_dashboard_menu_items.tsx @@ -23,6 +23,7 @@ import { CHANGE_CHECK_DEBOUNCE } from '../../dashboard_constants'; import { confirmDiscardUnsavedChanges } from '../../dashboard_listing/confirm_overlays'; import { SaveDashboardReturn } from '../../services/dashboard_content_management/types'; import { useDashboardApi } from '../../dashboard_api/use_dashboard_api'; +import { openSettingsFlyout } from '../../dashboard_container/embeddable/api'; export const useDashboardMenuItems = ({ isLabsShown, @@ -84,7 +85,7 @@ export const useDashboardMenuItems = ({ anchorElement, savedObjectId: lastSavedId, isDirty: Boolean(hasUnsavedChanges), - getPanelsState: dashboardApi.getPanelsState, + getPanelsState: () => dashboardApi.panels$.value, }); }, [dashboardTitle, hasUnsavedChanges, lastSavedId, dashboardApi] @@ -227,7 +228,7 @@ export const useDashboardMenuItems = ({ id: 'settings', testId: 'dashboardSettingsButton', disableButton: disableTopNav, - run: () => dashboardApi.openSettingsFlyout(), + run: () => openSettingsFlyout(dashboardApi), }, }; }, [ diff --git a/src/plugins/dashboard/public/dashboard_container/component/empty_screen/dashboard_empty_screen.test.tsx b/src/plugins/dashboard/public/dashboard_container/component/empty_screen/dashboard_empty_screen.test.tsx index 3ba77bd29d768d..4204953e226962 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/empty_screen/dashboard_empty_screen.test.tsx +++ b/src/plugins/dashboard/public/dashboard_container/component/empty_screen/dashboard_empty_screen.test.tsx @@ -14,7 +14,8 @@ import { findTestSubject } from '@elastic/eui/lib/test'; import { buildMockDashboard } from '../../../mocks'; import { DashboardEmptyScreen } from './dashboard_empty_screen'; import { pluginServices } from '../../../services/plugin_services'; -import { DashboardContainerContext } from '../../embeddable/dashboard_container'; +import { DashboardContext } from '../../../dashboard_api/use_dashboard_api'; +import { DashboardApi } from '../../../dashboard_api/types'; import { ViewMode } from '@kbn/embeddable-plugin/public'; pluginServices.getServices().visualizations.getAliases = jest @@ -23,11 +24,11 @@ pluginServices.getServices().visualizations.getAliases = jest describe('DashboardEmptyScreen', () => { function mountComponent(viewMode: ViewMode) { - const dashboardContainer = buildMockDashboard({ overrides: { viewMode } }); + const dashboardApi = buildMockDashboard({ overrides: { viewMode } }) as DashboardApi; return mountWithIntl( - + - + ); } diff --git a/src/plugins/dashboard/public/dashboard_container/component/empty_screen/dashboard_empty_screen.tsx b/src/plugins/dashboard/public/dashboard_container/component/empty_screen/dashboard_empty_screen.tsx index 60dba3f3f62662..e619c1911d365a 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/empty_screen/dashboard_empty_screen.tsx +++ b/src/plugins/dashboard/public/dashboard_container/component/empty_screen/dashboard_empty_screen.tsx @@ -22,9 +22,10 @@ import { import { METRIC_TYPE } from '@kbn/analytics'; import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { useStateFromPublishingSubject } from '@kbn/presentation-publishing'; import { DASHBOARD_UI_METRIC_ID } from '../../../dashboard_constants'; import { pluginServices } from '../../../services/plugin_services'; -import { useDashboardContainer } from '../../embeddable/dashboard_container'; +import { useDashboardApi } from '../../../dashboard_api/use_dashboard_api'; import { emptyScreenStrings } from '../../_dashboard_container_strings'; export function DashboardEmptyScreen() { @@ -45,13 +46,19 @@ export function DashboardEmptyScreen() { [getVisTypeAliases] ); - const dashboardContainer = useDashboardContainer(); + const dashboardApi = useDashboardApi(); const isDarkTheme = useObservable(theme$)?.darkMode; - const isEditMode = - dashboardContainer.select((state) => state.explicitInput.viewMode) === ViewMode.EDIT; - const embeddableAppContext = dashboardContainer.getAppContext(); - const originatingPath = embeddableAppContext?.getCurrentPath?.() ?? ''; - const originatingApp = embeddableAppContext?.currentAppId; + const viewMode = useStateFromPublishingSubject(dashboardApi.viewMode); + const isEditMode = useMemo(() => { + return viewMode === 'edit'; + }, [viewMode]); + const { originatingPath, originatingApp } = useMemo(() => { + const appContext = dashboardApi.getAppContext(); + return { + originatingApp: appContext?.currentAppId, + originatingPath: appContext?.getCurrentPath?.() ?? '', + }; + }, [dashboardApi]); const goToLens = useCallback(() => { if (!lensAlias || !lensAlias.alias) return; @@ -128,7 +135,7 @@ export function DashboardEmptyScreen() { dashboardContainer.addFromLibrary()} + onClick={() => dashboardApi.addFromLibrary()} > {emptyScreenStrings.getAddFromLibraryButtonTitle()} @@ -138,10 +145,7 @@ export function DashboardEmptyScreen() { } if (showWriteControls) { return ( - dashboardContainer.dispatch.setViewMode(ViewMode.EDIT)} - > + dashboardApi.setViewMode(ViewMode.EDIT)}> {emptyScreenStrings.getEditLinkTitle()} ); diff --git a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.test.tsx b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.test.tsx index dc1d7f4671ce16..b91e972dffa887 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.test.tsx +++ b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.test.tsx @@ -15,7 +15,9 @@ import { CONTACT_CARD_EMBEDDABLE } from '@kbn/embeddable-plugin/public/lib/test_ import { DashboardGrid } from './dashboard_grid'; import { buildMockDashboard } from '../../../mocks'; import type { Props as DashboardGridItemProps } from './dashboard_grid_item'; -import { DashboardContainerContext } from '../../embeddable/dashboard_container'; +import { DashboardContext } from '../../../dashboard_api/use_dashboard_api'; +import { DashboardApi } from '../../../dashboard_api/types'; +import { DashboardPanelMap } from '../../../../common'; jest.mock('./dashboard_grid_item', () => { return { @@ -45,59 +47,62 @@ jest.mock('./dashboard_grid_item', () => { }; }); -const createAndMountDashboardGrid = async () => { +const PANELS = { + '1': { + gridData: { x: 0, y: 0, w: 6, h: 6, i: '1' }, + type: CONTACT_CARD_EMBEDDABLE, + explicitInput: { id: '1' }, + }, + '2': { + gridData: { x: 6, y: 6, w: 6, h: 6, i: '2' }, + type: CONTACT_CARD_EMBEDDABLE, + explicitInput: { id: '2' }, + }, +}; + +const createAndMountDashboardGrid = async (panels: DashboardPanelMap = PANELS) => { const dashboardContainer = buildMockDashboard({ overrides: { - panels: { - '1': { - gridData: { x: 0, y: 0, w: 6, h: 6, i: '1' }, - type: CONTACT_CARD_EMBEDDABLE, - explicitInput: { id: '1' }, - }, - '2': { - gridData: { x: 6, y: 6, w: 6, h: 6, i: '2' }, - type: CONTACT_CARD_EMBEDDABLE, - explicitInput: { id: '2' }, - }, - }, + panels, }, }); await dashboardContainer.untilContainerInitialized(); const component = mountWithIntl( - + - + ); - return { dashboardContainer, component }; + return { dashboardApi: dashboardContainer, component }; }; test('renders DashboardGrid', async () => { - const { component } = await createAndMountDashboardGrid(); + const { component } = await createAndMountDashboardGrid(PANELS); const panelElements = component.find('GridItem'); expect(panelElements.length).toBe(2); }); test('renders DashboardGrid with no visualizations', async () => { - const { dashboardContainer, component } = await createAndMountDashboardGrid(); - dashboardContainer.updateInput({ panels: {} }); - component.update(); + const { component } = await createAndMountDashboardGrid({}); expect(component.find('GridItem').length).toBe(0); }); test('DashboardGrid removes panel when removed from container', async () => { - const { dashboardContainer, component } = await createAndMountDashboardGrid(); - const originalPanels = dashboardContainer.getInput().panels; - const filteredPanels = { ...originalPanels }; - delete filteredPanels['1']; - dashboardContainer.updateInput({ panels: filteredPanels }); + const { dashboardApi, component } = await createAndMountDashboardGrid(PANELS); + expect(component.find('GridItem').length).toBe(2); + + dashboardApi.setPanels({ + '2': PANELS['2'], + }); + await new Promise((resolve) => setTimeout(resolve, 1)); component.update(); - const panelElements = component.find('GridItem'); - expect(panelElements.length).toBe(1); + + expect(component.find('GridItem').length).toBe(1); }); test('DashboardGrid renders expanded panel', async () => { - const { dashboardContainer, component } = await createAndMountDashboardGrid(); - dashboardContainer.setExpandedPanelId('1'); + const { dashboardApi, component } = await createAndMountDashboardGrid(); + dashboardApi.setExpandedPanelId('1'); + await new Promise((resolve) => setTimeout(resolve, 1)); component.update(); // Both panels should still exist in the dom, so nothing needs to be re-fetched once minimized. expect(component.find('GridItem').length).toBe(2); @@ -105,7 +110,8 @@ test('DashboardGrid renders expanded panel', async () => { expect(component.find('#mockDashboardGridItem_1').hasClass('expandedPanel')).toBe(true); expect(component.find('#mockDashboardGridItem_2').hasClass('hiddenPanel')).toBe(true); - dashboardContainer.setExpandedPanelId(); + dashboardApi.setExpandedPanelId(); + await new Promise((resolve) => setTimeout(resolve, 1)); component.update(); expect(component.find('GridItem').length).toBe(2); @@ -114,8 +120,9 @@ test('DashboardGrid renders expanded panel', async () => { }); test('DashboardGrid renders focused panel', async () => { - const { dashboardContainer, component } = await createAndMountDashboardGrid(); - dashboardContainer.setFocusedPanelId('2'); + const { dashboardApi, component } = await createAndMountDashboardGrid(); + dashboardApi.setFocusedPanelId('2'); + await new Promise((resolve) => setTimeout(resolve, 1)); component.update(); // Both panels should still exist in the dom, so nothing needs to be re-fetched once minimized. expect(component.find('GridItem').length).toBe(2); @@ -123,7 +130,8 @@ test('DashboardGrid renders focused panel', async () => { expect(component.find('#mockDashboardGridItem_1').hasClass('blurredPanel')).toBe(true); expect(component.find('#mockDashboardGridItem_2').hasClass('focusedPanel')).toBe(true); - dashboardContainer.setFocusedPanelId(undefined); + dashboardApi.setFocusedPanelId(undefined); + await new Promise((resolve) => setTimeout(resolve, 1)); component.update(); expect(component.find('GridItem').length).toBe(2); diff --git a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.tsx b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.tsx index d10f614cc191e4..577661b393c67b 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.tsx +++ b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid.tsx @@ -17,23 +17,26 @@ import { Layout, Responsive as ResponsiveReactGridLayout } from 'react-grid-layo import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; import { DashboardPanelState } from '../../../../common'; import { DashboardGridItem } from './dashboard_grid_item'; import { useDashboardGridSettings } from './use_dashboard_grid_settings'; -import { useDashboardContainer } from '../../embeddable/dashboard_container'; +import { useDashboardApi } from '../../../dashboard_api/use_dashboard_api'; import { getPanelLayoutsAreEqual } from '../../state/diffing/dashboard_diffing_utils'; import { DASHBOARD_GRID_HEIGHT, DASHBOARD_MARGIN_SIZE } from '../../../dashboard_constants'; export const DashboardGrid = ({ viewportWidth }: { viewportWidth: number }) => { - const dashboard = useDashboardContainer(); - const panels = dashboard.select((state) => state.explicitInput.panels); - 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 focusedPanelId = dashboard.select((state) => state.componentState.focusedPanelId); - const animatePanelTransforms = dashboard.select( - (state) => state.componentState.animatePanelTransforms - ); + const dashboardApi = useDashboardApi(); + + const [animatePanelTransforms, expandedPanelId, focusedPanelId, panels, useMargins, viewMode] = + useBatchedPublishingSubjects( + dashboardApi.animatePanelTransforms$, + dashboardApi.expandedPanelId, + dashboardApi.focusedPanelId$, + dashboardApi.panels$, + dashboardApi.useMargins$, + dashboardApi.viewMode + ); /** * Track panel maximized state delayed by one tick and use it to prevent @@ -96,10 +99,10 @@ export const DashboardGrid = ({ viewportWidth }: { viewportWidth: number }) => { {} as { [key: string]: DashboardPanelState } ); if (!getPanelLayoutsAreEqual(panels, updatedPanels)) { - dashboard.dispatch.setPanels(updatedPanels); + dashboardApi.setPanels(updatedPanels); } }, - [dashboard, panels, viewMode] + [dashboardApi, panels, viewMode] ); const classes = classNames({ @@ -110,7 +113,7 @@ export const DashboardGrid = ({ viewportWidth }: { viewportWidth: number }) => { 'dshLayout-isMaximizedPanel': expandedPanelId !== undefined, }); - const { layouts, breakpoints, columns } = useDashboardGridSettings(panelsInOrder); + const { layouts, breakpoints, columns } = useDashboardGridSettings(panelsInOrder, panels); // in print mode, dashboard layout is not controlled by React Grid Layout if (viewMode === ViewMode.PRINT) { diff --git a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.test.tsx b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.test.tsx index 6f8c1452c8045b..416448f6d8132c 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.test.tsx +++ b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.test.tsx @@ -14,7 +14,8 @@ import { CONTACT_CARD_EMBEDDABLE } from '@kbn/embeddable-plugin/public/lib/test_ import { buildMockDashboard } from '../../../mocks'; import { Item, Props as DashboardGridItemProps } from './dashboard_grid_item'; -import { DashboardContainerContext } from '../../embeddable/dashboard_container'; +import { DashboardContext } from '../../../dashboard_api/use_dashboard_api'; +import { DashboardApi } from '../../../dashboard_api/types'; jest.mock('@kbn/embeddable-plugin/public', () => { const original = jest.requireActual('@kbn/embeddable-plugin/public'); @@ -44,14 +45,14 @@ const createAndMountDashboardGridItem = (props: DashboardGridItemProps) => { explicitInput: { id: '2' }, }, }; - const dashboardContainer = buildMockDashboard({ overrides: { panels } }); + const dashboardApi = buildMockDashboard({ overrides: { panels } }) as DashboardApi; const component = mountWithIntl( - + - + ); - return { dashboardContainer, component }; + return { dashboardApi, component }; }; test('renders Item', async () => { diff --git a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx index fdc0487f172654..0a116eae997d42 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx +++ b/src/plugins/dashboard/public/dashboard_container/component/grid/dashboard_grid_item.tsx @@ -9,12 +9,13 @@ import { EuiLoadingChart } from '@elastic/eui'; import { css } from '@emotion/react'; -import { EmbeddablePanel, ReactEmbeddableRenderer, ViewMode } from '@kbn/embeddable-plugin/public'; +import { EmbeddablePanel, ReactEmbeddableRenderer } from '@kbn/embeddable-plugin/public'; import classNames from 'classnames'; import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; import { DashboardPanelState } from '../../../../common'; import { pluginServices } from '../../../services/plugin_services'; -import { useDashboardContainer } from '../../embeddable/dashboard_container'; +import { useDashboardApi } from '../../../dashboard_api/use_dashboard_api'; type DivProps = Pick, 'className' | 'style' | 'children'>; @@ -45,10 +46,13 @@ export const Item = React.forwardRef( }, ref ) => { - const container = useDashboardContainer(); - const scrollToPanelId = container.select((state) => state.componentState.scrollToPanelId); - const highlightPanelId = container.select((state) => state.componentState.highlightPanelId); - const useMargins = container.select((state) => state.explicitInput.useMargins); + const dashboardApi = useDashboardApi(); + const [highlightPanelId, scrollToPanelId, useMargins, viewMode] = useBatchedPublishingSubjects( + dashboardApi.highlightPanelId$, + dashboardApi.scrollToPanelId$, + dashboardApi.useMargins$, + dashboardApi.viewMode + ); const expandPanel = expandedPanelId !== undefined && expandedPanelId === id; const hidePanel = expandedPanelId !== undefined && expandedPanelId !== id; @@ -60,17 +64,17 @@ export const Item = React.forwardRef( 'dshDashboardGrid__item--focused': focusPanel, 'dshDashboardGrid__item--blurred': blurPanel, // eslint-disable-next-line @typescript-eslint/naming-convention - printViewport__vis: container.getInput().viewMode === ViewMode.PRINT, + printViewport__vis: viewMode === 'print', }); useLayoutEffect(() => { if (typeof ref !== 'function' && ref?.current) { const panelRef = ref.current; if (scrollToPanelId === id) { - container.scrollToPanel(panelRef); + dashboardApi.scrollToPanel(panelRef); } if (highlightPanelId === id) { - container.highlightPanel(panelRef); + dashboardApi.highlightPanel(panelRef); } panelRef.querySelectorAll('*').forEach((e) => { @@ -83,7 +87,7 @@ export const Item = React.forwardRef( } }); } - }, [id, container, scrollToPanelId, highlightPanelId, ref, blurPanel]); + }, [id, dashboardApi, scrollToPanelId, highlightPanelId, ref, blurPanel]); const focusStyles = blurPanel ? css` @@ -110,10 +114,10 @@ export const Item = React.forwardRef( container} + getParentApi={() => dashboardApi} key={`${type}_${id}`} panelProps={panelProps} - onApiAvailable={(api) => container.registerChildApi(api)} + onApiAvailable={(api) => dashboardApi.registerChildApi(api)} /> ); } @@ -122,11 +126,11 @@ export const Item = React.forwardRef( container.untilEmbeddableLoaded(id)} + embeddable={() => dashboardApi.untilEmbeddableLoaded(id)} {...panelProps} /> ); - }, [id, container, type, index, useMargins]); + }, [id, dashboardApi, type, index, useMargins]); return (
((props, const { settings: { isProjectEnabledInLabs }, } = pluginServices.getServices(); - const container = useDashboardContainer(); - const focusedPanelId = container.select((state) => state.componentState.focusedPanelId); - - const dashboard = useDashboardContainer(); + const dashboardApi = useDashboardApi(); + const [focusedPanelId, viewMode] = useBatchedPublishingSubjects( + dashboardApi.focusedPanelId$, + dashboardApi.viewMode + ); - const isPrintMode = dashboard.select((state) => state.explicitInput.viewMode) === ViewMode.PRINT; const isEnabled = - !isPrintMode && + viewMode !== 'print' && isProjectEnabledInLabs('labs:dashboard:deferBelowFold') && (!focusedPanelId || focusedPanelId === props.id); diff --git a/src/plugins/dashboard/public/dashboard_container/component/grid/use_dashboard_grid_settings.tsx b/src/plugins/dashboard/public/dashboard_container/component/grid/use_dashboard_grid_settings.tsx index e31718654119fa..155d7022141dbe 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/grid/use_dashboard_grid_settings.tsx +++ b/src/plugins/dashboard/public/dashboard_container/component/grid/use_dashboard_grid_settings.tsx @@ -12,15 +12,16 @@ import { useMemo } from 'react'; import { useEuiTheme } from '@elastic/eui'; import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { useStateFromPublishingSubject } from '@kbn/presentation-publishing'; +import { DashboardPanelMap } from '../../../../common'; import { DASHBOARD_GRID_COLUMN_COUNT } from '../../../dashboard_constants'; -import { useDashboardContainer } from '../../embeddable/dashboard_container'; +import { useDashboardApi } from '../../../dashboard_api/use_dashboard_api'; -export const useDashboardGridSettings = (panelsInOrder: string[]) => { - const dashboard = useDashboardContainer(); +export const useDashboardGridSettings = (panelsInOrder: string[], panels: DashboardPanelMap) => { + const dashboardApi = useDashboardApi(); const { euiTheme } = useEuiTheme(); - const panels = dashboard.select((state) => state.explicitInput.panels); - const viewMode = dashboard.select((state) => state.explicitInput.viewMode); + const viewMode = useStateFromPublishingSubject(dashboardApi.viewMode); const layouts = useMemo(() => { return { diff --git a/src/plugins/dashboard/public/dashboard_container/component/settings/settings_flyout.tsx b/src/plugins/dashboard/public/dashboard_container/component/settings/settings_flyout.tsx index 635f0ac5f43098..d4160d70029497 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/settings/settings_flyout.tsx +++ b/src/plugins/dashboard/public/dashboard_container/component/settings/settings_flyout.tsx @@ -31,7 +31,7 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { DashboardContainerInput } from '../../../../common'; import { pluginServices } from '../../../services/plugin_services'; -import { useDashboardContainer } from '../../embeddable/dashboard_container'; +import { useDashboardApi } from '../../../dashboard_api/use_dashboard_api'; interface DashboardSettingsProps { onClose: () => void; @@ -45,19 +45,14 @@ export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => { dashboardContentManagement: { checkForDuplicateDashboardTitle }, } = pluginServices.getServices(); - const dashboard = useDashboardContainer(); + const dashboardApi = useDashboardApi(); - const [dashboardSettingsState, setDashboardSettingsState] = useState({ - ...dashboard.getInput(), - }); + const [localSettings, setLocalSettings] = useState(dashboardApi.getSettings()); const [isTitleDuplicate, setIsTitleDuplicate] = useState(false); const [isTitleDuplicateConfirmed, setIsTitleDuplicateConfirmed] = useState(false); const [isApplying, setIsApplying] = useState(false); - const lastSavedId = dashboard.select((state) => state.componentState.lastSavedId); - const lastSavedTitle = dashboard.select((state) => state.explicitInput.title); - const isMounted = useMountedState(); const onTitleDuplicate = () => { @@ -69,9 +64,9 @@ export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => { const onApply = async () => { setIsApplying(true); const validTitle = await checkForDuplicateDashboardTitle({ - title: dashboardSettingsState.title, + title: localSettings.title, copyOnSave: false, - lastSavedTitle, + lastSavedTitle: dashboardApi.panelTitle.value ?? '', onTitleDuplicate, isTitleDuplicateConfirmed, }); @@ -81,15 +76,15 @@ export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => { setIsApplying(false); if (validTitle) { - dashboard.dispatch.setStateFromSettingsFlyout({ lastSavedId, ...dashboardSettingsState }); + dashboardApi.setSettings(localSettings); onClose(); } }; const updateDashboardSetting = useCallback((newSettings: Partial) => { - setDashboardSettingsState((prevDashboardSettingsState) => { + setLocalSettings((prevSettings) => { return { - ...prevDashboardSettingsState, + ...prevSettings, ...newSettings, }; }); @@ -117,7 +112,7 @@ export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => { id="dashboard.embeddableApi.showSettings.flyout.form.duplicateTitleDescription" defaultMessage="Saving ''{title}'' creates a duplicate title." values={{ - title: dashboardSettingsState.title, + title: localSettings.title, }} />

@@ -137,7 +132,7 @@ export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => { } > updateDashboardSetting({ tags: selectedTags })} /> @@ -173,7 +168,7 @@ export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => { data-test-subj="dashboardTitleInput" name="title" type="text" - value={dashboardSettingsState.title} + value={localSettings.title} onChange={(event) => { setIsTitleDuplicate(false); setIsTitleDuplicateConfirmed(false); @@ -201,7 +196,7 @@ export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => { className="dashboardDescriptionInputText" data-test-subj="dashboardDescriptionInput" name="description" - value={dashboardSettingsState.description ?? ''} + value={localSettings.description ?? ''} onChange={(event) => updateDashboardSetting({ description: event.target.value })} aria-label={i18n.translate( 'dashboard.embeddableApi.showSettings.flyout.form.panelDescriptionAriaLabel', @@ -222,7 +217,7 @@ export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => { > updateDashboardSetting({ timeRestore: event.target.checked })} label={ { defaultMessage: 'Use margins between panels', } )} - checked={dashboardSettingsState.useMargins} + checked={localSettings.useMargins} onChange={(event) => updateDashboardSetting({ useMargins: event.target.checked })} data-test-subj="dashboardMarginsCheckbox" /> @@ -254,7 +249,7 @@ export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => { defaultMessage: 'Show panel titles', } )} - checked={!dashboardSettingsState.hidePanelTitles} + checked={!localSettings.hidePanelTitles} onChange={(event) => updateDashboardSetting({ hidePanelTitles: !event.target.checked }) } @@ -313,7 +308,7 @@ export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => { /> } - checked={dashboardSettingsState.syncColors} + checked={localSettings.syncColors} onChange={(event) => updateDashboardSetting({ syncColors: event.target.checked })} data-test-subj="dashboardSyncColorsCheckbox" /> @@ -326,10 +321,10 @@ export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => { defaultMessage: 'Sync cursor across panels', } )} - checked={dashboardSettingsState.syncCursor} + checked={localSettings.syncCursor} onChange={(event) => { const syncCursor = event.target.checked; - if (!syncCursor && dashboardSettingsState.syncTooltips) { + if (!syncCursor && localSettings.syncTooltips) { updateDashboardSetting({ syncCursor, syncTooltips: false }); } else { updateDashboardSetting({ syncCursor }); @@ -346,8 +341,8 @@ export const DashboardSettings = ({ onClose }: DashboardSettingsProps) => { defaultMessage: 'Sync tooltips across panels', } )} - checked={dashboardSettingsState.syncTooltips} - disabled={!Boolean(dashboardSettingsState.syncCursor)} + checked={localSettings.syncTooltips} + disabled={!Boolean(localSettings.syncCursor)} onChange={(event) => updateDashboardSetting({ syncTooltips: event.target.checked }) } diff --git a/src/plugins/dashboard/public/dashboard_container/component/viewport/dashboard_viewport.tsx b/src/plugins/dashboard/public/dashboard_container/component/viewport/dashboard_viewport.tsx index 488f0ba99c098c..1e4e0822f7ce8e 100644 --- a/src/plugins/dashboard/public/dashboard_container/component/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard/public/dashboard_container/component/viewport/dashboard_viewport.tsx @@ -22,9 +22,9 @@ import { ControlGroupSerializedState, } from '@kbn/controls-plugin/public'; import { CONTROL_GROUP_TYPE } from '@kbn/controls-plugin/common'; -import { useStateFromPublishingSubject } from '@kbn/presentation-publishing'; +import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing'; import { DashboardGrid } from '../grid'; -import { useDashboardContainer } from '../../embeddable/dashboard_container'; +import { useDashboardApi } from '../../../dashboard_api/use_dashboard_api'; import { DashboardEmptyScreen } from '../empty_screen/dashboard_empty_screen'; export const useDebouncedWidthObserver = (skipDebounce = false, wait = 100) => { @@ -42,17 +42,33 @@ export const useDebouncedWidthObserver = (skipDebounce = false, wait = 100) => { }; export const DashboardViewportComponent = () => { - const dashboard = useDashboardContainer(); - - const controlGroupApi = useStateFromPublishingSubject(dashboard.controlGroupApi$); - const panelCount = Object.keys(dashboard.select((state) => state.explicitInput.panels)).length; + const dashboardApi = useDashboardApi(); const [hasControls, setHasControls] = useState(false); - const viewMode = dashboard.select((state) => state.explicitInput.viewMode); - const dashboardTitle = dashboard.select((state) => state.explicitInput.title); - const useMargins = dashboard.select((state) => state.explicitInput.useMargins); - const description = dashboard.select((state) => state.explicitInput.description); - const focusedPanelId = dashboard.select((state) => state.componentState.focusedPanelId); - const expandedPanelId = dashboard.select((state) => state.componentState.expandedPanelId); + const [ + controlGroupApi, + dashboardTitle, + description, + expandedPanelId, + focusedPanelId, + panels, + viewMode, + useMargins, + uuid, + ] = useBatchedPublishingSubjects( + dashboardApi.controlGroupApi$, + dashboardApi.panelTitle, + dashboardApi.panelDescription, + dashboardApi.expandedPanelId, + dashboardApi.focusedPanelId$, + dashboardApi.panels$, + dashboardApi.viewMode, + dashboardApi.useMargins$, + dashboardApi.uuid$ + ); + + const panelCount = useMemo(() => { + return Object.keys(panels).length; + }, [panels]); const { ref: resizeRef, width: viewportWidth } = useDebouncedWidthObserver(!!focusedPanelId); @@ -104,19 +120,19 @@ export const DashboardViewportComponent = () => { ControlGroupRuntimeState, ControlGroupApi > - key={dashboard.getInput().id} + key={uuid} hidePanelChrome={true} panelProps={{ hideLoader: true }} type={CONTROL_GROUP_TYPE} maybeId={'control_group'} getParentApi={() => { return { - ...dashboard, - getSerializedStateForChild: dashboard.getSerializedStateForControlGroup, - getRuntimeStateForChild: dashboard.getRuntimeStateForControlGroup, + ...dashboardApi, + getSerializedStateForChild: dashboardApi.getSerializedStateForControlGroup, + getRuntimeStateForChild: dashboardApi.getRuntimeStateForControlGroup, }; }} - onApiAvailable={(api) => dashboard.setControlGroupApi(api)} + onApiAvailable={(api) => dashboardApi.setControlGroupApi(api)} />
) : null} @@ -143,11 +159,11 @@ export const DashboardViewportComponent = () => { // because ExitFullScreenButton sets isFullscreenMode to false on unmount while rerendering. // This specifically fixed maximizing/minimizing panels without exiting fullscreen mode. const WithFullScreenButton = ({ children }: { children: JSX.Element }) => { - const dashboard = useDashboardContainer(); + const dashboardApi = useDashboardApi(); - const isFullScreenMode = dashboard.select((state) => state.componentState.fullScreenMode); - const isEmbeddedExternally = dashboard.select( - (state) => state.componentState.isEmbeddedExternally + const [isFullScreenMode, isEmbeddedExternally] = useBatchedPublishingSubjects( + dashboardApi.fullScreenMode$, + dashboardApi.embeddedExternally$ ); return ( @@ -156,7 +172,7 @@ const WithFullScreenButton = ({ children }: { children: JSX.Element }) => { {isFullScreenMode && ( dashboard.dispatch.setFullScreenMode(false)} + onExit={() => dashboardApi.setFullScreenMode(false)} toggleChrome={!isEmbeddedExternally} /> diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/api/index.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/api/index.ts index 81e8699093fb2f..4829a7b61318ef 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/api/index.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/api/index.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export { showSettings } from './show_settings'; +export { openSettingsFlyout } from './open_settings_flyout'; export { addFromLibrary } from './add_panel_from_library'; export { addOrUpdateEmbeddable } from './panel_management'; export { runQuickSave, runInteractiveSave } from './run_save_functions'; diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/api/show_settings.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/api/open_settings_flyout.tsx similarity index 68% rename from src/plugins/dashboard/public/dashboard_container/embeddable/api/show_settings.tsx rename to src/plugins/dashboard/public/dashboard_container/embeddable/api/open_settings_flyout.tsx index 9433418b0c371a..70955ea67d7873 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/api/show_settings.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/api/open_settings_flyout.tsx @@ -13,37 +13,33 @@ import { toMountPoint } from '@kbn/react-kibana-mount'; import { pluginServices } from '../../../services/plugin_services'; import { DashboardSettings } from '../../component/settings/settings_flyout'; -import { DashboardContainer, DashboardContainerContext } from '../dashboard_container'; +import { DashboardContext } from '../../../dashboard_api/use_dashboard_api'; +import { DashboardApi } from '../../../dashboard_api/types'; -export function showSettings(this: DashboardContainer) { +export function openSettingsFlyout(dashboardApi: DashboardApi) { const { analytics, settings: { i18n, theme }, overlays, } = pluginServices.getServices(); - // TODO Move this action into DashboardContainer.openOverlay - this.dispatch.setHasOverlays(true); - - this.openOverlay( + dashboardApi.openOverlay( overlays.openFlyout( toMountPoint( - + { - this.dispatch.setHasOverlays(false); - this.clearOverlays(); + dashboardApi.clearOverlays(); }} /> - , + , { analytics, i18n, theme } ), { size: 's', 'data-test-subj': 'dashboardSettingsFlyout', onClose: (flyout) => { - this.clearOverlays(); - this.dispatch.setHasOverlays(false); + dashboardApi.clearOverlays(); flyout.close(); }, } diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx index 32262c64d1d06f..43f733895f1f5f 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx @@ -50,7 +50,7 @@ import { LocatorPublic } from '@kbn/share-plugin/common'; import { ExitFullScreenButtonKibanaProvider } from '@kbn/shared-ux-button-exit-full-screen'; import deepEqual from 'fast-deep-equal'; import { omit } from 'lodash'; -import React, { createContext, useContext } from 'react'; +import React from 'react'; import ReactDOM from 'react-dom'; import { batch } from 'react-redux'; import { BehaviorSubject, Subject, Subscription, first, skipWhile, switchMap } from 'rxjs'; @@ -59,8 +59,13 @@ import { v4 } from 'uuid'; import { PublishesSettings } from '@kbn/presentation-containers/interfaces/publishes_settings'; import { apiHasSerializableState } from '@kbn/presentation-containers/interfaces/serialized_state'; import { ControlGroupApi, ControlGroupSerializedState } from '@kbn/controls-plugin/public'; -import { DashboardLocatorParams, DASHBOARD_CONTAINER_TYPE } from '../..'; -import { DashboardAttributes, DashboardContainerInput, DashboardPanelState } from '../../../common'; +import { DashboardLocatorParams, DASHBOARD_CONTAINER_TYPE, DashboardApi } from '../..'; +import { + DashboardAttributes, + DashboardContainerInput, + DashboardPanelMap, + DashboardPanelState, +} from '../../../common'; import { getReferencesForControls, getReferencesForPanelId, @@ -81,14 +86,13 @@ import { DashboardViewport } from '../component/viewport/dashboard_viewport'; import { getDashboardPanelPlacementSetting } from '../panel_placement/panel_placement_registry'; import { dashboardContainerReducers } from '../state/dashboard_container_reducers'; import { getDiffingMiddleware } from '../state/diffing/dashboard_diffing_integration'; -import { DashboardPublicState, DashboardReduxState, UnsavedPanelState } from '../types'; import { - addFromLibrary, - addOrUpdateEmbeddable, - runQuickSave, - runInteractiveSave, - showSettings, -} from './api'; + DashboardPublicState, + DashboardReduxState, + DashboardStateFromSettingsFlyout, + UnsavedPanelState, +} from '../types'; +import { addFromLibrary, addOrUpdateEmbeddable, runQuickSave, runInteractiveSave } from './api'; import { duplicateDashboardPanel } from './api/duplicate_dashboard_panel'; import { combineDashboardFiltersWithControlGroupFilters, @@ -102,6 +106,7 @@ import { } from './dashboard_container_factory'; import { getPanelAddedSuccessString } from '../../dashboard_app/_dashboard_app_strings'; import { PANELS_CONTROL_GROUP_KEY } from '../../services/dashboard_backup/dashboard_backup_service'; +import { DashboardContext } from '../../dashboard_api/use_dashboard_api'; export interface InheritedChildInput { filters: Filter[]; @@ -124,15 +129,6 @@ type DashboardReduxEmbeddableTools = ReduxEmbeddableTools< typeof dashboardContainerReducers >; -export const DashboardContainerContext = createContext(null); -export const useDashboardContainer = (): DashboardContainer => { - const dashboard = useContext(DashboardContainerContext); - if (dashboard == null) { - throw new Error('useDashboardContainer must be used inside DashboardContainerContext.'); - } - return dashboard!; -}; - export class DashboardContainer extends Container implements @@ -297,6 +293,12 @@ export class DashboardContainer this.dispatch = reduxTools.dispatch; this.select = reduxTools.select; + this.uuid$ = embeddableInputToSubject( + this.publishingSubscription, + this, + 'id' + ) as BehaviorSubject; + this.savedObjectId = new BehaviorSubject(this.getDashboardSavedObjectId()); this.expandedPanelId = new BehaviorSubject(this.getExpandedPanelId()); this.focusedPanelId$ = new BehaviorSubject(this.getState().componentState.focusedPanelId); @@ -307,6 +309,16 @@ export class DashboardContainer ); this.hasUnsavedChanges$ = new BehaviorSubject(this.getState().componentState.hasUnsavedChanges); this.hasOverlays$ = new BehaviorSubject(this.getState().componentState.hasOverlays); + this.useMargins$ = new BehaviorSubject(this.getState().explicitInput.useMargins); + this.scrollToPanelId$ = new BehaviorSubject(this.getState().componentState.scrollToPanelId); + this.highlightPanelId$ = new BehaviorSubject(this.getState().componentState.highlightPanelId); + this.animatePanelTransforms$ = new BehaviorSubject( + this.getState().componentState.animatePanelTransforms + ); + this.panels$ = new BehaviorSubject(this.getState().explicitInput.panels); + this.embeddedExternally$ = new BehaviorSubject( + this.getState().componentState.isEmbeddedExternally + ); this.publishingSubscription.add( this.onStateChange(() => { const state = this.getState(); @@ -334,6 +346,24 @@ export class DashboardContainer if (this.hasOverlays$.value !== state.componentState.hasOverlays) { this.hasOverlays$.next(state.componentState.hasOverlays); } + if (this.useMargins$.value !== state.explicitInput.useMargins) { + this.useMargins$.next(state.explicitInput.useMargins); + } + if (this.scrollToPanelId$.value !== state.componentState.scrollToPanelId) { + this.scrollToPanelId$.next(state.componentState.scrollToPanelId); + } + if (this.highlightPanelId$.value !== state.componentState.highlightPanelId) { + this.highlightPanelId$.next(state.componentState.highlightPanelId); + } + if (this.animatePanelTransforms$.value !== state.componentState.animatePanelTransforms) { + this.animatePanelTransforms$.next(state.componentState.animatePanelTransforms); + } + if (this.embeddedExternally$.value !== state.componentState.isEmbeddedExternally) { + this.embeddedExternally$.next(state.componentState.isEmbeddedExternally); + } + if (this.panels$.value !== state.explicitInput.panels) { + this.panels$.next(state.explicitInput.panels); + } }) ); @@ -452,9 +482,9 @@ export class DashboardContainer - + - + , dom @@ -535,7 +565,6 @@ export class DashboardContainer public runInteractiveSave = runInteractiveSave; public runQuickSave = runQuickSave; - public openSettingsFlyout = showSettings; public addFromLibrary = addFromLibrary; public duplicatePanel(id: string) { @@ -555,6 +584,13 @@ export class DashboardContainer public hasRunMigrations$: BehaviorSubject; public hasUnsavedChanges$: BehaviorSubject; public hasOverlays$: BehaviorSubject; + public useMargins$: BehaviorSubject; + public scrollToPanelId$: BehaviorSubject; + public highlightPanelId$: BehaviorSubject; + public animatePanelTransforms$: BehaviorSubject; + public panels$: BehaviorSubject; + public embeddedExternally$: BehaviorSubject; + public uuid$: BehaviorSubject; public async replacePanel(idToRemove: string, { panelType, initialState }: PanelPackage) { const newId = await this.replaceEmbeddable( @@ -812,6 +848,26 @@ export class DashboardContainer return this.getState().explicitInput.panels; }; + public getSettings = (): DashboardStateFromSettingsFlyout => { + const state = this.getState(); + return { + description: state.explicitInput.description, + hidePanelTitles: state.explicitInput.hidePanelTitles, + lastSavedId: state.componentState.lastSavedId, + syncColors: state.explicitInput.syncColors, + syncCursor: state.explicitInput.syncCursor, + syncTooltips: state.explicitInput.syncTooltips, + tags: state.explicitInput.tags, + timeRestore: state.explicitInput.timeRestore, + title: state.explicitInput.title, + useMargins: state.explicitInput.useMargins, + }; + }; + + public setSettings = (settings: DashboardStateFromSettingsFlyout) => { + this.dispatch.setStateFromSettingsFlyout(settings); + }; + public setExpandedPanelId = (newId?: string) => { this.dispatch.setExpandedPanelId(newId); }; @@ -925,6 +981,10 @@ export class DashboardContainer this.setScrollToPanelId(id); }; + public setPanels = (panels: DashboardPanelMap) => { + this.dispatch.setPanels(panels); + }; + // ------------------------------------------------------------------------------------------------------ // React Embeddable system // ------------------------------------------------------------------------------------------------------ diff --git a/src/plugins/dashboard/public/dashboard_top_nav/internal_dashboard_top_nav.tsx b/src/plugins/dashboard/public/dashboard_top_nav/internal_dashboard_top_nav.tsx index a166a01ba327d7..02cbaa7d901008 100644 --- a/src/plugins/dashboard/public/dashboard_top_nav/internal_dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/dashboard_top_nav/internal_dashboard_top_nav.tsx @@ -48,6 +48,7 @@ import './_dashboard_top_nav.scss'; import { DashboardRedirect } from '../dashboard_container/types'; import { SaveDashboardReturn } from '../services/dashboard_content_management/types'; import { useDashboardApi } from '../dashboard_api/use_dashboard_api'; +import { openSettingsFlyout } from '../dashboard_container/embeddable/api'; export interface InternalDashboardTopNavProps { customLeadingBreadCrumbs?: EuiBreadcrumb[]; @@ -188,7 +189,7 @@ export function InternalDashboardTopNav({ size="s" type="pencil" className="dshTitleBreadcrumbs__updateIcon" - onClick={() => dashboardApi.openSettingsFlyout()} + onClick={() => openSettingsFlyout(dashboardApi)} /> ) : (