mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[New Platform Migration]: Management - Implement NP API (#66781)
* [New Platform Migration]: Management - Implement NP API Part of #47432 * partial progress on a number of management sections * fix passing history * Fixed types * Fixed routing for Ingest Node Pipelines * introduce and use react router wrapped eui components * react router utils * work in progress => hashRouter to router * more partial progress * remove console.log * use reactRouterNavigate for management_sidebar * Breadcrumbs will need to make use of the reactRouterNavigate function * [triggersActions] app. Hash Router -> Router * Replace /app/kibana#/management urls to /app/management * remove ui/public/management * fix some links to management apps * fix management url for functional tests * add data-test-subj for EuiSideNavItem * partial progress * fix some of ts issues * Fixed breadcrumbs for data index management * [kibana/spaces] section * fix functional test * [role_management] fix Breadcrumbs * [api_keys] fix Breadcrumbs and Navigation * Fixed routing for remote cluster * [role_mapping] Partial progress * [users] partial progress * [watcher] partial progress * fix eslint issues * [snapshot_restore] partial progress * [rollup_jobs] partial progress * Fixed routing for cross cluster replications (partial progress). Enhanced reactRouterNavigate * Perf optimization: fix extra re-rendering * fix TS errors * x-pack fix config for functional tests * Fixed routing for index lifecycle management * fix some broken CI tests * fix PR comment * [snapshot_restore] move onClick into reactRouterNavigate * fix some jest * fix some functional tests * fix functiona test: management scripted fields testing regression for issue * fix some functional tests * [licence_management] partial progress * Fixed x-pack jest tests * [saved_object_management] partial progress * Fixed some tests * fix functional test: should add new role myroleEast * Reverted part of changes for ml * [transforms] partial progress * fix TS errors * fix functional: redirects to Kibana home * add support of Backward compatibility * fix functional: Saved objects management feature controls saved objects management global visualize all privileges listing redirects to Kibana home * fix PR comment * fix TS issues * Fixed x-pack jest tests * fix oss JEST * Fixed functional test * fix functional test * fix PR comment * Fixed i18n * fix typo * fix Styles * Fixed paths for cross_cluster_replication * fix wrong link * Fixed jest * Fixed some comments * fix sorting * fix type check * fixed x-pack jest * fixed x-pack jest * reverted using of parentHistory * Add debugging toasts to CCR. * Comment out non-CCR functional tests. * Fix typo. * Uncomment non-CCR functional tests. * Enable CCR. * fix CI * Add comment to explain why CCR is enabled by default and move config variable back to original location in CCR plugin. * revert some changes in APM * add space between index pattern name and tags * fix function test * Update x-pack/plugins/security/public/management/management_urls.ts Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> * Update x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> * Update x-pack/plugins/spaces/public/management/spaces_management_app.tsx Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> * Update x-pack/plugins/security/public/management/roles/roles_management_app.tsx Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> * Update x-pack/plugins/security/public/management/users/users_management_app.tsx Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> * Update x-pack/plugins/security/public/management/management_urls.ts Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> * Update x-pack/plugins/security/public/management/management_urls.ts Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> * [security] getUrlForApp -> navigateToApp * [mp] fix Uncaught (in promise) undefined Co-authored-by: Matt Kime <matt@mattki.me> Co-authored-by: Uladzislau Lasitsa <Uladzislau_Lasitsa@epam.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: CJ Cenizal <cj@cenizal.com> Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com>
This commit is contained in:
parent
a2f44bc27f
commit
d661d66faa
343 changed files with 3433 additions and 3308 deletions
|
@ -38,7 +38,7 @@ export const uiSettingsType: SavedObjectsType = {
|
|||
importableAndExportable: true,
|
||||
getInAppUrl() {
|
||||
return {
|
||||
path: `/app/kibana#/management/kibana/settings`,
|
||||
path: `/app/management/kibana/settings`,
|
||||
uiCapabilitiesPath: 'advancedSettings.show',
|
||||
};
|
||||
},
|
||||
|
|
|
@ -26,8 +26,7 @@ import { exportApi } from './server/routes/api/export';
|
|||
import { getUiSettingDefaults } from './server/ui_setting_defaults';
|
||||
import { registerCspCollector } from './server/lib/csp_usage_collector';
|
||||
import { injectVars } from './inject_vars';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server';
|
||||
|
||||
import { kbnBaseUrl } from '../../../plugins/kibana_legacy/server';
|
||||
|
||||
const mkdirAsync = promisify(Fs.mkdir);
|
||||
|
@ -53,19 +52,7 @@ export default function (kibana) {
|
|||
main: 'plugins/kibana/kibana',
|
||||
},
|
||||
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
|
||||
links: [
|
||||
{
|
||||
id: 'kibana:stack_management',
|
||||
title: i18n.translate('kbn.managementTitle', {
|
||||
defaultMessage: 'Stack Management',
|
||||
}),
|
||||
order: 9003,
|
||||
url: `${kbnBaseUrl}#/management`,
|
||||
euiIconType: 'managementApp',
|
||||
linkToLastSubUrl: false,
|
||||
category: DEFAULT_APP_CATEGORIES.management,
|
||||
},
|
||||
],
|
||||
links: [],
|
||||
|
||||
injectDefaultVars(server, options) {
|
||||
const mapConfig = server.config().get('map');
|
||||
|
|
|
@ -1,83 +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.
|
||||
*/
|
||||
|
||||
const topLevelConfig = require('../../../../../.eslintrc.js');
|
||||
const path = require('path');
|
||||
|
||||
const topLevelRestricedZones = topLevelConfig.overrides.find(
|
||||
override =>
|
||||
override.files[0] === '**/*.{js,ts,tsx}' &&
|
||||
Object.keys(override.rules)[0] === '@kbn/eslint/no-restricted-paths'
|
||||
).rules['@kbn/eslint/no-restricted-paths'][1].zones;
|
||||
|
||||
/**
|
||||
* Builds custom restricted paths configuration for the shimmed plugins within the kibana plugin.
|
||||
* These custom rules extend the default checks in the top level `eslintrc.js` by also checking two other things:
|
||||
* * Making sure nothing within np_ready imports from the `ui` directory
|
||||
* * Making sure no other code is importing things deep from within the shimmed plugins
|
||||
* @param shimmedPlugins List of plugin names within the kibana plugin that are partially np ready
|
||||
* @returns zones configuration for the no-restricted-paths linter
|
||||
*/
|
||||
function buildRestrictedPaths(shimmedPlugins) {
|
||||
return shimmedPlugins
|
||||
.map(shimmedPlugin => [
|
||||
{
|
||||
target: [`src/legacy/core_plugins/kibana/public/${shimmedPlugin}/np_ready/**/*`],
|
||||
from: [
|
||||
'ui/**/*',
|
||||
'src/legacy/ui/**/*',
|
||||
'src/legacy/core_plugins/kibana/public/**/*',
|
||||
`!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/**/*`,
|
||||
],
|
||||
allowSameFolder: false,
|
||||
errorMessage: `${shimmedPlugin} is a shimmed plugin that is not allowed to import modules from the legacy platform. If you need legacy modules for the transition period, import them either in the legacy_imports, kibana_services or index module.`,
|
||||
},
|
||||
{
|
||||
target: [
|
||||
'src/**/*',
|
||||
`!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/**/*`,
|
||||
'x-pack/**/*',
|
||||
],
|
||||
from: [
|
||||
`src/legacy/core_plugins/kibana/public/${shimmedPlugin}/**/*`,
|
||||
`!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/index.ts`,
|
||||
`!src/legacy/core_plugins/kibana/public/${shimmedPlugin}/legacy.ts`,
|
||||
],
|
||||
allowSameFolder: false,
|
||||
errorMessage: `kibana/public/${shimmedPlugin} is behaving like a NP plugin and does not allow deep imports. If you need something from within ${shimmedPlugin} in another plugin, consider re-exporting it from the top level index module`,
|
||||
},
|
||||
])
|
||||
.reduce((acc, part) => [...acc, ...part], []);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
rules: {
|
||||
'no-console': 2,
|
||||
'import/no-default-export': 'error',
|
||||
'@kbn/eslint/no-restricted-paths': [
|
||||
'error',
|
||||
{
|
||||
basePath: path.resolve(__dirname, '../../../../../'),
|
||||
zones: topLevelRestricedZones.concat(
|
||||
buildRestrictedPaths(['visualize', 'discover', 'dashboard', 'devTools'])
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
|
@ -38,17 +38,22 @@ import 'uiExports/shareContextMenuExtensions';
|
|||
import 'uiExports/interpreter';
|
||||
|
||||
import 'ui/autoload/all';
|
||||
import './management';
|
||||
|
||||
import { localApplicationService } from './local_application_service';
|
||||
|
||||
npSetup.plugins.kibanaLegacy.registerLegacyAppAlias('doc', 'discover', { keepPrefix: true });
|
||||
npSetup.plugins.kibanaLegacy.registerLegacyAppAlias('context', 'discover', { keepPrefix: true });
|
||||
|
||||
npSetup.plugins.kibanaLegacy.forwardApp('management', 'management', (path) => {
|
||||
return path.replace('/management', '');
|
||||
});
|
||||
|
||||
localApplicationService.attachToAngular(routes);
|
||||
|
||||
routes.enable();
|
||||
|
||||
const { config } = npSetup.plugins.kibanaLegacy;
|
||||
|
||||
routes.otherwise({
|
||||
redirectTo: `/${config.defaultAppId || 'discover'}`,
|
||||
});
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
// SASSTODO: figure out why this is needed
|
||||
kbn-management-app,
|
||||
kbn-management-landing,
|
||||
kbn-management-indices,
|
||||
kbn-management-indices-edit,
|
||||
kbn-management-indices-create,
|
||||
kbn-management-advanced,
|
||||
kbn-management-objects,
|
||||
kbn-management-objects-view {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#management-landing {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.kbn-management-tab:first-letter {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
// SASSTODO: Remove when this is replaced with EuiCode
|
||||
kbn-management-objects-view {
|
||||
.ace_editor { height: 300px; }
|
||||
}
|
||||
|
||||
// Hack because the management wrapper is flat HTML and needs a class
|
||||
.mgtPage__body {
|
||||
max-width: map-get($euiBreakpoints, 'xl');
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
.mgtPanel {
|
||||
margin-bottom: $euiSize;
|
||||
background: $euiColorEmptyShade;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Override kuiPanelBody styles to accommodate padding of items within the panel body..
|
||||
*/
|
||||
.mgtPanel__body {
|
||||
padding: 5px 10px; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Create vertical space between items when they wrap.
|
||||
*/
|
||||
.mgtPanel__item {
|
||||
padding: 5px 15px; /* 1 */
|
||||
}
|
||||
|
||||
// SASSTODO: Remove when this is replaced by the side nav
|
||||
.mgtPanel__link {
|
||||
@include euiFontSizeL;
|
||||
|
||||
line-height: 1.5; // Make sure the space between wrapped lines is than the vertical space between items.
|
||||
|
||||
&.mgtPanel__link--disabled {
|
||||
opacity: $euiColorDarkShade;
|
||||
cursor: default;
|
||||
|
||||
&:hover, &:visited {
|
||||
color: $euiColorPrimary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SASSTODO: Remove when this form is replaced by EUI
|
||||
kbn-management-objects {
|
||||
form {
|
||||
margin-bottom: $euiSize;
|
||||
}
|
||||
.list-unstyled {
|
||||
li {
|
||||
border-bottom: $euiBorderThin;
|
||||
padding: $euiSizeS;
|
||||
}
|
||||
}
|
||||
.empty {
|
||||
color: $euiColorDarkShade;
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: $euiSizeM;
|
||||
|
||||
.item-title {
|
||||
margin-left: $euiSizeL;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: $euiSizeXS;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
.title, .controls {
|
||||
padding-right: 1em;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
<div class="euiPage mgtPage">
|
||||
<div id="management-sidenav" class="euiPageSideBar" style="position: static;" data-test-subj="managementNav"></div>
|
||||
<main class="euiPageBody euiPageBody--restrictWidth-default mgtPage__body" ng-transclude></main>
|
||||
</div>
|
|
@ -1,166 +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 React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { I18nContext } from 'ui/i18n';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import appTemplate from './app.html';
|
||||
import landingTemplate from './landing.html';
|
||||
import { management, MANAGEMENT_BREADCRUMB } from 'ui/management';
|
||||
import { ManagementSidebarNav } from '../../../../../plugins/management/public';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
import {
|
||||
EuiPageContent,
|
||||
EuiTitle,
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
EuiIcon,
|
||||
EuiHorizontalRule,
|
||||
} from '@elastic/eui';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
|
||||
const SIDENAV_ID = 'management-sidenav';
|
||||
const LANDING_ID = 'management-landing';
|
||||
|
||||
uiRoutes.when('/management', {
|
||||
template: landingTemplate,
|
||||
k7Breadcrumbs: () => [MANAGEMENT_BREADCRUMB],
|
||||
});
|
||||
|
||||
uiRoutes.when('/management/:section', {
|
||||
redirectTo: '/management',
|
||||
});
|
||||
|
||||
export function updateLandingPage(version) {
|
||||
const node = document.getElementById(LANDING_ID);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
render(
|
||||
<EuiPageContent horizontalPosition="center" data-test-subj="managementHome">
|
||||
<I18nContext>
|
||||
<div>
|
||||
<div className="eui-textCenter">
|
||||
<EuiIcon type="managementApp" size="xxl" />
|
||||
<EuiSpacer />
|
||||
<EuiTitle>
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="kbn.management.landing.header"
|
||||
defaultMessage="Welcome to Stack Management {version}"
|
||||
values={{ version }}
|
||||
/>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="kbn.management.landing.subhead"
|
||||
defaultMessage="Manage your indices, index patterns, saved objects, Kibana settings, and more."
|
||||
/>
|
||||
</EuiText>
|
||||
</div>
|
||||
|
||||
<EuiHorizontalRule />
|
||||
|
||||
<EuiText color="subdued" size="s" textAlign="center">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="kbn.management.landing.text"
|
||||
defaultMessage="A complete list of apps is in the menu on the left."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</div>
|
||||
</I18nContext>
|
||||
</EuiPageContent>,
|
||||
node
|
||||
);
|
||||
}
|
||||
|
||||
export function updateSidebar(legacySections, id) {
|
||||
const node = document.getElementById(SIDENAV_ID);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
render(
|
||||
<I18nContext>
|
||||
<ManagementSidebarNav
|
||||
getSections={npStart.plugins.management.sections.getSectionsEnabled}
|
||||
legacySections={legacySections}
|
||||
selectedId={id}
|
||||
className="mgtSideNav"
|
||||
/>
|
||||
</I18nContext>,
|
||||
node
|
||||
);
|
||||
}
|
||||
|
||||
export const destroyReact = (id) => {
|
||||
const node = document.getElementById(id);
|
||||
node && unmountComponentAtNode(node);
|
||||
};
|
||||
|
||||
uiModules.get('apps/management').directive('kbnManagementApp', function ($location) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: appTemplate,
|
||||
transclude: true,
|
||||
scope: {
|
||||
sectionName: '@section',
|
||||
omitPages: '@omitBreadcrumbPages',
|
||||
pageTitle: '=',
|
||||
},
|
||||
|
||||
link: function ($scope) {
|
||||
timefilter.disableAutoRefreshSelector();
|
||||
timefilter.disableTimeRangeSelector();
|
||||
$scope.sections = management.visibleItems;
|
||||
$scope.section = management.getSection($scope.sectionName) || management;
|
||||
|
||||
if ($scope.section) {
|
||||
$scope.section.items.forEach((item) => {
|
||||
item.active = `#${$location.path()}`.indexOf(item.url) > -1;
|
||||
});
|
||||
}
|
||||
|
||||
updateSidebar($scope.sections, $scope.section.id);
|
||||
$scope.$on('$destroy', () => destroyReact(SIDENAV_ID));
|
||||
management.addListener(() => updateSidebar(management.visibleItems, $scope.section.id));
|
||||
|
||||
updateLandingPage($scope.$root.chrome.getKibanaVersion());
|
||||
$scope.$on('$destroy', () => destroyReact(LANDING_ID));
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
uiModules.get('apps/management').directive('kbnManagementLanding', function (kbnVersion) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function ($scope) {
|
||||
$scope.sections = management.visibleItems;
|
||||
$scope.kbnVersion = kbnVersion;
|
||||
},
|
||||
};
|
||||
});
|
|
@ -7,9 +7,7 @@
|
|||
// mgtChart__legend--small
|
||||
// mgtChart__legend-isLoading
|
||||
|
||||
@import 'hacks';
|
||||
|
||||
// Core
|
||||
@import 'management_app';
|
||||
@import '../../../../../plugins/advanced_settings/public/index';
|
||||
|
||||
@import 'sections/index_patterns/index';
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
<kbn-management-app>
|
||||
<div id="management-landing" data-test-subj="management-landing"></div>
|
||||
</kbn-management-app>
|
|
@ -69,7 +69,7 @@ const savedObjectsManagement = getManagementaMock({
|
|||
},
|
||||
getInAppUrl(obj) {
|
||||
return {
|
||||
path: `/app/kibana#/management/kibana/indexPatterns/patterns/${encodeURIComponent(obj.id)}`,
|
||||
path: `/app/management/kibana/indexPatterns/patterns/${encodeURIComponent(obj.id)}`,
|
||||
uiCapabilitiesPath: 'management.kibana.index_patterns',
|
||||
};
|
||||
},
|
||||
|
@ -325,7 +325,7 @@ describe('findRelationships', () => {
|
|||
title: 'My Index Pattern',
|
||||
editUrl: '/management/kibana/indexPatterns/patterns/1',
|
||||
inAppUrl: {
|
||||
path: '/app/kibana#/management/kibana/indexPatterns/patterns/1',
|
||||
path: '/app/management/kibana/indexPatterns/patterns/1',
|
||||
uiCapabilitiesPath: 'management.kibana.index_patterns',
|
||||
},
|
||||
},
|
||||
|
@ -439,7 +439,7 @@ describe('findRelationships', () => {
|
|||
title: 'My Index Pattern',
|
||||
editUrl: '/management/kibana/indexPatterns/patterns/1',
|
||||
inAppUrl: {
|
||||
path: '/app/kibana#/management/kibana/indexPatterns/patterns/1',
|
||||
path: '/app/management/kibana/indexPatterns/patterns/1',
|
||||
uiCapabilitiesPath: 'management.kibana.index_patterns',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { HashRouter, Switch, Route } from 'react-router-dom';
|
||||
import { Router, Switch, Route } from 'react-router-dom';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
|
@ -60,7 +60,7 @@ export async function mountManagementSection(
|
|||
|
||||
ReactDOM.render(
|
||||
<I18nProvider>
|
||||
<HashRouter basename={params.basePath}>
|
||||
<Router history={params.history}>
|
||||
<Switch>
|
||||
<Route path={['/:query', '/']}>
|
||||
<AdvancedSettings
|
||||
|
@ -72,7 +72,7 @@ export async function mountManagementSection(
|
|||
/>
|
||||
</Route>
|
||||
</Switch>
|
||||
</HashRouter>
|
||||
</Router>
|
||||
</I18nProvider>,
|
||||
params.element
|
||||
);
|
||||
|
|
|
@ -91,9 +91,9 @@ export const createEnsureDefaultIndexPattern = (core: CoreStart) => {
|
|||
if (redirectTarget === '/home') {
|
||||
core.application.navigateToApp('home');
|
||||
} else {
|
||||
window.location.href = core.http.basePath.prepend(
|
||||
`/app/kibana#/management/kibana/indexPatterns?bannerMessage=${bannerMessage}`
|
||||
);
|
||||
core.application.navigateToApp('management', {
|
||||
path: `/kibana/indexPatterns?bannerMessage=${bannerMessage}`,
|
||||
});
|
||||
}
|
||||
|
||||
// return never-resolving promise to stop resolving and wait for the url change
|
||||
|
|
|
@ -44,7 +44,7 @@ export function LongQueryNotification(props: Props) {
|
|||
<EuiButton
|
||||
size="s"
|
||||
onClick={async () => {
|
||||
await props.application.navigateToApp('kibana#/management/stack/license_management');
|
||||
await props.application.navigateToApp('management/stack/license_management');
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -36,7 +36,7 @@ export const indexPatternSavedObjectType: SavedObjectsType = {
|
|||
},
|
||||
getInAppUrl(obj) {
|
||||
return {
|
||||
path: `/app/kibana#/management/kibana/indexPatterns/patterns/${encodeURIComponent(obj.id)}`,
|
||||
path: `/app/management/kibana/indexPatterns/patterns/${encodeURIComponent(obj.id)}`,
|
||||
uiCapabilitiesPath: 'management.kibana.index_patterns',
|
||||
};
|
||||
},
|
||||
|
|
|
@ -162,8 +162,8 @@ app.config(($routeProvider) => {
|
|||
mapping: {
|
||||
search: '/',
|
||||
'index-pattern': {
|
||||
app: 'kibana',
|
||||
path: `#/management/kibana/objects/savedSearches/${$route.current.params.id}`,
|
||||
app: 'management',
|
||||
path: `kibana/objects/savedSearches/${$route.current.params.id}`,
|
||||
},
|
||||
},
|
||||
toastNotifications,
|
||||
|
|
|
@ -53,7 +53,7 @@ exports[`render 1`] = `
|
|||
>
|
||||
<EuiButton
|
||||
fill={true}
|
||||
href="#/management/kibana/objects?_a=(tab:search)"
|
||||
href="/app/management/kibana/objects?_a=(tab:search)"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -40,6 +40,7 @@ const SEARCH_OBJECT_TYPE = 'search';
|
|||
export function OpenSearchPanel(props) {
|
||||
const {
|
||||
core: { uiSettings, savedObjects },
|
||||
addBasePath,
|
||||
} = getServices();
|
||||
|
||||
return (
|
||||
|
@ -86,7 +87,9 @@ export function OpenSearchPanel(props) {
|
|||
<EuiButton
|
||||
fill
|
||||
onClick={props.onClose}
|
||||
href={`#/management/kibana/objects?_a=${rison.encode({ tab: SEARCH_OBJECT_TYPE })}`}
|
||||
href={addBasePath(
|
||||
`/app/management/kibana/objects?_a=${rison.encode({ tab: SEARCH_OBJECT_TYPE })}`
|
||||
)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="discover.topNav.openSearchPanel.manageSearchesButtonLabel"
|
||||
|
|
|
@ -24,6 +24,7 @@ jest.mock('../../../kibana_services', () => {
|
|||
return {
|
||||
getServices: () => ({
|
||||
core: { uiSettings: {}, savedObjects: {} },
|
||||
addBasePath: (path) => path,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
|
|
@ -277,7 +277,7 @@ exports[`apmUiEnabled 1`] = `
|
|||
/>
|
||||
</strong>
|
||||
<EuiLink
|
||||
href="#/management/kibana/indexPatterns"
|
||||
href="path/app/management/kibana/indexPatterns"
|
||||
style={
|
||||
Object {
|
||||
"display": "block",
|
||||
|
@ -543,7 +543,7 @@ exports[`isNewKibanaInstance 1`] = `
|
|||
/>
|
||||
</strong>
|
||||
<EuiLink
|
||||
href="#/management/kibana/indexPatterns"
|
||||
href="path/app/management/kibana/indexPatterns"
|
||||
style={
|
||||
Object {
|
||||
"display": "block",
|
||||
|
@ -876,7 +876,7 @@ exports[`mlEnabled 1`] = `
|
|||
/>
|
||||
</strong>
|
||||
<EuiLink
|
||||
href="#/management/kibana/indexPatterns"
|
||||
href="path/app/management/kibana/indexPatterns"
|
||||
style={
|
||||
Object {
|
||||
"display": "block",
|
||||
|
@ -1142,7 +1142,7 @@ exports[`render 1`] = `
|
|||
/>
|
||||
</strong>
|
||||
<EuiLink
|
||||
href="#/management/kibana/indexPatterns"
|
||||
href="path/app/management/kibana/indexPatterns"
|
||||
style={
|
||||
Object {
|
||||
"display": "block",
|
||||
|
|
|
@ -141,7 +141,7 @@ exports[`should render a Welcome screen with the telemetry disclaimer 1`] = `
|
|||
values={Object {}}
|
||||
/>
|
||||
<EuiLink
|
||||
href="#/management/kibana/settings"
|
||||
href="rootapp/management/kibana/settings"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="disable usage data here."
|
||||
|
@ -240,7 +240,7 @@ exports[`should render a Welcome screen with the telemetry disclaimer when optIn
|
|||
values={Object {}}
|
||||
/>
|
||||
<EuiLink
|
||||
href="#/management/kibana/settings"
|
||||
href="rootapp/management/kibana/settings"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="enable usage data here."
|
||||
|
@ -339,7 +339,7 @@ exports[`should render a Welcome screen with the telemetry disclaimer when optIn
|
|||
values={Object {}}
|
||||
/>
|
||||
<EuiLink
|
||||
href="#/management/kibana/settings"
|
||||
href="rootapp/management/kibana/settings"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="disable usage data here."
|
||||
|
|
|
@ -40,6 +40,7 @@ import {
|
|||
|
||||
const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => {
|
||||
const basePath = getServices().getBasePath();
|
||||
|
||||
const renderCards = () => {
|
||||
const apmData = {
|
||||
title: intl.formatMessage({
|
||||
|
@ -296,7 +297,7 @@ const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => {
|
|||
</strong>
|
||||
<EuiLink
|
||||
style={{ display: 'block', textAlign: 'center' }}
|
||||
href="#/management/kibana/indexPatterns"
|
||||
href={`${basePath}/app/management/kibana/indexPatterns`}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="home.addData.yourDataLink"
|
||||
|
|
|
@ -101,7 +101,7 @@ export class Welcome extends React.Component<Props> {
|
|||
id="home.dataManagementDisableCollection"
|
||||
defaultMessage=" To stop collection, "
|
||||
/>
|
||||
<EuiLink href="#/management/kibana/settings">
|
||||
<EuiLink href={this.services.addBasePath('app/management/kibana/settings')}>
|
||||
<FormattedMessage
|
||||
id="home.dataManagementDisableCollectionLink"
|
||||
defaultMessage="disable usage data here."
|
||||
|
@ -116,7 +116,7 @@ export class Welcome extends React.Component<Props> {
|
|||
id="home.dataManagementEnableCollection"
|
||||
defaultMessage=" To start collection, "
|
||||
/>
|
||||
<EuiLink href="#/management/kibana/settings">
|
||||
<EuiLink href={this.services.addBasePath('app/management/kibana/settings')}>
|
||||
<FormattedMessage
|
||||
id="home.dataManagementEnableCollectionLink"
|
||||
defaultMessage="enable usage data here."
|
||||
|
|
|
@ -26,7 +26,7 @@ export function getListBreadcrumbs() {
|
|||
text: i18n.translate('indexPatternManagement.indexPatterns.listBreadcrumb', {
|
||||
defaultMessage: 'Index patterns',
|
||||
}),
|
||||
href: `#/management/kibana/indexPatterns/`,
|
||||
href: `/`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ export function getCreateBreadcrumbs() {
|
|||
text: i18n.translate('indexPatternManagement.indexPatterns.createBreadcrumb', {
|
||||
defaultMessage: 'Create index pattern',
|
||||
}),
|
||||
href: `#/management/kibana/indexPatterns/create`,
|
||||
href: `/create`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ export function getEditBreadcrumbs(indexPattern: IndexPattern) {
|
|||
...getListBreadcrumbs(),
|
||||
{
|
||||
text: indexPattern.title,
|
||||
href: `#/management/kibana/indexPatterns/patterns/${indexPattern.id}`,
|
||||
href: `/patterns/${indexPattern.id}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
@ -79,7 +79,9 @@ export const CreateEditField = withRouter(
|
|||
chrome.docTitle.change([docFieldName, indexPattern.title]);
|
||||
|
||||
const redirectAway = () => {
|
||||
history.push(`${url}?_a=(tab:${field?.scripted ? TAB_SCRIPTED_FIELDS : TAB_INDEXED_FIELDS})`);
|
||||
history.push(
|
||||
`${url}#/?_a=(tab:${field?.scripted ? TAB_SCRIPTED_FIELDS : TAB_INDEXED_FIELDS})`
|
||||
);
|
||||
};
|
||||
|
||||
if (field) {
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
import React from 'react';
|
||||
import { render } from 'enzyme';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { ScopedHistory } from 'kibana/public';
|
||||
import { scopedHistoryMock } from '../../../../../../../../core/public/mocks';
|
||||
|
||||
import { Header } from './header';
|
||||
|
||||
|
@ -28,7 +30,7 @@ describe('Header', () => {
|
|||
const component = render(
|
||||
<Header.WrappedComponent
|
||||
indexPatternId="test"
|
||||
history={({} as unknown) as RouteComponentProps['history']}
|
||||
history={(scopedHistoryMock.create() as unknown) as ScopedHistory}
|
||||
location={({} as unknown) as RouteComponentProps['location']}
|
||||
match={({} as unknown) as RouteComponentProps['match']}
|
||||
/>
|
||||
|
|
|
@ -22,9 +22,13 @@ import { withRouter, RouteComponentProps } from 'react-router-dom';
|
|||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ScopedHistory } from 'kibana/public';
|
||||
|
||||
import { reactRouterNavigate } from '../../../../../../../kibana_react/public';
|
||||
|
||||
interface HeaderProps extends RouteComponentProps {
|
||||
indexPatternId: string;
|
||||
history: ScopedHistory;
|
||||
}
|
||||
|
||||
export const Header = withRouter(({ indexPatternId, history }: HeaderProps) => (
|
||||
|
@ -52,9 +56,7 @@ export const Header = withRouter(({ indexPatternId, history }: HeaderProps) => (
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="addScriptedFieldLink"
|
||||
onClick={() => {
|
||||
history.push(`${indexPatternId}/create-field/`);
|
||||
}}
|
||||
{...reactRouterNavigate(history, `patterns/${indexPatternId}/create-field/`)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="indexPatternManagement.editIndexPattern.scripted.addFieldButton"
|
||||
|
|
|
@ -117,7 +117,7 @@ export function getTabs(
|
|||
}
|
||||
|
||||
export function getPath(field: IndexPatternField) {
|
||||
return `${field.indexPattern?.id}/field/${field.name}`;
|
||||
return `/patterns/${field.indexPattern?.id}/field/${field.name}`;
|
||||
}
|
||||
|
||||
const allTypesDropDown = i18n.translate(
|
||||
|
|
|
@ -27,12 +27,13 @@ import {
|
|||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiBadgeGroup,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibana } from '../../../../../plugins/kibana_react/public';
|
||||
import { reactRouterNavigate, useKibana } from '../../../../../plugins/kibana_react/public';
|
||||
import { IndexPatternManagmentContext } from '../../types';
|
||||
import { CreateButton } from '../create_button';
|
||||
import { CreateIndexPatternPrompt } from '../create_index_pattern_prompt';
|
||||
|
@ -40,35 +41,6 @@ import { IndexPatternTableItem, IndexPatternCreationOption } from '../types';
|
|||
import { getIndexPatterns } from '../utils';
|
||||
import { getListBreadcrumbs } from '../breadcrumbs';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
field: 'title',
|
||||
name: 'Pattern',
|
||||
render: (
|
||||
name: string,
|
||||
index: {
|
||||
id: string;
|
||||
tags?: Array<{
|
||||
key: string;
|
||||
name: string;
|
||||
}>;
|
||||
}
|
||||
) => (
|
||||
<EuiButtonEmpty size="xs" href={`#/management/kibana/indexPatterns/patterns/${index.id}`}>
|
||||
{name}
|
||||
{index.tags &&
|
||||
index.tags.map(({ key: tagKey, name: tagName }) => (
|
||||
<EuiBadge className="indexPatternList__badge" key={tagKey}>
|
||||
{tagName}
|
||||
</EuiBadge>
|
||||
))}
|
||||
</EuiButtonEmpty>
|
||||
),
|
||||
dataType: 'string' as const,
|
||||
sortable: ({ sort }: { sort: string }) => sort,
|
||||
},
|
||||
];
|
||||
|
||||
const pagination = {
|
||||
initialPageSize: 10,
|
||||
pageSizeOptions: [5, 10, 25, 50],
|
||||
|
@ -140,6 +112,39 @@ export const IndexPatternTable = ({ canSave, history }: Props) => {
|
|||
|
||||
chrome.docTitle.change(title);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
field: 'title',
|
||||
name: 'Pattern',
|
||||
render: (
|
||||
name: string,
|
||||
index: {
|
||||
id: string;
|
||||
tags?: Array<{
|
||||
key: string;
|
||||
name: string;
|
||||
}>;
|
||||
}
|
||||
) => (
|
||||
<>
|
||||
<EuiButtonEmpty size="xs" {...reactRouterNavigate(history, `patterns/${index.id}`)}>
|
||||
{name}
|
||||
</EuiButtonEmpty>
|
||||
<EuiBadgeGroup gutterSize="s">
|
||||
{index.tags &&
|
||||
index.tags.map(({ key: tagKey, name: tagName }) => (
|
||||
<EuiBadge className="indexPatternList__badge" key={tagKey}>
|
||||
{tagName}
|
||||
</EuiBadge>
|
||||
))}
|
||||
</EuiBadgeGroup>
|
||||
</>
|
||||
),
|
||||
dataType: 'string' as const,
|
||||
sortable: ({ sort }: { sort: string }) => sort,
|
||||
},
|
||||
];
|
||||
|
||||
const createButton = canSave ? (
|
||||
<CreateButton options={creationOptions}>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { HashRouter, Switch, Route } from 'react-router-dom';
|
||||
import { Router, Switch, Route } from 'react-router-dom';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
|
@ -78,7 +78,7 @@ export async function mountManagementSection(
|
|||
ReactDOM.render(
|
||||
<KibanaContextProvider services={deps}>
|
||||
<I18nProvider>
|
||||
<HashRouter basename={params.basePath}>
|
||||
<Router history={params.history}>
|
||||
<Switch>
|
||||
<Route path={['/create']}>
|
||||
<CreateIndexPatternWizardWithRouter />
|
||||
|
@ -93,7 +93,7 @@ export async function mountManagementSection(
|
|||
<IndexPatternTableWithRouter canSave={canSave} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</HashRouter>
|
||||
</Router>
|
||||
</I18nProvider>
|
||||
</KibanaContextProvider>,
|
||||
params.element
|
||||
|
|
|
@ -71,7 +71,7 @@ export class IndexPatternManagementPlugin
|
|||
throw new Error('`kibana` management section not found.');
|
||||
}
|
||||
|
||||
const newAppPath = `kibana#/management/kibana/${IPM_APP_ID}`;
|
||||
const newAppPath = `management/kibana/${IPM_APP_ID}`;
|
||||
const legacyPatternsPath = 'management/kibana/index_patterns';
|
||||
|
||||
kibanaLegacy.forwardApp('management/kibana/index_pattern', newAppPath, (path) => '/create');
|
||||
|
|
|
@ -426,7 +426,11 @@ const $setupUrlOverflowHandling = (newPlatform: CoreStart, isLocalAngular: boole
|
|||
values={{
|
||||
storeInSessionStorageParam: <code>state:storeInSessionStorage</code>,
|
||||
advancedSettingsLink: (
|
||||
<a href="#/management/kibana/settings">
|
||||
<a
|
||||
href={newPlatform.application.getUrlForApp('management', {
|
||||
path: 'kibana/settings',
|
||||
})}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kibana_legacy.bigUrlWarningNotificationMessage.advancedSettingsLinkText"
|
||||
defaultMessage="advanced settings"
|
||||
|
|
|
@ -25,6 +25,7 @@ export * from './ui_settings';
|
|||
export * from './field_icon';
|
||||
export * from './table_list_view';
|
||||
export * from './split_panel';
|
||||
export * from './react_router_navigate';
|
||||
export { ValidatedDualRange, Value } from './validated_range';
|
||||
export * from './notifications';
|
||||
export { Markdown, MarkdownSimple } from './markdown';
|
||||
|
|
|
@ -17,5 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { LegacyManagementAdapter } from './sections_register';
|
||||
export { LegacyManagementSection } from './section';
|
||||
export { reactRouterNavigate, reactRouterOnClickHandler } from './react_router_navigate';
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 { ScopedHistory } from 'kibana/public';
|
||||
import { History } from 'history';
|
||||
|
||||
interface LocationObject {
|
||||
pathname?: string;
|
||||
search?: string;
|
||||
hash?: string;
|
||||
}
|
||||
|
||||
const isModifiedEvent = (event: any) =>
|
||||
!!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
|
||||
|
||||
const isLeftClickEvent = (event: any) => event.button === 0;
|
||||
|
||||
export const toLocationObject = (to: string | LocationObject) =>
|
||||
typeof to === 'string' ? { pathname: to } : to;
|
||||
|
||||
export const reactRouterNavigate = (
|
||||
history: ScopedHistory | History,
|
||||
to: string | LocationObject,
|
||||
onClickCallback?: Function
|
||||
) => ({
|
||||
href: history.createHref(toLocationObject(to)),
|
||||
onClick: reactRouterOnClickHandler(history, toLocationObject(to), onClickCallback),
|
||||
});
|
||||
|
||||
export const reactRouterOnClickHandler = (
|
||||
history: ScopedHistory | History,
|
||||
to: string | LocationObject,
|
||||
onClickCallback?: Function
|
||||
) => (event: any) => {
|
||||
if (onClickCallback) {
|
||||
onClickCallback(event);
|
||||
}
|
||||
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target.getAttribute('target')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isModifiedEvent(event) || !isLeftClickEvent(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// prevents page reload
|
||||
event.preventDefault();
|
||||
history.push(toLocationObject(to));
|
||||
};
|
|
@ -35,7 +35,7 @@ import { ScopedHistory } from '../../../../../core/public';
|
|||
|
||||
describe('kbn_url_storage', () => {
|
||||
describe('getStateFromUrl & setStateToUrl', () => {
|
||||
const url = 'http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id';
|
||||
const url = 'http://localhost:5601/oxf/app/kibana#/yourApp';
|
||||
const state1 = {
|
||||
testStr: '123',
|
||||
testNumber: 0,
|
||||
|
@ -50,14 +50,14 @@ describe('kbn_url_storage', () => {
|
|||
it('should set expanded state to url', () => {
|
||||
let newUrl = setStateToKbnUrl('_s', state1, { useHash: false }, url);
|
||||
expect(newUrl).toMatchInlineSnapshot(
|
||||
`"http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_s=(testArray:!(1,2,()),testNull:!n,testNumber:0,testObj:(test:'123'),testStr:'123')"`
|
||||
`"http://localhost:5601/oxf/app/kibana#/yourApp?_s=(testArray:!(1,2,()),testNull:!n,testNumber:0,testObj:(test:'123'),testStr:'123')"`
|
||||
);
|
||||
const retrievedState1 = getStateFromKbnUrl('_s', newUrl);
|
||||
expect(retrievedState1).toEqual(state1);
|
||||
|
||||
newUrl = setStateToKbnUrl('_s', state2, { useHash: false }, newUrl);
|
||||
expect(newUrl).toMatchInlineSnapshot(
|
||||
`"http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_s=(test:'123')"`
|
||||
`"http://localhost:5601/oxf/app/kibana#/yourApp?_s=(test:'123')"`
|
||||
);
|
||||
const retrievedState2 = getStateFromKbnUrl('_s', newUrl);
|
||||
expect(retrievedState2).toEqual(state2);
|
||||
|
@ -66,14 +66,14 @@ describe('kbn_url_storage', () => {
|
|||
it('should set hashed state to url', () => {
|
||||
let newUrl = setStateToKbnUrl('_s', state1, { useHash: true }, url);
|
||||
expect(newUrl).toMatchInlineSnapshot(
|
||||
`"http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_s=h@a897fac"`
|
||||
`"http://localhost:5601/oxf/app/kibana#/yourApp?_s=h@a897fac"`
|
||||
);
|
||||
const retrievedState1 = getStateFromKbnUrl('_s', newUrl);
|
||||
expect(retrievedState1).toEqual(state1);
|
||||
|
||||
newUrl = setStateToKbnUrl('_s', state2, { useHash: true }, newUrl);
|
||||
expect(newUrl).toMatchInlineSnapshot(
|
||||
`"http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_s=h@40f94d5"`
|
||||
`"http://localhost:5601/oxf/app/kibana#/yourApp?_s=h@40f94d5"`
|
||||
);
|
||||
const retrievedState2 = getStateFromKbnUrl('_s', newUrl);
|
||||
expect(retrievedState2).toEqual(state2);
|
||||
|
@ -244,67 +244,55 @@ describe('kbn_url_storage', () => {
|
|||
it('should extract path relative to browser history without basename', () => {
|
||||
const history = createBrowserHistory();
|
||||
const url =
|
||||
"http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')";
|
||||
"http://localhost:5601/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')";
|
||||
const relativePath = getRelativeToHistoryPath(url, history);
|
||||
expect(relativePath).toEqual(
|
||||
"/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"
|
||||
"/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"
|
||||
);
|
||||
});
|
||||
|
||||
it('should extract path relative to browser history with basename', () => {
|
||||
const url =
|
||||
"http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')";
|
||||
"http://localhost:5601/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')";
|
||||
const history1 = createBrowserHistory({ basename: '/oxf/app/' });
|
||||
const relativePath1 = getRelativeToHistoryPath(url, history1);
|
||||
expect(relativePath1).toEqual(
|
||||
"/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"
|
||||
"/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"
|
||||
);
|
||||
|
||||
const history2 = createBrowserHistory({ basename: '/oxf/app/kibana/' });
|
||||
const relativePath2 = getRelativeToHistoryPath(url, history2);
|
||||
expect(relativePath2).toEqual(
|
||||
"#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"
|
||||
);
|
||||
expect(relativePath2).toEqual("#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')");
|
||||
});
|
||||
|
||||
it('should extract path relative to browser history with basename from relative url', () => {
|
||||
const history = createBrowserHistory({ basename: '/oxf/app/' });
|
||||
const url =
|
||||
"/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')";
|
||||
const url = "/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')";
|
||||
const relativePath = getRelativeToHistoryPath(url, history);
|
||||
expect(relativePath).toEqual(
|
||||
"/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"
|
||||
);
|
||||
expect(relativePath).toEqual("/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')");
|
||||
});
|
||||
|
||||
it('should extract path relative to hash history without basename', () => {
|
||||
const history = createHashHistory();
|
||||
const url =
|
||||
"http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')";
|
||||
"http://localhost:5601/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')";
|
||||
const relativePath = getRelativeToHistoryPath(url, history);
|
||||
expect(relativePath).toEqual(
|
||||
"/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"
|
||||
);
|
||||
expect(relativePath).toEqual("/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')");
|
||||
});
|
||||
|
||||
it('should extract path relative to hash history with basename', () => {
|
||||
const history = createHashHistory({ basename: 'management' });
|
||||
const url =
|
||||
"http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')";
|
||||
"http://localhost:5601/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')";
|
||||
const relativePath = getRelativeToHistoryPath(url, history);
|
||||
expect(relativePath).toEqual(
|
||||
"/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"
|
||||
);
|
||||
expect(relativePath).toEqual("/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')");
|
||||
});
|
||||
|
||||
it('should extract path relative to hash history with basename from relative url', () => {
|
||||
const history = createHashHistory({ basename: 'management' });
|
||||
const url =
|
||||
"/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')";
|
||||
const url = "/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')";
|
||||
const relativePath = getRelativeToHistoryPath(url, history);
|
||||
expect(relativePath).toEqual(
|
||||
"/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"
|
||||
);
|
||||
expect(relativePath).toEqual("/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,7 +31,7 @@ import { url as urlUtils } from '../../../common';
|
|||
* e.g.:
|
||||
*
|
||||
* given an url:
|
||||
* http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')
|
||||
* http://localhost:5601/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')
|
||||
* will return object:
|
||||
* {_a: {tab: 'indexedFields'}, _b: {f: 'test', i: '', l: ''}};
|
||||
*/
|
||||
|
@ -57,7 +57,7 @@ export function getStatesFromKbnUrl(
|
|||
* e.g.:
|
||||
*
|
||||
* given an url:
|
||||
* http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')
|
||||
* http://localhost:5601/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')
|
||||
* and key '_a'
|
||||
* will return object:
|
||||
* {tab: 'indexedFields'}
|
||||
|
@ -74,12 +74,12 @@ export function getStateFromKbnUrl<State>(
|
|||
* Doesn't actually updates history
|
||||
*
|
||||
* e.g.:
|
||||
* given a url: http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')
|
||||
* given a url: http://localhost:5601/oxf/app/kibana#/yourApp?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')
|
||||
* key: '_a'
|
||||
* and state: {tab: 'other'}
|
||||
*
|
||||
* will return url:
|
||||
* http://localhost:5601/oxf/app/kibana#/management/kibana/indexPatterns/patterns/id?_a=(tab:other)&_b=(f:test,i:'',l:'')
|
||||
* http://localhost:5601/oxf/app/kibana#/yourApp?_a=(tab:other)&_b=(f:test,i:'',l:'')
|
||||
*/
|
||||
export function setStateToKbnUrl<State>(
|
||||
key: string,
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Management app can mount and unmount 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
Test App - Hello world!
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Management app can mount and unmount 2`] = `<div />`;
|
|
@ -17,11 +17,26 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
declare module 'ui/management' {
|
||||
export const SidebarNav: React.FC<any>;
|
||||
export const management: any; // TODO - properly provide types
|
||||
export const MANAGEMENT_BREADCRUMB: {
|
||||
text: string;
|
||||
href: string;
|
||||
};
|
||||
}
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { AppMountContext, AppMountParameters } from 'kibana/public';
|
||||
import { ManagementApp, ManagementAppDependencies } from './components/management_app';
|
||||
|
||||
export const renderApp = async (
|
||||
context: AppMountContext,
|
||||
{ history, appBasePath, element }: AppMountParameters,
|
||||
dependencies: ManagementAppDependencies
|
||||
) => {
|
||||
ReactDOM.render(
|
||||
<ManagementApp
|
||||
context={context}
|
||||
dependencies={dependencies}
|
||||
appBasePath={appBasePath}
|
||||
history={history}
|
||||
/>,
|
||||
element
|
||||
);
|
||||
|
||||
return () => ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
|
@ -1 +0,0 @@
|
|||
@import './management_sidebar_nav/index';
|
|
@ -17,5 +17,5 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { ManagementSidebarNav } from './management_sidebar_nav';
|
||||
export { ManagementChrome } from './management_chrome';
|
||||
export { ManagementApp } from './management_app';
|
||||
export { managementSections } from './management_sections';
|
||||
|
|
|
@ -17,4 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { ManagementChrome } from './management_chrome';
|
||||
export { ManagementLandingPage } from './landing';
|
76
src/plugins/management/public/components/landing/landing.tsx
Normal file
76
src/plugins/management/public/components/landing/landing.tsx
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import {
|
||||
EuiHorizontalRule,
|
||||
EuiIcon,
|
||||
EuiPageContent,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
interface ManagementLandingPageProps {
|
||||
version: string;
|
||||
setBreadcrumbs: () => void;
|
||||
}
|
||||
|
||||
export const ManagementLandingPage = ({ version, setBreadcrumbs }: ManagementLandingPageProps) => {
|
||||
setBreadcrumbs();
|
||||
|
||||
return (
|
||||
<EuiPageContent horizontalPosition="center" data-test-subj="managementHome">
|
||||
<div>
|
||||
<div className="eui-textCenter">
|
||||
<EuiIcon type="managementApp" size="xxl" />
|
||||
<EuiSpacer />
|
||||
<EuiTitle>
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="management.landing.header"
|
||||
defaultMessage="Welcome to Stack Management {version}"
|
||||
values={{ version }}
|
||||
/>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="management.landing.subhead"
|
||||
defaultMessage="Manage your indices, index patterns, saved objects, Kibana settings, and more."
|
||||
/>
|
||||
</EuiText>
|
||||
</div>
|
||||
|
||||
<EuiHorizontalRule />
|
||||
|
||||
<EuiText color="subdued" size="s" textAlign="center">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="management.landing.text"
|
||||
defaultMessage="A complete list of apps is in the menu on the left."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</div>
|
||||
</EuiPageContent>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { ManagementApp, ManagementAppDependencies } from './management_app';
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
// Hack because the management wrapper is flat HTML and needs a class
|
||||
.mgtPage__body {
|
||||
max-width: map-get($euiBreakpoints, 'xl');
|
||||
margin: 0 auto;
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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 React, { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
AppMountContext,
|
||||
AppMountParameters,
|
||||
ChromeBreadcrumb,
|
||||
ScopedHistory,
|
||||
} from 'kibana/public';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { EuiPage } from '@elastic/eui';
|
||||
import { ManagementStart } from '../../types';
|
||||
import { ManagementSection, MANAGEMENT_BREADCRUMB } from '../../utils';
|
||||
|
||||
import { ManagementRouter } from './management_router';
|
||||
import { ManagementSidebarNav } from '../management_sidebar_nav';
|
||||
import { reactRouterNavigate } from '../../../../kibana_react/public';
|
||||
|
||||
import './management_app.scss';
|
||||
|
||||
interface ManagementAppProps {
|
||||
appBasePath: string;
|
||||
context: AppMountContext;
|
||||
history: AppMountParameters['history'];
|
||||
dependencies: ManagementAppDependencies;
|
||||
}
|
||||
|
||||
export interface ManagementAppDependencies {
|
||||
management: ManagementStart;
|
||||
kibanaVersion: string;
|
||||
}
|
||||
|
||||
export const ManagementApp = ({ context, dependencies, history }: ManagementAppProps) => {
|
||||
const [selectedId, setSelectedId] = useState<string>('');
|
||||
const [sections, setSections] = useState<ManagementSection[]>();
|
||||
|
||||
const onAppMounted = useCallback((id: string) => {
|
||||
setSelectedId(id);
|
||||
window.scrollTo(0, 0);
|
||||
}, []);
|
||||
|
||||
const setBreadcrumbs = useCallback(
|
||||
(crumbs: ChromeBreadcrumb[] = [], appHistory?: ScopedHistory) => {
|
||||
const wrapBreadcrumb = (item: ChromeBreadcrumb, scopedHistory: ScopedHistory) => ({
|
||||
...item,
|
||||
...(item.href ? reactRouterNavigate(scopedHistory, item.href) : {}),
|
||||
});
|
||||
|
||||
context.core.chrome.setBreadcrumbs([
|
||||
wrapBreadcrumb(MANAGEMENT_BREADCRUMB, history),
|
||||
...crumbs.map((item) => wrapBreadcrumb(item, appHistory || history)),
|
||||
]);
|
||||
},
|
||||
[context.core.chrome, history]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setSections(dependencies.management.sections.getSectionsEnabled());
|
||||
}, [dependencies.management.sections]);
|
||||
|
||||
if (!sections) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<I18nProvider>
|
||||
<EuiPage>
|
||||
<ManagementSidebarNav selectedId={selectedId} sections={sections} history={history} />
|
||||
<ManagementRouter
|
||||
history={history}
|
||||
setBreadcrumbs={setBreadcrumbs}
|
||||
onAppMounted={onAppMounted}
|
||||
sections={sections}
|
||||
dependencies={dependencies}
|
||||
/>
|
||||
</EuiPage>
|
||||
</I18nProvider>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 React, { memo } from 'react';
|
||||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
import { EuiPageBody } from '@elastic/eui';
|
||||
import { AppMountParameters, ChromeBreadcrumb, ScopedHistory } from 'kibana/public';
|
||||
import { ManagementAppWrapper } from '../management_app_wrapper';
|
||||
import { ManagementLandingPage } from '../landing';
|
||||
import { ManagementAppDependencies } from './management_app';
|
||||
import { ManagementSection } from '../../utils';
|
||||
|
||||
interface ManagementRouterProps {
|
||||
history: AppMountParameters['history'];
|
||||
dependencies: ManagementAppDependencies;
|
||||
setBreadcrumbs: (crumbs?: ChromeBreadcrumb[], appHistory?: ScopedHistory) => void;
|
||||
onAppMounted: (id: string) => void;
|
||||
sections: ManagementSection[];
|
||||
}
|
||||
|
||||
export const ManagementRouter = memo(
|
||||
({ dependencies, history, setBreadcrumbs, onAppMounted, sections }: ManagementRouterProps) => (
|
||||
<Router history={history}>
|
||||
<EuiPageBody restrictWidth={false} className="mgtPage__body">
|
||||
<Switch>
|
||||
{sections.map((section) =>
|
||||
section
|
||||
.getAppsEnabled()
|
||||
.map((app) => (
|
||||
<Route
|
||||
path={`${app.basePath}`}
|
||||
component={() => (
|
||||
<ManagementAppWrapper
|
||||
app={app}
|
||||
setBreadcrumbs={setBreadcrumbs}
|
||||
onAppMounted={onAppMounted}
|
||||
history={history}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
<Route
|
||||
path={'/'}
|
||||
component={() => (
|
||||
<ManagementLandingPage
|
||||
version={dependencies.kibanaVersion}
|
||||
setBreadcrumbs={setBreadcrumbs}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
</EuiPageBody>
|
||||
</Router>
|
||||
)
|
||||
);
|
|
@ -17,8 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { npSetup } from 'ui/new_platform';
|
||||
|
||||
const registry = npSetup.plugins.savedObjectsManagement?.serviceRegistry;
|
||||
|
||||
export const savedObjectManagementRegistry = registry!;
|
||||
export { ManagementAppWrapper } from './management_app_wrapper';
|
|
@ -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 React, { createRef, Component } from 'react';
|
||||
|
||||
import { ChromeBreadcrumb, AppMountParameters, ScopedHistory } from 'kibana/public';
|
||||
import { ManagementApp } from '../../utils';
|
||||
import { Unmount } from '../../types';
|
||||
|
||||
interface ManagementSectionWrapperProps {
|
||||
app: ManagementApp;
|
||||
setBreadcrumbs: (crumbs?: ChromeBreadcrumb[], history?: ScopedHistory) => void;
|
||||
onAppMounted: (id: string) => void;
|
||||
history: AppMountParameters['history'];
|
||||
}
|
||||
|
||||
export class ManagementAppWrapper extends Component<ManagementSectionWrapperProps> {
|
||||
private unmount?: Unmount;
|
||||
private mountElementRef = createRef<HTMLElement>();
|
||||
|
||||
componentDidMount() {
|
||||
const { setBreadcrumbs, app, onAppMounted, history } = this.props;
|
||||
const { mount, basePath } = app;
|
||||
const appHistory = history.createSubHistory(app.basePath);
|
||||
|
||||
const mountResult = mount({
|
||||
basePath,
|
||||
setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => setBreadcrumbs(crumbs, appHistory),
|
||||
element: this.mountElementRef.current!,
|
||||
history: appHistory,
|
||||
});
|
||||
|
||||
onAppMounted(app.id);
|
||||
|
||||
if (mountResult instanceof Promise) {
|
||||
mountResult.then((um) => {
|
||||
this.unmount = um;
|
||||
});
|
||||
} else {
|
||||
this.unmount = mountResult;
|
||||
}
|
||||
}
|
||||
|
||||
async componentWillUnmount() {
|
||||
if (this.unmount) {
|
||||
await this.unmount();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <main ref={this.mountElementRef} />;
|
||||
}
|
||||
}
|
|
@ -1,59 +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 * as React from 'react';
|
||||
import { EuiPage, EuiPageBody, EuiPageSideBar } from '@elastic/eui';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { ManagementSidebarNav } from '../management_sidebar_nav';
|
||||
import { LegacySection } from '../../types';
|
||||
import { ManagementSection } from '../../management_section';
|
||||
|
||||
interface Props {
|
||||
getSections: () => ManagementSection[];
|
||||
legacySections: LegacySection[];
|
||||
selectedId: string;
|
||||
onMounted: (element: HTMLDivElement) => void;
|
||||
}
|
||||
|
||||
export class ManagementChrome extends React.Component<Props> {
|
||||
private container = React.createRef<HTMLDivElement>();
|
||||
componentDidMount() {
|
||||
if (this.container.current) {
|
||||
this.props.onMounted(this.container.current);
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<I18nProvider>
|
||||
<EuiPage>
|
||||
<EuiPageSideBar>
|
||||
<ManagementSidebarNav
|
||||
getSections={this.props.getSections}
|
||||
legacySections={this.props.legacySections}
|
||||
selectedId={this.props.selectedId}
|
||||
/>
|
||||
</EuiPageSideBar>
|
||||
<EuiPageBody restrictWidth={true} className="mgtPage__body">
|
||||
<div ref={this.container} />
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
</I18nProvider>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -20,14 +20,14 @@
|
|||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiToolTip, EuiIcon } from '@elastic/eui';
|
||||
|
||||
import { ManagementSectionId } from './types';
|
||||
import { ManagementSectionId } from '../types';
|
||||
|
||||
interface Props {
|
||||
interface ManagementSectionTitleProps {
|
||||
text: string;
|
||||
tip: string;
|
||||
}
|
||||
|
||||
const ManagementSectionTitle = ({ text, tip }: Props) => (
|
||||
const ManagementSectionTitle = ({ text, tip }: ManagementSectionTitleProps) => (
|
||||
<EuiToolTip content={tip} position="right">
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={false}>{text}</EuiFlexItem>
|
|
@ -1,95 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Management adds legacy apps to existing SidebarNav sections 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"data-test-subj": "activeSection",
|
||||
"icon": null,
|
||||
"id": "activeSection",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-test-subj": "item",
|
||||
"href": undefined,
|
||||
"id": "item",
|
||||
"isSelected": false,
|
||||
"name": "item",
|
||||
"order": undefined,
|
||||
},
|
||||
],
|
||||
"name": "activeSection",
|
||||
"order": 10,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "no-active-items",
|
||||
"icon": null,
|
||||
"id": "no-active-items",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-test-subj": "disabled",
|
||||
"href": undefined,
|
||||
"id": "disabled",
|
||||
"isSelected": false,
|
||||
"name": "disabled",
|
||||
"order": undefined,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "notVisible",
|
||||
"href": undefined,
|
||||
"id": "notVisible",
|
||||
"isSelected": false,
|
||||
"name": "notVisible",
|
||||
"order": undefined,
|
||||
},
|
||||
],
|
||||
"name": "No active items",
|
||||
"order": 10,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`Management maps legacy sections and apps into SidebarNav items 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"data-test-subj": "no-active-items",
|
||||
"icon": null,
|
||||
"id": "no-active-items",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-test-subj": "disabled",
|
||||
"href": undefined,
|
||||
"id": "disabled",
|
||||
"isSelected": false,
|
||||
"name": "disabled",
|
||||
"order": undefined,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "notVisible",
|
||||
"href": undefined,
|
||||
"id": "notVisible",
|
||||
"isSelected": false,
|
||||
"name": "notVisible",
|
||||
"order": undefined,
|
||||
},
|
||||
],
|
||||
"name": "No active items",
|
||||
"order": 10,
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "activeSection",
|
||||
"icon": null,
|
||||
"id": "activeSection",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-test-subj": "item",
|
||||
"href": undefined,
|
||||
"id": "item",
|
||||
"isSelected": false,
|
||||
"name": "item",
|
||||
"order": undefined,
|
||||
},
|
||||
],
|
||||
"name": "activeSection",
|
||||
"order": 10,
|
||||
},
|
||||
]
|
||||
`;
|
|
@ -1 +0,0 @@
|
|||
@import './sidebar_nav';
|
|
@ -1,5 +1,6 @@
|
|||
.mgtSideBarNav {
|
||||
width: 210px;
|
||||
margin-right: $euiSize;
|
||||
}
|
||||
|
||||
@include euiBreakpoint('xs','s') {
|
|
@ -1,98 +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 { IndexedArray } from '../../../../../legacy/ui/public/indexed_array';
|
||||
import { mergeLegacyItems } from './management_sidebar_nav';
|
||||
|
||||
const toIndexedArray = (initialSet: any[]) =>
|
||||
new IndexedArray({
|
||||
index: ['id'],
|
||||
order: ['order'],
|
||||
initialSet,
|
||||
});
|
||||
|
||||
const activeProps = { visible: true, disabled: false };
|
||||
const disabledProps = { visible: true, disabled: true };
|
||||
const notVisibleProps = { visible: false, disabled: false };
|
||||
const visibleItem = { display: 'item', id: 'item', ...activeProps };
|
||||
|
||||
const notVisibleSection = {
|
||||
display: 'Not visible',
|
||||
id: 'not-visible',
|
||||
order: 10,
|
||||
visibleItems: toIndexedArray([visibleItem]),
|
||||
...notVisibleProps,
|
||||
};
|
||||
const disabledSection = {
|
||||
display: 'Disabled',
|
||||
id: 'disabled',
|
||||
order: 10,
|
||||
visibleItems: toIndexedArray([visibleItem]),
|
||||
...disabledProps,
|
||||
};
|
||||
const noItemsSection = {
|
||||
display: 'No items',
|
||||
id: 'no-items',
|
||||
order: 10,
|
||||
visibleItems: toIndexedArray([]),
|
||||
...activeProps,
|
||||
};
|
||||
const noActiveItemsSection = {
|
||||
display: 'No active items',
|
||||
id: 'no-active-items',
|
||||
order: 10,
|
||||
visibleItems: toIndexedArray([
|
||||
{ display: 'disabled', id: 'disabled', ...disabledProps },
|
||||
{ display: 'notVisible', id: 'notVisible', ...notVisibleProps },
|
||||
]),
|
||||
...activeProps,
|
||||
};
|
||||
const activeSection = {
|
||||
display: 'activeSection',
|
||||
id: 'activeSection',
|
||||
order: 10,
|
||||
visibleItems: toIndexedArray([visibleItem]),
|
||||
...activeProps,
|
||||
};
|
||||
|
||||
const managementSections = [
|
||||
notVisibleSection,
|
||||
disabledSection,
|
||||
noItemsSection,
|
||||
noActiveItemsSection,
|
||||
activeSection,
|
||||
];
|
||||
|
||||
describe('Management', () => {
|
||||
it('maps legacy sections and apps into SidebarNav items', () => {
|
||||
expect(mergeLegacyItems([], managementSections, 'active-item-id')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('adds legacy apps to existing SidebarNav sections', () => {
|
||||
const navSection = {
|
||||
'data-test-subj': 'activeSection',
|
||||
icon: null,
|
||||
id: 'activeSection',
|
||||
items: [],
|
||||
name: 'activeSection',
|
||||
order: 10,
|
||||
};
|
||||
expect(mergeLegacyItems([navSection], managementSections, 'active-item-id')).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -17,184 +17,97 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiIcon,
|
||||
// @ts-ignore
|
||||
EuiSideNav,
|
||||
EuiScreenReaderOnly,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { ReactElement } from 'react';
|
||||
import { LegacySection, LegacyApp } from '../../types';
|
||||
import { ManagementApp } from '../../management_app';
|
||||
import { ManagementSection } from '../../management_section';
|
||||
import { sortBy } from 'lodash';
|
||||
|
||||
interface NavApp {
|
||||
id: string;
|
||||
name: ReactElement | string;
|
||||
[key: string]: unknown;
|
||||
order: number; // only needed while merging platform and legacy
|
||||
}
|
||||
import { EuiIcon, EuiSideNav, EuiScreenReaderOnly, EuiSideNavItemType } from '@elastic/eui';
|
||||
import { AppMountParameters } from 'kibana/public';
|
||||
import { ManagementApp, ManagementSection } from '../../utils';
|
||||
|
||||
interface NavSection extends NavApp {
|
||||
items: NavApp[];
|
||||
}
|
||||
import './management_sidebar_nav.scss';
|
||||
|
||||
import { ManagementItem } from '../../utils/management_item';
|
||||
import { reactRouterNavigate } from '../../../../kibana_react/public';
|
||||
|
||||
interface ManagementSidebarNavProps {
|
||||
getSections: () => ManagementSection[];
|
||||
legacySections: LegacySection[];
|
||||
sections: ManagementSection[];
|
||||
history: AppMountParameters['history'];
|
||||
selectedId: string;
|
||||
}
|
||||
|
||||
interface ManagementSidebarNavState {
|
||||
isSideNavOpenOnMobile: boolean;
|
||||
}
|
||||
|
||||
const managementSectionOrAppToNav = (appOrSection: ManagementApp | ManagementSection) => ({
|
||||
id: appOrSection.id,
|
||||
name: appOrSection.title,
|
||||
'data-test-subj': appOrSection.id,
|
||||
order: appOrSection.order,
|
||||
const headerLabel = i18n.translate('management.nav.label', {
|
||||
defaultMessage: 'Management',
|
||||
});
|
||||
|
||||
const managementSectionToNavSection = (section: ManagementSection) => {
|
||||
const iconType = section.euiIconType
|
||||
? section.euiIconType
|
||||
: section.icon
|
||||
? section.icon
|
||||
: 'empty';
|
||||
const navMenuLabel = i18n.translate('management.nav.menu', {
|
||||
defaultMessage: 'Management menu',
|
||||
});
|
||||
|
||||
return {
|
||||
icon: <EuiIcon type={iconType} size="m" />,
|
||||
...managementSectionOrAppToNav(section),
|
||||
/** @internal **/
|
||||
export const ManagementSidebarNav = ({
|
||||
selectedId,
|
||||
sections,
|
||||
history,
|
||||
}: ManagementSidebarNavProps) => {
|
||||
const HEADER_ID = 'stack-management-nav-header';
|
||||
const [isSideNavOpenOnMobile, setIsSideNavOpenOnMobile] = useState(false);
|
||||
const toggleOpenOnMobile = () => setIsSideNavOpenOnMobile(!isSideNavOpenOnMobile);
|
||||
|
||||
const sectionsToNavItems = (managementSections: ManagementSection[]) => {
|
||||
const sortedManagementSections = sortBy(managementSections, 'order');
|
||||
|
||||
return sortedManagementSections.reduce<Array<EuiSideNavItemType<any>>>((acc, section) => {
|
||||
const apps = sortBy(section.getAppsEnabled(), 'order');
|
||||
|
||||
if (apps.length) {
|
||||
acc.push({
|
||||
...createNavItem(section, {
|
||||
items: appsToNavItems(apps),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
};
|
||||
};
|
||||
|
||||
const managementAppToNavItem = (selectedId?: string, parentId?: string) => (
|
||||
app: ManagementApp
|
||||
) => ({
|
||||
isSelected: selectedId === app.id,
|
||||
href: `#/management/${parentId}/${app.id}`,
|
||||
...managementSectionOrAppToNav(app),
|
||||
});
|
||||
const appsToNavItems = (managementApps: ManagementApp[]) =>
|
||||
managementApps.map((app) => ({
|
||||
...createNavItem(app, {
|
||||
...reactRouterNavigate(history, app.basePath),
|
||||
}),
|
||||
}));
|
||||
|
||||
const legacySectionToNavSection = (section: LegacySection) => ({
|
||||
name: section.display,
|
||||
id: section.id,
|
||||
icon: section.icon ? <EuiIcon type={section.icon} /> : null,
|
||||
items: [],
|
||||
'data-test-subj': section.id,
|
||||
// @ts-ignore
|
||||
order: section.order,
|
||||
});
|
||||
const createNavItem = <T extends ManagementItem>(
|
||||
item: T,
|
||||
customParams: Partial<EuiSideNavItemType<any>> = {}
|
||||
) => {
|
||||
const iconType = item.euiIconType || item.icon;
|
||||
|
||||
const legacyAppToNavItem = (app: LegacyApp, selectedId: string) => ({
|
||||
isSelected: selectedId === app.id,
|
||||
name: app.display,
|
||||
id: app.id,
|
||||
href: app.url,
|
||||
'data-test-subj': app.id,
|
||||
// @ts-ignore
|
||||
order: app.order,
|
||||
});
|
||||
|
||||
const sectionVisible = (section: LegacySection | LegacyApp) => !section.disabled && section.visible;
|
||||
|
||||
const sideNavItems = (sections: ManagementSection[], selectedId: string) =>
|
||||
sections.map((section) => ({
|
||||
items: section.getAppsEnabled().map(managementAppToNavItem(selectedId, section.id)),
|
||||
...managementSectionToNavSection(section),
|
||||
}));
|
||||
|
||||
const findOrAddSection = (navItems: NavSection[], legacySection: LegacySection): NavSection => {
|
||||
const foundSection = navItems.find((sec) => sec.id === legacySection.id);
|
||||
|
||||
if (foundSection) {
|
||||
return foundSection;
|
||||
} else {
|
||||
const newSection = legacySectionToNavSection(legacySection);
|
||||
navItems.push(newSection);
|
||||
navItems.sort((a: NavSection, b: NavSection) => a.order - b.order); // only needed while merging platform and legacy
|
||||
return newSection;
|
||||
}
|
||||
};
|
||||
|
||||
export const mergeLegacyItems = (
|
||||
navItems: NavSection[],
|
||||
legacySections: LegacySection[],
|
||||
selectedId: string
|
||||
) => {
|
||||
const filteredLegacySections = legacySections
|
||||
.filter(sectionVisible)
|
||||
.filter((section) => section.visibleItems.length);
|
||||
|
||||
filteredLegacySections.forEach((legacySection) => {
|
||||
const section = findOrAddSection(navItems, legacySection);
|
||||
legacySection.visibleItems.forEach((app) => {
|
||||
section.items.push(legacyAppToNavItem(app, selectedId));
|
||||
return section.items.sort((a, b) => a.order - b.order);
|
||||
});
|
||||
});
|
||||
|
||||
return navItems;
|
||||
};
|
||||
|
||||
const sectionsToItems = (
|
||||
sections: ManagementSection[],
|
||||
legacySections: LegacySection[],
|
||||
selectedId: string
|
||||
) => {
|
||||
const navItems = sideNavItems(sections, selectedId);
|
||||
return mergeLegacyItems(navItems, legacySections, selectedId);
|
||||
};
|
||||
|
||||
export class ManagementSidebarNav extends React.Component<
|
||||
ManagementSidebarNavProps,
|
||||
ManagementSidebarNavState
|
||||
> {
|
||||
constructor(props: ManagementSidebarNavProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isSideNavOpenOnMobile: false,
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.title,
|
||||
isSelected: item.id === selectedId,
|
||||
icon: iconType ? <EuiIcon type={iconType} size="m" /> : undefined,
|
||||
'data-test-subj': item.id,
|
||||
...customParams,
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
const HEADER_ID = 'stack-management-nav-header';
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiScreenReaderOnly>
|
||||
<h2 id={HEADER_ID}>
|
||||
{i18n.translate('management.nav.label', {
|
||||
defaultMessage: 'Management',
|
||||
})}
|
||||
</h2>
|
||||
</EuiScreenReaderOnly>
|
||||
<EuiSideNav
|
||||
aria-labelledby={HEADER_ID}
|
||||
mobileTitle={this.renderMobileTitle()}
|
||||
isOpenOnMobile={this.state.isSideNavOpenOnMobile}
|
||||
toggleOpenOnMobile={this.toggleOpenOnMobile}
|
||||
items={sectionsToItems(
|
||||
this.props.getSections(),
|
||||
this.props.legacySections,
|
||||
this.props.selectedId
|
||||
)}
|
||||
className="mgtSideBarNav"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private renderMobileTitle() {
|
||||
return <FormattedMessage id="management.nav.menu" defaultMessage="Management menu" />;
|
||||
}
|
||||
|
||||
private toggleOpenOnMobile = () => {
|
||||
this.setState({
|
||||
isSideNavOpenOnMobile: !this.state.isSideNavOpenOnMobile,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiScreenReaderOnly>
|
||||
<h2 id={HEADER_ID}>{headerLabel}</h2>
|
||||
</EuiScreenReaderOnly>
|
||||
<EuiSideNav
|
||||
aria-labelledby={HEADER_ID}
|
||||
mobileTitle={navMenuLabel}
|
||||
toggleOpenOnMobile={toggleOpenOnMobile}
|
||||
isOpenOnMobile={isSideNavOpenOnMobile}
|
||||
items={sectionsToNavItems(sections)}
|
||||
className="mgtSideBarNav"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -21,17 +21,14 @@ import { PluginInitializerContext } from 'kibana/public';
|
|||
import { ManagementPlugin } from './plugin';
|
||||
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new ManagementPlugin();
|
||||
return new ManagementPlugin(initializerContext);
|
||||
}
|
||||
|
||||
export { RegisterManagementAppArgs, ManagementSection, ManagementApp } from './utils';
|
||||
|
||||
export {
|
||||
ManagementSectionId,
|
||||
ManagementAppMountParams,
|
||||
ManagementSetup,
|
||||
ManagementStart,
|
||||
RegisterManagementApp,
|
||||
ManagementSectionId,
|
||||
RegisterManagementAppArgs,
|
||||
ManagementAppMountParams,
|
||||
} from './types';
|
||||
export { ManagementApp } from './management_app';
|
||||
export { ManagementSection } from './management_section';
|
||||
export { ManagementSidebarNav } from './components'; // for use in legacy management apps
|
||||
|
|
|
@ -1,67 +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 React from 'react';
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
import { NotificationsStart, OverlayStart } from 'kibana/public';
|
||||
import { parse } from 'query-string';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { toMountPoint } from '../../../kibana_react/public';
|
||||
import { MarkdownSimple } from '../../../kibana_react/public';
|
||||
|
||||
/**
|
||||
* Show banners and toasts carried over from other applications. This is only necessary as long as
|
||||
* management is rendered in the legacy platform (which requires a full page reload to switch to).
|
||||
*
|
||||
* Once management is rendered using the core application service, this file and the places setting
|
||||
* bannerMessage and notFoundMessage URL params can be removed.
|
||||
* @param notifications Core notifications service
|
||||
* @param overlays Core overlays service
|
||||
*/
|
||||
export function showLegacyRedirectMessages(
|
||||
notifications: NotificationsStart,
|
||||
overlays: OverlayStart
|
||||
) {
|
||||
const queryPosition = window.location.hash.indexOf('?');
|
||||
if (queryPosition === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const urlParams = parse(window.location.hash.substr(queryPosition)) as Record<string, string>;
|
||||
|
||||
if (urlParams.bannerMessage) {
|
||||
const bannerId = overlays.banners.add(
|
||||
toMountPoint(
|
||||
<EuiCallOut color="warning" iconType="iInCircle" title={urlParams.bannerMessage} />
|
||||
)
|
||||
);
|
||||
setTimeout(() => {
|
||||
overlays.banners.remove(bannerId);
|
||||
}, 15000);
|
||||
}
|
||||
|
||||
if (urlParams.notFoundMessage) {
|
||||
notifications.toasts.addWarning({
|
||||
title: i18n.translate('management.history.savedObjectIsMissingNotificationMessage', {
|
||||
defaultMessage: 'Saved object is missing',
|
||||
}),
|
||||
text: toMountPoint(<MarkdownSimple>{urlParams.notFoundMessage}</MarkdownSimple>),
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,160 +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 { assign } from 'lodash';
|
||||
import { IndexedArray } from '../../../../legacy/ui/public/indexed_array';
|
||||
|
||||
const listeners = [];
|
||||
|
||||
export class LegacyManagementSection {
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {object} options
|
||||
* @param {number|null} options.order
|
||||
* @param {string|null} options.display - defaults to id
|
||||
* @param {string|null} options.url - defaults to ''
|
||||
* @param {boolean|null} options.visible - defaults to true
|
||||
* @param {boolean|null} options.disabled - defaults to false
|
||||
* @param {string|null} options.tooltip - defaults to ''
|
||||
* @param {string|null} options.icon - defaults to ''
|
||||
* @returns {ManagementSection}
|
||||
*/
|
||||
|
||||
constructor(id, options = {}, capabilities) {
|
||||
this.display = id;
|
||||
this.id = id;
|
||||
this.items = new IndexedArray({
|
||||
index: ['id'],
|
||||
order: ['order'],
|
||||
});
|
||||
this.visible = true;
|
||||
this.disabled = false;
|
||||
this.tooltip = '';
|
||||
this.icon = '';
|
||||
this.url = '';
|
||||
this.capabilities = capabilities;
|
||||
|
||||
assign(this, options);
|
||||
}
|
||||
|
||||
get visibleItems() {
|
||||
return this.items.inOrder.filter((item) => {
|
||||
const capabilityManagementSection = this.capabilities.management[this.id];
|
||||
const itemCapability = capabilityManagementSection
|
||||
? capabilityManagementSection[item.id]
|
||||
: null;
|
||||
|
||||
return item.visible && itemCapability !== false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback that will be executed when management sections are updated
|
||||
* Globally bound to solve for sidebar nav needs
|
||||
*
|
||||
* @param {function} fn
|
||||
*/
|
||||
addListener(fn) {
|
||||
listeners.push(fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a sub-section
|
||||
*
|
||||
* @param {string} id
|
||||
* @param {object} options
|
||||
* @returns {ManagementSection}
|
||||
*/
|
||||
|
||||
register(id, options = {}) {
|
||||
const item = new LegacyManagementSection(
|
||||
id,
|
||||
assign(options, { parent: this }),
|
||||
this.capabilities
|
||||
);
|
||||
|
||||
if (this.hasItem(id)) {
|
||||
throw new Error(`'${id}' is already registered`);
|
||||
}
|
||||
|
||||
this.items.push(item);
|
||||
listeners.forEach((fn) => fn());
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deregisters a section
|
||||
*
|
||||
* @param {string} id
|
||||
*/
|
||||
deregister(id) {
|
||||
this.items.remove((item) => item.id === id);
|
||||
listeners.forEach((fn) => fn(this.items));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an id is already registered
|
||||
*
|
||||
* @param {string} id
|
||||
* @returns {boolean}
|
||||
*/
|
||||
|
||||
hasItem(id) {
|
||||
return this.items.byId.hasOwnProperty(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a section by id
|
||||
*
|
||||
* @param {string} id
|
||||
* @returns {ManagementSection}
|
||||
*/
|
||||
|
||||
getSection(id) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sectionPath = id.split('/');
|
||||
return sectionPath.reduce((currentSection, nextSection) => {
|
||||
if (!currentSection) {
|
||||
return;
|
||||
}
|
||||
|
||||
return currentSection.items.byId[nextSection];
|
||||
}, this);
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.visible = false;
|
||||
}
|
||||
|
||||
show() {
|
||||
this.visible = true;
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.disabled = true;
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.disabled = false;
|
||||
}
|
||||
}
|
|
@ -1,285 +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 { LegacyManagementSection } from './section';
|
||||
import { IndexedArray } from '../../../../legacy/ui/public/indexed_array';
|
||||
|
||||
const capabilitiesMock = {
|
||||
management: {
|
||||
kibana: { sampleFeature2: false },
|
||||
},
|
||||
};
|
||||
|
||||
describe('ManagementSection', () => {
|
||||
describe('constructor', () => {
|
||||
it('defaults display to id', () => {
|
||||
const section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
|
||||
expect(section.display).toBe('kibana');
|
||||
});
|
||||
|
||||
it('defaults visible to true', () => {
|
||||
const section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
|
||||
expect(section.visible).toBe(true);
|
||||
});
|
||||
|
||||
it('defaults disabled to false', () => {
|
||||
const section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
|
||||
expect(section.disabled).toBe(false);
|
||||
});
|
||||
|
||||
it('defaults tooltip to empty string', () => {
|
||||
const section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
|
||||
expect(section.tooltip).toBe('');
|
||||
});
|
||||
|
||||
it('defaults url to empty string', () => {
|
||||
const section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
|
||||
expect(section.url).toBe('');
|
||||
});
|
||||
|
||||
it('exposes items', () => {
|
||||
const section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
|
||||
expect(section.items).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('exposes visibleItems', () => {
|
||||
const section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
|
||||
expect(section.visibleItems).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('assigns all options', () => {
|
||||
const section = new LegacyManagementSection(
|
||||
'kibana',
|
||||
{ description: 'test', url: 'foobar' },
|
||||
capabilitiesMock
|
||||
);
|
||||
expect(section.description).toBe('test');
|
||||
expect(section.url).toBe('foobar');
|
||||
});
|
||||
});
|
||||
|
||||
describe('register', () => {
|
||||
let section;
|
||||
|
||||
beforeEach(() => {
|
||||
section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
|
||||
});
|
||||
|
||||
it('returns a ManagementSection', () => {
|
||||
expect(section.register('about')).toBeInstanceOf(LegacyManagementSection);
|
||||
});
|
||||
|
||||
it('provides a reference to the parent', () => {
|
||||
expect(section.register('about').parent).toBe(section);
|
||||
});
|
||||
|
||||
it('adds item', function () {
|
||||
section.register('about', { description: 'test' });
|
||||
|
||||
expect(section.items).toHaveLength(1);
|
||||
expect(section.items[0]).toBeInstanceOf(LegacyManagementSection);
|
||||
expect(section.items[0].id).toBe('about');
|
||||
});
|
||||
|
||||
it('can only register a section once', () => {
|
||||
let threwException = false;
|
||||
section.register('about');
|
||||
|
||||
try {
|
||||
section.register('about');
|
||||
} catch (e) {
|
||||
threwException = e.message.indexOf('is already registered') > -1;
|
||||
}
|
||||
|
||||
expect(threwException).toBe(true);
|
||||
});
|
||||
|
||||
it('calls listener when item added', () => {
|
||||
let listerCalled = false;
|
||||
const listenerFn = () => {
|
||||
listerCalled = true;
|
||||
};
|
||||
|
||||
section.addListener(listenerFn);
|
||||
section.register('about');
|
||||
expect(listerCalled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deregister', () => {
|
||||
let section;
|
||||
|
||||
beforeEach(() => {
|
||||
section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
|
||||
section.register('about');
|
||||
});
|
||||
|
||||
it('deregisters an existing section', () => {
|
||||
section.deregister('about');
|
||||
expect(section.items).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('allows deregistering a section more than once', () => {
|
||||
section.deregister('about');
|
||||
section.deregister('about');
|
||||
expect(section.items).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('calls listener when item added', () => {
|
||||
let listerCalled = false;
|
||||
const listenerFn = () => {
|
||||
listerCalled = true;
|
||||
};
|
||||
|
||||
section.addListener(listenerFn);
|
||||
section.deregister('about');
|
||||
expect(listerCalled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSection', () => {
|
||||
let section;
|
||||
|
||||
beforeEach(() => {
|
||||
section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
|
||||
section.register('about');
|
||||
});
|
||||
|
||||
it('returns registered section', () => {
|
||||
expect(section.getSection('about')).toBeInstanceOf(LegacyManagementSection);
|
||||
});
|
||||
|
||||
it('returns undefined if un-registered', () => {
|
||||
expect(section.getSection('unknown')).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('returns sub-sections specified via a /-separated path', () => {
|
||||
section.getSection('about').register('time');
|
||||
expect(section.getSection('about/time')).toBeInstanceOf(LegacyManagementSection);
|
||||
expect(section.getSection('about/time')).toBe(section.getSection('about').getSection('time'));
|
||||
});
|
||||
|
||||
it('returns undefined if a sub-section along a /-separated path does not exist', () => {
|
||||
expect(section.getSection('about/damn/time')).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('items', () => {
|
||||
let section;
|
||||
|
||||
beforeEach(() => {
|
||||
section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
|
||||
|
||||
section.register('three', { order: 3 });
|
||||
section.register('one', { order: 1 });
|
||||
section.register('two', { order: 2 });
|
||||
});
|
||||
|
||||
it('is an indexed array', () => {
|
||||
expect(section.items).toBeInstanceOf(IndexedArray);
|
||||
});
|
||||
|
||||
it('is indexed on id', () => {
|
||||
const keys = Object.keys(section.items.byId).sort();
|
||||
expect(section.items.byId).toBeInstanceOf(Object);
|
||||
|
||||
expect(keys).toEqual(['one', 'three', 'two']);
|
||||
});
|
||||
|
||||
it('can be ordered', () => {
|
||||
const ids = section.items.inOrder.map((i) => {
|
||||
return i.id;
|
||||
});
|
||||
expect(ids).toEqual(['one', 'two', 'three']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('visible', () => {
|
||||
let section;
|
||||
|
||||
beforeEach(() => {
|
||||
section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
|
||||
});
|
||||
|
||||
it('hide sets visible to false', () => {
|
||||
section.hide();
|
||||
expect(section.visible).toBe(false);
|
||||
});
|
||||
|
||||
it('show sets visible to true', () => {
|
||||
section.hide();
|
||||
section.show();
|
||||
expect(section.visible).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('disabled', () => {
|
||||
let section;
|
||||
|
||||
beforeEach(() => {
|
||||
section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
|
||||
});
|
||||
|
||||
it('disable sets disabled to true', () => {
|
||||
section.disable();
|
||||
expect(section.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('enable sets disabled to false', () => {
|
||||
section.enable();
|
||||
expect(section.disabled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('visibleItems', () => {
|
||||
let section;
|
||||
|
||||
beforeEach(() => {
|
||||
section = new LegacyManagementSection('kibana', {}, capabilitiesMock);
|
||||
|
||||
section.register('three', { order: 3 });
|
||||
section.register('one', { order: 1 });
|
||||
section.register('two', { order: 2 });
|
||||
});
|
||||
|
||||
it('maintains the order', () => {
|
||||
const ids = section.visibleItems.map((i) => {
|
||||
return i.id;
|
||||
});
|
||||
expect(ids).toEqual(['one', 'two', 'three']);
|
||||
});
|
||||
|
||||
it('does not include hidden items', () => {
|
||||
section.getSection('two').hide();
|
||||
|
||||
const ids = section.visibleItems.map((i) => {
|
||||
return i.id;
|
||||
});
|
||||
expect(ids).toEqual(['one', 'three']);
|
||||
});
|
||||
|
||||
it('does not include visible items hidden via uiCapabilities', () => {
|
||||
section.register('sampleFeature2', { order: 4, visible: true });
|
||||
const ids = section.visibleItems.map((i) => {
|
||||
return i.id;
|
||||
});
|
||||
expect(ids).toEqual(['one', 'two', 'three']);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,47 +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 { i18n } from '@kbn/i18n';
|
||||
import { LegacyManagementSection } from './section';
|
||||
import { managementSections } from '../management_sections';
|
||||
|
||||
export class LegacyManagementAdapter {
|
||||
main = undefined;
|
||||
init = (capabilities) => {
|
||||
this.main = new LegacyManagementSection(
|
||||
'management',
|
||||
{
|
||||
display: i18n.translate('management.displayName', {
|
||||
defaultMessage: 'Stack Management',
|
||||
}),
|
||||
},
|
||||
capabilities
|
||||
);
|
||||
|
||||
managementSections.forEach(({ id, title }, idx) => {
|
||||
this.main.register(id, {
|
||||
display: title,
|
||||
order: idx,
|
||||
});
|
||||
});
|
||||
|
||||
return this.main;
|
||||
};
|
||||
getManagement = () => this.main;
|
||||
}
|
|
@ -1,66 +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 * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { coreMock } from '../../../core/public/mocks';
|
||||
|
||||
import { ManagementApp } from './management_app';
|
||||
// @ts-ignore
|
||||
import { LegacyManagementSection } from './legacy';
|
||||
|
||||
function createTestApp() {
|
||||
const legacySection = new LegacyManagementSection('legacy');
|
||||
return new ManagementApp(
|
||||
{
|
||||
id: 'test-app',
|
||||
title: 'Test App',
|
||||
basePath: '',
|
||||
mount(params) {
|
||||
params.setBreadcrumbs([{ text: 'Test App' }]);
|
||||
ReactDOM.render(<div>Test App - Hello world!</div>, params.element);
|
||||
|
||||
return () => {
|
||||
ReactDOM.unmountComponentAtNode(params.element);
|
||||
};
|
||||
},
|
||||
},
|
||||
() => [],
|
||||
jest.fn(),
|
||||
() => legacySection,
|
||||
coreMock.createSetup().getStartServices
|
||||
);
|
||||
}
|
||||
|
||||
test('Management app can mount and unmount', async () => {
|
||||
const testApp = createTestApp();
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
const unmount = testApp.mount({ element: container, basePath: '', setBreadcrumbs: jest.fn() });
|
||||
expect(container).toMatchSnapshot();
|
||||
(await unmount)();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Enabled by default, can disable', () => {
|
||||
const testApp = createTestApp();
|
||||
expect(testApp.enabled).toBe(true);
|
||||
testApp.disable();
|
||||
expect(testApp.enabled).toBe(false);
|
||||
});
|
|
@ -1,107 +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 * as React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CreateManagementApp, ManagementSectionMount, Unmount } from './types';
|
||||
import { KibanaLegacySetup } from '../../kibana_legacy/public';
|
||||
// @ts-ignore
|
||||
import { LegacyManagementSection } from './legacy';
|
||||
import { ManagementChrome } from './components';
|
||||
import { ManagementSection } from './management_section';
|
||||
import { ChromeBreadcrumb, StartServicesAccessor } from '../../../core/public/';
|
||||
|
||||
export class ManagementApp {
|
||||
readonly id: string;
|
||||
readonly title: string;
|
||||
readonly basePath: string;
|
||||
readonly order: number;
|
||||
readonly mount: ManagementSectionMount;
|
||||
private enabledStatus = true;
|
||||
|
||||
constructor(
|
||||
{ id, title, basePath, order = 100, mount }: CreateManagementApp,
|
||||
getSections: () => ManagementSection[],
|
||||
registerLegacyApp: KibanaLegacySetup['registerLegacyApp'],
|
||||
getLegacyManagementSections: () => LegacyManagementSection,
|
||||
getStartServices: StartServicesAccessor
|
||||
) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.basePath = basePath;
|
||||
this.order = order;
|
||||
this.mount = mount;
|
||||
|
||||
registerLegacyApp({
|
||||
id: basePath.substr(1), // get rid of initial slash
|
||||
title,
|
||||
mount: async ({}, params) => {
|
||||
let appUnmount: Unmount;
|
||||
if (!this.enabledStatus) {
|
||||
const [coreStart] = await getStartServices();
|
||||
coreStart.application.navigateToApp('kibana#/management');
|
||||
return () => {};
|
||||
}
|
||||
async function setBreadcrumbs(crumbs: ChromeBreadcrumb[]) {
|
||||
const [coreStart] = await getStartServices();
|
||||
coreStart.chrome.setBreadcrumbs([
|
||||
{
|
||||
text: i18n.translate('management.breadcrumb', {
|
||||
defaultMessage: 'Stack Management',
|
||||
}),
|
||||
href: '#/management',
|
||||
},
|
||||
...crumbs,
|
||||
]);
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<ManagementChrome
|
||||
getSections={getSections}
|
||||
selectedId={id}
|
||||
legacySections={getLegacyManagementSections().items}
|
||||
onMounted={async (element) => {
|
||||
appUnmount = await mount({
|
||||
basePath,
|
||||
element,
|
||||
setBreadcrumbs,
|
||||
});
|
||||
}}
|
||||
/>,
|
||||
params.element
|
||||
);
|
||||
|
||||
return async () => {
|
||||
appUnmount();
|
||||
ReactDOM.unmountComponentAtNode(params.element);
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
public enable() {
|
||||
this.enabledStatus = true;
|
||||
}
|
||||
public disable() {
|
||||
this.enabledStatus = false;
|
||||
}
|
||||
public get enabled() {
|
||||
return this.enabledStatus;
|
||||
}
|
||||
}
|
|
@ -1,66 +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 { ManagementSection } from './management_section';
|
||||
import { ManagementSectionId } from './types';
|
||||
// @ts-ignore
|
||||
import { LegacyManagementSection } from './legacy';
|
||||
import { coreMock } from '../../../core/public/mocks';
|
||||
|
||||
function createSection(registerLegacyApp: () => void) {
|
||||
const legacySection = new LegacyManagementSection('legacy');
|
||||
const getLegacySection = () => legacySection;
|
||||
const getManagementSections: () => ManagementSection[] = () => [];
|
||||
|
||||
const testSectionConfig = { id: ManagementSectionId.Data, title: 'Test Section' };
|
||||
return new ManagementSection(
|
||||
testSectionConfig,
|
||||
getManagementSections,
|
||||
registerLegacyApp,
|
||||
getLegacySection,
|
||||
coreMock.createSetup().getStartServices
|
||||
);
|
||||
}
|
||||
|
||||
test('cannot register two apps with the same id', () => {
|
||||
const registerLegacyApp = jest.fn();
|
||||
const section = createSection(registerLegacyApp);
|
||||
|
||||
const testAppConfig = { id: 'test-app', title: 'Test App', mount: () => () => {} };
|
||||
|
||||
section.registerApp(testAppConfig);
|
||||
expect(registerLegacyApp).toHaveBeenCalled();
|
||||
expect(section.apps.length).toEqual(1);
|
||||
|
||||
expect(() => {
|
||||
section.registerApp(testAppConfig);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test('can enable and disable apps', () => {
|
||||
const registerLegacyApp = jest.fn();
|
||||
const section = createSection(registerLegacyApp);
|
||||
|
||||
const testAppConfig = { id: 'test-app', title: 'Test App', mount: () => () => {} };
|
||||
|
||||
const app = section.registerApp(testAppConfig);
|
||||
expect(section.getAppsEnabled().length).toEqual(1);
|
||||
app.disable();
|
||||
expect(section.getAppsEnabled().length).toEqual(0);
|
||||
});
|
|
@ -1,80 +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 { ReactElement } from 'react';
|
||||
|
||||
import { CreateSection, RegisterManagementAppArgs, ManagementSectionId } from './types';
|
||||
import { KibanaLegacySetup } from '../../kibana_legacy/public';
|
||||
import { StartServicesAccessor } from '../../../core/public';
|
||||
// @ts-ignore
|
||||
import { LegacyManagementSection } from './legacy';
|
||||
import { ManagementApp } from './management_app';
|
||||
|
||||
export class ManagementSection {
|
||||
public readonly id: ManagementSectionId;
|
||||
public readonly title: string | ReactElement = '';
|
||||
public readonly apps: ManagementApp[] = [];
|
||||
public readonly order: number;
|
||||
public readonly euiIconType?: string;
|
||||
public readonly icon?: string;
|
||||
private readonly getSections: () => ManagementSection[];
|
||||
private readonly registerLegacyApp: KibanaLegacySetup['registerLegacyApp'];
|
||||
private readonly getLegacyManagementSection: () => LegacyManagementSection;
|
||||
private readonly getStartServices: StartServicesAccessor;
|
||||
|
||||
constructor(
|
||||
{ id, title, order = 100, euiIconType, icon }: CreateSection,
|
||||
getSections: () => ManagementSection[],
|
||||
registerLegacyApp: KibanaLegacySetup['registerLegacyApp'],
|
||||
getLegacyManagementSection: () => ManagementSection,
|
||||
getStartServices: StartServicesAccessor
|
||||
) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.order = order;
|
||||
this.euiIconType = euiIconType;
|
||||
this.icon = icon;
|
||||
this.getSections = getSections;
|
||||
this.registerLegacyApp = registerLegacyApp;
|
||||
this.getLegacyManagementSection = getLegacyManagementSection;
|
||||
this.getStartServices = getStartServices;
|
||||
}
|
||||
|
||||
registerApp({ id, title, order, mount }: RegisterManagementAppArgs) {
|
||||
if (this.getApp(id)) {
|
||||
throw new Error(`Management app already registered - id: ${id}, title: ${title}`);
|
||||
}
|
||||
|
||||
const app = new ManagementApp(
|
||||
{ id, title, order, mount, basePath: `/management/${this.id}/${id}` },
|
||||
this.getSections,
|
||||
this.registerLegacyApp,
|
||||
this.getLegacyManagementSection,
|
||||
this.getStartServices
|
||||
);
|
||||
this.apps.push(app);
|
||||
return app;
|
||||
}
|
||||
getApp(id: ManagementApp['id']) {
|
||||
return this.apps.find((app) => app.id === id);
|
||||
}
|
||||
getAppsEnabled() {
|
||||
return this.apps.filter((app) => app.enabled).sort((a, b) => a.order - b.order);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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 { ManagementSectionId } from './index';
|
||||
import { ManagementSectionsService } from './management_sections_service';
|
||||
|
||||
describe('ManagementService', () => {
|
||||
let managementService: ManagementSectionsService;
|
||||
|
||||
beforeEach(() => {
|
||||
managementService = new ManagementSectionsService();
|
||||
});
|
||||
|
||||
test('Provides default sections', () => {
|
||||
managementService.setup();
|
||||
const start = managementService.start();
|
||||
|
||||
expect(start.getAllSections().length).toEqual(6);
|
||||
expect(start.getSection(ManagementSectionId.Ingest)).toBeDefined();
|
||||
expect(start.getSection(ManagementSectionId.Data)).toBeDefined();
|
||||
expect(start.getSection(ManagementSectionId.InsightsAndAlerting)).toBeDefined();
|
||||
expect(start.getSection(ManagementSectionId.Security)).toBeDefined();
|
||||
expect(start.getSection(ManagementSectionId.Kibana)).toBeDefined();
|
||||
expect(start.getSection(ManagementSectionId.Stack)).toBeDefined();
|
||||
});
|
||||
|
||||
test('Register section, enable and disable', () => {
|
||||
// Setup phase:
|
||||
const setup = managementService.setup();
|
||||
const testSection = setup.register({ id: 'test-section', title: 'Test Section' });
|
||||
|
||||
expect(setup.getSection('test-section')).not.toBeUndefined();
|
||||
|
||||
// Start phase:
|
||||
const start = managementService.start();
|
||||
|
||||
expect(start.getSectionsEnabled().length).toEqual(7);
|
||||
|
||||
testSection.disable();
|
||||
|
||||
expect(start.getSectionsEnabled().length).toEqual(6);
|
||||
});
|
||||
});
|
65
src/plugins/management/public/management_sections_service.ts
Normal file
65
src/plugins/management/public/management_sections_service.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { ReactElement } from 'react';
|
||||
import { ManagementSection, RegisterManagementSectionArgs } from './utils';
|
||||
import { managementSections } from './components/management_sections';
|
||||
|
||||
import { ManagementSectionId, SectionsServiceSetup, SectionsServiceStart } from './types';
|
||||
|
||||
export class ManagementSectionsService {
|
||||
private sections: Map<ManagementSectionId | string, ManagementSection> = new Map();
|
||||
|
||||
private getSection = (sectionId: ManagementSectionId | string) =>
|
||||
this.sections.get(sectionId) as ManagementSection;
|
||||
|
||||
private getAllSections = () => [...this.sections.values()];
|
||||
|
||||
private registerSection = (section: RegisterManagementSectionArgs) => {
|
||||
if (this.sections.has(section.id)) {
|
||||
throw Error(`ManagementSection '${section.id}' already registered`);
|
||||
}
|
||||
|
||||
const newSection = new ManagementSection(section);
|
||||
|
||||
this.sections.set(section.id, newSection);
|
||||
return newSection;
|
||||
};
|
||||
|
||||
setup(): SectionsServiceSetup {
|
||||
managementSections.forEach(
|
||||
({ id, title }: { id: ManagementSectionId; title: ReactElement }, idx: number) => {
|
||||
this.registerSection({ id, title, order: idx });
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
register: this.registerSection,
|
||||
getSection: this.getSection,
|
||||
};
|
||||
}
|
||||
|
||||
start(): SectionsServiceStart {
|
||||
return {
|
||||
getSection: this.getSection,
|
||||
getAllSections: this.getAllSections,
|
||||
getSectionsEnabled: () => this.getAllSections().filter((section) => section.enabled),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,40 +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 { ManagementService } from './management_service';
|
||||
import { ManagementSectionId } from './types';
|
||||
import { coreMock } from '../../../core/public/mocks';
|
||||
import { npSetup } from '../../../legacy/ui/public/new_platform/__mocks__';
|
||||
|
||||
jest.mock('ui/new_platform');
|
||||
|
||||
test('Provides default sections', () => {
|
||||
const service = new ManagementService().setup(
|
||||
npSetup.plugins.kibanaLegacy,
|
||||
() => {},
|
||||
coreMock.createSetup().getStartServices
|
||||
);
|
||||
expect(service.getAllSections().length).toEqual(6);
|
||||
expect(service.getSection(ManagementSectionId.Ingest)).toBeDefined();
|
||||
expect(service.getSection(ManagementSectionId.Data)).toBeDefined();
|
||||
expect(service.getSection(ManagementSectionId.InsightsAndAlerting)).toBeDefined();
|
||||
expect(service.getSection(ManagementSectionId.Security)).toBeDefined();
|
||||
expect(service.getSection(ManagementSectionId.Kibana)).toBeDefined();
|
||||
expect(service.getSection(ManagementSectionId.Stack)).toBeDefined();
|
||||
});
|
|
@ -1,109 +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 { ReactElement } from 'react';
|
||||
|
||||
import { ManagementSection } from './management_section';
|
||||
import { managementSections } from './management_sections';
|
||||
import { KibanaLegacySetup } from '../../kibana_legacy/public';
|
||||
// @ts-ignore
|
||||
import { LegacyManagementSection, sections } from './legacy';
|
||||
import { CreateSection, ManagementSectionId } from './types';
|
||||
import { StartServicesAccessor, CoreStart } from '../../../core/public';
|
||||
|
||||
export class ManagementService {
|
||||
private sections: ManagementSection[] = [];
|
||||
|
||||
private register(
|
||||
registerLegacyApp: KibanaLegacySetup['registerLegacyApp'],
|
||||
getLegacyManagement: () => LegacyManagementSection,
|
||||
getStartServices: StartServicesAccessor
|
||||
) {
|
||||
return (section: CreateSection) => {
|
||||
if (this.getSection(section.id)) {
|
||||
throw Error(`ManagementSection '${section.id}' already registered`);
|
||||
}
|
||||
|
||||
const newSection = new ManagementSection(
|
||||
section,
|
||||
this.getSectionsEnabled.bind(this),
|
||||
registerLegacyApp,
|
||||
getLegacyManagement,
|
||||
getStartServices
|
||||
);
|
||||
this.sections.push(newSection);
|
||||
return newSection;
|
||||
};
|
||||
}
|
||||
|
||||
private getSection(sectionId: ManagementSectionId) {
|
||||
return this.sections.find((section) => section.id === sectionId);
|
||||
}
|
||||
|
||||
private getAllSections() {
|
||||
return this.sections;
|
||||
}
|
||||
|
||||
private getSectionsEnabled() {
|
||||
return this.sections
|
||||
.filter((section) => section.getAppsEnabled().length > 0)
|
||||
.sort((a, b) => a.order - b.order);
|
||||
}
|
||||
|
||||
private sharedInterface = {
|
||||
getSection: (sectionId: ManagementSectionId) => {
|
||||
const section = this.getSection(sectionId);
|
||||
if (!section) {
|
||||
throw new Error(`Management section with id ${sectionId} is undefined`);
|
||||
}
|
||||
return section;
|
||||
},
|
||||
getSectionsEnabled: this.getSectionsEnabled.bind(this),
|
||||
getAllSections: this.getAllSections.bind(this),
|
||||
};
|
||||
|
||||
public setup(
|
||||
kibanaLegacy: KibanaLegacySetup,
|
||||
getLegacyManagement: () => LegacyManagementSection,
|
||||
getStartServices: StartServicesAccessor
|
||||
) {
|
||||
const register = this.register.bind(this)(
|
||||
kibanaLegacy.registerLegacyApp,
|
||||
getLegacyManagement,
|
||||
getStartServices
|
||||
);
|
||||
|
||||
managementSections.forEach(
|
||||
({ id, title }: { id: ManagementSectionId; title: ReactElement }, idx: number) => {
|
||||
register({ id, title, order: idx });
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
...this.sharedInterface,
|
||||
};
|
||||
}
|
||||
|
||||
public start(navigateToApp: CoreStart['application']['navigateToApp']) {
|
||||
return {
|
||||
navigateToApp, // apps are currently registered as top level apps but this may change in the future
|
||||
...this.sharedInterface,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -18,29 +18,29 @@
|
|||
*/
|
||||
|
||||
import { ManagementSetup, ManagementStart } from '../types';
|
||||
import { ManagementSection } from '../management_section';
|
||||
import { ManagementSection } from '../index';
|
||||
|
||||
const createManagementSectionMock = (): jest.Mocked<PublicMethodsOf<ManagementSection>> => {
|
||||
return {
|
||||
const createManagementSectionMock = () =>
|
||||
(({
|
||||
disable: jest.fn(),
|
||||
enable: jest.fn(),
|
||||
registerApp: jest.fn(),
|
||||
getApp: jest.fn(),
|
||||
getAppsEnabled: jest.fn().mockReturnValue([]),
|
||||
};
|
||||
};
|
||||
getEnabledItems: jest.fn().mockReturnValue([]),
|
||||
} as unknown) as ManagementSection);
|
||||
|
||||
const createSetupContract = (): DeeplyMockedKeys<ManagementSetup> => ({
|
||||
sections: {
|
||||
register: jest.fn(),
|
||||
getSection: jest.fn().mockReturnValue(createManagementSectionMock()),
|
||||
getAllSections: jest.fn().mockReturnValue([]),
|
||||
},
|
||||
});
|
||||
|
||||
const createStartContract = (): DeeplyMockedKeys<ManagementStart> => ({
|
||||
legacy: {},
|
||||
sections: {
|
||||
getSection: jest.fn(),
|
||||
getAllSections: jest.fn(),
|
||||
navigateToApp: jest.fn(),
|
||||
getSectionsEnabled: jest.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -18,23 +18,30 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CoreSetup, CoreStart, Plugin } from 'kibana/public';
|
||||
import { ManagementSetup, ManagementStart } from './types';
|
||||
import { ManagementService } from './management_service';
|
||||
import { KibanaLegacySetup } from '../../kibana_legacy/public';
|
||||
import { FeatureCatalogueCategory, HomePublicPluginSetup } from '../../home/public';
|
||||
// @ts-ignore
|
||||
import { LegacyManagementAdapter } from './legacy';
|
||||
import { showLegacyRedirectMessages } from './legacy/redirect_messages';
|
||||
import {
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
Plugin,
|
||||
DEFAULT_APP_CATEGORIES,
|
||||
PluginInitializerContext,
|
||||
} from '../../../core/public';
|
||||
|
||||
import { ManagementSectionsService } from './management_sections_service';
|
||||
|
||||
interface ManagementSetupDependencies {
|
||||
home: HomePublicPluginSetup;
|
||||
}
|
||||
|
||||
export class ManagementPlugin implements Plugin<ManagementSetup, ManagementStart> {
|
||||
private managementSections = new ManagementService();
|
||||
private legacyManagement = new LegacyManagementAdapter();
|
||||
private readonly managementSections = new ManagementSectionsService();
|
||||
|
||||
constructor(private initializerContext: PluginInitializerContext) {}
|
||||
|
||||
public setup(core: CoreSetup, { home }: ManagementSetupDependencies) {
|
||||
const kibanaVersion = this.initializerContext.env.packageInfo.version;
|
||||
|
||||
public setup(
|
||||
core: CoreSetup,
|
||||
{ kibanaLegacy, home }: { kibanaLegacy: KibanaLegacySetup; home: HomePublicPluginSetup }
|
||||
) {
|
||||
home.featureCatalogue.register({
|
||||
id: 'stack-management',
|
||||
title: i18n.translate('management.stackManagement.managementLabel', {
|
||||
|
@ -44,25 +51,38 @@ export class ManagementPlugin implements Plugin<ManagementSetup, ManagementStart
|
|||
defaultMessage: 'Your center console for managing the Elastic Stack.',
|
||||
}),
|
||||
icon: 'managementApp',
|
||||
path: '/app/kibana#/management',
|
||||
path: '/app/management',
|
||||
showOnHomePage: false,
|
||||
category: FeatureCatalogueCategory.ADMIN,
|
||||
});
|
||||
|
||||
core.application.register({
|
||||
id: 'management',
|
||||
title: i18n.translate('management.stackManagement.title', {
|
||||
defaultMessage: 'Stack Management',
|
||||
}),
|
||||
order: 9003,
|
||||
euiIconType: 'managementApp',
|
||||
category: DEFAULT_APP_CATEGORIES.management,
|
||||
async mount(context, params) {
|
||||
const { renderApp } = await import('./application');
|
||||
const selfStart = (await core.getStartServices())[2] as ManagementStart;
|
||||
|
||||
return renderApp(context, params, {
|
||||
kibanaVersion,
|
||||
management: selfStart,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
sections: this.managementSections.setup(
|
||||
kibanaLegacy,
|
||||
this.legacyManagement.getManagement,
|
||||
core.getStartServices
|
||||
),
|
||||
sections: this.managementSections.setup(),
|
||||
};
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {
|
||||
showLegacyRedirectMessages(core.notifications, core.overlays);
|
||||
return {
|
||||
sections: this.managementSections.start(core.application.navigateToApp),
|
||||
legacy: this.legacyManagement.init(core.application.capabilities),
|
||||
sections: this.managementSections.start(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,30 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 { ReactElement } from 'react';
|
||||
import { IconType } from '@elastic/eui';
|
||||
import { ManagementApp } from './management_app';
|
||||
import { ManagementSection } from './management_section';
|
||||
import { ChromeBreadcrumb, ApplicationStart } from '../../../core/public/';
|
||||
import { ScopedHistory } from 'kibana/public';
|
||||
import { ManagementSection, RegisterManagementSectionArgs } from './utils';
|
||||
import { ChromeBreadcrumb } from '../../../core/public/';
|
||||
|
||||
export interface ManagementSetup {
|
||||
sections: SectionsServiceSetup;
|
||||
|
@ -48,7 +28,17 @@ export interface ManagementSetup {
|
|||
|
||||
export interface ManagementStart {
|
||||
sections: SectionsServiceStart;
|
||||
legacy: any;
|
||||
}
|
||||
|
||||
export interface SectionsServiceSetup {
|
||||
register: (args: RegisterManagementSectionArgs) => ManagementSection;
|
||||
getSection: (sectionId: ManagementSectionId | string) => ManagementSection;
|
||||
}
|
||||
|
||||
export interface SectionsServiceStart {
|
||||
getSection: (sectionId: ManagementSectionId | string) => ManagementSection;
|
||||
getAllSections: () => ManagementSection[];
|
||||
getSectionsEnabled: () => ManagementSection[];
|
||||
}
|
||||
|
||||
export enum ManagementSectionId {
|
||||
|
@ -60,67 +50,20 @@ export enum ManagementSectionId {
|
|||
Stack = 'stack',
|
||||
}
|
||||
|
||||
interface SectionsServiceSetup {
|
||||
getSection: (sectionId: ManagementSectionId) => ManagementSection;
|
||||
getAllSections: () => ManagementSection[];
|
||||
}
|
||||
|
||||
interface SectionsServiceStart {
|
||||
getSection: (sectionId: ManagementSectionId) => ManagementSection;
|
||||
getAllSections: () => ManagementSection[];
|
||||
navigateToApp: ApplicationStart['navigateToApp'];
|
||||
}
|
||||
|
||||
export interface CreateSection {
|
||||
id: ManagementSectionId;
|
||||
title: string | ReactElement;
|
||||
order?: number;
|
||||
euiIconType?: string; // takes precedence over `icon` property.
|
||||
icon?: string; // URL to image file; fallback if no `euiIconType`
|
||||
}
|
||||
|
||||
export type RegisterSection = (section: CreateSection) => ManagementSection;
|
||||
|
||||
export interface RegisterManagementAppArgs {
|
||||
id: string;
|
||||
title: string;
|
||||
mount: ManagementSectionMount;
|
||||
order?: number;
|
||||
}
|
||||
|
||||
export type RegisterManagementApp = (managementApp: RegisterManagementAppArgs) => ManagementApp;
|
||||
|
||||
export type Unmount = () => Promise<void> | void;
|
||||
export type Mount = (params: ManagementAppMountParams) => Unmount | Promise<Unmount>;
|
||||
|
||||
export interface ManagementAppMountParams {
|
||||
basePath: string; // base path for setting up your router
|
||||
element: HTMLElement; // element the section should render into
|
||||
setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void;
|
||||
history: ScopedHistory;
|
||||
}
|
||||
|
||||
export type ManagementSectionMount = (
|
||||
params: ManagementAppMountParams
|
||||
) => Unmount | Promise<Unmount>;
|
||||
|
||||
export interface CreateManagementApp {
|
||||
export interface CreateManagementItemArgs {
|
||||
id: string;
|
||||
title: string;
|
||||
basePath: string;
|
||||
title: string | ReactElement;
|
||||
order?: number;
|
||||
mount: ManagementSectionMount;
|
||||
}
|
||||
|
||||
export interface LegacySection extends LegacyApp {
|
||||
visibleItems: LegacyApp[];
|
||||
}
|
||||
|
||||
export interface LegacyApp {
|
||||
disabled: boolean;
|
||||
visible: boolean;
|
||||
id: string;
|
||||
display: string;
|
||||
url?: string;
|
||||
euiIconType?: IconType;
|
||||
icon?: string;
|
||||
order: number;
|
||||
euiIconType?: string; // takes precedence over `icon` property.
|
||||
icon?: string; // URL to image file; fallback if no `euiIconType`
|
||||
}
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const MANAGEMENT_BREADCRUMB = Object.freeze({
|
||||
text: i18n.translate('common.ui.stackManagement.breadcrumb', {
|
||||
export const MANAGEMENT_BREADCRUMB = {
|
||||
text: i18n.translate('management.breadcrumb', {
|
||||
defaultMessage: 'Stack Management',
|
||||
}),
|
||||
href: '#/management',
|
||||
});
|
||||
href: '/',
|
||||
};
|
|
@ -18,5 +18,5 @@
|
|||
*/
|
||||
|
||||
export { MANAGEMENT_BREADCRUMB } from './breadcrumbs';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
export const management = npStart.plugins.management.legacy;
|
||||
export { ManagementApp, RegisterManagementAppArgs } from './management_app';
|
||||
export { ManagementSection, RegisterManagementSectionArgs } from './management_section';
|
|
@ -17,18 +17,22 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export interface IndexPatternCreationOption {
|
||||
text: string;
|
||||
description?: string;
|
||||
onClick: () => void;
|
||||
import { CreateManagementItemArgs, Mount } from '../types';
|
||||
import { ManagementItem } from './management_item';
|
||||
|
||||
export interface RegisterManagementAppArgs extends CreateManagementItemArgs {
|
||||
mount: Mount;
|
||||
basePath: string;
|
||||
}
|
||||
|
||||
export interface IndexPattern {
|
||||
id: string;
|
||||
title: string;
|
||||
url: string;
|
||||
active: boolean;
|
||||
default: boolean;
|
||||
tag?: string[];
|
||||
sort: string;
|
||||
export class ManagementApp extends ManagementItem {
|
||||
public readonly mount: Mount;
|
||||
public readonly basePath: string;
|
||||
|
||||
constructor(args: RegisterManagementAppArgs) {
|
||||
super(args);
|
||||
|
||||
this.mount = args.mount;
|
||||
this.basePath = args.basePath;
|
||||
}
|
||||
}
|
46
src/plugins/management/public/utils/management_item.ts
Normal file
46
src/plugins/management/public/utils/management_item.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 { ReactElement } from 'react';
|
||||
import { CreateManagementItemArgs } from '../types';
|
||||
|
||||
export class ManagementItem {
|
||||
public readonly id: string = '';
|
||||
public readonly title: string | ReactElement = '';
|
||||
public readonly order: number;
|
||||
public readonly euiIconType?: string;
|
||||
public readonly icon?: string;
|
||||
|
||||
public enabled: boolean = true;
|
||||
|
||||
constructor({ id, title, order = 100, euiIconType, icon }: CreateManagementItemArgs) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.order = order;
|
||||
this.euiIconType = euiIconType;
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.enabled = true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 { ManagementSection, RegisterManagementSectionArgs } from './management_section';
|
||||
|
||||
describe('ManagementSection', () => {
|
||||
const createSection = (
|
||||
config: RegisterManagementSectionArgs = {
|
||||
id: 'test-section',
|
||||
title: 'Test Section',
|
||||
} as RegisterManagementSectionArgs
|
||||
) => new ManagementSection(config);
|
||||
|
||||
test('cannot register two apps with the same id', () => {
|
||||
const section = createSection();
|
||||
const testAppConfig = { id: 'test-app', title: 'Test App', mount: () => () => {} };
|
||||
|
||||
section.registerApp(testAppConfig);
|
||||
|
||||
expect(section.apps.length).toEqual(1);
|
||||
|
||||
expect(() => {
|
||||
section.registerApp(testAppConfig);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test('can enable and disable apps', () => {
|
||||
const section = createSection();
|
||||
const testAppConfig = { id: 'test-app', title: 'Test App', mount: () => () => {} };
|
||||
|
||||
const app = section.registerApp(testAppConfig);
|
||||
|
||||
expect(section.getAppsEnabled().length).toEqual(1);
|
||||
|
||||
app.disable();
|
||||
|
||||
expect(section.getAppsEnabled().length).toEqual(0);
|
||||
});
|
||||
});
|
58
src/plugins/management/public/utils/management_section.ts
Normal file
58
src/plugins/management/public/utils/management_section.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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 { Assign } from '@kbn/utility-types';
|
||||
import { CreateManagementItemArgs, ManagementSectionId } from '../types';
|
||||
import { ManagementItem } from './management_item';
|
||||
import { ManagementApp, RegisterManagementAppArgs } from './management_app';
|
||||
|
||||
export type RegisterManagementSectionArgs = Assign<
|
||||
CreateManagementItemArgs,
|
||||
{ id: ManagementSectionId | string }
|
||||
>;
|
||||
|
||||
export class ManagementSection extends ManagementItem {
|
||||
public readonly apps: ManagementApp[] = [];
|
||||
|
||||
constructor(args: RegisterManagementSectionArgs) {
|
||||
super(args);
|
||||
}
|
||||
|
||||
registerApp(args: Omit<RegisterManagementAppArgs, 'basePath'>) {
|
||||
if (this.getApp(args.id)) {
|
||||
throw new Error(`Management app already registered - id: ${args.id}, title: ${args.title}`);
|
||||
}
|
||||
|
||||
const app = new ManagementApp({
|
||||
...args,
|
||||
basePath: `/${this.id}/${args.id}`,
|
||||
});
|
||||
|
||||
this.apps.push(app);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
getApp(id: ManagementApp['id']) {
|
||||
return this.apps.find((app) => app.id === id);
|
||||
}
|
||||
|
||||
getAppsEnabled() {
|
||||
return this.apps.filter((app) => app.enabled);
|
||||
}
|
||||
}
|
|
@ -19,10 +19,10 @@
|
|||
|
||||
import React, { lazy, Suspense } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { HashRouter, Switch, Route } from 'react-router-dom';
|
||||
import { Router, Switch, Route } from 'react-router-dom';
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { CoreSetup, Capabilities } from 'src/core/public';
|
||||
import { CoreSetup } from 'src/core/public';
|
||||
import { ManagementAppMountParams } from '../../../management/public';
|
||||
import { StartDependencies, SavedObjectsManagementPluginStart } from '../plugin';
|
||||
import { ISavedObjectsManagementServiceRegistry } from '../services';
|
||||
|
@ -44,30 +44,41 @@ export const mountManagementSection = async ({
|
|||
serviceRegistry,
|
||||
}: MountParams) => {
|
||||
const [coreStart, { data }, pluginStart] = await core.getStartServices();
|
||||
const { element, basePath, setBreadcrumbs } = mountParams;
|
||||
const { element, history, setBreadcrumbs } = mountParams;
|
||||
if (allowedObjectTypes === undefined) {
|
||||
allowedObjectTypes = await getAllowedTypes(coreStart.http);
|
||||
}
|
||||
|
||||
const capabilities = coreStart.application.capabilities;
|
||||
|
||||
const RedirectToHomeIfUnauthorized: React.FunctionComponent = ({ children }) => {
|
||||
const allowed = capabilities?.management?.kibana?.objects ?? false;
|
||||
|
||||
if (!allowed) {
|
||||
coreStart.application.navigateToApp('home');
|
||||
return null;
|
||||
}
|
||||
return children! as React.ReactElement;
|
||||
};
|
||||
|
||||
ReactDOM.render(
|
||||
<I18nProvider>
|
||||
<HashRouter basename={basePath}>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path={'/:service/:id'} exact={true}>
|
||||
<RedirectToHomeIfUnauthorized capabilities={capabilities}>
|
||||
<RedirectToHomeIfUnauthorized>
|
||||
<Suspense fallback={<EuiLoadingSpinner />}>
|
||||
<SavedObjectsEditionPage
|
||||
coreStart={coreStart}
|
||||
serviceRegistry={serviceRegistry}
|
||||
setBreadcrumbs={setBreadcrumbs}
|
||||
history={history}
|
||||
/>
|
||||
</Suspense>
|
||||
</RedirectToHomeIfUnauthorized>
|
||||
</Route>
|
||||
<Route path={'/'} exact={false}>
|
||||
<RedirectToHomeIfUnauthorized capabilities={capabilities}>
|
||||
<RedirectToHomeIfUnauthorized>
|
||||
<Suspense fallback={<EuiLoadingSpinner />}>
|
||||
<SavedObjectsTablePage
|
||||
coreStart={coreStart}
|
||||
|
@ -81,7 +92,7 @@ export const mountManagementSection = async ({
|
|||
</RedirectToHomeIfUnauthorized>
|
||||
</Route>
|
||||
</Switch>
|
||||
</HashRouter>
|
||||
</Router>
|
||||
</I18nProvider>,
|
||||
element
|
||||
);
|
||||
|
@ -90,14 +101,3 @@ export const mountManagementSection = async ({
|
|||
ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
||||
};
|
||||
|
||||
const RedirectToHomeIfUnauthorized: React.FunctionComponent<{
|
||||
capabilities: Capabilities;
|
||||
}> = ({ children, capabilities }) => {
|
||||
const allowed = capabilities?.management?.kibana?.objects ?? false;
|
||||
if (!allowed) {
|
||||
window.location.hash = '/home';
|
||||
return null;
|
||||
}
|
||||
return children! as React.ReactElement;
|
||||
};
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
OverlayStart,
|
||||
NotificationsStart,
|
||||
SimpleSavedObject,
|
||||
ScopedHistory,
|
||||
} from '../../../../../core/public';
|
||||
import { ISavedObjectsManagementServiceRegistry } from '../../services';
|
||||
import { Header, NotFoundErrors, Intro, Form } from './components';
|
||||
|
@ -41,6 +42,7 @@ interface SavedObjectEditionProps {
|
|||
notifications: NotificationsStart;
|
||||
notFoundType?: string;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
history: ScopedHistory;
|
||||
}
|
||||
|
||||
interface SavedObjectEditionState {
|
||||
|
@ -171,6 +173,6 @@ export class SavedObjectEdition extends Component<
|
|||
};
|
||||
|
||||
redirectToListing() {
|
||||
window.location.hash = '/management/kibana/objects';
|
||||
this.props.history.push('/');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -314,7 +314,7 @@ exports[`SavedObjectsTable relationships should show the flyout 1`] = `
|
|||
Object {
|
||||
"id": "2",
|
||||
"meta": Object {
|
||||
"editUrl": "#/management/kibana/objects/savedSearches/2",
|
||||
"editUrl": "/management/kibana/objects/savedSearches/2",
|
||||
"icon": "search",
|
||||
"inAppUrl": Object {
|
||||
"path": "/discover/2",
|
||||
|
@ -404,7 +404,7 @@ exports[`SavedObjectsTable should render normally 1`] = `
|
|||
Object {
|
||||
"id": "2",
|
||||
"meta": Object {
|
||||
"editUrl": "#/management/kibana/objects/savedSearches/2",
|
||||
"editUrl": "/management/kibana/objects/savedSearches/2",
|
||||
"icon": "search",
|
||||
"inAppUrl": Object {
|
||||
"path": "/discover/2",
|
||||
|
@ -417,7 +417,7 @@ exports[`SavedObjectsTable should render normally 1`] = `
|
|||
Object {
|
||||
"id": "3",
|
||||
"meta": Object {
|
||||
"editUrl": "#/management/kibana/objects/savedDashboards/3",
|
||||
"editUrl": "/management/kibana/objects/savedDashboards/3",
|
||||
"icon": "dashboardApp",
|
||||
"inAppUrl": Object {
|
||||
"path": "/dashboard/3",
|
||||
|
@ -430,7 +430,7 @@ exports[`SavedObjectsTable should render normally 1`] = `
|
|||
Object {
|
||||
"id": "4",
|
||||
"meta": Object {
|
||||
"editUrl": "#/management/kibana/objects/savedVisualizations/4",
|
||||
"editUrl": "/management/kibana/objects/savedVisualizations/4",
|
||||
"icon": "visualizeApp",
|
||||
"inAppUrl": Object {
|
||||
"path": "/edit/4",
|
||||
|
|
|
@ -455,7 +455,7 @@ exports[`Relationships should render searches normally 1`] = `
|
|||
"editUrl": "/management/kibana/indexPatterns/patterns/1",
|
||||
"icon": "indexPatternApp",
|
||||
"inAppUrl": Object {
|
||||
"path": "/app/kibana#/management/kibana/indexPatterns/patterns/1",
|
||||
"path": "/app/management/kibana/indexPatterns/patterns/1",
|
||||
"uiCapabilitiesPath": "management.kibana.index_patterns",
|
||||
},
|
||||
"title": "My Index Pattern",
|
||||
|
|
|
@ -112,7 +112,7 @@ describe('Relationships', () => {
|
|||
editUrl: '/management/kibana/indexPatterns/patterns/1',
|
||||
icon: 'indexPatternApp',
|
||||
inAppUrl: {
|
||||
path: '/app/kibana#/management/kibana/indexPatterns/patterns/1',
|
||||
path: '/app/management/kibana/indexPatterns/patterns/1',
|
||||
uiCapabilitiesPath: 'management.kibana.index_patterns',
|
||||
},
|
||||
title: 'My Index Pattern',
|
||||
|
@ -141,7 +141,7 @@ describe('Relationships', () => {
|
|||
meta: {
|
||||
title: 'MySearch',
|
||||
icon: 'search',
|
||||
editUrl: '#/management/kibana/objects/savedSearches/1',
|
||||
editUrl: '/management/kibana/objects/savedSearches/1',
|
||||
inAppUrl: {
|
||||
path: '/discover/1',
|
||||
uiCapabilitiesPath: 'discover.show',
|
||||
|
@ -208,7 +208,7 @@ describe('Relationships', () => {
|
|||
meta: {
|
||||
title: 'MyViz',
|
||||
icon: 'visualizeApp',
|
||||
editUrl: '#/management/kibana/objects/savedVisualizations/1',
|
||||
editUrl: '/management/kibana/objects/savedVisualizations/1',
|
||||
inAppUrl: {
|
||||
path: '/edit/1',
|
||||
uiCapabilitiesPath: 'visualize.show',
|
||||
|
@ -275,7 +275,7 @@ describe('Relationships', () => {
|
|||
meta: {
|
||||
title: 'MyDashboard',
|
||||
icon: 'dashboardApp',
|
||||
editUrl: '#/management/kibana/objects/savedDashboards/1',
|
||||
editUrl: '/management/kibana/objects/savedDashboards/1',
|
||||
inAppUrl: {
|
||||
path: '/dashboard/1',
|
||||
uiCapabilitiesPath: 'dashboard.show',
|
||||
|
@ -315,7 +315,7 @@ describe('Relationships', () => {
|
|||
meta: {
|
||||
title: 'MyDashboard',
|
||||
icon: 'dashboardApp',
|
||||
editUrl: '#/management/kibana/objects/savedDashboards/1',
|
||||
editUrl: '/management/kibana/objects/savedDashboards/1',
|
||||
inAppUrl: {
|
||||
path: '/dashboard/1',
|
||||
uiCapabilitiesPath: 'dashboard.show',
|
||||
|
|
|
@ -168,7 +168,7 @@ describe('SavedObjectsTable', () => {
|
|||
meta: {
|
||||
title: `MySearch`,
|
||||
icon: 'search',
|
||||
editUrl: '#/management/kibana/objects/savedSearches/2',
|
||||
editUrl: '/management/kibana/objects/savedSearches/2',
|
||||
inAppUrl: {
|
||||
path: '/discover/2',
|
||||
uiCapabilitiesPath: 'discover.show',
|
||||
|
@ -181,7 +181,7 @@ describe('SavedObjectsTable', () => {
|
|||
meta: {
|
||||
title: `MyDashboard`,
|
||||
icon: 'dashboardApp',
|
||||
editUrl: '#/management/kibana/objects/savedDashboards/3',
|
||||
editUrl: '/management/kibana/objects/savedDashboards/3',
|
||||
inAppUrl: {
|
||||
path: '/dashboard/3',
|
||||
uiCapabilitiesPath: 'dashboard.show',
|
||||
|
@ -194,7 +194,7 @@ describe('SavedObjectsTable', () => {
|
|||
meta: {
|
||||
title: `MyViz`,
|
||||
icon: 'visualizeApp',
|
||||
editUrl: '#/management/kibana/objects/savedVisualizations/4',
|
||||
editUrl: '/management/kibana/objects/savedVisualizations/4',
|
||||
inAppUrl: {
|
||||
path: '/edit/4',
|
||||
uiCapabilitiesPath: 'visualize.show',
|
||||
|
@ -441,7 +441,7 @@ describe('SavedObjectsTable', () => {
|
|||
meta: {
|
||||
title: `MySearch`,
|
||||
icon: 'search',
|
||||
editUrl: '#/management/kibana/objects/savedSearches/2',
|
||||
editUrl: '/management/kibana/objects/savedSearches/2',
|
||||
inAppUrl: {
|
||||
path: '/discover/2',
|
||||
uiCapabilitiesPath: 'discover.show',
|
||||
|
@ -456,7 +456,7 @@ describe('SavedObjectsTable', () => {
|
|||
type: 'search',
|
||||
meta: {
|
||||
title: 'MySearch',
|
||||
editUrl: '#/management/kibana/objects/savedSearches/2',
|
||||
editUrl: '/management/kibana/objects/savedSearches/2',
|
||||
icon: 'search',
|
||||
inAppUrl: {
|
||||
path: '/discover/2',
|
||||
|
|
|
@ -457,8 +457,8 @@ export class SavedObjectsTable extends Component<SavedObjectsTableProps, SavedOb
|
|||
return null;
|
||||
}
|
||||
const { applications } = this.props;
|
||||
const newIndexPatternUrl = applications.getUrlForApp('kibana', {
|
||||
path: '#/management/kibana/indexPattern',
|
||||
const newIndexPatternUrl = applications.getUrlForApp('management', {
|
||||
path: 'kibana/indexPatterns',
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -21,7 +21,7 @@ import React, { useEffect } from 'react';
|
|||
import { useParams, useLocation } from 'react-router-dom';
|
||||
import { parse } from 'query-string';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CoreStart, ChromeBreadcrumb } from 'src/core/public';
|
||||
import { CoreStart, ChromeBreadcrumb, ScopedHistory } from 'src/core/public';
|
||||
import { ISavedObjectsManagementServiceRegistry } from '../services';
|
||||
import { SavedObjectEdition } from './object_view';
|
||||
|
||||
|
@ -29,10 +29,12 @@ const SavedObjectsEditionPage = ({
|
|||
coreStart,
|
||||
serviceRegistry,
|
||||
setBreadcrumbs,
|
||||
history,
|
||||
}: {
|
||||
coreStart: CoreStart;
|
||||
serviceRegistry: ISavedObjectsManagementServiceRegistry;
|
||||
setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void;
|
||||
history: ScopedHistory;
|
||||
}) => {
|
||||
const { service: serviceName, id } = useParams<{ service: string; id: string }>();
|
||||
const capabilities = coreStart.application.capabilities;
|
||||
|
@ -47,7 +49,7 @@ const SavedObjectsEditionPage = ({
|
|||
text: i18n.translate('savedObjectsManagement.breadcrumb.index', {
|
||||
defaultMessage: 'Saved objects',
|
||||
}),
|
||||
href: '#/management/kibana/objects',
|
||||
href: '/',
|
||||
},
|
||||
{
|
||||
text: i18n.translate('savedObjectsManagement.breadcrumb.edit', {
|
||||
|
@ -68,6 +70,7 @@ const SavedObjectsEditionPage = ({
|
|||
notifications={coreStart.notifications}
|
||||
capabilities={capabilities}
|
||||
notFoundType={query.notFound as string}
|
||||
history={history}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -52,7 +52,7 @@ const SavedObjectsTablePage = ({
|
|||
text: i18n.translate('savedObjectsManagement.breadcrumb.index', {
|
||||
defaultMessage: 'Saved objects',
|
||||
}),
|
||||
href: '#/management/kibana/objects',
|
||||
href: '/',
|
||||
},
|
||||
]);
|
||||
}, [setBreadcrumbs]);
|
||||
|
@ -73,11 +73,7 @@ const SavedObjectsTablePage = ({
|
|||
goInspectObject={(savedObject) => {
|
||||
const { editUrl } = savedObject.meta;
|
||||
if (editUrl) {
|
||||
// previously, kbnUrl.change(object.meta.editUrl); was used.
|
||||
// using direct access to location.hash seems the only option for now,
|
||||
// as using react-router-dom will prefix the url with the router's basename
|
||||
// which should be ignored there.
|
||||
window.location.hash = editUrl;
|
||||
return coreStart.application.navigateToUrl('/app' + editUrl);
|
||||
}
|
||||
}}
|
||||
canGoInApp={(savedObject) => {
|
||||
|
|
|
@ -82,7 +82,7 @@ export class SavedObjectsManagementPlugin
|
|||
'Import, export, and manage your saved searches, visualizations, and dashboards.',
|
||||
}),
|
||||
icon: 'savedObjectsApp',
|
||||
path: '/app/kibana#/management/kibana/objects',
|
||||
path: '/app/management/kibana/objects',
|
||||
showOnHomePage: true,
|
||||
category: FeatureCatalogueCategory.ADMIN,
|
||||
});
|
||||
|
|
|
@ -49,7 +49,7 @@ export const LOCALSTORAGE_KEY = 'telemetry.data';
|
|||
/**
|
||||
* Link to Advanced Settings.
|
||||
*/
|
||||
export const PATH_TO_ADVANCED_SETTINGS = 'kibana#/management/kibana/settings';
|
||||
export const PATH_TO_ADVANCED_SETTINGS = 'management/kibana/settings';
|
||||
|
||||
/**
|
||||
* Link to the Elastic Telemetry privacy statement.
|
||||
|
|
|
@ -10,7 +10,7 @@ exports[`OptInDetailsComponent renders as expected 1`] = `
|
|||
values={
|
||||
Object {
|
||||
"disableLink": <ForwardRef
|
||||
href="kibana#/management/kibana/settings"
|
||||
href="management/kibana/settings"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -211,22 +211,16 @@ export function initVisualizeApp(app, deps) {
|
|||
mapping: {
|
||||
visualization: VisualizeConstants.LANDING_PAGE_PATH,
|
||||
search: {
|
||||
app: 'kibana',
|
||||
path:
|
||||
'#/management/kibana/objects/savedVisualizations/' +
|
||||
$route.current.params.id,
|
||||
app: 'management',
|
||||
path: 'kibana/objects/savedVisualizations/' + $route.current.params.id,
|
||||
},
|
||||
'index-pattern': {
|
||||
app: 'kibana',
|
||||
path:
|
||||
'#/management/kibana/objects/savedVisualizations/' +
|
||||
$route.current.params.id,
|
||||
app: 'management',
|
||||
path: 'kibana/objects/savedVisualizations/' + $route.current.params.id,
|
||||
},
|
||||
'index-pattern-field': {
|
||||
app: 'kibana',
|
||||
path:
|
||||
'#/management/kibana/objects/savedVisualizations/' +
|
||||
$route.current.params.id,
|
||||
app: 'management',
|
||||
path: 'kibana/objects/savedVisualizations/' + $route.current.params.id,
|
||||
},
|
||||
},
|
||||
toastNotifications,
|
||||
|
|
|
@ -285,7 +285,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
'/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357',
|
||||
inAppUrl: {
|
||||
path:
|
||||
'/app/kibana#/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357',
|
||||
'/app/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357',
|
||||
uiCapabilitiesPath: 'management.kibana.index_patterns',
|
||||
},
|
||||
});
|
||||
|
|
|
@ -86,7 +86,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
'/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357',
|
||||
inAppUrl: {
|
||||
path:
|
||||
'/app/kibana#/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357',
|
||||
'/app/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357',
|
||||
uiCapabilitiesPath: 'management.kibana.index_patterns',
|
||||
},
|
||||
},
|
||||
|
@ -127,7 +127,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
'/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357',
|
||||
inAppUrl: {
|
||||
path:
|
||||
'/app/kibana#/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357',
|
||||
'/app/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357',
|
||||
uiCapabilitiesPath: 'management.kibana.index_patterns',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -71,7 +71,7 @@ export async function createTestUserService(
|
|||
}
|
||||
}
|
||||
|
||||
async setRoles(roles: string[]) {
|
||||
async setRoles(roles: string[], shouldRefreshBrowser: boolean = true) {
|
||||
if (isEnabled()) {
|
||||
log.debug(`set roles = ${roles}`);
|
||||
await user.create('test_user', {
|
||||
|
@ -80,7 +80,7 @@ export async function createTestUserService(
|
|||
full_name: 'test user',
|
||||
});
|
||||
|
||||
if (browser && testSubjects) {
|
||||
if (browser && testSubjects && shouldRefreshBrowser) {
|
||||
if (await testSubjects.exists('kibanaChrome', { allowHidden: true })) {
|
||||
await browser.refresh();
|
||||
await testSubjects.find('kibanaChrome', config.get('timeouts.find') * 10);
|
||||
|
|
|
@ -82,9 +82,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
let objects = await PageObjects.settings.getSavedObjectsInTable();
|
||||
expect(objects.includes('A Dashboard')).to.be(true);
|
||||
|
||||
await PageObjects.common.navigateToActualUrl(
|
||||
'kibana',
|
||||
'/management/kibana/objects/savedDashboards/i-exist'
|
||||
await PageObjects.common.navigateToUrl(
|
||||
'management',
|
||||
'kibana/objects/savedDashboards/i-exist',
|
||||
{
|
||||
shouldUseHashForSubUrl: false,
|
||||
}
|
||||
);
|
||||
|
||||
await testSubjects.existOrFail('savedObjectEditSave');
|
||||
|
@ -100,9 +103,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
expect(objects.includes('A Dashboard')).to.be(false);
|
||||
expect(objects.includes('Edited Dashboard')).to.be(true);
|
||||
|
||||
await PageObjects.common.navigateToActualUrl(
|
||||
'kibana',
|
||||
'/management/kibana/objects/savedDashboards/i-exist'
|
||||
await PageObjects.common.navigateToUrl(
|
||||
'management',
|
||||
'kibana/objects/savedDashboards/i-exist',
|
||||
{
|
||||
shouldUseHashForSubUrl: false,
|
||||
}
|
||||
);
|
||||
|
||||
expect(await getFieldValue('title')).to.eql('Edited Dashboard');
|
||||
|
@ -110,9 +116,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('allows to delete a saved object', async () => {
|
||||
await PageObjects.common.navigateToActualUrl(
|
||||
'kibana',
|
||||
'/management/kibana/objects/savedDashboards/i-exist'
|
||||
await PageObjects.common.navigateToUrl(
|
||||
'management',
|
||||
'kibana/objects/savedDashboards/i-exist',
|
||||
{
|
||||
shouldUseHashForSubUrl: false,
|
||||
}
|
||||
);
|
||||
|
||||
await focusAndClickButton('savedObjectEditDelete');
|
||||
|
@ -124,7 +133,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
|
||||
it('preserves the object references when saving', async () => {
|
||||
const testVisualizationUrl =
|
||||
'/management/kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed';
|
||||
'kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed';
|
||||
const visualizationRefs = [
|
||||
{
|
||||
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
|
@ -139,7 +148,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
const objects = await PageObjects.settings.getSavedObjectsInTable();
|
||||
expect(objects.includes('A Pie')).to.be(true);
|
||||
|
||||
await PageObjects.common.navigateToActualUrl('kibana', testVisualizationUrl);
|
||||
await PageObjects.common.navigateToUrl('management', testVisualizationUrl, {
|
||||
shouldUseHashForSubUrl: false,
|
||||
});
|
||||
|
||||
await testSubjects.existOrFail('savedObjectEditSave');
|
||||
|
||||
|
@ -151,7 +162,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
|
||||
await PageObjects.settings.getSavedObjectsInTable();
|
||||
|
||||
await PageObjects.common.navigateToActualUrl('kibana', testVisualizationUrl);
|
||||
await PageObjects.common.navigateToUrl('management', testVisualizationUrl, {
|
||||
shouldUseHashForSubUrl: false,
|
||||
});
|
||||
|
||||
// Parsing to avoid random keys ordering issues in raw string comparison
|
||||
expect(JSON.parse(await getAceEditorFieldValue('references'))).to.eql(visualizationRefs);
|
||||
|
@ -162,7 +175,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
|
||||
await PageObjects.settings.getSavedObjectsInTable();
|
||||
|
||||
await PageObjects.common.navigateToActualUrl('kibana', testVisualizationUrl);
|
||||
await PageObjects.common.navigateToUrl('management', testVisualizationUrl, {
|
||||
shouldUseHashForSubUrl: false,
|
||||
});
|
||||
|
||||
displayedReferencesValue = await getAceEditorFieldValue('references');
|
||||
|
||||
|
|
|
@ -83,9 +83,12 @@ export default async function ({ readConfigFile }) {
|
|||
pathname: '/app/dashboards',
|
||||
hash: '/list',
|
||||
},
|
||||
management: {
|
||||
pathname: '/app/management',
|
||||
},
|
||||
/** @obsolete "management" should be instead of "settings" **/
|
||||
settings: {
|
||||
pathname: '/app/kibana',
|
||||
hash: '/management',
|
||||
pathname: '/app/management',
|
||||
},
|
||||
timelion: {
|
||||
pathname: '/app/timelion',
|
||||
|
|
|
@ -147,13 +147,19 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo
|
|||
shouldLoginIfPrompted = true,
|
||||
useActualUrl = false,
|
||||
insertTimestamp = true,
|
||||
shouldUseHashForSubUrl = true,
|
||||
} = {}
|
||||
) {
|
||||
const appConfig = {
|
||||
const appConfig: { pathname: string; hash?: string } = {
|
||||
pathname: `${basePath}${config.get(['apps', appName]).pathname}`,
|
||||
hash: useActualUrl ? subUrl : `/${appName}/${subUrl}`,
|
||||
};
|
||||
|
||||
if (shouldUseHashForSubUrl) {
|
||||
appConfig.hash = useActualUrl ? subUrl : `/${appName}/${subUrl}`;
|
||||
} else {
|
||||
appConfig.pathname += `/${subUrl}`;
|
||||
}
|
||||
|
||||
await this.navigate({
|
||||
appConfig,
|
||||
ensureCurrentUrl,
|
||||
|
|
|
@ -300,7 +300,9 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider
|
|||
|
||||
async getIndexPatternList() {
|
||||
await testSubjects.existOrFail('indexPatternTable', { timeout: 5000 });
|
||||
return await find.allByCssSelector('[data-test-subj="indexPatternTable"] .euiTable a');
|
||||
return await find.allByCssSelector(
|
||||
'[data-test-subj="indexPatternTable"] .euiTable .euiTableRow'
|
||||
);
|
||||
}
|
||||
|
||||
async isIndexPatternListEmpty() {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { HashRouter as Router, Switch, Route, Link } from 'react-router-dom';
|
||||
import { Router, Switch, Route, Link } from 'react-router-dom';
|
||||
import { CoreSetup, Plugin } from 'kibana/public';
|
||||
import { ManagementSetup, ManagementSectionId } from '../../../../../src/plugins/management/public';
|
||||
|
||||
|
@ -34,19 +34,19 @@ export class ManagementTestPlugin
|
|||
mount(params: any) {
|
||||
params.setBreadcrumbs([{ text: 'Management Test' }]);
|
||||
ReactDOM.render(
|
||||
<Router>
|
||||
<Router history={params.history}>
|
||||
<h1 data-test-subj="test-management-header">Hello from management test plugin</h1>
|
||||
<Switch>
|
||||
<Route exact path={`${params.basePath}`}>
|
||||
<Link to={`${params.basePath}/one`} data-test-subj="test-management-link-one">
|
||||
Link to /one
|
||||
</Link>
|
||||
</Route>
|
||||
<Route path={`${params.basePath}/one`}>
|
||||
<Route path={'/one'}>
|
||||
<Link to={`${params.basePath}`} data-test-subj="test-management-link-basepath">
|
||||
Link to basePath
|
||||
</Link>
|
||||
</Route>
|
||||
<Route path={'/'}>
|
||||
<Link to={'/one'} data-test-subj="test-management-link-one">
|
||||
Link to /one
|
||||
</Link>
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>,
|
||||
params.element
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue