diff --git a/src/plugins/dashboard/public/application/_hacks.scss b/src/plugins/dashboard/public/application/_hacks.scss
deleted file mode 100644
index d3a98dc3fd7c..000000000000
--- a/src/plugins/dashboard/public/application/_hacks.scss
+++ /dev/null
@@ -1,14 +0,0 @@
-// ANGULAR SELECTOR HACKS
-
-/**
- * Needs to correspond with the react root nested inside angular.
- */
-#dashboardViewport {
- flex: 1;
- display: flex;
- flex-direction: column;
-
- [data-reactroot] {
- flex: 1;
- }
-}
diff --git a/src/plugins/dashboard/public/application/app.tsx b/src/plugins/dashboard/public/application/app.tsx
new file mode 100644
index 000000000000..439806bb8de3
--- /dev/null
+++ b/src/plugins/dashboard/public/application/app.tsx
@@ -0,0 +1,37 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ *
+ * Any modifications Copyright OpenSearch Contributors. See
+ * GitHub history for details.
+ */
+
+import { AppMountParameters } from 'opensearch-dashboards/public';
+import React from 'react';
+import { Route, Switch } from 'react-router-dom';
+import { DashboardConstants, createDashboardEditUrl } from '../dashboard_constants';
+import { DashboardEditor, DashboardListing, DashboardNoMatch } from './components';
+
+export interface DashboardAppProps {
+ onAppLeave: AppMountParameters['onAppLeave'];
+}
+
+export const DashboardApp = ({ onAppLeave }: DashboardAppProps) => {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/plugins/dashboard/public/application/application.ts b/src/plugins/dashboard/public/application/application.ts
deleted file mode 100644
index 35899cddf69d..000000000000
--- a/src/plugins/dashboard/public/application/application.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * SPDX-License-Identifier: Apache-2.0
- *
- * The OpenSearch Contributors require contributions made to
- * this file be licensed under the Apache-2.0 license or a
- * compatible open source license.
- *
- * Any modifications Copyright OpenSearch Contributors. See
- * GitHub history for details.
- */
-
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import './index.scss';
-
-import { EuiIcon } from '@elastic/eui';
-import angular, { IModule } from 'angular';
-// required for `ngSanitize` angular module
-import 'angular-sanitize';
-import { i18nDirective, i18nFilter, I18nProvider } from '@osd/i18n/angular';
-import {
- ChromeStart,
- ToastsStart,
- IUiSettingsClient,
- CoreStart,
- SavedObjectsClientContract,
- PluginInitializerContext,
- ScopedHistory,
- AppMountParameters,
-} from 'opensearch-dashboards/public';
-import { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
-import { DashboardProvider } from 'src/plugins/dashboard/public/types';
-import { Storage } from '../../../opensearch_dashboards_utils/public';
-// @ts-ignore
-import { initDashboardApp } from './legacy_app';
-import { EmbeddableStart } from '../../../embeddable/public';
-import { NavigationPublicPluginStart as NavigationStart } from '../../../navigation/public';
-import { DataPublicPluginStart } from '../../../data/public';
-import { SharePluginStart } from '../../../share/public';
-import {
- OpenSearchDashboardsLegacyStart,
- configureAppAngularModule,
-} from '../../../opensearch_dashboards_legacy/public';
-import { UrlForwardingStart } from '../../../url_forwarding/public';
-import { SavedObjectLoader, SavedObjectsStart } from '../../../saved_objects/public';
-
-// required for i18nIdDirective
-import 'angular-sanitize';
-// required for ngRoute
-import 'angular-route';
-
-export interface RenderDeps {
- pluginInitializerContext: PluginInitializerContext;
- core: CoreStart;
- data: DataPublicPluginStart;
- navigation: NavigationStart;
- savedObjectsClient: SavedObjectsClientContract;
- savedDashboards: SavedObjectLoader;
- dashboardProviders: () => { [key: string]: DashboardProvider };
- dashboardConfig: OpenSearchDashboardsLegacyStart['dashboardConfig'];
- dashboardCapabilities: any;
- embeddableCapabilities: {
- visualizeCapabilities: any;
- mapsCapabilities: any;
- };
- uiSettings: IUiSettingsClient;
- chrome: ChromeStart;
- addBasePath: (path: string) => string;
- savedQueryService: DataPublicPluginStart['query']['savedQueries'];
- embeddable: EmbeddableStart;
- localStorage: Storage;
- share?: SharePluginStart;
- usageCollection?: UsageCollectionSetup;
- navigateToDefaultApp: UrlForwardingStart['navigateToDefaultApp'];
- navigateToLegacyOpenSearchDashboardsUrl: UrlForwardingStart['navigateToLegacyOpenSearchDashboardsUrl'];
- scopedHistory: () => ScopedHistory;
- setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
- savedObjects: SavedObjectsStart;
- restorePreviousUrl: () => void;
- toastNotifications: ToastsStart;
-}
-
-let angularModuleInstance: IModule | null = null;
-
-export const renderApp = (element: HTMLElement, appBasePath: string, deps: RenderDeps) => {
- if (!angularModuleInstance) {
- angularModuleInstance = createLocalAngularModule();
- // global routing stuff
- configureAppAngularModule(
- angularModuleInstance,
- { core: deps.core, env: deps.pluginInitializerContext.env },
- true,
- deps.scopedHistory
- );
- initDashboardApp(angularModuleInstance, deps);
- }
-
- const $injector = mountDashboardApp(appBasePath, element);
-
- return () => {
- ($injector.get('osdUrlStateStorage') as any).cancel();
- $injector.get('$rootScope').$destroy();
- };
-};
-
-const mainTemplate = (basePath: string) => `
-
-
`;
-
-const moduleName = 'app/dashboard';
-
-const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react'];
-
-function mountDashboardApp(appBasePath: string, element: HTMLElement) {
- const mountpoint = document.createElement('div');
- mountpoint.setAttribute('class', 'dshAppContainer');
- // eslint-disable-next-line no-unsanitized/property
- mountpoint.innerHTML = mainTemplate(appBasePath);
- // bootstrap angular into detached element and attach it later to
- // make angular-within-angular possible
- const $injector = angular.bootstrap(mountpoint, [moduleName]);
- // initialize global state handler
- element.appendChild(mountpoint);
- return $injector;
-}
-
-function createLocalAngularModule() {
- createLocalI18nModule();
- createLocalIconModule();
-
- return angular.module(moduleName, [
- ...thirdPartyAngularDependencies,
- 'app/dashboard/I18n',
- 'app/dashboard/icon',
- ]);
-}
-
-function createLocalIconModule() {
- angular
- .module('app/dashboard/icon', ['react'])
- .directive('icon', (reactDirective) => reactDirective(EuiIcon));
-}
-
-function createLocalI18nModule() {
- angular
- .module('app/dashboard/I18n', [])
- .provider('i18n', I18nProvider)
- .filter('i18n', i18nFilter)
- .directive('i18nId', i18nDirective);
-}
diff --git a/src/plugins/dashboard/public/application/components/dashboard_editor.tsx b/src/plugins/dashboard/public/application/components/dashboard_editor.tsx
new file mode 100644
index 000000000000..d3d0814f0c4a
--- /dev/null
+++ b/src/plugins/dashboard/public/application/components/dashboard_editor.tsx
@@ -0,0 +1,10 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+
+export const DashboardEditor = () => {
+ return Dashboard Editor
;
+};
diff --git a/src/plugins/dashboard/public/application/components/dashboard_listing.tsx b/src/plugins/dashboard/public/application/components/dashboard_listing.tsx
new file mode 100644
index 000000000000..1ee5cd3de88b
--- /dev/null
+++ b/src/plugins/dashboard/public/application/components/dashboard_listing.tsx
@@ -0,0 +1,10 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+
+export const DashboardListing = () => {
+ return Dashboard Listing
;
+};
diff --git a/src/plugins/dashboard/public/application/components/dashboard_no_match.tsx b/src/plugins/dashboard/public/application/components/dashboard_no_match.tsx
new file mode 100644
index 000000000000..bd3c40dcf1c4
--- /dev/null
+++ b/src/plugins/dashboard/public/application/components/dashboard_no_match.tsx
@@ -0,0 +1,10 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+
+export const DashboardNoMatch = () => {
+ return Dashboard No Match
;
+};
diff --git a/src/plugins/dashboard/public/application/components/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/components/dashboard_top_nav.tsx
new file mode 100644
index 000000000000..1603fb89dad5
--- /dev/null
+++ b/src/plugins/dashboard/public/application/components/dashboard_top_nav.tsx
@@ -0,0 +1,10 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+
+export const DashboardTopNav = () => {
+ return Dashboard Top Nav
;
+};
diff --git a/src/plugins/dashboard/public/application/components/index.ts b/src/plugins/dashboard/public/application/components/index.ts
new file mode 100644
index 000000000000..27f7e46ad74e
--- /dev/null
+++ b/src/plugins/dashboard/public/application/components/index.ts
@@ -0,0 +1,9 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+export { DashboardListing } from './dashboard_listing';
+export { DashboardEditor } from './dashboard_editor';
+export { DashboardNoMatch } from './dashboard_no_match';
+export { DashboardTopNav } from './dashboard_top_nav';
diff --git a/src/plugins/dashboard/public/application/dashboard_app.html b/src/plugins/dashboard/public/application/dashboard_app.html
deleted file mode 100644
index 87a5728ac205..000000000000
--- a/src/plugins/dashboard/public/application/dashboard_app.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
- {{screenTitle}}
-
-
-
diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx
deleted file mode 100644
index 6141329c5a53..000000000000
--- a/src/plugins/dashboard/public/application/dashboard_app.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * SPDX-License-Identifier: Apache-2.0
- *
- * The OpenSearch Contributors require contributions made to
- * this file be licensed under the Apache-2.0 license or a
- * compatible open source license.
- *
- * Any modifications Copyright OpenSearch Contributors. See
- * GitHub history for details.
- */
-
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import moment from 'moment';
-import { Subscription } from 'rxjs';
-import { History } from 'history';
-
-import { ViewMode } from 'src/plugins/embeddable/public';
-import { IIndexPattern, TimeRange, Query, Filter, SavedQuery } from 'src/plugins/data/public';
-import { IOsdUrlStateStorage } from 'src/plugins/opensearch_dashboards_utils/public';
-
-import { DashboardAppState, SavedDashboardPanel } from '../types';
-import { DashboardAppController } from './dashboard_app_controller';
-import { RenderDeps } from './application';
-import { SavedObjectDashboard } from '../saved_dashboards';
-
-export interface DashboardAppScope extends ng.IScope {
- dash: SavedObjectDashboard;
- appState: DashboardAppState;
- model: {
- query: Query;
- filters: Filter[];
- timeRestore: boolean;
- title: string;
- description: string;
- timeRange:
- | TimeRange
- | { to: string | moment.Moment | undefined; from: string | moment.Moment | undefined };
- refreshInterval: any;
- };
- savedQuery?: SavedQuery;
- refreshInterval: any;
- panels: SavedDashboardPanel[];
- indexPatterns: IIndexPattern[];
- dashboardViewMode: ViewMode;
- expandedPanel?: string;
- getShouldShowEditHelp: () => boolean;
- getShouldShowViewHelp: () => boolean;
- handleRefresh: (
- { query, dateRange }: { query?: Query; dateRange: TimeRange },
- isUpdate?: boolean
- ) => void;
- topNavMenu: any;
- showAddPanel: any;
- showSaveQuery: boolean;
- osdTopNav: any;
- enterEditMode: () => void;
- timefilterSubscriptions$: Subscription;
- isVisible: boolean;
-}
-
-export function initDashboardAppDirective(app: any, deps: RenderDeps) {
- app.directive('dashboardApp', () => ({
- restrict: 'E',
- controllerAs: 'dashboardApp',
- controller: (
- $scope: DashboardAppScope,
- $route: any,
- $routeParams: {
- id?: string;
- },
- osdUrlStateStorage: IOsdUrlStateStorage,
- history: History
- ) =>
- new DashboardAppController({
- $route,
- $scope,
- $routeParams,
- indexPatterns: deps.data.indexPatterns,
- osdUrlStateStorage,
- history,
- ...deps,
- }),
- }));
-}
diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx
deleted file mode 100644
index 414860e348a6..000000000000
--- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx
+++ /dev/null
@@ -1,1175 +0,0 @@
-/*
- * SPDX-License-Identifier: Apache-2.0
- *
- * The OpenSearch Contributors require contributions made to
- * this file be licensed under the Apache-2.0 license or a
- * compatible open source license.
- *
- * Any modifications Copyright OpenSearch Contributors. See
- * GitHub history for details.
- */
-
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _, { uniqBy } from 'lodash';
-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 React, { useState, ReactElement } from 'react';
-import ReactDOM from 'react-dom';
-import angular from 'angular';
-import deepEqual from 'fast-deep-equal';
-
-import { Observable, pipe, Subscription, merge, EMPTY } from 'rxjs';
-import {
- filter,
- map,
- debounceTime,
- mapTo,
- startWith,
- switchMap,
- distinctUntilChanged,
- catchError,
-} from 'rxjs/operators';
-import { History } from 'history';
-import { SavedObjectSaveOpts } from 'src/plugins/saved_objects/public';
-import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public';
-import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen';
-
-import {
- connectToQueryState,
- opensearchFilters,
- IndexPattern,
- IndexPatternsContract,
- QueryState,
- SavedQuery,
- syncQueryStateWithUrl,
-} from '../../../data/public';
-import { getSavedObjectFinder, SaveResult, showSaveModal } from '../../../saved_objects/public';
-
-import {
- DASHBOARD_CONTAINER_TYPE,
- DashboardContainer,
- DashboardContainerInput,
- DashboardPanelState,
-} from './embeddable';
-import {
- EmbeddableFactoryNotFoundError,
- ErrorEmbeddable,
- isErrorEmbeddable,
- openAddPanelFlyout,
- ViewMode,
- ContainerOutput,
- EmbeddableInput,
-} from '../../../embeddable/public';
-import { NavAction, SavedDashboardPanel } from '../types';
-
-import { showOptionsPopover } from './top_nav/show_options_popover';
-import { DashboardSaveModal } from './top_nav/save_modal';
-import { showCloneModal } from './top_nav/show_clone_modal';
-import { saveDashboard } from './lib';
-import { DashboardStateManager } from './dashboard_state_manager';
-import { createDashboardEditUrl, DashboardConstants } from '../dashboard_constants';
-import { getTopNavConfig } from './top_nav/get_top_nav_config';
-import { TopNavIds } from './top_nav/top_nav_ids';
-import { getDashboardTitle } from './dashboard_strings';
-import { DashboardAppScope } from './dashboard_app';
-import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters';
-import { RenderDeps } from './application';
-import { IOsdUrlStateStorage, unhashUrl } from '../../../opensearch_dashboards_utils/public';
-import {
- addFatalError,
- AngularHttpError,
- OpenSearchDashboardsLegacyStart,
- subscribeWithScope,
-} from '../../../opensearch_dashboards_legacy/public';
-import { migrateLegacyQuery } from './lib/migrate_legacy_query';
-
-export interface DashboardAppControllerDependencies extends RenderDeps {
- $scope: DashboardAppScope;
- $route: any;
- $routeParams: any;
- indexPatterns: IndexPatternsContract;
- dashboardConfig: OpenSearchDashboardsLegacyStart['dashboardConfig'];
- history: History;
- osdUrlStateStorage: IOsdUrlStateStorage;
- navigation: NavigationStart;
-}
-
-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 class DashboardAppController {
- // Part of the exposed plugin API - do not remove without careful consideration.
- appStatus: {
- dirty: boolean;
- };
-
- constructor({
- pluginInitializerContext,
- $scope,
- $route,
- $routeParams,
- dashboardConfig,
- indexPatterns,
- savedQueryService,
- embeddable,
- share,
- dashboardCapabilities,
- scopedHistory,
- embeddableCapabilities: { visualizeCapabilities, mapsCapabilities },
- data: { query: queryService },
- core: {
- notifications,
- overlays,
- chrome,
- injectedMetadata,
- fatalErrors,
- uiSettings,
- savedObjects,
- http,
- i18n: i18nStart,
- },
- history,
- setHeaderActionMenu,
- osdUrlStateStorage,
- usageCollection,
- navigation,
- }: DashboardAppControllerDependencies) {
- const filterManager = queryService.filterManager;
- const timefilter = queryService.timefilter.timefilter;
- const queryStringManager = queryService.queryString;
- const isEmbeddedExternally = Boolean($routeParams.embed);
-
- // url param rules should only apply when embedded (e.g. url?embed=true)
- const shouldForceDisplay = (param: string): boolean =>
- isEmbeddedExternally && Boolean($routeParams[param]);
-
- const forceShowTopNavMenu = shouldForceDisplay(UrlParams.SHOW_TOP_MENU);
- const forceShowQueryInput = shouldForceDisplay(UrlParams.SHOW_QUERY_INPUT);
- const forceShowDatePicker = shouldForceDisplay(UrlParams.SHOW_TIME_FILTER);
- const forceHideFilterBar = shouldForceDisplay(UrlParams.HIDE_FILTER_BAR);
-
- let lastReloadRequestTime = 0;
- const dash = ($scope.dash = $route.current.locals.dash);
- if (dash.id) {
- chrome.docTitle.change(dash.title);
- }
-
- let incomingEmbeddable = embeddable
- .getStateTransfer(scopedHistory())
- .getIncomingEmbeddablePackage();
-
- const dashboardStateManager = new DashboardStateManager({
- savedDashboard: dash,
- hideWriteControls: dashboardConfig.getHideWriteControls(),
- opensearchDashboardsVersion: pluginInitializerContext.env.packageInfo.version,
- osdUrlStateStorage,
- history,
- usageCollection,
- });
-
- // sync initial app filters from state to filterManager
- // if there is an existing similar global filter, then leave it as global
- filterManager.setAppFilters(_.cloneDeep(dashboardStateManager.appState.filters));
- queryStringManager.setQuery(migrateLegacyQuery(dashboardStateManager.appState.query));
-
- // setup syncing of app filters between appState and filterManager
- const stopSyncingAppFilters = connectToQueryState(
- queryService,
- {
- set: ({ filters, query }) => {
- dashboardStateManager.setFilters(filters || []);
- dashboardStateManager.setQuery(query || queryStringManager.getDefaultQuery());
- },
- get: () => ({
- filters: dashboardStateManager.appState.filters,
- query: dashboardStateManager.getQuery(),
- }),
- state$: dashboardStateManager.appState$.pipe(
- map((state) => ({
- filters: state.filters,
- query: queryStringManager.formatQuery(state.query),
- }))
- ),
- },
- {
- filters: opensearchFilters.FilterStateStore.APP_STATE,
- query: true,
- }
- );
-
- // The hash check is so we only update the time filter on dashboard open, not during
- // normal cross app navigation.
- if (dashboardStateManager.getIsTimeSavedWithDashboard()) {
- const initialGlobalStateInUrl = osdUrlStateStorage.get('_g');
- if (!initialGlobalStateInUrl?.time) {
- dashboardStateManager.syncTimefilterWithDashboardTime(timefilter);
- }
- if (!initialGlobalStateInUrl?.refreshInterval) {
- dashboardStateManager.syncTimefilterWithDashboardRefreshInterval(timefilter);
- }
- }
-
- // starts syncing `_g` portion of url with query services
- // it is important to start this syncing after `dashboardStateManager.syncTimefilterWithDashboard(timefilter);` above is run,
- // otherwise it will case redundant browser history records
- const { stop: stopSyncingQueryServiceStateWithUrl } = syncQueryStateWithUrl(
- queryService,
- osdUrlStateStorage
- );
-
- // starts syncing `_a` portion of url
- dashboardStateManager.startStateSyncing();
-
- $scope.showSaveQuery = dashboardCapabilities.saveQuery as boolean;
-
- const getShouldShowEditHelp = () =>
- !dashboardStateManager.getPanels().length &&
- dashboardStateManager.getIsEditMode() &&
- !dashboardConfig.getHideWriteControls();
-
- const getShouldShowViewHelp = () =>
- !dashboardStateManager.getPanels().length &&
- dashboardStateManager.getIsViewMode() &&
- !dashboardConfig.getHideWriteControls();
-
- const shouldShowUnauthorizedEmptyState = () => {
- const readonlyMode =
- !dashboardStateManager.getPanels().length &&
- !getShouldShowEditHelp() &&
- !getShouldShowViewHelp() &&
- dashboardConfig.getHideWriteControls();
- const userHasNoPermissions =
- !dashboardStateManager.getPanels().length &&
- !visualizeCapabilities.save &&
- !mapsCapabilities.save;
- return readonlyMode || userHasNoPermissions;
- };
-
- const addVisualization = () => {
- navActions[TopNavIds.VISUALIZE]();
- };
-
- function getDashboardIndexPatterns(container: DashboardContainer): IndexPattern[] {
- let panelIndexPatterns: IndexPattern[] = [];
- Object.values(container.getChildIds()).forEach((id) => {
- const embeddableInstance = container.getChild(id);
- if (isErrorEmbeddable(embeddableInstance)) return;
- const embeddableIndexPatterns = (embeddableInstance.getOutput() as any).indexPatterns;
- if (!embeddableIndexPatterns) return;
- panelIndexPatterns.push(...embeddableIndexPatterns);
- });
- panelIndexPatterns = uniqBy(panelIndexPatterns, 'id');
- return panelIndexPatterns;
- }
-
- const updateIndexPatternsOperator = pipe(
- filter((container: DashboardContainer) => !!container && !isErrorEmbeddable(container)),
- map(getDashboardIndexPatterns),
- distinctUntilChanged((a, b) =>
- deepEqual(
- a.map((ip) => ip.id),
- b.map((ip) => ip.id)
- )
- ),
- // using switchMap for previous task cancellation
- switchMap((panelIndexPatterns: IndexPattern[]) => {
- return new Observable((observer) => {
- if (panelIndexPatterns && panelIndexPatterns.length > 0) {
- $scope.$evalAsync(() => {
- if (observer.closed) return;
- $scope.indexPatterns = panelIndexPatterns;
- observer.complete();
- });
- } else {
- indexPatterns.getDefault().then((defaultIndexPattern) => {
- if (observer.closed) return;
- $scope.$evalAsync(() => {
- if (observer.closed) return;
- $scope.indexPatterns = [defaultIndexPattern as IndexPattern];
- observer.complete();
- });
- });
- }
- });
- })
- );
-
- const getEmptyScreenProps = (
- shouldShowEditHelp: boolean,
- isEmptyInReadOnlyMode: boolean
- ): DashboardEmptyScreenProps => {
- const emptyScreenProps: DashboardEmptyScreenProps = {
- onLinkClick: shouldShowEditHelp ? $scope.showAddPanel : $scope.enterEditMode,
- showLinkToVisualize: shouldShowEditHelp,
- uiSettings,
- http,
- };
- if (shouldShowEditHelp) {
- emptyScreenProps.onVisualizeClick = addVisualization;
- }
- if (isEmptyInReadOnlyMode) {
- emptyScreenProps.isReadonlyMode = true;
- }
- return emptyScreenProps;
- };
-
- const getDashboardInput = (): DashboardContainerInput => {
- const embeddablesMap: {
- [key: string]: DashboardPanelState;
- } = {};
- dashboardStateManager.getPanels().forEach((panel: SavedDashboardPanel) => {
- embeddablesMap[panel.panelIndex] = convertSavedDashboardPanelToPanelState(panel);
- });
-
- // If the incoming embeddable state's id already exists in the embeddables map, replace the input, retaining the existing gridData for that panel.
- if (incomingEmbeddable?.embeddableId && embeddablesMap[incomingEmbeddable.embeddableId]) {
- const originalPanelState = embeddablesMap[incomingEmbeddable.embeddableId];
- embeddablesMap[incomingEmbeddable.embeddableId] = {
- gridData: originalPanelState.gridData,
- type: incomingEmbeddable.type,
- explicitInput: {
- ...originalPanelState.explicitInput,
- ...incomingEmbeddable.input,
- id: incomingEmbeddable.embeddableId,
- },
- };
- incomingEmbeddable = undefined;
- }
-
- const shouldShowEditHelp = getShouldShowEditHelp();
- const shouldShowViewHelp = getShouldShowViewHelp();
- const isEmptyInReadonlyMode = shouldShowUnauthorizedEmptyState();
- return {
- id: dashboardStateManager.savedDashboard.id || '',
- filters: filterManager.getFilters(),
- hidePanelTitles: dashboardStateManager.getHidePanelTitles(),
- query: $scope.model.query,
- timeRange: {
- ..._.cloneDeep(timefilter.getTime()),
- },
- refreshConfig: timefilter.getRefreshInterval(),
- viewMode: dashboardStateManager.getViewMode(),
- panels: embeddablesMap,
- isFullScreenMode: dashboardStateManager.getFullScreenMode(),
- isEmbeddedExternally,
- isEmptyState: shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadonlyMode,
- useMargins: dashboardStateManager.getUseMargins(),
- lastReloadRequestTime,
- title: dashboardStateManager.getTitle(),
- description: dashboardStateManager.getDescription(),
- expandedPanelId: dashboardStateManager.getExpandedPanelId(),
- };
- };
-
- const updateState = () => {
- // Following the "best practice" of always have a '.' in your ng-models –
- // https://github.com/angular/angular.js/wiki/Understanding-Scopes
- $scope.model = {
- query: dashboardStateManager.getQuery(),
- filters: filterManager.getFilters(),
- timeRestore: dashboardStateManager.getTimeRestore(),
- title: dashboardStateManager.getTitle(),
- description: dashboardStateManager.getDescription(),
- timeRange: timefilter.getTime(),
- refreshInterval: timefilter.getRefreshInterval(),
- };
- $scope.panels = dashboardStateManager.getPanels();
- };
-
- updateState();
-
- 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);
-
- 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
- )
- .subscribe();
-
- inputSubscription = dashboardContainer.getInput$().subscribe(() => {
- let dirty = false;
-
- // 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
- );
- dirty = true;
- }
-
- dashboardStateManager.handleDashboardContainerChanges(container);
- $scope.$evalAsync(() => {
- if (dirty) {
- updateState();
- }
- });
- });
-
- dashboardStateManager.registerChangeListener(() => {
- // we aren't checking dirty state because there are changes the container needs to know about
- // that won't make the dashboard "dirty" - like a view mode change.
- refreshDashboardContainer();
- });
-
- // If the incomingEmbeddable does not yet exist in the panels listing, create a new panel using the container's addEmbeddable method.
- if (
- incomingEmbeddable &&
- (!incomingEmbeddable.embeddableId ||
- !container.getInput().panels[incomingEmbeddable.embeddableId])
- ) {
- container.addNewEmbeddable(
- incomingEmbeddable.type,
- incomingEmbeddable.input
- );
- }
- }
-
- if (dashboardDom && container) {
- container.render(dashboardDom);
- }
- });
- }
-
- // Part of the exposed plugin API - do not remove without careful consideration.
- this.appStatus = {
- dirty: !dash.id,
- };
-
- dashboardStateManager.registerChangeListener((status) => {
- this.appStatus.dirty = status.dirty || !dash.id;
- updateState();
- });
-
- dashboardStateManager.applyFilters(
- dashboardStateManager.getQuery() || queryStringManager.getDefaultQuery(),
- filterManager.getFilters()
- );
-
- timefilter.disableTimeRangeSelector();
- timefilter.disableAutoRefreshSelector();
-
- const landingPageUrl = () => `#${DashboardConstants.LANDING_PAGE_PATH}`;
-
- const getDashTitle = () =>
- getDashboardTitle(
- dashboardStateManager.getTitle(),
- dashboardStateManager.getViewMode(),
- dashboardStateManager.getIsDirty(timefilter),
- dashboardStateManager.isNew()
- );
-
- // Push breadcrumbs to new header navigation
- const updateBreadcrumbs = () => {
- chrome.setBreadcrumbs([
- {
- text: i18n.translate('dashboard.dashboardAppBreadcrumbsTitle', {
- defaultMessage: 'Dashboard',
- }),
- href: landingPageUrl(),
- },
- { text: getDashTitle() },
- ]);
- };
-
- updateBreadcrumbs();
- dashboardStateManager.registerChangeListener(updateBreadcrumbs);
-
- const getChangesFromAppStateForContainerState = () => {
- const appStateDashboardInput = getDashboardInput();
- if (!dashboardContainer || isErrorEmbeddable(dashboardContainer)) {
- return appStateDashboardInput;
- }
-
- const containerInput = dashboardContainer.getInput();
- const differences: Partial = {};
-
- // Filters shouldn't be compared using regular isEqual
- if (
- !opensearchFilters.compareFilters(
- containerInput.filters,
- appStateDashboardInput.filters,
- opensearchFilters.COMPARE_ALL_OPTIONS
- )
- ) {
- differences.filters = appStateDashboardInput.filters;
- }
-
- Object.keys(_.omit(containerInput, ['filters'])).forEach((key) => {
- const containerValue = (containerInput as { [key: string]: unknown })[key];
- const appStateValue = ((appStateDashboardInput as unknown) as { [key: string]: unknown })[
- key
- ];
- if (!_.isEqual(containerValue, appStateValue)) {
- (differences as { [key: string]: unknown })[key] = appStateValue;
- }
- });
-
- // cloneDeep hack is needed, as there are multiple place, where container's input mutated,
- // but values from appStateValue are deeply frozen, as they can't be mutated directly
- return Object.values(differences).length === 0 ? undefined : _.cloneDeep(differences);
- };
-
- const refreshDashboardContainer = () => {
- const changes = getChangesFromAppStateForContainerState();
- if (changes && dashboardContainer) {
- dashboardContainer.updateInput(changes);
- }
- };
-
- $scope.handleRefresh = function (_payload, isUpdate) {
- if (isUpdate === false) {
- // The user can still request a reload in the query bar, even if the
- // query is the same, and in that case, we have to explicitly ask for
- // a reload, since no state changes will cause it.
- lastReloadRequestTime = new Date().getTime();
- refreshDashboardContainer();
- }
- };
-
- const updateStateFromSavedQuery = (savedQuery: SavedQuery) => {
- const allFilters = filterManager.getFilters();
- dashboardStateManager.applyFilters(savedQuery.attributes.query, allFilters);
- if (savedQuery.attributes.timefilter) {
- timefilter.setTime({
- from: savedQuery.attributes.timefilter.from,
- to: savedQuery.attributes.timefilter.to,
- });
- if (savedQuery.attributes.timefilter.refreshInterval) {
- timefilter.setRefreshInterval(savedQuery.attributes.timefilter.refreshInterval);
- }
- }
- // Making this method sync broke the updates.
- // Temporary fix, until we fix the complex state in this file.
- setTimeout(() => {
- filterManager.setFilters(allFilters);
- }, 0);
- };
-
- $scope.$watch('savedQuery', (newSavedQuery: SavedQuery) => {
- if (!newSavedQuery) return;
- dashboardStateManager.setSavedQueryId(newSavedQuery.id);
-
- updateStateFromSavedQuery(newSavedQuery);
- });
-
- $scope.$watch(
- () => {
- return dashboardStateManager.getSavedQueryId();
- },
- (newSavedQueryId) => {
- if (!newSavedQueryId) {
- $scope.savedQuery = undefined;
- return;
- }
- if (!$scope.savedQuery || newSavedQueryId !== $scope.savedQuery.id) {
- savedQueryService.getSavedQuery(newSavedQueryId).then((savedQuery: SavedQuery) => {
- $scope.$evalAsync(() => {
- $scope.savedQuery = savedQuery;
- updateStateFromSavedQuery(savedQuery);
- });
- });
- }
- }
- );
-
- $scope.indexPatterns = [];
-
- $scope.$watch(
- () => dashboardCapabilities.saveQuery,
- (newCapability) => {
- $scope.showSaveQuery = newCapability as boolean;
- }
- );
-
- const onSavedQueryIdChange = (savedQueryId?: string) => {
- dashboardStateManager.setSavedQueryId(savedQueryId);
- };
-
- const shouldShowFilterBar = (forceHide: boolean): boolean =>
- !forceHide && ($scope.model.filters.length > 0 || !dashboardStateManager.getFullScreenMode());
-
- const shouldShowNavBarComponent = (forceShow: boolean): boolean =>
- (forceShow || $scope.isVisible) && !dashboardStateManager.getFullScreenMode();
-
- const getNavBarProps = () => {
- const isFullScreenMode = dashboardStateManager.getFullScreenMode();
- const screenTitle = dashboardStateManager.getTitle();
- const showTopNavMenu = shouldShowNavBarComponent(forceShowTopNavMenu);
- const showQueryInput = shouldShowNavBarComponent(forceShowQueryInput);
- const showDatePicker = shouldShowNavBarComponent(forceShowDatePicker);
- const showQueryBar = showQueryInput || showDatePicker;
- const showFilterBar = shouldShowFilterBar(forceHideFilterBar);
- const showSearchBar = showQueryBar || showFilterBar;
-
- return {
- appName: 'dashboard',
- config: showTopNavMenu ? $scope.topNavMenu : undefined,
- className: isFullScreenMode ? 'osdTopNavMenu-isFullScreen' : undefined,
- screenTitle,
- showTopNavMenu,
- showSearchBar,
- showQueryBar,
- showQueryInput,
- showDatePicker,
- showFilterBar,
- indexPatterns: $scope.indexPatterns,
- showSaveQuery: $scope.showSaveQuery,
- savedQuery: $scope.savedQuery,
- onSavedQueryIdChange,
- savedQueryId: dashboardStateManager.getSavedQueryId(),
- useDefaultBehaviors: true,
- onQuerySubmit: $scope.handleRefresh,
- };
- };
- const dashboardNavBar = document.getElementById('dashboardChrome');
- const updateNavBar = () => {
- ReactDOM.render(
- ,
- dashboardNavBar
- );
- };
-
- const unmountNavBar = () => {
- if (dashboardNavBar) {
- ReactDOM.unmountComponentAtNode(dashboardNavBar);
- }
- };
-
- $scope.timefilterSubscriptions$ = new Subscription();
- const timeChanges$ = merge(timefilter.getRefreshIntervalUpdate$(), timefilter.getTimeUpdate$());
- $scope.timefilterSubscriptions$.add(
- subscribeWithScope(
- $scope,
- timeChanges$,
- {
- next: () => {
- updateState();
- refreshDashboardContainer();
- },
- },
- (error: AngularHttpError | Error | string) => addFatalError(fatalErrors, error)
- )
- );
-
- function updateViewMode(newMode: ViewMode) {
- dashboardStateManager.switchViewMode(newMode);
- }
-
- const onChangeViewMode = (newMode: ViewMode) => {
- const isPageRefresh = newMode === dashboardStateManager.getViewMode();
- const isLeavingEditMode = !isPageRefresh && newMode === ViewMode.VIEW;
- const willLoseChanges = isLeavingEditMode && dashboardStateManager.getIsDirty(timefilter);
-
- if (!willLoseChanges) {
- updateViewMode(newMode);
- return;
- }
-
- function revertChangesAndExitEditMode() {
- 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();
- };
-
- /**
- * Saves the dashboard.
- *
- * @param {object} [saveOptions={}]
- * @property {boolean} [saveOptions.confirmOverwrite=false] - If true, attempts to create the source so it
- * can confirm an overwrite if a document with the id already exists.
- * @property {boolean} [saveOptions.isTitleDuplicateConfirmed=false] - If true, save allowed with duplicate title
- * @property {func} [saveOptions.onTitleDuplicate] - function called if duplicate title exists.
- * When not provided, confirm modal will be displayed asking user to confirm or cancel save.
- * @return {Promise}
- * @resolved {String} - The id of the doc
- */
- function save(saveOptions: SavedObjectSaveOpts): Promise {
- return saveDashboard(angular.toJson, timefilter, dashboardStateManager, saveOptions)
- .then(function (id) {
- if (id) {
- notifications.toasts.addSuccess({
- title: i18n.translate('dashboard.dashboardWasSavedSuccessMessage', {
- defaultMessage: `Dashboard '{dashTitle}' was saved`,
- values: { dashTitle: dash.title },
- }),
- 'data-test-subj': 'saveDashboardSuccess',
- });
-
- if (dash.id !== $routeParams.id) {
- // 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(createDashboardEditUrl(dash.id));
- } else {
- chrome.docTitle.change(dash.lastSavedTitle);
- updateViewMode(ViewMode.VIEW);
- }
- }
- return { id };
- })
- .catch((error) => {
- notifications.toasts.addDanger({
- title: i18n.translate('dashboard.dashboardWasNotSavedDangerMessage', {
- defaultMessage: `Dashboard '{dashTitle}' was not saved. Error: {errorMessage}`,
- values: {
- dashTitle: dash.title,
- errorMessage: error.message,
- },
- }),
- 'data-test-subj': 'saveDashboardFailure',
- });
- return { error };
- });
- }
-
- $scope.showAddPanel = () => {
- dashboardStateManager.setFullScreenMode(false);
- /*
- * Temp solution for triggering menu click.
- * When de-angularizing this code, please call the underlaying action function
- * directly and not via the top nav object.
- **/
- navActions[TopNavIds.ADD_EXISTING]();
- };
- $scope.enterEditMode = () => {
- dashboardStateManager.setFullScreenMode(false);
- /*
- * Temp solution for triggering menu click.
- * When de-angularizing this code, please call the underlaying action function
- * directly and not via the top nav object.
- **/
- navActions[TopNavIds.ENTER_EDIT_MODE]();
- };
- const navActions: {
- [key: string]: NavAction;
- } = {};
- navActions[TopNavIds.FULL_SCREEN] = () => {
- dashboardStateManager.setFullScreenMode(true);
- updateNavBar();
- };
- navActions[TopNavIds.EXIT_EDIT_MODE] = () => onChangeViewMode(ViewMode.VIEW);
- navActions[TopNavIds.ENTER_EDIT_MODE] = () => onChangeViewMode(ViewMode.EDIT);
- navActions[TopNavIds.SAVE] = () => {
- const currentTitle = dashboardStateManager.getTitle();
- const currentDescription = dashboardStateManager.getDescription();
- const currentTimeRestore = dashboardStateManager.getTimeRestore();
- const onSave = ({
- newTitle,
- newDescription,
- newCopyOnSave,
- newTimeRestore,
- isTitleDuplicateConfirmed,
- onTitleDuplicate,
- }: {
- newTitle: string;
- newDescription: string;
- newCopyOnSave: boolean;
- newTimeRestore: boolean;
- isTitleDuplicateConfirmed: boolean;
- onTitleDuplicate: () => void;
- }) => {
- dashboardStateManager.setTitle(newTitle);
- dashboardStateManager.setDescription(newDescription);
- dashboardStateManager.savedDashboard.copyOnSave = newCopyOnSave;
- dashboardStateManager.setTimeRestore(newTimeRestore);
- 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) {
- dashboardStateManager.setTitle(currentTitle);
- dashboardStateManager.setDescription(currentDescription);
- dashboardStateManager.setTimeRestore(currentTimeRestore);
- }
- return response;
- });
- };
-
- const dashboardSaveModal = (
- {}}
- title={currentTitle}
- description={currentDescription}
- timeRestore={currentTimeRestore}
- showCopyOnSave={dash.id ? true : false}
- />
- );
- showSaveModal(dashboardSaveModal, i18nStart.Context);
- };
- navActions[TopNavIds.CLONE] = () => {
- const currentTitle = dashboardStateManager.getTitle();
- const onClone = (
- newTitle: string,
- isTitleDuplicateConfirmed: boolean,
- onTitleDuplicate: () => void
- ) => {
- dashboardStateManager.savedDashboard.copyOnSave = true;
- dashboardStateManager.setTitle(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) {
- dashboardStateManager.setTitle(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: dashboardStateManager.getUseMargins(),
- onUseMarginsChange: (isChecked: boolean) => {
- dashboardStateManager.setUseMargins(isChecked);
- },
- hidePanelTitles: dashboardStateManager.getHidePanelTitles(),
- onHidePanelTitlesChange: (isChecked: boolean) => {
- dashboardStateManager.setHidePanelTitles(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: dash.id,
- objectType: 'dashboard',
- sharingData: {
- title: dash.title,
- },
- isDirty: dashboardStateManager.getIsDirty(),
- embedUrlParamExtensions: [
- {
- paramName: 'embed',
- component: EmbedUrlParamExtension,
- },
- ],
- });
- };
- }
-
- updateViewMode(dashboardStateManager.getViewMode());
-
- const filterChanges = merge(filterManager.getUpdates$(), queryStringManager.getUpdates$()).pipe(
- debounceTime(100)
- );
-
- // update root source when filters update
- const updateSubscription = filterChanges.subscribe({
- next: () => {
- $scope.model.filters = filterManager.getFilters();
- $scope.model.query = queryStringManager.getQuery();
- dashboardStateManager.applyFilters($scope.model.query, $scope.model.filters);
- if (dashboardContainer) {
- dashboardContainer.updateInput({
- filters: $scope.model.filters,
- query: $scope.model.query,
- });
- }
- },
- });
-
- const visibleSubscription = chrome.getIsVisible$().subscribe((isVisible) => {
- $scope.$evalAsync(() => {
- $scope.isVisible = isVisible;
- updateNavBar();
- });
- });
-
- dashboardStateManager.registerChangeListener(() => {
- // view mode could have changed, so trigger top nav update
- $scope.topNavMenu = getTopNavConfig(
- dashboardStateManager.getViewMode(),
- navActions,
- dashboardConfig.getHideWriteControls()
- );
- updateNavBar();
- });
-
- $scope.$watch('indexPatterns', () => {
- updateNavBar();
- });
-
- $scope.$on('$destroy', () => {
- // we have to unmount nav bar manually to make sure all internal subscriptions are unsubscribed
- unmountNavBar();
-
- updateSubscription.unsubscribe();
- stopSyncingQueryServiceStateWithUrl();
- stopSyncingAppFilters();
- visibleSubscription.unsubscribe();
- $scope.timefilterSubscriptions$.unsubscribe();
-
- dashboardStateManager.destroy();
- if (inputSubscription) {
- inputSubscription.unsubscribe();
- }
- if (outputSubscription) {
- outputSubscription.unsubscribe();
- }
- if (dashboardContainer) {
- dashboardContainer.destroy();
- }
- });
- }
-}
diff --git a/src/plugins/dashboard/public/application/dashboard_state_manager.ts b/src/plugins/dashboard/public/application/dashboard_state_manager.ts
deleted file mode 100644
index ff8d7664f917..000000000000
--- a/src/plugins/dashboard/public/application/dashboard_state_manager.ts
+++ /dev/null
@@ -1,657 +0,0 @@
-/*
- * SPDX-License-Identifier: Apache-2.0
- *
- * The OpenSearch Contributors require contributions made to
- * this file be licensed under the Apache-2.0 license or a
- * compatible open source license.
- *
- * Any modifications Copyright OpenSearch Contributors. See
- * GitHub history for details.
- */
-
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { i18n } from '@osd/i18n';
-import _ from 'lodash';
-import { Observable, Subscription } from 'rxjs';
-import { Moment } from 'moment';
-import { History } from 'history';
-
-import { Filter, Query, TimefilterContract as Timefilter } from 'src/plugins/data/public';
-import { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
-import { migrateLegacyQuery } from './lib/migrate_legacy_query';
-
-import { ViewMode } from '../embeddable_plugin';
-import { getAppStateDefaults, migrateAppState, getDashboardIdFromUrl } from './lib';
-import { convertPanelStateToSavedDashboardPanel } from './lib/embeddable_saved_object_converters';
-import { FilterUtils } from './lib/filter_utils';
-import {
- DashboardAppState,
- DashboardAppStateDefaults,
- DashboardAppStateInUrl,
- DashboardAppStateTransitions,
- SavedDashboardPanel,
-} from '../types';
-import {
- createStateContainer,
- IOsdUrlStateStorage,
- ISyncStateRef,
- ReduxLikeStateContainer,
- syncState,
-} from '../../../opensearch_dashboards_utils/public';
-import { SavedObjectDashboard } from '../saved_dashboards';
-import { DashboardContainer } from './embeddable';
-
-/**
- * Dashboard state manager handles connecting angular and redux state between the angular and react portions of the
- * app. There are two "sources of truth" that need to stay in sync - AppState (aka the `_a` portion of the url) and
- * the Store. They aren't complete duplicates of each other as AppState has state that the Store doesn't, and vice
- * versa. They should be as decoupled as possible so updating the store won't affect bwc of urls.
- */
-export class DashboardStateManager {
- public savedDashboard: SavedObjectDashboard;
- public lastSavedDashboardFilters: {
- timeTo?: string | Moment;
- timeFrom?: string | Moment;
- filterBars: Filter[];
- query: Query;
- };
- private stateDefaults: DashboardAppStateDefaults;
- private hideWriteControls: boolean;
- private opensearchDashboardsVersion: string;
- public isDirty: boolean;
- private changeListeners: Array<(status: { dirty: boolean }) => void>;
-
- public get appState(): DashboardAppState {
- return this.stateContainer.get();
- }
-
- public get appState$(): Observable {
- return this.stateContainer.state$;
- }
-
- private readonly stateContainer: ReduxLikeStateContainer<
- DashboardAppState,
- DashboardAppStateTransitions
- >;
- private readonly stateContainerChangeSub: Subscription;
- private readonly STATE_STORAGE_KEY = '_a';
- private readonly osdUrlStateStorage: IOsdUrlStateStorage;
- private readonly stateSyncRef: ISyncStateRef;
- private readonly history: History;
- private readonly usageCollection: UsageCollectionSetup | undefined;
-
- /**
- *
- * @param savedDashboard
- * @param hideWriteControls true if write controls should be hidden.
- * @param opensearchDashboardsVersion current opensearchDashboardsVersion
- * @param
- */
- constructor({
- savedDashboard,
- hideWriteControls,
- opensearchDashboardsVersion,
- osdUrlStateStorage,
- history,
- usageCollection,
- }: {
- savedDashboard: SavedObjectDashboard;
- hideWriteControls: boolean;
- opensearchDashboardsVersion: string;
- osdUrlStateStorage: IOsdUrlStateStorage;
- history: History;
- usageCollection?: UsageCollectionSetup;
- }) {
- this.history = history;
- this.opensearchDashboardsVersion = opensearchDashboardsVersion;
- this.savedDashboard = savedDashboard;
- this.hideWriteControls = hideWriteControls;
- this.usageCollection = usageCollection;
-
- // get state defaults from saved dashboard, make sure it is migrated
- this.stateDefaults = migrateAppState(
- getAppStateDefaults(this.savedDashboard, this.hideWriteControls),
- opensearchDashboardsVersion,
- usageCollection
- );
-
- this.osdUrlStateStorage = osdUrlStateStorage;
-
- // setup initial state by merging defaults with state from url
- // also run migration, as state in url could be of older version
- const initialState = migrateAppState(
- {
- ...this.stateDefaults,
- ...this.osdUrlStateStorage.get(this.STATE_STORAGE_KEY),
- },
- opensearchDashboardsVersion,
- usageCollection
- );
-
- // setup state container using initial state both from defaults and from url
- this.stateContainer = createStateContainer(
- initialState,
- {
- set: (state) => (prop, value) => ({ ...state, [prop]: value }),
- setOption: (state) => (option, value) => ({
- ...state,
- options: {
- ...state.options,
- [option]: value,
- },
- }),
- }
- );
-
- this.isDirty = false;
-
- // We can't compare the filters stored on this.appState to this.savedDashboard because in order to apply
- // the filters to the visualizations, we need to save it on the dashboard. We keep track of the original
- // filter state in order to let the user know if their filters changed and provide this specific information
- // in the 'lose changes' warning message.
- this.lastSavedDashboardFilters = this.getFilterState();
-
- this.changeListeners = [];
-
- this.stateContainerChangeSub = this.stateContainer.state$.subscribe(() => {
- this.isDirty = this.checkIsDirty();
- this.changeListeners.forEach((listener) => listener({ dirty: this.isDirty }));
- });
-
- // setup state syncing utils. state container will be synced with url into `this.STATE_STORAGE_KEY` query param
- this.stateSyncRef = syncState({
- storageKey: this.STATE_STORAGE_KEY,
- stateContainer: {
- ...this.stateContainer,
- get: () => this.toUrlState(this.stateContainer.get()),
- set: (state: DashboardAppStateInUrl | null) => {
- // sync state required state container to be able to handle null
- // overriding set() so it could handle null coming from url
- if (state) {
- // Skip this update if current dashboardId in the url is different from what we have in the current instance of state manager
- // As dashboard is driven by angular at the moment, the destroy cycle happens async,
- // If the dashboardId has changed it means this instance
- // is going to be destroyed soon and we shouldn't sync state anymore,
- // as it could potentially trigger further url updates
- const currentDashboardIdInUrl = getDashboardIdFromUrl(history.location.pathname);
- if (currentDashboardIdInUrl !== this.savedDashboard.id) return;
-
- this.stateContainer.set({
- ...this.stateDefaults,
- ...state,
- });
- } else {
- // Do nothing in case when state from url is empty,
- // this fixes: https://github.com/elastic/kibana/issues/57789
- // There are not much cases when state in url could become empty:
- // 1. User manually removed `_a` from the url
- // 2. Browser is navigating away from the page and most likely there is no `_a` in the url.
- // In this case we don't want to do any state updates
- // and just allow $scope.$on('destroy') fire later and clean up everything
- }
- },
- },
- stateStorage: this.osdUrlStateStorage,
- });
- }
-
- public startStateSyncing() {
- this.saveState({ replace: true });
- this.stateSyncRef.start();
- }
-
- public registerChangeListener(callback: (status: { dirty: boolean }) => void) {
- this.changeListeners.push(callback);
- }
-
- public handleDashboardContainerChanges(dashboardContainer: DashboardContainer) {
- let dirty = false;
- let dirtyBecauseOfInitialStateMigration = false;
-
- const savedDashboardPanelMap: { [key: string]: SavedDashboardPanel } = {};
-
- const input = dashboardContainer.getInput();
- this.getPanels().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,
- this.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) {
- this.stateContainer.transitions.set('panels', Object.values(convertedPanelStateMap));
- if (dirtyBecauseOfInitialStateMigration) {
- this.saveState({ replace: true });
- }
- }
-
- if (input.isFullScreenMode !== this.getFullScreenMode()) {
- this.setFullScreenMode(input.isFullScreenMode);
- }
-
- if (input.expandedPanelId !== this.getExpandedPanelId()) {
- this.setExpandedPanelId(input.expandedPanelId);
- }
-
- if (!_.isEqual(input.query, this.getQuery())) {
- this.setQuery(input.query);
- }
-
- this.changeListeners.forEach((listener) => listener({ dirty }));
- }
-
- public getFullScreenMode() {
- return this.appState.fullScreenMode;
- }
-
- public setFullScreenMode(fullScreenMode: boolean) {
- this.stateContainer.transitions.set('fullScreenMode', fullScreenMode);
- }
-
- public getExpandedPanelId() {
- return this.appState.expandedPanelId;
- }
-
- public setExpandedPanelId(expandedPanelId?: string) {
- this.stateContainer.transitions.set('expandedPanelId', expandedPanelId);
- }
-
- public setFilters(filters: Filter[]) {
- this.stateContainer.transitions.set('filters', filters);
- }
-
- /**
- * Resets the state back to the last saved version of the dashboard.
- */
- public resetState() {
- // In order to show the correct warning, we have to store the unsaved
- // title on the dashboard object. We should fix this at some point, but this is how all the other object
- // save panels work at the moment.
- this.savedDashboard.title = this.savedDashboard.lastSavedTitle;
-
- // appState.reset uses the internal defaults to reset the state, but some of the default settings (e.g. the panels
- // array) point to the same object that is stored on appState and is getting modified.
- // The right way to fix this might be to ensure the defaults object stored on state is a deep
- // clone, but given how much code uses the state object, I determined that to be too risky of a change for
- // now. TODO: revisit this!
- this.stateDefaults = migrateAppState(
- getAppStateDefaults(this.savedDashboard, this.hideWriteControls),
- this.opensearchDashboardsVersion,
- this.usageCollection
- );
- // The original query won't be restored by the above because the query on this.savedDashboard is applied
- // in place in order for it to affect the visualizations.
- this.stateDefaults.query = this.lastSavedDashboardFilters.query;
- // Need to make a copy to ensure they are not overwritten.
- this.stateDefaults.filters = [...this.getLastSavedFilterBars()];
-
- this.isDirty = false;
- this.stateContainer.set(this.stateDefaults);
- }
-
- /**
- * Returns an object which contains the current filter state of this.savedDashboard.
- */
- public getFilterState() {
- return {
- timeTo: this.savedDashboard.timeTo,
- timeFrom: this.savedDashboard.timeFrom,
- filterBars: this.savedDashboard.getFilters(),
- query: this.savedDashboard.getQuery(),
- };
- }
-
- public getTitle() {
- return this.appState.title;
- }
-
- public isSaved() {
- return !!this.savedDashboard.id;
- }
-
- public isNew() {
- return !this.isSaved();
- }
-
- public getDescription() {
- return this.appState.description;
- }
-
- public setDescription(description: string) {
- this.stateContainer.transitions.set('description', description);
- }
-
- public setTitle(title: string) {
- this.savedDashboard.title = title;
- this.stateContainer.transitions.set('title', title);
- }
-
- public getAppState() {
- return this.stateContainer.get();
- }
-
- public getQuery(): Query {
- return migrateLegacyQuery(this.stateContainer.get().query);
- }
-
- public getSavedQueryId() {
- return this.stateContainer.get().savedQuery;
- }
-
- public setSavedQueryId(id?: string) {
- this.stateContainer.transitions.set('savedQuery', id);
- }
-
- public getUseMargins() {
- // Existing dashboards that don't define this should default to false.
- return this.appState.options.useMargins === undefined
- ? false
- : this.appState.options.useMargins;
- }
-
- public setUseMargins(useMargins: boolean) {
- this.stateContainer.transitions.setOption('useMargins', useMargins);
- }
-
- public getHidePanelTitles() {
- return this.appState.options.hidePanelTitles;
- }
-
- public setHidePanelTitles(hidePanelTitles: boolean) {
- this.stateContainer.transitions.setOption('hidePanelTitles', hidePanelTitles);
- }
-
- public getTimeRestore() {
- return this.appState.timeRestore;
- }
-
- public setTimeRestore(timeRestore: boolean) {
- this.stateContainer.transitions.set('timeRestore', timeRestore);
- }
-
- public getIsTimeSavedWithDashboard() {
- return this.savedDashboard.timeRestore;
- }
-
- public getLastSavedFilterBars(): Filter[] {
- return this.lastSavedDashboardFilters.filterBars;
- }
-
- public getLastSavedQuery() {
- return this.lastSavedDashboardFilters.query;
- }
-
- /**
- * @returns True if the query changed since the last time the dashboard was saved, or if it's a
- * new dashboard, if the query differs from the default.
- */
- public getQueryChanged() {
- const currentQuery = this.appState.query;
- const lastSavedQuery = this.getLastSavedQuery();
-
- const query = migrateLegacyQuery(currentQuery);
-
- const isLegacyStringQuery =
- _.isString(lastSavedQuery) && _.isPlainObject(currentQuery) && _.has(currentQuery, 'query');
- if (isLegacyStringQuery) {
- return lastSavedQuery !== query.query;
- }
-
- return !_.isEqual(currentQuery, lastSavedQuery);
- }
-
- /**
- * @returns True if the filter bar state has changed since the last time the dashboard was saved,
- * or if it's a new dashboard, if the query differs from the default.
- */
- public getFilterBarChanged() {
- return !_.isEqual(
- FilterUtils.cleanFiltersForComparison(this.appState.filters),
- FilterUtils.cleanFiltersForComparison(this.getLastSavedFilterBars())
- );
- }
-
- /**
- * @param timeFilter
- * @returns True if the time state has changed since the time saved with the dashboard.
- */
- public getTimeChanged(timeFilter: Timefilter) {
- return (
- !FilterUtils.areTimesEqual(
- this.lastSavedDashboardFilters.timeFrom,
- timeFilter.getTime().from
- ) ||
- !FilterUtils.areTimesEqual(this.lastSavedDashboardFilters.timeTo, timeFilter.getTime().to)
- );
- }
-
- public getViewMode() {
- return this.hideWriteControls ? ViewMode.VIEW : this.appState.viewMode;
- }
-
- public getIsViewMode() {
- return this.getViewMode() === ViewMode.VIEW;
- }
-
- public getIsEditMode() {
- return this.getViewMode() === ViewMode.EDIT;
- }
-
- /**
- *
- * @returns True if the dashboard has changed since the last save (or, is new).
- */
- public getIsDirty(timeFilter?: Timefilter) {
- // Filter bar comparison is done manually (see cleanFiltersForComparison for the reason) and time picker
- // changes are not tracked by the state monitor.
- const hasTimeFilterChanged = timeFilter ? this.getFiltersChanged(timeFilter) : false;
- return this.getIsEditMode() && (this.isDirty || hasTimeFilterChanged);
- }
-
- public getPanels(): SavedDashboardPanel[] {
- return this.appState.panels;
- }
-
- public updatePanel(panelIndex: string, panelAttributes: any) {
- const foundPanel = this.getPanels().find(
- (panel: SavedDashboardPanel) => panel.panelIndex === panelIndex
- );
- Object.assign(foundPanel, panelAttributes);
- return foundPanel;
- }
-
- /**
- * @param timeFilter
- * @returns An array of user friendly strings indicating the filter types that have changed.
- */
- public getChangedFilterTypes(timeFilter: Timefilter) {
- const changedFilters = [];
- if (this.getFilterBarChanged()) {
- changedFilters.push('filter');
- }
- if (this.getQueryChanged()) {
- changedFilters.push('query');
- }
- if (this.savedDashboard.timeRestore && this.getTimeChanged(timeFilter)) {
- changedFilters.push('time range');
- }
- return changedFilters;
- }
-
- /**
- * @returns True if filters (query, filter bar filters, and time picker if time is stored
- * with the dashboard) have changed since the last saved state (or if the dashboard hasn't been saved,
- * the default state).
- */
- public getFiltersChanged(timeFilter: Timefilter) {
- return this.getChangedFilterTypes(timeFilter).length > 0;
- }
-
- /**
- * Updates timeFilter to match the time saved with the dashboard.
- * @param timeFilter
- * @param timeFilter.setTime
- * @param timeFilter.setRefreshInterval
- */
- public syncTimefilterWithDashboardTime(timeFilter: Timefilter) {
- if (!this.getIsTimeSavedWithDashboard()) {
- throw new Error(
- i18n.translate('dashboard.stateManager.timeNotSavedWithDashboardErrorMessage', {
- defaultMessage: 'The time is not saved with this dashboard so should not be synced.',
- })
- );
- }
-
- if (this.savedDashboard.timeFrom && this.savedDashboard.timeTo) {
- timeFilter.setTime({
- from: this.savedDashboard.timeFrom,
- to: this.savedDashboard.timeTo,
- });
- }
- }
-
- /**
- * Updates timeFilter to match the refreshInterval saved with the dashboard.
- * @param timeFilter
- */
- public syncTimefilterWithDashboardRefreshInterval(timeFilter: Timefilter) {
- if (!this.getIsTimeSavedWithDashboard()) {
- throw new Error(
- i18n.translate('dashboard.stateManager.timeNotSavedWithDashboardErrorMessage', {
- defaultMessage: 'The time is not saved with this dashboard so should not be synced.',
- })
- );
- }
-
- if (this.savedDashboard.refreshInterval) {
- timeFilter.setRefreshInterval(this.savedDashboard.refreshInterval);
- }
- }
-
- /**
- * Synchronously writes current state to url
- * returned boolean indicates whether the update happened and if history was updated
- */
- private saveState({ replace }: { replace: boolean }): boolean {
- // schedules setting current state to url
- this.osdUrlStateStorage.set(
- this.STATE_STORAGE_KEY,
- this.toUrlState(this.stateContainer.get())
- );
- // immediately forces scheduled updates and changes location
- return this.osdUrlStateStorage.flush({ replace });
- }
-
- // TODO: find nicer solution for this
- // this function helps to make just 1 browser history update, when we imperatively changing the dashboard url
- // It could be that there is pending *dashboardStateManager* updates, which aren't flushed yet to the url.
- // So to prevent 2 browser updates:
- // 1. Force flush any pending state updates (syncing state to query)
- // 2. If url was updated, then apply path change with replace
- public changeDashboardUrl(pathname: string) {
- // synchronously persist current state to url with push()
- const updated = this.saveState({ replace: false });
- // change pathname
- this.history[updated ? 'replace' : 'push']({
- ...this.history.location,
- pathname,
- });
- }
-
- public setQuery(query: Query) {
- this.stateContainer.transitions.set('query', query);
- }
-
- /**
- * Applies the current filter state to the dashboard.
- * @param filter An array of filter bar filters.
- */
- public applyFilters(query: Query, filters: Filter[]) {
- this.savedDashboard.searchSource.setField('query', query);
- this.savedDashboard.searchSource.setField('filter', filters);
- this.stateContainer.transitions.set('query', query);
- }
-
- public switchViewMode(newMode: ViewMode) {
- this.stateContainer.transitions.set('viewMode', newMode);
- }
-
- /**
- * Destroys and cleans up this object when it's no longer used.
- */
- public destroy() {
- this.stateContainerChangeSub.unsubscribe();
- this.savedDashboard.destroy();
- if (this.stateSyncRef) {
- this.stateSyncRef.stop();
- }
- }
-
- private checkIsDirty() {
- // Filters need to be compared manually because they sometimes have a $$hashkey stored on the object.
- // Query needs to be compared manually because saved legacy queries get migrated in app state automatically
- const propsToIgnore: Array = ['viewMode', 'filters', 'query'];
-
- const initial = _.omit(this.stateDefaults, propsToIgnore);
- const current = _.omit(this.stateContainer.get(), propsToIgnore);
- return !_.isEqual(initial, current);
- }
-
- private toUrlState(state: DashboardAppState): DashboardAppStateInUrl {
- if (state.viewMode === ViewMode.VIEW) {
- const { panels, ...stateWithoutPanels } = state;
- return stateWithoutPanels;
- }
-
- return state;
- }
-}
diff --git a/src/plugins/dashboard/public/application/index.ts b/src/plugins/dashboard/public/application/index.ts
deleted file mode 100644
index 131a8a1e9c10..000000000000
--- a/src/plugins/dashboard/public/application/index.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * SPDX-License-Identifier: Apache-2.0
- *
- * The OpenSearch Contributors require contributions made to
- * this file be licensed under the Apache-2.0 license or a
- * compatible open source license.
- *
- * Any modifications Copyright OpenSearch Contributors. See
- * GitHub history for details.
- */
-
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export * from './embeddable';
-export * from './actions';
-export type { RenderDeps } from './application';
diff --git a/src/plugins/dashboard/public/application/index.tsx b/src/plugins/dashboard/public/application/index.tsx
new file mode 100644
index 000000000000..6ef2e641b62d
--- /dev/null
+++ b/src/plugins/dashboard/public/application/index.tsx
@@ -0,0 +1,42 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ *
+ * Any modifications Copyright OpenSearch Contributors. See
+ * GitHub history for details.
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Router } from 'react-router-dom';
+import { AppMountParameters } from 'opensearch-dashboards/public';
+import { OpenSearchDashboardsContextProvider } from '../../../opensearch_dashboards_react/public';
+import { addHelpMenuToAppChrome } from './help_menu/help_menu_util';
+import { DashboardApp } from './app';
+import { DashboardServices } from '../types';
+export * from './embeddable';
+export * from './actions';
+
+export const renderApp = (
+ { element, appBasePath, onAppLeave }: AppMountParameters,
+ services: DashboardServices
+) => {
+ addHelpMenuToAppChrome(services.chrome, services.docLinks);
+
+ const app = (
+
+
+
+
+
+
+
+ );
+
+ ReactDOM.render(app, element);
+
+ return () => ReactDOM.unmountComponentAtNode(element);
+};
diff --git a/src/plugins/dashboard/public/application/legacy_app.js b/src/plugins/dashboard/public/application/legacy_app.js
deleted file mode 100644
index 0c10653d7f41..000000000000
--- a/src/plugins/dashboard/public/application/legacy_app.js
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * SPDX-License-Identifier: Apache-2.0
- *
- * The OpenSearch Contributors require contributions made to
- * this file be licensed under the Apache-2.0 license or a
- * compatible open source license.
- *
- * Any modifications Copyright OpenSearch Contributors. See
- * GitHub history for details.
- */
-
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { i18n } from '@osd/i18n';
-import { parse } from 'query-string';
-
-import dashboardTemplate from './dashboard_app.html';
-import dashboardListingTemplate from './listing/dashboard_listing_ng_wrapper.html';
-import { createHashHistory } from 'history';
-
-import { initDashboardAppDirective } from './dashboard_app';
-import { createDashboardEditUrl, DashboardConstants } from '../dashboard_constants';
-import {
- createOsdUrlStateStorage,
- redirectWhenMissing,
- SavedObjectNotFound,
- withNotifyOnErrors,
-} from '../../../opensearch_dashboards_utils/public';
-import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing';
-import { addHelpMenuToAppChrome } from './help_menu/help_menu_util';
-import { syncQueryStateWithUrl } from '../../../data/public';
-
-export function initDashboardApp(app, deps) {
- initDashboardAppDirective(app, deps);
-
- app.directive('dashboardListing', function (reactDirective) {
- return reactDirective(DashboardListing, [
- ['core', { watchDepth: 'reference' }],
- ['dashboardProviders', { watchDepth: 'reference' }],
- ['createItem', { watchDepth: 'reference' }],
- ['editItem', { watchDepth: 'reference' }],
- ['viewItem', { watchDepth: 'reference' }],
- ['findItems', { watchDepth: 'reference' }],
- ['deleteItems', { watchDepth: 'reference' }],
- ['listingLimit', { watchDepth: 'reference' }],
- ['hideWriteControls', { watchDepth: 'reference' }],
- ['initialFilter', { watchDepth: 'reference' }],
- ['initialPageSize', { watchDepth: 'reference' }],
- ]);
- });
-
- function createNewDashboardCtrl($scope) {
- $scope.visitVisualizeAppLinkText = i18n.translate('dashboard.visitVisualizeAppLinkText', {
- defaultMessage: 'visit the Visualize app',
- });
- addHelpMenuToAppChrome(deps.chrome, deps.core.docLinks);
- }
-
- app.factory('history', () => createHashHistory());
- app.factory('osdUrlStateStorage', (history) =>
- createOsdUrlStateStorage({
- history,
- useHash: deps.uiSettings.get('state:storeInSessionStorage'),
- ...withNotifyOnErrors(deps.core.notifications.toasts),
- })
- );
-
- app.config(function ($routeProvider) {
- const defaults = {
- reloadOnSearch: false,
- requireUICapability: 'dashboard.show',
- badge: () => {
- if (deps.dashboardCapabilities.showWriteControls) {
- return undefined;
- }
-
- return {
- text: i18n.translate('dashboard.badge.readOnly.text', {
- defaultMessage: 'Read only',
- }),
- tooltip: i18n.translate('dashboard.badge.readOnly.tooltip', {
- defaultMessage: 'Unable to save dashboards',
- }),
- iconType: 'glasses',
- };
- },
- };
-
- $routeProvider
- .when('/', {
- redirectTo: DashboardConstants.LANDING_PAGE_PATH,
- })
- .when(DashboardConstants.LANDING_PAGE_PATH, {
- ...defaults,
- template: dashboardListingTemplate,
- controller: function ($scope, osdUrlStateStorage, history) {
- deps.core.chrome.docTitle.change(
- i18n.translate('dashboard.dashboardPageTitle', { defaultMessage: 'Dashboards' })
- );
- const dashboardConfig = deps.dashboardConfig;
-
- // syncs `_g` portion of url with query services
- const { stop: stopSyncingQueryServiceStateWithUrl } = syncQueryStateWithUrl(
- deps.data.query,
- osdUrlStateStorage
- );
-
- $scope.listingLimit = deps.savedObjects.settings.getListingLimit();
- $scope.initialPageSize = deps.savedObjects.settings.getPerPage();
- $scope.create = () => {
- history.push(DashboardConstants.CREATE_NEW_DASHBOARD_URL);
- };
- $scope.dashboardProviders = deps.dashboardProviders() || [];
- $scope.dashboardListTypes = Object.keys($scope.dashboardProviders);
-
- const mapListAttributesToDashboardProvider = (obj) => {
- const provider = $scope.dashboardProviders[obj.type];
- return {
- id: obj.id,
- appId: provider.appId,
- type: provider.savedObjectsName,
- ...obj.attributes,
- updated_at: obj.updated_at,
- viewUrl: provider.viewUrlPathFn(obj),
- editUrl: provider.editUrlPathFn(obj),
- };
- };
-
- $scope.find = async (search) => {
- const savedObjectsClient = deps.savedObjectsClient;
-
- const res = await savedObjectsClient.find({
- type: $scope.dashboardListTypes,
- search: search ? `${search}*` : undefined,
- fields: ['title', 'type', 'description', 'updated_at'],
- perPage: $scope.listingLimit,
- page: 1,
- searchFields: ['title^3', 'type', 'description'],
- defaultSearchOperator: 'AND',
- });
- const list = res.savedObjects?.map(mapListAttributesToDashboardProvider) || [];
-
- return {
- total: list.length,
- hits: list,
- };
- };
-
- $scope.editItem = ({ appId, editUrl }) => {
- if (appId === 'dashboard') {
- history.push(editUrl);
- } else {
- deps.core.application.navigateToUrl(editUrl);
- }
- };
- $scope.viewItem = ({ appId, viewUrl }) => {
- if (appId === 'dashboard') {
- history.push(viewUrl);
- } else {
- deps.core.application.navigateToUrl(viewUrl);
- }
- };
- $scope.delete = (dashboards) => {
- const ids = dashboards.map((d) => ({ id: d.id, appId: d.appId }));
- return Promise.all(
- ids.map(({ id, appId }) => {
- return deps.savedObjectsClient.delete(appId, id);
- })
- ).catch((error) => {
- deps.toastNotifications.addError(error, {
- title: i18n.translate('dashboard.dashboardListingDeleteErrorTitle', {
- defaultMessage: 'Error deleting dashboard',
- }),
- });
- });
- };
- $scope.hideWriteControls = dashboardConfig.getHideWriteControls();
- $scope.initialFilter = parse(history.location.search).filter || EMPTY_FILTER;
- deps.chrome.setBreadcrumbs([
- {
- text: i18n.translate('dashboard.dashboardBreadcrumbsTitle', {
- defaultMessage: 'Dashboards',
- }),
- },
- ]);
- addHelpMenuToAppChrome(deps.chrome, deps.core.docLinks);
- $scope.core = deps.core;
-
- $scope.$on('$destroy', () => {
- stopSyncingQueryServiceStateWithUrl();
- });
- },
- resolve: {
- dash: function ($route, history) {
- return deps.data.indexPatterns.ensureDefaultIndexPattern(history).then(() => {
- const savedObjectsClient = deps.savedObjectsClient;
- const title = $route.current.params.title;
- if (title) {
- return savedObjectsClient
- .find({
- search: `"${title}"`,
- search_fields: 'title',
- type: 'dashboard',
- })
- .then((results) => {
- // The search isn't an exact match, lets see if we can find a single exact match to use
- const matchingDashboards = results.savedObjects.filter(
- (dashboard) =>
- dashboard.attributes.title.toLowerCase() === title.toLowerCase()
- );
- if (matchingDashboards.length === 1) {
- history.replace(createDashboardEditUrl(matchingDashboards[0].id));
- } else {
- history.replace(`${DashboardConstants.LANDING_PAGE_PATH}?filter="${title}"`);
- $route.reload();
- }
- return new Promise(() => {});
- });
- }
- });
- },
- },
- })
- .when(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {
- ...defaults,
- template: dashboardTemplate,
- controller: createNewDashboardCtrl,
- requireUICapability: 'dashboard.createNew',
- resolve: {
- dash: (history) =>
- deps.data.indexPatterns
- .ensureDefaultIndexPattern(history)
- .then(() => deps.savedDashboards.get())
- .catch(
- redirectWhenMissing({
- history,
- navigateToApp: deps.core.application.navigateToApp,
- mapping: {
- dashboard: DashboardConstants.LANDING_PAGE_PATH,
- },
- toastNotifications: deps.core.notifications.toasts,
- })
- ),
- },
- })
- .when(createDashboardEditUrl(':id'), {
- ...defaults,
- template: dashboardTemplate,
- controller: createNewDashboardCtrl,
- resolve: {
- dash: function ($route, history) {
- const id = $route.current.params.id;
-
- return deps.data.indexPatterns
- .ensureDefaultIndexPattern(history)
- .then(() => deps.savedDashboards.get(id))
- .then((savedDashboard) => {
- deps.chrome.recentlyAccessed.add(
- savedDashboard.getFullPath(),
- savedDashboard.title,
- id
- );
- return 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.
- if (error instanceof SavedObjectNotFound && id === 'create') {
- // Note preserve querystring part is necessary so the state is preserved through the redirect.
- history.replace({
- ...history.location, // preserve query,
- pathname: DashboardConstants.CREATE_NEW_DASHBOARD_URL,
- });
-
- deps.core.notifications.toasts.addWarning(
- i18n.translate('dashboard.urlWasRemovedInSixZeroWarningMessage', {
- defaultMessage:
- 'The url "dashboard/create" was removed in 6.0. Please update your bookmarks.',
- })
- );
- return new Promise(() => {});
- } else {
- // E.g. a corrupt or deleted dashboard
- deps.core.notifications.toasts.addDanger(error.message);
- history.push(DashboardConstants.LANDING_PAGE_PATH);
- return new Promise(() => {});
- }
- });
- },
- },
- })
- .otherwise({
- resolveRedirectTo: function ($rootScope) {
- const path = window.location.hash.substr(1);
- deps.restorePreviousUrl();
- $rootScope.$applyAsync(() => {
- const { navigated } = deps.navigateToLegacyOpenSearchDashboardsUrl(path);
- if (!navigated) {
- deps.navigateToDefaultApp();
- }
- });
- // prevent angular from completing the navigation
- return new Promise(() => {});
- },
- });
- });
-}
diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing_ng_wrapper.html b/src/plugins/dashboard/public/application/listing/dashboard_listing_ng_wrapper.html
deleted file mode 100644
index 5e19fbfe678b..000000000000
--- a/src/plugins/dashboard/public/application/listing/dashboard_listing_ng_wrapper.html
+++ /dev/null
@@ -1,14 +0,0 @@
-
diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx
index 87f934925899..5a5df77c1310 100644
--- a/src/plugins/dashboard/public/plugin.tsx
+++ b/src/plugins/dashboard/public/plugin.tsx
@@ -47,6 +47,7 @@ import {
} from 'src/core/public';
import { UrlForwardingSetup, UrlForwardingStart } from 'src/plugins/url_forwarding/public';
import { isEmpty } from 'lodash';
+import { createHashHistory } from 'history';
import { UsageCollectionSetup } from '../../usage_collection/public';
import {
CONTEXT_MENU_TRIGGER,
@@ -72,7 +73,12 @@ import {
ExitFullScreenButton as ExitFullScreenButtonUi,
ExitFullScreenButtonProps,
} from '../../opensearch_dashboards_react/public';
-import { createOsdUrlTracker, Storage } from '../../opensearch_dashboards_utils/public';
+import {
+ createOsdUrlTracker,
+ Storage,
+ createOsdUrlStateStorage,
+ withNotifyOnErrors,
+} from '../../opensearch_dashboards_utils/public';
import {
initAngularBootstrap,
OpenSearchDashboardsLegacySetup,
@@ -93,7 +99,6 @@ import {
DashboardContainerFactoryDefinition,
ExpandPanelAction,
ExpandPanelActionContext,
- RenderDeps,
ReplacePanelAction,
ReplacePanelActionContext,
ACTION_UNLINK_FROM_LIBRARY,
@@ -121,7 +126,7 @@ import {
AttributeServiceOptions,
ATTRIBUTE_SERVICE_KEY,
} from './attribute_service/attribute_service';
-import { DashboardProvider } from './types';
+import { DashboardProvider, DashboardServices } from './types';
declare module '../../share/public' {
export interface UrlGeneratorStateMapping {
@@ -380,8 +385,17 @@ export class DashboardPlugin
savedObjects,
} = pluginsStart;
- const deps: RenderDeps = {
+ const history = createHashHistory(); // need more research
+ const services: DashboardServices = {
+ ...coreStart,
pluginInitializerContext: this.initializerContext,
+ opensearchDashboardsVersion: this.initializerContext.env.packageInfo.version,
+ history,
+ osdUrlStateStorage: createOsdUrlStateStorage({
+ history,
+ useHash: coreStart.uiSettings.get('state:storeInSessionStorage'),
+ ...withNotifyOnErrors(coreStart.notifications.toasts),
+ }),
core: coreStart,
dashboardConfig,
navigateToDefaultApp,
@@ -406,14 +420,14 @@ export class DashboardPlugin
usageCollection,
scopedHistory: () => this.currentHistory!,
setHeaderActionMenu: params.setHeaderActionMenu,
- savedObjects,
+ savedObjectsPublic: savedObjects,
restorePreviousUrl,
};
// make sure the index pattern list is up to date
await dataStart.indexPatterns.clearCache();
- const { renderApp } = await import('./application/application');
params.element.classList.add('dshAppContainer');
- const unmount = renderApp(params.element, params.appBasePath, deps);
+ const { renderApp } = await import('./application');
+ const unmount = renderApp(params, services);
return () => {
unmount();
appUnMounted();
diff --git a/src/plugins/dashboard/public/types.ts b/src/plugins/dashboard/public/types.ts
index 42a07b40ef1c..4de26fdebadf 100644
--- a/src/plugins/dashboard/public/types.ts
+++ b/src/plugins/dashboard/public/types.ts
@@ -28,10 +28,29 @@
* under the License.
*/
-import { Query, Filter } from 'src/plugins/data/public';
-import { SavedObject as SavedObjectType, SavedObjectAttributes } from 'src/core/public';
+import { Query, Filter, DataPublicPluginStart } from 'src/plugins/data/public';
+import {
+ SavedObject as SavedObjectType,
+ SavedObjectAttributes,
+ CoreStart,
+ PluginInitializerContext,
+ SavedObjectsClientContract,
+ IUiSettingsClient,
+ ChromeStart,
+ ScopedHistory,
+ AppMountParameters,
+ SavedObjectsStart,
+} from 'src/core/public';
+import { IOsdUrlStateStorage } from 'src/plugins/opensearch_dashboards_utils/public';
+import { SavedObjectLoader } from 'src/plugins/saved_objects/public';
+import { OpenSearchDashboardsLegacyStart } from 'src/plugins/opensearch_dashboards_legacy/public';
+import { SharePluginStart } from 'src/plugins/share/public';
+import { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
+import { UrlForwardingStart } from 'src/plugins/url_forwarding/public';
+import { History } from 'history';
+import { NavigationPublicPluginStart as NavigationStart } from '../../navigation/public';
+import { EmbeddableStart, ViewMode } from './embeddable_plugin';
import { SavedDashboardPanel730ToLatest } from '../common';
-import { ViewMode } from './embeddable_plugin';
export interface DashboardCapabilities {
showWriteControls: boolean;
@@ -212,3 +231,36 @@ export interface DashboardProvider {
// "http://../app/myplugin#/edit/abc123"
editUrlPathFn: (obj: SavedObjectType) => string;
}
+
+export interface DashboardServices extends CoreStart {
+ pluginInitializerContext: PluginInitializerContext;
+ opensearchDashboardsVersion: string;
+ history: History;
+ osdUrlStateStorage: IOsdUrlStateStorage;
+ core: CoreStart;
+ data: DataPublicPluginStart;
+ navigation: NavigationStart;
+ savedObjectsClient: SavedObjectsClientContract;
+ savedDashboards: SavedObjectLoader;
+ dashboardProviders: () => { [key: string]: DashboardProvider };
+ dashboardConfig: OpenSearchDashboardsLegacyStart['dashboardConfig'];
+ dashboardCapabilities: any;
+ embeddableCapabilities: {
+ visualizeCapabilities: any;
+ mapsCapabilities: any;
+ };
+ uiSettings: IUiSettingsClient;
+ chrome: ChromeStart;
+ savedQueryService: DataPublicPluginStart['query']['savedQueries'];
+ embeddable: EmbeddableStart;
+ localStorage: Storage;
+ share?: SharePluginStart;
+ usageCollection?: UsageCollectionSetup;
+ navigateToDefaultApp: UrlForwardingStart['navigateToDefaultApp'];
+ navigateToLegacyOpenSearchDashboardsUrl: UrlForwardingStart['navigateToLegacyOpenSearchDashboardsUrl'];
+ scopedHistory: () => ScopedHistory;
+ setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
+ savedObjectsPublic: SavedObjectsStart;
+ restorePreviousUrl: () => void;
+ addBasePath?: (url: string) => string;
+}