mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* Kibana app migration: Shim dashboard (#48913) * fix location of feature catalogue
This commit is contained in:
parent
b9e2fbf71b
commit
43d8a53cdb
56 changed files with 1906 additions and 1258 deletions
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { AppStateClass } from 'ui/state_management/app_state';
|
||||
import { AppStateClass } from '../legacy_imports';
|
||||
|
||||
/**
|
||||
* A poor excuse for a mock just to get some basic tests to run in jest without requiring the injector.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.dshAppContainer {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dshStartScreen {
|
||||
|
|
228
src/legacy/core_plugins/kibana/public/dashboard/application.ts
Normal file
228
src/legacy/core_plugins/kibana/public/dashboard/application.ts
Normal file
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* 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 { EuiConfirmModal, EuiIcon } from '@elastic/eui';
|
||||
import angular, { IModule } from 'angular';
|
||||
import { IPrivate } from 'ui/private';
|
||||
import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular';
|
||||
import {
|
||||
AppMountContext,
|
||||
ChromeStart,
|
||||
LegacyCoreStart,
|
||||
SavedObjectsClientContract,
|
||||
UiSettingsClientContract,
|
||||
} from 'kibana/public';
|
||||
import { Storage } from '../../../../../plugins/kibana_utils/public';
|
||||
import {
|
||||
GlobalStateProvider,
|
||||
StateManagementConfigProvider,
|
||||
AppStateProvider,
|
||||
PrivateProvider,
|
||||
EventsProvider,
|
||||
PersistedState,
|
||||
createTopNavDirective,
|
||||
createTopNavHelper,
|
||||
PromiseServiceCreator,
|
||||
KbnUrlProvider,
|
||||
RedirectWhenMissingProvider,
|
||||
confirmModalFactory,
|
||||
configureAppAngularModule,
|
||||
} from './legacy_imports';
|
||||
|
||||
// @ts-ignore
|
||||
import { initDashboardApp } from './legacy_app';
|
||||
import { DataStart } from '../../../data/public';
|
||||
import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public';
|
||||
import { NavigationStart } from '../../../navigation/public';
|
||||
import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public';
|
||||
import { SharePluginStart } from '../../../../../plugins/share/public';
|
||||
|
||||
export interface RenderDeps {
|
||||
core: LegacyCoreStart;
|
||||
indexPatterns: DataStart['indexPatterns']['indexPatterns'];
|
||||
dataStart: DataStart;
|
||||
npDataStart: NpDataStart;
|
||||
navigation: NavigationStart;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
savedObjectRegistry: any;
|
||||
dashboardConfig: any;
|
||||
savedDashboards: any;
|
||||
dashboardCapabilities: any;
|
||||
uiSettings: UiSettingsClientContract;
|
||||
chrome: ChromeStart;
|
||||
addBasePath: (path: string) => string;
|
||||
savedQueryService: DataStart['search']['services']['savedQueryService'];
|
||||
embeddables: ReturnType<EmbeddablePublicPlugin['start']>;
|
||||
localStorage: Storage;
|
||||
share: SharePluginStart;
|
||||
}
|
||||
|
||||
let angularModuleInstance: IModule | null = null;
|
||||
|
||||
export const renderApp = (element: HTMLElement, appBasePath: string, deps: RenderDeps) => {
|
||||
if (!angularModuleInstance) {
|
||||
angularModuleInstance = createLocalAngularModule(deps.core, deps.navigation);
|
||||
// global routing stuff
|
||||
configureAppAngularModule(angularModuleInstance, deps.core as LegacyCoreStart, true);
|
||||
// custom routing stuff
|
||||
initDashboardApp(angularModuleInstance, deps);
|
||||
}
|
||||
const $injector = mountDashboardApp(appBasePath, element);
|
||||
return () => {
|
||||
$injector.get('$rootScope').$destroy();
|
||||
};
|
||||
};
|
||||
|
||||
const mainTemplate = (basePath: string) => `<div style="height: 100%">
|
||||
<base href="${basePath}" />
|
||||
<div ng-view style="height: 100%;"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const moduleName = 'app/dashboard';
|
||||
|
||||
const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react'];
|
||||
|
||||
function mountDashboardApp(appBasePath: string, element: HTMLElement) {
|
||||
const mountpoint = document.createElement('div');
|
||||
mountpoint.setAttribute('style', 'height: 100%');
|
||||
// eslint-disable-next-line
|
||||
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(core: AppMountContext['core'], navigation: NavigationStart) {
|
||||
createLocalI18nModule();
|
||||
createLocalPrivateModule();
|
||||
createLocalPromiseModule();
|
||||
createLocalConfigModule(core);
|
||||
createLocalKbnUrlModule();
|
||||
createLocalStateModule();
|
||||
createLocalPersistedStateModule();
|
||||
createLocalTopNavModule(navigation);
|
||||
createLocalConfirmModalModule();
|
||||
createLocalIconModule();
|
||||
|
||||
const dashboardAngularModule = angular.module(moduleName, [
|
||||
...thirdPartyAngularDependencies,
|
||||
'app/dashboard/Config',
|
||||
'app/dashboard/I18n',
|
||||
'app/dashboard/Private',
|
||||
'app/dashboard/PersistedState',
|
||||
'app/dashboard/TopNav',
|
||||
'app/dashboard/State',
|
||||
'app/dashboard/ConfirmModal',
|
||||
'app/dashboard/icon',
|
||||
]);
|
||||
return dashboardAngularModule;
|
||||
}
|
||||
|
||||
function createLocalIconModule() {
|
||||
angular
|
||||
.module('app/dashboard/icon', ['react'])
|
||||
.directive('icon', reactDirective => reactDirective(EuiIcon));
|
||||
}
|
||||
|
||||
function createLocalConfirmModalModule() {
|
||||
angular
|
||||
.module('app/dashboard/ConfirmModal', ['react'])
|
||||
.factory('confirmModal', confirmModalFactory)
|
||||
.directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal));
|
||||
}
|
||||
|
||||
function createLocalStateModule() {
|
||||
angular
|
||||
.module('app/dashboard/State', [
|
||||
'app/dashboard/Private',
|
||||
'app/dashboard/Config',
|
||||
'app/dashboard/KbnUrl',
|
||||
'app/dashboard/Promise',
|
||||
'app/dashboard/PersistedState',
|
||||
])
|
||||
.factory('AppState', function(Private: any) {
|
||||
return Private(AppStateProvider);
|
||||
})
|
||||
.service('getAppState', function(Private: any) {
|
||||
return Private(AppStateProvider).getAppState;
|
||||
})
|
||||
.service('globalState', function(Private: any) {
|
||||
return Private(GlobalStateProvider);
|
||||
});
|
||||
}
|
||||
|
||||
function createLocalPersistedStateModule() {
|
||||
angular
|
||||
.module('app/dashboard/PersistedState', ['app/dashboard/Private', 'app/dashboard/Promise'])
|
||||
.factory('PersistedState', (Private: IPrivate) => {
|
||||
const Events = Private(EventsProvider);
|
||||
return class AngularPersistedState extends PersistedState {
|
||||
constructor(value: any, path: any) {
|
||||
super(value, path, Events);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function createLocalKbnUrlModule() {
|
||||
angular
|
||||
.module('app/dashboard/KbnUrl', ['app/dashboard/Private', 'ngRoute'])
|
||||
.service('kbnUrl', (Private: IPrivate) => Private(KbnUrlProvider))
|
||||
.service('redirectWhenMissing', (Private: IPrivate) => Private(RedirectWhenMissingProvider));
|
||||
}
|
||||
|
||||
function createLocalConfigModule(core: AppMountContext['core']) {
|
||||
angular
|
||||
.module('app/dashboard/Config', ['app/dashboard/Private'])
|
||||
.provider('stateManagementConfig', StateManagementConfigProvider)
|
||||
.provider('config', () => {
|
||||
return {
|
||||
$get: () => ({
|
||||
get: core.uiSettings.get.bind(core.uiSettings),
|
||||
}),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function createLocalPromiseModule() {
|
||||
angular.module('app/dashboard/Promise', []).service('Promise', PromiseServiceCreator);
|
||||
}
|
||||
|
||||
function createLocalPrivateModule() {
|
||||
angular.module('app/dashboard/Private', []).provider('Private', PrivateProvider);
|
||||
}
|
||||
|
||||
function createLocalTopNavModule(navigation: NavigationStart) {
|
||||
angular
|
||||
.module('app/dashboard/TopNav', ['react'])
|
||||
.directive('kbnTopNav', createTopNavDirective)
|
||||
.directive('kbnTopNavHelper', createTopNavHelper(navigation.ui));
|
||||
}
|
||||
|
||||
function createLocalI18nModule() {
|
||||
angular
|
||||
.module('app/dashboard/I18n', [])
|
||||
.provider('i18n', I18nProvider)
|
||||
.filter('i18n', i18nFilter)
|
||||
.directive('i18nId', i18nDirective);
|
||||
}
|
|
@ -4,11 +4,11 @@
|
|||
>
|
||||
<!-- Local nav. -->
|
||||
<kbn-top-nav
|
||||
ng-show="chrome.getVisible()"
|
||||
ng-show="isVisible"
|
||||
app-name="'dashboard'"
|
||||
config="topNavMenu"
|
||||
|
||||
show-search-bar="chrome.getVisible()"
|
||||
show-search-bar="isVisible"
|
||||
show-filter-bar="showFilterBar()"
|
||||
show-save-query="showSaveQuery"
|
||||
|
||||
|
@ -34,13 +34,21 @@
|
|||
The top nav is hidden in embed mode but the filter bar must still be present so
|
||||
we show the filter bar on its own here if the chrome is not visible.
|
||||
-->
|
||||
<filter-bar
|
||||
ng-if="showFilterBar() && !chrome.getVisible()"
|
||||
<kbn-top-nav
|
||||
ng-if="showFilterBar() && !isVisible"
|
||||
class-name="'globalFilterGroup__filterBar'"
|
||||
|
||||
app-name="'dashboard'"
|
||||
show-search-bar="true"
|
||||
show-filter-bar="true"
|
||||
show-save-query="false"
|
||||
show-date-picker="false"
|
||||
|
||||
filters="model.filters"
|
||||
on-filters-updated="onFiltersUpdated"
|
||||
index-patterns="indexPatterns"
|
||||
></filter-bar>
|
||||
on-filters-updated="onFiltersUpdated"
|
||||
>
|
||||
</kbn-top-nav>
|
||||
|
||||
<div ng-show="getShouldShowEditHelp() || getShouldShowViewHelp()" class="dshStartScreen">
|
||||
<div class="euiPanel euiPanel--paddingLarge euiPageContent euiPageContent--horizontalCenter">
|
||||
|
|
|
@ -17,26 +17,16 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
// @ts-ignore
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { IInjector } from 'ui/chrome';
|
||||
|
||||
// @ts-ignore
|
||||
import * as filterActions from 'plugins/kibana/discover/doc_table/actions/filter';
|
||||
import { StaticIndexPattern, SavedQuery } from 'plugins/data';
|
||||
import moment from 'moment';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import {
|
||||
AppStateClass as TAppStateClass,
|
||||
AppState as TAppState,
|
||||
} from 'ui/state_management/app_state';
|
||||
|
||||
import { KbnUrl } from 'ui/url/kbn_url';
|
||||
import { IndexPattern } from 'ui/index_patterns';
|
||||
import { IPrivate } from 'ui/private';
|
||||
import { StaticIndexPattern, SavedQuery } from 'plugins/data';
|
||||
import moment from 'moment';
|
||||
import { Subscription } from 'rxjs';
|
||||
IInjector,
|
||||
KbnUrl,
|
||||
} from './legacy_imports';
|
||||
|
||||
import { ViewMode } from '../../../embeddable_api/public/np_ready/public';
|
||||
import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard';
|
||||
|
@ -44,6 +34,7 @@ import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn } from './types'
|
|||
import { TimeRange, Query, esFilters } from '../../../../../../src/plugins/data/public';
|
||||
|
||||
import { DashboardAppController } from './dashboard_app_controller';
|
||||
import { RenderDeps } from './application';
|
||||
|
||||
export interface DashboardAppScope extends ng.IScope {
|
||||
dash: SavedObjectDashboard;
|
||||
|
@ -90,54 +81,40 @@ export interface DashboardAppScope extends ng.IScope {
|
|||
kbnTopNav: any;
|
||||
enterEditMode: () => void;
|
||||
timefilterSubscriptions$: Subscription;
|
||||
isVisible: boolean;
|
||||
}
|
||||
|
||||
const app = uiModules.get('app/dashboard', ['elasticsearch', 'ngRoute', 'react', 'kibana/config']);
|
||||
export function initDashboardAppDirective(app: any, deps: RenderDeps) {
|
||||
app.directive('dashboardApp', function($injector: IInjector) {
|
||||
const AppState = $injector.get<TAppStateClass<DashboardAppState>>('AppState');
|
||||
const kbnUrl = $injector.get<KbnUrl>('kbnUrl');
|
||||
const confirmModal = $injector.get<ConfirmModalFn>('confirmModal');
|
||||
const config = deps.uiSettings;
|
||||
|
||||
app.directive('dashboardApp', function($injector: IInjector) {
|
||||
const AppState = $injector.get<TAppStateClass<DashboardAppState>>('AppState');
|
||||
const kbnUrl = $injector.get<KbnUrl>('kbnUrl');
|
||||
const confirmModal = $injector.get<ConfirmModalFn>('confirmModal');
|
||||
const config = $injector.get('config');
|
||||
|
||||
const Private = $injector.get<IPrivate>('Private');
|
||||
|
||||
const indexPatterns = $injector.get<{
|
||||
getDefault: () => Promise<IndexPattern>;
|
||||
}>('indexPatterns');
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
controllerAs: 'dashboardApp',
|
||||
controller: (
|
||||
$scope: DashboardAppScope,
|
||||
$route: any,
|
||||
$routeParams: {
|
||||
id?: string;
|
||||
},
|
||||
getAppState: {
|
||||
previouslyStored: () => TAppState | undefined;
|
||||
},
|
||||
dashboardConfig: {
|
||||
getHideWriteControls: () => boolean;
|
||||
},
|
||||
localStorage: {
|
||||
get: (prop: string) => unknown;
|
||||
}
|
||||
) =>
|
||||
new DashboardAppController({
|
||||
$route,
|
||||
$scope,
|
||||
$routeParams,
|
||||
getAppState,
|
||||
dashboardConfig,
|
||||
localStorage,
|
||||
Private,
|
||||
kbnUrl,
|
||||
AppStateClass: AppState,
|
||||
indexPatterns,
|
||||
config,
|
||||
confirmModal,
|
||||
}),
|
||||
};
|
||||
});
|
||||
return {
|
||||
restrict: 'E',
|
||||
controllerAs: 'dashboardApp',
|
||||
controller: (
|
||||
$scope: DashboardAppScope,
|
||||
$route: any,
|
||||
$routeParams: {
|
||||
id?: string;
|
||||
},
|
||||
getAppState: any,
|
||||
globalState: any
|
||||
) =>
|
||||
new DashboardAppController({
|
||||
$route,
|
||||
$scope,
|
||||
$routeParams,
|
||||
getAppState,
|
||||
globalState,
|
||||
kbnUrl,
|
||||
AppStateClass: AppState,
|
||||
config,
|
||||
confirmModal,
|
||||
...deps,
|
||||
}),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -23,41 +23,23 @@ import React from 'react';
|
|||
import angular from 'angular';
|
||||
import { uniq } from 'lodash';
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
import { subscribeWithScope } from 'ui/utils/subscribe_with_scope';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
|
||||
// @ts-ignore
|
||||
import { ConfirmationButtonTypes } from 'ui/modals/confirm_modal';
|
||||
import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter';
|
||||
|
||||
import { docTitle } from 'ui/doc_title/doc_title';
|
||||
|
||||
import { showSaveModal, SaveResult } from 'ui/saved_objects/show_saved_object_save_modal';
|
||||
|
||||
import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query';
|
||||
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
|
||||
import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing/get_unhashable_states_provider';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import {
|
||||
subscribeWithScope,
|
||||
ConfirmationButtonTypes,
|
||||
showSaveModal,
|
||||
SaveResult,
|
||||
migrateLegacyQuery,
|
||||
State,
|
||||
AppStateClass as TAppStateClass,
|
||||
AppState as TAppState,
|
||||
} from 'ui/state_management/app_state';
|
||||
|
||||
import { KbnUrl } from 'ui/url/kbn_url';
|
||||
import { IndexPattern } from 'ui/index_patterns';
|
||||
import { IPrivate } from 'ui/private';
|
||||
import { SavedQuery } from 'src/legacy/core_plugins/data/public';
|
||||
import { SaveOptions } from 'ui/saved_objects/saved_object';
|
||||
import { capabilities } from 'ui/capabilities';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { unhashUrl } from 'ui/state_management/state_hashing';
|
||||
import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder';
|
||||
KbnUrl,
|
||||
SaveOptions,
|
||||
SavedObjectFinder,
|
||||
unhashUrl,
|
||||
} from './legacy_imports';
|
||||
import { FilterStateManager, IndexPattern, SavedQuery } from '../../../data/public';
|
||||
import { Query } from '../../../../../plugins/data/public';
|
||||
import { start as data } from '../../../data/public/legacy';
|
||||
|
||||
import {
|
||||
DashboardContainer,
|
||||
|
@ -72,7 +54,6 @@ import {
|
|||
ViewMode,
|
||||
openAddPanelFlyout,
|
||||
} from '../../../embeddable_api/public/np_ready/public';
|
||||
import { start } from '../../../embeddable_api/public/np_ready/public/legacy';
|
||||
import { DashboardAppState, NavAction, ConfirmModalFn, SavedDashboardPanel } from './types';
|
||||
|
||||
import { showOptionsPopover } from './top_nav/show_options_popover';
|
||||
|
@ -87,8 +68,23 @@ import { getDashboardTitle } from './dashboard_strings';
|
|||
import { DashboardAppScope } from './dashboard_app';
|
||||
import { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize/embeddable';
|
||||
import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters';
|
||||
import { RenderDeps } from './application';
|
||||
|
||||
const { savedQueryService } = data.search.services;
|
||||
export interface DashboardAppControllerDependencies extends RenderDeps {
|
||||
$scope: DashboardAppScope;
|
||||
$route: any;
|
||||
$routeParams: any;
|
||||
getAppState: any;
|
||||
globalState: State;
|
||||
indexPatterns: {
|
||||
getDefault: () => Promise<IndexPattern>;
|
||||
};
|
||||
dashboardConfig: any;
|
||||
kbnUrl: KbnUrl;
|
||||
AppStateClass: TAppStateClass<DashboardAppState>;
|
||||
config: any;
|
||||
confirmModal: ConfirmModalFn;
|
||||
}
|
||||
|
||||
export class DashboardAppController {
|
||||
// Part of the exposed plugin API - do not remove without careful consideration.
|
||||
|
@ -101,58 +97,55 @@ export class DashboardAppController {
|
|||
$route,
|
||||
$routeParams,
|
||||
getAppState,
|
||||
globalState,
|
||||
dashboardConfig,
|
||||
localStorage,
|
||||
Private,
|
||||
kbnUrl,
|
||||
AppStateClass,
|
||||
indexPatterns,
|
||||
config,
|
||||
confirmModal,
|
||||
}: {
|
||||
$scope: DashboardAppScope;
|
||||
$route: any;
|
||||
$routeParams: any;
|
||||
getAppState: {
|
||||
previouslyStored: () => TAppState | undefined;
|
||||
};
|
||||
indexPatterns: {
|
||||
getDefault: () => Promise<IndexPattern>;
|
||||
};
|
||||
dashboardConfig: any;
|
||||
localStorage: {
|
||||
get: (prop: string) => unknown;
|
||||
};
|
||||
Private: IPrivate;
|
||||
kbnUrl: KbnUrl;
|
||||
AppStateClass: TAppStateClass<DashboardAppState>;
|
||||
config: any;
|
||||
confirmModal: ConfirmModalFn;
|
||||
}) {
|
||||
const queryFilter = Private(FilterBarQueryFilterProvider);
|
||||
const getUnhashableStates = Private(getUnhashableStatesProvider);
|
||||
savedQueryService,
|
||||
embeddables,
|
||||
share,
|
||||
dashboardCapabilities,
|
||||
npDataStart: {
|
||||
query: {
|
||||
filterManager,
|
||||
timefilter: { timefilter },
|
||||
},
|
||||
},
|
||||
core: { notifications, overlays, chrome, injectedMetadata },
|
||||
}: DashboardAppControllerDependencies) {
|
||||
new FilterStateManager(globalState, getAppState, filterManager);
|
||||
const queryFilter = filterManager;
|
||||
|
||||
function getUnhashableStates(): State[] {
|
||||
return [getAppState(), globalState].filter(Boolean);
|
||||
}
|
||||
|
||||
let lastReloadRequestTime = 0;
|
||||
|
||||
const dash = ($scope.dash = $route.current.locals.dash);
|
||||
if (dash.id) {
|
||||
docTitle.change(dash.title);
|
||||
chrome.docTitle.change(dash.title);
|
||||
}
|
||||
|
||||
const dashboardStateManager = new DashboardStateManager({
|
||||
savedDashboard: dash,
|
||||
AppStateClass,
|
||||
hideWriteControls: dashboardConfig.getHideWriteControls(),
|
||||
kibanaVersion: injectedMetadata.getKibanaVersion(),
|
||||
});
|
||||
|
||||
$scope.appState = dashboardStateManager.getAppState();
|
||||
|
||||
// The 'previouslyStored' check is so we only update the time filter on dashboard open, not during
|
||||
// The hash check is so we only update the time filter on dashboard open, not during
|
||||
// normal cross app navigation.
|
||||
if (dashboardStateManager.getIsTimeSavedWithDashboard() && !getAppState.previouslyStored()) {
|
||||
if (dashboardStateManager.getIsTimeSavedWithDashboard() && !globalState.$inheritedGlobalState) {
|
||||
dashboardStateManager.syncTimefilterWithDashboard(timefilter);
|
||||
}
|
||||
$scope.showSaveQuery = capabilities.get().dashboard.saveQuery as boolean;
|
||||
$scope.showSaveQuery = dashboardCapabilities.saveQuery as boolean;
|
||||
|
||||
const updateIndexPatterns = (container?: DashboardContainer) => {
|
||||
if (!container || isErrorEmbeddable(container)) {
|
||||
|
@ -187,10 +180,7 @@ export class DashboardAppController {
|
|||
[key: string]: DashboardPanelState;
|
||||
} = {};
|
||||
dashboardStateManager.getPanels().forEach((panel: SavedDashboardPanel) => {
|
||||
embeddablesMap[panel.panelIndex] = convertSavedDashboardPanelToPanelState(
|
||||
panel,
|
||||
dashboardStateManager.getUseMargins()
|
||||
);
|
||||
embeddablesMap[panel.panelIndex] = convertSavedDashboardPanelToPanelState(panel);
|
||||
});
|
||||
let expandedPanelId;
|
||||
if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) {
|
||||
|
@ -239,7 +229,7 @@ export class DashboardAppController {
|
|||
let outputSubscription: Subscription | undefined;
|
||||
|
||||
const dashboardDom = document.getElementById('dashboardViewport');
|
||||
const dashboardFactory = start.getEmbeddableFactory(
|
||||
const dashboardFactory = embeddables.getEmbeddableFactory(
|
||||
DASHBOARD_CONTAINER_TYPE
|
||||
) as DashboardContainerFactory;
|
||||
dashboardFactory
|
||||
|
@ -334,7 +324,7 @@ export class DashboardAppController {
|
|||
|
||||
// Push breadcrumbs to new header navigation
|
||||
const updateBreadcrumbs = () => {
|
||||
chrome.breadcrumbs.set([
|
||||
chrome.setBreadcrumbs([
|
||||
{
|
||||
text: i18n.translate('kbn.dashboard.dashboardAppBreadcrumbsTitle', {
|
||||
defaultMessage: 'Dashboard',
|
||||
|
@ -495,7 +485,7 @@ export class DashboardAppController {
|
|||
});
|
||||
|
||||
$scope.$watch(
|
||||
() => capabilities.get().dashboard.saveQuery,
|
||||
() => dashboardCapabilities.saveQuery,
|
||||
newCapability => {
|
||||
$scope.showSaveQuery = newCapability as boolean;
|
||||
}
|
||||
|
@ -595,7 +585,7 @@ export class DashboardAppController {
|
|||
return saveDashboard(angular.toJson, timefilter, dashboardStateManager, saveOptions)
|
||||
.then(function(id) {
|
||||
if (id) {
|
||||
toastNotifications.addSuccess({
|
||||
notifications.toasts.addSuccess({
|
||||
title: i18n.translate('kbn.dashboard.dashboardWasSavedSuccessMessage', {
|
||||
defaultMessage: `Dashboard '{dashTitle}' was saved`,
|
||||
values: { dashTitle: dash.title },
|
||||
|
@ -606,14 +596,14 @@ export class DashboardAppController {
|
|||
if (dash.id !== $routeParams.id) {
|
||||
kbnUrl.change(createDashboardEditUrl(dash.id));
|
||||
} else {
|
||||
docTitle.change(dash.lastSavedTitle);
|
||||
chrome.docTitle.change(dash.lastSavedTitle);
|
||||
updateViewMode(ViewMode.VIEW);
|
||||
}
|
||||
}
|
||||
return { id };
|
||||
})
|
||||
.catch(error => {
|
||||
toastNotifications.addDanger({
|
||||
notifications.toasts.addDanger({
|
||||
title: i18n.translate('kbn.dashboard.dashboardWasNotSavedDangerMessage', {
|
||||
defaultMessage: `Dashboard '{dashTitle}' was not saved. Error: {errorMessage}`,
|
||||
values: {
|
||||
|
@ -734,10 +724,10 @@ export class DashboardAppController {
|
|||
if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) {
|
||||
openAddPanelFlyout({
|
||||
embeddable: dashboardContainer,
|
||||
getAllFactories: start.getEmbeddableFactories,
|
||||
getFactory: start.getEmbeddableFactory,
|
||||
notifications: npStart.core.notifications,
|
||||
overlays: npStart.core.overlays,
|
||||
getAllFactories: embeddables.getEmbeddableFactories,
|
||||
getFactory: embeddables.getEmbeddableFactory,
|
||||
notifications,
|
||||
overlays,
|
||||
SavedObjectFinder,
|
||||
});
|
||||
}
|
||||
|
@ -757,7 +747,7 @@ export class DashboardAppController {
|
|||
});
|
||||
};
|
||||
navActions[TopNavIds.SHARE] = anchorElement => {
|
||||
npStart.plugins.share.toggleShareContextMenu({
|
||||
share.toggleShareContextMenu({
|
||||
anchorElement,
|
||||
allowEmbed: true,
|
||||
allowShortUrl: !dashboardConfig.getHideWriteControls(),
|
||||
|
@ -784,8 +774,15 @@ export class DashboardAppController {
|
|||
},
|
||||
});
|
||||
|
||||
const visibleSubscription = chrome.getIsVisible$().subscribe(isVisible => {
|
||||
$scope.$evalAsync(() => {
|
||||
$scope.isVisible = isVisible;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', () => {
|
||||
updateSubscription.unsubscribe();
|
||||
visibleSubscription.unsubscribe();
|
||||
$scope.timefilterSubscriptions$.unsubscribe();
|
||||
|
||||
dashboardStateManager.destroy();
|
||||
|
|
|
@ -21,11 +21,14 @@ import './np_core.test.mocks';
|
|||
|
||||
import { DashboardStateManager } from './dashboard_state_manager';
|
||||
import { getAppStateMock, getSavedDashboardMock } from './__tests__';
|
||||
import { AppStateClass } from 'ui/state_management/app_state';
|
||||
import { AppStateClass } from './legacy_imports';
|
||||
import { DashboardAppState } from './types';
|
||||
import { TimeRange, TimefilterContract } from 'src/plugins/data/public';
|
||||
import { TimeRange, TimefilterContract, InputTimeRange } from 'src/plugins/data/public';
|
||||
import { ViewMode } from 'src/plugins/embeddable/public';
|
||||
import { InputTimeRange } from 'ui/timefilter';
|
||||
|
||||
jest.mock('ui/state_management/state', () => ({
|
||||
State: {},
|
||||
}));
|
||||
|
||||
jest.mock('ui/state_management/state', () => ({
|
||||
State: {},
|
||||
|
@ -50,6 +53,7 @@ describe('DashboardState', function() {
|
|||
savedDashboard,
|
||||
AppStateClass: getAppStateMock() as AppStateClass<DashboardAppState>,
|
||||
hideWriteControls: false,
|
||||
kibanaVersion: '7.0.0',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -20,15 +20,21 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_monitor_factory';
|
||||
import { Timefilter } from 'ui/timefilter';
|
||||
import { AppStateClass as TAppStateClass } from 'ui/state_management/app_state';
|
||||
import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query';
|
||||
import { Moment } from 'moment';
|
||||
|
||||
import { DashboardContainer } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public';
|
||||
import { ViewMode } from '../../../../../../src/plugins/embeddable/public';
|
||||
import { Query, esFilters } from '../../../../../../src/plugins/data/public';
|
||||
import {
|
||||
stateMonitorFactory,
|
||||
StateMonitor,
|
||||
AppStateClass as TAppStateClass,
|
||||
migrateLegacyQuery,
|
||||
} from './legacy_imports';
|
||||
import {
|
||||
Query,
|
||||
esFilters,
|
||||
TimefilterContract as Timefilter,
|
||||
} from '../../../../../../src/plugins/data/public';
|
||||
|
||||
import { getAppStateDefaults, migrateAppState } from './lib';
|
||||
import { convertPanelStateToSavedDashboardPanel } from './lib/embeddable_saved_object_converters';
|
||||
|
@ -54,6 +60,7 @@ export class DashboardStateManager {
|
|||
};
|
||||
private stateDefaults: DashboardAppStateDefaults;
|
||||
private hideWriteControls: boolean;
|
||||
private kibanaVersion: string;
|
||||
public isDirty: boolean;
|
||||
private changeListeners: Array<(status: { dirty: boolean }) => void>;
|
||||
private stateMonitor: StateMonitor<DashboardAppStateDefaults>;
|
||||
|
@ -68,11 +75,14 @@ export class DashboardStateManager {
|
|||
savedDashboard,
|
||||
AppStateClass,
|
||||
hideWriteControls,
|
||||
kibanaVersion,
|
||||
}: {
|
||||
savedDashboard: SavedObjectDashboard;
|
||||
AppStateClass: TAppStateClass<DashboardAppState>;
|
||||
hideWriteControls: boolean;
|
||||
kibanaVersion: string;
|
||||
}) {
|
||||
this.kibanaVersion = kibanaVersion;
|
||||
this.savedDashboard = savedDashboard;
|
||||
this.hideWriteControls = hideWriteControls;
|
||||
|
||||
|
@ -84,7 +94,7 @@ export class DashboardStateManager {
|
|||
// appState based on the URL (the url trumps the defaults). This means if we update the state format at all and
|
||||
// want to handle BWC, we must not only migrate the data stored with saved Dashboard, but also any old state in the
|
||||
// url.
|
||||
migrateAppState(this.appState);
|
||||
migrateAppState(this.appState, kibanaVersion);
|
||||
|
||||
this.isDirty = false;
|
||||
|
||||
|
@ -146,7 +156,8 @@ export class DashboardStateManager {
|
|||
}
|
||||
|
||||
convertedPanelStateMap[panelState.explicitInput.id] = convertPanelStateToSavedDashboardPanel(
|
||||
panelState
|
||||
panelState,
|
||||
this.kibanaVersion
|
||||
);
|
||||
|
||||
if (
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 { State } from './legacy_imports';
|
||||
import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public';
|
||||
|
||||
/**
|
||||
* Helper function to sync the global state with the various state providers
|
||||
* when a local angular application mounts. There are three different ways
|
||||
* global state can be passed into the application:
|
||||
* * parameter in the URL hash - e.g. shared link
|
||||
* * in-memory state in the data plugin exports (timefilter and filterManager) - e.g. default values
|
||||
*
|
||||
* This function looks up the three sources (earlier in the list means it takes precedence),
|
||||
* puts it into the globalState object and syncs it with the url.
|
||||
*
|
||||
* Currently the legacy chrome takes care of restoring the global state when navigating from
|
||||
* one app to another - to migrate away from that it will become necessary to also write the current
|
||||
* state to local storage
|
||||
*/
|
||||
export function syncOnMount(
|
||||
globalState: State,
|
||||
{
|
||||
query: {
|
||||
filterManager,
|
||||
timefilter: { timefilter },
|
||||
},
|
||||
}: NpDataStart
|
||||
) {
|
||||
// pull in global state information from the URL
|
||||
globalState.fetch();
|
||||
// remember whether there were info in the URL
|
||||
const hasGlobalURLState = Boolean(Object.keys(globalState.toObject()).length);
|
||||
|
||||
// sync kibana platform state with the angular global state
|
||||
if (!globalState.time) {
|
||||
globalState.time = timefilter.getTime();
|
||||
}
|
||||
if (!globalState.refreshInterval) {
|
||||
globalState.refreshInterval = timefilter.getRefreshInterval();
|
||||
}
|
||||
if (!globalState.filters && filterManager.getGlobalFilters().length > 0) {
|
||||
globalState.filters = filterManager.getGlobalFilters();
|
||||
}
|
||||
// only inject cross app global state if there is none in the url itself (that takes precedence)
|
||||
if (hasGlobalURLState) {
|
||||
// set flag the global state is set from the URL
|
||||
globalState.$inheritedGlobalState = true;
|
||||
}
|
||||
globalState.save();
|
||||
}
|
|
@ -17,26 +17,30 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { Fragment, PureComponent } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { EuiButton, EuiHorizontalRule, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links';
|
||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
||||
|
||||
export class HelpMenu extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiHorizontalRule margin="none" />
|
||||
<EuiSpacer />
|
||||
<EuiButton
|
||||
fill
|
||||
iconType="popout"
|
||||
href={`${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/dashboard.html`}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage id="kbn.dashboard.helpMenu.docLabel" defaultMessage="Dashboard documentation" />
|
||||
</EuiButton>
|
||||
</Fragment>
|
||||
<I18nProvider>
|
||||
<>
|
||||
<EuiHorizontalRule margin="none" />
|
||||
<EuiSpacer />
|
||||
<EuiButton
|
||||
fill
|
||||
iconType="popout"
|
||||
href={`${this.props.docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${this.props.docLinks.DOC_LINK_VERSION}/dashboard.html`}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.dashboard.helpMenu.docLabel"
|
||||
defaultMessage="Dashboard documentation"
|
||||
/>
|
||||
</EuiButton>
|
||||
</>
|
||||
</I18nProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,9 +21,9 @@ import React from 'react';
|
|||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { HelpMenu } from './help_menu';
|
||||
|
||||
export function addHelpMenuToAppChrome(chrome) {
|
||||
chrome.helpExtension.set(domElement => {
|
||||
render(<HelpMenu/>, domElement);
|
||||
export function addHelpMenuToAppChrome(chrome, docLinks) {
|
||||
chrome.setHelpExtension(domElement => {
|
||||
render(<HelpMenu docLinks={docLinks} />, domElement);
|
||||
return () => {
|
||||
unmountComponentAtNode(domElement);
|
||||
};
|
||||
|
|
|
@ -1,207 +0,0 @@
|
|||
/*
|
||||
* 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 './dashboard_app';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import './saved_dashboard/saved_dashboards';
|
||||
import './dashboard_config';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import chrome from 'ui/chrome';
|
||||
import { wrapInI18nContext } from 'ui/i18n';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
|
||||
import dashboardTemplate from './dashboard_app.html';
|
||||
import dashboardListingTemplate from './listing/dashboard_listing_ng_wrapper.html';
|
||||
|
||||
import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants';
|
||||
import { InvalidJSONProperty, SavedObjectNotFound } from '../../../../../plugins/kibana_utils/public';
|
||||
import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry';
|
||||
import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import 'ui/capabilities/route_setup';
|
||||
import { addHelpMenuToAppChrome } from './help_menu/help_menu_util';
|
||||
|
||||
import { npStart } from 'ui/new_platform';
|
||||
|
||||
// load directives
|
||||
import '../../../data/public';
|
||||
|
||||
const app = uiModules.get('app/dashboard', [
|
||||
'ngRoute',
|
||||
'react',
|
||||
]);
|
||||
|
||||
app.directive('dashboardListing', function (reactDirective) {
|
||||
return reactDirective(wrapInI18nContext(DashboardListing));
|
||||
});
|
||||
|
||||
function createNewDashboardCtrl($scope) {
|
||||
$scope.visitVisualizeAppLinkText = i18n.translate('kbn.dashboard.visitVisualizeAppLinkText', {
|
||||
defaultMessage: 'visit the Visualize app',
|
||||
});
|
||||
addHelpMenuToAppChrome(chrome);
|
||||
}
|
||||
|
||||
uiRoutes
|
||||
.defaults(/dashboard/, {
|
||||
requireDefaultIndex: true,
|
||||
requireUICapability: 'dashboard.show',
|
||||
badge: uiCapabilities => {
|
||||
if (uiCapabilities.dashboard.showWriteControls) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
text: i18n.translate('kbn.dashboard.badge.readOnly.text', {
|
||||
defaultMessage: 'Read only',
|
||||
}),
|
||||
tooltip: i18n.translate('kbn.dashboard.badge.readOnly.tooltip', {
|
||||
defaultMessage: 'Unable to save dashboards',
|
||||
}),
|
||||
iconType: 'glasses'
|
||||
};
|
||||
}
|
||||
})
|
||||
.when(DashboardConstants.LANDING_PAGE_PATH, {
|
||||
template: dashboardListingTemplate,
|
||||
controller($injector, $location, $scope, Private, config) {
|
||||
const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName;
|
||||
const kbnUrl = $injector.get('kbnUrl');
|
||||
const dashboardConfig = $injector.get('dashboardConfig');
|
||||
|
||||
$scope.listingLimit = config.get('savedObjects:listingLimit');
|
||||
$scope.create = () => {
|
||||
kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL);
|
||||
};
|
||||
$scope.find = (search) => {
|
||||
return services.dashboards.find(search, $scope.listingLimit);
|
||||
};
|
||||
$scope.editItem = ({ id }) => {
|
||||
kbnUrl.redirect(`${createDashboardEditUrl(id)}?_a=(viewMode:edit)`);
|
||||
};
|
||||
$scope.getViewUrl = ({ id }) => {
|
||||
return chrome.addBasePath(`#${createDashboardEditUrl(id)}`);
|
||||
};
|
||||
$scope.delete = (dashboards) => {
|
||||
return services.dashboards.delete(dashboards.map(d => d.id));
|
||||
};
|
||||
$scope.hideWriteControls = dashboardConfig.getHideWriteControls();
|
||||
$scope.initialFilter = ($location.search()).filter || EMPTY_FILTER;
|
||||
chrome.breadcrumbs.set([{
|
||||
text: i18n.translate('kbn.dashboard.dashboardBreadcrumbsTitle', {
|
||||
defaultMessage: 'Dashboards',
|
||||
}),
|
||||
}]);
|
||||
addHelpMenuToAppChrome(chrome);
|
||||
},
|
||||
resolve: {
|
||||
dash: function ($route, Private, redirectWhenMissing, kbnUrl) {
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
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) {
|
||||
kbnUrl.redirect(createDashboardEditUrl(matchingDashboards[0].id));
|
||||
} else {
|
||||
kbnUrl.redirect(`${DashboardConstants.LANDING_PAGE_PATH}?filter="${title}"`);
|
||||
}
|
||||
throw uiRoutes.WAIT_FOR_URL_CHANGE_TOKEN;
|
||||
}).catch(redirectWhenMissing({
|
||||
'dashboard': DashboardConstants.LANDING_PAGE_PATH
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.when(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {
|
||||
template: dashboardTemplate,
|
||||
controller: createNewDashboardCtrl,
|
||||
requireUICapability: 'dashboard.createNew',
|
||||
resolve: {
|
||||
dash: function (savedDashboards, redirectWhenMissing) {
|
||||
return savedDashboards.get()
|
||||
.catch(redirectWhenMissing({
|
||||
'dashboard': DashboardConstants.LANDING_PAGE_PATH
|
||||
}));
|
||||
}
|
||||
}
|
||||
})
|
||||
.when(createDashboardEditUrl(':id'), {
|
||||
template: dashboardTemplate,
|
||||
controller: createNewDashboardCtrl,
|
||||
resolve: {
|
||||
dash: function (savedDashboards, $route, redirectWhenMissing, kbnUrl, AppState) {
|
||||
const id = $route.current.params.id;
|
||||
|
||||
return savedDashboards.get(id)
|
||||
.then((savedDashboard) => {
|
||||
npStart.core.chrome.recentlyAccessed.add(savedDashboard.getFullPath(), savedDashboard.title, id);
|
||||
return savedDashboard;
|
||||
})
|
||||
.catch((error) => {
|
||||
// A corrupt dashboard was detected (e.g. with invalid JSON properties)
|
||||
if (error instanceof InvalidJSONProperty) {
|
||||
toastNotifications.addDanger(error.message);
|
||||
kbnUrl.redirect(DashboardConstants.LANDING_PAGE_PATH);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 "new AppState" is necessary so the state in the url is preserved through the redirect.
|
||||
kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {}, new AppState());
|
||||
toastNotifications.addWarning(i18n.translate('kbn.dashboard.urlWasRemovedInSixZeroWarningMessage',
|
||||
{ defaultMessage: 'The url "dashboard/create" was removed in 6.0. Please update your bookmarks.' }
|
||||
));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
.catch(redirectWhenMissing({
|
||||
'dashboard': DashboardConstants.LANDING_PAGE_PATH
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
FeatureCatalogueRegistryProvider.register(() => {
|
||||
return {
|
||||
id: 'dashboard',
|
||||
title: i18n.translate('kbn.dashboard.featureCatalogue.dashboardTitle', {
|
||||
defaultMessage: 'Dashboard',
|
||||
}),
|
||||
description: i18n.translate('kbn.dashboard.featureCatalogue.dashboardDescription', {
|
||||
defaultMessage: 'Display and share a collection of visualizations and saved searches.',
|
||||
}),
|
||||
icon: 'dashboardApp',
|
||||
path: `/app/kibana#${DashboardConstants.LANDING_PAGE_PATH}`,
|
||||
showOnHomePage: true,
|
||||
category: FeatureCatalogueCategory.DATA
|
||||
};
|
||||
});
|
69
src/legacy/core_plugins/kibana/public/dashboard/index.ts
Normal file
69
src/legacy/core_plugins/kibana/public/dashboard/index.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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 {
|
||||
npSetup,
|
||||
npStart,
|
||||
SavedObjectRegistryProvider,
|
||||
legacyChrome,
|
||||
IPrivate,
|
||||
} from './legacy_imports';
|
||||
import { DashboardPlugin, LegacyAngularInjectedDependencies } from './plugin';
|
||||
import { start as data } from '../../../data/public/legacy';
|
||||
import { localApplicationService } from '../local_application_service';
|
||||
import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy';
|
||||
import { start as navigation } from '../../../navigation/public/legacy';
|
||||
import './saved_dashboard/saved_dashboards';
|
||||
import './dashboard_config';
|
||||
|
||||
/**
|
||||
* Get dependencies relying on the global angular context.
|
||||
* They also have to get resolved together with the legacy imports above
|
||||
*/
|
||||
async function getAngularDependencies(): Promise<LegacyAngularInjectedDependencies> {
|
||||
const injector = await legacyChrome.dangerouslyGetActiveInjector();
|
||||
|
||||
const Private = injector.get<IPrivate>('Private');
|
||||
|
||||
const savedObjectRegistry = Private(SavedObjectRegistryProvider);
|
||||
|
||||
return {
|
||||
dashboardConfig: injector.get('dashboardConfig'),
|
||||
savedObjectRegistry,
|
||||
savedDashboards: injector.get('savedDashboards'),
|
||||
};
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const instance = new DashboardPlugin();
|
||||
instance.setup(npSetup.core, {
|
||||
...npSetup.plugins,
|
||||
__LEGACY: {
|
||||
localApplicationService,
|
||||
getAngularDependencies,
|
||||
},
|
||||
});
|
||||
instance.start(npStart.core, {
|
||||
...npStart.plugins,
|
||||
data,
|
||||
npData: npStart.plugins.data,
|
||||
embeddables,
|
||||
navigation,
|
||||
});
|
||||
})();
|
224
src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js
Normal file
224
src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js
Normal file
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* 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 '@kbn/i18n';
|
||||
|
||||
import dashboardTemplate from './dashboard_app.html';
|
||||
import dashboardListingTemplate from './listing/dashboard_listing_ng_wrapper.html';
|
||||
|
||||
import { ensureDefaultIndexPattern } from './legacy_imports';
|
||||
import { initDashboardAppDirective } from './dashboard_app';
|
||||
import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants';
|
||||
import {
|
||||
InvalidJSONProperty,
|
||||
SavedObjectNotFound,
|
||||
} from '../../../../../plugins/kibana_utils/public';
|
||||
import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing';
|
||||
import { addHelpMenuToAppChrome } from './help_menu/help_menu_util';
|
||||
import { registerTimefilterWithGlobalStateFactory } from '../../../../ui/public/timefilter/setup_router';
|
||||
import { syncOnMount } from './global_state_sync';
|
||||
|
||||
export function initDashboardApp(app, deps) {
|
||||
initDashboardAppDirective(app, deps);
|
||||
|
||||
app.directive('dashboardListing', function (reactDirective) {
|
||||
return reactDirective(DashboardListing);
|
||||
});
|
||||
|
||||
function createNewDashboardCtrl($scope) {
|
||||
$scope.visitVisualizeAppLinkText = i18n.translate('kbn.dashboard.visitVisualizeAppLinkText', {
|
||||
defaultMessage: 'visit the Visualize app',
|
||||
});
|
||||
addHelpMenuToAppChrome(deps.chrome, deps.core.docLinks);
|
||||
}
|
||||
|
||||
app.run(globalState => {
|
||||
syncOnMount(globalState, deps.npDataStart);
|
||||
});
|
||||
|
||||
app.run((globalState, $rootScope) => {
|
||||
registerTimefilterWithGlobalStateFactory(
|
||||
deps.npDataStart.query.timefilter.timefilter,
|
||||
globalState,
|
||||
$rootScope
|
||||
);
|
||||
});
|
||||
|
||||
app.config(function ($routeProvider) {
|
||||
const defaults = {
|
||||
reloadOnSearch: false,
|
||||
requireUICapability: 'dashboard.show',
|
||||
badge: () => {
|
||||
if (deps.dashboardCapabilities.showWriteControls) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
text: i18n.translate('kbn.dashboard.badge.readOnly.text', {
|
||||
defaultMessage: 'Read only',
|
||||
}),
|
||||
tooltip: i18n.translate('kbn.dashboard.badge.readOnly.tooltip', {
|
||||
defaultMessage: 'Unable to save dashboards',
|
||||
}),
|
||||
iconType: 'glasses',
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
$routeProvider
|
||||
.when(DashboardConstants.LANDING_PAGE_PATH, {
|
||||
...defaults,
|
||||
template: dashboardListingTemplate,
|
||||
controller($injector, $location, $scope) {
|
||||
const services = deps.savedObjectRegistry.byLoaderPropertiesName;
|
||||
const kbnUrl = $injector.get('kbnUrl');
|
||||
const dashboardConfig = deps.dashboardConfig;
|
||||
|
||||
$scope.listingLimit = deps.uiSettings.get('savedObjects:listingLimit');
|
||||
$scope.create = () => {
|
||||
kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL);
|
||||
};
|
||||
$scope.find = search => {
|
||||
return services.dashboards.find(search, $scope.listingLimit);
|
||||
};
|
||||
$scope.editItem = ({ id }) => {
|
||||
kbnUrl.redirect(`${createDashboardEditUrl(id)}?_a=(viewMode:edit)`);
|
||||
};
|
||||
$scope.getViewUrl = ({ id }) => {
|
||||
return deps.addBasePath(`#${createDashboardEditUrl(id)}`);
|
||||
};
|
||||
$scope.delete = dashboards => {
|
||||
return services.dashboards.delete(dashboards.map(d => d.id));
|
||||
};
|
||||
$scope.hideWriteControls = dashboardConfig.getHideWriteControls();
|
||||
$scope.initialFilter = $location.search().filter || EMPTY_FILTER;
|
||||
deps.chrome.setBreadcrumbs([
|
||||
{
|
||||
text: i18n.translate('kbn.dashboard.dashboardBreadcrumbsTitle', {
|
||||
defaultMessage: 'Dashboards',
|
||||
}),
|
||||
},
|
||||
]);
|
||||
addHelpMenuToAppChrome(deps.chrome, deps.core.docLinks);
|
||||
},
|
||||
resolve: {
|
||||
dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl) {
|
||||
return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl).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) {
|
||||
kbnUrl.redirect(createDashboardEditUrl(matchingDashboards[0].id));
|
||||
} else {
|
||||
kbnUrl.redirect(`${DashboardConstants.LANDING_PAGE_PATH}?filter="${title}"`);
|
||||
}
|
||||
$rootScope.$digest();
|
||||
return new Promise(() => {});
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
})
|
||||
.when(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {
|
||||
...defaults,
|
||||
template: dashboardTemplate,
|
||||
controller: createNewDashboardCtrl,
|
||||
requireUICapability: 'dashboard.createNew',
|
||||
resolve: {
|
||||
dash: function (redirectWhenMissing, $rootScope, kbnUrl) {
|
||||
return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl)
|
||||
.then(() => {
|
||||
return deps.savedDashboards.get();
|
||||
})
|
||||
.catch(
|
||||
redirectWhenMissing({
|
||||
dashboard: DashboardConstants.LANDING_PAGE_PATH,
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
})
|
||||
.when(createDashboardEditUrl(':id'), {
|
||||
...defaults,
|
||||
template: dashboardTemplate,
|
||||
controller: createNewDashboardCtrl,
|
||||
resolve: {
|
||||
dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl, AppState) {
|
||||
const id = $route.current.params.id;
|
||||
|
||||
return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl)
|
||||
.then(() => {
|
||||
return deps.savedDashboards.get(id);
|
||||
})
|
||||
.then(savedDashboard => {
|
||||
deps.chrome.recentlyAccessed.add(
|
||||
savedDashboard.getFullPath(),
|
||||
savedDashboard.title,
|
||||
id
|
||||
);
|
||||
return savedDashboard;
|
||||
})
|
||||
.catch(error => {
|
||||
// A corrupt dashboard was detected (e.g. with invalid JSON properties)
|
||||
if (error instanceof InvalidJSONProperty) {
|
||||
deps.toastNotifications.addDanger(error.message);
|
||||
kbnUrl.redirect(DashboardConstants.LANDING_PAGE_PATH);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 "new AppState" is necessary so the state in the url is preserved through the redirect.
|
||||
kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {}, new AppState());
|
||||
deps.toastNotifications.addWarning(
|
||||
i18n.translate('kbn.dashboard.urlWasRemovedInSixZeroWarningMessage', {
|
||||
defaultMessage:
|
||||
'The url "dashboard/create" was removed in 6.0. Please update your bookmarks.',
|
||||
})
|
||||
);
|
||||
return new Promise(() => {});
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
.catch(
|
||||
redirectWhenMissing({
|
||||
dashboard: DashboardConstants.LANDING_PAGE_PATH,
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
})
|
||||
.when(`dashboard/:tail*?`, { redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}` })
|
||||
.when(`dashboards/:tail*?`, { redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}` });
|
||||
});
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The imports in this file are static functions and types which still live in legacy folders and are used
|
||||
* within dashboard. To consolidate them all in one place, they are re-exported from this file. Eventually
|
||||
* this list should become empty. Imports from the top level of shimmed or moved plugins can be imported
|
||||
* directly where they are needed.
|
||||
*/
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
export const legacyChrome = chrome;
|
||||
export { State } from 'ui/state_management/state';
|
||||
export { AppState } from 'ui/state_management/app_state';
|
||||
export { AppStateClass } from 'ui/state_management/app_state';
|
||||
export { SaveOptions } from 'ui/saved_objects/saved_object';
|
||||
export { npSetup, npStart } from 'ui/new_platform';
|
||||
export { SavedObjectRegistryProvider } from 'ui/saved_objects';
|
||||
export { IPrivate } from 'ui/private';
|
||||
export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal';
|
||||
export { subscribeWithScope } from 'ui/utils/subscribe_with_scope';
|
||||
// @ts-ignore
|
||||
export { ConfirmationButtonTypes } from 'ui/modals/confirm_modal';
|
||||
export { showSaveModal, SaveResult } from 'ui/saved_objects/show_saved_object_save_modal';
|
||||
export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query';
|
||||
export { KbnUrl } from 'ui/url/kbn_url';
|
||||
// @ts-ignore
|
||||
export { GlobalStateProvider } from 'ui/state_management/global_state';
|
||||
// @ts-ignore
|
||||
export { StateManagementConfigProvider } from 'ui/state_management/config_provider';
|
||||
// @ts-ignore
|
||||
export { AppStateProvider } from 'ui/state_management/app_state';
|
||||
// @ts-ignore
|
||||
export { PrivateProvider } from 'ui/private/private';
|
||||
// @ts-ignore
|
||||
export { EventsProvider } from 'ui/events';
|
||||
export { PersistedState } from 'ui/persisted_state';
|
||||
// @ts-ignore
|
||||
export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav';
|
||||
// @ts-ignore
|
||||
export { PromiseServiceCreator } from 'ui/promises/promises';
|
||||
// @ts-ignore
|
||||
export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url';
|
||||
// @ts-ignore
|
||||
export { confirmModalFactory } from 'ui/modals/confirm_modal';
|
||||
export { configureAppAngularModule } from 'ui/legacy_compat';
|
||||
export { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_monitor_factory';
|
||||
export { ensureDefaultIndexPattern } from 'ui/legacy_compat';
|
||||
export { unhashUrl } from 'ui/state_management/state_hashing';
|
||||
export { IInjector } from 'ui/chrome';
|
||||
export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder';
|
|
@ -48,7 +48,7 @@ test('convertSavedDashboardPanelToPanelState', () => {
|
|||
version: '7.0.0',
|
||||
};
|
||||
|
||||
expect(convertSavedDashboardPanelToPanelState(savedDashboardPanel, true)).toEqual({
|
||||
expect(convertSavedDashboardPanelToPanelState(savedDashboardPanel)).toEqual({
|
||||
gridData: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
@ -82,7 +82,7 @@ test('convertSavedDashboardPanelToPanelState does not include undefined id', ()
|
|||
version: '7.0.0',
|
||||
};
|
||||
|
||||
const converted = convertSavedDashboardPanelToPanelState(savedDashboardPanel, false);
|
||||
const converted = convertSavedDashboardPanelToPanelState(savedDashboardPanel);
|
||||
expect(converted.hasOwnProperty('savedObjectId')).toBe(false);
|
||||
});
|
||||
|
||||
|
@ -103,7 +103,7 @@ test('convertPanelStateToSavedDashboardPanel', () => {
|
|||
type: 'search',
|
||||
};
|
||||
|
||||
expect(convertPanelStateToSavedDashboardPanel(dashboardPanel)).toEqual({
|
||||
expect(convertPanelStateToSavedDashboardPanel(dashboardPanel, '6.3.0')).toEqual({
|
||||
type: 'search',
|
||||
embeddableConfig: {
|
||||
something: 'hi!',
|
||||
|
@ -137,6 +137,6 @@ test('convertPanelStateToSavedDashboardPanel will not add an undefined id when n
|
|||
type: 'search',
|
||||
};
|
||||
|
||||
const converted = convertPanelStateToSavedDashboardPanel(dashboardPanel);
|
||||
const converted = convertPanelStateToSavedDashboardPanel(dashboardPanel, '8.0.0');
|
||||
expect(converted.hasOwnProperty('id')).toBe(false);
|
||||
});
|
||||
|
|
|
@ -18,12 +18,10 @@
|
|||
*/
|
||||
import { omit } from 'lodash';
|
||||
import { DashboardPanelState } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public';
|
||||
import chrome from 'ui/chrome';
|
||||
import { SavedDashboardPanel } from '../types';
|
||||
|
||||
export function convertSavedDashboardPanelToPanelState(
|
||||
savedDashboardPanel: SavedDashboardPanel,
|
||||
useMargins: boolean
|
||||
savedDashboardPanel: SavedDashboardPanel
|
||||
): DashboardPanelState {
|
||||
return {
|
||||
type: savedDashboardPanel.type,
|
||||
|
@ -38,13 +36,14 @@ export function convertSavedDashboardPanelToPanelState(
|
|||
}
|
||||
|
||||
export function convertPanelStateToSavedDashboardPanel(
|
||||
panelState: DashboardPanelState
|
||||
panelState: DashboardPanelState,
|
||||
version: string
|
||||
): SavedDashboardPanel {
|
||||
const customTitle: string | undefined = panelState.explicitInput.title
|
||||
? (panelState.explicitInput.title as string)
|
||||
: undefined;
|
||||
return {
|
||||
version: chrome.getKibanaVersion(),
|
||||
version,
|
||||
type: panelState.type,
|
||||
gridData: panelState.gridData,
|
||||
panelIndex: panelState.explicitInput.id,
|
||||
|
|
|
@ -43,7 +43,7 @@ test('migrate app state from 6.0', async () => {
|
|||
getQueryParamName: () => 'a',
|
||||
save: mockSave,
|
||||
};
|
||||
migrateAppState(appState);
|
||||
migrateAppState(appState, '8.0');
|
||||
expect(appState.uiState).toBeUndefined();
|
||||
|
||||
const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel;
|
||||
|
@ -58,6 +58,7 @@ test('migrate app state from 6.0', async () => {
|
|||
});
|
||||
|
||||
test('migrate sort from 6.1', async () => {
|
||||
const TARGET_VERSION = '8.0';
|
||||
const mockSave = jest.fn();
|
||||
const appState = {
|
||||
uiState: {
|
||||
|
@ -80,7 +81,7 @@ test('migrate sort from 6.1', async () => {
|
|||
save: mockSave,
|
||||
useMargins: false,
|
||||
};
|
||||
migrateAppState(appState);
|
||||
migrateAppState(appState, TARGET_VERSION);
|
||||
expect(appState.uiState).toBeUndefined();
|
||||
|
||||
const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel;
|
||||
|
@ -112,7 +113,7 @@ test('migrates 6.0 even when uiState does not exist', async () => {
|
|||
getQueryParamName: () => 'a',
|
||||
save: mockSave,
|
||||
};
|
||||
migrateAppState(appState);
|
||||
migrateAppState(appState, '8.0');
|
||||
expect((appState as any).uiState).toBeUndefined();
|
||||
|
||||
const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel;
|
||||
|
@ -147,7 +148,7 @@ test('6.2 migration adjusts w & h without margins', async () => {
|
|||
save: mockSave,
|
||||
useMargins: false,
|
||||
};
|
||||
migrateAppState(appState);
|
||||
migrateAppState(appState, '8.0');
|
||||
expect((appState as any).uiState).toBeUndefined();
|
||||
|
||||
const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel;
|
||||
|
@ -184,7 +185,7 @@ test('6.2 migration adjusts w & h with margins', async () => {
|
|||
save: mockSave,
|
||||
useMargins: true,
|
||||
};
|
||||
migrateAppState(appState);
|
||||
migrateAppState(appState, '8.0');
|
||||
expect((appState as any).uiState).toBeUndefined();
|
||||
|
||||
const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel;
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
*/
|
||||
|
||||
import semver from 'semver';
|
||||
import chrome from 'ui/chrome';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { createUiStatsReporter, METRIC_TYPE } from '../../../../ui_metric/public';
|
||||
import {
|
||||
|
@ -37,7 +36,10 @@ import { migratePanelsTo730 } from '../migrations/migrate_to_730_panels';
|
|||
*
|
||||
* Once we hit a major version, we can remove support for older style URLs and get rid of this logic.
|
||||
*/
|
||||
export function migrateAppState(appState: { [key: string]: unknown } | DashboardAppState) {
|
||||
export function migrateAppState(
|
||||
appState: { [key: string]: unknown } | DashboardAppState,
|
||||
kibanaVersion: string
|
||||
) {
|
||||
if (!appState.panels) {
|
||||
throw new Error(
|
||||
i18n.translate('kbn.dashboard.panel.invalidData', {
|
||||
|
@ -73,7 +75,7 @@ export function migrateAppState(appState: { [key: string]: unknown } | Dashboard
|
|||
| SavedDashboardPanel630
|
||||
| SavedDashboardPanel640To720
|
||||
>,
|
||||
chrome.getKibanaVersion(),
|
||||
kibanaVersion,
|
||||
appState.useMargins,
|
||||
appState.uiState
|
||||
);
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { SaveOptions } from 'ui/saved_objects/saved_object';
|
||||
import { Timefilter } from 'ui/timefilter';
|
||||
import { TimefilterContract } from 'src/plugins/data/public';
|
||||
import { SaveOptions } from '../legacy_imports';
|
||||
import { updateSavedDashboard } from './update_saved_dashboard';
|
||||
import { DashboardStateManager } from '../dashboard_state_manager';
|
||||
|
||||
|
@ -32,7 +32,7 @@ import { DashboardStateManager } from '../dashboard_state_manager';
|
|||
*/
|
||||
export function saveDashboard(
|
||||
toJson: (obj: any) => string,
|
||||
timeFilter: Timefilter,
|
||||
timeFilter: TimefilterContract,
|
||||
dashboardStateManager: DashboardStateManager,
|
||||
saveOptions: SaveOptions
|
||||
): Promise<string> {
|
||||
|
|
|
@ -18,16 +18,15 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { AppState } from 'ui/state_management/app_state';
|
||||
import { Timefilter } from 'ui/timefilter';
|
||||
import { RefreshInterval } from 'src/plugins/data/public';
|
||||
import { RefreshInterval, TimefilterContract } from 'src/plugins/data/public';
|
||||
import { AppState } from '../legacy_imports';
|
||||
import { FilterUtils } from './filter_utils';
|
||||
import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard';
|
||||
|
||||
export function updateSavedDashboard(
|
||||
savedDashboard: SavedObjectDashboard,
|
||||
appState: AppState,
|
||||
timeFilter: Timefilter,
|
||||
timeFilter: TimefilterContract,
|
||||
toJson: <T>(object: T) => string
|
||||
) {
|
||||
savedDashboard.title = appState.title;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -19,7 +19,7 @@
|
|||
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EuiLink, EuiButton, EuiEmptyPrompt } from '@elastic/eui';
|
||||
|
@ -41,27 +41,29 @@ export class DashboardListing extends React.Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<TableListView
|
||||
createItem={this.props.hideWriteControls ? null : this.props.createItem}
|
||||
findItems={this.props.findItems}
|
||||
deleteItems={this.props.hideWriteControls ? null : this.props.deleteItems}
|
||||
editItem={this.props.hideWriteControls ? null : this.props.editItem}
|
||||
tableColumns={this.getTableColumns()}
|
||||
listingLimit={this.props.listingLimit}
|
||||
initialFilter={this.props.initialFilter}
|
||||
noItemsFragment={this.getNoItemsMessage()}
|
||||
entityName={i18n.translate('kbn.dashboard.listing.table.entityName', {
|
||||
defaultMessage: 'dashboard',
|
||||
})}
|
||||
entityNamePlural={i18n.translate('kbn.dashboard.listing.table.entityNamePlural', {
|
||||
defaultMessage: 'dashboards',
|
||||
})}
|
||||
tableListTitle={i18n.translate('kbn.dashboard.listing.dashboardsTitle', {
|
||||
defaultMessage: 'Dashboards',
|
||||
})}
|
||||
toastNotifications={npStart.core.notifications.toasts}
|
||||
uiSettings={npStart.core.uiSettings}
|
||||
/>
|
||||
<I18nProvider>
|
||||
<TableListView
|
||||
createItem={this.props.hideWriteControls ? null : this.props.createItem}
|
||||
findItems={this.props.findItems}
|
||||
deleteItems={this.props.hideWriteControls ? null : this.props.deleteItems}
|
||||
editItem={this.props.hideWriteControls ? null : this.props.editItem}
|
||||
tableColumns={this.getTableColumns()}
|
||||
listingLimit={this.props.listingLimit}
|
||||
initialFilter={this.props.initialFilter}
|
||||
noItemsFragment={this.getNoItemsMessage()}
|
||||
entityName={i18n.translate('kbn.dashboard.listing.table.entityName', {
|
||||
defaultMessage: 'dashboard',
|
||||
})}
|
||||
entityNamePlural={i18n.translate('kbn.dashboard.listing.table.entityNamePlural', {
|
||||
defaultMessage: 'dashboards',
|
||||
})}
|
||||
tableListTitle={i18n.translate('kbn.dashboard.listing.dashboardsTitle', {
|
||||
defaultMessage: 'Dashboards',
|
||||
})}
|
||||
toastNotifications={npStart.core.notifications.toasts}
|
||||
uiSettings={npStart.core.uiSettings}
|
||||
/>
|
||||
</I18nProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
151
src/legacy/core_plugins/kibana/public/dashboard/plugin.ts
Normal file
151
src/legacy/core_plugins/kibana/public/dashboard/plugin.ts
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* 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 {
|
||||
App,
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
LegacyCoreStart,
|
||||
Plugin,
|
||||
SavedObjectsClientContract,
|
||||
} from 'kibana/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { RenderDeps } from './application';
|
||||
import { LocalApplicationService } from '../local_application_service';
|
||||
import { DataStart } from '../../../data/public';
|
||||
import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public';
|
||||
import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public';
|
||||
import { Storage } from '../../../../../plugins/kibana_utils/public';
|
||||
import { NavigationStart } from '../../../navigation/public';
|
||||
import { DashboardConstants } from './dashboard_constants';
|
||||
import { SharePluginStart } from '../../../../../plugins/share/public';
|
||||
import {
|
||||
HomePublicPluginSetup,
|
||||
FeatureCatalogueCategory,
|
||||
} from '../../../../../plugins/home/public';
|
||||
|
||||
export interface LegacyAngularInjectedDependencies {
|
||||
dashboardConfig: any;
|
||||
savedObjectRegistry: any;
|
||||
savedDashboards: any;
|
||||
}
|
||||
|
||||
export interface DashboardPluginStartDependencies {
|
||||
data: DataStart;
|
||||
npData: NpDataStart;
|
||||
embeddables: ReturnType<EmbeddablePublicPlugin['start']>;
|
||||
navigation: NavigationStart;
|
||||
share: SharePluginStart;
|
||||
}
|
||||
|
||||
export interface DashboardPluginSetupDependencies {
|
||||
__LEGACY: {
|
||||
getAngularDependencies: () => Promise<LegacyAngularInjectedDependencies>;
|
||||
localApplicationService: LocalApplicationService;
|
||||
};
|
||||
home: HomePublicPluginSetup;
|
||||
}
|
||||
|
||||
export class DashboardPlugin implements Plugin {
|
||||
private startDependencies: {
|
||||
dataStart: DataStart;
|
||||
npDataStart: NpDataStart;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
embeddables: ReturnType<EmbeddablePublicPlugin['start']>;
|
||||
navigation: NavigationStart;
|
||||
share: SharePluginStart;
|
||||
} | null = null;
|
||||
|
||||
public setup(
|
||||
core: CoreSetup,
|
||||
{
|
||||
__LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices },
|
||||
home,
|
||||
}: DashboardPluginSetupDependencies
|
||||
) {
|
||||
const app: App = {
|
||||
id: '',
|
||||
title: 'Dashboards',
|
||||
mount: async ({ core: contextCore }, params) => {
|
||||
if (this.startDependencies === null) {
|
||||
throw new Error('not started yet');
|
||||
}
|
||||
const {
|
||||
dataStart,
|
||||
savedObjectsClient,
|
||||
embeddables,
|
||||
navigation,
|
||||
share,
|
||||
npDataStart,
|
||||
} = this.startDependencies;
|
||||
const angularDependencies = await getAngularDependencies();
|
||||
const deps: RenderDeps = {
|
||||
core: contextCore as LegacyCoreStart,
|
||||
...legacyServices,
|
||||
...angularDependencies,
|
||||
navigation,
|
||||
dataStart,
|
||||
share,
|
||||
npDataStart,
|
||||
indexPatterns: dataStart.indexPatterns.indexPatterns,
|
||||
savedObjectsClient,
|
||||
chrome: contextCore.chrome,
|
||||
addBasePath: contextCore.http.basePath.prepend,
|
||||
uiSettings: contextCore.uiSettings,
|
||||
savedQueryService: dataStart.search.services.savedQueryService,
|
||||
embeddables,
|
||||
dashboardCapabilities: contextCore.application.capabilities.dashboard,
|
||||
localStorage: new Storage(localStorage),
|
||||
};
|
||||
const { renderApp } = await import('./application');
|
||||
return renderApp(params.element, params.appBasePath, deps);
|
||||
},
|
||||
};
|
||||
localApplicationService.register({ ...app, id: 'dashboard' });
|
||||
localApplicationService.register({ ...app, id: 'dashboards' });
|
||||
|
||||
home.featureCatalogue.register({
|
||||
id: 'dashboard',
|
||||
title: i18n.translate('kbn.dashboard.featureCatalogue.dashboardTitle', {
|
||||
defaultMessage: 'Dashboard',
|
||||
}),
|
||||
description: i18n.translate('kbn.dashboard.featureCatalogue.dashboardDescription', {
|
||||
defaultMessage: 'Display and share a collection of visualizations and saved searches.',
|
||||
}),
|
||||
icon: 'dashboardApp',
|
||||
path: `/app/kibana#${DashboardConstants.LANDING_PAGE_PATH}`,
|
||||
showOnHomePage: true,
|
||||
category: FeatureCatalogueCategory.DATA,
|
||||
});
|
||||
}
|
||||
|
||||
start(
|
||||
{ savedObjects: { client: savedObjectsClient } }: CoreStart,
|
||||
{ data: dataStart, embeddables, navigation, npData, share }: DashboardPluginStartDependencies
|
||||
) {
|
||||
this.startDependencies = {
|
||||
dataStart,
|
||||
npDataStart: npData,
|
||||
savedObjectsClient,
|
||||
embeddables,
|
||||
navigation,
|
||||
share,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -17,9 +17,16 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
|
||||
import React from 'react';
|
||||
import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers';
|
||||
|
||||
jest.mock('../legacy_imports', () => ({
|
||||
SavedObjectSaveModal: () => null
|
||||
}));
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
import { DashboardSaveModal } from './save_modal';
|
||||
|
||||
test('renders DashboardSaveModal', () => {
|
||||
|
|
|
@ -19,10 +19,10 @@
|
|||
|
||||
import React, { Fragment } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal';
|
||||
import { EuiFormRow, EuiTextArea, EuiSwitch } from '@elastic/eui';
|
||||
|
||||
import { SavedObjectSaveModal } from '../legacy_imports';
|
||||
|
||||
interface SaveOptions {
|
||||
newTitle: string;
|
||||
newDescription: string;
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { DashboardCloneModal } from './clone_modal';
|
||||
|
||||
export function showCloneModal(
|
||||
|
@ -54,7 +54,7 @@ export function showCloneModal(
|
|||
};
|
||||
document.body.appendChild(container);
|
||||
const element = (
|
||||
<I18nContext>
|
||||
<I18nProvider>
|
||||
<DashboardCloneModal
|
||||
onClone={onCloneConfirmed}
|
||||
onClose={closeModal}
|
||||
|
@ -63,7 +63,7 @@ export function showCloneModal(
|
|||
values: { title },
|
||||
})}
|
||||
/>
|
||||
</I18nContext>
|
||||
</I18nProvider>
|
||||
);
|
||||
ReactDOM.render(element, container);
|
||||
}
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { EuiWrappingPopover } from '@elastic/eui';
|
||||
|
||||
import { OptionsMenu } from './options';
|
||||
|
||||
let isOpen = false;
|
||||
|
@ -55,7 +55,7 @@ export function showOptionsPopover({
|
|||
|
||||
document.body.appendChild(container);
|
||||
const element = (
|
||||
<I18nContext>
|
||||
<I18nProvider>
|
||||
<EuiWrappingPopover id="popover" button={anchorElement} isOpen={true} closePopover={onClose}>
|
||||
<OptionsMenu
|
||||
useMargins={useMargins}
|
||||
|
@ -64,7 +64,7 @@ export function showOptionsPopover({
|
|||
onHidePanelTitlesChange={onHidePanelTitlesChange}
|
||||
/>
|
||||
</EuiWrappingPopover>
|
||||
</I18nContext>
|
||||
</I18nProvider>
|
||||
);
|
||||
ReactDOM.render(element, container);
|
||||
}
|
||||
|
|
|
@ -17,9 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { AppState } from 'ui/state_management/app_state';
|
||||
import { AppState as TAppState } from 'ui/state_management/app_state';
|
||||
import { ViewMode } from 'src/plugins/embeddable/public';
|
||||
import { AppState } from './legacy_imports';
|
||||
import {
|
||||
RawSavedDashboardPanelTo60,
|
||||
RawSavedDashboardPanel610,
|
||||
|
@ -153,5 +152,5 @@ export type AddFilterFn = (
|
|||
operator: string;
|
||||
index: string;
|
||||
},
|
||||
appState: TAppState
|
||||
appState: AppState
|
||||
) => void;
|
||||
|
|
|
@ -57,9 +57,11 @@ import {
|
|||
vislibSeriesResponseHandlerProvider,
|
||||
Vis,
|
||||
SavedObjectSaveModal,
|
||||
ensureDefaultIndexPattern,
|
||||
} from '../kibana_services';
|
||||
|
||||
const {
|
||||
core,
|
||||
chrome,
|
||||
docTitle,
|
||||
FilterBarQueryFilterProvider,
|
||||
|
@ -72,7 +74,6 @@ const {
|
|||
} = getServices();
|
||||
|
||||
import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../breadcrumbs';
|
||||
import { extractTimeFilter, changeTimeFilter } from '../../../../data/public';
|
||||
import { start as data } from '../../../../data/public/legacy';
|
||||
import { generateFilters } from '../../../../../../plugins/data/public';
|
||||
|
||||
|
@ -91,7 +92,6 @@ const app = uiModules.get('apps/discover', [
|
|||
|
||||
uiRoutes
|
||||
.defaults(/^\/discover(\/|$)/, {
|
||||
requireDefaultIndex: true,
|
||||
requireUICapability: 'discover.show',
|
||||
k7Breadcrumbs: ($route, $injector) =>
|
||||
$injector.invoke(
|
||||
|
@ -119,50 +119,53 @@ uiRoutes
|
|||
template: indexTemplate,
|
||||
reloadOnSearch: false,
|
||||
resolve: {
|
||||
ip: function (Promise, indexPatterns, config, Private) {
|
||||
savedObjects: function (Promise, indexPatterns, config, Private, $rootScope, kbnUrl, redirectWhenMissing, savedSearches, $route) {
|
||||
const State = Private(StateProvider);
|
||||
return indexPatterns.getCache().then((savedObjects)=> {
|
||||
/**
|
||||
* In making the indexPattern modifiable it was placed in appState. Unfortunately,
|
||||
* the load order of AppState conflicts with the load order of many other things
|
||||
* so in order to get the name of the index we should use, and to switch to the
|
||||
* default if necessary, we parse the appState with a temporary State object and
|
||||
* then destroy it immediatly after we're done
|
||||
*
|
||||
* @type {State}
|
||||
*/
|
||||
const state = new State('_a', {});
|
||||
|
||||
const specified = !!state.index;
|
||||
const exists = _.findIndex(savedObjects, o => o.id === state.index) > -1;
|
||||
const id = exists ? state.index : config.get('defaultIndex');
|
||||
state.destroy();
|
||||
const savedSearchId = $route.current.params.id;
|
||||
|
||||
return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl).then(() => {
|
||||
return Promise.props({
|
||||
list: savedObjects,
|
||||
loaded: indexPatterns.get(id),
|
||||
stateVal: state.index,
|
||||
stateValFound: specified && exists
|
||||
ip: indexPatterns.getCache().then((savedObjects) => {
|
||||
/**
|
||||
* In making the indexPattern modifiable it was placed in appState. Unfortunately,
|
||||
* the load order of AppState conflicts with the load order of many other things
|
||||
* so in order to get the name of the index we should use, and to switch to the
|
||||
* default if necessary, we parse the appState with a temporary State object and
|
||||
* then destroy it immediatly after we're done
|
||||
*
|
||||
* @type {State}
|
||||
*/
|
||||
const state = new State('_a', {});
|
||||
|
||||
const specified = !!state.index;
|
||||
const exists = _.findIndex(savedObjects, o => o.id === state.index) > -1;
|
||||
const id = exists ? state.index : config.get('defaultIndex');
|
||||
state.destroy();
|
||||
|
||||
return Promise.props({
|
||||
list: savedObjects,
|
||||
loaded: indexPatterns.get(id),
|
||||
stateVal: state.index,
|
||||
stateValFound: specified && exists
|
||||
});
|
||||
}),
|
||||
savedSearch: savedSearches.get(savedSearchId)
|
||||
.then((savedSearch) => {
|
||||
if (savedSearchId) {
|
||||
chrome.recentlyAccessed.add(
|
||||
savedSearch.getFullPath(),
|
||||
savedSearch.title,
|
||||
savedSearchId);
|
||||
}
|
||||
return savedSearch;
|
||||
})
|
||||
.catch(redirectWhenMissing({
|
||||
'search': '/discover',
|
||||
'index-pattern': '/management/kibana/objects/savedSearches/' + $route.current.params.id
|
||||
}))
|
||||
});
|
||||
});
|
||||
},
|
||||
savedSearch: function (redirectWhenMissing, savedSearches, $route) {
|
||||
const savedSearchId = $route.current.params.id;
|
||||
return savedSearches.get(savedSearchId)
|
||||
.then((savedSearch) => {
|
||||
if (savedSearchId) {
|
||||
chrome.recentlyAccessed.add(
|
||||
savedSearch.getFullPath(),
|
||||
savedSearch.title,
|
||||
savedSearchId);
|
||||
}
|
||||
return savedSearch;
|
||||
})
|
||||
.catch(redirectWhenMissing({
|
||||
'search': '/discover',
|
||||
'index-pattern': '/management/kibana/objects/savedSearches/' + $route.current.params.id
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -224,7 +227,7 @@ function discoverController(
|
|||
};
|
||||
|
||||
// the saved savedSearch
|
||||
const savedSearch = $route.current.locals.savedSearch;
|
||||
const savedSearch = $route.current.locals.savedObjects.savedSearch;
|
||||
|
||||
let abortController;
|
||||
$scope.$on('$destroy', () => {
|
||||
|
@ -417,20 +420,6 @@ function discoverController(
|
|||
queryFilter.setFilters(filters);
|
||||
};
|
||||
|
||||
$scope.applyFilters = filters => {
|
||||
const { timeRangeFilter, restOfFilters } = extractTimeFilter($scope.indexPattern.timeFieldName, filters);
|
||||
queryFilter.addFilters(restOfFilters);
|
||||
if (timeRangeFilter) changeTimeFilter(timefilter, timeRangeFilter);
|
||||
|
||||
$scope.state.$newFilters = [];
|
||||
};
|
||||
|
||||
$scope.$watch('state.$newFilters', (filters = []) => {
|
||||
if (filters.length === 1) {
|
||||
$scope.applyFilters(filters);
|
||||
}
|
||||
});
|
||||
|
||||
const getFieldCounts = async () => {
|
||||
// the field counts aren't set until we have the data back,
|
||||
// so we wait for the fetch to be done before proceeding
|
||||
|
@ -539,7 +528,7 @@ function discoverController(
|
|||
sampleSize: config.get('discover:sampleSize'),
|
||||
timefield: isDefaultTypeIndexPattern($scope.indexPattern) && $scope.indexPattern.timeFieldName,
|
||||
savedSearch: savedSearch,
|
||||
indexPatternList: $route.current.locals.ip.list,
|
||||
indexPatternList: $route.current.locals.savedObjects.ip.list,
|
||||
};
|
||||
|
||||
const shouldSearchOnPageLoad = () => {
|
||||
|
@ -1055,7 +1044,7 @@ function discoverController(
|
|||
loaded: loadedIndexPattern,
|
||||
stateVal,
|
||||
stateValFound,
|
||||
} = $route.current.locals.ip;
|
||||
} = $route.current.locals.savedObjects.ip;
|
||||
|
||||
const ownIndexPattern = $scope.searchSource.getOwnField('index');
|
||||
|
||||
|
@ -1103,12 +1092,12 @@ function discoverController(
|
|||
// Block the UI from loading if the user has loaded a rollup index pattern but it isn't
|
||||
// supported.
|
||||
$scope.isUnsupportedIndexPattern = (
|
||||
!isDefaultTypeIndexPattern($route.current.locals.ip.loaded)
|
||||
&& !hasSearchStategyForIndexPattern($route.current.locals.ip.loaded)
|
||||
!isDefaultTypeIndexPattern($route.current.locals.savedObjects.ip.loaded)
|
||||
&& !hasSearchStategyForIndexPattern($route.current.locals.savedObjects.ip.loaded)
|
||||
);
|
||||
|
||||
if ($scope.isUnsupportedIndexPattern) {
|
||||
$scope.unsupportedIndexPatternType = $route.current.locals.ip.loaded.type;
|
||||
$scope.unsupportedIndexPatternType = $route.current.locals.savedObjects.ip.loaded.type;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ export function getSavedSearchBreadcrumbs($route: any) {
|
|||
return [
|
||||
...getRootBreadcrumbs(),
|
||||
{
|
||||
text: $route.current.locals.savedSearch.id,
|
||||
text: $route.current.locals.savedObjects.savedSearch.id,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ import * as docViewsRegistry from 'ui/registry/doc_views';
|
|||
|
||||
const services = {
|
||||
// new plattform
|
||||
core: npStart.core,
|
||||
addBasePath: npStart.core.http.basePath.prepend,
|
||||
capabilities: npStart.core.application.capabilities,
|
||||
chrome: npStart.core.chrome,
|
||||
|
@ -108,6 +109,7 @@ export { getUnhashableStatesProvider } from 'ui/state_management/state_hashing';
|
|||
export { tabifyAggResponse } from 'ui/agg_response/tabify';
|
||||
// @ts-ignore
|
||||
export { vislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib';
|
||||
export { ensureDefaultIndexPattern } from 'ui/legacy_compat';
|
||||
export { unhashUrl } from 'ui/state_management/state_hashing';
|
||||
|
||||
// EXPORT types
|
||||
|
|
|
@ -28,7 +28,6 @@ import { I18nContext } from 'ui/i18n';
|
|||
import { uiModules } from 'ui/modules';
|
||||
import appTemplate from './app.html';
|
||||
import landingTemplate from './landing.html';
|
||||
import { capabilities } from 'ui/capabilities';
|
||||
import { management, SidebarNav, MANAGEMENT_BREADCRUMB } from 'ui/management';
|
||||
import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
|
@ -50,13 +49,6 @@ uiRoutes
|
|||
redirectTo: '/management'
|
||||
});
|
||||
|
||||
require('./route_setup/load_default')({
|
||||
whenMissingRedirectTo: () => {
|
||||
const canManageIndexPatterns = capabilities.get().management.kibana.index_patterns;
|
||||
return canManageIndexPatterns ? '/management/kibana/index_pattern' : '/home';
|
||||
}
|
||||
});
|
||||
|
||||
export function updateLandingPage(version) {
|
||||
const node = document.getElementById(LANDING_ID);
|
||||
if (!node) {
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
/*
|
||||
* 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 _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { banners } from 'ui/notify';
|
||||
import { NoDefaultIndexPattern } from 'ui/index_patterns';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import {
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
import { clearTimeout } from 'timers';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
let bannerId;
|
||||
let timeoutId;
|
||||
|
||||
function displayBanner() {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
// Avoid being hostile to new users who don't have an index pattern setup yet
|
||||
// give them a friendly info message instead of a terse error message
|
||||
bannerId = banners.set({
|
||||
id: bannerId, // initially undefined, but reused after first set
|
||||
component: (
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
iconType="iInCircle"
|
||||
title={
|
||||
i18n.translate('kbn.management.indexPattern.bannerLabel',
|
||||
//eslint-disable-next-line max-len
|
||||
{ defaultMessage: 'In order to visualize and explore data in Kibana, you\'ll need to create an index pattern to retrieve data from Elasticsearch.' })
|
||||
}
|
||||
/>
|
||||
)
|
||||
});
|
||||
|
||||
// hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around
|
||||
timeoutId = setTimeout(() => {
|
||||
banners.remove(bannerId);
|
||||
timeoutId = undefined;
|
||||
}, 15000);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function (opts) {
|
||||
opts = opts || {};
|
||||
const whenMissingRedirectTo = opts.whenMissingRedirectTo || null;
|
||||
|
||||
uiRoutes
|
||||
.addSetupWork(function loadDefaultIndexPattern(Promise, $route, config, indexPatterns) {
|
||||
const route = _.get($route, 'current.$$route');
|
||||
|
||||
if (!route.requireDefaultIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
return indexPatterns.getIds()
|
||||
.then(function (patterns) {
|
||||
let defaultId = config.get('defaultIndex');
|
||||
let defined = !!defaultId;
|
||||
const exists = _.contains(patterns, defaultId);
|
||||
|
||||
if (defined && !exists) {
|
||||
config.remove('defaultIndex');
|
||||
defaultId = defined = false;
|
||||
}
|
||||
|
||||
if (!defined) {
|
||||
// If there is any index pattern created, set the first as default
|
||||
if (patterns.length >= 1) {
|
||||
defaultId = patterns[0];
|
||||
config.set('defaultIndex', defaultId);
|
||||
} else {
|
||||
throw new NoDefaultIndexPattern();
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.afterWork(
|
||||
// success
|
||||
null,
|
||||
|
||||
// failure
|
||||
function (err, kbnUrl) {
|
||||
const hasDefault = !(err instanceof NoDefaultIndexPattern);
|
||||
if (hasDefault || !whenMissingRedirectTo) throw err; // rethrow
|
||||
|
||||
kbnUrl.change(whenMissingRedirectTo());
|
||||
|
||||
displayBanner();
|
||||
}
|
||||
);
|
||||
}
|
|
@ -24,6 +24,7 @@ import '../saved_visualizations/saved_visualizations';
|
|||
import './visualization_editor';
|
||||
import './visualization';
|
||||
|
||||
import { ensureDefaultIndexPattern } from 'ui/legacy_compat';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { migrateAppState } from './lib';
|
||||
|
@ -49,6 +50,7 @@ import {
|
|||
} from '../kibana_services';
|
||||
|
||||
const {
|
||||
core,
|
||||
capabilities,
|
||||
chrome,
|
||||
chromeLegacy,
|
||||
|
@ -71,7 +73,7 @@ uiRoutes
|
|||
template: editorTemplate,
|
||||
k7Breadcrumbs: getCreateBreadcrumbs,
|
||||
resolve: {
|
||||
savedVis: function (savedVisualizations, redirectWhenMissing, $route) {
|
||||
savedVis: function (savedVisualizations, redirectWhenMissing, $route, $rootScope, kbnUrl) {
|
||||
const visTypes = visualizations.types.all();
|
||||
const visType = _.find(visTypes, { name: $route.current.params.type });
|
||||
const shouldHaveIndex = visType.requiresSearch && visType.options.showIndexSelection;
|
||||
|
@ -84,7 +86,7 @@ uiRoutes
|
|||
);
|
||||
}
|
||||
|
||||
return savedVisualizations.get($route.current.params)
|
||||
return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl).then(() => savedVisualizations.get($route.current.params))
|
||||
.then(savedVis => {
|
||||
if (savedVis.vis.type.setup) {
|
||||
return savedVis.vis.type.setup(savedVis)
|
||||
|
@ -102,28 +104,33 @@ uiRoutes
|
|||
template: editorTemplate,
|
||||
k7Breadcrumbs: getEditBreadcrumbs,
|
||||
resolve: {
|
||||
savedVis: function (savedVisualizations, redirectWhenMissing, $route) {
|
||||
return savedVisualizations.get($route.current.params.id)
|
||||
savedVis: function (savedVisualizations, redirectWhenMissing, $route, $rootScope, kbnUrl) {
|
||||
return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl)
|
||||
.then(() => savedVisualizations.get($route.current.params.id))
|
||||
.then((savedVis) => {
|
||||
chrome.recentlyAccessed.add(
|
||||
savedVis.getFullPath(),
|
||||
savedVis.title,
|
||||
savedVis.id);
|
||||
savedVis.id
|
||||
);
|
||||
return savedVis;
|
||||
})
|
||||
.then(savedVis => {
|
||||
if (savedVis.vis.type.setup) {
|
||||
return savedVis.vis.type.setup(savedVis)
|
||||
.catch(() => savedVis);
|
||||
return savedVis.vis.type.setup(savedVis).catch(() => savedVis);
|
||||
}
|
||||
return savedVis;
|
||||
})
|
||||
.catch(redirectWhenMissing({
|
||||
'visualization': '/visualize',
|
||||
'search': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id,
|
||||
'index-pattern': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id,
|
||||
'index-pattern-field': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id
|
||||
}));
|
||||
.catch(
|
||||
redirectWhenMissing({
|
||||
visualization: '/visualize',
|
||||
search: '/management/kibana/objects/savedVisualizations/' + $route.current.params.id,
|
||||
'index-pattern':
|
||||
'/management/kibana/objects/savedVisualizations/' + $route.current.params.id,
|
||||
'index-pattern-field':
|
||||
'/management/kibana/objects/savedVisualizations/' + $route.current.params.id,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { ensureDefaultIndexPattern } from 'ui/legacy_compat';
|
||||
import './editor/editor';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import './saved_visualizations/_saved_vis';
|
||||
|
@ -32,7 +33,6 @@ const { FeatureCatalogueRegistryProvider, uiRoutes } = getServices();
|
|||
|
||||
uiRoutes
|
||||
.defaults(/visualize/, {
|
||||
requireDefaultIndex: true,
|
||||
requireUICapability: 'visualize.show',
|
||||
badge: uiCapabilities => {
|
||||
if (uiCapabilities.visualize.save) {
|
||||
|
@ -57,6 +57,7 @@ uiRoutes
|
|||
controllerAs: 'listingController',
|
||||
resolve: {
|
||||
createNewVis: () => false,
|
||||
hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().data, $rootScope, kbnUrl)
|
||||
},
|
||||
})
|
||||
.when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, {
|
||||
|
@ -66,6 +67,7 @@ uiRoutes
|
|||
controllerAs: 'listingController',
|
||||
resolve: {
|
||||
createNewVis: () => true,
|
||||
hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().data, $rootScope, kbnUrl)
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ const services = {
|
|||
savedObjectsClient: npStart.core.savedObjects.client,
|
||||
toastNotifications: npStart.core.notifications.toasts,
|
||||
uiSettings: npStart.core.uiSettings,
|
||||
core: npStart.core,
|
||||
|
||||
share: npStart.plugins.share,
|
||||
data,
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* 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 { get } from 'lodash';
|
||||
import chrome from 'ui/chrome';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { UICapabilities } from '.';
|
||||
|
||||
uiRoutes.addSetupWork(
|
||||
(uiCapabilities: UICapabilities, kbnBaseUrl: string, $route: any, kbnUrl: any) => {
|
||||
const route = get($route, 'current.$$route') as any;
|
||||
if (!route.requireUICapability) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!get(uiCapabilities, route.requireUICapability)) {
|
||||
const url = chrome.addBasePath(`${kbnBaseUrl}#/home`);
|
||||
kbnUrl.redirect(url);
|
||||
throw uiRoutes.WAIT_FOR_URL_CHANGE_TOKEN;
|
||||
}
|
||||
}
|
||||
);
|
4
src/legacy/ui/public/chrome/api/angular.js
vendored
4
src/legacy/ui/public/chrome/api/angular.js
vendored
|
@ -21,13 +21,15 @@ import { uiModules } from '../../modules';
|
|||
|
||||
import { directivesProvider } from '../directives';
|
||||
import { registerSubUrlHooks } from './sub_url_hooks';
|
||||
import { start as data } from '../../../../core_plugins/data/public/legacy';
|
||||
import { configureAppAngularModule } from 'ui/legacy_compat';
|
||||
import { npStart } from '../../new_platform/new_platform';
|
||||
|
||||
export function initAngularApi(chrome, internals) {
|
||||
chrome.setupAngular = function () {
|
||||
const kibana = uiModules.get('kibana');
|
||||
|
||||
configureAppAngularModule(kibana);
|
||||
configureAppAngularModule(kibana, npStart.core, data, false);
|
||||
|
||||
kibana.value('chrome', chrome);
|
||||
|
||||
|
|
|
@ -75,8 +75,7 @@ export function createTopNavDirective() {
|
|||
|
||||
module.directive('kbnTopNav', createTopNavDirective);
|
||||
|
||||
export function createTopNavHelper(reactDirective) {
|
||||
const { TopNavMenu } = navigation.ui;
|
||||
export const createTopNavHelper = ({ TopNavMenu }) => (reactDirective) => {
|
||||
return reactDirective(
|
||||
wrapInI18nContext(TopNavMenu),
|
||||
[
|
||||
|
@ -116,6 +115,6 @@ export function createTopNavHelper(reactDirective) {
|
|||
'showAutoRefreshOnly',
|
||||
],
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
module.directive('kbnTopNavHelper', createTopNavHelper);
|
||||
module.directive('kbnTopNavHelper', createTopNavHelper(navigation.ui));
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
IRootScopeService,
|
||||
} from 'angular';
|
||||
import $ from 'jquery';
|
||||
import { cloneDeep, forOwn, set } from 'lodash';
|
||||
import _, { cloneDeep, forOwn, get, set } from 'lodash';
|
||||
import React, { Fragment } from 'react';
|
||||
import * as Rx from 'rxjs';
|
||||
|
||||
|
@ -37,27 +37,43 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
import { CoreStart, LegacyCoreStart } from 'kibana/public';
|
||||
|
||||
import { fatalError } from 'ui/notify';
|
||||
import { capabilities } from 'ui/capabilities';
|
||||
import { RouteConfiguration } from 'ui/routes/route_manager';
|
||||
// @ts-ignore
|
||||
import { modifyUrl } from 'ui/url';
|
||||
import { toMountPoint } from '../../../../plugins/kibana_react/public';
|
||||
// @ts-ignore
|
||||
import { UrlOverflowService } from '../error_url_overflow';
|
||||
import { npStart } from '../new_platform';
|
||||
import { toastNotifications } from '../notify';
|
||||
// @ts-ignore
|
||||
import { isSystemApiRequest } from '../system_api';
|
||||
|
||||
const URL_LIMIT_WARN_WITHIN = 1000;
|
||||
|
||||
function isDummyWrapperRoute($route: any) {
|
||||
/**
|
||||
* Detects whether a given angular route is a dummy route that doesn't
|
||||
* require any action. There are two ways this can happen:
|
||||
* If `outerAngularWrapperRoute` is set on the route config object,
|
||||
* it means the local application service set up this route on the outer angular
|
||||
* and the internal routes will handle the hooks.
|
||||
*
|
||||
* If angular did not detect a route and it is the local angular, we are currently
|
||||
* navigating away from a URL controlled by a local angular router and the
|
||||
* application will get unmounted. In this case the outer router will handle
|
||||
* the hooks.
|
||||
* @param $route Injected $route dependency
|
||||
* @param isLocalAngular Flag whether this is the local angular router
|
||||
*/
|
||||
function isDummyRoute($route: any, isLocalAngular: boolean) {
|
||||
return (
|
||||
$route.current && $route.current.$$route && $route.current.$$route.outerAngularWrapperRoute
|
||||
($route.current && $route.current.$$route && $route.current.$$route.outerAngularWrapperRoute) ||
|
||||
(!$route.current && isLocalAngular)
|
||||
);
|
||||
}
|
||||
|
||||
export const configureAppAngularModule = (angularModule: IModule) => {
|
||||
const newPlatform = npStart.core;
|
||||
export const configureAppAngularModule = (
|
||||
angularModule: IModule,
|
||||
newPlatform: LegacyCoreStart,
|
||||
isLocalAngular: boolean
|
||||
) => {
|
||||
const legacyMetadata = newPlatform.injectedMetadata.getLegacyMetadata();
|
||||
|
||||
forOwn(newPlatform.injectedMetadata.getInjectedVars(), (val, name) => {
|
||||
|
@ -73,15 +89,16 @@ export const configureAppAngularModule = (angularModule: IModule) => {
|
|||
.value('buildSha', legacyMetadata.buildSha)
|
||||
.value('serverName', legacyMetadata.serverName)
|
||||
.value('esUrl', getEsUrl(newPlatform))
|
||||
.value('uiCapabilities', capabilities.get())
|
||||
.value('uiCapabilities', newPlatform.application.capabilities)
|
||||
.config(setupCompileProvider(newPlatform))
|
||||
.config(setupLocationProvider(newPlatform))
|
||||
.config($setupXsrfRequestInterceptor(newPlatform))
|
||||
.run(capture$httpLoadingCount(newPlatform))
|
||||
.run($setupBreadcrumbsAutoClear(newPlatform))
|
||||
.run($setupBadgeAutoClear(newPlatform))
|
||||
.run($setupHelpExtensionAutoClear(newPlatform))
|
||||
.run($setupUrlOverflowHandling(newPlatform));
|
||||
.run($setupBreadcrumbsAutoClear(newPlatform, isLocalAngular))
|
||||
.run($setupBadgeAutoClear(newPlatform, isLocalAngular))
|
||||
.run($setupHelpExtensionAutoClear(newPlatform, isLocalAngular))
|
||||
.run($setupUrlOverflowHandling(newPlatform, isLocalAngular))
|
||||
.run($setupUICapabilityRedirect(newPlatform));
|
||||
};
|
||||
|
||||
const getEsUrl = (newPlatform: CoreStart) => {
|
||||
|
@ -168,12 +185,42 @@ const capture$httpLoadingCount = (newPlatform: CoreStart) => (
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* integrates with angular to automatically redirect to home if required
|
||||
* capability is not met
|
||||
*/
|
||||
const $setupUICapabilityRedirect = (newPlatform: CoreStart) => (
|
||||
$rootScope: IRootScopeService,
|
||||
$injector: any
|
||||
) => {
|
||||
const isKibanaAppRoute = window.location.pathname.endsWith('/app/kibana');
|
||||
// this feature only works within kibana app for now after everything is
|
||||
// switched to the application service, this can be changed to handle all
|
||||
// apps.
|
||||
if (!isKibanaAppRoute) {
|
||||
return;
|
||||
}
|
||||
$rootScope.$on(
|
||||
'$routeChangeStart',
|
||||
(event, { $$route: route }: { $$route?: RouteConfiguration } = {}) => {
|
||||
if (!route || !route.requireUICapability) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!get(newPlatform.application.capabilities, route.requireUICapability)) {
|
||||
$injector.get('kbnUrl').change('/home');
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* internal angular run function that will be called when angular bootstraps and
|
||||
* lets us integrate with the angular router so that we can automatically clear
|
||||
* the breadcrumbs if we switch to a Kibana app that does not use breadcrumbs correctly
|
||||
*/
|
||||
const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => (
|
||||
const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart, isLocalAngular: boolean) => (
|
||||
$rootScope: IRootScopeService,
|
||||
$injector: any
|
||||
) => {
|
||||
|
@ -195,7 +242,7 @@ const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => (
|
|||
});
|
||||
|
||||
$rootScope.$on('$routeChangeSuccess', () => {
|
||||
if (isDummyWrapperRoute($route)) {
|
||||
if (isDummyRoute($route, isLocalAngular)) {
|
||||
return;
|
||||
}
|
||||
const current = $route.current || {};
|
||||
|
@ -223,7 +270,7 @@ const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => (
|
|||
* lets us integrate with the angular router so that we can automatically clear
|
||||
* the badge if we switch to a Kibana app that does not use the badge correctly
|
||||
*/
|
||||
const $setupBadgeAutoClear = (newPlatform: CoreStart) => (
|
||||
const $setupBadgeAutoClear = (newPlatform: CoreStart, isLocalAngular: boolean) => (
|
||||
$rootScope: IRootScopeService,
|
||||
$injector: any
|
||||
) => {
|
||||
|
@ -237,7 +284,7 @@ const $setupBadgeAutoClear = (newPlatform: CoreStart) => (
|
|||
});
|
||||
|
||||
$rootScope.$on('$routeChangeSuccess', () => {
|
||||
if (isDummyWrapperRoute($route)) {
|
||||
if (isDummyRoute($route, isLocalAngular)) {
|
||||
return;
|
||||
}
|
||||
const current = $route.current || {};
|
||||
|
@ -266,7 +313,7 @@ const $setupBadgeAutoClear = (newPlatform: CoreStart) => (
|
|||
* the helpExtension if we switch to a Kibana app that does not set its own
|
||||
* helpExtension
|
||||
*/
|
||||
const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => (
|
||||
const $setupHelpExtensionAutoClear = (newPlatform: CoreStart, isLocalAngular: boolean) => (
|
||||
$rootScope: IRootScopeService,
|
||||
$injector: any
|
||||
) => {
|
||||
|
@ -284,14 +331,14 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => (
|
|||
const $route = $injector.has('$route') ? $injector.get('$route') : {};
|
||||
|
||||
$rootScope.$on('$routeChangeStart', () => {
|
||||
if (isDummyWrapperRoute($route)) {
|
||||
if (isDummyRoute($route, isLocalAngular)) {
|
||||
return;
|
||||
}
|
||||
helpExtensionSetSinceRouteChange = false;
|
||||
});
|
||||
|
||||
$rootScope.$on('$routeChangeSuccess', () => {
|
||||
if (isDummyWrapperRoute($route)) {
|
||||
if (isDummyRoute($route, isLocalAngular)) {
|
||||
return;
|
||||
}
|
||||
const current = $route.current || {};
|
||||
|
@ -304,7 +351,7 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => (
|
|||
});
|
||||
};
|
||||
|
||||
const $setupUrlOverflowHandling = (newPlatform: CoreStart) => (
|
||||
const $setupUrlOverflowHandling = (newPlatform: CoreStart, isLocalAngular: boolean) => (
|
||||
$location: ILocationService,
|
||||
$rootScope: IRootScopeService,
|
||||
$injector: auto.IInjectorService
|
||||
|
@ -312,7 +359,7 @@ const $setupUrlOverflowHandling = (newPlatform: CoreStart) => (
|
|||
const $route = $injector.has('$route') ? $injector.get('$route') : {};
|
||||
const urlOverflow = new UrlOverflowService();
|
||||
const check = () => {
|
||||
if (isDummyWrapperRoute($route)) {
|
||||
if (isDummyRoute($route, isLocalAngular)) {
|
||||
return;
|
||||
}
|
||||
// disable long url checks when storing state in session storage
|
||||
|
@ -326,7 +373,7 @@ const $setupUrlOverflowHandling = (newPlatform: CoreStart) => (
|
|||
|
||||
try {
|
||||
if (urlOverflow.check($location.absUrl()) <= URL_LIMIT_WARN_WITHIN) {
|
||||
toastNotifications.addWarning({
|
||||
newPlatform.notifications.toasts.addWarning({
|
||||
title: i18n.translate('common.ui.chrome.bigUrlWarningNotificationTitle', {
|
||||
defaultMessage: 'The URL is big and Kibana might stop working',
|
||||
}),
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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 { contains } from 'lodash';
|
||||
import { IRootScopeService } from 'angular';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import { DataStart } from '../../../core_plugins/data/public';
|
||||
|
||||
let bannerId: string;
|
||||
let timeoutId: NodeJS.Timeout | undefined;
|
||||
|
||||
/**
|
||||
* Checks whether a default index pattern is set and exists and defines
|
||||
* one otherwise.
|
||||
*
|
||||
* If there are no index patterns, redirect to management page and show
|
||||
* banner. In this case the promise returned from this function will never
|
||||
* resolve to wait for the URL change to happen.
|
||||
*/
|
||||
export async function ensureDefaultIndexPattern(
|
||||
newPlatform: CoreStart,
|
||||
data: DataStart,
|
||||
$rootScope: IRootScopeService,
|
||||
kbnUrl: any
|
||||
) {
|
||||
const patterns = await data.indexPatterns.indexPatterns.getIds();
|
||||
let defaultId = newPlatform.uiSettings.get('defaultIndex');
|
||||
let defined = !!defaultId;
|
||||
const exists = contains(patterns, defaultId);
|
||||
|
||||
if (defined && !exists) {
|
||||
newPlatform.uiSettings.remove('defaultIndex');
|
||||
defaultId = defined = false;
|
||||
}
|
||||
|
||||
if (defined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is any index pattern created, set the first as default
|
||||
if (patterns.length >= 1) {
|
||||
defaultId = patterns[0];
|
||||
newPlatform.uiSettings.set('defaultIndex', defaultId);
|
||||
} else {
|
||||
const canManageIndexPatterns =
|
||||
newPlatform.application.capabilities.management.kibana.index_patterns;
|
||||
const redirectTarget = canManageIndexPatterns ? '/management/kibana/index_pattern' : '/home';
|
||||
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
// Avoid being hostile to new users who don't have an index pattern setup yet
|
||||
// give them a friendly info message instead of a terse error message
|
||||
bannerId = newPlatform.overlays.banners.replace(bannerId, (element: HTMLElement) => {
|
||||
ReactDOM.render(
|
||||
<I18nProvider>
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
iconType="iInCircle"
|
||||
title={i18n.translate('common.ui.indexPattern.bannerLabel', {
|
||||
defaultMessage:
|
||||
"In order to visualize and explore data in Kibana, you'll need to create an index pattern to retrieve data from Elasticsearch.",
|
||||
})}
|
||||
/>
|
||||
</I18nProvider>,
|
||||
element
|
||||
);
|
||||
return () => ReactDOM.unmountComponentAtNode(element);
|
||||
});
|
||||
|
||||
// hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around
|
||||
timeoutId = setTimeout(() => {
|
||||
newPlatform.overlays.banners.remove(bannerId);
|
||||
timeoutId = undefined;
|
||||
}, 15000);
|
||||
|
||||
kbnUrl.change(redirectTarget);
|
||||
$rootScope.$digest();
|
||||
|
||||
// return never-resolving promise to stop resolving and wait for the url change
|
||||
return new Promise(() => {});
|
||||
}
|
||||
}
|
|
@ -18,3 +18,4 @@
|
|||
*/
|
||||
|
||||
export { configureAppAngularModule } from './angular_config';
|
||||
export { ensureDefaultIndexPattern } from './ensure_default_index_pattern';
|
||||
|
|
|
@ -119,18 +119,6 @@ describe('routes/route_manager', function () {
|
|||
expect($rp.when.secondCall.args[1]).to.have.property('reloadOnSearch', false);
|
||||
expect($rp.when.lastCall.args[1]).to.have.property('reloadOnSearch', true);
|
||||
});
|
||||
|
||||
it('sets route.requireDefaultIndex to false by default', function () {
|
||||
routes.when('/nothing-set');
|
||||
routes.when('/no-index-required', { requireDefaultIndex: false });
|
||||
routes.when('/index-required', { requireDefaultIndex: true });
|
||||
routes.config($rp);
|
||||
|
||||
expect($rp.when.callCount).to.be(3);
|
||||
expect($rp.when.firstCall.args[1]).to.have.property('requireDefaultIndex', false);
|
||||
expect($rp.when.secondCall.args[1]).to.have.property('requireDefaultIndex', false);
|
||||
expect($rp.when.lastCall.args[1]).to.have.property('requireDefaultIndex', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#defaults()', () => {
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
import { ChromeBreadcrumb } from '../../../../core/public';
|
||||
|
||||
interface RouteConfiguration {
|
||||
export interface RouteConfiguration {
|
||||
controller?: string | ((...args: any[]) => void);
|
||||
redirectTo?: string;
|
||||
resolveRedirectTo?: (...args: any[]) => void;
|
||||
|
|
|
@ -46,10 +46,6 @@ export default function RouteManager() {
|
|||
route.reloadOnSearch = false;
|
||||
}
|
||||
|
||||
if (route.requireDefaultIndex == null) {
|
||||
route.requireDefaultIndex = false;
|
||||
}
|
||||
|
||||
wrapRouteWithPrep(route, setup);
|
||||
$routeProvider.when(path, route);
|
||||
});
|
||||
|
|
|
@ -42,9 +42,14 @@ import {
|
|||
isStateHash,
|
||||
} from './state_storage';
|
||||
|
||||
export function StateProvider(Private, $rootScope, $location, stateManagementConfig, config, kbnUrl) {
|
||||
export function StateProvider(Private, $rootScope, $location, stateManagementConfig, config, kbnUrl, $injector) {
|
||||
const Events = Private(EventsProvider);
|
||||
|
||||
const isDummyRoute = () =>
|
||||
$injector.has('$route') &&
|
||||
$injector.get('$route').current &&
|
||||
$injector.get('$route').current.outerAngularWrapperRoute;
|
||||
|
||||
createLegacyClass(State).inherits(Events);
|
||||
function State(
|
||||
urlParam,
|
||||
|
@ -137,7 +142,7 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon
|
|||
|
||||
let stash = this._readFromURL();
|
||||
|
||||
// nothing to read from the url? save if ordered to persist
|
||||
// nothing to read from the url? save if ordered to persist, but only if it's not on a wrapper route
|
||||
if (stash === null) {
|
||||
if (this._persistAcrossApps) {
|
||||
return this.save();
|
||||
|
@ -150,7 +155,7 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon
|
|||
// apply diff to state from stash, will change state in place via side effect
|
||||
const diffResults = applyDiff(this, stash);
|
||||
|
||||
if (diffResults.keys.length) {
|
||||
if (!isDummyRoute() && diffResults.keys.length) {
|
||||
this.emit('fetch_with_changes', diffResults.keys);
|
||||
}
|
||||
};
|
||||
|
@ -164,6 +169,10 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon
|
|||
return;
|
||||
}
|
||||
|
||||
if (isDummyRoute()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let stash = this._readFromURL();
|
||||
const state = this.toObject();
|
||||
replace = replace || false;
|
||||
|
|
|
@ -42,9 +42,14 @@ describe('registerTimefilterWithGlobalState()', () => {
|
|||
}
|
||||
};
|
||||
|
||||
const rootScope = {
|
||||
$on: jest.fn()
|
||||
};
|
||||
|
||||
registerTimefilterWithGlobalState(
|
||||
timefilter,
|
||||
globalState
|
||||
globalState,
|
||||
rootScope,
|
||||
);
|
||||
|
||||
expect(setTime.mock.calls.length).toBe(2);
|
||||
|
|
|
@ -23,6 +23,7 @@ import moment from 'moment';
|
|||
import { subscribeWithScope } from 'ui/utils/subscribe_with_scope';
|
||||
import chrome from 'ui/chrome';
|
||||
import { RefreshInterval, TimeRange, TimefilterContract } from 'src/plugins/data/public';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
// TODO
|
||||
// remove everything underneath once globalState is no longer an angular service
|
||||
|
@ -40,49 +41,62 @@ export function getTimefilterConfig() {
|
|||
};
|
||||
}
|
||||
|
||||
export const registerTimefilterWithGlobalStateFactory = (
|
||||
timefilter: TimefilterContract,
|
||||
globalState: any,
|
||||
$rootScope: IScope
|
||||
) => {
|
||||
// settings have to be re-fetched here, to make sure that settings changed by overrideLocalDefault are taken into account.
|
||||
const config = getTimefilterConfig();
|
||||
timefilter.setTime(_.defaults(globalState.time || {}, config.timeDefaults));
|
||||
timefilter.setRefreshInterval(
|
||||
_.defaults(globalState.refreshInterval || {}, config.refreshIntervalDefaults)
|
||||
);
|
||||
|
||||
globalState.on('fetch_with_changes', () => {
|
||||
// clone and default to {} in one
|
||||
const newTime: TimeRange = _.defaults({}, globalState.time, config.timeDefaults);
|
||||
const newRefreshInterval: RefreshInterval = _.defaults(
|
||||
{},
|
||||
globalState.refreshInterval,
|
||||
config.refreshIntervalDefaults
|
||||
);
|
||||
|
||||
if (newTime) {
|
||||
if (newTime.to) newTime.to = convertISO8601(newTime.to);
|
||||
if (newTime.from) newTime.from = convertISO8601(newTime.from);
|
||||
}
|
||||
|
||||
timefilter.setTime(newTime);
|
||||
timefilter.setRefreshInterval(newRefreshInterval);
|
||||
});
|
||||
|
||||
const updateGlobalStateWithTime = () => {
|
||||
globalState.time = timefilter.getTime();
|
||||
globalState.refreshInterval = timefilter.getRefreshInterval();
|
||||
globalState.save();
|
||||
};
|
||||
|
||||
const subscriptions = new Subscription();
|
||||
subscriptions.add(
|
||||
subscribeWithScope($rootScope, timefilter.getRefreshIntervalUpdate$(), {
|
||||
next: updateGlobalStateWithTime,
|
||||
})
|
||||
);
|
||||
|
||||
subscriptions.add(
|
||||
subscribeWithScope($rootScope, timefilter.getTimeUpdate$(), {
|
||||
next: updateGlobalStateWithTime,
|
||||
})
|
||||
);
|
||||
|
||||
$rootScope.$on('$destroy', () => {
|
||||
subscriptions.unsubscribe();
|
||||
});
|
||||
};
|
||||
|
||||
// Currently some parts of Kibana (index patterns, timefilter) rely on addSetupWork in the uiRouter
|
||||
// and require it to be executed to properly function.
|
||||
// This function is exposed for applications that do not use uiRoutes like APM
|
||||
// Kibana issue https://github.com/elastic/kibana/issues/19110 tracks the removal of this dependency on uiRouter
|
||||
export const registerTimefilterWithGlobalState = _.once(
|
||||
(timefilter: TimefilterContract, globalState: any, $rootScope: IScope) => {
|
||||
// settings have to be re-fetched here, to make sure that settings changed by overrideLocalDefault are taken into account.
|
||||
const config = getTimefilterConfig();
|
||||
timefilter.setTime(_.defaults(globalState.time || {}, config.timeDefaults));
|
||||
timefilter.setRefreshInterval(
|
||||
_.defaults(globalState.refreshInterval || {}, config.refreshIntervalDefaults)
|
||||
);
|
||||
|
||||
globalState.on('fetch_with_changes', () => {
|
||||
// clone and default to {} in one
|
||||
const newTime: TimeRange = _.defaults({}, globalState.time, config.timeDefaults);
|
||||
const newRefreshInterval: RefreshInterval = _.defaults(
|
||||
{},
|
||||
globalState.refreshInterval,
|
||||
config.refreshIntervalDefaults
|
||||
);
|
||||
|
||||
if (newTime) {
|
||||
if (newTime.to) newTime.to = convertISO8601(newTime.to);
|
||||
if (newTime.from) newTime.from = convertISO8601(newTime.from);
|
||||
}
|
||||
|
||||
timefilter.setTime(newTime);
|
||||
timefilter.setRefreshInterval(newRefreshInterval);
|
||||
});
|
||||
|
||||
const updateGlobalStateWithTime = () => {
|
||||
globalState.time = timefilter.getTime();
|
||||
globalState.refreshInterval = timefilter.getRefreshInterval();
|
||||
globalState.save();
|
||||
};
|
||||
|
||||
subscribeWithScope($rootScope, timefilter.getRefreshIntervalUpdate$(), {
|
||||
next: updateGlobalStateWithTime,
|
||||
});
|
||||
|
||||
subscribeWithScope($rootScope, timefilter.getTimeUpdate$(), {
|
||||
next: updateGlobalStateWithTime,
|
||||
});
|
||||
}
|
||||
);
|
||||
export const registerTimefilterWithGlobalState = _.once(registerTimefilterWithGlobalStateFactory);
|
||||
|
|
|
@ -115,7 +115,6 @@ const VisFiltersProvider = (getAppState, $timeout) => {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
pushFilters,
|
||||
};
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
.dshDashboardViewport {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: $euiColorEmptyShade;
|
||||
}
|
||||
|
||||
.dshDashboardViewport-withMargins {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
const kibanaServer = getService('kibanaServer');
|
||||
const PageObjects = getPageObjects(['dashboard', 'common']);
|
||||
const browser = getService('browser');
|
||||
const globalNav = getService('globalNav');
|
||||
|
||||
describe('embed mode', () => {
|
||||
before(async () => {
|
||||
|
@ -38,8 +39,8 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
|
||||
it('hides the chrome', async () => {
|
||||
const isChromeVisible = await PageObjects.common.isChromeVisible();
|
||||
expect(isChromeVisible).to.be(true);
|
||||
const globalNavShown = await globalNav.exists();
|
||||
expect(globalNavShown).to.be(true);
|
||||
|
||||
const currentUrl = await browser.getCurrentUrl();
|
||||
const newUrl = currentUrl + '&embed=true';
|
||||
|
@ -48,8 +49,8 @@ export default function ({ getService, getPageObjects }) {
|
|||
await browser.get(newUrl.toString(), useTimeStamp);
|
||||
|
||||
await retry.try(async () => {
|
||||
const isChromeHidden = await PageObjects.common.isChromeHidden();
|
||||
expect(isChromeHidden).to.be(true);
|
||||
const globalNavHidden = !(await globalNav.exists());
|
||||
expect(globalNavHidden).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -37,6 +37,8 @@ import 'ui/agg_response';
|
|||
import 'ui/agg_types';
|
||||
import 'leaflet';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { localApplicationService } from 'plugins/kibana/local_application_service';
|
||||
|
||||
|
||||
import { showAppRedirectNotification } from 'ui/notify';
|
||||
import { DashboardConstants, createDashboardEditUrl } from 'plugins/kibana/dashboard/dashboard_constants';
|
||||
|
@ -44,6 +46,8 @@ import { DashboardConstants, createDashboardEditUrl } from 'plugins/kibana/dashb
|
|||
uiModules.get('kibana')
|
||||
.config(dashboardConfigProvider => dashboardConfigProvider.turnHideWriteControlsOn());
|
||||
|
||||
localApplicationService.attachToAngular(routes);
|
||||
|
||||
routes.enable();
|
||||
routes.otherwise({ redirectTo: defaultUrl() });
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_regis
|
|||
import { npSetup, npStart } from 'ui/new_platform';
|
||||
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
|
||||
import { start as data } from '../../../../../src/legacy/core_plugins/data/public/legacy';
|
||||
import { start as navigation } from '../../../../../src/legacy/core_plugins/navigation/public/legacy';
|
||||
import { GraphPlugin } from './plugin';
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -52,6 +53,7 @@ async function getAngularInjectedDependencies(): Promise<LegacyAngularInjectedDe
|
|||
instance.start(npStart.core, {
|
||||
data,
|
||||
npData: npStart.plugins.data,
|
||||
navigation,
|
||||
__LEGACY: {
|
||||
angularDependencies: await getAngularInjectedDependencies(),
|
||||
},
|
||||
|
|
|
@ -9,10 +9,12 @@ import { CoreSetup, CoreStart, Plugin, SavedObjectsClientContract } from 'src/co
|
|||
import { DataStart } from 'src/legacy/core_plugins/data/public';
|
||||
import { Plugin as DataPlugin } from 'src/plugins/data/public';
|
||||
import { LegacyAngularInjectedDependencies } from './render_app';
|
||||
import { NavigationStart } from '../../../../../src/legacy/core_plugins/navigation/public';
|
||||
|
||||
export interface GraphPluginStartDependencies {
|
||||
data: DataStart;
|
||||
npData: ReturnType<DataPlugin['start']>;
|
||||
navigation: NavigationStart;
|
||||
}
|
||||
|
||||
export interface GraphPluginSetupDependencies {
|
||||
|
@ -30,6 +32,7 @@ export interface GraphPluginStartDependencies {
|
|||
|
||||
export class GraphPlugin implements Plugin {
|
||||
private dataStart: DataStart | null = null;
|
||||
private navigationStart: NavigationStart | null = null;
|
||||
private npDataStart: ReturnType<DataPlugin['start']> | null = null;
|
||||
private savedObjectsClient: SavedObjectsClientContract | null = null;
|
||||
private angularDependencies: LegacyAngularInjectedDependencies | null = null;
|
||||
|
@ -42,6 +45,7 @@ export class GraphPlugin implements Plugin {
|
|||
const { renderApp } = await import('./render_app');
|
||||
return renderApp({
|
||||
...params,
|
||||
navigation: this.navigationStart!,
|
||||
npData: this.npDataStart!,
|
||||
savedObjectsClient: this.savedObjectsClient!,
|
||||
xpackInfo,
|
||||
|
@ -66,9 +70,9 @@ export class GraphPlugin implements Plugin {
|
|||
|
||||
start(
|
||||
core: CoreStart,
|
||||
{ data, npData, __LEGACY: { angularDependencies } }: GraphPluginStartDependencies
|
||||
{ data, npData, navigation, __LEGACY: { angularDependencies } }: GraphPluginStartDependencies
|
||||
) {
|
||||
// TODO is this really the right way? I though the app context would give us those
|
||||
this.navigationStart = navigation;
|
||||
this.dataStart = data;
|
||||
this.npDataStart = npData;
|
||||
this.angularDependencies = angularDependencies;
|
||||
|
|
|
@ -25,6 +25,7 @@ import { DataStart } from 'src/legacy/core_plugins/data/public';
|
|||
import {
|
||||
AppMountContext,
|
||||
ChromeStart,
|
||||
LegacyCoreStart,
|
||||
SavedObjectsClientContract,
|
||||
ToastsStart,
|
||||
UiSettingsClientContract,
|
||||
|
@ -32,6 +33,7 @@ import {
|
|||
// @ts-ignore
|
||||
import { initGraphApp } from './app';
|
||||
import { Plugin as DataPlugin } from '../../../../../src/plugins/data/public';
|
||||
import { NavigationStart } from '../../../../../src/legacy/core_plugins/navigation/public';
|
||||
|
||||
/**
|
||||
* These are dependencies of the Graph app besides the base dependencies
|
||||
|
@ -44,6 +46,7 @@ export interface GraphDependencies extends LegacyAngularInjectedDependencies {
|
|||
appBasePath: string;
|
||||
capabilities: Record<string, boolean | Record<string, boolean>>;
|
||||
coreStart: AppMountContext['core'];
|
||||
navigation: NavigationStart;
|
||||
chrome: ChromeStart;
|
||||
config: UiSettingsClientContract;
|
||||
toastNotifications: ToastsStart;
|
||||
|
@ -75,8 +78,8 @@ export interface LegacyAngularInjectedDependencies {
|
|||
}
|
||||
|
||||
export const renderApp = ({ appBasePath, element, ...deps }: GraphDependencies) => {
|
||||
const graphAngularModule = createLocalAngularModule(deps.coreStart);
|
||||
configureAppAngularModule(graphAngularModule);
|
||||
const graphAngularModule = createLocalAngularModule(deps.navigation);
|
||||
configureAppAngularModule(graphAngularModule, deps.coreStart as LegacyCoreStart, true);
|
||||
initGraphApp(graphAngularModule, deps);
|
||||
const $injector = mountGraphApp(appBasePath, element);
|
||||
return () => $injector.get('$rootScope').$destroy();
|
||||
|
@ -104,9 +107,9 @@ function mountGraphApp(appBasePath: string, element: HTMLElement) {
|
|||
return $injector;
|
||||
}
|
||||
|
||||
function createLocalAngularModule(core: AppMountContext['core']) {
|
||||
function createLocalAngularModule(navigation: NavigationStart) {
|
||||
createLocalI18nModule();
|
||||
createLocalTopNavModule();
|
||||
createLocalTopNavModule(navigation);
|
||||
createLocalConfirmModalModule();
|
||||
|
||||
const graphAngularModule = angular.module(moduleName, [
|
||||
|
@ -125,11 +128,11 @@ function createLocalConfirmModalModule() {
|
|||
.directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal));
|
||||
}
|
||||
|
||||
function createLocalTopNavModule() {
|
||||
function createLocalTopNavModule(navigation: NavigationStart) {
|
||||
angular
|
||||
.module('graphTopNav', ['react'])
|
||||
.directive('kbnTopNav', createTopNavDirective)
|
||||
.directive('kbnTopNavHelper', createTopNavHelper);
|
||||
.directive('kbnTopNavHelper', createTopNavHelper(navigation.ui));
|
||||
}
|
||||
|
||||
function createLocalI18nModule() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue