diff --git a/src/plugins/dashboard/public/application/app.tsx b/src/plugins/dashboard/public/application/app.tsx
index 439806bb8de3..710908233d98 100644
--- a/src/plugins/dashboard/public/application/app.tsx
+++ b/src/plugins/dashboard/public/application/app.tsx
@@ -29,7 +29,10 @@ export const DashboardApp = ({ onAppLeave }: DashboardAppProps) => {
exact
path={[DashboardConstants.CREATE_NEW_DASHBOARD_URL, createDashboardEditUrl(':id')]}
>
-
+
diff --git a/src/plugins/dashboard/public/application/components/dashboard_editor.tsx b/src/plugins/dashboard/public/application/components/dashboard_editor.tsx
index 2be15917e940..b90687556493 100644
--- a/src/plugins/dashboard/public/application/components/dashboard_editor.tsx
+++ b/src/plugins/dashboard/public/application/components/dashboard_editor.tsx
@@ -6,42 +6,23 @@
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { EventEmitter } from 'events';
-import { EMPTY, Subscription, merge } from 'rxjs';
-import { catchError, distinctUntilChanged, map, mapTo, startWith, switchMap } from 'rxjs/operators';
-import deepEqual from 'fast-deep-equal';
import { DashboardTopNav } from '../components/dashboard_top_nav';
import { useChromeVisibility } from '../utils/use/use_chrome_visibility';
import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public';
import { useSavedDashboardInstance } from '../utils/use/use_saved_dashboard_instance';
-
-import { DashboardServices, SavedDashboardPanel } from '../../types';
-import {
- DASHBOARD_CONTAINER_TYPE,
- DashboardContainer,
- DashboardContainerInput,
- DashboardPanelState,
-} from '../embeddable';
-import {
- ContainerOutput,
- ErrorEmbeddable,
- ViewMode,
- isErrorEmbeddable,
-} from '../../embeddable_plugin';
-import { DashboardEmptyScreen, DashboardEmptyScreenProps } from '../dashboard_empty_screen';
-import { convertSavedDashboardPanelToPanelState } from '../lib/embeddable_saved_object_converters';
+import { DashboardServices } from '../../types';
import { useDashboardAppState } from '../utils/use/use_dashboard_app_state';
+import { useDashboardContainer } from '../utils/use/use_dashboard_container';
+import { useEditorUpdates } from '../utils/use/use_editor_updates';
export const DashboardEditor = () => {
const { id: dashboardIdFromUrl } = useParams<{ id: string }>();
const { services } = useOpenSearchDashboards();
- const { embeddable, data, dashboardConfig, embeddableCapabilities, uiSettings, http } = services;
- const { query: queryService } = data;
- const { visualizeCapabilities, mapsCapabilities } = embeddableCapabilities;
- const timefilter = queryService.timefilter.timefilter;
const isChromeVisible = useChromeVisibility(services.chrome);
const [eventEmitter] = useState(new EventEmitter());
+ const dashboardDom = document.getElementById('dashboardViewport');
- const { savedDashboardInstance } = useSavedDashboardInstance(
+ const savedDashboardInstance = useSavedDashboardInstance(
services,
eventEmitter,
isChromeVisible,
@@ -50,165 +31,50 @@ export const DashboardEditor = () => {
const { appState } = useDashboardAppState(services, eventEmitter, savedDashboardInstance);
- const appStateData = appState?.get();
- if (!appStateData) {
- return null;
- }
- let dashboardContainer: DashboardContainer | undefined;
- let inputSubscription: Subscription | undefined;
- let outputSubscription: Subscription | undefined;
-
- const dashboardDom = document.getElementById('dashboardViewport');
- const dashboardFactory = embeddable.getEmbeddableFactory<
- DashboardContainerInput,
- ContainerOutput,
- DashboardContainer
- >(DASHBOARD_CONTAINER_TYPE);
-
- const getShouldShowEditHelp = () => {
- return (
- !appStateData.panels.length &&
- appStateData.viewMode === ViewMode.EDIT &&
- !dashboardConfig.getHideWriteControls()
- );
- };
-
- const getShouldShowViewHelp = () => {
- return (
- !appStateData.panels.length &&
- appStateData.viewMode === ViewMode.VIEW &&
- !dashboardConfig.getHideWriteControls()
- );
- };
-
- const shouldShowUnauthorizedEmptyState = () => {
- const readonlyMode =
- !appStateData.panels.length &&
- !getShouldShowEditHelp() &&
- !getShouldShowViewHelp() &&
- dashboardConfig.getHideWriteControls();
- const userHasNoPermissions =
- !appStateData.panels.length && !visualizeCapabilities.save && !mapsCapabilities.save;
- return readonlyMode || userHasNoPermissions;
- };
-
- const getEmptyScreenProps = (
- shouldShowEditHelp: boolean,
- isEmptyInReadOnlyMode: boolean
- ): DashboardEmptyScreenProps => {
- const emptyScreenProps: DashboardEmptyScreenProps = {
- onLinkClick: () => {}, // TODO
- showLinkToVisualize: shouldShowEditHelp,
- uiSettings,
- http,
- };
- if (shouldShowEditHelp) {
- emptyScreenProps.onVisualizeClick = () => {
- alert('click'); // TODO
- };
- }
- if (isEmptyInReadOnlyMode) {
- emptyScreenProps.isReadonlyMode = true;
- }
- return emptyScreenProps;
- };
+ const { dashboardContainer } = useDashboardContainer(
+ services,
+ isChromeVisible,
+ eventEmitter,
+ dashboardDom,
+ savedDashboardInstance,
+ appState
+ );
- const getDashboardInput = () => {
- const embeddablesMap: {
- [key: string]: DashboardPanelState;
- } = {};
- appStateData.panels.forEach((panel: SavedDashboardPanel) => {
- embeddablesMap[panel.panelIndex] = convertSavedDashboardPanelToPanelState(panel);
- });
+ const { isEmbeddableRendered, currentAppState } = useEditorUpdates(
+ services,
+ eventEmitter,
+ savedDashboardInstance,
+ dashboardContainer,
+ appState
+ );
- const lastReloadRequestTime = 0;
- return {
- id: savedDashboardInstance.id || '',
- filters: appStateData.filters,
- hidePanelTitles: appStateData?.options.hidePanelTitles,
- query: appStateData.query,
- timeRange: {
- ..._.cloneDeep(timefilter.getTime()),
- },
- refreshConfig: timefilter.getRefreshInterval(),
- viewMode: appStateData.viewMode,
- panels: embeddablesMap,
- isFullScreenMode: appStateData?.fullScreenMode,
- isEmbeddedExternally: false, // TODO
- // isEmptyState: shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadonlyMode,
- isEmptyState: false, // TODO
- useMargins: appStateData.options.useMargins,
- lastReloadRequestTime, // TODO
- title: appStateData.title,
- description: appStateData.description,
- expandedPanelId: appStateData.expandedPanelId,
+ useEffect(() => {
+ // clean up all registered listeners if any is left
+ return () => {
+ eventEmitter.removeAllListeners();
};
- };
-
- if (dashboardFactory) {
- dashboardFactory
- .create(getDashboardInput())
- .then((container: DashboardContainer | ErrorEmbeddable | undefined) => {
- if (container && !isErrorEmbeddable(container)) {
- dashboardContainer = container;
-
- dashboardContainer.renderEmpty = () => {
- const shouldShowEditHelp = getShouldShowEditHelp();
- const shouldShowViewHelp = getShouldShowViewHelp();
- const isEmptyInReadOnlyMode = shouldShowUnauthorizedEmptyState();
- const isEmptyState = shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadOnlyMode;
- return isEmptyState ? (
-
- ) : null;
- };
-
- outputSubscription = merge(
- // output of dashboard container itself
- dashboardContainer.getOutput$(),
- // plus output of dashboard container children,
- // children may change, so make sure we subscribe/unsubscribe with switchMap
- dashboardContainer.getOutput$().pipe(
- map(() => dashboardContainer!.getChildIds()),
- distinctUntilChanged(deepEqual),
- switchMap((newChildIds: string[]) =>
- merge(
- ...newChildIds.map((childId) =>
- dashboardContainer!
- .getChild(childId)
- .getOutput$()
- .pipe(catchError(() => EMPTY))
- )
- )
- )
- )
- )
- .pipe(
- mapTo(dashboardContainer),
- startWith(dashboardContainer) // to trigger initial index pattern update
- // updateIndexPatternsOperator //TODO
- )
- .subscribe();
-
- inputSubscription = dashboardContainer.getInput$().subscribe(() => {});
+ }, [eventEmitter]);
- if (dashboardDom && container) {
- container.render(dashboardDom);
- }
- }
- });
- }
+ console.log('savedDashboardInstance', savedDashboardInstance);
+ console.log('appState', appState);
+ console.log('currentAppState', currentAppState);
+ console.log('isEmbeddableRendered', isEmbeddableRendered);
+ console.log('dashboardContainer', dashboardContainer);
return (
- {savedDashboardInstance && appState && (
-
- )}
+
+ {savedDashboardInstance && appState && dashboardContainer && currentAppState && (
+
+ )}
+
);
};
diff --git a/src/plugins/dashboard/public/application/components/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/components/dashboard_top_nav.tsx
index 4ee92567c590..f79cf76d87d2 100644
--- a/src/plugins/dashboard/public/application/components/dashboard_top_nav.tsx
+++ b/src/plugins/dashboard/public/application/components/dashboard_top_nav.tsx
@@ -8,12 +8,22 @@ import { Filter } from 'src/plugins/data/public';
import { useCallback } from 'react';
import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public';
import { getTopNavConfig } from '../top_nav/get_top_nav_config';
-import { DashboardAppState, DashboardServices, NavAction } from '../../types';
+import {
+ DashboardAppStateContainer,
+ DashboardAppState,
+ DashboardServices,
+ NavAction,
+} from '../../types';
+import { getNavActions } from '../utils/get_nav_actions';
+import { DashboardContainer } from '../embeddable';
interface DashboardTopNavProps {
isChromeVisible: boolean;
savedDashboardInstance: any;
- appState: DashboardAppState;
+ stateContainer: DashboardAppStateContainer;
+ currentAppState: DashboardAppState;
+ isEmbeddableRendered: boolean;
+ dashboardContainer?: DashboardContainer;
}
enum UrlParams {
@@ -24,7 +34,14 @@ enum UrlParams {
HIDE_FILTER_BAR = 'hide-filter-bar',
}
-const TopNav = ({ isChromeVisible, savedDashboardInstance, appState }: DashboardTopNavProps) => {
+const TopNav = ({
+ isChromeVisible,
+ savedDashboardInstance,
+ stateContainer,
+ currentAppState,
+ isEmbeddableRendered,
+ dashboardContainer,
+}: DashboardTopNavProps) => {
const [filters, setFilters] = useState([]);
const [topNavMenu, setTopNavMenu] = useState();
const [isFullScreenMode, setIsFullScreenMode] = useState();
@@ -44,27 +61,44 @@ const TopNav = ({ isChromeVisible, savedDashboardInstance, appState }: Dashboard
};
const shouldShowNavBarComponent = (forceShow: boolean): boolean =>
- (forceShow || isChromeVisible) && !appState?.fullScreenMode;
+ (forceShow || isChromeVisible) && !currentAppState?.fullScreenMode;
useEffect(() => {
setFilters(queryService.filterManager.getFilters());
}, [services, queryService]);
useEffect(() => {
- const navActions: {
- [key: string]: NavAction;
- } = {}; // TODO: need to implement nav actions
- setTopNavMenu(
- getTopNavConfig(appState?.viewMode, navActions, dashboardConfig.getHideWriteControls())
- );
- }, [appState, services, dashboardConfig]);
+ if (isEmbeddableRendered) {
+ const navActions = getNavActions(
+ stateContainer,
+ savedDashboardInstance,
+ services,
+ dashboardContainer
+ );
+ setTopNavMenu(
+ getTopNavConfig(
+ currentAppState?.viewMode,
+ navActions,
+ dashboardConfig.getHideWriteControls()
+ )
+ );
+ }
+ }, [
+ currentAppState,
+ services,
+ dashboardConfig,
+ dashboardContainer,
+ savedDashboardInstance,
+ stateContainer,
+ isEmbeddableRendered,
+ ]);
useEffect(() => {
- setIsFullScreenMode(appState?.fullScreenMode);
- }, [appState, services]);
+ setIsFullScreenMode(currentAppState?.fullScreenMode);
+ }, [currentAppState, services]);
const shouldShowFilterBar = (forceHide: boolean): boolean =>
- !forceHide && (filters!.length > 0 || !appState?.fullScreenMode);
+ !forceHide && (filters!.length > 0 || !currentAppState?.fullScreenMode);
const forceShowTopNavMenu = shouldForceDisplay(UrlParams.SHOW_TOP_MENU);
const forceShowQueryInput = shouldForceDisplay(UrlParams.SHOW_QUERY_INPUT);
@@ -90,13 +124,16 @@ const TopNav = ({ isChromeVisible, savedDashboardInstance, appState }: Dashboard
}*/
}, []);
+ // console.log('currentAppState', currentAppState);
+ // console.log('state container get()', stateContainer.get());
+ // console.log('dashboard container top nav', dashboardContainer);
return isChromeVisible ? (
string,
timeFilter: TimefilterContract,
- dashboardStateManager: DashboardStateManager,
+ stateContainer: DashboardAppStateContainer,
+ savedDashboard: any,
saveOptions: SavedObjectSaveOpts
): Promise {
- const savedDashboard = dashboardStateManager.savedDashboard;
- const appState = dashboardStateManager.appState;
+ const appState = stateContainer.getState();
- updateSavedDashboard(savedDashboard, appState, timeFilter, toJson);
+ updateSavedDashboard(savedDashboard, appState, timeFilter);
return savedDashboard.save(saveOptions).then((id: string) => {
if (id) {
- // reset state only when save() was successful
- // e.g. save() could be interrupted if title is duplicated and not confirmed
- dashboardStateManager.lastSavedDashboardFilters = dashboardStateManager.getFilterState();
- dashboardStateManager.resetState();
+ return id;
}
-
- return id;
});
}
diff --git a/src/plugins/dashboard/public/application/lib/update_saved_dashboard.ts b/src/plugins/dashboard/public/application/lib/update_saved_dashboard.ts
index 0a52e8fbb94f..64ed0e86a6fd 100644
--- a/src/plugins/dashboard/public/application/lib/update_saved_dashboard.ts
+++ b/src/plugins/dashboard/public/application/lib/update_saved_dashboard.ts
@@ -38,14 +38,13 @@ import { opensearchFilters } from '../../../../data/public';
export function updateSavedDashboard(
savedDashboard: SavedObjectDashboard,
appState: DashboardAppState,
- timeFilter: TimefilterContract,
- toJson: (object: T) => string
+ timeFilter: TimefilterContract
) {
savedDashboard.title = appState.title;
savedDashboard.description = appState.description;
savedDashboard.timeRestore = appState.timeRestore;
- savedDashboard.panelsJSON = toJson(appState.panels);
- savedDashboard.optionsJSON = toJson(appState.options);
+ savedDashboard.panelsJSON = JSON.stringify(appState.panels);
+ savedDashboard.optionsJSON = JSON.stringify(appState.options);
savedDashboard.timeFrom = savedDashboard.timeRestore
? FilterUtils.convertTimeToUTCString(timeFilter.getTime().from)
diff --git a/src/plugins/dashboard/public/application/utils/dashboard_embeddable_editor.tsx b/src/plugins/dashboard/public/application/utils/dashboard_embeddable_editor.tsx
new file mode 100644
index 000000000000..9d594194e25c
--- /dev/null
+++ b/src/plugins/dashboard/public/application/utils/dashboard_embeddable_editor.tsx
@@ -0,0 +1,44 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { Suspense, lazy } from 'react';
+import { useEffect } from 'react';
+
+function DashboardEmbeddableEditor({
+ timeRange,
+ filters,
+ query,
+ dom,
+ savedDashboardInstance,
+ eventEmitter,
+ dashboardContainer,
+}: any) {
+ useEffect(() => {
+ if (!dom) {
+ return;
+ }
+
+ dashboardContainer.render(dom);
+ setTimeout(() => {
+ eventEmitter.emit('embeddableRendered');
+ });
+
+ return () => dashboardContainer.destroy();
+ }, [dashboardContainer, eventEmitter, dom]);
+
+ useEffect(() => {
+ dashboardContainer.updateInput({
+ timeRange,
+ filters,
+ query,
+ });
+ }, [dashboardContainer, timeRange, filters, query]);
+
+ return ;
+}
+
+// default export required for React.Lazy
+// eslint-disable-next-line import/no-default-export
+export { DashboardEmbeddableEditor as default };
diff --git a/src/plugins/dashboard/public/application/utils/get_nav_actions.tsx b/src/plugins/dashboard/public/application/utils/get_nav_actions.tsx
new file mode 100644
index 000000000000..d11d4413a451
--- /dev/null
+++ b/src/plugins/dashboard/public/application/utils/get_nav_actions.tsx
@@ -0,0 +1,409 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { ReactElement, useState } from 'react';
+import { i18n } from '@osd/i18n';
+import { EUI_MODAL_CANCEL_BUTTON, EuiCheckboxGroup } from '@elastic/eui';
+import { EuiCheckboxGroupIdToSelectedMap } from '@elastic/eui/src/components/form/checkbox/checkbox_group';
+import {
+ SaveResult,
+ SavedObjectSaveOpts,
+ getSavedObjectFinder,
+ showSaveModal,
+} from '../../../../saved_objects/public';
+import { DashboardAppStateContainer, DashboardServices, NavAction } from '../../types';
+import { DashboardSaveModal } from '../top_nav/save_modal';
+import { TopNavIds } from '../top_nav/top_nav_ids';
+import {
+ EmbeddableFactoryNotFoundError,
+ EmbeddableInput,
+ ViewMode,
+ isErrorEmbeddable,
+ openAddPanelFlyout,
+} from '../../embeddable_plugin';
+import { showCloneModal } from '../top_nav/show_clone_modal';
+import { showOptionsPopover } from '../top_nav/show_options_popover';
+import { saveDashboard } from '../lib';
+import { DashboardContainer } from '../embeddable/dashboard_container';
+import { DashboardConstants, createDashboardEditUrl } from '../../dashboard_constants';
+import { unhashUrl } from '../../../../opensearch_dashboards_utils/public';
+
+enum UrlParams {
+ SHOW_TOP_MENU = 'show-top-menu',
+ SHOW_QUERY_INPUT = 'show-query-input',
+ SHOW_TIME_FILTER = 'show-time-filter',
+ SHOW_FILTER_BAR = 'show-filter-bar',
+ HIDE_FILTER_BAR = 'hide-filter-bar',
+}
+
+interface UrlParamsSelectedMap {
+ [UrlParams.SHOW_TOP_MENU]: boolean;
+ [UrlParams.SHOW_QUERY_INPUT]: boolean;
+ [UrlParams.SHOW_TIME_FILTER]: boolean;
+ [UrlParams.SHOW_FILTER_BAR]: boolean;
+}
+
+interface UrlParamValues extends Omit {
+ [UrlParams.HIDE_FILTER_BAR]: boolean;
+}
+
+export const getNavActions = (
+ stateContainer: DashboardAppStateContainer,
+ savedDashboard: any,
+ services: DashboardServices,
+ dashboardContainer?: DashboardContainer
+) => {
+ const {
+ history,
+ embeddable,
+ data: { query: queryService },
+ notifications,
+ overlays,
+ i18n: { Context: I18nContext },
+ savedObjects,
+ uiSettings,
+ chrome,
+ share,
+ dashboardConfig,
+ dashboardCapabilities,
+ } = services;
+ const navActions: {
+ [key: string]: NavAction;
+ } = {};
+
+ if (!stateContainer) {
+ return navActions;
+ }
+ const appState = stateContainer.getState();
+ navActions[TopNavIds.FULL_SCREEN] = () => {
+ stateContainer.transitions.set('fullScreenMode', true);
+ // updateNavBar();
+ };
+ navActions[TopNavIds.EXIT_EDIT_MODE] = () => onChangeViewMode(ViewMode.VIEW);
+ navActions[TopNavIds.ENTER_EDIT_MODE] = () => onChangeViewMode(ViewMode.EDIT);
+ navActions[TopNavIds.SAVE] = () => {
+ const currentTitle = appState.title;
+ const currentDescription = appState.description;
+ const currentTimeRestore = appState.timeRestore;
+ const onSave = ({
+ newTitle,
+ newDescription,
+ newCopyOnSave,
+ newTimeRestore,
+ isTitleDuplicateConfirmed,
+ onTitleDuplicate,
+ }: {
+ newTitle: string;
+ newDescription: string;
+ newCopyOnSave: boolean;
+ newTimeRestore: boolean;
+ isTitleDuplicateConfirmed: boolean;
+ onTitleDuplicate: () => void;
+ }) => {
+ stateContainer.transitions.set('title', newTitle);
+ stateContainer.transitions.set('description', newDescription);
+ stateContainer.transitions.set('timeRestore', newTimeRestore);
+ // dashboardStateManager.savedDashboard.copyOnSave = newCopyOnSave;
+
+ const saveOptions = {
+ confirmOverwrite: false,
+ isTitleDuplicateConfirmed,
+ onTitleDuplicate,
+ };
+ return save(saveOptions).then((response: SaveResult) => {
+ // If the save wasn't successful, put the original values back.
+ if (!(response as { id: string }).id) {
+ stateContainer.transitions.set('title', currentTitle);
+ stateContainer.transitions.set('description', currentDescription);
+ stateContainer.transitions.set('timeRestore', currentTimeRestore);
+ }
+ return response;
+ });
+ };
+
+ const dashboardSaveModal = (
+ {}}
+ title={currentTitle}
+ description={currentDescription}
+ timeRestore={currentTimeRestore}
+ showCopyOnSave={savedDashboard.id ? true : false}
+ />
+ );
+ showSaveModal(dashboardSaveModal, I18nContext);
+ };
+ navActions[TopNavIds.CLONE] = () => {
+ const currentTitle = appState.title;
+ const onClone = (
+ newTitle: string,
+ isTitleDuplicateConfirmed: boolean,
+ onTitleDuplicate: () => void
+ ) => {
+ savedDashboard.copyOnSave = true;
+ stateContainer.transitions.set('title', newTitle);
+ const saveOptions = {
+ confirmOverwrite: false,
+ isTitleDuplicateConfirmed,
+ onTitleDuplicate,
+ };
+ return save(saveOptions).then((response: { id?: string } | { error: Error }) => {
+ // If the save wasn't successful, put the original title back.
+ if ((response as { error: Error }).error) {
+ stateContainer.transitions.set('title', currentTitle);
+ }
+ // updateNavBar();
+ return response;
+ });
+ };
+
+ showCloneModal(onClone, currentTitle);
+ };
+
+ navActions[TopNavIds.ADD_EXISTING] = () => {
+ if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) {
+ openAddPanelFlyout({
+ embeddable: dashboardContainer,
+ getAllFactories: embeddable.getEmbeddableFactories,
+ getFactory: embeddable.getEmbeddableFactory,
+ notifications,
+ overlays,
+ SavedObjectFinder: getSavedObjectFinder(savedObjects, uiSettings),
+ });
+ }
+ };
+
+ navActions[TopNavIds.VISUALIZE] = async () => {
+ const type = 'visualization';
+ const factory = embeddable.getEmbeddableFactory(type);
+ if (!factory) {
+ throw new EmbeddableFactoryNotFoundError(type);
+ }
+ await factory.create({} as EmbeddableInput, dashboardContainer);
+ };
+
+ navActions[TopNavIds.OPTIONS] = (anchorElement) => {
+ showOptionsPopover({
+ anchorElement,
+ useMargins: appState.options.useMargins === undefined ? false : appState.options.useMargins,
+ onUseMarginsChange: (isChecked: boolean) => {
+ stateContainer.transitions.setOption('useMargins', isChecked);
+ },
+ hidePanelTitles: appState.options.hidePanelTitles,
+ onHidePanelTitlesChange: (isChecked: boolean) => {
+ stateContainer.transitions.setOption('hidePanelTitles', isChecked);
+ },
+ });
+ };
+
+ if (share) {
+ // the share button is only availabale if "share" plugin contract enabled
+ navActions[TopNavIds.SHARE] = (anchorElement) => {
+ const EmbedUrlParamExtension = ({
+ setParamValue,
+ }: {
+ setParamValue: (paramUpdate: UrlParamValues) => void;
+ }): ReactElement => {
+ const [urlParamsSelectedMap, setUrlParamsSelectedMap] = useState({
+ [UrlParams.SHOW_TOP_MENU]: false,
+ [UrlParams.SHOW_QUERY_INPUT]: false,
+ [UrlParams.SHOW_TIME_FILTER]: false,
+ [UrlParams.SHOW_FILTER_BAR]: true,
+ });
+
+ const checkboxes = [
+ {
+ id: UrlParams.SHOW_TOP_MENU,
+ label: i18n.translate('dashboard.embedUrlParamExtension.topMenu', {
+ defaultMessage: 'Top menu',
+ }),
+ },
+ {
+ id: UrlParams.SHOW_QUERY_INPUT,
+ label: i18n.translate('dashboard.embedUrlParamExtension.query', {
+ defaultMessage: 'Query',
+ }),
+ },
+ {
+ id: UrlParams.SHOW_TIME_FILTER,
+ label: i18n.translate('dashboard.embedUrlParamExtension.timeFilter', {
+ defaultMessage: 'Time filter',
+ }),
+ },
+ {
+ id: UrlParams.SHOW_FILTER_BAR,
+ label: i18n.translate('dashboard.embedUrlParamExtension.filterBar', {
+ defaultMessage: 'Filter bar',
+ }),
+ },
+ ];
+
+ const handleChange = (param: string): void => {
+ const urlParamsSelectedMapUpdate = {
+ ...urlParamsSelectedMap,
+ [param]: !urlParamsSelectedMap[param as keyof UrlParamsSelectedMap],
+ };
+ setUrlParamsSelectedMap(urlParamsSelectedMapUpdate);
+
+ const urlParamValues = {
+ [UrlParams.SHOW_TOP_MENU]: urlParamsSelectedMap[UrlParams.SHOW_TOP_MENU],
+ [UrlParams.SHOW_QUERY_INPUT]: urlParamsSelectedMap[UrlParams.SHOW_QUERY_INPUT],
+ [UrlParams.SHOW_TIME_FILTER]: urlParamsSelectedMap[UrlParams.SHOW_TIME_FILTER],
+ [UrlParams.HIDE_FILTER_BAR]: !urlParamsSelectedMap[UrlParams.SHOW_FILTER_BAR],
+ [param === UrlParams.SHOW_FILTER_BAR ? UrlParams.HIDE_FILTER_BAR : param]:
+ param === UrlParams.SHOW_FILTER_BAR
+ ? urlParamsSelectedMap[UrlParams.SHOW_FILTER_BAR]
+ : !urlParamsSelectedMap[param as keyof UrlParamsSelectedMap],
+ };
+ setParamValue(urlParamValues);
+ };
+
+ return (
+
+ );
+ };
+
+ share.toggleShareContextMenu({
+ anchorElement,
+ allowEmbed: true,
+ allowShortUrl:
+ !dashboardConfig.getHideWriteControls() || dashboardCapabilities.createShortUrl,
+ shareableUrl: unhashUrl(window.location.href),
+ objectId: savedDashboard.id,
+ objectType: 'dashboard',
+ sharingData: {
+ title: savedDashboard.title,
+ },
+ isDirty: false, // TODO
+ embedUrlParamExtensions: [
+ {
+ paramName: 'embed',
+ component: EmbedUrlParamExtension,
+ },
+ ],
+ });
+ };
+ }
+
+ function onChangeViewMode(newMode: ViewMode) {
+ const isPageRefresh = newMode === appState.viewMode;
+ const isLeavingEditMode = !isPageRefresh && newMode === ViewMode.VIEW;
+ // TODO: check if any query and filter changed
+ const willLoseChanges = isLeavingEditMode;
+
+ if (!willLoseChanges) {
+ stateContainer.transitions.set('viewMode', newMode);
+ return;
+ }
+
+ function revertChangesAndExitEditMode() {
+ stateContainer.transitions.set('viewMode', ViewMode.VIEW);
+ const pathname = savedDashboard.id
+ ? createDashboardEditUrl(savedDashboard.id)
+ : DashboardConstants.CREATE_NEW_DASHBOARD_URL;
+ history.push(pathname);
+
+ /* dashboardStateManager.resetState();
+ // This is only necessary for new dashboards, which will default to Edit mode.
+ updateViewMode(ViewMode.VIEW);
+
+ // We need to do a hard reset of the timepicker. appState will not reload like
+ // it does on 'open' because it's been saved to the url and the getAppState.previouslyStored() check on
+ // reload will cause it not to sync.
+ if (dashboardStateManager.getIsTimeSavedWithDashboard()) {
+ dashboardStateManager.syncTimefilterWithDashboardTime(timefilter);
+ dashboardStateManager.syncTimefilterWithDashboardRefreshInterval(timefilter);
+ }
+
+ // Angular's $location skips this update because of history updates from syncState which happen simultaneously
+ // when calling osdUrl.change() angular schedules url update and when angular finally starts to process it,
+ // the update is considered outdated and angular skips it
+ // so have to use implementation of dashboardStateManager.changeDashboardUrl, which workarounds those issues
+ dashboardStateManager.changeDashboardUrl(
+ dash.id ? createDashboardEditUrl(dash.id) : DashboardConstants.CREATE_NEW_DASHBOARD_URL
+ );*/
+ }
+
+ overlays
+ .openConfirm(
+ i18n.translate('dashboard.changeViewModeConfirmModal.discardChangesDescription', {
+ defaultMessage: `Once you discard your changes, there's no getting them back.`,
+ }),
+ {
+ confirmButtonText: i18n.translate(
+ 'dashboard.changeViewModeConfirmModal.confirmButtonLabel',
+ { defaultMessage: 'Discard changes' }
+ ),
+ cancelButtonText: i18n.translate(
+ 'dashboard.changeViewModeConfirmModal.cancelButtonLabel',
+ { defaultMessage: 'Continue editing' }
+ ),
+ defaultFocusedButton: EUI_MODAL_CANCEL_BUTTON,
+ title: i18n.translate('dashboard.changeViewModeConfirmModal.discardChangesTitle', {
+ defaultMessage: 'Discard changes to dashboard?',
+ }),
+ }
+ )
+ .then((isConfirmed) => {
+ if (isConfirmed) {
+ revertChangesAndExitEditMode();
+ }
+ });
+
+ // updateNavBar();
+ }
+
+ async function save(saveOptions: SavedObjectSaveOpts) {
+ const timefilter = queryService.timefilter.timefilter;
+ try {
+ const id = await saveDashboard(timefilter, stateContainer, savedDashboard, saveOptions);
+
+ if (id) {
+ notifications.toasts.addSuccess({
+ title: i18n.translate('dashboard.dashboardWasSavedSuccessMessage', {
+ defaultMessage: `Dashboard '{dashTitle}' was saved`,
+ values: { dashTitle: savedDashboard.title },
+ }),
+ 'data-test-subj': 'saveDashboardSuccess',
+ });
+
+ const appPath = `${createDashboardEditUrl(id)}`;
+
+ // Manually insert a new url so the back button will open the saved visualization.
+ history.replace(appPath);
+ // setActiveUrl(appPath);
+ chrome.docTitle.change(savedDashboard.lastSavedTitle);
+ stateContainer.transitions.set('viewMode', ViewMode.VIEW);
+ }
+ return { id };
+ } catch (error) {
+ // eslint-disable-next-line
+ console.error(error);
+ notifications.toasts.addDanger({
+ title: i18n.translate('dashboard.dashboardWasNotSavedDangerMessage', {
+ defaultMessage: `Dashboard '{dashTitle}' was not saved. Error: {errorMessage}`,
+ values: {
+ dashTitle: savedDashboard.title,
+ errorMessage: savedDashboard.message,
+ },
+ }),
+ 'data-test-subj': 'saveDashboardFailure',
+ });
+ return { error };
+ }
+ }
+
+ return navActions;
+};
diff --git a/src/plugins/dashboard/public/application/utils/use/use_dashboard_app_state.tsx b/src/plugins/dashboard/public/application/utils/use/use_dashboard_app_state.tsx
index e14e790125f0..464febb05c60 100644
--- a/src/plugins/dashboard/public/application/utils/use/use_dashboard_app_state.tsx
+++ b/src/plugins/dashboard/public/application/utils/use/use_dashboard_app_state.tsx
@@ -24,64 +24,64 @@ export const useDashboardAppState = (
eventEmitter: EventEmitter,
instance: any
) => {
- const [appState, setAppState] = useState(null);
+ const [appState, setAppState] = useState();
useEffect(() => {
- if (!instance) {
- return;
- }
- const { dashboardConfig, usageCollection, opensearchDashboardsVersion } = services;
- const hideWriteControls = dashboardConfig.getHideWriteControls();
- const stateDefaults = migrateAppState(
- getAppStateDefaults(instance, hideWriteControls),
- opensearchDashboardsVersion,
- usageCollection
- );
+ if (instance) {
+ // const savedDashboardInstance = ('savedDashboardInstance' in instance)
+ const { dashboardConfig, usageCollection, opensearchDashboardsVersion } = services;
+ const hideWriteControls = dashboardConfig.getHideWriteControls();
+ const stateDefaults = migrateAppState(
+ getAppStateDefaults(instance, hideWriteControls),
+ opensearchDashboardsVersion,
+ usageCollection
+ );
- const { stateContainer, stopStateSync } = createDashboardAppState({
- stateDefaults,
- osdUrlStateStorage: services.osdUrlStateStorage,
- services,
- instance,
- });
+ const { stateContainer, stopStateSync } = createDashboardAppState({
+ stateDefaults,
+ osdUrlStateStorage: services.osdUrlStateStorage,
+ services,
+ instance,
+ });
- const { filterManager, queryString } = services.data.query;
+ const { filterManager, queryString } = services.data.query;
- // sync initial app state from state container to managers
- filterManager.setAppFilters(cloneDeep(stateContainer.getState().filters));
- queryString.setQuery(migrateLegacyQuery(stateContainer.getState().query));
+ // sync initial app state from state container to managers
+ filterManager.setAppFilters(cloneDeep(stateContainer.getState().filters));
+ queryString.setQuery(migrateLegacyQuery(stateContainer.getState().query));
- // setup syncing of app filters between app state and query services
- const stopSyncingAppFilters = connectToQueryState(
- services.data.query,
- {
- set: ({ filters, query }) => {
- stateContainer.transitions.set('filters', filters || []);
- stateContainer.transitions.set('query', query || queryString.getDefaultQuery());
+ // setup syncing of app filters between app state and query services
+ const stopSyncingAppFilters = connectToQueryState(
+ services.data.query,
+ {
+ set: ({ filters, query }) => {
+ stateContainer.transitions.set('filters', filters || []);
+ stateContainer.transitions.set('query', query || queryString.getDefaultQuery());
+ },
+ get: () => ({
+ filters: stateContainer.getState().filters,
+ query: migrateLegacyQuery(stateContainer.getState().query),
+ }),
+ state$: stateContainer.state$.pipe(
+ map((state) => ({
+ filters: state.filters,
+ query: queryString.formatQuery(state.query),
+ }))
+ ),
},
- get: () => ({
- filters: stateContainer.getState().filters,
- query: migrateLegacyQuery(stateContainer.getState().query),
- }),
- state$: stateContainer.state$.pipe(
- map((state) => ({
- filters: state.filters,
- query: queryString.formatQuery(state.query),
- }))
- ),
- },
- {
- filters: opensearchFilters.FilterStateStore.APP_STATE,
- query: true,
- }
- );
+ {
+ filters: opensearchFilters.FilterStateStore.APP_STATE,
+ query: true,
+ }
+ );
- setAppState(stateContainer);
+ setAppState(stateContainer);
- return () => {
- stopStateSync();
- stopSyncingAppFilters();
- };
+ return () => {
+ stopStateSync();
+ stopSyncingAppFilters();
+ };
+ }
}, [eventEmitter, instance, services]);
return { appState };
diff --git a/src/plugins/dashboard/public/application/utils/use/use_dashboard_container.tsx b/src/plugins/dashboard/public/application/utils/use/use_dashboard_container.tsx
new file mode 100644
index 000000000000..f8a5136c494b
--- /dev/null
+++ b/src/plugins/dashboard/public/application/utils/use/use_dashboard_container.tsx
@@ -0,0 +1,326 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { useState } from 'react';
+import { EMPTY, Subscription, merge } from 'rxjs';
+import { catchError, distinctUntilChanged, map, mapTo, startWith, switchMap } from 'rxjs/operators';
+import deepEqual from 'fast-deep-equal';
+import { EventEmitter } from 'stream';
+import { useEffect } from 'react';
+import { opensearchFilters } from '../../../../../data/public';
+import {
+ DASHBOARD_CONTAINER_TYPE,
+ DashboardContainer,
+ DashboardContainerInput,
+ DashboardPanelState,
+} from '../../embeddable';
+import {
+ ContainerOutput,
+ ErrorEmbeddable,
+ ViewMode,
+ isErrorEmbeddable,
+} from '../../../embeddable_plugin';
+import {
+ convertPanelStateToSavedDashboardPanel,
+ convertSavedDashboardPanelToPanelState,
+} from '../../lib/embeddable_saved_object_converters';
+import { DashboardEmptyScreen, DashboardEmptyScreenProps } from '../../dashboard_empty_screen';
+import { DashboardAppStateContainer, DashboardServices, SavedDashboardPanel } from '../../../types';
+import { migrateLegacyQuery } from '../../lib/migrate_legacy_query';
+
+export const useDashboardContainer = (
+ services: DashboardServices,
+ isChromeVisible: boolean,
+ eventEmitter: EventEmitter,
+ dashboardDom: HTMLElement | null,
+ savedDashboardInstance?: any,
+ appState?: DashboardAppStateContainer
+) => {
+ const [dashboardContainer, setDashboardContainer] = useState();
+
+ useEffect(() => {
+ const getDashboardContainer = async () => {
+ try {
+ if (savedDashboardInstance && appState) {
+ let dashboardContainerEmbeddable: DashboardContainer | undefined;
+ try {
+ dashboardContainerEmbeddable = await createDashboardEmbeddable(
+ savedDashboardInstance,
+ services,
+ appState
+ );
+ } catch (error) {
+ console.log(error);
+ }
+ setDashboardContainer(dashboardContainerEmbeddable);
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ };
+
+ getDashboardContainer();
+ }, [appState, dashboardDom, eventEmitter, isChromeVisible, savedDashboardInstance, services]);
+
+ return { dashboardContainer };
+};
+
+const createDashboardEmbeddable = async (
+ savedDash: any,
+ dashboardServices: DashboardServices,
+ appState: DashboardAppStateContainer
+) => {
+ let dashboardContainer: DashboardContainer;
+ let inputSubscription: Subscription | undefined;
+ let outputSubscription: Subscription | undefined;
+
+ const {
+ embeddable,
+ data,
+ uiSettings,
+ http,
+ dashboardConfig,
+ embeddableCapabilities,
+ } = dashboardServices;
+ const { query: queryService } = data;
+ const filterManager = queryService.filterManager;
+ const timefilter = queryService.timefilter.timefilter;
+ const queryStringManager = queryService.queryString;
+ const { visualizeCapabilities, mapsCapabilities } = embeddableCapabilities;
+ // const dashboardDom = document.getElementById('dashboardViewport');
+ const dashboardFactory = embeddable.getEmbeddableFactory<
+ DashboardContainerInput,
+ ContainerOutput,
+ DashboardContainer
+ >(DASHBOARD_CONTAINER_TYPE);
+
+ const getShouldShowEditHelp = () => {
+ return (
+ !savedDash.panels.length &&
+ savedDash.viewMode === ViewMode.EDIT &&
+ !dashboardConfig.getHideWriteControls()
+ );
+ };
+
+ const getShouldShowViewHelp = () => {
+ return (
+ !savedDash.panels.length &&
+ savedDash.viewMode === ViewMode.VIEW &&
+ !dashboardConfig.getHideWriteControls()
+ );
+ };
+
+ const shouldShowUnauthorizedEmptyState = () => {
+ const readonlyMode =
+ !savedDash.panels.length &&
+ !getShouldShowEditHelp() &&
+ !getShouldShowViewHelp() &&
+ dashboardConfig.getHideWriteControls();
+ const userHasNoPermissions =
+ !savedDash.panels.length && !visualizeCapabilities.save && !mapsCapabilities.save;
+ return readonlyMode || userHasNoPermissions;
+ };
+
+ const getEmptyScreenProps = (
+ shouldShowEditHelp: boolean,
+ isEmptyInReadOnlyMode: boolean
+ ): DashboardEmptyScreenProps => {
+ const emptyScreenProps: DashboardEmptyScreenProps = {
+ onLinkClick: () => {}, // TODO
+ showLinkToVisualize: shouldShowEditHelp,
+ uiSettings,
+ http,
+ };
+ if (shouldShowEditHelp) {
+ emptyScreenProps.onVisualizeClick = () => {
+ alert('click'); // TODO
+ };
+ }
+ if (isEmptyInReadOnlyMode) {
+ emptyScreenProps.isReadonlyMode = true;
+ }
+ return emptyScreenProps;
+ };
+
+ const getDashboardInput = () => {
+ const appStateData = appState.getState();
+ const embeddablesMap: {
+ [key: string]: DashboardPanelState;
+ } = {};
+ appStateData.panels.forEach((panel: SavedDashboardPanel) => {
+ embeddablesMap[panel.panelIndex] = convertSavedDashboardPanelToPanelState(panel);
+ });
+
+ const lastReloadRequestTime = 0;
+ return {
+ id: savedDash.id || '',
+ filters: data.query.filterManager.getFilters(),
+ hidePanelTitles: appStateData.options.hidePanelTitles,
+ query: savedDash.query,
+ timeRange: data.query.timefilter.timefilter.getTime(),
+ refreshConfig: data.query.timefilter.timefilter.getRefreshInterval(),
+ viewMode: appStateData.viewMode,
+ panels: embeddablesMap,
+ isFullScreenMode: appStateData.fullScreenMode,
+ isEmbeddedExternally: false, // TODO
+ // isEmptyState: shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadonlyMode,
+ isEmptyState: false, // TODO
+ useMargins: appStateData.options.useMargins,
+ lastReloadRequestTime, // TODO
+ title: appStateData.title,
+ description: appStateData.description,
+ expandedPanelId: appStateData.expandedPanelId,
+ };
+ };
+
+ if (dashboardFactory) {
+ return dashboardFactory
+ .create(getDashboardInput())
+ .then((container: DashboardContainer | ErrorEmbeddable | undefined) => {
+ if (container && !isErrorEmbeddable(container)) {
+ dashboardContainer = container;
+
+ dashboardContainer.renderEmpty = () => {
+ const shouldShowEditHelp = getShouldShowEditHelp();
+ const shouldShowViewHelp = getShouldShowViewHelp();
+ const isEmptyInReadOnlyMode = shouldShowUnauthorizedEmptyState();
+ const isEmptyState = shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadOnlyMode;
+ return isEmptyState ? (
+
+ ) : null;
+ };
+
+ // TODO: handle dashboard container input and output subsciptions
+ // issue:
+ outputSubscription = merge(
+ // output of dashboard container itself
+ dashboardContainer.getOutput$(),
+ // plus output of dashboard container children,
+ // children may change, so make sure we subscribe/unsubscribe with switchMap
+ dashboardContainer.getOutput$().pipe(
+ map(() => dashboardContainer!.getChildIds()),
+ distinctUntilChanged(deepEqual),
+ switchMap((newChildIds: string[]) =>
+ merge(
+ ...newChildIds.map((childId) =>
+ dashboardContainer!
+ .getChild(childId)
+ .getOutput$()
+ .pipe(catchError(() => EMPTY))
+ )
+ )
+ )
+ )
+ )
+ .pipe(
+ mapTo(dashboardContainer),
+ startWith(dashboardContainer) // to trigger initial index pattern update
+ // updateIndexPatternsOperator //TODO
+ )
+ .subscribe();
+
+ inputSubscription = dashboardContainer.getInput$().subscribe((foo) => {
+ // This has to be first because handleDashboardContainerChanges causes
+ // appState.save which will cause refreshDashboardContainer to be called.
+
+ if (
+ !opensearchFilters.compareFilters(
+ container.getInput().filters,
+ filterManager.getFilters(),
+ opensearchFilters.COMPARE_ALL_OPTIONS
+ )
+ ) {
+ // Add filters modifies the object passed to it, hence the clone deep.
+ filterManager.addFilters(_.cloneDeep(container.getInput().filters));
+
+ /* dashboardStateManager.applyFilters(
+ $scope.model.query,
+ container.getInput().filters
+ );*/
+
+ appState.transitions.set('query', queryStringManager.getQuery());
+ }
+ // triggered when dashboard embeddable container has changes, and update the appState
+ // handleDashboardContainerChanges(container, appState, dashboardServices);
+ });
+ return dashboardContainer;
+ }
+ });
+ }
+ return undefined;
+};
+
+const handleDashboardContainerChanges = (
+ dashboardContainer: DashboardContainer,
+ appState: DashboardAppStateContainer,
+ dashboardServices: DashboardServices
+) => {
+ let dirty = false;
+ let dirtyBecauseOfInitialStateMigration = false;
+
+ const appStateData = appState.getState();
+ const savedDashboardPanelMap: { [key: string]: SavedDashboardPanel } = {};
+ const { opensearchDashboardsVersion } = dashboardServices;
+
+ const input = dashboardContainer.getInput();
+ appStateData.panels.forEach((savedDashboardPanel) => {
+ if (input.panels[savedDashboardPanel.panelIndex] !== undefined) {
+ savedDashboardPanelMap[savedDashboardPanel.panelIndex] = savedDashboardPanel;
+ } else {
+ // A panel was deleted.
+ dirty = true;
+ }
+ });
+
+ const convertedPanelStateMap: { [key: string]: SavedDashboardPanel } = {};
+
+ Object.values(input.panels).forEach((panelState) => {
+ if (savedDashboardPanelMap[panelState.explicitInput.id] === undefined) {
+ dirty = true;
+ }
+
+ convertedPanelStateMap[panelState.explicitInput.id] = convertPanelStateToSavedDashboardPanel(
+ panelState,
+ opensearchDashboardsVersion
+ );
+
+ if (
+ !_.isEqual(
+ convertedPanelStateMap[panelState.explicitInput.id],
+ savedDashboardPanelMap[panelState.explicitInput.id]
+ )
+ ) {
+ // A panel was changed
+ dirty = true;
+
+ const oldVersion = savedDashboardPanelMap[panelState.explicitInput.id]?.version;
+ const newVersion = convertedPanelStateMap[panelState.explicitInput.id]?.version;
+ if (oldVersion && newVersion && oldVersion !== newVersion) {
+ dirtyBecauseOfInitialStateMigration = true;
+ }
+ }
+ });
+
+ if (dirty) {
+ appState.transitions.set('panels', Object.values(convertedPanelStateMap));
+ if (dirtyBecauseOfInitialStateMigration) {
+ // this.saveState({ replace: true });
+ }
+ }
+
+ if (input.isFullScreenMode !== appStateData.fullScreenMode) {
+ appState.transitions.set('fullScreenMode', input.isFullScreenMode);
+ }
+
+ if (input.expandedPanelId !== appStateData.expandedPanelId) {
+ appState.transitions.set('expandedPanelId', input.expandedPanelId);
+ }
+
+ if (!_.isEqual(input.query, migrateLegacyQuery(appState.get().query))) {
+ appState.transitions.set('query', input.query);
+ }
+};
diff --git a/src/plugins/dashboard/public/application/utils/use/use_editor_updates.ts b/src/plugins/dashboard/public/application/utils/use/use_editor_updates.ts
new file mode 100644
index 000000000000..60dfb9ba927a
--- /dev/null
+++ b/src/plugins/dashboard/public/application/utils/use/use_editor_updates.ts
@@ -0,0 +1,66 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import EventEmitter from 'events';
+import { useEffect, useState } from 'react';
+import { DashboardAppState, DashboardAppStateContainer, DashboardServices } from '../../../types';
+import { DashboardContainer } from '../../embeddable';
+
+export const useEditorUpdates = (
+ services: DashboardServices,
+ eventEmitter: EventEmitter,
+ dashboardInstance?: any,
+ dashboardContainer?: DashboardContainer,
+ appState?: DashboardAppStateContainer
+) => {
+ const [isEmbeddableRendered, setIsEmbeddableRendered] = useState(false);
+ const [currentAppState, setCurrentAppState] = useState();
+ const dom = document.getElementById('dashboardViewport');
+
+ const {
+ timefilter: { timefilter },
+ filterManager,
+ queryString,
+ state$,
+ } = services.data.query;
+
+ useEffect(() => {
+ if (appState && dashboardInstance && dashboardContainer) {
+ const initialState = appState.getState();
+ setCurrentAppState(initialState);
+
+ const unsubscribeStateUpdates = appState.subscribe((state) => {
+ setCurrentAppState(state);
+ dashboardContainer.reload();
+ });
+
+ return () => {
+ unsubscribeStateUpdates();
+ };
+ }
+ }, [
+ appState,
+ eventEmitter,
+ dashboardInstance,
+ services,
+ dashboardContainer,
+ isEmbeddableRendered,
+ currentAppState,
+ ]);
+
+ useEffect(() => {
+ if (!dom || !dashboardContainer) {
+ return;
+ }
+ dashboardContainer.render(dom);
+ setIsEmbeddableRendered(true);
+
+ return () => {
+ setIsEmbeddableRendered(false);
+ };
+ }, [appState, dashboardInstance, currentAppState, dashboardContainer, state$, dom]);
+
+ return { isEmbeddableRendered, currentAppState };
+};
diff --git a/src/plugins/dashboard/public/application/utils/use/use_saved_dashboard_instance.ts b/src/plugins/dashboard/public/application/utils/use/use_saved_dashboard_instance.ts
index e7e1633ac41b..d8336d74fc1f 100644
--- a/src/plugins/dashboard/public/application/utils/use/use_saved_dashboard_instance.ts
+++ b/src/plugins/dashboard/public/application/utils/use/use_saved_dashboard_instance.ts
@@ -23,28 +23,26 @@ export const useSavedDashboardInstance = (
isChromeVisible: boolean | undefined,
dashboardIdFromUrl: string | undefined
) => {
- const [state, setState] = useState<{
- savedDashboardInstance?: any;
- }>({});
-
+ const [savedDashboardInstance, setSavedDashboardInstance] = useState();
const dashboardId = useRef('');
useEffect(() => {
+ const {
+ application: { navigateToApp },
+ chrome,
+ history,
+ http: { basePath },
+ notifications,
+ savedDashboards,
+ } = services;
+
const getSavedDashboardInstance = async () => {
- const {
- application: { navigateToApp },
- chrome,
- history,
- http: { basePath },
- notifications,
- savedDashboards,
- } = services;
try {
console.log('trying to get saved dashboard');
- let savedDashboardInstance: any;
+ let savedDashboard: any;
if (history.location.pathname === '/create') {
try {
- savedDashboardInstance = await savedDashboards.get();
+ savedDashboard = await savedDashboards.get();
} catch {
redirectWhenMissing({
history,
@@ -58,13 +56,13 @@ export const useSavedDashboardInstance = (
}
} else if (dashboardIdFromUrl) {
try {
- savedDashboardInstance = await savedDashboards.get(dashboardIdFromUrl);
+ savedDashboard = await savedDashboards.get(dashboardIdFromUrl);
chrome.recentlyAccessed.add(
- savedDashboardInstance.getFullPath(),
- savedDashboardInstance.title,
+ savedDashboard.getFullPath(),
+ savedDashboard.title,
dashboardIdFromUrl
);
- console.log('saved dashboard', savedDashboardInstance);
+ console.log('saved dashboard', savedDashboard);
} catch (error) {
// Preserve BWC of v5.3.0 links for new, unsaved dashboards.
// See https://github.com/elastic/kibana/issues/10951 for more context.
@@ -91,7 +89,7 @@ export const useSavedDashboardInstance = (
}
}
- setState({ savedDashboardInstance });
+ setSavedDashboardInstance(savedDashboard);
} catch (error) {}
};
@@ -106,15 +104,13 @@ export const useSavedDashboardInstance = (
} else if (
dashboardIdFromUrl &&
dashboardId.current !== dashboardIdFromUrl &&
- state.savedDashboardInstance?.id !== dashboardIdFromUrl
+ savedDashboardInstance?.id !== dashboardIdFromUrl
) {
dashboardId.current = dashboardIdFromUrl;
- setState({});
+ setSavedDashboardInstance({});
getSavedDashboardInstance();
}
- }, [eventEmitter, isChromeVisible, services, state.savedDashboardInstance, dashboardIdFromUrl]);
+ }, [eventEmitter, isChromeVisible, services, savedDashboardInstance, dashboardIdFromUrl]);
- return {
- ...state,
- };
+ return savedDashboardInstance;
};
diff --git a/src/plugins/dashboard/public/types.ts b/src/plugins/dashboard/public/types.ts
index 8a94a424b940..79160ac5de7a 100644
--- a/src/plugins/dashboard/public/types.ts
+++ b/src/plugins/dashboard/public/types.ts
@@ -254,7 +254,7 @@ export interface DashboardServices extends CoreStart {
savedDashboards: SavedObjectLoader;
dashboardProviders: () => { [key: string]: DashboardProvider } | undefined;
dashboardConfig: OpenSearchDashboardsLegacyStart['dashboardConfig'];
- dashboardCapabilities: DashboardCapabilities;
+ dashboardCapabilities: any;
embeddableCapabilities: {
visualizeCapabilities: any;
mapsCapabilities: any;
@@ -274,3 +274,8 @@ export interface DashboardServices extends CoreStart {
restorePreviousUrl: () => void;
addBasePath?: (url: string) => string;
}
+
+export interface DashEditorController {
+ render(props: any): void;
+ destroy(): void;
+}
diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable.ts b/src/plugins/discover/public/application/embeddable/search_embeddable.ts
index 933f807f8153..3c0d926cc03d 100644
--- a/src/plugins/discover/public/application/embeddable/search_embeddable.ts
+++ b/src/plugins/discover/public/application/embeddable/search_embeddable.ts
@@ -175,7 +175,8 @@ export class SearchEmbeddable
*/
public render(domNode: HTMLElement) {
if (!this.searchScope) {
- throw new Error('Search scope not defined');
+ // throw new Error('Search scope not defined');
+ return;
}
this.searchInstance = this.$compile(searchTemplate)(this.searchScope);
const rootNode = angular.element(domNode);
diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts
index 2cece186b33a..8ef942cef43c 100644
--- a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts
+++ b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts
@@ -79,38 +79,6 @@ export const getSavedObjects = (): SavedObject[] => [
},
references: [],
},
- {
- id: '571aaf70-4c88-11e8-b3d7-01146121b73d',
- type: 'search',
- updated_at: '2018-05-09T15:49:03.736Z',
- version: '1',
- migrationVersion: {},
- attributes: {
- title: i18n.translate('home.sampleData.flightsSpec.flightLogTitle', {
- defaultMessage: '[Flights] Flight Log',
- }),
- description: '',
- hits: 0,
- columns: [
- 'Carrier',
- 'OriginCityName',
- 'OriginCountry',
- 'DestCityName',
- 'DestCountry',
- 'FlightTimeMin',
- 'AvgTicketPrice',
- 'Cancelled',
- 'FlightDelayType',
- ],
- sort: [['timestamp', 'desc']],
- version: 1,
- kibanaSavedObjectMeta: {
- searchSourceJSON:
- '{"index":"d3d7af60-4c81-11e8-b3d7-01146121b73d","highlightAll":true,"version":true,"query":{"language":"kuery","query":""},"filter":[]}',
- },
- },
- references: [],
- },
{
id: '8f4d0c00-4c86-11e8-b3d7-01146121b73d',
type: 'visualization',
@@ -472,11 +440,6 @@ export const getSavedObjects = (): SavedObject[] => [
type: 'visualization',
id: 'c8fc3d30-4c87-11e8-b3d7-01146121b73d',
},
- {
- name: 'panel_2',
- type: 'search',
- id: '571aaf70-4c88-11e8-b3d7-01146121b73d',
- },
{
name: 'panel_3',
type: 'visualization',
@@ -569,7 +532,7 @@ export const getSavedObjects = (): SavedObject[] => [
}
),
panelsJSON:
- '[{"panelIndex":"1","gridData":{"x":0,"y":0,"w":32,"h":7,"i":"1"},"embeddableConfig":{},"version":"6.3.0","panelRefName":"panel_0"},{"panelIndex":"3","gridData":{"x":17,"y":7,"w":23,"h":12,"i":"3"},"embeddableConfig":{"vis":{"colors":{"Average Ticket Price":"#0A50A1","Flight Count":"#82B5D8"},"legendOpen":false}},"version":"6.3.0","panelRefName":"panel_1"},{"panelIndex":"4","gridData":{"x":0,"y":85,"w":48,"h":15,"i":"4"},"embeddableConfig":{},"version":"6.3.0","panelRefName":"panel_2"},{"panelIndex":"5","gridData":{"x":0,"y":7,"w":17,"h":12,"i":"5"},"embeddableConfig":{"vis":{"colors":{"OpenSearch-Air":"#447EBC","BeatsWest":"#65C5DB","OpenSearch Dashboards Airlines":"#BA43A9","Logstash Airways":"#E5AC0E"},"legendOpen":false}},"version":"6.3.0","panelRefName":"panel_3"},{"panelIndex":"6","gridData":{"x":24,"y":33,"w":24,"h":14,"i":"6"},"embeddableConfig":{"vis":{"colors":{"Carrier Delay":"#5195CE","Late Aircraft Delay":"#1F78C1","NAS Delay":"#70DBED","No Delay":"#BADFF4","Security Delay":"#052B51","Weather Delay":"#6ED0E0"}}},"version":"6.3.0","panelRefName":"panel_4"},{"panelIndex":"7","gridData":{"x":24,"y":19,"w":24,"h":14,"i":"7"},"embeddableConfig":{},"version":"6.3.0","panelRefName":"panel_5"},{"panelIndex":"10","gridData":{"x":0,"y":35,"w":24,"h":12,"i":"10"},"embeddableConfig":{"vis":{"colors":{"Count":"#1F78C1"},"legendOpen":false}},"version":"6.3.0","panelRefName":"panel_6"},{"panelIndex":"13","gridData":{"x":10,"y":19,"w":14,"h":8,"i":"13"},"embeddableConfig":{"vis":{"colors":{"Count":"#1F78C1"},"legendOpen":false}},"version":"6.3.0","panelRefName":"panel_7"},{"panelIndex":"14","gridData":{"x":10,"y":27,"w":14,"h":8,"i":"14"},"embeddableConfig":{"vis":{"colors":{"Count":"#1F78C1"},"legendOpen":false}},"version":"6.3.0","panelRefName":"panel_8"},{"panelIndex":"21","gridData":{"x":0,"y":62,"w":48,"h":8,"i":"21"},"embeddableConfig":{},"version":"6.3.0","panelRefName":"panel_10"},{"panelIndex":"22","gridData":{"x":32,"y":0,"w":16,"h":7,"i":"22"},"embeddableConfig":{},"version":"6.3.0","panelRefName":"panel_11"},{"panelIndex":"23","gridData":{"x":0,"y":70,"w":48,"h":15,"i":"23"},"embeddableConfig":{"mapCenter":[42.19556096274418,9.536742995308601e-7],"mapZoom":1},"version":"6.3.0","panelRefName":"panel_12"},{"panelIndex":"25","gridData":{"x":0,"y":19,"w":10,"h":8,"i":"25"},"embeddableConfig":{"vis":{"defaultColors":{"0 - 50":"rgb(247,251,255)","100 - 150":"rgb(107,174,214)","150 - 200":"rgb(33,113,181)","200 - 250":"rgb(8,48,107)","50 - 100":"rgb(198,219,239)"},"legendOpen":false}},"version":"6.3.0","panelRefName":"panel_13"},{"panelIndex":"27","gridData":{"x":0,"y":27,"w":10,"h":8,"i":"27"},"embeddableConfig":{"vis":{"defaultColors":{"0 - 50":"rgb(247,251,255)","100 - 150":"rgb(107,174,214)","150 - 200":"rgb(33,113,181)","200 - 250":"rgb(8,48,107)","50 - 100":"rgb(198,219,239)"},"legendOpen":false}},"version":"6.3.0","panelRefName":"panel_14"},{"panelIndex":"28","gridData":{"x":0,"y":47,"w":24,"h":15,"i":"28"},"embeddableConfig":{"vis":{"defaultColors":{"0 - 11":"rgb(247,251,255)","11 - 22":"rgb(208,225,242)","22 - 33":"rgb(148,196,223)","33 - 44":"rgb(74,152,201)","44 - 55":"rgb(23,100,171)"},"legendOpen":false}},"version":"6.3.0","panelRefName":"panel_15"},{"panelIndex":"29","gridData":{"x":40,"y":7,"w":8,"h":6,"i":"29"},"embeddableConfig":{},"version":"6.3.0","panelRefName":"panel_16"},{"panelIndex":"30","gridData":{"x":40,"y":13,"w":8,"h":6,"i":"30"},"embeddableConfig":{},"version":"6.3.0","panelRefName":"panel_17"},{"panelIndex":"31","gridData":{"x":24,"y":47,"w":24,"h":15,"i":"31"},"embeddableConfig":{},"version":"6.3.0","panelRefName":"panel_18"}]',
+ '[{"panelIndex":"1","gridData":{"x":0,"y":0,"w":32,"h":7,"i":"1"},"embeddableConfig":{},"version":"6.3.0","panelRefName":"panel_0"},{"panelIndex":"3","gridData":{"x":17,"y":7,"w":23,"h":12,"i":"3"},"embeddableConfig":{"vis":{"colors":{"Average Ticket Price":"#0A50A1","Flight Count":"#82B5D8"},"legendOpen":false}},"version":"6.3.0","panelRefName":"panel_1"},{"panelIndex":"5","gridData":{"x":0,"y":7,"w":17,"h":12,"i":"5"},"embeddableConfig":{"vis":{"colors":{"OpenSearch-Air":"#447EBC","BeatsWest":"#65C5DB","OpenSearch Dashboards Airlines":"#BA43A9","Logstash Airways":"#E5AC0E"},"legendOpen":false}},"version":"6.3.0","panelRefName":"panel_3"},{"panelIndex":"6","gridData":{"x":24,"y":33,"w":24,"h":14,"i":"6"},"embeddableConfig":{"vis":{"colors":{"Carrier Delay":"#5195CE","Late Aircraft Delay":"#1F78C1","NAS Delay":"#70DBED","No Delay":"#BADFF4","Security Delay":"#052B51","Weather Delay":"#6ED0E0"}}},"version":"6.3.0","panelRefName":"panel_4"},{"panelIndex":"7","gridData":{"x":24,"y":19,"w":24,"h":14,"i":"7"},"embeddableConfig":{},"version":"6.3.0","panelRefName":"panel_5"},{"panelIndex":"10","gridData":{"x":0,"y":35,"w":24,"h":12,"i":"10"},"embeddableConfig":{"vis":{"colors":{"Count":"#1F78C1"},"legendOpen":false}},"version":"6.3.0","panelRefName":"panel_6"},{"panelIndex":"13","gridData":{"x":10,"y":19,"w":14,"h":8,"i":"13"},"embeddableConfig":{"vis":{"colors":{"Count":"#1F78C1"},"legendOpen":false}},"version":"6.3.0","panelRefName":"panel_7"},{"panelIndex":"14","gridData":{"x":10,"y":27,"w":14,"h":8,"i":"14"},"embeddableConfig":{"vis":{"colors":{"Count":"#1F78C1"},"legendOpen":false}},"version":"6.3.0","panelRefName":"panel_8"},{"panelIndex":"21","gridData":{"x":0,"y":62,"w":48,"h":8,"i":"21"},"embeddableConfig":{},"version":"6.3.0","panelRefName":"panel_10"},{"panelIndex":"22","gridData":{"x":32,"y":0,"w":16,"h":7,"i":"22"},"embeddableConfig":{},"version":"6.3.0","panelRefName":"panel_11"},{"panelIndex":"23","gridData":{"x":0,"y":70,"w":48,"h":15,"i":"23"},"embeddableConfig":{"mapCenter":[42.19556096274418,9.536742995308601e-7],"mapZoom":1},"version":"6.3.0","panelRefName":"panel_12"},{"panelIndex":"25","gridData":{"x":0,"y":19,"w":10,"h":8,"i":"25"},"embeddableConfig":{"vis":{"defaultColors":{"0 - 50":"rgb(247,251,255)","100 - 150":"rgb(107,174,214)","150 - 200":"rgb(33,113,181)","200 - 250":"rgb(8,48,107)","50 - 100":"rgb(198,219,239)"},"legendOpen":false}},"version":"6.3.0","panelRefName":"panel_13"},{"panelIndex":"27","gridData":{"x":0,"y":27,"w":10,"h":8,"i":"27"},"embeddableConfig":{"vis":{"defaultColors":{"0 - 50":"rgb(247,251,255)","100 - 150":"rgb(107,174,214)","150 - 200":"rgb(33,113,181)","200 - 250":"rgb(8,48,107)","50 - 100":"rgb(198,219,239)"},"legendOpen":false}},"version":"6.3.0","panelRefName":"panel_14"},{"panelIndex":"28","gridData":{"x":0,"y":47,"w":24,"h":15,"i":"28"},"embeddableConfig":{"vis":{"defaultColors":{"0 - 11":"rgb(247,251,255)","11 - 22":"rgb(208,225,242)","22 - 33":"rgb(148,196,223)","33 - 44":"rgb(74,152,201)","44 - 55":"rgb(23,100,171)"},"legendOpen":false}},"version":"6.3.0","panelRefName":"panel_15"},{"panelIndex":"29","gridData":{"x":40,"y":7,"w":8,"h":6,"i":"29"},"embeddableConfig":{},"version":"6.3.0","panelRefName":"panel_16"},{"panelIndex":"30","gridData":{"x":40,"y":13,"w":8,"h":6,"i":"30"},"embeddableConfig":{},"version":"6.3.0","panelRefName":"panel_17"},{"panelIndex":"31","gridData":{"x":24,"y":47,"w":24,"h":15,"i":"31"},"embeddableConfig":{},"version":"6.3.0","panelRefName":"panel_18"}]',
optionsJSON: '{"hidePanelTitles":false,"useMargins":true}',
version: 1,
timeRestore: true,
diff --git a/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.tsx b/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.tsx
index 91971a363c05..ee9fb6fb8093 100644
--- a/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.tsx
+++ b/src/plugins/visualize/public/application/utils/use/use_visualize_app_state.tsx
@@ -68,6 +68,7 @@ export const useVisualizeAppState = (
});
const onDirtyStateChange = ({ isDirty }: { isDirty: boolean }) => {
+ console.log('on dirty state change', instance);
if (!isDirty) {
// it is important to update vis state with fresh data
stateContainer.transitions.updateVisState(visStateToEditorState(instance, services).vis);