diff --git a/src/core/server/ui_settings/saved_objects/ui_settings.ts b/src/core/server/ui_settings/saved_objects/ui_settings.ts index 1bea65ddee924..0eab40a7b3a5d 100644 --- a/src/core/server/ui_settings/saved_objects/ui_settings.ts +++ b/src/core/server/ui_settings/saved_objects/ui_settings.ts @@ -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', }; }, diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index ef56ae0e2380c..ae613e0e80904 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -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'); diff --git a/src/legacy/core_plugins/kibana/public/.eslintrc.js b/src/legacy/core_plugins/kibana/public/.eslintrc.js deleted file mode 100644 index 1153706eb8566..0000000000000 --- a/src/legacy/core_plugins/kibana/public/.eslintrc.js +++ /dev/null @@ -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']) - ), - }, - ], - }, -}; diff --git a/src/legacy/core_plugins/kibana/public/_hacks.scss b/src/legacy/core_plugins/kibana/public/_hacks.scss deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 0bf74edc77cb6..51dedcc629c76 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -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'}`, }); diff --git a/src/legacy/core_plugins/kibana/public/management/_hacks.scss b/src/legacy/core_plugins/kibana/public/management/_hacks.scss deleted file mode 100644 index 59af9c9617a30..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/_hacks.scss +++ /dev/null @@ -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'); -} diff --git a/src/legacy/core_plugins/kibana/public/management/_management_app.scss b/src/legacy/core_plugins/kibana/public/management/_management_app.scss deleted file mode 100644 index bd3cabbc574d3..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/_management_app.scss +++ /dev/null @@ -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; - } - } -} diff --git a/src/legacy/core_plugins/kibana/public/management/app.html b/src/legacy/core_plugins/kibana/public/management/app.html deleted file mode 100644 index 11198c02960c7..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/app.html +++ /dev/null @@ -1,4 +0,0 @@ -
-
-
-
diff --git a/src/legacy/core_plugins/kibana/public/management/index.js b/src/legacy/core_plugins/kibana/public/management/index.js deleted file mode 100644 index 48f0e2517a486..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/index.js +++ /dev/null @@ -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( - - -
-
- - - -

- -

-
- - - -
- - - - -

- -

-
-
-
-
, - node - ); -} - -export function updateSidebar(legacySections, id) { - const node = document.getElementById(SIDENAV_ID); - if (!node) { - return; - } - - render( - - - , - 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; - }, - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/management/index.scss b/src/legacy/core_plugins/kibana/public/management/index.scss index 123580c0b7907..fb267b714f1c9 100644 --- a/src/legacy/core_plugins/kibana/public/management/index.scss +++ b/src/legacy/core_plugins/kibana/public/management/index.scss @@ -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'; diff --git a/src/legacy/core_plugins/kibana/public/management/landing.html b/src/legacy/core_plugins/kibana/public/management/landing.html deleted file mode 100644 index 39459b26f7415..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/landing.html +++ /dev/null @@ -1,3 +0,0 @@ - -
-
diff --git a/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js b/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js index 64676b1bce75c..4df0e7a140205 100644 --- a/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js +++ b/src/legacy/core_plugins/kibana/server/lib/__tests__/relationships.js @@ -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', }, }, diff --git a/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx b/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx index df44ea45e9d01..b4779d051ab02 100644 --- a/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx +++ b/src/plugins/advanced_settings/public/management_app/mount_management_section.tsx @@ -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( - + - + , params.element ); diff --git a/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx b/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx index 9115e523f5302..2088bd8c925df 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx +++ b/src/plugins/data/public/index_patterns/index_patterns/ensure_default_index_pattern.tsx @@ -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 diff --git a/src/plugins/data/public/search/long_query_notification.tsx b/src/plugins/data/public/search/long_query_notification.tsx index 0bdf8ab7c66f8..1db298618fae8 100644 --- a/src/plugins/data/public/search/long_query_notification.tsx +++ b/src/plugins/data/public/search/long_query_notification.tsx @@ -44,7 +44,7 @@ export function LongQueryNotification(props: Props) { { - await props.application.navigateToApp('kibana#/management/stack/license_management'); + await props.application.navigateToApp('management/stack/license_management'); }} > { 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, diff --git a/src/plugins/discover/public/application/components/top_nav/__snapshots__/open_search_panel.test.js.snap b/src/plugins/discover/public/application/components/top_nav/__snapshots__/open_search_panel.test.js.snap index 3204252c808ac..42cd8613b1de0 100644 --- a/src/plugins/discover/public/application/components/top_nav/__snapshots__/open_search_panel.test.js.snap +++ b/src/plugins/discover/public/application/components/top_nav/__snapshots__/open_search_panel.test.js.snap @@ -53,7 +53,7 @@ exports[`render 1`] = ` > { return { getServices: () => ({ core: { uiSettings: {}, savedObjects: {} }, + addBasePath: (path) => path, }), }; }); diff --git a/src/plugins/home/public/application/components/__snapshots__/add_data.test.js.snap b/src/plugins/home/public/application/components/__snapshots__/add_data.test.js.snap index 924de9bbd0994..3b3f86e579f1a 100644 --- a/src/plugins/home/public/application/components/__snapshots__/add_data.test.js.snap +++ b/src/plugins/home/public/application/components/__snapshots__/add_data.test.js.snap @@ -277,7 +277,7 @@ exports[`apmUiEnabled 1`] = ` /> { const basePath = getServices().getBasePath(); + const renderCards = () => { const apmData = { title: intl.formatMessage({ @@ -296,7 +297,7 @@ const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => { { id="home.dataManagementDisableCollection" defaultMessage=" To stop collection, " /> - + { id="home.dataManagementEnableCollection" defaultMessage=" To start collection, " /> - + { - 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) { diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx index 11fdae39aee3c..a0d6a43d6f776 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx @@ -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( diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.tsx index dc48f61d1aa65..e432b9b466367 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.tsx @@ -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) => ( { - history.push(`${indexPatternId}/create-field/`); - }} + {...reactRouterNavigate(history, `patterns/${indexPatternId}/create-field/`)} > ; - } - ) => ( - - {name} - {index.tags && - index.tags.map(({ key: tagKey, name: tagName }) => ( - - {tagName} - - ))} - - ), - 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; + }>; + } + ) => ( + <> + + {name} + + + {index.tags && + index.tags.map(({ key: tagKey, name: tagName }) => ( + + {tagName} + + ))} + + + ), + dataType: 'string' as const, + sortable: ({ sort }: { sort: string }) => sort, + }, + ]; + const createButton = canSave ? ( - + @@ -93,7 +93,7 @@ export async function mountManagementSection( - + , params.element diff --git a/src/plugins/index_pattern_management/public/plugin.ts b/src/plugins/index_pattern_management/public/plugin.ts index ebcd92f25c13b..a98cc05a0a80a 100644 --- a/src/plugins/index_pattern_management/public/plugin.ts +++ b/src/plugins/index_pattern_management/public/plugin.ts @@ -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'); diff --git a/src/plugins/kibana_legacy/public/angular/angular_config.tsx b/src/plugins/kibana_legacy/public/angular/angular_config.tsx index ed91d1726f1ac..fcfe2bc7f86a2 100644 --- a/src/plugins/kibana_legacy/public/angular/angular_config.tsx +++ b/src/plugins/kibana_legacy/public/angular/angular_config.tsx @@ -426,7 +426,11 @@ const $setupUrlOverflowHandling = (newPlatform: CoreStart, isLocalAngular: boole values={{ storeInSessionStorageParam: state:storeInSessionStorage, advancedSettingsLink: ( - + + !!(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)); +}; diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts index 8db1d60f09d72..a8c3aab2202d1 100644 --- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts @@ -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:'')"); }); }); }); diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts index 20816c08c550e..d9149095a2fa2 100644 --- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts @@ -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( * 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( key: string, diff --git a/src/plugins/management/public/__snapshots__/management_app.test.tsx.snap b/src/plugins/management/public/__snapshots__/management_app.test.tsx.snap deleted file mode 100644 index 7f13472ee02ee..0000000000000 --- a/src/plugins/management/public/__snapshots__/management_app.test.tsx.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Management app can mount and unmount 1`] = ` -
-
- Test App - Hello world! -
-
-`; - -exports[`Management app can mount and unmount 2`] = `
`; diff --git a/src/legacy/ui/public/management/index.d.ts b/src/plugins/management/public/application.tsx similarity index 56% rename from src/legacy/ui/public/management/index.d.ts rename to src/plugins/management/public/application.tsx index 529efd36623a3..5d014504b8938 100644 --- a/src/legacy/ui/public/management/index.d.ts +++ b/src/plugins/management/public/application.tsx @@ -17,11 +17,26 @@ * under the License. */ -declare module 'ui/management' { - export const SidebarNav: React.FC; - 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( + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/src/plugins/management/public/components/_index.scss b/src/plugins/management/public/components/_index.scss deleted file mode 100644 index df0ebb48803d9..0000000000000 --- a/src/plugins/management/public/components/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './management_sidebar_nav/index'; diff --git a/src/plugins/management/public/components/index.ts b/src/plugins/management/public/components/index.ts index 2650d23d3c25c..8979809c5245e 100644 --- a/src/plugins/management/public/components/index.ts +++ b/src/plugins/management/public/components/index.ts @@ -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'; diff --git a/src/plugins/management/public/components/management_chrome/index.ts b/src/plugins/management/public/components/landing/index.ts similarity index 93% rename from src/plugins/management/public/components/management_chrome/index.ts rename to src/plugins/management/public/components/landing/index.ts index b82c1af871be7..79a1c2b1145c2 100644 --- a/src/plugins/management/public/components/management_chrome/index.ts +++ b/src/plugins/management/public/components/landing/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { ManagementChrome } from './management_chrome'; +export { ManagementLandingPage } from './landing'; diff --git a/src/plugins/management/public/components/landing/landing.tsx b/src/plugins/management/public/components/landing/landing.tsx new file mode 100644 index 0000000000000..f15374173e5f3 --- /dev/null +++ b/src/plugins/management/public/components/landing/landing.tsx @@ -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 ( + +
+
+ + + +

+ +

+
+ + + +
+ + + + +

+ +

+
+
+
+ ); +}; diff --git a/src/plugins/management/public/components/management_app/index.ts b/src/plugins/management/public/components/management_app/index.ts new file mode 100644 index 0000000000000..83f8ae0159978 --- /dev/null +++ b/src/plugins/management/public/components/management_app/index.ts @@ -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'; diff --git a/src/plugins/management/public/components/management_app/management_app.scss b/src/plugins/management/public/components/management_app/management_app.scss new file mode 100644 index 0000000000000..00b3e51fb53ee --- /dev/null +++ b/src/plugins/management/public/components/management_app/management_app.scss @@ -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; +} diff --git a/src/plugins/management/public/components/management_app/management_app.tsx b/src/plugins/management/public/components/management_app/management_app.tsx new file mode 100644 index 0000000000000..fc5a8924c95d6 --- /dev/null +++ b/src/plugins/management/public/components/management_app/management_app.tsx @@ -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(''); + const [sections, setSections] = useState(); + + 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 ( + + + + + + + ); +}; diff --git a/src/plugins/management/public/components/management_app/management_router.tsx b/src/plugins/management/public/components/management_app/management_router.tsx new file mode 100644 index 0000000000000..3f934fa68c6ba --- /dev/null +++ b/src/plugins/management/public/components/management_app/management_router.tsx @@ -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) => ( + + + + {sections.map((section) => + section + .getAppsEnabled() + .map((app) => ( + ( + + )} + /> + )) + )} + ( + + )} + /> + + + + ) +); diff --git a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts b/src/plugins/management/public/components/management_app_wrapper/index.tsx similarity index 82% rename from src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts rename to src/plugins/management/public/components/management_app_wrapper/index.tsx index 587a372f91555..71546e7ca1342 100644 --- a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts +++ b/src/plugins/management/public/components/management_app_wrapper/index.tsx @@ -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'; diff --git a/src/plugins/management/public/components/management_app_wrapper/management_app_wrapper.tsx b/src/plugins/management/public/components/management_app_wrapper/management_app_wrapper.tsx new file mode 100644 index 0000000000000..02da2a46540c2 --- /dev/null +++ b/src/plugins/management/public/components/management_app_wrapper/management_app_wrapper.tsx @@ -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 { + private unmount?: Unmount; + private mountElementRef = createRef(); + + 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
; + } +} diff --git a/src/plugins/management/public/components/management_chrome/management_chrome.tsx b/src/plugins/management/public/components/management_chrome/management_chrome.tsx deleted file mode 100644 index df844e2208936..0000000000000 --- a/src/plugins/management/public/components/management_chrome/management_chrome.tsx +++ /dev/null @@ -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 { - private container = React.createRef(); - componentDidMount() { - if (this.container.current) { - this.props.onMounted(this.container.current); - } - } - render() { - return ( - - - - - - -
- - - - ); - } -} diff --git a/src/plugins/management/public/management_sections.tsx b/src/plugins/management/public/components/management_sections.tsx similarity index 93% rename from src/plugins/management/public/management_sections.tsx rename to src/plugins/management/public/components/management_sections.tsx index 77e494626a00e..33c3526c4d23b 100644 --- a/src/plugins/management/public/management_sections.tsx +++ b/src/plugins/management/public/components/management_sections.tsx @@ -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) => ( {text} diff --git a/src/plugins/management/public/components/management_sidebar_nav/__snapshots__/management_sidebar_nav.test.ts.snap b/src/plugins/management/public/components/management_sidebar_nav/__snapshots__/management_sidebar_nav.test.ts.snap deleted file mode 100644 index e7225b356ed68..0000000000000 --- a/src/plugins/management/public/components/management_sidebar_nav/__snapshots__/management_sidebar_nav.test.ts.snap +++ /dev/null @@ -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, - }, -] -`; diff --git a/src/plugins/management/public/components/management_sidebar_nav/_index.scss b/src/plugins/management/public/components/management_sidebar_nav/_index.scss deleted file mode 100644 index 0a48807344abd..0000000000000 --- a/src/plugins/management/public/components/management_sidebar_nav/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './sidebar_nav'; \ No newline at end of file diff --git a/src/plugins/management/public/components/management_sidebar_nav/_sidebar_nav.scss b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.scss similarity index 84% rename from src/plugins/management/public/components/management_sidebar_nav/_sidebar_nav.scss rename to src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.scss index 5302bd200de3f..a148c1e141e8d 100644 --- a/src/plugins/management/public/components/management_sidebar_nav/_sidebar_nav.scss +++ b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.scss @@ -1,5 +1,6 @@ .mgtSideBarNav { width: 210px; + margin-right: $euiSize; } @include euiBreakpoint('xs','s') { diff --git a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.test.ts b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.test.ts deleted file mode 100644 index e04e0a7572612..0000000000000 --- a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.test.ts +++ /dev/null @@ -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(); - }); -}); diff --git a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx index 208f577b76996..055dda5ed84a1 100644 --- a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx +++ b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx @@ -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'; - -interface NavApp { - id: string; - name: ReactElement | string; - [key: string]: unknown; - order: number; // only needed while merging platform and legacy -} +import { sortBy } from 'lodash'; -interface NavSection extends NavApp { - items: NavApp[]; -} +import { EuiIcon, EuiSideNav, EuiScreenReaderOnly, EuiSideNavItemType } from '@elastic/eui'; +import { AppMountParameters } from 'kibana/public'; +import { ManagementApp, ManagementSection } from '../../utils'; + +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 managementSectionToNavSection = (section: ManagementSection) => { - const iconType = section.euiIconType - ? section.euiIconType - : section.icon - ? section.icon - : 'empty'; - - return { - icon: , - ...managementSectionOrAppToNav(section), - }; -}; - -const managementAppToNavItem = (selectedId?: string, parentId?: string) => ( - app: ManagementApp -) => ({ - isSelected: selectedId === app.id, - href: `#/management/${parentId}/${app.id}`, - ...managementSectionOrAppToNav(app), +const headerLabel = i18n.translate('management.nav.label', { + defaultMessage: 'Management', }); -const legacySectionToNavSection = (section: LegacySection) => ({ - name: section.display, - id: section.id, - icon: section.icon ? : null, - items: [], - 'data-test-subj': section.id, - // @ts-ignore - order: section.order, +const navMenuLabel = i18n.translate('management.nav.menu', { + defaultMessage: 'Management menu', }); -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); -}; +/** @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>>((acc, section) => { + const apps = sortBy(section.getAppsEnabled(), 'order'); + + if (apps.length) { + acc.push({ + ...createNavItem(section, { + items: appsToNavItems(apps), + }), + }); + } + + return acc; + }, []); + }; -export class ManagementSidebarNav extends React.Component< - ManagementSidebarNavProps, - ManagementSidebarNavState -> { - constructor(props: ManagementSidebarNavProps) { - super(props); - this.state = { - isSideNavOpenOnMobile: false, + const appsToNavItems = (managementApps: ManagementApp[]) => + managementApps.map((app) => ({ + ...createNavItem(app, { + ...reactRouterNavigate(history, app.basePath), + }), + })); + + const createNavItem = ( + item: T, + customParams: Partial> = {} + ) => { + const iconType = item.euiIconType || item.icon; + + return { + id: item.id, + name: item.title, + isSelected: item.id === selectedId, + icon: iconType ? : undefined, + 'data-test-subj': item.id, + ...customParams, }; - } - - public render() { - const HEADER_ID = 'stack-management-nav-header'; - - return ( - <> - -

- {i18n.translate('management.nav.label', { - defaultMessage: 'Management', - })} -

-
- - - ); - } - - private renderMobileTitle() { - return ; - } - - private toggleOpenOnMobile = () => { - this.setState({ - isSideNavOpenOnMobile: !this.state.isSideNavOpenOnMobile, - }); }; -} + + return ( + <> + +

{headerLabel}

+
+ + + ); +}; diff --git a/src/plugins/management/public/index.ts b/src/plugins/management/public/index.ts index f2cc6a00b93d8..3ba469c7831f6 100644 --- a/src/plugins/management/public/index.ts +++ b/src/plugins/management/public/index.ts @@ -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 { - ManagementSetup, - ManagementStart, - RegisterManagementApp, ManagementSectionId, - RegisterManagementAppArgs, ManagementAppMountParams, + ManagementSetup, + ManagementStart, } from './types'; -export { ManagementApp } from './management_app'; -export { ManagementSection } from './management_section'; -export { ManagementSidebarNav } from './components'; // for use in legacy management apps diff --git a/src/plugins/management/public/legacy/redirect_messages.tsx b/src/plugins/management/public/legacy/redirect_messages.tsx deleted file mode 100644 index f8cb975e6fae5..0000000000000 --- a/src/plugins/management/public/legacy/redirect_messages.tsx +++ /dev/null @@ -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; - - if (urlParams.bannerMessage) { - const bannerId = overlays.banners.add( - toMountPoint( - - ) - ); - 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({urlParams.notFoundMessage}), - }); - } -} diff --git a/src/plugins/management/public/legacy/section.js b/src/plugins/management/public/legacy/section.js deleted file mode 100644 index 5b39f350bf444..0000000000000 --- a/src/plugins/management/public/legacy/section.js +++ /dev/null @@ -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; - } -} diff --git a/src/plugins/management/public/legacy/section.test.js b/src/plugins/management/public/legacy/section.test.js deleted file mode 100644 index bf75506a218d5..0000000000000 --- a/src/plugins/management/public/legacy/section.test.js +++ /dev/null @@ -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']); - }); - }); -}); diff --git a/src/plugins/management/public/legacy/sections_register.js b/src/plugins/management/public/legacy/sections_register.js deleted file mode 100644 index d77f87e80ea18..0000000000000 --- a/src/plugins/management/public/legacy/sections_register.js +++ /dev/null @@ -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; -} diff --git a/src/plugins/management/public/management_app.test.tsx b/src/plugins/management/public/management_app.test.tsx deleted file mode 100644 index a76b234d95ef5..0000000000000 --- a/src/plugins/management/public/management_app.test.tsx +++ /dev/null @@ -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(
Test App - Hello world!
, 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); -}); diff --git a/src/plugins/management/public/management_app.tsx b/src/plugins/management/public/management_app.tsx deleted file mode 100644 index 2954cefa86d5c..0000000000000 --- a/src/plugins/management/public/management_app.tsx +++ /dev/null @@ -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( - { - 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; - } -} diff --git a/src/plugins/management/public/management_section.test.ts b/src/plugins/management/public/management_section.test.ts deleted file mode 100644 index e1d047425ac18..0000000000000 --- a/src/plugins/management/public/management_section.test.ts +++ /dev/null @@ -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); -}); diff --git a/src/plugins/management/public/management_section.ts b/src/plugins/management/public/management_section.ts deleted file mode 100644 index 80ef1a108ecd8..0000000000000 --- a/src/plugins/management/public/management_section.ts +++ /dev/null @@ -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); - } -} diff --git a/src/plugins/management/public/management_sections_service.test.ts b/src/plugins/management/public/management_sections_service.test.ts new file mode 100644 index 0000000000000..2c5d04883235c --- /dev/null +++ b/src/plugins/management/public/management_sections_service.test.ts @@ -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); + }); +}); diff --git a/src/plugins/management/public/management_sections_service.ts b/src/plugins/management/public/management_sections_service.ts new file mode 100644 index 0000000000000..08a87b3e89f2b --- /dev/null +++ b/src/plugins/management/public/management_sections_service.ts @@ -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 = 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), + }; + } +} diff --git a/src/plugins/management/public/management_service.test.ts b/src/plugins/management/public/management_service.test.ts deleted file mode 100644 index 1507d6f43619d..0000000000000 --- a/src/plugins/management/public/management_service.test.ts +++ /dev/null @@ -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(); -}); diff --git a/src/plugins/management/public/management_service.ts b/src/plugins/management/public/management_service.ts deleted file mode 100644 index 84939fe095336..0000000000000 --- a/src/plugins/management/public/management_service.ts +++ /dev/null @@ -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, - }; - } -} diff --git a/src/plugins/management/public/mocks/index.ts b/src/plugins/management/public/mocks/index.ts index 3e32ff4fe26b2..123e3f28877aa 100644 --- a/src/plugins/management/public/mocks/index.ts +++ b/src/plugins/management/public/mocks/index.ts @@ -18,29 +18,29 @@ */ import { ManagementSetup, ManagementStart } from '../types'; -import { ManagementSection } from '../management_section'; +import { ManagementSection } from '../index'; -const createManagementSectionMock = (): jest.Mocked> => { - 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 => ({ sections: { + register: jest.fn(), getSection: jest.fn().mockReturnValue(createManagementSectionMock()), - getAllSections: jest.fn().mockReturnValue([]), }, }); const createStartContract = (): DeeplyMockedKeys => ({ - legacy: {}, sections: { getSection: jest.fn(), getAllSections: jest.fn(), - navigateToApp: jest.fn(), + getSectionsEnabled: jest.fn(), }, }); diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index e7f86996a9a1b..71656d7c0b83b 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -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 { - 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 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; +export type Mount = (params: ManagementAppMountParams) => Unmount | Promise; 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; - -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` } diff --git a/src/legacy/ui/public/management/breadcrumbs.ts b/src/plugins/management/public/utils/breadcrumbs.ts similarity index 85% rename from src/legacy/ui/public/management/breadcrumbs.ts rename to src/plugins/management/public/utils/breadcrumbs.ts index 936e99caff565..147d157d29d7f 100644 --- a/src/legacy/ui/public/management/breadcrumbs.ts +++ b/src/plugins/management/public/utils/breadcrumbs.ts @@ -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: '/', +}; diff --git a/src/legacy/ui/public/management/index.js b/src/plugins/management/public/utils/index.ts similarity index 83% rename from src/legacy/ui/public/management/index.js rename to src/plugins/management/public/utils/index.ts index 25d3678c5dbba..04c0c4c6811c7 100644 --- a/src/legacy/ui/public/management/index.js +++ b/src/plugins/management/public/utils/index.ts @@ -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'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/types.ts b/src/plugins/management/public/utils/management_app.ts similarity index 62% rename from src/legacy/core_plugins/kibana/public/management/sections/index_patterns/types.ts rename to src/plugins/management/public/utils/management_app.ts index 81184d6fdd1a3..a27db5522af82 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/types.ts +++ b/src/plugins/management/public/utils/management_app.ts @@ -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; + } } diff --git a/src/plugins/management/public/utils/management_item.ts b/src/plugins/management/public/utils/management_item.ts new file mode 100644 index 0000000000000..ef0c8e4693895 --- /dev/null +++ b/src/plugins/management/public/utils/management_item.ts @@ -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; + } +} diff --git a/src/plugins/management/public/utils/management_section.test.ts b/src/plugins/management/public/utils/management_section.test.ts new file mode 100644 index 0000000000000..f5ce86f5dc963 --- /dev/null +++ b/src/plugins/management/public/utils/management_section.test.ts @@ -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); + }); +}); diff --git a/src/plugins/management/public/utils/management_section.ts b/src/plugins/management/public/utils/management_section.ts new file mode 100644 index 0000000000000..d226825e39d19 --- /dev/null +++ b/src/plugins/management/public/utils/management_section.ts @@ -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) { + 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); + } +} diff --git a/src/plugins/saved_objects_management/public/management_section/mount_section.tsx b/src/plugins/saved_objects_management/public/management_section/mount_section.tsx index c1daf3445219f..9cfe99fd3bbf8 100644 --- a/src/plugins/saved_objects_management/public/management_section/mount_section.tsx +++ b/src/plugins/saved_objects_management/public/management_section/mount_section.tsx @@ -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( - + - + }> - + }> - + , 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; -}; diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx index 83644e6404c81..1572ef9164700 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.tsx @@ -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('/'); } } diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap index 78af61c20c828..26ddb0809d24b 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap @@ -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", diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/relationships.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/relationships.test.tsx.snap index cb2a2dd0e4d1e..6eb9e36394ee3 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/relationships.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/relationships.test.tsx.snap @@ -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", diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx index 5ee70e73c873b..9277d9b00305b 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx @@ -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', diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx index b46bc8dd1b4ee..191bde8b192fd 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx @@ -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', diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx index d9b856a79b496..c24f5d29f3870 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx @@ -457,8 +457,8 @@ export class SavedObjectsTable extends Component 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} /> ); }; diff --git a/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx b/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx index 4e8418d7406b5..75692777f08bb 100644 --- a/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx +++ b/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx @@ -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) => { diff --git a/src/plugins/saved_objects_management/public/plugin.ts b/src/plugins/saved_objects_management/public/plugin.ts index 1d765c70edb97..f3d6318db89f2 100644 --- a/src/plugins/saved_objects_management/public/plugin.ts +++ b/src/plugins/saved_objects_management/public/plugin.ts @@ -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, }); diff --git a/src/plugins/telemetry/common/constants.ts b/src/plugins/telemetry/common/constants.ts index 2e058fdc13e4a..53c79b738f750 100644 --- a/src/plugins/telemetry/common/constants.ts +++ b/src/plugins/telemetry/common/constants.ts @@ -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. diff --git a/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap b/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap index 193205cd394e2..dd774d956dc10 100644 --- a/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap +++ b/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap @@ -10,7 +10,7 @@ exports[`OptInDetailsComponent renders as expected 1`] = ` values={ Object { "disableLink": { - 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'); diff --git a/test/functional/config.js b/test/functional/config.js index 52806e8ca68ea..c377e64f2fc45 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -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', diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index 7810cce5c78bb..91e9c020a0e7c 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -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, diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts index 9cf7a48cb3d88..f5b4eb7ad5de8 100644 --- a/test/functional/page_objects/settings_page.ts +++ b/test/functional/page_objects/settings_page.ts @@ -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() { diff --git a/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx index 96297f6d51566..494570b26f561 100644 --- a/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx +++ b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx @@ -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( - +

Hello from management test plugin

- - - Link to /one - - - + Link to basePath + + + Link to /one + +
, params.element diff --git a/test/plugin_functional/test_suites/core_plugins/applications.ts b/test/plugin_functional/test_suites/core_plugins/applications.ts index 64d27103e59e2..6d31889a9cbe4 100644 --- a/test/plugin_functional/test_suites/core_plugins/applications.ts +++ b/test/plugin_functional/test_suites/core_plugins/applications.ts @@ -135,7 +135,8 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide expect(wrapperHeight).to.be.below(windowHeight); }); - it('can navigate from NP apps to legacy apps', async () => { + // Not sure if we need this test or not. If yes, we need to find another legacy app + it.skip('can navigate from NP apps to legacy apps', async () => { await appsMenu.clickLink('Stack Management'); await testSubjects.existOrFail('managementNav'); }); diff --git a/test/plugin_functional/test_suites/management/management_plugin.js b/test/plugin_functional/test_suites/management/management_plugin.js index 87542c97a3f5d..8d879cfa67d7a 100644 --- a/test/plugin_functional/test_suites/management/management_plugin.js +++ b/test/plugin_functional/test_suites/management/management_plugin.js @@ -23,7 +23,7 @@ export default function ({ getService, getPageObjects }) { describe('management plugin', function describeIndexTests() { before(async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management'); + await PageObjects.common.navigateToActualUrl('management'); }); it('should be able to navigate to management test app', async () => { @@ -38,11 +38,12 @@ export default function ({ getService, getPageObjects }) { }); it('should redirect when app is disabled', async () => { - await PageObjects.common.navigateToActualUrl( - 'kibana', - 'management/data/test-management-disabled' - ); - await testSubjects.existOrFail('management-landing'); + await PageObjects.common.navigateToUrl('management', 'data/test-management-disabled', { + useActualUrl: true, + shouldUseHashForSubUrl: false, + }); + + await testSubjects.existOrFail('managementHome'); }); }); } diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx index bfb9e99b4fc4c..80d5f739bea5a 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx @@ -82,7 +82,7 @@ export function AlertIntegrations(props: Props) { } ), href: plugin.core.http.basePath.prepend( - '/app/kibana#/management/insightsAndAlerting/triggersActions/alerts' + '/app/management/insightsAndAlerting/triggersActions/alerts' ), icon: 'tableOfContents', }, diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx index bcc31a30b154d..321617ed8496a 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx @@ -92,7 +92,7 @@ export class ServiceIntegrations extends React.Component { ), icon: 'watchesApp', href: core.http.basePath.prepend( - '/app/kibana#/management/insightsAndAlerting/watcher' + '/app/management/insightsAndAlerting/watcher' ), target: '_blank', onClick: () => this.closePopover(), diff --git a/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx b/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx index 4033d684da981..481e89e09685e 100644 --- a/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx +++ b/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx @@ -11,7 +11,7 @@ import { useApmPluginContext } from '../../hooks/useApmPluginContext'; export function InvalidLicenseNotification() { const { core } = useApmPluginContext(); const manageLicenseURL = core.http.basePath.prepend( - '/app/kibana#/management/stack/license_management' + '/app/management/stack/license_management' ); return ( diff --git a/x-pack/plugins/beats_management/public/application.tsx b/x-pack/plugins/beats_management/public/application.tsx index 6711e93895b62..0b8b0ad3dafc9 100644 --- a/x-pack/plugins/beats_management/public/application.tsx +++ b/x-pack/plugins/beats_management/public/application.tsx @@ -8,7 +8,7 @@ import * as euiVars from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; import React from 'react'; import ReactDOM from 'react-dom'; -import { HashRouter } from 'react-router-dom'; +import { Router } from 'react-router-dom'; import { ThemeProvider } from 'styled-components'; import { Provider as UnstatedProvider, Subscribe } from 'unstated'; import { Background } from './components/layouts/background'; @@ -19,19 +19,13 @@ import { TagsContainer } from './containers/tags'; import { FrontendLibs } from './lib/types'; import { AppRouter } from './router'; import { services } from './kbn_services'; -import { - ManagementAppMountParams, - ManagementSectionId, -} from '../../../../src/plugins/management/public'; +import { ManagementAppMountParams } from '../../../../src/plugins/management/public'; -export const renderApp = ( - { basePath, element, setBreadcrumbs }: ManagementAppMountParams, - libs: FrontendLibs -) => { +export const renderApp = ({ element, history }: ManagementAppMountParams, libs: FrontendLibs) => { ReactDOM.render( - + @@ -48,7 +42,7 @@ export const renderApp = ( - +
, element diff --git a/x-pack/plugins/cross_cluster_replication/common/constants/index.ts b/x-pack/plugins/cross_cluster_replication/common/constants/index.ts index 96884cf4bead8..0574cb9b2dd6d 100644 --- a/x-pack/plugins/cross_cluster_replication/common/constants/index.ts +++ b/x-pack/plugins/cross_cluster_replication/common/constants/index.ts @@ -24,8 +24,7 @@ export const APPS = { }; export const MANAGEMENT_ID = 'cross_cluster_replication'; -export const BASE_PATH = `/management/data/${MANAGEMENT_ID}`; -export const BASE_PATH_REMOTE_CLUSTERS = '/management/data/remote_clusters'; +export const BASE_PATH_REMOTE_CLUSTERS = 'data/remote_clusters'; export const API_BASE_PATH = '/api/cross_cluster_replication'; export const API_REMOTE_CLUSTERS_BASE_PATH = '/api/remote_clusters'; export const API_INDEX_MANAGEMENT_BASE_PATH = '/api/index_management'; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js index e240b5b304c33..a4edee7268a23 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js @@ -12,7 +12,11 @@ import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: (router) => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + ...router, + getUrlForApp: () => '', + }), }, }; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js index c32f6a54114c7..ea372d534d817 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js @@ -14,7 +14,11 @@ import { AUTO_FOLLOW_PATTERN_EDIT_NAME } from './constants'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: (router) => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + ...router, + getUrlForApp: () => '', + }), // The auto-follow pattern id to fetch is read from the router ":id" param // so we first set it in our initial entries initialEntries: [`/${AUTO_FOLLOW_PATTERN_EDIT_NAME}`], diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js index 87cf6a13ba011..550e178a1c733 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js @@ -12,7 +12,18 @@ import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: (router) => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + ...router, + history: { + ...router.history, + parentHistory: { + createHref: () => '', + push: () => {}, + }, + }, + getUrlForApp: () => '', + }), }, }; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js index 9778b595111a2..31f3844f57fb5 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js @@ -12,7 +12,11 @@ import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: (router) => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + ...router, + getUrlForApp: () => '', + }), }, }; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js index 993cde86ada22..8fc2811e58cdb 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js @@ -14,7 +14,11 @@ import { FOLLOWER_INDEX_EDIT_NAME } from './constants'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: (router) => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + ...router, + getUrlForApp: () => '', + }), // The follower index id to fetch is read from the router ":id" param // so we first set it in our initial entries initialEntries: [`/${FOLLOWER_INDEX_EDIT_NAME}`], diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js index 16a4fa9cd3f3e..65be10b9d272e 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js @@ -12,7 +12,17 @@ import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, memoryRouter: { - onRouter: (router) => (routing.reactRouter = router), + onRouter: (router) => + (routing.reactRouter = { + history: { + ...router.history, + parentHistory: { + createHref: () => '', + push: () => {}, + }, + }, + getUrlForApp: () => '', + }), }, }; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js index 26a0d86cdcbca..e51c444a6b370 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js @@ -5,7 +5,6 @@ */ import { registerTestBed } from '../../../../../../test_utils'; -import { BASE_PATH } from '../../../../common/constants'; import { CrossClusterReplicationHome } from '../../../app/sections/home/home'; import { ccrStore } from '../../../app/store'; import { routing } from '../../../app/services/routing'; @@ -13,8 +12,8 @@ import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, memoryRouter: { - initialEntries: [`${BASE_PATH}/follower_indices`], - componentRoutePath: `${BASE_PATH}/:section`, + initialEntries: [`/follower_indices`], + componentRoutePath: `/:section`, onRouter: (router) => (routing.reactRouter = router), }, }; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/app.tsx b/x-pack/plugins/cross_cluster_replication/public/app/app.tsx index ec349ccd6f2c7..288da20c353d2 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/app.tsx +++ b/x-pack/plugins/cross_cluster_replication/public/app/app.tsx @@ -5,8 +5,8 @@ */ import React, { Component, Fragment } from 'react'; -import { Route, Switch, Redirect, withRouter, RouteComponentProps } from 'react-router-dom'; -import { History } from 'history'; +import { Route, Switch, Router, Redirect } from 'react-router-dom'; +import { ScopedHistory, ApplicationStart } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -20,7 +20,6 @@ import { EuiTitle, } from '@elastic/eui'; -import { BASE_PATH } from '../../common/constants'; import { getFatalErrors } from './services/notifications'; import { SectionError } from './components'; import { routing } from './services/routing'; @@ -37,8 +36,8 @@ import { } from './sections'; interface AppProps { - history: History; - location: any; + history: ScopedHistory; + getUrlForApp: ApplicationStart['getUrlForApp']; } interface AppState { @@ -48,7 +47,7 @@ interface AppState { missingClusterPrivileges: any[]; } -class AppComponent extends Component { +class AppComponent extends Component { constructor(props: any) { super(props); this.registerRouter(); @@ -99,12 +98,13 @@ class AppComponent extends Component { } registerRouter() { - const { history, location } = this.props; + const { history, getUrlForApp } = this.props; routing.reactRouter = { history, route: { - location, + location: history.location, }, + getUrlForApp, }; } @@ -189,30 +189,18 @@ class AppComponent extends Component { } return ( -
+ - - - - - - + + + + + + -
+ ); } } -export const App = withRouter(AppComponent); +export const App = AppComponent; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx index 5474708f313c8..1d8f8bacc8c84 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx @@ -99,7 +99,7 @@ const AutoFollowPatternActionMenuUI: FunctionComponent = ({ }), icon: , onClick: () => { - window.location.hash = routing.getAutoFollowPatternPath(patterns[0].name); + routing.navigate(routing.getAutoFollowPatternPath(patterns[0].name)); }, } : null, diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js index 6e4c019dab85c..101e3df6bf710 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js @@ -10,7 +10,7 @@ import { connect } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiConfirmModal, EuiLink, EuiOverlayMask } from '@elastic/eui'; - +import { reactRouterNavigate } from '../../../../../../src/plugins/kibana_react/public'; import { routing } from '../services/routing'; import { resumeFollowerIndex } from '../store/actions'; import { arrify } from '../../../common/services/utils'; @@ -97,7 +97,13 @@ class FollowerIndexResumeProviderUi extends PureComponent { custom advanced settings, {editLink}." values={{ editLink: ( - + ( @@ -152,10 +151,9 @@ export class RemoteClustersFormField extends PureComponent { {' '} {/* Break out of EuiFormRow's flexbox layout */} {this.errorMessages.noClusterFound()}

{description}

{this.errorMessages.remoteClusterDoesNotExist(name)}

{ +const renderApp = ( + element: Element, + I18nContext: I18nStart['Context'], + history: ScopedHistory, + getUrlForApp: ApplicationStart['getUrlForApp'] +): UnmountCallback => { render( - - - + , element @@ -36,17 +38,21 @@ export async function mountApp({ I18nContext, ELASTIC_WEBSITE_URL, DOC_LINK_VERSION, + history, + getUrlForApp, }: { element: Element; setBreadcrumbs: SetBreadcrumbs; I18nContext: I18nStart['Context']; ELASTIC_WEBSITE_URL: string; DOC_LINK_VERSION: string; + history: ScopedHistory; + getUrlForApp: ApplicationStart['getUrlForApp']; }): Promise { // Import and initialize additional services here instead of in plugin.ts to reduce the size of the // initial bundle as much as possible. initBreadcrumbs(setBreadcrumbs); initDocumentation(`${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/`); - return renderApp(element, I18nContext); + return renderApp(element, I18nContext, history, getUrlForApp); } diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js index 60a6cc79376e5..76fdf6e2fd766 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js @@ -27,7 +27,7 @@ export class AutoFollowPatternAdd extends PureComponent { }; componentDidMount() { - setBreadcrumbs([listBreadcrumb, addBreadcrumb]); + setBreadcrumbs([listBreadcrumb('/auto_follow_patterns'), addBreadcrumb]); } componentWillUnmount() { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js index 387d7817a0357..d89e3adb531ab 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js @@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPageContent, EuiSpacer } from '@elastic/eui'; import { listBreadcrumb, editBreadcrumb, setBreadcrumbs } from '../../services/breadcrumbs'; -import { routing } from '../../services/routing'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; import { AutoFollowPatternForm, AutoFollowPatternPageTitle, @@ -54,7 +54,7 @@ export class AutoFollowPatternEdit extends PureComponent { selectAutoFollowPattern(decodedId); - setBreadcrumbs([listBreadcrumb, editBreadcrumb]); + setBreadcrumbs([listBreadcrumb('/auto_follow_patterns'), editBreadcrumb]); } componentDidUpdate(prevProps, prevState) { @@ -108,7 +108,7 @@ export class AutoFollowPatternEdit extends PureComponent { @@ -122,7 +122,7 @@ export class AutoFollowPatternList extends PureComponent { {isAuthorized && ( (window.location.hash = routing.getAutoFollowPatternPath(name))} + onClick={() => routing.navigate(routing.getAutoFollowPatternPath(name))} data-test-subj="contextMenuEditButton" > diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js index 3f2ed82420ff1..6b2ee01bff8df 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js @@ -31,6 +31,7 @@ import { EuiTitle, } from '@elastic/eui'; +import { routing } from '../../../../../services/routing'; import { AutoFollowPatternIndicesPreview, AutoFollowPatternActionMenu, @@ -296,7 +297,12 @@ export class DetailPanel extends Component { - + { - const uri = routing.getFollowerIndexPath(id, '/edit', false); + const uri = routing.getFollowerIndexPath(id); routing.navigate(uri); }; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js index 4436d76643e6c..a133c10b148aa 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js @@ -32,6 +32,7 @@ import { import 'brace/theme/textmate'; import { getIndexListUri } from '../../../../../../../../../plugins/index_management/public'; +import { routing } from '../../../../../services/routing'; import { API_STATUS } from '../../../../../constants'; import { ContextMenu } from '../context_menu'; @@ -452,7 +453,12 @@ export class DetailPanel extends Component { - + { - const uri = routing.getFollowerIndexPath(id, '/edit', false); + const uri = routing.getFollowerIndexPath(id); routing.navigate(uri); }; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js index 7b843d08cefd3..4d4cbbf6825ec 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js @@ -17,7 +17,7 @@ import { EuiSpacer, } from '@elastic/eui'; -import { routing } from '../../../services/routing'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; import { extractQueryParams } from '../../../services/query_params'; import { trackUiMetric, METRIC_TYPE } from '../../../services/track_ui_metric'; import { API_STATUS, UIM_FOLLOWER_INDEX_LIST_LOAD } from '../../../constants'; @@ -94,7 +94,7 @@ export class FollowerIndicesList extends PureComponent { } renderHeader() { - const { isAuthorized } = this.props; + const { isAuthorized, history } = this.props; return ( @@ -113,7 +113,7 @@ export class FollowerIndicesList extends PureComponent { {isAuthorized && ( { + setBreadcrumbs([listBreadcrumb(`/${section}`)]); routing.navigate(`/${section}`); }; @@ -94,12 +94,8 @@ export class CrossClusterReplicationHome extends PureComponent { - - + + diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/breadcrumbs.ts b/x-pack/plugins/cross_cluster_replication/public/app/services/breadcrumbs.ts index 84ac9356462ad..c3ca893e6182b 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/breadcrumbs.ts +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/breadcrumbs.ts @@ -9,8 +9,6 @@ import { ChromeBreadcrumb } from 'src/core/public'; import { ManagementAppMountParams } from '../../../../../../src/plugins/management/public'; -import { BASE_PATH } from '../../../common/constants'; - export type SetBreadcrumbs = ManagementAppMountParams['setBreadcrumbs']; let setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; @@ -19,11 +17,13 @@ export const init = (_setBreadcrumbs: SetBreadcrumbs): void => { setBreadcrumbs = _setBreadcrumbs; }; -export const listBreadcrumb = { - text: i18n.translate('xpack.crossClusterReplication.homeBreadcrumbTitle', { - defaultMessage: 'Cross-Cluster Replication', - }), - href: `#${BASE_PATH}`, +export const listBreadcrumb = (section?: string) => { + return { + text: i18n.translate('xpack.crossClusterReplication.homeBreadcrumbTitle', { + defaultMessage: 'Cross-Cluster Replication', + }), + href: section || '/', + }; }; export const addBreadcrumb = { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/routing.js b/x-pack/plugins/cross_cluster_replication/public/app/services/routing.js index 1a488cc951c49..59210272534b9 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/routing.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/routing.js @@ -8,14 +8,8 @@ * This file based on guidance from https://github.com/elastic/eui/blob/master/wiki/react-router.md */ -import { createLocation } from 'history'; import { stringify } from 'query-string'; -import { APPS, BASE_PATH, BASE_PATH_REMOTE_CLUSTERS } from '../../../common/constants'; - -const isModifiedEvent = (event) => - !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); - -const isLeftClickEvent = (event) => event.button === 0; +import { BASE_PATH_REMOTE_CLUSTERS } from '../../../common/constants'; const queryParamsFromObject = (params, encodeParams = false) => { if (!params) { @@ -26,67 +20,31 @@ const queryParamsFromObject = (params, encodeParams = false) => { return `?${paramsStr}`; }; -const appToBasePathMap = { - [APPS.CCR_APP]: BASE_PATH, - [APPS.REMOTE_CLUSTER_APP]: BASE_PATH_REMOTE_CLUSTERS, -}; - class Routing { _reactRouter = null; - /** - * The logic for generating hrefs and onClick handlers from the `to` prop is largely borrowed from - * https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/Link.js. - * - * @param {*} to URL to navigate to - */ - getRouterLinkProps(to, base = BASE_PATH, params = {}, encodeParams = false) { + getHrefToRemoteClusters(route = '/', params, encodeParams = false) { const search = queryParamsFromObject(params, encodeParams) || ''; - const location = - typeof to === 'string' - ? createLocation(base + to + search, null, null, this._reactRouter.history.location) - : to; - const href = this._reactRouter.history.createHref(location); - - const onClick = (event) => { - if (event.defaultPrevented) { - return; - } - - // If target prop is set (e.g. to "_blank"), let browser handle link. - if (event.target.getAttribute('target')) { - return; - } - - if (isModifiedEvent(event) || !isLeftClickEvent(event)) { - return; - } - - // Prevent regular link behavior, which causes a browser refresh. - event.preventDefault(); - this._reactRouter.history.push(location); - }; - - return { href, onClick }; + return this._reactRouter.getUrlForApp('management', { + path: `${BASE_PATH_REMOTE_CLUSTERS}${route}${search}`, + }); } - navigate(route = '/home', app = APPS.CCR_APP, params, encodeParams = false) { + navigate(route = '/home', params, encodeParams = false) { const search = queryParamsFromObject(params, encodeParams); this._reactRouter.history.push({ - pathname: encodeURI(appToBasePathMap[app] + route), + pathname: encodeURI(route), search, }); } getAutoFollowPatternPath = (name, section = '/edit') => { - return encodeURI(`#${BASE_PATH}/auto_follow_patterns${section}/${encodeURIComponent(name)}`); + return encodeURI(`/auto_follow_patterns${section}/${encodeURIComponent(name)}`); }; - getFollowerIndexPath = (name, section = '/edit', withBase = true) => { - return withBase - ? encodeURI(`#${BASE_PATH}/follower_indices${section}/${encodeURIComponent(name)}`) - : encodeURI(`/follower_indices${section}/${encodeURIComponent(name)}`); + getFollowerIndexPath = (name, section = '/edit') => { + return encodeURI(`/follower_indices${section}/${encodeURIComponent(name)}`); }; get reactRouter() { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js index ea6801b55458d..6503333924951 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js @@ -76,7 +76,7 @@ export const saveAutoFollowPattern = (id, autoFollowPattern, isUpdating = false) ); getToasts().addSuccess(successMessage); - routing.navigate(`/auto_follow_patterns`, undefined, { + routing.navigate(`/auto_follow_patterns`, { pattern: encodeURIComponent(id), }); }, diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js index 61d0ed1d51c72..1af5a95a29b98 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js @@ -77,7 +77,7 @@ export const saveFollowerIndex = (name, followerIndex, isUpdating = false) => ); getToasts().addSuccess(successMessage); - routing.navigate(`/follower_indices`, undefined, { + routing.navigate(`/follower_indices`, { name: encodeURIComponent(name), }); }, diff --git a/x-pack/plugins/cross_cluster_replication/public/plugin.ts b/x-pack/plugins/cross_cluster_replication/public/plugin.ts index e748822ab8ae7..8bf0d519e685d 100644 --- a/x-pack/plugins/cross_cluster_replication/public/plugin.ts +++ b/x-pack/plugins/cross_cluster_replication/public/plugin.ts @@ -41,13 +41,14 @@ export class CrossClusterReplicationPlugin implements Plugin { id: MANAGEMENT_ID, title: PLUGIN.TITLE, order: 6, - mount: async ({ element, setBreadcrumbs }) => { + mount: async ({ element, setBreadcrumbs, history }) => { const { mountApp } = await import('./app'); const [coreStart] = await getStartServices(); const { i18n: { Context: I18nContext }, docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, + application: { getUrlForApp }, } = coreStart; return mountApp({ @@ -56,12 +57,14 @@ export class CrossClusterReplicationPlugin implements Plugin { I18nContext, ELASTIC_WEBSITE_URL, DOC_LINK_VERSION, + history, + getUrlForApp, }); }, }); - ccrApp.disable(); - + // NOTE: We enable the plugin by default instead of disabling it by default because this + // creates a race condition that causes functional tests to fail on CI (see #66781). licensing.license$ .pipe(first()) .toPromise() @@ -76,8 +79,6 @@ export class CrossClusterReplicationPlugin implements Plugin { const isCcrUiEnabled = config.ui.enabled && remoteClusters.isUiEnabled; if (isLicenseOk && isCcrUiEnabled) { - ccrApp.enable(); - if (indexManagement) { const propertyPath = 'isFollowerIndex'; @@ -94,6 +95,8 @@ export class CrossClusterReplicationPlugin implements Plugin { indexManagement.extensionsService.addBadge(followerBadgeExtension); } + } else { + ccrApp.disable(); } }); } diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap index d64c8c6239fcd..9441ffd731524 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap +++ b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap @@ -92,6 +92,7 @@ Array [ exports[`extend index management ilm summary extension should return extension when index has lifecycle error 1`] = `
testy @@ -564,6 +565,7 @@ exports[`extend index management ilm summary extension should return extension w exports[`extend index management ilm summary extension should return extension when index has lifecycle policy 1`] = ` testy diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap b/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap index 857a63826505e..5edc5a9343fc3 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap +++ b/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap @@ -91,11 +91,10 @@ exports[`policy table should show empty state when there are not any policies 1`
{ store = indexLifecycleManagementStore(); component = ( - + {}} /> ); store.dispatch(fetchedPolicies(policies)); @@ -90,7 +91,7 @@ describe('policy table', () => { store = indexLifecycleManagementStore(); component = ( - + {}} /> ); const rendered = mountWithIntl(component); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.js b/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.js index b5d9b91e8c412..4fa1838115840 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.js +++ b/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.js @@ -115,6 +115,10 @@ const indexWithLifecycleError = { moment.tz.setDefault('utc'); +const getUrlForApp = (appId, options) => { + return appId + '/' + (options ? options.path : ''); +}; + describe('extend index management', () => { describe('retry lifecycle action extension', () => { test('should return null when no indices have index lifecycle policy', () => { @@ -171,13 +175,17 @@ describe('extend index management', () => { describe('add lifecycle policy action extension', () => { test('should return null when index has index lifecycle policy', () => { - const extension = addLifecyclePolicyActionExtension({ indices: [indexWithLifecyclePolicy] }); + const extension = addLifecyclePolicyActionExtension( + { indices: [indexWithLifecyclePolicy] }, + getUrlForApp + ); expect(extension).toBeNull(); }); test('should return null when more than one index is passed', () => { const extension = addLifecyclePolicyActionExtension({ indices: [indexWithoutLifecyclePolicy, indexWithoutLifecyclePolicy], + getUrlForApp, }); expect(extension).toBeNull(); }); @@ -185,6 +193,7 @@ describe('extend index management', () => { test('should return extension when one index is passed and it does not have lifecycle policy', () => { const extension = addLifecyclePolicyActionExtension({ indices: [indexWithoutLifecyclePolicy], + getUrlForApp, }); expect(extension.renderConfirmModal).toBeDefined; const component = extension.renderConfirmModal(jest.fn()); @@ -220,20 +229,20 @@ describe('extend index management', () => { describe('ilm summary extension', () => { test('should render null when index has no index lifecycle policy', () => { - const extension = ilmSummaryExtension(indexWithoutLifecyclePolicy); + const extension = ilmSummaryExtension(indexWithoutLifecyclePolicy, getUrlForApp); const rendered = mountWithIntl(extension); expect(rendered.isEmptyRender()).toBeTruthy(); }); test('should return extension when index has lifecycle policy', () => { - const extension = ilmSummaryExtension(indexWithLifecyclePolicy); + const extension = ilmSummaryExtension(indexWithLifecyclePolicy, getUrlForApp); expect(extension).toBeDefined(); const rendered = mountWithIntl(extension); expect(rendered).toMatchSnapshot(); }); test('should return extension when index has lifecycle error', () => { - const extension = ilmSummaryExtension(indexWithLifecycleError); + const extension = ilmSummaryExtension(indexWithLifecycleError, getUrlForApp); expect(extension).toBeDefined(); const rendered = mountWithIntl(extension); expect(rendered).toMatchSnapshot(); diff --git a/x-pack/plugins/index_lifecycle_management/common/constants/index.ts b/x-pack/plugins/index_lifecycle_management/common/constants/index.ts index 59e623934c60d..5c89b917163d8 100644 --- a/x-pack/plugins/index_lifecycle_management/common/constants/index.ts +++ b/x-pack/plugins/index_lifecycle_management/common/constants/index.ts @@ -17,6 +17,4 @@ export const PLUGIN = { }), }; -export const BASE_PATH = '/management/data/index_lifecycle_management/'; - export const API_BASE_PATH = '/api/index_lifecycle_management'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/app.tsx b/x-pack/plugins/index_lifecycle_management/public/application/app.tsx index 993dced20bbe6..11cd5d181f4ad 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/app.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/app.tsx @@ -5,25 +5,35 @@ */ import React, { useEffect } from 'react'; -import { HashRouter, Switch, Route, Redirect } from 'react-router-dom'; +import { Router, Switch, Route, Redirect } from 'react-router-dom'; +import { ScopedHistory, ApplicationStart } from 'kibana/public'; import { METRIC_TYPE } from '@kbn/analytics'; -import { BASE_PATH } from '../../common/constants'; import { UIM_APP_LOAD } from './constants'; import { EditPolicy } from './sections/edit_policy'; import { PolicyTable } from './sections/policy_table'; import { trackUiMetric } from './services/ui_metric'; -export const App = () => { +export const App = ({ + history, + navigateToApp, +}: { + history: ScopedHistory; + navigateToApp: ApplicationStart['navigateToApp']; +}) => { useEffect(() => trackUiMetric(METRIC_TYPE.LOADED, UIM_APP_LOAD), []); return ( - + - - - + + } + /> + - + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx index a7d88d31e58fc..eddbb5528ad84 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/index.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/index.tsx @@ -7,17 +7,22 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Provider } from 'react-redux'; -import { I18nStart } from 'kibana/public'; +import { I18nStart, ScopedHistory, ApplicationStart } from 'kibana/public'; import { UnmountCallback } from 'src/core/public'; import { App } from './app'; import { indexLifecycleManagementStore } from './store'; -export const renderApp = (element: Element, I18nContext: I18nStart['Context']): UnmountCallback => { +export const renderApp = ( + element: Element, + I18nContext: I18nStart['Context'], + history: ScopedHistory, + navigateToApp: ApplicationStart['navigateToApp'] +): UnmountCallback => { render( - + , element diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js index 94186b7fc79d7..998143929afef 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.js @@ -36,7 +36,6 @@ import { } from '../../constants'; import { toasts } from '../../services/notification'; -import { goToPolicyList } from '../../services/navigation'; import { findFirstError } from '../../services/find_errors'; import { LearnMoreLink } from '../components'; import { NodeAttrsDetails } from './components/node_attrs_details'; @@ -100,7 +99,7 @@ export class EditPolicy extends Component { backToPolicyList = () => { this.props.setSelectedPolicy(null); - goToPolicyList(); + this.props.history.push('/policies'); }; submit = async () => { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.js index d9d74becf9e5d..dad259681eb7a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_table/components/policy_table/policy_table.js @@ -36,9 +36,8 @@ import { } from '@elastic/eui'; import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; - +import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public'; import { getIndexListUri } from '../../../../../../../index_management/public'; -import { BASE_PATH } from '../../../../../../common/constants'; import { UIM_EDIT_CLICK } from '../../../../constants'; import { getPolicyPath } from '../../../../services/navigation'; import { flattenPanelTree } from '../../../../services/flatten_panel_tree'; @@ -181,8 +180,9 @@ export class PolicyTable extends Component { /* eslint-disable-next-line @elastic/eui/href-or-on-click */ trackUiMetric('click', UIM_EDIT_CLICK)} + {...reactRouterNavigate(this.props.history, getPolicyPath(value), () => + trackUiMetric('click', UIM_EDIT_CLICK) + )} > {value} @@ -201,7 +201,7 @@ export class PolicyTable extends Component { renderCreatePolicyButton() { return ( { - window.location.hash = getIndexListUri(`ilm.policy:${policy.name}`); + this.props.navigateToApp('management', { + path: `/data/index_management${getIndexListUri(`ilm.policy:${policy.name}`)}`, + }); }, }); } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts index 2d518ebb3015e..72e9d51d8fdeb 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/navigation.ts @@ -4,12 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BASE_PATH } from '../../../common/constants'; - -export const goToPolicyList = () => { - window.location.hash = `${BASE_PATH}policies`; -}; - export const getPolicyPath = (policyName: string): string => { - return encodeURI(`#${BASE_PATH}policies/edit/${encodeURIComponent(policyName)}`); + return encodeURI(`/policies/edit/${encodeURIComponent(policyName)}`); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.js b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.js index 110998a7e9354..0bd313c9a9f8d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.js +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/add_lifecycle_confirm_modal.js @@ -23,7 +23,6 @@ import { EuiModalHeaderTitle, } from '@elastic/eui'; -import { BASE_PATH } from '../../../common/constants'; import { loadPolicies, addLifecyclePolicyToIndex } from '../../application/services/api'; import { showApiError } from '../../application/services/api_errors'; import { toasts } from '../../application/services/notification'; @@ -216,7 +215,7 @@ export class AddLifecyclePolicyConfirmModal extends Component { } render() { const { policies } = this.state; - const { indexName, closeModal } = this.props; + const { indexName, closeModal, getUrlForApp } = this.props; const title = (

- + {value}; + content = ( + + {value} + + ); } else { content = value; } diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js index 43f8332f4b6bd..e7afc8f12859c 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js @@ -67,7 +67,7 @@ export const removeLifecyclePolicyActionExtension = ({ indices, reloadIndices }) }; }; -export const addLifecyclePolicyActionExtension = ({ indices, reloadIndices }) => { +export const addLifecyclePolicyActionExtension = ({ indices, reloadIndices, getUrlForApp }) => { if (indices.length !== 1) { return null; } @@ -86,6 +86,7 @@ export const addLifecyclePolicyActionExtension = ({ indices, reloadIndices }) => closeModal={closeModal} index={index} reloadIndices={reloadIndices} + getUrlForApp={getUrlForApp} /> ); }, @@ -123,8 +124,8 @@ export const ilmBannerExtension = (indices) => { }; }; -export const ilmSummaryExtension = (index) => { - return ; +export const ilmSummaryExtension = (index, getUrlForApp) => { + return ; }; export const ilmFilterExtension = (indices) => { diff --git a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx index 8fce57b0e79b0..49856dee47fba 100644 --- a/x-pack/plugins/index_lifecycle_management/public/plugin.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/plugin.tsx @@ -42,11 +42,12 @@ export class IndexLifecycleManagementPlugin { id: PLUGIN.ID, title: PLUGIN.TITLE, order: 2, - mount: async ({ element }) => { + mount: async ({ element, history }) => { const [coreStart] = await getStartServices(); const { i18n: { Context: I18nContext }, docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, + application: { navigateToApp }, } = coreStart; // Initialize additional services. @@ -55,7 +56,7 @@ export class IndexLifecycleManagementPlugin { ); const { renderApp } = await import('./application'); - return renderApp(element, I18nContext); + return renderApp(element, I18nContext, history, navigateToApp); }, }); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts index 57b925b8c6fc1..36d8e1d343b5e 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts @@ -14,7 +14,6 @@ import { nextTick, } from '../../../../../test_utils'; import { IndexManagementHome } from '../../../public/application/sections/home'; // eslint-disable-line @kbn/eslint/no-restricted-paths -import { BASE_PATH } from '../../../common/constants'; import { indexManagementStore } from '../../../public/application/store'; // eslint-disable-line @kbn/eslint/no-restricted-paths import { TemplateDeserialized } from '../../../common'; import { WithAppDependencies, services } from './setup_environment'; @@ -22,8 +21,8 @@ import { WithAppDependencies, services } from './setup_environment'; const testBedConfig: TestBedConfig = { store: () => indexManagementStore(services as any), memoryRouter: { - initialEntries: [`${BASE_PATH}indices?includeHidden=true`], - componentRoutePath: `${BASE_PATH}:section(indices|templates)`, + initialEntries: [`/indices?includeHidden=true`], + componentRoutePath: `/:section(indices|templates)`, }, doMountAsync: true, }; diff --git a/x-pack/plugins/index_management/public/application/app.tsx b/x-pack/plugins/index_management/public/application/app.tsx index 83997dd6ece18..10bbe3ced64da 100644 --- a/x-pack/plugins/index_management/public/application/app.tsx +++ b/x-pack/plugins/index_management/public/application/app.tsx @@ -5,8 +5,9 @@ */ import React, { useEffect } from 'react'; -import { HashRouter, Switch, Route, Redirect } from 'react-router-dom'; -import { BASE_PATH, UIM_APP_LOAD } from '../../common/constants'; +import { Router, Switch, Route, Redirect } from 'react-router-dom'; +import { ScopedHistory } from 'kibana/public'; +import { UIM_APP_LOAD } from '../../common/constants'; import { IndexManagementHome } from './sections/home'; import { TemplateCreate } from './sections/template_create'; import { TemplateClone } from './sections/template_clone'; @@ -14,24 +15,24 @@ import { TemplateEdit } from './sections/template_edit'; import { useServices } from './app_context'; -export const App = () => { +export const App = ({ history }: { history: ScopedHistory }) => { const { uiMetricService } = useServices(); useEffect(() => uiMetricService.trackMetric('loaded', UIM_APP_LOAD), [uiMetricService]); return ( - + - + ); }; // Export this so we can test it with a different router. export const AppWithoutRouter = () => ( - - - - - + + + + + ); diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx index 2bb618ad8efce..84938de416941 100644 --- a/x-pack/plugins/index_management/public/application/app_context.tsx +++ b/x-pack/plugins/index_management/public/application/app_context.tsx @@ -5,6 +5,7 @@ */ import React, { createContext, useContext } from 'react'; +import { ScopedHistory } from 'kibana/public'; import { CoreStart } from '../../../../../src/core/public'; import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/public'; @@ -17,6 +18,7 @@ const AppContext = createContext(undefined); export interface AppDependencies { core: { fatalErrors: CoreStart['fatalErrors']; + getUrlForApp: CoreStart['application']['getUrlForApp']; }; plugins: { usageCollection: UsageCollectionSetup; @@ -27,6 +29,7 @@ export interface AppDependencies { httpService: HttpService; notificationService: NotificationService; }; + history: ScopedHistory; } export const AppContextProvider = ({ diff --git a/x-pack/plugins/index_management/public/application/index.tsx b/x-pack/plugins/index_management/public/application/index.tsx index 5850cb8d42f1a..8da556cc81fcc 100644 --- a/x-pack/plugins/index_management/public/application/index.tsx +++ b/x-pack/plugins/index_management/public/application/index.tsx @@ -24,13 +24,13 @@ export const renderApp = ( const { i18n } = core; const { Context: I18nContext } = i18n; - const { services } = dependencies; + const { services, history } = dependencies; render( - + , diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts index c47b0603dc1c8..e8b6f200fb349 100644 --- a/x-pack/plugins/index_management/public/application/mount_management_section.ts +++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts @@ -30,9 +30,9 @@ export async function mountManagementSection( services: InternalServices, params: ManagementAppMountParams ) { - const { element, setBreadcrumbs } = params; + const { element, setBreadcrumbs, history } = params; const [core] = await coreSetup.getStartServices(); - const { docLinks, fatalErrors } = core; + const { docLinks, fatalErrors, application } = core; breadcrumbService.setup(setBreadcrumbs); documentationService.setup(docLinks); @@ -40,11 +40,13 @@ export async function mountManagementSection( const appDependencies: AppDependencies = { core: { fatalErrors, + getUrlForApp: application.getUrlForApp, }, plugins: { usageCollection, }, services, + history, }; return renderApp(element, { core, dependencies: appDependencies }); diff --git a/x-pack/plugins/index_management/public/application/sections/home/home.tsx b/x-pack/plugins/index_management/public/application/sections/home/home.tsx index 8e8616d24be20..9d4331d742a25 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/home.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/home.tsx @@ -18,7 +18,6 @@ import { EuiTabs, EuiTitle, } from '@elastic/eui'; -import { BASE_PATH } from '../../../../common/constants'; import { documentationService } from '../../services/documentation'; import { IndexList } from './index_list'; import { TemplateList } from './template_list'; @@ -53,7 +52,7 @@ export const IndexManagementHome: React.FunctionComponent { - history.push(`${BASE_PATH}${newSection}`); + history.push(`/${newSection}`); }; useEffect(() => { @@ -107,9 +106,13 @@ export const IndexManagementHome: React.FunctionComponent - - - + + + diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/summary/summary.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/summary/summary.js index e49b3c353931e..2fda71035fb58 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/summary/summary.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/detail_panel/summary/summary.js @@ -54,14 +54,14 @@ const getHeaders = () => { }; export class Summary extends React.PureComponent { - getAdditionalContent(extensionsService) { + getAdditionalContent(extensionsService, getUrlForApp) { const { index } = this.props; const extensions = extensionsService.summaries; return extensions.map((summaryExtension, i) => { return ( - {summaryExtension(index)} + {summaryExtension(index, getUrlForApp)} ); }); @@ -103,9 +103,12 @@ export class Summary extends React.PureComponent { render() { return ( - {({ services }) => { + {({ services, core }) => { const { left, right } = this.buildRows(); - const additionalContent = this.getAdditionalContent(services.extensionsService); + const additionalContent = this.getAdditionalContent( + services.extensionsService, + core.getUrlForApp + ); return ( diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js index effd80c39f0d1..1931884cf7306 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js @@ -46,7 +46,7 @@ export class IndexActionsContextMenu extends Component { confirmAction = (isActionConfirmed) => { this.setState({ isActionConfirmed }); }; - panels({ services: { extensionsService } }) { + panels({ services: { extensionsService }, core: { getUrlForApp } }) { const { closeIndices, openIndices, @@ -214,6 +214,7 @@ export class IndexActionsContextMenu extends Component { const actionExtensionDefinition = actionExtension({ indices, reloadIndices, + getUrlForApp, }); if (actionExtensionDefinition) { const { diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx index bc942b6b3f55b..db0833ea03233 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_list.tsx @@ -7,6 +7,7 @@ import React, { Fragment, useState, useEffect, useMemo } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; +import { ScopedHistory } from 'kibana/public'; import { EuiEmptyPrompt, EuiSpacer, @@ -144,6 +145,7 @@ export const TemplateList: React.FunctionComponent ); diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_table/template_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_table/template_table.tsx index 36ae5104ea092..1c487158c2022 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_table/template_table.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_table/template_table.tsx @@ -8,18 +8,20 @@ import React, { useState, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiInMemoryTable, EuiIcon, EuiButton, EuiLink, EuiBasicTableColumn } from '@elastic/eui'; +import { ScopedHistory } from 'kibana/public'; import { TemplateListItem, IndexTemplateFormatVersion } from '../../../../../../common'; -import { BASE_PATH, UIM_TEMPLATE_SHOW_DETAILS_CLICK } from '../../../../../../common/constants'; +import { UIM_TEMPLATE_SHOW_DETAILS_CLICK } from '../../../../../../common/constants'; import { TemplateDeleteModal } from '../../../../components'; import { useServices } from '../../../../app_context'; -import { getTemplateDetailsLink } from '../../../../services/routing'; import { SendRequestResponse } from '../../../../../shared_imports'; +import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public'; interface Props { templates: TemplateListItem[]; reload: () => Promise; editTemplate: (name: string, formatVersion: IndexTemplateFormatVersion) => void; cloneTemplate: (name: string, formatVersion: IndexTemplateFormatVersion) => void; + history: ScopedHistory; } export const TemplateTable: React.FunctionComponent = ({ @@ -27,6 +29,7 @@ export const TemplateTable: React.FunctionComponent = ({ reload, editTemplate, cloneTemplate, + history, }) => { const { uiMetricService } = useServices(); const [selection, setSelection] = useState([]); @@ -46,9 +49,15 @@ export const TemplateTable: React.FunctionComponent = ({ return ( /* eslint-disable-next-line @elastic/eui/href-or-on-click */ uiMetricService.trackMetric('click', UIM_TEMPLATE_SHOW_DETAILS_CLICK) + )} data-test-subj="templateDetailsLink" - onClick={() => uiMetricService.trackMetric('click', UIM_TEMPLATE_SHOW_DETAILS_CLICK)} > {name} @@ -237,11 +246,11 @@ export const TemplateTable: React.FunctionComponent = ({ /> , { if (filter) { // React router tries to decode url params but it can't because the browser partially // decodes them. So we have to encode both the URL and the filter to get it all to // work correctly for filters with URL unsafe characters in them. - return encodeURI(`#${BASE_PATH}indices/filter/${encodeURIComponent(filter)}`); + return encodeURI(`/indices/filter/${encodeURIComponent(filter)}`); } // If no filter, URI is already safe so no need to encode. - return `#${BASE_PATH}indices`; + return '/indices'; }; export const getILMPolicyPath = (policyName: string) => { - return encodeURI( - `#/management/data/index_lifecycle_management/policies/edit/${encodeURIComponent(policyName)}` - ); + return encodeURI(`/policies/edit/${encodeURIComponent(policyName)}`); }; diff --git a/x-pack/plugins/index_management/public/application/services/routing.ts b/x-pack/plugins/index_management/public/application/services/routing.ts index a6d8f67751cd1..fe118b1181082 100644 --- a/x-pack/plugins/index_management/public/application/services/routing.ts +++ b/x-pack/plugins/index_management/public/application/services/routing.ts @@ -3,11 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { BASE_PATH } from '../../../common/constants'; import { IndexTemplateFormatVersion } from '../../../common'; export const getTemplateListLink = () => { - return `${BASE_PATH}templates`; + return `/templates`; }; // Need to add some additonal encoding/decoding logic to work with React Router @@ -17,22 +16,20 @@ export const getTemplateDetailsLink = ( formatVersion: IndexTemplateFormatVersion, withHash = false ) => { - const baseUrl = `${BASE_PATH}templates/${encodeURIComponent( - encodeURIComponent(name) - )}?v=${formatVersion}`; + const baseUrl = `/templates/${encodeURIComponent(encodeURIComponent(name))}?v=${formatVersion}`; const url = withHash ? `#${baseUrl}` : baseUrl; return encodeURI(url); }; export const getTemplateEditLink = (name: string, formatVersion: IndexTemplateFormatVersion) => { return encodeURI( - `${BASE_PATH}edit_template/${encodeURIComponent(encodeURIComponent(name))}?v=${formatVersion}` + `/edit_template/${encodeURIComponent(encodeURIComponent(name))}?v=${formatVersion}` ); }; export const getTemplateCloneLink = (name: string, formatVersion: IndexTemplateFormatVersion) => { return encodeURI( - `${BASE_PATH}clone_template/${encodeURIComponent(encodeURIComponent(name))}?v=${formatVersion}` + `/clone_template/${encodeURIComponent(encodeURIComponent(name))}?v=${formatVersion}` ); }; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx index bafb38459b17b..52033a00327c0 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx @@ -35,7 +35,7 @@ export const MetricsAlertDropdown = () => { icon="tableOfContents" key="manageLink" href={kibana.services?.application?.getUrlForApp( - 'kibana#/management/insightsAndAlerting/triggersActions/alerts' + 'management/insightsAndAlerting/triggersActions/alerts' )} > diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx b/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx index a3cebcf33f386..c48b5b9a2cc58 100644 --- a/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx @@ -35,7 +35,7 @@ export const InventoryAlertDropdown = () => { icon="tableOfContents" key="manageLink" href={kibana.services?.application?.getUrlForApp( - 'kibana#/management/insightsAndAlerting/triggersActions/alerts' + 'management/insightsAndAlerting/triggersActions/alerts' )} > diff --git a/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx b/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx index d808b4f3b64aa..b8eb73b99f45e 100644 --- a/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx @@ -15,8 +15,8 @@ export const AlertDropdown = () => { const [flyoutVisible, setFlyoutVisible] = useState(false); const manageAlertsLinkProps = useLinkProps( { - app: 'kibana', - hash: 'management/insightsAndAlerting/triggersActions/alerts', + app: 'management', + pathname: '/insightsAndAlerting/triggersActions/alerts', }, { hrefOnly: true, diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx index 3243d665832f2..fa8c4f82c1b68 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx @@ -5,13 +5,15 @@ */ /* eslint-disable @kbn/eslint/no-restricted-paths */ import React from 'react'; - +import { LocationDescriptorObject } from 'history'; +import { ScopedHistory } from 'kibana/public'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { notificationServiceMock, fatalErrorsServiceMock, docLinksServiceMock, injectedMetadataServiceMock, + scopedHistoryMock, } from '../../../../../../src/core/public/mocks'; import { usageCollectionPluginMock } from '../../../../../../src/plugins/usage_collection/public/mocks'; @@ -33,12 +35,18 @@ const httpServiceSetupMock = new HttpService().setup({ fatalErrors: fatalErrorsServiceMock.createSetupContract(), }); +const history = (scopedHistoryMock.create() as unknown) as ScopedHistory; +history.createHref = (location: LocationDescriptorObject) => { + return `${location.pathname}?${location.search}`; +}; + const appServices = { breadcrumbs: breadcrumbService, metric: uiMetricService, documentation: documentationService, api: apiService, notifications: notificationServiceMock.createSetupContract(), + history, }; export const setupEnvironment = () => { diff --git a/x-pack/plugins/ingest_pipelines/common/constants.ts b/x-pack/plugins/ingest_pipelines/common/constants.ts index de291e364e02f..4c6c6fefaad83 100644 --- a/x-pack/plugins/ingest_pipelines/common/constants.ts +++ b/x-pack/plugins/ingest_pipelines/common/constants.ts @@ -11,7 +11,7 @@ export const PLUGIN_ID = 'ingest_pipelines'; export const PLUGIN_MIN_LICENSE_TYPE = basicLicense; -export const BASE_PATH = '/management/ingest/ingest_pipelines'; +export const BASE_PATH = '/'; export const API_BASE_PATH = '/api/ingest_pipelines'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/app.tsx b/x-pack/plugins/ingest_pipelines/public/application/app.tsx index f4ac640722120..55b59caab8d60 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/app.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/app.tsx @@ -6,9 +6,11 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPageContent } from '@elastic/eui'; import React, { FunctionComponent } from 'react'; -import { HashRouter, Switch, Route } from 'react-router-dom'; +import { Router, Switch, Route } from 'react-router-dom'; -import { BASE_PATH, APP_CLUSTER_REQUIRED_PRIVILEGES } from '../../common/constants'; +import { useKibana } from '../shared_imports'; + +import { APP_CLUSTER_REQUIRED_PRIVILEGES } from '../../common/constants'; import { SectionError, @@ -22,10 +24,10 @@ import { PipelinesList, PipelinesCreate, PipelinesEdit, PipelinesClone } from '. export const AppWithoutRouter = () => ( - - - - + + + + {/* Catch all */} @@ -33,6 +35,7 @@ export const AppWithoutRouter = () => ( export const App: FunctionComponent = () => { const { apiError } = useAuthorizationContext(); + const { history } = useKibana().services; if (apiError) { return ( @@ -91,9 +94,9 @@ export const App: FunctionComponent = () => { } return ( - + - + ); }} diff --git a/x-pack/plugins/ingest_pipelines/public/application/index.tsx b/x-pack/plugins/ingest_pipelines/public/application/index.tsx index e43dba4689b44..a8e6febeb2e59 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/index.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/index.tsx @@ -8,6 +8,7 @@ import { HttpSetup } from 'kibana/public'; import React, { ReactNode } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { NotificationsSetup } from 'kibana/public'; +import { ManagementAppMountParams } from 'src/plugins/management/public'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import { API_BASE_PATH } from '../../common/constants'; @@ -23,6 +24,7 @@ export interface AppServices { documentation: DocumentationService; api: ApiService; notifications: NotificationsSetup; + history: ManagementAppMountParams['history']; } export interface CoreServices { diff --git a/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts b/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts index e36f27cbf5f62..49c8f5a7b2e1e 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts @@ -13,7 +13,7 @@ export async function mountManagementSection( { http, getStartServices, notifications }: CoreSetup, params: ManagementAppMountParams ) { - const { element, setBreadcrumbs } = params; + const { element, setBreadcrumbs, history } = params; const [coreStart] = await getStartServices(); const { docLinks, @@ -29,6 +29,7 @@ export async function mountManagementSection( documentation: documentationService, api: apiService, notifications, + history, }; return renderApp(element, I18nContext, services, { http }); diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx index f6fe2f0cf65fa..eba69ff454911 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/empty_list.tsx @@ -6,12 +6,15 @@ import React, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiButton, EuiEmptyPrompt, EuiLink, EuiPageBody, EuiPageContent } from '@elastic/eui'; -import { BASE_PATH } from '../../../../common/constants'; +import { EuiEmptyPrompt, EuiLink, EuiPageBody, EuiPageContent, EuiButton } from '@elastic/eui'; +import { useHistory } from 'react-router-dom'; +import { ScopedHistory } from 'kibana/public'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; import { useKibana } from '../../../shared_imports'; export const EmptyList: FunctionComponent = () => { const { services } = useKibana(); + const history = useHistory() as ScopedHistory; return ( @@ -41,7 +44,7 @@ export const EmptyList: FunctionComponent = () => {

} actions={ - + {i18n.translate('xpack.ingestPipelines.list.table.emptyPrompt.createButtonLabel', { defaultMessage: 'Create a pipeline', })} diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx index 541a2b486b5a7..97775965f9b45 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/table.tsx @@ -13,9 +13,10 @@ import { EuiInMemoryTableProps, EuiTableFieldDataColumnType, } from '@elastic/eui'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; -import { BASE_PATH } from '../../../../common/constants'; import { Pipeline } from '../../../../common/types'; +import { useKibana } from '../../../shared_imports'; export interface Props { pipelines: Pipeline[]; @@ -32,6 +33,7 @@ export const PipelineTable: FunctionComponent = ({ onClonePipelineClick, onDeletePipelineClick, }) => { + const { history } = useKibana().services; const [selection, setSelection] = useState([]); const tableProps: EuiInMemoryTableProps = { @@ -80,14 +82,14 @@ export const PipelineTable: FunctionComponent = ({ })} , {i18n.translate('xpack.ingestPipelines.list.table.createPipelineButtonLabel', { - defaultMessage: 'Create a pipeline', + defaultMessage: 'Create a pipeline here', })} , ], @@ -107,7 +109,10 @@ export const PipelineTable: FunctionComponent = ({ }), sortable: true, render: (name: string) => ( - + {name} ), diff --git a/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts b/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts index 1ccdbbad9b1bb..5fc8e13e3dcad 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/services/breadcrumbs.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; -import { BASE_PATH } from '../../../common/constants'; import { ManagementAppMountParams } from '../../../../../../src/plugins/management/public'; type SetBreadcrumbs = ManagementAppMountParams['setBreadcrumbs']; @@ -28,7 +27,7 @@ export class BreadcrumbService { create: [ { text: homeBreadcrumbText, - href: `#${BASE_PATH}`, + href: `/`, }, { text: i18n.translate('xpack.ingestPipelines.breadcrumb.createPipelineLabel', { @@ -39,7 +38,7 @@ export class BreadcrumbService { edit: [ { text: homeBreadcrumbText, - href: `#${BASE_PATH}`, + href: `/`, }, { text: i18n.translate('xpack.ingestPipelines.breadcrumb.editPipelineLabel', { diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap index e4411807dfa56..28ce3c6c07501 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`AddLicense component when license is active should display correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; +exports[`AddLicense component when license is active should display correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; -exports[`AddLicense component when license is expired should display with correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; +exports[`AddLicense component when license is expired should display with correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap index b621e89efbee3..cc8cbfe679eff 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap @@ -112,6 +112,42 @@ exports[`UploadLicense should display a modal when license requires acknowledgem "refresh": [MockFunction], }, }, + "services": Object { + "history": Object { + "action": "PUSH", + "block": [MockFunction], + "createHref": [MockFunction] { + "calls": Array [ + Array [ + Object { + "pathname": "/home", + }, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": "/home", + }, + ], + }, + "createSubHistory": [MockFunction], + "go": [MockFunction], + "goBack": [MockFunction], + "goForward": [MockFunction], + "length": 1, + "listen": [MockFunction], + "location": Object { + "hash": "", + "key": undefined, + "pathname": "/", + "search": "", + "state": undefined, + }, + "push": [MockFunction], + "replace": [MockFunction], + }, + }, } } > @@ -126,12 +162,85 @@ exports[`UploadLicense should display a modal when license requires acknowledgem } } > - + @@ -1162,12 +1336,139 @@ exports[`UploadLicense should display an error when ES says license is expired 1 } } > - + @@ -1628,12 +1994,139 @@ exports[`UploadLicense should display an error when ES says license is invalid 1 } } > - + @@ -2094,12 +2652,139 @@ exports[`UploadLicense should display an error when submitting invalid JSON 1`] } } > - + @@ -2560,12 +3310,139 @@ exports[`UploadLicense should display error when ES returns error 1`] = ` } } > - + {}; let store: any = null; let component: any = null; +const history = scopedHistoryMock.create(); +history.createHref.mockImplementation((location: LocationDescriptorObject) => { + return `${location.pathname}${location.search ? '?' + location.search : ''}`; +}); const appDependencies = { plugins: { @@ -39,14 +44,15 @@ const appDependencies = { refresh: jest.fn(), }, }, + services: { + history, + }, docLinks: {}, }; const thunkServices = { http: httpServiceMock.createSetupContract(), - history: { - replace: jest.fn(), - }, + history, breadcrumbService: { setBreadcrumbs() {}, }, @@ -59,7 +65,7 @@ describe('UploadLicense', () => { component = ( - + ); diff --git a/x-pack/plugins/license_management/__jest__/util/util.js b/x-pack/plugins/license_management/__jest__/util/util.js index 5a7e49c8c3315..c13dcdb7fdbfa 100644 --- a/x-pack/plugins/license_management/__jest__/util/util.js +++ b/x-pack/plugins/license_management/__jest__/util/util.js @@ -9,14 +9,22 @@ import React from 'react'; import { Provider } from 'react-redux'; import { mountWithIntl } from '../../../../test_utils/enzyme_helpers'; -import { httpServiceMock } from '../../../../../src/core/public/mocks'; +import { httpServiceMock, scopedHistoryMock } from '../../../../../src/core/public/mocks'; import { licenseManagementStore } from '../../public/application/store/store'; import { AppContextProvider } from '../../public/application/app_context'; const highExpirationMillis = new Date('October 13, 2099 00:00:00Z').getTime(); +const history = scopedHistoryMock.create(); +history.createHref.mockImplementation((location) => { + return `${location.pathname}${location.search ? '?' + location.search : ''}`; +}); + const appDependencies = { docLinks: {}, + services: { + history, + }, }; export const createMockLicense = (type, expiryDateInMillis = highExpirationMillis) => { @@ -30,6 +38,7 @@ export const createMockLicense = (type, expiryDateInMillis = highExpirationMilli export const getComponent = (initialState, Component) => { const services = { http: httpServiceMock.createSetupContract(), + history, }; const store = licenseManagementStore(initialState, services); return mountWithIntl( diff --git a/x-pack/plugins/license_management/common/constants/base_path.ts b/x-pack/plugins/license_management/common/constants/base_path.ts index 6ed03a0428096..ffb470e931921 100644 --- a/x-pack/plugins/license_management/common/constants/base_path.ts +++ b/x-pack/plugins/license_management/common/constants/base_path.ts @@ -4,6 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const BASE_PATH = '/management/stack/license_management/'; - export const API_BASE_PATH = '/api/license'; diff --git a/x-pack/plugins/license_management/common/constants/index.ts b/x-pack/plugins/license_management/common/constants/index.ts index ec411fea4b7a9..a531ba08401f8 100644 --- a/x-pack/plugins/license_management/common/constants/index.ts +++ b/x-pack/plugins/license_management/common/constants/index.ts @@ -5,6 +5,6 @@ */ export { PLUGIN } from './plugin'; -export { BASE_PATH, API_BASE_PATH } from './base_path'; +export { API_BASE_PATH } from './base_path'; export { EXTERNAL_LINKS } from './external_links'; export { APP_PERMISSION } from './permissions'; diff --git a/x-pack/plugins/license_management/public/application/app.js b/x-pack/plugins/license_management/public/application/app.js index 46d0da5252ceb..6885a249be01c 100644 --- a/x-pack/plugins/license_management/public/application/app.js +++ b/x-pack/plugins/license_management/public/application/app.js @@ -8,7 +8,7 @@ import React, { Component } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { LicenseDashboard, UploadLicense } from './sections'; import { Switch, Route } from 'react-router-dom'; -import { APP_PERMISSION, BASE_PATH } from '../../common/constants'; +import { APP_PERMISSION } from '../../common/constants'; import { EuiPageBody, EuiEmptyPrompt, EuiText, EuiLoadingSpinner, EuiCallOut } from '@elastic/eui'; export class App extends Component { @@ -89,8 +89,8 @@ export class App extends Component { return ( - - + + ); diff --git a/x-pack/plugins/license_management/public/application/app_context.tsx b/x-pack/plugins/license_management/public/application/app_context.tsx index 1e90f4c907b8c..39e7ef5f16e79 100644 --- a/x-pack/plugins/license_management/public/application/app_context.tsx +++ b/x-pack/plugins/license_management/public/application/app_context.tsx @@ -5,6 +5,7 @@ */ import React, { createContext, useContext } from 'react'; +import { ScopedHistory } from 'kibana/public'; import { CoreStart } from '../../../../../src/core/public'; import { LicensingPluginSetup, ILicense } from '../../../licensing/public'; @@ -18,6 +19,7 @@ export interface AppDependencies { core: CoreStart; services: { breadcrumbService: BreadcrumbService; + history: ScopedHistory; }; plugins: { licensing: LicensingPluginSetup; diff --git a/x-pack/plugins/license_management/public/application/app_providers.tsx b/x-pack/plugins/license_management/public/application/app_providers.tsx index 9f9fd2a8275df..139290f2c46ce 100644 --- a/x-pack/plugins/license_management/public/application/app_providers.tsx +++ b/x-pack/plugins/license_management/public/application/app_providers.tsx @@ -4,10 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import * as history from 'history'; import { Provider } from 'react-redux'; -import { BASE_PATH } from '../../common/constants'; import { AppContextProvider, AppDependencies } from './app_context'; // @ts-ignore import { licenseManagementStore } from './store'; @@ -33,8 +31,7 @@ export const AppProviders = ({ appDependencies, children }: Props) => { // Setup Redux store const thunkServices = { - // So we can imperatively control the hash route - history: history.createHashHistory({ basename: BASE_PATH }), + history: appDependencies.services.history, toasts, http, telemetry: plugins.telemetry, diff --git a/x-pack/plugins/license_management/public/application/breadcrumbs.ts b/x-pack/plugins/license_management/public/application/breadcrumbs.ts index b1773a10f01ba..d3a69f55c4347 100644 --- a/x-pack/plugins/license_management/public/application/breadcrumbs.ts +++ b/x-pack/plugins/license_management/public/application/breadcrumbs.ts @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; import { ManagementAppMountParams } from '../../../../../src/plugins/management/public'; -import { BASE_PATH } from '../../common/constants'; type SetBreadcrumbs = ManagementAppMountParams['setBreadcrumbs']; @@ -32,7 +31,7 @@ export class BreadcrumbService { text: i18n.translate('xpack.licenseMgmt.dashboard.breadcrumb', { defaultMessage: 'License management', }), - href: `#${BASE_PATH}home`, + href: `/`, }, ]; diff --git a/x-pack/plugins/license_management/public/application/index.tsx b/x-pack/plugins/license_management/public/application/index.tsx index 75f2f98f51e6e..cca164b14b8b8 100644 --- a/x-pack/plugins/license_management/public/application/index.tsx +++ b/x-pack/plugins/license_management/public/application/index.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter } from 'react-router-dom'; +import { Router } from 'react-router-dom'; import { AppDependencies } from './app_context'; import { AppProviders } from './app_providers'; @@ -14,15 +14,18 @@ import { AppProviders } from './app_providers'; import { App } from './app.container'; const AppWithRouter = (props: { [key: string]: any }) => ( - + - + ); export const renderApp = (element: Element, dependencies: AppDependencies) => { render( - + , element ); diff --git a/x-pack/plugins/license_management/public/application/sections/license_dashboard/add_license/add_license.js b/x-pack/plugins/license_management/public/application/sections/license_dashboard/add_license/add_license.js index 158702e1286ae..d13a3bc34a7f2 100644 --- a/x-pack/plugins/license_management/public/application/sections/license_dashboard/add_license/add_license.js +++ b/x-pack/plugins/license_management/public/application/sections/license_dashboard/add_license/add_license.js @@ -5,12 +5,16 @@ */ import React from 'react'; -import { BASE_PATH } from '../../../../../common/constants'; import { EuiCard, EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { useAppContext } from '../../../app_context'; + +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; + +export const AddLicense = ({ uploadPath = `/upload_license` }) => { + const { services } = useAppContext(); -export const AddLicense = ({ uploadPath = `#${BASE_PATH}upload_license` }) => { return ( { /> } footer={ - + @@ -189,7 +190,7 @@ export class UploadLicense extends React.PureComponent { - + { + mount: async ({ element, setBreadcrumbs, history }) => { const [core] = await getStartServices(); const initialLicense = await plugins.licensing.license$.pipe(first()).toPromise(); @@ -72,6 +72,7 @@ export class LicenseManagementUIPlugin }, services: { breadcrumbService: this.breadcrumbService, + history, }, store: { initialLicense, diff --git a/x-pack/plugins/licensing/public/plugin.test.ts b/x-pack/plugins/licensing/public/plugin.test.ts index 564f2fdb21116..960fe3699e210 100644 --- a/x-pack/plugins/licensing/public/plugin.test.ts +++ b/x-pack/plugins/licensing/public/plugin.test.ts @@ -374,7 +374,7 @@ describe('licensing plugin', () => { expect(coreStart.overlays.banners.add).toHaveBeenCalledTimes(1); expect(mountExpiredBannerMock).toHaveBeenCalledWith({ type: 'gold', - uploadUrl: '/app/kibana#/management/stack/license_management/upload_license', + uploadUrl: '/app/management/stack/license_management/upload_license', }); }); }); diff --git a/x-pack/plugins/licensing/public/plugin.ts b/x-pack/plugins/licensing/public/plugin.ts index c39acb12b06e1..ec42a73f610c0 100644 --- a/x-pack/plugins/licensing/public/plugin.ts +++ b/x-pack/plugins/licensing/public/plugin.ts @@ -161,7 +161,7 @@ export class LicensingPlugin implements Plugin ) => { @@ -43,12 +43,12 @@ export const renderApp = async ( ReactDOM.render( - + { + render={() => { setBreadcrumbs(Breadcrumbs.getPipelineListBreadcrumbs()); return ( history.push(`/pipeline/${id}/edit`)} clonePipeline={(id: string) => history.push(`/pipeline/${id}/edit?clone`)} - createPipeline={() => history.push(`/pipeline/new-pipeline`)} + createPipeline={() => history.push(`pipeline/new-pipeline`)} pipelinesService={pipelinesService} toastNotifications={core.notifications.toasts} /> @@ -70,7 +70,7 @@ export const renderApp = async ( ( + render={() => ( ( + render={({ match }) => ( - + , element ); diff --git a/x-pack/plugins/logstash/public/plugin.ts b/x-pack/plugins/logstash/public/plugin.ts index 70fdb420ca2d2..ade6abdb63f43 100644 --- a/x-pack/plugins/logstash/public/plugin.ts +++ b/x-pack/plugins/logstash/public/plugin.ts @@ -71,7 +71,7 @@ export class LogstashPlugin implements Plugin { defaultMessage: 'Create, delete, update, and clone data ingestion pipelines.', }), icon: 'pipelineApp', - path: '/app/kibana#/management/ingest/pipelines', + path: '/app/management/ingest/pipelines', showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }); diff --git a/x-pack/plugins/maps/public/components/no_index_pattern_callout.js b/x-pack/plugins/maps/public/components/no_index_pattern_callout.js index 89cd884a6dd32..2daab8c6322dd 100644 --- a/x-pack/plugins/maps/public/components/no_index_pattern_callout.js +++ b/x-pack/plugins/maps/public/components/no_index_pattern_callout.js @@ -24,7 +24,7 @@ export function NoIndexPatternCallout() { id="xpack.maps.noIndexPattern.doThisPrefixDescription" defaultMessage="You'll need to " /> - + { } description={startTrialDescription()} footer={ - + = ({ /> } description="" - href={`${basePath.get()}/app/kibana#/management/data/index_management/indices/filter/${index}`} + href={`${basePath.get()}/app/management/data/index_management/indices/filter/${index}`} /> @@ -153,7 +153,7 @@ export const ResultsLinks: FC = ({ /> } description="" - href={`${basePath.get()}/app/kibana#/management/kibana/indexPatterns${ + href={`${basePath.get()}/app/management/kibana/indexPatterns${ createIndexPattern ? `/patterns/${indexPatternId}` : '' }`} /> diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js index 67de83e90695d..eeff91be130ea 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js @@ -167,7 +167,7 @@ class CreateWatchService { saveWatch(watchModel) .then(() => { this.status.watch = this.STATUS.SAVED; - this.config.watcherEditURL = `${basePath.get()}/app/kibana#/management/insightsAndAlerting/watcher/watches/watch/${id}/edit?_g=()`; + this.config.watcherEditURL = `${basePath.get()}/app/management/insightsAndAlerting/watcher/watches/watch/${id}/edit?_g=()`; resolve({ id, url: this.config.watcherEditURL, diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/index.ts b/x-pack/plugins/ml/public/application/management/jobs_list/index.ts index cfe37ce14bb78..5d1fc6f0a3c92 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/index.ts +++ b/x-pack/plugins/ml/public/application/management/jobs_list/index.ts @@ -11,12 +11,14 @@ import { ManagementAppMountParams } from '../../../../../../../src/plugins/manag import { MlStartDependencies } from '../../../plugin'; import { JobsListPage } from './components'; import { getJobsListBreadcrumbs } from '../breadcrumbs'; +import { setDependencyCache, clearCache } from '../../util/dependency_cache'; const renderApp = (element: HTMLElement, coreStart: CoreStart) => { const I18nContext = coreStart.i18n.Context; ReactDOM.render(React.createElement(JobsListPage, { I18nContext }), element); return () => { unmountComponentAtNode(element); + clearCache(); }; }; @@ -25,6 +27,15 @@ export async function mountApp( params: ManagementAppMountParams ) { const [coreStart] = await core.getStartServices(); + + setDependencyCache({ + docLinks: coreStart.docLinks!, + basePath: coreStart.http.basePath, + http: coreStart.http, + i18n: coreStart.i18n, + }); + params.setBreadcrumbs(getJobsListBreadcrumbs()); + return renderApp(params.element, coreStart); } diff --git a/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx b/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx index 87a7156b6f52e..119346ec8035a 100644 --- a/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx @@ -42,7 +42,7 @@ export const OverviewSideBar: FC = ({ createAnomalyDetectionJobDisabled } const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; const docsLink = `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/xpack-ml.html`; - const transformsLink = `${basePath.get()}/app/kibana#/management/data/transform`; + const transformsLink = `${basePath.get()}/app/management/data/transform`; return ( diff --git a/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js b/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js index 263da16340cda..f1a867536b606 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js +++ b/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js @@ -288,7 +288,7 @@ const handleClickIncompatibleLicense = (scope, clusterName) => { }; const handleClickInvalidLicense = (scope, clusterName) => { - const licensingPath = `${Legacy.shims.getBasePath()}/app/kibana#/management/stack/license_management/home`; + const licensingPath = `${Legacy.shims.getBasePath()}/app/management/stack/license_management/home`; licenseWarning(scope, { title: toMountPoint( diff --git a/x-pack/plugins/monitoring/public/components/license/index.js b/x-pack/plugins/monitoring/public/components/license/index.js index e8ea1f8df227a..076b8e6d543e6 100644 --- a/x-pack/plugins/monitoring/public/components/license/index.js +++ b/x-pack/plugins/monitoring/public/components/license/index.js @@ -169,7 +169,7 @@ const LicenseUpdateInfoForRemote = ({ isPrimaryCluster }) => { export function License(props) { const { status, type, isExpired, expiryDate } = props; - const licenseManagement = `${Legacy.shims.getBasePath()}/app/kibana#/management/stack/license_management`; + const licenseManagement = `${Legacy.shims.getBasePath()}/app/management/stack/license_management`; return ( diff --git a/x-pack/plugins/remote_clusters/public/application/app.js b/x-pack/plugins/remote_clusters/public/application/app.js index 483b2f5b97e27..714887b039a42 100644 --- a/x-pack/plugins/remote_clusters/public/application/app.js +++ b/x-pack/plugins/remote_clusters/public/application/app.js @@ -6,9 +6,9 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { Switch, Route, Redirect, withRouter } from 'react-router-dom'; +import { Switch, Route, Redirect, Router } from 'react-router-dom'; -import { CRUD_APP_BASE_PATH, UIM_APP_LOAD } from './constants'; +import { UIM_APP_LOAD } from './constants'; import { registerRouter, setUserHasLeftApp, trackUiMetric, METRIC_TYPE } from './services'; import { RemoteClusterList, RemoteClusterAdd, RemoteClusterEdit } from './sections'; @@ -22,15 +22,16 @@ class AppComponent extends Component { constructor(...args) { super(...args); + setUserHasLeftApp(false); this.registerRouter(); } registerRouter() { // Share the router with the app without requiring React or context. - const { history, location } = this.props; + const { history } = this.props; registerRouter({ history, - route: { location }, + route: { location: history.location }, }); } @@ -45,17 +46,16 @@ class AppComponent extends Component { render() { return ( -
+ - - - - - + + + + -
+ ); } } -export const App = withRouter(AppComponent); +export const App = AppComponent; diff --git a/x-pack/plugins/remote_clusters/public/application/index.d.ts b/x-pack/plugins/remote_clusters/public/application/index.d.ts index b021dca51bacd..8b2af65e4fff7 100644 --- a/x-pack/plugins/remote_clusters/public/application/index.d.ts +++ b/x-pack/plugins/remote_clusters/public/application/index.d.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ScopedHistory } from 'kibana/public'; import { RegisterManagementAppArgs, I18nStart } from '../types'; export declare const renderApp: ( @@ -11,5 +12,6 @@ export declare const renderApp: ( I18nContext: I18nStart['Context'], appDependencies: { isCloudEnabled?: boolean; - } + }, + history: ScopedHistory ) => ReturnType; diff --git a/x-pack/plugins/remote_clusters/public/application/index.js b/x-pack/plugins/remote_clusters/public/application/index.js index cf6e855ba58df..25e171c9ef51d 100644 --- a/x-pack/plugins/remote_clusters/public/application/index.js +++ b/x-pack/plugins/remote_clusters/public/application/index.js @@ -6,7 +6,6 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter } from 'react-router-dom'; import { Provider } from 'react-redux'; import { App } from './app'; @@ -15,14 +14,12 @@ import { AppContextProvider } from './app_context'; import './_hacks.scss'; -export const renderApp = (elem, I18nContext, appDependencies) => { +export const renderApp = (elem, I18nContext, appDependencies, history) => { render( - - - + , diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js index f5053e3e18ccf..b13e833f60b18 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_add/remote_cluster_add.js @@ -10,7 +10,6 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPageContent } from '@elastic/eui'; -import { CRUD_APP_BASE_PATH } from '../../constants'; import { getRouter, redirect, extractQueryParams } from '../../services'; import { setBreadcrumbs } from '../../services/breadcrumb'; import { RemoteClusterPageTitle, RemoteClusterForm } from '../components'; @@ -49,7 +48,7 @@ export class RemoteClusterAdd extends PureComponent { const decodedRedirect = decodeURIComponent(redirectUrl); redirect(decodedRedirect); } else { - history.push(CRUD_APP_BASE_PATH); + history.push('/list'); } }; diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js index 5e3b2f12a57fd..9018647600b8d 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_edit/remote_cluster_edit.js @@ -20,8 +20,8 @@ import { EuiTextColor, } from '@elastic/eui'; -import { CRUD_APP_BASE_PATH } from '../../constants'; -import { extractQueryParams, getRouter, getRouterLinkProps, redirect } from '../../services'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; +import { extractQueryParams, getRouter, redirect } from '../../services'; import { setBreadcrumbs } from '../../services/breadcrumb'; import { RemoteClusterPageTitle, RemoteClusterForm, ConfiguredByNodeWarning } from '../components'; @@ -89,7 +89,7 @@ export class RemoteClusterEdit extends Component { const decodedRedirect = decodeURIComponent(redirectUrl); redirect(decodedRedirect); } else { - history.push(CRUD_APP_BASE_PATH); + history.push('/list'); openDetailPanel(clusterName); } }; @@ -143,7 +143,7 @@ export class RemoteClusterEdit extends Component { diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js index 22c986c203a04..03be45c760244 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/detail_panel/detail_panel.js @@ -30,12 +30,11 @@ import { EuiTextColor, EuiTitle, } from '@elastic/eui'; - -import { CRUD_APP_BASE_PATH } from '../../../constants'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; import { PROXY_MODE } from '../../../../../common/constants'; -import { getRouterLinkProps } from '../../../services'; import { ConfiguredByNodeWarning } from '../../components'; import { ConnectionStatus, RemoveClusterButtonProvider } from '../components'; +import { getRouter } from '../../../services'; import { proxyModeUrl } from '../../../services/documentation'; export class DetailPanel extends Component { @@ -114,7 +113,8 @@ export class DetailPanel extends Component { renderClusterWithDeprecatedSettingWarning( { hasDeprecatedProxySetting, isConfiguredByNode }, - clusterName + clusterName, + history ) { if (!hasDeprecatedProxySetting) { return null; @@ -156,7 +156,7 @@ export class DetailPanel extends Component { defaultMessage="{editLink} to update the settings." values={{ editLink: ( - + {this.renderClusterConfiguredByNodeWarning(cluster)} - {this.renderClusterWithDeprecatedSettingWarning(cluster, clusterName)} + {this.renderClusterWithDeprecatedSettingWarning(cluster, clusterName, history)} {this.renderCluster(cluster)} )} @@ -465,7 +465,7 @@ export class DetailPanel extends Component { ); } - renderFlyoutFooter() { + renderFlyoutFooter(history) { const { cluster, clusterName, closeDetailPanel } = this.props; return ( @@ -507,7 +507,7 @@ export class DetailPanel extends Component { - {this.renderFlyoutBody()} + {this.renderFlyoutBody(history)} - {this.renderFlyoutFooter()} + {this.renderFlyoutFooter(history)} ); } diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js index 207aa8045c011..6d40cbbeb82ae 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_list.js @@ -28,8 +28,8 @@ import { EuiCallOut, } from '@elastic/eui'; -import { CRUD_APP_BASE_PATH } from '../../constants'; -import { getRouterLinkProps, extractQueryParams } from '../../services'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; +import { extractQueryParams } from '../../services'; import { setBreadcrumbs } from '../../services/breadcrumb'; import { RemoteClusterTable } from './remote_cluster_table'; @@ -99,7 +99,7 @@ export class RemoteClusterList extends Component { {isAuthorized && ( @@ -185,7 +185,7 @@ export class RemoteClusterList extends Component { } actions={ { @@ -94,6 +94,7 @@ export class RemoteClusterTable extends Component { render() { const { openDetailPanel } = this.props; const { selectedItems, filteredClusters } = this.state; + const { history } = getRouter(); const columns = [ { @@ -256,7 +257,7 @@ export class RemoteClusterTable extends Component { iconType="pencil" color="primary" isDisabled={isConfiguredByNode} - {...getRouterLinkProps(`${CRUD_APP_BASE_PATH}/edit/${name}`)} + {...reactRouterNavigate(history, `/edit/${name}`)} disabled={isConfiguredByNode} /> diff --git a/x-pack/plugins/remote_clusters/public/application/services/breadcrumb.ts b/x-pack/plugins/remote_clusters/public/application/services/breadcrumb.ts index f90a0d3456166..feec7d523e7c1 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/breadcrumb.ts +++ b/x-pack/plugins/remote_clusters/public/application/services/breadcrumb.ts @@ -6,8 +6,6 @@ import { i18n } from '@kbn/i18n'; -import { CRUD_APP_BASE_PATH } from '../constants'; - interface Breadcrumb { text: string; href?: string; @@ -28,7 +26,7 @@ export function init(setGlobalBreadcrumbs: (breadcrumbs: Breadcrumb[]) => void): text: i18n.translate('xpack.remoteClusters.listBreadcrumbTitle', { defaultMessage: 'Remote Clusters', }), - href: `#${CRUD_APP_BASE_PATH}/list`, + href: `/list`, }, add: { text: i18n.translate('xpack.remoteClusters.addBreadcrumbTitle', { diff --git a/x-pack/plugins/remote_clusters/public/application/services/index.js b/x-pack/plugins/remote_clusters/public/application/services/index.js index 387a04b6e5d8c..ce8d06b6e2278 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/index.js +++ b/x-pack/plugins/remote_clusters/public/application/services/index.js @@ -14,12 +14,6 @@ export { isAddressValid, isPortValid } from './validate_address'; export { extractQueryParams } from './query_params'; -export { - setUserHasLeftApp, - getUserHasLeftApp, - registerRouter, - getRouter, - getRouterLinkProps, -} from './routing'; +export { setUserHasLeftApp, getUserHasLeftApp, registerRouter, getRouter } from './routing'; export { trackUiMetric, METRIC_TYPE } from './ui_metric'; diff --git a/x-pack/plugins/remote_clusters/public/application/services/redirect.ts b/x-pack/plugins/remote_clusters/public/application/services/redirect.ts index 00a97fa74c5ce..1130dbc77fc75 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/redirect.ts +++ b/x-pack/plugins/remote_clusters/public/application/services/redirect.ts @@ -13,5 +13,5 @@ export function init(_navigateToApp: CoreStart['application']['navigateToApp']) } export function redirect(path: string) { - navigateToApp('kibana', { path: `#${path}` }); + navigateToApp('management', { path }); } diff --git a/x-pack/plugins/remote_clusters/public/application/services/routing.js b/x-pack/plugins/remote_clusters/public/application/services/routing.js index 6e60f75fd8bb3..c86c9756cfcc8 100644 --- a/x-pack/plugins/remote_clusters/public/application/services/routing.js +++ b/x-pack/plugins/remote_clusters/public/application/services/routing.js @@ -8,8 +8,6 @@ * This file based on guidance from https://github.com/elastic/eui/blob/master/wiki/react-router.md */ -import { createLocation } from 'history'; - let _userHasLeftApp = false; export function setUserHasLeftApp(userHasLeftApp) { @@ -20,11 +18,6 @@ export function getUserHasLeftApp() { return _userHasLeftApp; } -const isModifiedEvent = (event) => - !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); - -const isLeftClickEvent = (event) => event.button === 0; - let router; export function registerRouter(reactRouter) { router = reactRouter; @@ -33,35 +26,3 @@ export function registerRouter(reactRouter) { export function getRouter() { return router; } - -/** - * The logic for generating hrefs and onClick handlers from the `to` prop is largely borrowed from - * https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/Link.js. - */ -export function getRouterLinkProps(to) { - const location = - typeof to === 'string' ? createLocation(to, null, null, router.history.location) : to; - - const href = router.history.createHref(location); - - const onClick = (event) => { - if (event.defaultPrevented) { - return; - } - - // If target prop is set (e.g. to "_blank"), let browser handle link. - if (event.target.getAttribute('target')) { - return; - } - - if (isModifiedEvent(event) || !isLeftClickEvent(event)) { - return; - } - - // Prevent regular link behavior, which causes a browser refresh. - event.preventDefault(); - router.history.push(location); - }; - - return { href, onClick }; -} diff --git a/x-pack/plugins/remote_clusters/public/application/store/actions/add_cluster.js b/x-pack/plugins/remote_clusters/public/application/store/actions/add_cluster.js index 17523ceda54b5..d57fd37e791a1 100644 --- a/x-pack/plugins/remote_clusters/public/application/store/actions/add_cluster.js +++ b/x-pack/plugins/remote_clusters/public/application/store/actions/add_cluster.js @@ -6,7 +6,6 @@ import { i18n } from '@kbn/i18n'; -import { CRUD_APP_BASE_PATH } from '../../constants'; import { addCluster as sendAddClusterRequest, getRouter, @@ -108,7 +107,7 @@ export const addCluster = (cluster) => async (dispatch) => { // This will open the new job in the detail panel. Note that we're *not* showing a success toast // here, because it would partially obscure the detail panel. history.push({ - pathname: `${CRUD_APP_BASE_PATH}/list`, + pathname: `/list`, search: `?cluster=${cluster.name}`, }); } diff --git a/x-pack/plugins/remote_clusters/public/application/store/actions/edit_cluster.js b/x-pack/plugins/remote_clusters/public/application/store/actions/edit_cluster.js index 436e6bdce36ed..4fd8faeb7021e 100644 --- a/x-pack/plugins/remote_clusters/public/application/store/actions/edit_cluster.js +++ b/x-pack/plugins/remote_clusters/public/application/store/actions/edit_cluster.js @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; import { toasts, fatalError } from '../../services/notification'; -import { CRUD_APP_BASE_PATH } from '../../constants'; import { loadClusters } from './load_clusters'; import { @@ -95,7 +94,7 @@ export const editCluster = (cluster) => async (dispatch) => { // This will open the edited cluster in the detail panel. Note that we're *not* showing a success toast // here, because it would partially obscure the detail panel. history.push({ - pathname: `${CRUD_APP_BASE_PATH}/list`, + pathname: `/list`, search: `?cluster=${cluster.name}`, }); } diff --git a/x-pack/plugins/remote_clusters/public/plugin.ts b/x-pack/plugins/remote_clusters/public/plugin.ts index fde8ffa511319..8881db0f9196e 100644 --- a/x-pack/plugins/remote_clusters/public/plugin.ts +++ b/x-pack/plugins/remote_clusters/public/plugin.ts @@ -41,7 +41,7 @@ export class RemoteClustersUIPlugin defaultMessage: 'Remote Clusters', }), order: 7, - mount: async ({ element, setBreadcrumbs }) => { + mount: async ({ element, setBreadcrumbs, history }) => { const [core] = await getStartServices(); const { i18n: { Context: i18nContext }, @@ -59,7 +59,7 @@ export class RemoteClustersUIPlugin const isCloudEnabled = Boolean(cloud?.isCloudEnabled); const { renderApp } = await import('./application'); - return renderApp(element, i18nContext, { isCloudEnabled }); + return renderApp(element, i18nContext, { isCloudEnabled }, history); }, }); } diff --git a/x-pack/plugins/reporting/constants.ts b/x-pack/plugins/reporting/constants.ts index 9a1d0cec2cf96..772c52dde4a15 100644 --- a/x-pack/plugins/reporting/constants.ts +++ b/x-pack/plugins/reporting/constants.ts @@ -12,7 +12,7 @@ export const API_BASE_URL = '/api/reporting'; export const API_LIST_URL = `${API_BASE_URL}/jobs`; export const API_BASE_GENERATE = `${API_BASE_URL}/generate`; export const API_GENERATE_IMMEDIATE = `${API_BASE_URL}/v1/generate/immediate/csv/saved-object`; -export const REPORTING_MANAGEMENT_HOME = '/app/kibana#/management/insightsAndAlerting/reporting'; +export const REPORTING_MANAGEMENT_HOME = '/app/management/insightsAndAlerting/reporting'; // Statuses export const JOB_STATUS_FAILED = 'failed'; diff --git a/x-pack/plugins/reporting/public/components/report_listing.tsx b/x-pack/plugins/reporting/public/components/report_listing.tsx index c79373665d056..afcae93a8db16 100644 --- a/x-pack/plugins/reporting/public/components/report_listing.tsx +++ b/x-pack/plugins/reporting/public/components/report_listing.tsx @@ -266,7 +266,7 @@ class ReportListingUi extends Component { } catch (fetchError) { if (!this.licenseAllowsToShowThisPage()) { this.props.toasts.addDanger(this.state.badLicenseMessage); - this.props.redirect('kibana#/management'); + this.props.redirect('management'); return; } diff --git a/x-pack/plugins/reporting/public/plugin.tsx b/x-pack/plugins/reporting/public/plugin.tsx index 7495e46de47d9..8f59a898b8e35 100644 --- a/x-pack/plugins/reporting/public/plugin.tsx +++ b/x-pack/plugins/reporting/public/plugin.tsx @@ -111,7 +111,7 @@ export class ReportingPublicPlugin implements Plugin { defaultMessage: 'Manage your reports generated from Discover, Visualize, and Dashboard.', }), icon: 'reportingApp', - path: '/app/kibana#/management/kibana/reporting', + path: '/app/management/kibana/reporting', showOnHomePage: false, category: FeatureCatalogueCategory.ADMIN, }); diff --git a/x-pack/plugins/rollup/public/application.tsx b/x-pack/plugins/rollup/public/application.tsx index 1bdf940d746b2..16a0312341118 100644 --- a/x-pack/plugins/rollup/public/application.tsx +++ b/x-pack/plugins/rollup/public/application.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { ChromeBreadcrumb, CoreSetup } from 'kibana/public'; +import { CoreSetup } from 'kibana/public'; import { render, unmountComponentAtNode } from 'react-dom'; import { Provider } from 'react-redux'; import { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; @@ -16,28 +16,28 @@ import { App } from './crud_app/app'; import './index.scss'; +import { ManagementAppMountParams } from '../../../../src/plugins/management/public'; + /** * This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle. */ export const renderApp = async ( core: CoreSetup, - { - element, - setBreadcrumbs, - }: { element: HTMLElement; setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void } + { history, element, setBreadcrumbs }: ManagementAppMountParams ) => { const [coreStart] = await core.getStartServices(); const I18nContext = coreStart.i18n.Context; + const services = { + history, + setBreadcrumbs, + }; + render( - + - + , diff --git a/x-pack/plugins/rollup/public/crud_app/app.js b/x-pack/plugins/rollup/public/crud_app/app.js index 0ef3253eeb94e..4eff849776aef 100644 --- a/x-pack/plugins/rollup/public/crud_app/app.js +++ b/x-pack/plugins/rollup/public/crud_app/app.js @@ -6,10 +6,9 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { HashRouter, Switch, Route, Redirect, withRouter } from 'react-router-dom'; +import { Router, Switch, Route, Redirect, withRouter } from 'react-router-dom'; import { UIM_APP_LOAD } from '../../common'; -import { CRUD_APP_BASE_PATH } from './constants'; import { registerRouter, setUserHasLeftApp, METRIC_TYPE } from './services'; import { trackUiMetric } from '../kibana_services'; import { JobList, JobCreate } from './sections'; @@ -53,15 +52,15 @@ export class App extends Component { render() { return ( - + - - - + + + - + ); } } diff --git a/x-pack/plugins/rollup/public/crud_app/constants/index.js b/x-pack/plugins/rollup/public/crud_app/constants/index.js index f3a218fc3b493..132affafea87d 100644 --- a/x-pack/plugins/rollup/public/crud_app/constants/index.js +++ b/x-pack/plugins/rollup/public/crud_app/constants/index.js @@ -4,6 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { CRUD_APP_BASE_PATH } from './paths'; - export { METRICS_CONFIG } from './metrics_config'; diff --git a/x-pack/plugins/rollup/public/crud_app/constants/paths.js b/x-pack/plugins/rollup/public/crud_app/constants/paths.js deleted file mode 100644 index 44829f38e79cd..0000000000000 --- a/x-pack/plugins/rollup/public/crud_app/constants/paths.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const CRUD_APP_BASE_PATH = '/management/data/rollup_jobs'; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js index 011becded148c..85cd6e742d27f 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js @@ -27,7 +27,6 @@ import { import { withKibana } from '../../../../../../../src/plugins/kibana_react/public'; -import { CRUD_APP_BASE_PATH } from '../../constants'; import { getRouterLinkProps, extractQueryParams, listBreadcrumb } from '../../services'; import { JobTable } from './job_table'; @@ -166,7 +165,7 @@ export class JobListUi extends Component { actions={ @@ -210,7 +209,7 @@ export class JobListUi extends Component { {this.getHeaderSection()} - + async (dispatch) => { // This will open the new job in the detail panel. Note that we're *not* showing a success toast // here, because it would partially obscure the detail panel. getRouter().history.push({ - pathname: `${CRUD_APP_BASE_PATH}/job_list`, + pathname: `/job_list`, search: `?job=${jobConfig.id}`, }); }; diff --git a/x-pack/plugins/rollup/public/crud_app/store/middleware/clone_job.js b/x-pack/plugins/rollup/public/crud_app/store/middleware/clone_job.js index 2012ec2248fbd..b8495a1c95a8e 100644 --- a/x-pack/plugins/rollup/public/crud_app/store/middleware/clone_job.js +++ b/x-pack/plugins/rollup/public/crud_app/store/middleware/clone_job.js @@ -6,7 +6,6 @@ import { getRouter, getUserHasLeftApp } from '../../services'; import { CLONE_JOB_START } from '../action_types'; -import { CRUD_APP_BASE_PATH } from '../../constants'; export const cloneJob = () => (next) => (action) => { const { type } = action; @@ -14,7 +13,7 @@ export const cloneJob = () => (next) => (action) => { if (type === CLONE_JOB_START) { if (!getUserHasLeftApp()) { getRouter().history.push({ - pathname: `${CRUD_APP_BASE_PATH}/create`, + pathname: `/create`, }); } } diff --git a/x-pack/plugins/rollup/public/plugin.ts b/x-pack/plugins/rollup/public/plugin.ts index b2e793d7e75e9..b55760c5cc5aa 100644 --- a/x-pack/plugins/rollup/public/plugin.ts +++ b/x-pack/plugins/rollup/public/plugin.ts @@ -16,8 +16,6 @@ import { FeatureCatalogueCategory, HomePublicPluginSetup, } from '../../../../src/plugins/home/public'; -// @ts-ignore -import { CRUD_APP_BASE_PATH } from './crud_app/constants'; import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { IndexManagementPluginSetup } from '../../index_management/public'; import { IndexPatternManagementSetup } from '../../../../src/plugins/index_pattern_management/public'; @@ -71,7 +69,7 @@ export class RollupPlugin implements Plugin { 'Summarize and store historical data in a smaller index for future analysis.', }), icon: 'indexRollupApp', - path: `#${CRUD_APP_BASE_PATH}/job_list`, + path: `/app/management/data/rollup_jobs/job_list`, showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js index 380275df05ba8..53a3af38f3235 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_list_clone.test.js @@ -8,7 +8,6 @@ import { mockHttpRequest, pageHelpers, nextTick } from './helpers'; import { JOB_TO_CLONE, JOB_CLONE_INDEX_PATTERN_CHECK } from './helpers/constants'; import { getRouter } from '../../crud_app/services/routing'; import { setHttp } from '../../crud_app/services'; -import { CRUD_APP_BASE_PATH } from '../../crud_app/constants'; import { coreMock } from '../../../../../../src/core/public/mocks'; jest.mock('lodash/function/debounce', () => (fn) => fn); @@ -65,8 +64,8 @@ describe('Smoke test cloning an existing rollup job from job list', () => { find('jobActionMenuButton').simulate('click'); - expect(router.history.location.pathname).not.toBe(`${CRUD_APP_BASE_PATH}/create`); + expect(router.history.location.pathname).not.toBe(`/create`); find('jobCloneActionContextMenu').simulate('click'); - expect(router.history.location.pathname).toBe(`${CRUD_APP_BASE_PATH}/create`); + expect(router.history.location.pathname).toBe(`/create`); }); }); diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx index 0500abcd2206a..94f9de010cc2a 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx @@ -64,10 +64,13 @@ describe('APIKeysGridPage', () => { }); }); + const coreStart = coreMock.createStart(); + const getViewProperties = () => { - const { docLinks, notifications } = coreMock.createStart(); + const { docLinks, notifications, application } = coreStart; return { docLinks: new DocumentationLinksService(docLinks), + navigateToApp: application.navigateToApp, notifications, apiKeysAPIClient: apiClientMock, }; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx index 8308a66e2d900..1ee1adf41a156 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx @@ -26,7 +26,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import moment from 'moment-timezone'; -import { NotificationsStart } from 'src/core/public'; +import { ApplicationStart, NotificationsStart } from 'src/core/public'; import { SectionLoading } from '../../../../../../../src/plugins/es_ui_shared/public'; import { ApiKey, ApiKeyToInvalidate } from '../../../../common/model'; import { APIKeysAPIClient } from '../api_keys_api_client'; @@ -40,6 +40,7 @@ interface Props { notifications: NotificationsStart; docLinks: DocumentationLinksService; apiKeysAPIClient: PublicMethodsOf; + navigateToApp: ApplicationStart['navigateToApp']; } interface State { @@ -137,7 +138,11 @@ export class APIKeysGridPage extends Component { if (!isLoadingTable && apiKeys && apiKeys.length === 0) { return ( - + ); } diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx index ef1ac40ca4b32..9b2ccfcb99ef3 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx @@ -5,6 +5,8 @@ */ import React, { Fragment } from 'react'; +import { ApplicationStart } from 'kibana/public'; + import { EuiEmptyPrompt, EuiButton, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { DocumentationLinksService } from '../../documentation_links'; @@ -12,9 +14,14 @@ import { DocumentationLinksService } from '../../documentation_links'; interface Props { isAdmin: boolean; docLinks: DocumentationLinksService; + navigateToApp: ApplicationStart['navigateToApp']; } -export const EmptyPrompt: React.FunctionComponent = ({ isAdmin, docLinks }) => ( +export const EmptyPrompt: React.FunctionComponent = ({ + isAdmin, + docLinks, + navigateToApp, +}) => ( = ({ isAdmin, docLinks } actions={ - + navigateToApp('dev_tools')} + data-test-subj="goToConsoleButton" + > ({ APIKeysGridPage: (props: any) => `Page: ${JSON.stringify(props)}`, })); - +import { ScopedHistory } from 'src/core/public'; import { apiKeysManagementApp } from './api_keys_management_app'; -import { coreMock } from '../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../src/core/public/mocks'; describe('apiKeysManagementApp', () => { it('create() returns proper management app descriptor', () => { @@ -37,10 +37,11 @@ describe('apiKeysManagementApp', () => { basePath: '/some-base-path', element: container, setBreadcrumbs, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, }); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: '#/some-base-path', text: 'API Keys' }]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: '/', text: 'API Keys' }]); expect(container).toMatchInlineSnapshot(`
Page: {"notifications":{"toasts":{}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"apiKeysAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}}} diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx index b9ec5b35b3f9d..6ff91852d0a3e 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx @@ -25,18 +25,18 @@ export const apiKeysManagementApp = Object.freeze({ title: i18n.translate('xpack.security.management.apiKeysTitle', { defaultMessage: 'API Keys', }), - async mount({ basePath, element, setBreadcrumbs }) { + async mount({ element, setBreadcrumbs }) { setBreadcrumbs([ { text: i18n.translate('xpack.security.apiKeys.breadcrumb', { defaultMessage: 'API Keys', }), - href: `#${basePath}`, + href: `/`, }, ]); const [ - [{ docLinks, http, notifications, i18n: i18nStart }], + [{ docLinks, http, notifications, i18n: i18nStart, application }], { APIKeysGridPage }, { APIKeysAPIClient }, ] = await Promise.all([ @@ -48,6 +48,7 @@ export const apiKeysManagementApp = Object.freeze({ render( { describe('setup()', () => { it('properly registers security section and its applications', () => { @@ -24,11 +30,10 @@ describe('ManagementService', () => { const { authc } = securityMock.createSetup(); const license = licenseMock.create(); - const mockSection = { registerApp: jest.fn() }; - const managementSetup = { + const managementSetup: ManagementSetup = { sections: { + register: jest.fn(), getSection: jest.fn().mockReturnValue(mockSection), - getAllSections: jest.fn(), }, }; @@ -80,17 +85,20 @@ describe('ManagementService', () => { license.features$ = licenseSubject; const service = new ManagementService(); + + const managementSetup: ManagementSetup = { + sections: { + register: jest.fn(), + getSection: jest.fn().mockReturnValue(mockSection), + }, + }; + service.setup({ getStartServices: getStartServices as any, license, fatalErrors, authc: securityMock.createSetup().authc, - management: { - sections: { - getSection: jest.fn().mockReturnValue({ registerApp: jest.fn() }), - getAllSections: jest.fn(), - }, - }, + management: managementSetup, }); const getMockedApp = () => { @@ -115,17 +123,18 @@ describe('ManagementService', () => { [roleMappingsManagementApp.id, getMockedApp()], ] as Array<[string, jest.Mocked]>); - service.start({ - management: { - sections: { - getSection: jest - .fn() - .mockReturnValue({ getApp: jest.fn().mockImplementation((id) => mockApps.get(id)) }), - getAllSections: jest.fn(), - navigateToApp: jest.fn(), - }, - legacy: undefined, + const managementStart: ManagementStart = { + sections: { + getSection: jest + .fn() + .mockReturnValue({ getApp: jest.fn().mockImplementation((id) => mockApps.get(id)) }), + getAllSections: jest.fn(), + getSectionsEnabled: jest.fn(), }, + }; + + service.start({ + management: managementStart, }); return { diff --git a/x-pack/plugins/security/public/management/management_urls.ts b/x-pack/plugins/security/public/management/management_urls.ts index 0d4e3fc920bdb..493f6c64317a5 100644 --- a/x-pack/plugins/security/public/management/management_urls.ts +++ b/x-pack/plugins/security/public/management/management_urls.ts @@ -4,20 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -const MANAGEMENT_PATH = '/management'; -const SECURITY_PATH = `${MANAGEMENT_PATH}/security`; -export const ROLES_PATH = `${SECURITY_PATH}/roles`; -export const EDIT_ROLES_PATH = `${ROLES_PATH}/edit`; -export const CLONE_ROLES_PATH = `${ROLES_PATH}/clone`; -export const USERS_PATH = `${SECURITY_PATH}/users`; -export const EDIT_USERS_PATH = `${USERS_PATH}/edit`; -export const ROLE_MAPPINGS_PATH = `${SECURITY_PATH}/role_mappings`; -const CREATE_ROLE_MAPPING_PATH = `${ROLE_MAPPINGS_PATH}/edit`; - -export const getEditRoleHref = (roleName: string) => - `#${ROLES_PATH}/edit/${encodeURIComponent(roleName)}`; - -export const getCreateRoleMappingHref = () => `#${CREATE_ROLE_MAPPING_PATH}`; +export const EDIT_ROLE_MAPPING_PATH = `/edit`; export const getEditRoleMappingHref = (roleMappingName: string) => - `#${CREATE_ROLE_MAPPING_PATH}/${encodeURIComponent(roleMappingName)}`; + `${EDIT_ROLE_MAPPING_PATH}/${encodeURIComponent(roleMappingName)}`; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx index eea6bbef94306..b4e755507f8c5 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx @@ -12,6 +12,7 @@ import { findTestSubject } from 'test_utils/find_test_subject'; // This is not required for the tests to pass, but it rather suppresses lengthy // warnings in the console which adds unnecessary noise to the test output. import 'test_utils/stub_web_worker'; +import { ScopedHistory } from 'kibana/public'; import { EditRoleMappingPage } from '.'; import { NoCompatibleRealms, SectionLoading, PermissionDenied } from '../components'; @@ -21,13 +22,15 @@ import { RolesAPIClient } from '../../roles'; import { Role } from '../../../../common/model'; import { DocumentationLinksService } from '../documentation_links'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../src/core/public/mocks'; import { roleMappingsAPIClientMock } from '../role_mappings_api_client.mock'; import { rolesAPIClientMock } from '../../roles/roles_api_client.mock'; import { RoleComboBox } from '../../role_combo_box'; describe('EditRoleMappingPage', () => { + const history = (scopedHistoryMock.create() as unknown) as ScopedHistory; let rolesAPI: PublicMethodsOf; + beforeEach(() => { rolesAPI = rolesAPIClientMock.create(); (rolesAPI as jest.Mocked).getRoles.mockResolvedValue([ @@ -54,6 +57,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -116,6 +120,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -163,6 +168,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); expect(wrapper.find(SectionLoading)).toHaveLength(1); @@ -190,6 +196,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); expect(wrapper.find(SectionLoading)).toHaveLength(1); @@ -227,6 +234,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -267,6 +275,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -309,6 +318,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -363,6 +373,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); @@ -418,6 +429,7 @@ describe('EditRoleMappingPage', () => { rolesAPIClient={rolesAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} /> ); diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx index 3b16bd8dc44ac..b4e3627039ecb 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.tsx @@ -19,7 +19,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NotificationsStart } from 'src/core/public'; +import { NotificationsStart, ScopedHistory } from 'src/core/public'; import { RoleMapping } from '../../../../common/model'; import { RuleEditorPanel } from './rule_editor_panel'; import { @@ -29,7 +29,6 @@ import { SectionLoading, } from '../components'; import { RolesAPIClient } from '../../roles'; -import { ROLE_MAPPINGS_PATH } from '../../management_urls'; import { validateRoleMappingForSave } from './services/role_mapping_validation'; import { MappingInfoPanel } from './mapping_info_panel'; import { DocumentationLinksService } from '../documentation_links'; @@ -55,6 +54,7 @@ interface Props { rolesAPIClient: PublicMethodsOf; notifications: NotificationsStart; docLinks: DocumentationLinksService; + history: ScopedHistory; } export class EditRoleMappingPage extends Component { @@ -342,7 +342,5 @@ export class EditRoleMappingPage extends Component { } } - private backToRoleMappingsList = () => { - window.location.hash = ROLE_MAPPINGS_PATH; - }; + private backToRoleMappingsList = () => this.props.history.push('/'); } diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/create_role_mapping_button/create_role_mapping_button.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/create_role_mapping_button/create_role_mapping_button.tsx index 6fe4bcc7a0bbb..7330dba968162 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/create_role_mapping_button/create_role_mapping_button.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/create_role_mapping_button/create_role_mapping_button.tsx @@ -7,11 +7,21 @@ import React from 'react'; import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getCreateRoleMappingHref } from '../../../management_urls'; +import { ScopedHistory } from 'kibana/public'; +import { EDIT_ROLE_MAPPING_PATH } from '../../../management_urls'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; -export const CreateRoleMappingButton = () => { +interface CreateRoleMappingButtonProps { + history: ScopedHistory; +} + +export const CreateRoleMappingButton = ({ history }: CreateRoleMappingButtonProps) => { return ( - + = () => ( +interface EmptyPromptProps { + history: ScopedHistory; +} + +export const EmptyPrompt: React.FunctionComponent = ({ history }) => ( = () => (

} - actions={} + actions={} data-test-subj="roleMappingsEmptyPrompt" /> ); diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx index 0d343ad33d78e..fb81ddb641e1f 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx @@ -5,6 +5,7 @@ */ import React from 'react'; +import { CoreStart, ScopedHistory } from 'kibana/public'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { RoleMappingsGridPage } from '.'; import { SectionLoading, PermissionDenied, NoCompatibleRealms } from '../components'; @@ -14,11 +15,19 @@ import { EuiLink } from '@elastic/eui'; import { act } from '@testing-library/react'; import { DocumentationLinksService } from '../documentation_links'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../src/core/public/mocks'; import { roleMappingsAPIClientMock } from '../role_mappings_api_client.mock'; import { rolesAPIClientMock } from '../../roles/index.mock'; describe('RoleMappingsGridPage', () => { + let history: ScopedHistory; + let coreStart: CoreStart; + + beforeEach(() => { + history = (scopedHistoryMock.create() as unknown) as ScopedHistory; + coreStart = coreMock.createStart(); + }); + it('renders an empty prompt when no role mappings exist', async () => { const roleMappingsAPI = roleMappingsAPIClientMock.create(); roleMappingsAPI.getRoleMappings.mockResolvedValue([]); @@ -34,6 +43,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); expect(wrapper.find(SectionLoading)).toHaveLength(1); @@ -61,6 +72,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); expect(wrapper.find(SectionLoading)).toHaveLength(1); @@ -96,6 +109,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); expect(wrapper.find(SectionLoading)).toHaveLength(1); @@ -130,6 +145,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); await nextTick(); @@ -137,9 +154,7 @@ describe('RoleMappingsGridPage', () => { const links = findTestSubject(wrapper, 'roleMappingRoles').find(EuiLink); expect(links).toHaveLength(1); - expect(links.at(0).props()).toMatchObject({ - href: '#/management/security/roles/edit/superuser', - }); + expect(links.at(0).props().onClick).toBeDefined(); }); it('describes the number of mapped role templates', async () => { @@ -164,6 +179,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); await nextTick(); @@ -202,6 +219,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); await nextTick(); @@ -263,6 +282,8 @@ describe('RoleMappingsGridPage', () => { roleMappingsAPI={roleMappingsAPI} notifications={notifications} docLinks={new DocumentationLinksService(docLinks)} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); await nextTick(); diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx index d0bc96b4fcedf..757e59a4e0583 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx @@ -24,7 +24,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NotificationsStart } from 'src/core/public'; +import { NotificationsStart, ApplicationStart, ScopedHistory } from 'src/core/public'; import { RoleMapping, Role } from '../../../../common/model'; import { EmptyPrompt } from './empty_prompt'; import { @@ -33,18 +33,21 @@ import { PermissionDenied, SectionLoading, } from '../components'; -import { getCreateRoleMappingHref, getEditRoleMappingHref } from '../../management_urls'; +import { EDIT_ROLE_MAPPING_PATH, getEditRoleMappingHref } from '../../management_urls'; import { DocumentationLinksService } from '../documentation_links'; import { RoleMappingsAPIClient } from '../role_mappings_api_client'; import { RoleTableDisplay } from '../../role_table_display'; import { RolesAPIClient } from '../../roles'; import { EnabledBadge, DisabledBadge } from '../../badges'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; interface Props { rolesAPIClient: PublicMethodsOf; roleMappingsAPI: PublicMethodsOf; notifications: NotificationsStart; docLinks: DocumentationLinksService; + history: ScopedHistory; + navigateToApp: ApplicationStart['navigateToApp']; } interface State { @@ -119,7 +122,7 @@ export class RoleMappingsGridPage extends Component { if (loadState === 'finished' && roleMappings && roleMappings.length === 0) { return ( - + ); } @@ -160,7 +163,10 @@ export class RoleMappingsGridPage extends Component { - + { render: (roleMappingName: string) => { return ( {roleMappingName} @@ -323,7 +329,13 @@ export class RoleMappingsGridPage extends Component { const role: Role | string = this.state.roles?.find((r) => r.name === rolename) ?? rolename; - return ; + return ( + + ); }); return
{roleLinks}
; }, @@ -367,7 +379,10 @@ export class RoleMappingsGridPage extends Component { iconType="pencil" color="primary" data-test-subj={`editRoleMappingButton-${record.name}`} - href={getEditRoleMappingHref(record.name)} + {...reactRouterNavigate( + this.props.history, + getEditRoleMappingHref(record.name) + )} /> ); diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx index 5907413d7299e..c95d78f90f51a 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx @@ -12,17 +12,22 @@ jest.mock('./edit_role_mapping', () => ({ EditRoleMappingPage: (props: any) => `Role Mapping Edit Page: ${JSON.stringify(props)}`, })); +import { ScopedHistory } from 'src/core/public'; import { roleMappingsManagementApp } from './role_mappings_management_app'; +import { coreMock, scopedHistoryMock } from '../../../../../../src/core/public/mocks'; -import { coreMock } from '../../../../../../src/core/public/mocks'; - -async function mountApp(basePath: string) { +async function mountApp(basePath: string, pathname: string) { const container = document.createElement('div'); const setBreadcrumbs = jest.fn(); const unmount = await roleMappingsManagementApp .create({ getStartServices: coreMock.createSetup().getStartServices as any }) - .mount({ basePath, element: container, setBreadcrumbs }); + .mount({ + basePath, + element: container, + setBreadcrumbs, + history: (scopedHistoryMock.create({ pathname }) as unknown) as ScopedHistory, + }); return { unmount, container, setBreadcrumbs }; } @@ -44,16 +49,13 @@ describe('roleMappingsManagementApp', () => { }); it('mount() works for the `grid` page', async () => { - const basePath = '/some-base-path/role_mappings'; - window.location.hash = basePath; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `#${basePath}`, text: 'Role Mappings' }]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Role Mappings' }]); expect(container).toMatchInlineSnapshot(`
- Role Mappings Page: {"notifications":{"toasts":{}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"}} + Role Mappings Page: {"notifications":{"toasts":{}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}}}
`); @@ -63,19 +65,16 @@ describe('roleMappingsManagementApp', () => { }); it('mount() works for the `create role mapping` page', async () => { - const basePath = '/some-base-path/role_mappings'; - window.location.hash = `${basePath}/edit`; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/edit'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Role Mappings' }, + { href: `/`, text: 'Role Mappings' }, { text: 'Create' }, ]); expect(container).toMatchInlineSnapshot(`
- Role Mapping Edit Page: {"roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"}} + Role Mapping Edit Page: {"roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit","search":"","hash":""}}}
`); @@ -85,20 +84,18 @@ describe('roleMappingsManagementApp', () => { }); it('mount() works for the `edit role mapping` page', async () => { - const basePath = '/some-base-path/role_mappings'; const roleMappingName = 'someRoleMappingName'; - window.location.hash = `${basePath}/edit/${roleMappingName}`; - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', `/edit/${roleMappingName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Role Mappings' }, - { href: `#/some-base-path/role_mappings/edit/${roleMappingName}`, text: roleMappingName }, + { href: `/`, text: 'Role Mappings' }, + { href: `/edit/${roleMappingName}`, text: roleMappingName }, ]); expect(container).toMatchInlineSnapshot(`
- Role Mapping Edit Page: {"name":"someRoleMappingName","roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"}} + Role Mapping Edit Page: {"name":"someRoleMappingName","roleMappingsAPI":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/someRoleMappingName","search":"","hash":""}}}
`); @@ -108,18 +105,15 @@ describe('roleMappingsManagementApp', () => { }); it('mount() properly encodes role mapping name in `edit role mapping` page link in breadcrumbs', async () => { - const basePath = '/some-base-path/role_mappings'; const roleMappingName = 'some 安全性 role mapping'; - window.location.hash = `${basePath}/edit/${roleMappingName}`; - const { setBreadcrumbs } = await mountApp(basePath); + const { setBreadcrumbs } = await mountApp('/', `/edit/${roleMappingName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Role Mappings' }, + { href: `/`, text: 'Role Mappings' }, { - href: - '#/some-base-path/role_mappings/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20role%20mapping', + href: '/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20role%20mapping', text: roleMappingName, }, ]); diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx index ffb6d6d98f180..bca3a070e64f9 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter as Router, Route, Switch, useParams } from 'react-router-dom'; +import { Router, Route, Switch, useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { StartServicesAccessor } from 'src/core/public'; import { RegisterManagementAppArgs } from '../../../../../../src/plugins/management/public'; @@ -26,13 +26,14 @@ export const roleMappingsManagementApp = Object.freeze({ title: i18n.translate('xpack.security.management.roleMappingsTitle', { defaultMessage: 'Role Mappings', }), - async mount({ basePath, element, setBreadcrumbs }) { + async mount({ element, setBreadcrumbs, history }) { + const [coreStart] = await getStartServices(); const roleMappingsBreadcrumbs = [ { text: i18n.translate('xpack.security.roleMapping.breadcrumb', { defaultMessage: 'Role Mappings', }), - href: `#${basePath}`, + href: `/`, }, ]; @@ -60,6 +61,8 @@ export const roleMappingsManagementApp = Object.freeze({ rolesAPIClient={new RolesAPIClient(http)} roleMappingsAPI={roleMappingsAPIClient} docLinks={dockLinksService} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); }; @@ -70,7 +73,7 @@ export const roleMappingsManagementApp = Object.freeze({ setBreadcrumbs([ ...roleMappingsBreadcrumbs, name - ? { text: name, href: `#${basePath}/edit/${encodeURIComponent(name)}` } + ? { text: name, href: `/edit/${encodeURIComponent(name)}` } : { text: i18n.translate('xpack.security.roleMappings.createBreadcrumb', { defaultMessage: 'Create', @@ -85,15 +88,16 @@ export const roleMappingsManagementApp = Object.freeze({ rolesAPIClient={new RolesAPIClient(http)} notifications={notifications} docLinks={dockLinksService} + history={history} /> ); }; render( - + - + diff --git a/x-pack/plugins/security/public/management/role_table_display/role_table_display.tsx b/x-pack/plugins/security/public/management/role_table_display/role_table_display.tsx index 28978f0090011..c1349eba9cddc 100644 --- a/x-pack/plugins/security/public/management/role_table_display/role_table_display.tsx +++ b/x-pack/plugins/security/public/management/role_table_display/role_table_display.tsx @@ -6,19 +6,20 @@ import React from 'react'; import { EuiLink, EuiToolTip, EuiIcon } from '@elastic/eui'; +import { ApplicationStart } from 'kibana/public'; import { Role, isRoleDeprecated, getExtendedRoleDeprecationNotice } from '../../../common/model'; -import { getEditRoleHref } from '../management_urls'; interface Props { role: Role | string; + navigateToApp: ApplicationStart['navigateToApp']; } -export const RoleTableDisplay = ({ role }: Props) => { +export const RoleTableDisplay = ({ role, navigateToApp }: Props) => { let content; - let href; + let path: string; if (typeof role === 'string') { content =
{role}
; - href = getEditRoleHref(role); + path = `security/roles/edit/${encodeURIComponent(role)}`; } else if (isRoleDeprecated(role)) { content = ( {
); - href = getEditRoleHref(role.name); + path = `security/roles/edit/${encodeURIComponent(role.name)}`; } else { content =
{role.name}
; - href = getEditRoleHref(role.name); + path = `security/roles/edit/${encodeURIComponent(role.name)}`; } - return {content}; + + return navigateToApp('management', { path })}>{content}; }; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx index f1ee681331005..afb8b6ec5dbe0 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx @@ -8,7 +8,7 @@ import { ReactWrapper } from 'enzyme'; import React from 'react'; import { act } from '@testing-library/react'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { Capabilities } from 'src/core/public'; +import { Capabilities, ScopedHistory } from 'src/core/public'; import { Feature } from '../../../../../features/public'; import { Role } from '../../../../common/model'; import { DocumentationLinksService } from '../documentation_links'; @@ -16,7 +16,7 @@ import { EditRolePage } from './edit_role_page'; import { SimplePrivilegeSection } from './privileges/kibana/simple_privilege_section'; import { TransformErrorSection } from './privileges/kibana/transform_error_section'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../src/core/public/mocks'; import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; import { licenseMock } from '../../../../common/licensing/index.mock'; import { userAPIClientMock } from '../../users/index.mock'; @@ -183,6 +183,7 @@ function getProps({ fatalErrors, spacesEnabled, uiCapabilities: buildUICapabilities(canManageSpaces), + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, }; } diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx index 3688f31be219b..77f4455d813c6 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx @@ -26,6 +26,7 @@ import React, { Fragment, FunctionComponent, HTMLProps, + useCallback, useEffect, useRef, useState, @@ -37,6 +38,7 @@ import { IHttpFetchError, NotificationsStart, } from 'src/core/public'; +import { ScopedHistory } from 'kibana/public'; import { FeaturesPluginStart } from '../../../../../features/public'; import { Feature } from '../../../../../features/common'; import { IndexPatternsContract } from '../../../../../../../src/plugins/data/public'; @@ -53,7 +55,6 @@ import { RoleIndexPrivilege, getExtendedRoleDeprecationNotice, } from '../../../../common/model'; -import { ROLES_PATH } from '../../management_urls'; import { RoleValidationResult, RoleValidator } from './validate_role'; import { DeleteRoleButton } from './delete_role_button'; import { ElasticsearchPrivileges, KibanaPrivilegesRegion } from './privileges'; @@ -65,6 +66,7 @@ import { IndicesAPIClient } from '../indices_api_client'; import { RolesAPIClient } from '../roles_api_client'; import { PrivilegesAPIClient } from '../privileges_api_client'; import { KibanaPrivileges } from '../model'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; interface Props { action: 'edit' | 'clone'; @@ -82,6 +84,7 @@ interface Props { uiCapabilities: Capabilities; notifications: NotificationsStart; fatalErrors: FatalErrorsSetup; + history: ScopedHistory; } function useRunAsUsers( @@ -156,6 +159,7 @@ function useRole( notifications: NotificationsStart, license: SecurityLicense, action: string, + backToRoleList: () => void, roleName?: string ) { const [role, setRole] = useState(null); @@ -216,7 +220,7 @@ function useRole( fatalErrors.add(err); } }); - }, [roleName, action, fatalErrors, rolesAPIClient, notifications, license]); + }, [roleName, action, fatalErrors, rolesAPIClient, notifications, license, backToRoleList]); return [role, setRole] as [Role | null, typeof setRole]; } @@ -263,10 +267,6 @@ function useFeatures( return features; } -function backToRoleList() { - window.location.hash = ROLES_PATH; -} - export const EditRolePage: FunctionComponent = ({ userAPIClient, indexPatterns, @@ -283,7 +283,10 @@ export const EditRolePage: FunctionComponent = ({ docLinks, uiCapabilities, notifications, + history, }) => { + const backToRoleList = useCallback(() => history.push('/'), [history]); + // We should keep the same mutable instance of Validator for every re-render since we'll // eventually enable validation after the first time user tries to save a role. const { current: validator } = useRef(new RoleValidator({ shouldValidate: false })); @@ -300,6 +303,7 @@ export const EditRolePage: FunctionComponent = ({ notifications, license, action, + backToRoleList, roleName ); @@ -460,7 +464,7 @@ export const EditRolePage: FunctionComponent = ({ const getReturnToRoleListButton = () => { return ( - + = ({
{getFormTitle()} - - {description} - {isRoleReserved && ( @@ -584,7 +585,6 @@ export const EditRolePage: FunctionComponent = ({ )} - {isDeprecatedRole && ( @@ -595,17 +595,11 @@ export const EditRolePage: FunctionComponent = ({ /> )} - - {getRoleName()} - {getElasticsearchPrivileges()} - {getKibanaPrivileges()} - - {getFormButtons()}
diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx index e0a7d96d1cf72..743510d45107e 100644 --- a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx @@ -12,10 +12,11 @@ import { RolesAPIClient } from '../roles_api_client'; import { PermissionDenied } from './permission_denied'; import { RolesGridPage } from './roles_grid_page'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../src/core/public/mocks'; import { rolesAPIClientMock } from '../index.mock'; import { ReservedBadge, DisabledBadge } from '../../badges'; import { findTestSubject } from 'test_utils/find_test_subject'; +import { ScopedHistory } from 'kibana/public'; const mock403 = () => ({ body: { statusCode: 403 } }); @@ -41,7 +42,10 @@ const waitForRender = async ( describe('', () => { let apiClientMock: jest.Mocked>; + let history: ScopedHistory; + beforeEach(() => { + history = (scopedHistoryMock.create() as unknown) as ScopedHistory; apiClientMock = rolesAPIClientMock.create(); apiClientMock.getRoles.mockResolvedValue([ { @@ -68,6 +72,7 @@ describe('', () => { const wrapper = mountWithIntl( ); @@ -85,6 +90,7 @@ describe('', () => { const wrapper = mountWithIntl( ); @@ -104,6 +110,7 @@ describe('', () => { const wrapper = mountWithIntl( ); @@ -117,6 +124,7 @@ describe('', () => { const wrapper = mountWithIntl( ); @@ -146,6 +154,7 @@ describe('', () => { const wrapper = mountWithIntl( ); diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx index 4f0d7ca8621a3..051c16f03d342 100644 --- a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.tsx @@ -26,6 +26,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { NotificationsStart } from 'src/core/public'; +import { ScopedHistory } from 'kibana/public'; import { Role, isRoleEnabled, @@ -38,10 +39,12 @@ import { RolesAPIClient } from '../roles_api_client'; import { ConfirmDelete } from './confirm_delete'; import { PermissionDenied } from './permission_denied'; import { DisabledBadge, DeprecatedBadge, ReservedBadge } from '../../badges'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; interface Props { notifications: NotificationsStart; rolesAPIClient: PublicMethodsOf; + history: ScopedHistory; } interface State { @@ -55,7 +58,7 @@ interface State { } const getRoleManagementHref = (action: 'edit' | 'clone', roleName?: string) => { - return `#/management/security/roles/${action}${roleName ? `/${roleName}` : ''}`; + return `/${action}${roleName ? `/${roleName}` : ''}`; }; export class RolesGridPage extends Component { @@ -106,7 +109,10 @@ export class RolesGridPage extends Component {
- + { render: (name: string, record: Role) => { return ( - + {name} @@ -228,7 +237,10 @@ export class RolesGridPage extends Component { title={title} color={'primary'} iconType={'pencil'} - href={getRoleManagementHref('edit', role.name)} + {...reactRouterNavigate( + this.props.history, + getRoleManagementHref('edit', role.name) + )} /> ); }, @@ -248,7 +260,10 @@ export class RolesGridPage extends Component { title={title} color={'primary'} iconType={'copy'} - href={getRoleManagementHref('clone', role.name)} + {...reactRouterNavigate( + this.props.history, + getRoleManagementHref('edit', role.name) + )} /> ); }, diff --git a/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx index 96051dbd7fa56..e7f38c86b045e 100644 --- a/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx @@ -14,12 +14,14 @@ jest.mock('./edit_role', () => ({ EditRolePage: (props: any) => `Role Edit Page: ${JSON.stringify(props)}`, })); +import { ScopedHistory } from 'src/core/public'; + import { rolesManagementApp } from './roles_management_app'; -import { coreMock } from '../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../src/core/public/mocks'; import { featuresPluginMock } from '../../../../features/public/mocks'; -async function mountApp(basePath: string) { +async function mountApp(basePath: string, pathname: string) { const { fatalErrors } = coreMock.createSetup(); const container = document.createElement('div'); const setBreadcrumbs = jest.fn(); @@ -34,7 +36,12 @@ async function mountApp(basePath: string) { .fn() .mockResolvedValue([coreMock.createStart(), { data: {}, features: featuresStart }]), }) - .mount({ basePath, element: container, setBreadcrumbs }); + .mount({ + basePath, + element: container, + setBreadcrumbs, + history: (scopedHistoryMock.create({ pathname }) as unknown) as ScopedHistory, + }); return { unmount, container, setBreadcrumbs }; } @@ -60,16 +67,13 @@ describe('rolesManagementApp', () => { }); it('mount() works for the `grid` page', async () => { - const basePath = '/some-base-path/roles'; - window.location.hash = basePath; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `#${basePath}`, text: 'Roles' }]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Roles' }]); expect(container).toMatchInlineSnapshot(`
- Roles Page: {"notifications":{"toasts":{}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}}} + Roles Page: {"notifications":{"toasts":{}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}}}
`); @@ -79,19 +83,13 @@ describe('rolesManagementApp', () => { }); it('mount() works for the `create role` page', async () => { - const basePath = '/some-base-path/roles'; - window.location.hash = `${basePath}/edit`; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/edit'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Roles' }, - { text: 'Create' }, - ]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Roles' }, { text: 'Create' }]); expect(container).toMatchInlineSnapshot(`
- Role Edit Page: {"action":"edit","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}}} + Role Edit Page: {"action":"edit","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit","search":"","hash":""}}}
`); @@ -101,20 +99,18 @@ describe('rolesManagementApp', () => { }); it('mount() works for the `edit role` page', async () => { - const basePath = '/some-base-path/roles'; const roleName = 'someRoleName'; - window.location.hash = `${basePath}/edit/${roleName}`; - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', `/edit/${roleName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Roles' }, - { href: `#/some-base-path/roles/edit/${roleName}`, text: roleName }, + { href: `/`, text: 'Roles' }, + { href: `/edit/${roleName}`, text: roleName }, ]); expect(container).toMatchInlineSnapshot(`
- Role Edit Page: {"action":"edit","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}}} + Role Edit Page: {"action":"edit","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/someRoleName","search":"","hash":""}}}
`); @@ -124,20 +120,15 @@ describe('rolesManagementApp', () => { }); it('mount() works for the `clone role` page', async () => { - const basePath = '/some-base-path/roles'; const roleName = 'someRoleName'; - window.location.hash = `${basePath}/clone/${roleName}`; - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', `/clone/${roleName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Roles' }, - { text: 'Create' }, - ]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Roles' }, { text: 'Create' }]); expect(container).toMatchInlineSnapshot(`
- Role Edit Page: {"action":"clone","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}}} + Role Edit Page: {"action":"clone","roleName":"someRoleName","rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"indicesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"privilegesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}},"notifications":{"toasts":{}},"fatalErrors":{},"license":{"features$":{"_isScalar":false}},"docLinks":{"esDocBasePath":"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/"},"uiCapabilities":{"catalogue":{},"management":{},"navLinks":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/clone/someRoleName","search":"","hash":""}}}
`); @@ -147,17 +138,15 @@ describe('rolesManagementApp', () => { }); it('mount() properly encodes role name in `edit role` page link in breadcrumbs', async () => { - const basePath = '/some-base-path/roles'; const roleName = 'some 安全性 role'; - window.location.hash = `${basePath}/edit/${roleName}`; - const { setBreadcrumbs } = await mountApp(basePath); + const { setBreadcrumbs } = await mountApp('/', `/edit/${roleName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Roles' }, + { href: `/`, text: 'Roles' }, { - href: '#/some-base-path/roles/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20role', + href: '/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20role', text: roleName, }, ]); diff --git a/x-pack/plugins/security/public/management/roles/roles_management_app.tsx b/x-pack/plugins/security/public/management/roles/roles_management_app.tsx index 9aaa3b47f3b19..8891809d0b934 100644 --- a/x-pack/plugins/security/public/management/roles/roles_management_app.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_management_app.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter as Router, Route, Switch, useParams } from 'react-router-dom'; +import { Router, Route, Switch, useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { StartServicesAccessor, FatalErrorsSetup } from 'src/core/public'; import { RegisterManagementAppArgs } from '../../../../../../src/plugins/management/public'; @@ -27,11 +27,11 @@ export const rolesManagementApp = Object.freeze({ id: this.id, order: 20, title: i18n.translate('xpack.security.management.rolesTitle', { defaultMessage: 'Roles' }), - async mount({ basePath, element, setBreadcrumbs }) { + async mount({ element, setBreadcrumbs, history }) { const rolesBreadcrumbs = [ { text: i18n.translate('xpack.security.roles.breadcrumb', { defaultMessage: 'Roles' }), - href: `#${basePath}`, + href: `/`, }, ]; @@ -59,7 +59,13 @@ export const rolesManagementApp = Object.freeze({ const rolesAPIClient = new RolesAPIClient(http); const RolesGridPageWithBreadcrumbs = () => { setBreadcrumbs(rolesBreadcrumbs); - return ; + return ( + + ); }; const EditRolePageWithBreadcrumbs = ({ action }: { action: 'edit' | 'clone' }) => { @@ -68,7 +74,7 @@ export const rolesManagementApp = Object.freeze({ setBreadcrumbs([ ...rolesBreadcrumbs, action === 'edit' && roleName - ? { text: roleName, href: `#${basePath}/edit/${encodeURIComponent(roleName)}` } + ? { text: roleName, href: `/edit/${encodeURIComponent(roleName)}` } : { text: i18n.translate('xpack.security.roles.createBreadcrumb', { defaultMessage: 'Create', @@ -95,15 +101,16 @@ export const rolesManagementApp = Object.freeze({ docLinks={new DocumentationLinksService(docLinks)} uiCapabilities={application.capabilities} indexPatterns={data.indexPatterns} + history={history} /> ); }; render( - + - + diff --git a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx index a97781ba25ea6..7ee33357b9af4 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx @@ -5,12 +5,13 @@ */ import { act } from '@testing-library/react'; +import { ScopedHistory } from 'kibana/public'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { EditUserPage } from './edit_user_page'; import React from 'react'; import { User, Role } from '../../../../common/model'; import { ReactWrapper } from 'enzyme'; -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../src/core/public/mocks'; import { mockAuthenticatedUser } from '../../../../common/model/authenticated_user.mock'; import { securityMock } from '../../../mocks'; import { rolesAPIClientMock } from '../../roles/index.mock'; @@ -103,6 +104,8 @@ function expectMissingSaveButton(wrapper: ReactWrapper) { } describe('EditUserPage', () => { + const history = (scopedHistoryMock.create() as unknown) as ScopedHistory; + it('allows reserved users to be viewed', async () => { const user = createUser('reserved_user'); const { apiClient, rolesAPIClient } = buildClients(user); @@ -114,6 +117,7 @@ describe('EditUserPage', () => { rolesAPIClient={rolesAPIClient} authc={securitySetup.authc} notifications={coreMock.createStart().notifications} + history={history} /> ); @@ -136,6 +140,7 @@ describe('EditUserPage', () => { rolesAPIClient={rolesAPIClient} authc={securitySetup.authc} notifications={coreMock.createStart().notifications} + history={history} /> ); @@ -158,6 +163,7 @@ describe('EditUserPage', () => { rolesAPIClient={rolesAPIClient} authc={securitySetup.authc} notifications={coreMock.createStart().notifications} + history={history} /> ); @@ -182,6 +188,7 @@ describe('EditUserPage', () => { rolesAPIClient={rolesAPIClient} authc={securitySetup.authc} notifications={coreMock.createStart().notifications} + history={history} /> ); @@ -204,6 +211,7 @@ describe('EditUserPage', () => { rolesAPIClient={rolesAPIClient} authc={securitySetup.authc} notifications={coreMock.createStart().notifications} + history={history} /> ); diff --git a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx index 49da4c66a7630..eea7edd62fbfa 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.tsx @@ -30,10 +30,9 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NotificationsStart } from 'src/core/public'; +import { NotificationsStart, ScopedHistory } from 'src/core/public'; import { User, EditUser, Role, isRoleDeprecated } from '../../../../common/model'; import { AuthenticationServiceSetup } from '../../../authentication'; -import { USERS_PATH } from '../../management_urls'; import { RolesAPIClient } from '../../roles'; import { ConfirmDeleteUsers, ChangePasswordForm } from '../components'; import { UserValidator, UserValidationResult } from './validate_user'; @@ -47,6 +46,7 @@ interface Props { rolesAPIClient: PublicMethodsOf; authc: AuthenticationServiceSetup; notifications: NotificationsStart; + history: ScopedHistory; } interface State { @@ -61,10 +61,6 @@ interface State { formError: UserValidationResult | null; } -function backToUserList() { - window.location.hash = USERS_PATH; -} - export class EditUserPage extends Component { private validator: UserValidator; @@ -102,6 +98,10 @@ export class EditUserPage extends Component { } } + private backToUserList() { + this.props.history.push('/'); + } + private async setCurrentUser() { const { username, userAPIClient, rolesAPIClient, notifications, authc } = this.props; let { user, currentUser } = this.state; @@ -120,7 +120,7 @@ export class EditUserPage extends Component { }), text: get(err, 'body.message') || err.message, }); - return backToUserList(); + return this.backToUserList(); } } @@ -148,7 +148,7 @@ export class EditUserPage extends Component { private handleDelete = (usernames: string[], errors: string[]) => { if (errors.length === 0) { - backToUserList(); + this.backToUserList(); } }; @@ -184,7 +184,7 @@ export class EditUserPage extends Component { ) ); - backToUserList(); + this.backToUserList(); } catch (e) { this.props.notifications.toasts.addDanger( i18n.translate('xpack.security.management.users.editUser.savingUserErrorMessage', { @@ -549,7 +549,7 @@ export class EditUserPage extends Component { {reserved && ( - + this.backToUserList()}> {
- + this.backToUserList()} + > { + let history: ScopedHistory; + let coreStart: CoreStart; + + beforeEach(() => { + history = (scopedHistoryMock.create() as unknown) as ScopedHistory; + history.createHref = (location: LocationDescriptorObject) => { + return `${location.pathname}${location.search ? '?' + location.search : ''}`; + }; + coreStart = coreMock.createStart(); + }); + it('renders the list of users', async () => { const apiClientMock = userAPIClientMock.create(); apiClientMock.getUsers.mockImplementation(() => { @@ -44,7 +57,9 @@ describe('UsersGridPage', () => { ); @@ -64,7 +79,9 @@ describe('UsersGridPage', () => { ); @@ -93,7 +110,9 @@ describe('UsersGridPage', () => { ); @@ -125,7 +144,9 @@ describe('UsersGridPage', () => { ); @@ -173,7 +194,9 @@ describe('UsersGridPage', () => { ); @@ -231,7 +254,9 @@ describe('UsersGridPage', () => { ); diff --git a/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx b/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx index 5fd2b4ec85589..50815808c4859 100644 --- a/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx +++ b/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.tsx @@ -23,19 +23,22 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NotificationsStart } from 'src/core/public'; +import { NotificationsStart, ApplicationStart, ScopedHistory } from 'src/core/public'; import { User, Role } from '../../../../common/model'; import { ConfirmDeleteUsers } from '../components'; import { isUserReserved, getExtendedUserDeprecationNotice, isUserDeprecated } from '../user_utils'; import { DisabledBadge, ReservedBadge, DeprecatedBadge } from '../../badges'; import { RoleTableDisplay } from '../../role_table_display'; import { RolesAPIClient } from '../../roles'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; import { UserAPIClient } from '..'; interface Props { userAPIClient: PublicMethodsOf; rolesAPIClient: PublicMethodsOf; notifications: NotificationsStart; + history: ScopedHistory; + navigateToApp: ApplicationStart['navigateToApp']; } interface State { @@ -70,6 +73,7 @@ export class UsersGridPage extends Component { public render() { const { users, roles, permissionDenied, showDeleteConfirmation, selection } = this.state; + if (permissionDenied) { return ( @@ -97,7 +101,6 @@ export class UsersGridPage extends Component { ); } - const path = '#/management/security/'; const columns: Array> = [ { field: 'username', @@ -107,7 +110,10 @@ export class UsersGridPage extends Component { sortable: true, truncateText: true, render: (username: string) => ( - + {username} ), @@ -144,7 +150,13 @@ export class UsersGridPage extends Component { render: (rolenames: string[]) => { const roleLinks = rolenames.map((rolename, index) => { const roleDefinition = roles?.find((role) => role.name === rolename) ?? rolename; - return ; + return ( + + ); }); return
{roleLinks}
; }, @@ -219,7 +231,10 @@ export class UsersGridPage extends Component { - + ({ EditUserPage: (props: any) => `User Edit Page: ${JSON.stringify(props)}`, })); +import { ScopedHistory } from 'src/core/public'; import { usersManagementApp } from './users_management_app'; -import { coreMock } from '../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../src/core/public/mocks'; import { securityMock } from '../../mocks'; -async function mountApp(basePath: string) { +async function mountApp(basePath: string, pathname: string) { const container = document.createElement('div'); const setBreadcrumbs = jest.fn(); @@ -26,7 +27,12 @@ async function mountApp(basePath: string) { authc: securityMock.createSetup().authc, getStartServices: coreMock.createSetup().getStartServices as any, }) - .mount({ basePath, element: container, setBreadcrumbs }); + .mount({ + basePath, + element: container, + setBreadcrumbs, + history: (scopedHistoryMock.create({ pathname }) as unknown) as ScopedHistory, + }); return { unmount, container, setBreadcrumbs }; } @@ -49,16 +55,13 @@ describe('usersManagementApp', () => { }); it('mount() works for the `grid` page', async () => { - const basePath = '/some-base-path/users'; - window.location.hash = basePath; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `#${basePath}`, text: 'Users' }]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Users' }]); expect(container).toMatchInlineSnapshot(`
- Users Page: {"notifications":{"toasts":{}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}}} + Users Page: {"notifications":{"toasts":{}},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}}}
`); @@ -68,19 +71,13 @@ describe('usersManagementApp', () => { }); it('mount() works for the `create user` page', async () => { - const basePath = '/some-base-path/users'; - window.location.hash = `${basePath}/edit`; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/edit'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Users' }, - { text: 'Create' }, - ]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Users' }, { text: 'Create' }]); expect(container).toMatchInlineSnapshot(`
- User Edit Page: {"authc":{},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}}} + User Edit Page: {"authc":{},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/edit","search":"","hash":""}}}
`); @@ -90,20 +87,18 @@ describe('usersManagementApp', () => { }); it('mount() works for the `edit user` page', async () => { - const basePath = '/some-base-path/users'; const userName = 'someUserName'; - window.location.hash = `${basePath}/edit/${userName}`; - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', `/edit/${userName}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Users' }, - { href: `#/some-base-path/users/edit/${userName}`, text: userName }, + { href: `/`, text: 'Users' }, + { href: `/edit/${userName}`, text: userName }, ]); expect(container).toMatchInlineSnapshot(`
- User Edit Page: {"authc":{},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"username":"someUserName"} + User Edit Page: {"authc":{},"userAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"rolesAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{}}},"notifications":{"toasts":{}},"username":"someUserName","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/someUserName","search":"","hash":""}}}
`); @@ -113,17 +108,15 @@ describe('usersManagementApp', () => { }); it('mount() properly encodes user name in `edit user` page link in breadcrumbs', async () => { - const basePath = '/some-base-path/users'; const username = 'some 安全性 user'; - window.location.hash = `${basePath}/edit/${username}`; - const { setBreadcrumbs } = await mountApp(basePath); + const { setBreadcrumbs } = await mountApp('/', `/edit/${username}`); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Users' }, + { href: `/`, text: 'Users' }, { - href: '#/some-base-path/users/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20user', + href: '/edit/some%20%E5%AE%89%E5%85%A8%E6%80%A7%20user', text: username, }, ]); diff --git a/x-pack/plugins/security/public/management/users/users_management_app.tsx b/x-pack/plugins/security/public/management/users/users_management_app.tsx index 9d337c1508ad4..82c55d67b9026 100644 --- a/x-pack/plugins/security/public/management/users/users_management_app.tsx +++ b/x-pack/plugins/security/public/management/users/users_management_app.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter as Router, Route, Switch, useParams } from 'react-router-dom'; +import { Router, Route, Switch, useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { StartServicesAccessor } from 'src/core/public'; import { RegisterManagementAppArgs } from '../../../../../../src/plugins/management/public'; @@ -25,11 +25,12 @@ export const usersManagementApp = Object.freeze({ id: this.id, order: 10, title: i18n.translate('xpack.security.management.usersTitle', { defaultMessage: 'Users' }), - async mount({ basePath, element, setBreadcrumbs }) { + async mount({ element, setBreadcrumbs, history }) { + const [coreStart] = await getStartServices(); const usersBreadcrumbs = [ { text: i18n.translate('xpack.security.users.breadcrumb', { defaultMessage: 'Users' }), - href: `#${basePath}`, + href: `/`, }, ]; @@ -56,6 +57,8 @@ export const usersManagementApp = Object.freeze({ notifications={notifications} userAPIClient={userAPIClient} rolesAPIClient={rolesAPIClient} + history={history} + navigateToApp={coreStart.application.navigateToApp} /> ); }; @@ -66,7 +69,7 @@ export const usersManagementApp = Object.freeze({ setBreadcrumbs([ ...usersBreadcrumbs, username - ? { text: username, href: `#${basePath}/edit/${encodeURIComponent(username)}` } + ? { text: username, href: `/edit/${encodeURIComponent(username)}` } : { text: i18n.translate('xpack.security.users.createBreadcrumb', { defaultMessage: 'Create', @@ -81,15 +84,16 @@ export const usersManagementApp = Object.freeze({ rolesAPIClient={new RolesAPIClient(http)} notifications={notifications} username={username} + history={history} /> ); }; render( - + - + diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx index 38ef552e75a9e..da69dd051c11d 100644 --- a/x-pack/plugins/security/public/plugin.tsx +++ b/x-pack/plugins/security/public/plugin.tsx @@ -122,7 +122,7 @@ export class SecurityPlugin 'Protect your data and easily manage who has access to what with users and roles.', }), icon: 'securityApp', - path: '/app/kibana#/management/security/users', + path: '/app/management/security/users', showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }); diff --git a/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/upgrade_contents.test.tsx.snap b/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/upgrade_contents.test.tsx.snap index 87cf9fb18adf3..546fa78babe22 100644 --- a/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/upgrade_contents.test.tsx.snap +++ b/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/upgrade_contents.test.tsx.snap @@ -50,7 +50,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` grow={false} > diff --git a/x-pack/plugins/siem/public/common/components/ml_popover/upgrade_contents.tsx b/x-pack/plugins/siem/public/common/components/ml_popover/upgrade_contents.tsx index eda6b0f28499c..e9efd7df23903 100644 --- a/x-pack/plugins/siem/public/common/components/ml_popover/upgrade_contents.tsx +++ b/x-pack/plugins/siem/public/common/components/ml_popover/upgrade_contents.tsx @@ -59,7 +59,7 @@ export const UpgradeContentsComponent = () => (
diff --git a/x-pack/plugins/siem/public/common/components/news_feed/no_news/index.tsx b/x-pack/plugins/siem/public/common/components/news_feed/no_news/index.tsx index c4e0482c6b30a..8061a8f9799e3 100644 --- a/x-pack/plugins/siem/public/common/components/news_feed/no_news/index.tsx +++ b/x-pack/plugins/siem/public/common/components/news_feed/no_news/index.tsx @@ -13,7 +13,7 @@ export const NoNews = React.memo(() => ( <> {i18n.NO_NEWS_MESSAGE}{' '} - + {i18n.ADVANCED_SETTINGS_LINK_TITLE} {'.'} diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx index 827fea7021eb4..c4f4876b8a1cd 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/setup_environment.tsx @@ -8,8 +8,10 @@ import React from 'react'; import axios from 'axios'; import axiosXhrAdapter from 'axios/lib/adapters/xhr'; import { i18n } from '@kbn/i18n'; +import { LocationDescriptorObject } from 'history'; +import { ScopedHistory } from 'kibana/public'; -import { coreMock } from 'src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from 'src/core/public/mocks'; import { setUiMetricService, httpService } from '../../../public/application/services/http'; import { breadcrumbService, @@ -23,10 +25,16 @@ import { documentationLinksService } from '../../../public/application/services/ const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); +const history = (scopedHistoryMock.create() as unknown) as ScopedHistory; +history.createHref = (location: LocationDescriptorObject) => { + return `${location.pathname}?${location.search}`; +}; + export const services = { uiMetricService: new UiMetricService('snapshot_restore'), httpService, i18n, + history, }; setUiMetricService(services.uiMetricService); diff --git a/x-pack/plugins/snapshot_restore/public/application/app.tsx b/x-pack/plugins/snapshot_restore/public/application/app.tsx index 8e57e79c08ee1..6174ff022b7ea 100644 --- a/x-pack/plugins/snapshot_restore/public/application/app.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/app.tsx @@ -17,7 +17,7 @@ import { NotAuthorizedSection, } from '../shared_imports'; import { SectionLoading } from './components'; -import { BASE_PATH, DEFAULT_SECTION, Section } from './constants'; +import { DEFAULT_SECTION, Section } from './constants'; import { RepositoryAdd, RepositoryEdit, @@ -63,34 +63,23 @@ export const App: React.FunctionComponent = () => { ) : hasPrivileges ? (
- + + - - + - {slmUi.enabled && ( - - )} - {slmUi.enabled && ( - - )} - + {slmUi.enabled && } + {slmUi.enabled && } + +
) : ( diff --git a/x-pack/plugins/snapshot_restore/public/application/app_context.tsx b/x-pack/plugins/snapshot_restore/public/application/app_context.tsx index 8ad05b3de5e98..d50f051c37fc0 100644 --- a/x-pack/plugins/snapshot_restore/public/application/app_context.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/app_context.tsx @@ -7,7 +7,7 @@ import React, { createContext, useContext } from 'react'; import { i18n } from '@kbn/i18n'; -import { CoreStart } from '../../../../../src/core/public'; +import { CoreStart, ScopedHistory } from '../../../../../src/core/public'; import { ClientConfigType } from '../types'; import { HttpService, UiMetricService } from './services'; @@ -19,6 +19,7 @@ export interface AppDependencies { httpService: HttpService; uiMetricService: UiMetricService; i18n: typeof i18n; + history: ScopedHistory; }; config: ClientConfigType; } diff --git a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx index 21e6bc6530707..8a7338f4db4e7 100644 --- a/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/components/policy_form/steps/step_logistics.tsx @@ -30,6 +30,8 @@ import { documentationLinksService } from '../../../services/documentation'; import { SectionLoading } from '../../'; import { StepProps } from './'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; + export const PolicyStepLogistics: React.FunctionComponent = ({ policy, updatePolicy, @@ -50,7 +52,7 @@ export const PolicyStepLogistics: React.FunctionComponent = ({ sendRequest: reloadRepositories, } = useLoadRepositories(); - const { i18n } = useServices(); + const { i18n, history } = useServices(); // State for touched inputs const [touched, setTouched] = useState({ @@ -226,7 +228,7 @@ export const PolicyStepLogistics: React.FunctionComponent = ({ }} actions={ ( - +interface AppWithRouterProps { + history: ScopedHistory; +} + +const AppWithRouter = ({ history }: AppWithRouterProps) => ( + - + ); export const renderApp = (elem: Element, dependencies: AppDependencies) => { render( - + , elem ); diff --git a/x-pack/plugins/snapshot_restore/public/application/mount_management_section.ts b/x-pack/plugins/snapshot_restore/public/application/mount_management_section.ts index 9697e24a7147e..3f28e78c6028c 100644 --- a/x-pack/plugins/snapshot_restore/public/application/mount_management_section.ts +++ b/x-pack/plugins/snapshot_restore/public/application/mount_management_section.ts @@ -23,7 +23,7 @@ export async function mountManagementSection( config: ClientConfigType, params: ManagementAppMountParams ) { - const { element, setBreadcrumbs } = params; + const { element, setBreadcrumbs, history } = params; const [core] = await coreSetup.getStartServices(); const { docLinks, @@ -41,6 +41,7 @@ export async function mountManagementSection( httpService, uiMetricService: services.uiMetricService, i18n, + history, }, }; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx index e1c20a76ba07c..5959ad6441f5d 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx @@ -41,6 +41,8 @@ import { } from '../../../../components'; import { TabSummary, TabHistory } from './tabs'; +import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public'; + interface Props { policyName: SlmPolicy['name']; onClose: () => void; @@ -62,7 +64,7 @@ export const PolicyDetails: React.FunctionComponent = ({ onPolicyDeleted, onPolicyExecuted, }) => { - const { i18n, uiMetricService } = useServices(); + const { i18n, uiMetricService, history } = useServices(); const { error, data: policyDetails, sendRequest: reload } = useLoadPolicy(policyName); const [activeTab, setActiveTab] = useState(TAB_SUMMARY); const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -258,7 +260,7 @@ export const PolicyDetails: React.FunctionComponent = ({ } ), icon: 'pencil', - href: linkToEditPolicy(policyName), + ...reactRouterNavigate(history, linkToEditPolicy(policyName)), }, { name: i18n.translate( @@ -317,9 +319,12 @@ export const PolicyDetails: React.FunctionComponent = ({ diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_history.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_history.tsx index 22c37241348e7..d6f379045a472 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_history.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_history.tsx @@ -23,6 +23,9 @@ import { import { SlmPolicy } from '../../../../../../../common/types'; import { FormattedDateTime } from '../../../../../components'; import { linkToSnapshot } from '../../../../../services/navigation'; +import { useServices } from '../../../../../app_context'; + +import { reactRouterNavigate } from '../../../../../../../../../../src/plugins/kibana_react/public'; interface Props { policy: SlmPolicy; @@ -30,6 +33,7 @@ interface Props { export const TabHistory: React.FunctionComponent = ({ policy }) => { const { lastSuccess, lastFailure, nextExecutionMillis, name, repository } = policy; + const { history } = useServices(); const renderLastSuccess = () => { if (!lastSuccess) { @@ -73,7 +77,11 @@ export const TabHistory: React.FunctionComponent = ({ policy }) => { - {snapshotName} + + {snapshotName} +
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_summary.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_summary.tsx index 053c4dc108e72..7bcee4f5f6621 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_summary.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/tabs/tab_summary.tsx @@ -25,12 +25,14 @@ import { useServices } from '../../../../../app_context'; import { FormattedDateTime, CollapsibleIndicesList } from '../../../../../components'; import { linkToSnapshots, linkToRepository } from '../../../../../services/navigation'; +import { reactRouterNavigate } from '../../../../../../../../../../src/plugins/kibana_react/public'; + interface Props { policy: SlmPolicy; } export const TabSummary: React.FunctionComponent = ({ policy }) => { - const { i18n } = useServices(); + const { i18n, history } = useServices(); const { version, @@ -180,7 +182,9 @@ export const TabSummary: React.FunctionComponent = ({ policy }) => { - {snapshotName} + + {snapshotName} +
@@ -193,7 +197,9 @@ export const TabSummary: React.FunctionComponent = ({ policy }) => { - {repository} + + {repository} +
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx index 050274f1319b5..39ef66eb0658c 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_list.tsx @@ -28,6 +28,8 @@ import { PolicyDetails } from './policy_details'; import { PolicyTable } from './policy_table'; import { PolicyRetentionSchedule } from './policy_retention_schedule'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; + interface MatchParams { policyName?: SlmPolicy['name']; } @@ -130,7 +132,7 @@ export const PolicyList: React.FunctionComponent Promise>; @@ -47,7 +49,7 @@ export const PolicyTable: React.FunctionComponent = ({ onPolicyDeleted, onPolicyExecuted, }) => { - const { i18n, uiMetricService } = useServices(); + const { i18n, uiMetricService, history } = useServices(); const [selectedItems, setSelectedItems] = useState([]); const columns = [ @@ -64,8 +66,9 @@ export const PolicyTable: React.FunctionComponent = ({ {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} uiMetricService.trackUiMetric(UIM_POLICY_SHOW_DETAILS_CLICK)} - href={openPolicyDetailsUrl(name)} + {...reactRouterNavigate(history, openPolicyDetailsUrl(name), () => + uiMetricService.trackUiMetric(UIM_POLICY_SHOW_DETAILS_CLICK) + )} data-test-subj="policyLink" > {name} @@ -249,7 +252,7 @@ export const PolicyTable: React.FunctionComponent = ({ )} iconType="pencil" color="primary" - href={linkToEditPolicy(name)} + {...reactRouterNavigate(history, linkToEditPolicy(name))} data-test-subj="editPolicyButton" /> @@ -373,7 +376,7 @@ export const PolicyTable: React.FunctionComponent = ({
, void; @@ -62,7 +64,7 @@ export const RepositoryDetails: React.FunctionComponent = ({ onClose, onRepositoryDeleted, }) => { - const { i18n } = useServices(); + const { i18n, history } = useServices(); const { error, data: repositoryDetails } = useLoadRepository(repositoryName); const [verification, setVerification] = useState(undefined); const [cleanup, setCleanup] = useState(undefined); @@ -162,7 +164,7 @@ export const RepositoryDetails: React.FunctionComponent = ({ ); } return ( - + = ({ - + = ({ openRepositoryDetailsUrl, onRepositoryDeleted, }) => { - const { i18n, uiMetricService } = useServices(); + const { i18n, uiMetricService, history } = useServices(); const [selectedItems, setSelectedItems] = useState([]); const columns = [ @@ -55,8 +57,9 @@ export const RepositoryTable: React.FunctionComponent = ({ {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} uiMetricService.trackUiMetric(UIM_REPOSITORY_SHOW_DETAILS_CLICK)} - href={openRepositoryDetailsUrl(name)} + {...reactRouterNavigate(history, openRepositoryDetailsUrl(name), () => + uiMetricService.trackUiMetric(UIM_REPOSITORY_SHOW_DETAILS_CLICK) + )} data-test-subj="repositoryLink" > {name} @@ -116,7 +119,7 @@ export const RepositoryTable: React.FunctionComponent = ({ )} iconType="pencil" color="primary" - href={linkToEditRepository(name)} + {...reactRouterNavigate(history, linkToEditRepository(name))} data-test-subj="editRepositoryButton" /> @@ -247,7 +250,7 @@ export const RepositoryTable: React.FunctionComponent = ({ , { currentInterval ); - const { uiMetricService } = useServices(); + const { uiMetricService, history } = useServices(); // Track component loaded useEffect(() => { @@ -110,7 +112,7 @@ export const RestoreList: React.FunctionComponent = () => { defaultMessage="Go to {snapshotsLink} to start a restore." values={{ snapshotsLink: ( - + = ({ onClose, onSnapshotDeleted, }) => { - const { i18n, uiMetricService } = useServices(); + const { i18n, uiMetricService, history } = useServices(); const { error, data: snapshotDetails } = useLoadSnapshot(repositoryName, snapshotId); const [activeTab, setActiveTab] = useState(TAB_SUMMARY); @@ -221,7 +223,10 @@ export const SnapshotDetails: React.FunctionComponent = ({ = ({

- + = ({ snapshotDetails }) => { policyName, } = snapshotDetails; + const { history } = useServices(); + return ( @@ -201,7 +206,9 @@ export const TabSummary: React.FC = ({ snapshotDetails }) => { - {policyName} + + {policyName} + ) : null} diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx index 43bdd973f3d45..2b8df7294c374 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_list.tsx @@ -27,6 +27,8 @@ import { useServices } from '../../../app_context'; import { SnapshotDetails } from './snapshot_details'; import { SnapshotTable } from './snapshot_table'; +import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public'; + interface MatchParams { repositoryName?: string; snapshotId?: string; @@ -141,7 +143,7 @@ export const SnapshotList: React.FunctionComponent +

{policies.length === 0 ? ( ) : ( + = ({ repositoryFilter, policyFilter, }) => { - const { i18n, uiMetricService } = useServices(); + const { i18n, uiMetricService, history } = useServices(); const [selectedItems, setSelectedItems] = useState([]); const lastSuccessfulManagedSnapshot = getLastSuccessfulManagedSnapshot(snapshots); @@ -74,8 +76,11 @@ export const SnapshotTable: React.FunctionComponent = ({ render: (snapshotId: string, snapshot: SnapshotDetails) => ( /* eslint-disable-next-line @elastic/eui/href-or-on-click */ uiMetricService.trackUiMetric(UIM_SNAPSHOT_SHOW_DETAILS_CLICK)} - href={openSnapshotDetailsUrl(snapshot.repository, snapshotId)} + {...reactRouterNavigate( + history, + openSnapshotDetailsUrl(snapshot.repository, snapshotId), + () => uiMetricService.trackUiMetric(UIM_SNAPSHOT_SHOW_DETAILS_CLICK) + )} data-test-subj="snapshotLink" > {snapshotId} @@ -90,7 +95,10 @@ export const SnapshotTable: React.FunctionComponent = ({ truncateText: true, sortable: true, render: (repositoryName: string) => ( - + {repositoryName} ), @@ -199,7 +207,7 @@ export const SnapshotTable: React.FunctionComponent = ({ iconType="importAction" color="primary" data-test-subj="srsnapshotListRestoreActionButton" - href={linkToRestoreSnapshot(repository, snapshot)} + {...reactRouterNavigate(history, linkToRestoreSnapshot(repository, snapshot))} isDisabled={!canRestore} /> diff --git a/x-pack/plugins/snapshot_restore/public/application/services/navigation/links.ts b/x-pack/plugins/snapshot_restore/public/application/services/navigation/links.ts index 6f95000726106..503704c6fe820 100644 --- a/x-pack/plugins/snapshot_restore/public/application/services/navigation/links.ts +++ b/x-pack/plugins/snapshot_restore/public/application/services/navigation/links.ts @@ -4,68 +4,60 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BASE_PATH } from '../../constants'; - export function linkToHome() { - return `#${BASE_PATH}`; + return `/`; } export function linkToRepositories() { - return `#${BASE_PATH}/repositories`; + return `/repositories`; } export function linkToRepository(repositoryName: string) { - return `#${BASE_PATH}/repositories/${encodeURIComponent(repositoryName)}`; + return `/repositories/${encodeURIComponent(repositoryName)}`; } export function linkToEditRepository(repositoryName: string) { - return `#${BASE_PATH}/edit_repository/${encodeURIComponent(repositoryName)}`; + return `/edit_repository/${encodeURIComponent(repositoryName)}`; } export function linkToAddRepository(redirect?: string) { - return `#${BASE_PATH}/add_repository${ - redirect ? `?redirect=${encodeURIComponent(redirect)}` : '' - }`; + return `/add_repository${redirect ? `?redirect=${encodeURIComponent(redirect)}` : ''}`; } export function linkToSnapshots(repositoryName?: string, policyName?: string) { if (repositoryName) { - return `#${BASE_PATH}/snapshots?repository=${encodeURIComponent(repositoryName)}`; + return `/snapshots?repository=${encodeURIComponent(repositoryName)}`; } if (policyName) { - return `#${BASE_PATH}/snapshots?policy=${encodeURIComponent(policyName)}`; + return `/snapshots?policy=${encodeURIComponent(policyName)}`; } - return `#${BASE_PATH}/snapshots`; + return `/snapshots`; } export function linkToSnapshot(repositoryName: string, snapshotName: string) { - return `#${BASE_PATH}/snapshots/${encodeURIComponent(repositoryName)}/${encodeURIComponent( - snapshotName - )}`; + return `/snapshots/${encodeURIComponent(repositoryName)}/${encodeURIComponent(snapshotName)}`; } export function linkToRestoreSnapshot(repositoryName: string, snapshotName: string) { - return `#${BASE_PATH}/restore/${encodeURIComponent(repositoryName)}/${encodeURIComponent( - snapshotName - )}`; + return `/restore/${encodeURIComponent(repositoryName)}/${encodeURIComponent(snapshotName)}`; } export function linkToPolicies() { - return `#${BASE_PATH}/policies`; + return `/policies`; } export function linkToPolicy(policyName: string) { - return `#${BASE_PATH}/policies/${encodeURIComponent(policyName)}`; + return `/policies/${encodeURIComponent(policyName)}`; } export function linkToEditPolicy(policyName: string) { - return `#${BASE_PATH}/edit_policy/${encodeURIComponent(policyName)}`; + return `/edit_policy/${encodeURIComponent(policyName)}`; } export function linkToAddPolicy() { - return `#${BASE_PATH}/add_policy`; + return `/add_policy`; } export function linkToRestoreStatus() { - return `#${BASE_PATH}/restore_status`; + return `/restore_status`; } diff --git a/x-pack/plugins/spaces/public/create_feature_catalogue_entry.ts b/x-pack/plugins/spaces/public/create_feature_catalogue_entry.ts index 2cf34e842ce33..15d141ccc328e 100644 --- a/x-pack/plugins/spaces/public/create_feature_catalogue_entry.ts +++ b/x-pack/plugins/spaces/public/create_feature_catalogue_entry.ts @@ -19,7 +19,7 @@ export const createSpacesFeatureCatalogueEntry = (): FeatureCatalogueEntry => { }), description: getSpacesFeatureDescription(), icon: 'spacesApp', - path: '/app/kibana#/management/kibana/spaces', + path: '/app/management/kibana/spaces', showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }; diff --git a/x-pack/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx b/x-pack/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx index 38d8451b658a8..3ce24f35ec8de 100644 --- a/x-pack/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx +++ b/x-pack/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx @@ -8,8 +8,13 @@ import { EuiHorizontalRule, EuiLink, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import React, { Fragment } from 'react'; +import { ApplicationStart } from 'kibana/public'; -export const SecureSpaceMessage = () => { +interface SecureSpaceMessageProps { + getUrlForApp: ApplicationStart['getUrlForApp']; +} + +export const SecureSpaceMessage = (props: SecureSpaceMessageProps) => { const rolesLinkTextAriaLabel = i18n.translate( 'xpack.spaces.management.secureSpaceMessage.rolesLinkTextAriaLabel', { defaultMessage: 'Roles management page' } @@ -24,7 +29,10 @@ export const SecureSpaceMessage = () => { defaultMessage="Want to assign a role to a space? Go to {rolesLink}." values={{ rolesLink: ( - + { + const getUrlForApp = (appId: string) => appId; + it(`renders as expected`, () => { expect( shallowWithIntl( @@ -44,6 +46,7 @@ describe('EnabledFeatures', () => { space={space} securityEnabled={true} onChange={jest.fn()} + getUrlForApp={getUrlForApp} /> ) ).toMatchSnapshot(); @@ -58,6 +61,7 @@ describe('EnabledFeatures', () => { space={space} securityEnabled={true} onChange={changeHandler} + getUrlForApp={getUrlForApp} /> ); @@ -86,6 +90,7 @@ describe('EnabledFeatures', () => { space={space} securityEnabled={true} onChange={changeHandler} + getUrlForApp={getUrlForApp} /> ); diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx index 6f0462a6ddcc2..9fe3bac73eeb1 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx @@ -8,6 +8,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText, EuiTitle } from import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component, Fragment, ReactNode } from 'react'; +import { ApplicationStart } from 'kibana/public'; import { FeatureConfig } from '../../../../../../plugins/features/public'; import { Space } from '../../../../common/model/space'; import { getEnabledFeatures } from '../../lib/feature_utils'; @@ -19,6 +20,7 @@ interface Props { features: FeatureConfig[]; securityEnabled: boolean; onChange: (space: Partial) => void; + getUrlForApp: ApplicationStart['getUrlForApp']; } export class EnabledFeatures extends Component { @@ -136,7 +138,9 @@ export class EnabledFeatures extends Component { defaultMessage="Want to secure access? Go to {rolesLink}." values={{ rolesLink: ( - + { + const getUrlForApp = (appId: string) => appId; + const history = (scopedHistoryMock.create() as unknown) as ScopedHistory; + it('allows a space to be created', async () => { const spacesManager = spacesManagerMock.create(); spacesManager.createSpace = jest.fn(spacesManager.createSpace); @@ -46,6 +51,8 @@ describe('ManageSpacePage', () => { getFeatures={featuresStart.getFeatures} notifications={notificationServiceMock.createStartContract()} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, @@ -103,6 +110,8 @@ describe('ManageSpacePage', () => { getFeatures={featuresStart.getFeatures} notifications={notificationServiceMock.createStartContract()} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, @@ -152,6 +161,8 @@ describe('ManageSpacePage', () => { getFeatures={() => Promise.reject(error)} notifications={notifications} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, @@ -187,6 +198,8 @@ describe('ManageSpacePage', () => { getFeatures={featuresStart.getFeatures} notifications={notificationServiceMock.createStartContract()} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, @@ -246,6 +259,8 @@ describe('ManageSpacePage', () => { getFeatures={featuresStart.getFeatures} notifications={notificationServiceMock.createStartContract()} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, diff --git a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx index 9931b4d113df6..e725310c41817 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx @@ -18,7 +18,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import _ from 'lodash'; import React, { Component, Fragment } from 'react'; -import { Capabilities, NotificationsStart } from 'src/core/public'; +import { ApplicationStart, Capabilities, NotificationsStart, ScopedHistory } from 'src/core/public'; import { Feature, FeaturesPluginStart } from '../../../../features/public'; import { isReservedSpace } from '../../../common'; import { Space } from '../../../common/model/space'; @@ -40,6 +40,8 @@ interface Props { onLoadSpace?: (space: Space) => void; capabilities: Capabilities; securityEnabled: boolean; + history: ScopedHistory; + getUrlForApp: ApplicationStart['getUrlForApp']; } interface State { @@ -154,6 +156,7 @@ export class ManageSpacePage extends Component { space={this.state.space} features={this.state.features} onChange={this.onSpaceChange} + getUrlForApp={this.props.getUrlForApp} securityEnabled={this.props.securityEnabled} /> @@ -195,7 +198,7 @@ export class ManageSpacePage extends Component { public maybeGetSecureSpacesMessage = () => { if (this.editingExistingSpace() && this.props.securityEnabled) { - return ; + return ; } return null; }; @@ -387,7 +390,9 @@ export class ManageSpacePage extends Component { } ) ); - window.location.hash = `#/management/kibana/spaces`; + + this.backToSpacesList(); + if (requireRefresh) { setTimeout(() => { window.location.reload(); @@ -408,9 +413,7 @@ export class ManageSpacePage extends Component { }); }; - private backToSpacesList = () => { - window.location.hash = `#/management/kibana/spaces`; - }; + private backToSpacesList = () => this.props.history.push('/'); private editingExistingSpace = () => !!this.props.spaceId; } diff --git a/x-pack/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap b/x-pack/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap index aa6db7e22fd5d..200c9c55a4f7c 100644 --- a/x-pack/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap +++ b/x-pack/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap @@ -39,7 +39,7 @@ exports[`SpacesGridPage renders as expected 1`] = ` > - +

`; diff --git a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx index d26b57ca7d80a..a98fae2561827 100644 --- a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx @@ -20,7 +20,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Capabilities, NotificationsStart } from 'src/core/public'; +import { ApplicationStart, Capabilities, NotificationsStart, ScopedHistory } from 'src/core/public'; import { Feature, FeaturesPluginStart } from '../../../../features/public'; import { isReservedSpace } from '../../../common'; import { DEFAULT_SPACE_ID } from '../../../common/constants'; @@ -32,6 +32,7 @@ import { ConfirmDeleteModal } from '../components/confirm_delete_modal'; import { SecureSpaceMessage } from '../components/secure_space_message'; import { UnauthorizedPrompt } from '../components/unauthorized_prompt'; import { getEnabledFeatures } from '../lib/feature_utils'; +import { reactRouterNavigate } from '../../../../../../src/plugins/kibana_react/public'; interface Props { spacesManager: SpacesManager; @@ -39,6 +40,8 @@ interface Props { getFeatures: FeaturesPluginStart['getFeatures']; capabilities: Capabilities; securityEnabled: boolean; + history: ScopedHistory; + getUrlForApp: ApplicationStart['getUrlForApp']; } interface State { @@ -71,7 +74,9 @@ export class SpacesGridPage extends Component { return (
{this.getPageContent()} - {this.props.securityEnabled && } + {this.props.securityEnabled && ( + + )} {this.getConfirmDeleteModal()}
); @@ -135,7 +140,7 @@ export class SpacesGridPage extends Component { public getPrimaryActionButton() { return ( - + { name: '', width: '50px', render: (value: string, record: Space) => ( - + ), @@ -256,7 +261,9 @@ export class SpacesGridPage extends Component { }), sortable: true, render: (value: string, record: Space) => ( - {value} + + {value} + ), }, { @@ -336,7 +343,7 @@ export class SpacesGridPage extends Component { )} color={'primary'} iconType={'pencil'} - href={this.getEditSpacePath(record)} + {...reactRouterNavigate(this.props.history, this.getEditSpacePath(record))} /> ), }, @@ -362,9 +369,7 @@ export class SpacesGridPage extends Component { ]; } - private getEditSpacePath = (space: Space) => { - return `#/management/kibana/spaces/edit/${encodeURIComponent(space.id)}`; - }; + private getEditSpacePath = (space: Space) => `edit/${encodeURIComponent(space.id)}`; private onDeleteSpaceClick = (space: Space) => { this.setState({ diff --git a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx index 9b7dc921b9a25..1868823823a1a 100644 --- a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx @@ -5,12 +5,13 @@ */ import React from 'react'; +import { ScopedHistory } from 'kibana/public'; import { mountWithIntl, shallowWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { SpaceAvatar } from '../../space_avatar'; import { spacesManagerMock } from '../../spaces_manager/mocks'; import { SpacesManager } from '../../spaces_manager'; import { SpacesGridPage } from './spaces_grid_page'; -import { httpServiceMock } from 'src/core/public/mocks'; +import { httpServiceMock, scopedHistoryMock } from 'src/core/public/mocks'; import { notificationServiceMock } from 'src/core/public/mocks'; import { featuresPluginMock } from '../../../../features/public/mocks'; import { Feature } from '../../../../features/public'; @@ -52,6 +53,9 @@ featuresStart.getFeatures.mockResolvedValue([ ]); describe('SpacesGridPage', () => { + const getUrlForApp = (appId: string) => appId; + const history = (scopedHistoryMock.create() as unknown) as ScopedHistory; + it('renders as expected', () => { const httpStart = httpServiceMock.createStartContract(); httpStart.get.mockResolvedValue([]); @@ -63,6 +67,8 @@ describe('SpacesGridPage', () => { getFeatures={featuresStart.getFeatures} notifications={notificationServiceMock.createStartContract()} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, @@ -84,6 +90,8 @@ describe('SpacesGridPage', () => { getFeatures={featuresStart.getFeatures} notifications={notificationServiceMock.createStartContract()} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, @@ -116,6 +124,8 @@ describe('SpacesGridPage', () => { getFeatures={featuresStart.getFeatures} notifications={notifications} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, @@ -149,6 +159,8 @@ describe('SpacesGridPage', () => { getFeatures={() => Promise.reject(error)} notifications={notifications} securityEnabled={true} + getUrlForApp={getUrlForApp} + history={history} capabilities={{ navLinks: {}, management: {}, diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx index 92c78d63d1b2e..834bfb73d8f46 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx @@ -17,16 +17,17 @@ jest.mock('./edit_space', () => ({ }, })); +import { ScopedHistory } from 'src/core/public'; import { spacesManagementApp } from './spaces_management_app'; -import { coreMock } from '../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../src/core/public/mocks'; import { securityMock } from '../../../security/public/mocks'; import { spacesManagerMock } from '../spaces_manager/mocks'; import { SecurityLicenseFeatures } from '../../../security/public'; import { featuresPluginMock } from '../../../features/public/mocks'; import { PluginsStart } from '../plugin'; -async function mountApp(basePath: string, spaceId?: string) { +async function mountApp(basePath: string, pathname: string, spaceId?: string) { const container = document.createElement('div'); const setBreadcrumbs = jest.fn(); @@ -53,7 +54,12 @@ async function mountApp(basePath: string, spaceId?: string) { securityLicense, getStartServices: async () => [coreStart, pluginsStart as PluginsStart, {}], }) - .mount({ basePath, element: container, setBreadcrumbs }); + .mount({ + basePath, + element: container, + setBreadcrumbs, + history: (scopedHistoryMock.create({ pathname }) as unknown) as ScopedHistory, + }); return { unmount, container, setBreadcrumbs }; } @@ -77,16 +83,13 @@ describe('spacesManagementApp', () => { }); it('mount() works for the `grid` page', async () => { - const basePath = '/some-base-path/spaces'; - window.location.hash = basePath; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `#${basePath}`, text: 'Spaces' }]); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: `/`, text: 'Spaces' }]); expect(container).toMatchInlineSnapshot(`
- Spaces Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"securityEnabled":true} + Spaces Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/","search":"","hash":""}},"securityEnabled":true}
`); @@ -96,19 +99,16 @@ describe('spacesManagementApp', () => { }); it('mount() works for the `create space` page', async () => { - const basePath = '/some-base-path/spaces'; - window.location.hash = `${basePath}/create`; - - const { setBreadcrumbs, container, unmount } = await mountApp(basePath); + const { setBreadcrumbs, container, unmount } = await mountApp('/', '/create'); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Spaces' }, + { href: `/`, text: 'Spaces' }, { text: 'Create' }, ]); expect(container).toMatchInlineSnapshot(`
- Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"securityEnabled":true} + Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/create","search":"","hash":""}},"securityEnabled":true}
`); @@ -118,20 +118,18 @@ describe('spacesManagementApp', () => { }); it('mount() works for the `edit space` page', async () => { - const basePath = '/some-base-path/spaces'; const spaceId = 'some-space'; - window.location.hash = `${basePath}/edit/${spaceId}`; - const { setBreadcrumbs, container, unmount } = await mountApp(basePath, spaceId); + const { setBreadcrumbs, container, unmount } = await mountApp('/', `/edit/${spaceId}`, spaceId); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); expect(setBreadcrumbs).toHaveBeenCalledWith([ - { href: `#${basePath}`, text: 'Spaces' }, - { href: `#/some-base-path/spaces/edit/${spaceId}`, text: `space with id some-space` }, + { href: `/`, text: 'Spaces' }, + { href: `/edit/${spaceId}`, text: `space with id some-space` }, ]); expect(container).toMatchInlineSnapshot(`
- Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"spaceId":"some-space","securityEnabled":true} + Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{"_isScalar":false}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"securityEnabled":true}
`); diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx index 079cf2234b13b..5b8b993d96adc 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter as Router, Route, Switch, useParams } from 'react-router-dom'; +import { Router, Route, Switch, useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { StartServicesAccessor } from 'src/core/public'; import { SecurityLicense } from '../../../security/public'; @@ -32,7 +32,7 @@ export const spacesManagementApp = Object.freeze({ title: i18n.translate('xpack.spaces.displayName', { defaultMessage: 'Spaces', }), - async mount({ basePath, element, setBreadcrumbs }) { + async mount({ element, setBreadcrumbs, history }) { const [ { notifications, i18n: i18nStart, application }, { features }, @@ -42,7 +42,7 @@ export const spacesManagementApp = Object.freeze({ text: i18n.translate('xpack.spaces.management.breadcrumb', { defaultMessage: 'Spaces', }), - href: `#${basePath}`, + href: `/`, }, ]; @@ -54,6 +54,8 @@ export const spacesManagementApp = Object.freeze({ getFeatures={features.getFeatures} notifications={notifications} spacesManager={spacesManager} + history={history} + getUrlForApp={application.getUrlForApp} securityEnabled={securityLicense?.getFeatures().showLinks ?? false} /> ); @@ -75,6 +77,8 @@ export const spacesManagementApp = Object.freeze({ getFeatures={features.getFeatures} notifications={notifications} spacesManager={spacesManager} + history={history} + getUrlForApp={application.getUrlForApp} securityEnabled={securityLicense?.getFeatures().showLinks ?? false} /> ); @@ -88,7 +92,7 @@ export const spacesManagementApp = Object.freeze({ ...spacesBreadcrumbs, { text: space.name, - href: `#${basePath}/edit/${encodeURIComponent(space.id)}`, + href: `/edit/${encodeURIComponent(space.id)}`, }, ]); }; @@ -101,6 +105,8 @@ export const spacesManagementApp = Object.freeze({ spacesManager={spacesManager} spaceId={spaceId} onLoadSpace={onLoadSpace} + history={history} + getUrlForApp={application.getUrlForApp} securityEnabled={securityLicense?.getFeatures().showLinks ?? false} /> ); @@ -108,9 +114,9 @@ export const spacesManagementApp = Object.freeze({ render( - + - + diff --git a/x-pack/plugins/spaces/public/nav_control/components/manage_spaces_button.tsx b/x-pack/plugins/spaces/public/nav_control/components/manage_spaces_button.tsx index 456b7b0189a75..98467ebdb790b 100644 --- a/x-pack/plugins/spaces/public/nav_control/components/manage_spaces_button.tsx +++ b/x-pack/plugins/spaces/public/nav_control/components/manage_spaces_button.tsx @@ -46,6 +46,6 @@ export class ManageSpacesButton extends Component { this.props.onClick(); } - this.props.navigateToApp('kibana', { path: '#/management/kibana/spaces' }); + this.props.navigateToApp('management', { path: 'kibana/spaces' }); }; } diff --git a/x-pack/plugins/spaces/public/plugin.test.ts b/x-pack/plugins/spaces/public/plugin.test.ts index a98f593f546a0..4a49cf20d3a4a 100644 --- a/x-pack/plugins/spaces/public/plugin.test.ts +++ b/x-pack/plugins/spaces/public/plugin.test.ts @@ -33,17 +33,11 @@ describe('Spaces plugin', () => { it('should register the management and feature catalogue sections when the management and home plugins are both available', () => { const coreSetup = coreMock.createSetup(); - const kibanaSection = new ManagementSection( - { - id: ManagementSectionId.Kibana, - title: 'Mock Kibana Section', - order: 1, - }, - jest.fn(), - jest.fn(), - jest.fn(), - coreSetup.getStartServices - ); + const kibanaSection = new ManagementSection({ + id: ManagementSectionId.Kibana, + title: 'Mock Kibana Section', + order: 1, + }); const registerAppSpy = jest.spyOn(kibanaSection, 'registerApp'); diff --git a/x-pack/plugins/transform/public/app/app.tsx b/x-pack/plugins/transform/public/app/app.tsx index 01ff7f5bff27f..ccfdc8b0942fa 100644 --- a/x-pack/plugins/transform/public/app/app.tsx +++ b/x-pack/plugins/transform/public/app/app.tsx @@ -6,7 +6,8 @@ import React, { useContext, FC } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { HashRouter, Redirect, Route, Switch } from 'react-router-dom'; +import { Router, Route, Switch } from 'react-router-dom'; +import { ScopedHistory } from 'kibana/public'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -15,7 +16,7 @@ import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/p import { API_BASE_PATH } from '../../common/constants'; import { SectionError } from './components'; -import { CLIENT_BASE_PATH, SECTION_SLUG } from './constants'; +import { SECTION_SLUG } from './constants'; import { AuthorizationContext, AuthorizationProvider } from './lib/authorization'; import { AppDependencies } from './app_dependencies'; @@ -23,7 +24,7 @@ import { CloneTransformSection } from './sections/clone_transform'; import { CreateTransformSection } from './sections/create_transform'; import { TransformManagementSection } from './sections/transform_management'; -export const App: FC = () => { +export const App: FC<{ history: ScopedHistory }> = ({ history }) => { const { apiError } = useContext(AuthorizationContext); if (apiError !== null) { return ( @@ -41,24 +42,19 @@ export const App: FC = () => { return (
- + - - + - +
); }; @@ -70,7 +66,7 @@ export const renderApp = (element: HTMLElement, appDependencies: AppDependencies - + , diff --git a/x-pack/plugins/transform/public/app/app_dependencies.tsx b/x-pack/plugins/transform/public/app/app_dependencies.tsx index 87db02988adf0..704875330fdd2 100644 --- a/x-pack/plugins/transform/public/app/app_dependencies.tsx +++ b/x-pack/plugins/transform/public/app/app_dependencies.tsx @@ -6,6 +6,7 @@ import { CoreSetup, CoreStart } from 'src/core/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; +import { ScopedHistory } from 'kibana/public'; import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; @@ -21,6 +22,7 @@ export interface AppDependencies { savedObjects: CoreStart['savedObjects']; storage: Storage; overlays: CoreStart['overlays']; + history: ScopedHistory; } export const useAppDependencies = () => { diff --git a/x-pack/plugins/transform/public/app/common/navigation.test.tsx b/x-pack/plugins/transform/public/app/common/navigation.test.tsx index 97cb5b4d5d236..4fd4376a021fc 100644 --- a/x-pack/plugins/transform/public/app/common/navigation.test.tsx +++ b/x-pack/plugins/transform/public/app/common/navigation.test.tsx @@ -9,7 +9,7 @@ import { getDiscoverUrl } from './navigation'; describe('navigation', () => { test('getDiscoverUrl should provide encoded url to Discover page', () => { expect(getDiscoverUrl('farequote-airline', 'http://example.com')).toBe( - 'http://example.com#/discover?_g=()&_a=(index:farequote-airline)' + 'http://example.com/app/discover#?_g=()&_a=(index:farequote-airline)' ); }); }); diff --git a/x-pack/plugins/transform/public/app/common/navigation.tsx b/x-pack/plugins/transform/public/app/common/navigation.tsx index 15966a93e1f42..c43a12ec3824b 100644 --- a/x-pack/plugins/transform/public/app/common/navigation.tsx +++ b/x-pack/plugins/transform/public/app/common/navigation.tsx @@ -8,7 +8,7 @@ import React, { FC } from 'react'; import { Redirect } from 'react-router-dom'; import rison from 'rison-node'; -import { CLIENT_BASE_PATH, SECTION_SLUG } from '../constants'; +import { SECTION_SLUG } from '../constants'; /** * Gets a url for navigating to Discover page. @@ -23,15 +23,13 @@ export function getDiscoverUrl(indexPatternId: string, baseUrl: string): string index: indexPatternId, }); - const hash = `#/discover?_g=${_g}&_a=${_a}`; + const hash = `/discover#?_g=${_g}&_a=${_a}`; - return `${baseUrl}${hash}`; + return `${baseUrl}/app${hash}`; } -export const RedirectToTransformManagement: FC = () => ( - -); +export const RedirectToTransformManagement: FC = () => ; export const RedirectToCreateTransform: FC<{ savedObjectId: string }> = ({ savedObjectId }) => ( - + ); diff --git a/x-pack/plugins/transform/public/app/constants/index.ts b/x-pack/plugins/transform/public/app/constants/index.ts index 3156fae7545b1..299007d84768f 100644 --- a/x-pack/plugins/transform/public/app/constants/index.ts +++ b/x-pack/plugins/transform/public/app/constants/index.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export const CLIENT_BASE_PATH = '/management/data/transform/'; - export enum SECTION_SLUG { HOME = 'transform_management', CLONE_TRANSFORM = 'clone_transform', diff --git a/x-pack/plugins/transform/public/app/mount_management_section.ts b/x-pack/plugins/transform/public/app/mount_management_section.ts index f3a48975a68e6..454738f7a313a 100644 --- a/x-pack/plugins/transform/public/app/mount_management_section.ts +++ b/x-pack/plugins/transform/public/app/mount_management_section.ts @@ -21,7 +21,7 @@ export async function mountManagementSection( coreSetup: CoreSetup, params: ManagementAppMountParams ) { - const { element, setBreadcrumbs } = params; + const { element, setBreadcrumbs, history } = params; const { http, notifications, getStartServices } = coreSetup; const startServices = await getStartServices(); const [core, plugins] = startServices; @@ -46,6 +46,7 @@ export async function mountManagementSection( savedObjects, storage: localStorage, uiSettings, + history, }; return renderApp(element, appDependencies); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx index 3fcfd77ba54cd..69fcf822de5d2 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx @@ -120,14 +120,23 @@ export const Wizard: FC = React.memo(({ cloneConfig, searchItems }) // as it was when transforms were part of the ML plugin. This will be revisited // to come up with an approach that's more in line with the overall layout // of the Kibana management section. - const managementBody = document.getElementsByClassName( + let managementBody = document.getElementsByClassName( KBN_MANAGEMENT_PAGE_CLASSNAME.DEFAULT_BODY ); if (managementBody.length > 0) { - managementBody[0].classList.add(KBN_MANAGEMENT_PAGE_CLASSNAME.TRANSFORM_BODY_MODIFIER); + managementBody[0].classList.replace( + KBN_MANAGEMENT_PAGE_CLASSNAME.DEFAULT_BODY, + KBN_MANAGEMENT_PAGE_CLASSNAME.TRANSFORM_BODY_MODIFIER + ); return () => { - managementBody[0].classList.remove(KBN_MANAGEMENT_PAGE_CLASSNAME.TRANSFORM_BODY_MODIFIER); + managementBody = document.getElementsByClassName( + KBN_MANAGEMENT_PAGE_CLASSNAME.TRANSFORM_BODY_MODIFIER + ); + managementBody[0].classList.replace( + KBN_MANAGEMENT_PAGE_CLASSNAME.TRANSFORM_BODY_MODIFIER, + KBN_MANAGEMENT_PAGE_CLASSNAME.DEFAULT_BODY + ); }; } }, []); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx index 4b333f73f048c..48188fd70bd7e 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx @@ -14,7 +14,7 @@ import { AuthorizationContext, } from '../../../../lib/authorization'; -import { CLIENT_BASE_PATH, SECTION_SLUG } from '../../../../constants'; +import { SECTION_SLUG } from '../../../../constants'; interface CloneActionProps { itemId: string; @@ -30,7 +30,7 @@ export const CloneAction: FC = ({ itemId }) => { }); function clickHandler() { - history.push(`${CLIENT_BASE_PATH}${SECTION_SLUG.CLONE_TRANSFORM}/${itemId}`); + history.push(`/${SECTION_SLUG.CLONE_TRANSFORM}/${itemId}`); } const cloneButton = ( diff --git a/x-pack/plugins/transform/public/app/services/navigation/breadcrumb.ts b/x-pack/plugins/transform/public/app/services/navigation/breadcrumb.ts index 6637b8a39cd56..7d4ab5d8163e1 100644 --- a/x-pack/plugins/transform/public/app/services/navigation/breadcrumb.ts +++ b/x-pack/plugins/transform/public/app/services/navigation/breadcrumb.ts @@ -5,7 +5,6 @@ */ import { textService } from '../text'; -import { linkToHome } from './links'; import { ManagementAppMountParams } from '../../../../../../../src/plugins/management/public'; @@ -41,7 +40,7 @@ class BreadcrumbService { this.breadcrumbs.home = [ { text: textService.breadcrumbs.home, - href: linkToHome(), + href: '/', }, ]; this.breadcrumbs.cloneTransform = [ diff --git a/x-pack/plugins/transform/public/app/services/navigation/index.ts b/x-pack/plugins/transform/public/app/services/navigation/index.ts index 40a6d6d9fbdc4..f1bd864f538ad 100644 --- a/x-pack/plugins/transform/public/app/services/navigation/index.ts +++ b/x-pack/plugins/transform/public/app/services/navigation/index.ts @@ -6,4 +6,3 @@ export { breadcrumbService, BREADCRUMB_SECTION } from './breadcrumb'; export { docTitleService } from './doc_title'; -export * from './links'; diff --git a/x-pack/plugins/transform/public/app/services/navigation/links.ts b/x-pack/plugins/transform/public/app/services/navigation/links.ts deleted file mode 100644 index 85088c3a4a69d..0000000000000 --- a/x-pack/plugins/transform/public/app/services/navigation/links.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { CLIENT_BASE_PATH } from '../../constants'; - -export function linkToHome() { - return `#${CLIENT_BASE_PATH}`; -} diff --git a/x-pack/plugins/transform/public/register_feature.ts b/x-pack/plugins/transform/public/register_feature.ts index c81a18a3def87..796fa370dab25 100644 --- a/x-pack/plugins/transform/public/register_feature.ts +++ b/x-pack/plugins/transform/public/register_feature.ts @@ -22,7 +22,7 @@ export const registerFeature = (home: HomePublicPluginSetup) => { 'Use transforms to pivot existing Elasticsearch indices into summarized or entity-centric indices.', }), icon: 'managementApp', // there is currently no Transforms icon, so using the general management app icon - path: '/app/kibana#/management/data/transform', + path: '/app/management/data/transform', showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 4d98f0f0cda6c..739aed1c71351 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -168,7 +168,6 @@ "common.ui.flotCharts.thuLabel": "木", "common.ui.flotCharts.tueLabel": "火", "common.ui.flotCharts.wedLabel": "水", - "common.ui.stackManagement.breadcrumb": "スタック管理", "common.ui.stateManagement.unableToParseUrlErrorMessage": "URL をパースできません", "common.ui.stateManagement.unableToRestoreUrlErrorMessage": "URL を完全に復元できません。共有機能を使用していることを確認してください。", "common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage": "セッションがいっぱいで安全に削除できるアイテムが見つからないため、Kibana は履歴アイテムを保存できません。\n\nこれは大抵新規タブに移動することで解決されますが、より大きな問題が原因である可能性もあります。このメッセージが定期的に表示される場合は、{gitHubIssuesUrl} で問題を報告してください。", @@ -2178,10 +2177,6 @@ "kbn.advancedSettings.visualization.tileMap.wmsDefaultsTitle": "デフォルトの WMS プロパティ", "kbn.advancedSettings.visualizeEnableLabsText": "ユーザーが実験的なビジュアライゼーションを作成、表示、編集できるようになります。無効の場合、\n ユーザーは本番準備が整ったビジュアライゼーションのみを利用できます。", "kbn.advancedSettings.visualizeEnableLabsTitle": "実験的なビジュアライゼーションを有効にする", - "kbn.management.landing.header": "Stack Management {version}へようこそ", - "kbn.management.landing.subhead": "インデックス、インデックスパターン、保存されたオブジェクト、Kibana の設定、その他を管理します。", - "kbn.management.landing.text": "アプリの一覧は左側のメニューにあります。", - "kbn.managementTitle": "スタック管理", "kibana_legacy.bigUrlWarningNotificationMessage": "{advancedSettingsLink}で{storeInSessionStorageParam}オプションを有効にするか、オンスクリーンビジュアルを簡素化してください。", "kibana_legacy.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高度な設定", "kibana_legacy.bigUrlWarningNotificationTitle": "URLが大きく、Kibanaの動作が停止する可能性があります", @@ -2223,9 +2218,11 @@ "kibana-react.tableListView.listing.table.editActionName": "編集", "kibana-react.tableListView.listing.unableToDeleteDangerMessage": "{entityName} を削除できません", "management.breadcrumb": "スタック管理", - "management.displayName": "スタック管理", "management.nav.label": "管理", "management.nav.menu": "管理メニュー", + "management.landing.header": "Stack Management {version}へようこそ", + "management.landing.subhead": "インデックス、インデックスパターン、保存されたオブジェクト、Kibana の設定、その他を管理します。", + "management.landing.text": "アプリの一覧は左側のメニューにあります。", "management.stackManagement.managementDescription": "Elastic Stack の管理を行うセンターコンソールです。", "management.stackManagement.managementLabel": "スタック管理", "maps_legacy.baseMapsVisualization.childShouldImplementMethodErrorMessage": "子はdata-updateに対応できるようこのメソッドを導入する必要があります", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 07169a2b73da8..7aeb8021b5582 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -168,7 +168,6 @@ "common.ui.flotCharts.thuLabel": "周四", "common.ui.flotCharts.tueLabel": "周二", "common.ui.flotCharts.wedLabel": "周三", - "common.ui.stackManagement.breadcrumb": "Stack Management", "common.ui.stateManagement.unableToParseUrlErrorMessage": "无法解析 URL", "common.ui.stateManagement.unableToRestoreUrlErrorMessage": "无法完整还原 URL,确保使用共享功能。", "common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage": "Kibana 无法将历史记录项存储在您的会话中,因为其已满,并且似乎没有任何可安全删除的项。\n\n通常可通过移至新的标签页来解决此问题,但这会导致更大的问题。如果您有规律地看到此消息,请在 {gitHubIssuesUrl} 提交问题。", @@ -2181,10 +2180,6 @@ "kbn.advancedSettings.visualization.tileMap.wmsDefaultsTitle": "默认 WMS 属性", "kbn.advancedSettings.visualizeEnableLabsText": "允许用户创建、查看和编辑实验性可视化。如果禁用,\n 仅被视为生产就绪的可视化可供用户使用。", "kbn.advancedSettings.visualizeEnableLabsTitle": "启用实验性可视化", - "kbn.management.landing.header": "欢迎使用 Stack Management {version}", - "kbn.management.landing.subhead": "管理您的索引、索引模式、已保存对象、Kibana 设置等等。", - "kbn.management.landing.text": "应用的完整列表位于左侧菜单中。", - "kbn.managementTitle": "Stack Management", "kibana_legacy.bigUrlWarningNotificationMessage": "在{advancedSettingsLink}中启用“{storeInSessionStorageParam}”选项或简化屏幕视觉效果。", "kibana_legacy.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高级设置", "kibana_legacy.bigUrlWarningNotificationTitle": "URL 过长,Kibana 可能无法工作", @@ -2226,9 +2221,11 @@ "kibana-react.tableListView.listing.table.editActionName": "编辑", "kibana-react.tableListView.listing.unableToDeleteDangerMessage": "无法删除{entityName}", "management.breadcrumb": "Stack Management", - "management.displayName": "Stack Management", "management.nav.label": "管理", "management.nav.menu": "管理菜单", + "management.landing.header": "欢迎使用 Stack Management {version}", + "management.landing.subhead": "管理您的索引、索引模式、已保存对象、Kibana 设置等等。", + "management.landing.text": "应用的完整列表位于左侧菜单中。", "management.stackManagement.managementDescription": "您用于管理 Elastic Stack 的中心控制台。", "management.stackManagement.managementLabel": "Stack Management", "maps_legacy.baseMapsVisualization.childShouldImplementMethodErrorMessage": "子对象应实现此方法以响应数据更新", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx index d7dc674ef8d0c..80f8e4a3a2c77 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { lazy } from 'react'; -import { Switch, Route, Redirect, HashRouter } from 'react-router-dom'; +import { Switch, Route, Redirect, Router } from 'react-router-dom'; import { ChromeStart, DocLinksStart, @@ -14,8 +14,9 @@ import { ApplicationStart, ChromeBreadcrumb, CoreStart, + ScopedHistory, } from 'kibana/public'; -import { BASE_PATH, Section, routeToAlertDetails } from './constants'; +import { Section, routeToAlertDetails } from './constants'; import { AppContextProvider, useAppDependencies } from './app_context'; import { hasShowAlertsCapability } from './lib/capabilities'; import { ActionTypeModel, AlertTypeModel } from '../types'; @@ -44,6 +45,7 @@ export interface AppDeps { capabilities: ApplicationStart['capabilities']; actionTypeRegistry: TypeRegistry; alertTypeRegistry: TypeRegistry; + history: ScopedHistory; } export const App = (appDeps: AppDeps) => { @@ -52,11 +54,11 @@ export const App = (appDeps: AppDeps) => { const sectionsRegex = sections.join('|'); return ( - + - +
); }; @@ -67,7 +69,7 @@ export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) = return ( {canShowAlerts && ( @@ -76,7 +78,7 @@ export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) = component={suspendedComponentWithProps(AlertDetailsRoute, 'xl')} /> )} - + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts index 47b55f44bfb92..299d8272f5797 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts @@ -7,14 +7,12 @@ export { BASE_ALERT_API_PATH } from '../../../../alerts/common'; export { BASE_ACTION_API_PATH } from '../../../../actions/common'; -export const BASE_PATH = '/management/insightsAndAlerting/triggersActions'; - export type Section = 'connectors' | 'alerts'; -export const routeToHome = `${BASE_PATH}`; -export const routeToConnectors = `${BASE_PATH}/connectors`; -export const routeToAlerts = `${BASE_PATH}/alerts`; -export const routeToAlertDetails = `${BASE_PATH}/alert/:alertId`; +export const routeToHome = `/`; +export const routeToConnectors = `/connectors`; +export const routeToAlerts = `/alerts`; +export const routeToAlertDetails = `/alert/:alertId`; export { TIME_UNITS } from './time_units'; export enum SORT_ORDERS { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx index 8d35b60dc8e44..eeb8a77717333 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx @@ -21,7 +21,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { BASE_PATH, Section, routeToConnectors, routeToAlerts } from './constants'; +import { Section, routeToConnectors, routeToAlerts } from './constants'; import { getCurrentBreadcrumb } from './lib/breadcrumb'; import { getCurrentDocTitle } from './lib/doc_title'; import { useAppDependencies } from './app_context'; @@ -76,7 +76,7 @@ export const TriggersActionsUIHome: React.FunctionComponent { - history.push(`${BASE_PATH}/${newSection}`); + history.push(`/${newSection}`); }; // Set breadcrumb and page title diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.test.ts index b75e014640d72..8ba909beff2a8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.test.ts @@ -13,19 +13,19 @@ describe('getCurrentBreadcrumb', () => { text: i18n.translate('xpack.triggersActionsUI.connectors.breadcrumbTitle', { defaultMessage: 'Connectors', }), - href: `#${routeToConnectors}`, + href: `${routeToConnectors}`, }); expect(getCurrentBreadcrumb('alerts')).toMatchObject({ text: i18n.translate('xpack.triggersActionsUI.alerts.breadcrumbTitle', { defaultMessage: 'Alerts', }), - href: `#${routeToAlerts}`, + href: `${routeToAlerts}`, }); expect(getCurrentBreadcrumb('home')).toMatchObject({ text: i18n.translate('xpack.triggersActionsUI.home.breadcrumbTitle', { defaultMessage: 'Alerts and Actions', }), - href: `#${routeToHome}`, + href: `${routeToHome}`, }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.ts index f833ae9eb39ac..3735942ff97af 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.ts @@ -15,21 +15,21 @@ export const getCurrentBreadcrumb = (type: string): { text: string; href: string text: i18n.translate('xpack.triggersActionsUI.connectors.breadcrumbTitle', { defaultMessage: 'Connectors', }), - href: `#${routeToConnectors}`, + href: `${routeToConnectors}`, }; case 'alerts': return { text: i18n.translate('xpack.triggersActionsUI.alerts.breadcrumbTitle', { defaultMessage: 'Alerts', }), - href: `#${routeToAlerts}`, + href: `${routeToAlerts}`, }; default: return { text: i18n.translate('xpack.triggersActionsUI.home.breadcrumbTitle', { defaultMessage: 'Alerts and Actions', }), - href: `#${routeToHome}`, + href: `${routeToHome}`, }; } }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx index 1da9abea40dba..f60199bc47f4b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx @@ -127,7 +127,7 @@ describe('connector_add_flyout', () => { const manageLink = callout.find('EuiButton'); expect(manageLink).toHaveLength(1); expect(manageLink.getElements()[0].props.href).toMatchInlineSnapshot( - `"/app/kibana#/management/stack/license_management/"` + `"/app/management/stack/license_management"` ); const subscriptionLink = callout.find('EuiButtonEmpty'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx index dc54875f4a914..2dd1f83749372 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx @@ -32,7 +32,6 @@ import { createActionConnector } from '../../lib/action_connector_api'; import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; import { VIEW_LICENSE_OPTIONS_LINK } from '../../../common/constants'; import { PLUGIN } from '../../constants/plugin'; -import { BASE_PATH as LICENSE_MANAGEMENT_BASE_PATH } from '../../../../../license_management/common/constants'; export interface ConnectorAddFlyoutProps { addFlyoutVisible: boolean; @@ -293,7 +292,7 @@ const UpgradeYourLicenseCallOut = ({ http }: { http: HttpSetup }) => ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx index 3d16bdfa61a00..a317c31a6e4b1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx @@ -4,9 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import * as React from 'react'; +import { ScopedHistory } from 'kibana/public'; + import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ActionsConnectorsList } from './actions_connectors_list'; -import { coreMock } from '../../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../../src/core/public/mocks'; import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { actionTypeRegistryMock } from '../../../action_type_registry.mock'; @@ -66,6 +68,7 @@ describe('actions_connectors_list component empty', () => { 'actions:delete': true, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: actionTypeRegistry as any, alertTypeRegistry: {} as any, @@ -168,6 +171,7 @@ describe('actions_connectors_list component with items', () => { 'actions:delete': true, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: { get() { @@ -250,6 +254,7 @@ describe('actions_connectors_list component empty with show only capability', () 'actions:delete': false, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: { get() { @@ -335,6 +340,7 @@ describe('actions_connectors_list with show only capability', () => { 'actions:delete': false, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: { get() { @@ -432,6 +438,7 @@ describe('actions_connectors_list component with disabled items', () => { 'actions:delete': true, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: { get() { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx index a59a4a37bec1f..8166aa9bf6925 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ import * as React from 'react'; +import { ScopedHistory } from 'kibana/public'; + import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { coreMock } from '../../../../../../../../src/core/public/mocks'; +import { coreMock, scopedHistoryMock } from '../../../../../../../../src/core/public/mocks'; import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { actionTypeRegistryMock } from '../../../action_type_registry.mock'; @@ -101,6 +103,7 @@ describe('alerts_list component empty', () => { 'alerting:delete': true, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: actionTypeRegistry as any, alertTypeRegistry: alertTypeRegistry as any, @@ -219,6 +222,7 @@ describe('alerts_list component with items', () => { 'alerting:delete': true, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: actionTypeRegistry as any, alertTypeRegistry: alertTypeRegistry as any, @@ -300,6 +304,7 @@ describe('alerts_list component empty with show only capability', () => { 'alerting:delete': false, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: { get() { @@ -414,6 +419,7 @@ describe('alerts_list with show only capability', () => { 'alerting:delete': false, }, }, + history: (scopedHistoryMock.create() as unknown) as ScopedHistory, setBreadcrumbs: jest.fn(), actionTypeRegistry: actionTypeRegistry as any, alertTypeRegistry: alertTypeRegistry as any, diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 3453165a15f69..db93e48ab3692 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -95,6 +95,7 @@ export class Plugin capabilities: core.application.capabilities, navigateToApp: core.application.navigateToApp, setBreadcrumbs: params.setBreadcrumbs, + history: params.history, actionTypeRegistry: this.actionTypeRegistry, alertTypeRegistry: this.alertTypeRegistry, }); diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/license_info.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/license_info.test.tsx.snap index 5ae90a1613575..2ba4eda82a391 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/license_info.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/license_info.test.tsx.snap @@ -28,7 +28,7 @@ Array [

@@ -64,7 +64,7 @@ exports[`ShowLicenseInfo shallow renders without errors 1`] = `

Start free 14-day trial diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap index df9de8c2ad03d..3a056e6621f58 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap @@ -160,7 +160,7 @@ exports[`ML Flyout component shows license info if no ml available 1`] = `

diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/license_info.tsx b/x-pack/plugins/uptime/public/components/monitor/ml/license_info.tsx index 33fbbb117a11a..e37ec4cc4715d 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/license_info.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ml/license_info.tsx @@ -22,7 +22,7 @@ export const ShowLicenseInfo = () => {

{labels.START_TRAIL_DESC}

{labels.START_TRAIL} diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx index 34c5a48ad55f0..18514bd92d7a0 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx @@ -61,7 +61,7 @@ export const ToggleAlertFlyoutButtonComponent: React.FC = ({ { + return `${location.pathname}${location.search ? '?' + location.search : ''}`; +}; + export const mockContextValue = { licenseStatus$: of({ valid: true }), docLinks: docLinksServiceMock.createStartContract(), @@ -39,6 +48,8 @@ export const mockContextValue = { } as any, // For our test harness, we don't use this mocked out http service http: httpServiceMock.createSetupContract(), + history, + getUrlForApp: jest.fn(), }; export const withAppContext = (Component: ComponentType) => (props: any) => { diff --git a/x-pack/plugins/watcher/__jest__/client_integration/watch_list.test.ts b/x-pack/plugins/watcher/__jest__/client_integration/watch_list.test.ts index 0436ed1d6bb06..e436971edbb69 100644 --- a/x-pack/plugins/watcher/__jest__/client_integration/watch_list.test.ts +++ b/x-pack/plugins/watcher/__jest__/client_integration/watch_list.test.ts @@ -131,7 +131,7 @@ describe('', () => { expect(findTestSubject(idColumn, `watchIdColumn-${watch1.id}`).length).toBe(1); expect(findTestSubject(idColumn, `watchIdColumn-${watch1.id}`).props().href).toEqual( - `#/management/insightsAndAlerting/watcher/watches/watch/${watch1.id}/status` + `/watches/watch/${watch1.id}/status` ); }); diff --git a/x-pack/plugins/watcher/public/application/app.tsx b/x-pack/plugins/watcher/public/application/app.tsx index 8a6d2746237e9..555440f9fbbec 100644 --- a/x-pack/plugins/watcher/public/application/app.tsx +++ b/x-pack/plugins/watcher/public/application/app.tsx @@ -6,29 +6,30 @@ import React, { useEffect, useState } from 'react'; import { Observable } from 'rxjs'; -import { DocLinksStart, HttpSetup, ToastsSetup, IUiSettingsClient } from 'kibana/public'; - import { - HashRouter, - Switch, - Route, - Redirect, - withRouter, - RouteComponentProps, -} from 'react-router-dom'; + DocLinksStart, + HttpSetup, + ToastsSetup, + IUiSettingsClient, + ApplicationStart, +} from 'kibana/public'; + +import { Router, Switch, Route, Redirect, withRouter, RouteComponentProps } from 'react-router-dom'; import { EuiCallOut, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { RegisterManagementAppArgs } from '../../../../../src/plugins/management/public'; +import { + RegisterManagementAppArgs, + ManagementAppMountParams, +} from '../../../../../src/plugins/management/public'; import { LicenseStatus } from '../../common/types/license_status'; import { WatchStatus } from './sections/watch_status/components/watch_status'; import { WatchEdit } from './sections/watch_edit/components/watch_edit'; import { WatchList } from './sections/watch_list/components/watch_list'; import { registerRouter } from './lib/navigation'; -import { BASE_PATH } from './constants'; import { AppContextProvider } from './app_context'; import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; @@ -46,6 +47,8 @@ export interface AppDeps { createTimeBuckets: () => any; licenseStatus$: Observable; setBreadcrumbs: Parameters[0]['setBreadcrumbs']; + history: ManagementAppMountParams['history']; + getUrlForApp: ApplicationStart['getUrlForApp']; } export const App = (deps: AppDeps) => { @@ -69,7 +72,7 @@ export const App = (deps: AppDeps) => { iconType="help" > {message}{' '} - + { ); } return ( - + - + ); }; // Export this so we can test it with a different router. export const AppWithoutRouter = () => ( - - - - - + + + + + + ); diff --git a/x-pack/plugins/watcher/public/application/lib/breadcrumbs.ts b/x-pack/plugins/watcher/public/application/lib/breadcrumbs.ts index db0e34fc57683..6fd47d4d487ae 100644 --- a/x-pack/plugins/watcher/public/application/lib/breadcrumbs.ts +++ b/x-pack/plugins/watcher/public/application/lib/breadcrumbs.ts @@ -15,7 +15,7 @@ export const listBreadcrumb: Breadcrumb = { text: i18n.translate('xpack.watcher.breadcrumb.listLabel', { defaultMessage: 'Watcher', }), - href: '#/management/insightsAndAlerting/watcher/watches/', + href: '/watches', }; export const createBreadcrumb: Breadcrumb = { diff --git a/x-pack/plugins/watcher/public/application/lib/navigation.ts b/x-pack/plugins/watcher/public/application/lib/navigation.ts index 87fa95ce53410..1ace9d36f2e3a 100644 --- a/x-pack/plugins/watcher/public/application/lib/navigation.ts +++ b/x-pack/plugins/watcher/public/application/lib/navigation.ts @@ -4,20 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BASE_PATH } from '../constants'; let router: any; export const registerRouter = (aRouter: any) => { router = aRouter; }; export const goToWatchList = () => { - router.history.push({ pathname: `${BASE_PATH}watches` }); + router.history.push({ pathname: `/watches` }); }; export const goToCreateThresholdAlert = () => { - router.history.push({ pathname: `${BASE_PATH}watches/new-watch/threshold` }); + router.history.push({ pathname: `/watches/new-watch/threshold` }); }; export const goToCreateAdvancedWatch = () => { - router.history.push({ pathname: `${BASE_PATH}watches/new-watch/json` }); + router.history.push({ pathname: `/watches/new-watch/json` }); }; diff --git a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx index 156a6f2eda890..b3fc8af81c94a 100644 --- a/x-pack/plugins/watcher/public/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx +++ b/x-pack/plugins/watcher/public/application/sections/watch_edit/components/monitoring_watch_edit/monitoring_watch_edit.tsx @@ -18,9 +18,13 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { WatchContext } from '../../watch_context'; +import { useAppContext } from '../../../../app_context'; + +import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public'; export const MonitoringWatchEdit = ({ pageTitle }: { pageTitle: string }) => { const { watch } = useContext(WatchContext); + const { history } = useAppContext(); const systemWatchTitle = ( { values={{ watchName: watch.name, watchStatusLink: ( - + { // hooks const { setBreadcrumbs, + history, links: { watcherGettingStartedUrl }, } = useAppContext(); const [selection, setSelection] = useState([]); @@ -242,7 +245,7 @@ export const WatchList = () => { return ( {id} @@ -326,7 +329,7 @@ export const WatchList = () => { )} iconType="pencil" color="primary" - href={`#/management/insightsAndAlerting/watcher/watches/watch/${watch.id}/edit`} + {...reactRouterNavigate(history, `/watches/watch/${watch.id}/edit`)} data-test-subj="editWatchButton" /> diff --git a/x-pack/plugins/watcher/public/plugin.ts b/x-pack/plugins/watcher/public/plugin.ts index 6496c742fcb40..80f8503af67ce 100644 --- a/x-pack/plugins/watcher/public/plugin.ts +++ b/x-pack/plugins/watcher/public/plugin.ts @@ -38,9 +38,9 @@ export class WatcherUIPlugin implements Plugin { { defaultMessage: 'Watcher' } ), order: 3, - mount: async ({ element, setBreadcrumbs }) => { + mount: async ({ element, setBreadcrumbs, history }) => { const [core] = await getStartServices(); - const { i18n: i18nDep, docLinks, savedObjects } = core; + const { i18n: i18nDep, docLinks, savedObjects, application } = core; const { boot } = await import('./application/boot'); const { TimeBuckets } = await import('./legacy'); @@ -58,6 +58,8 @@ export class WatcherUIPlugin implements Plugin { savedObjects: savedObjects.client, I18nContext: i18nDep.Context, createTimeBuckets: () => new TimeBuckets(uiSettings, data), + history, + getUrlForApp: application.getUrlForApp, }); }, }); @@ -76,7 +78,7 @@ export class WatcherUIPlugin implements Plugin { defaultMessage: 'Detect changes in your data by creating, managing, and monitoring alerts.', }), icon: 'watchesApp', - path: '/app/kibana#/management/insightsAndAlerting/watcher/watches', + path: '/app/management/insightsAndAlerting/watcher/watches', showOnHomePage: false, }; diff --git a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts index b64db5eaeffac..cbf1f3e1af2df 100644 --- a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts +++ b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts @@ -182,9 +182,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`does not allow navigation to advanced settings; redirects to management home`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/settings', { + await PageObjects.common.navigateToUrl('management', 'kibana/settings', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await testSubjects.existOrFail('managementHome', { timeout: config.get('timeouts.waitFor'), diff --git a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts index 7e4aa6e375aef..f7991e62fdaa9 100644 --- a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts +++ b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts @@ -47,10 +47,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`allows settings to be changed`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/settings', { + await PageObjects.common.navigateToUrl('management', 'kibana/settings', { basePath: `/s/custom_space`, ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await PageObjects.settings.setAdvancedSettingsSelect('dateFormat:tz', 'America/Phoenix'); const advancedSetting = await PageObjects.settings.getAdvancedSettings('dateFormat:tz'); @@ -77,10 +78,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`redirects to management home`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/settings', { + await PageObjects.common.navigateToUrl('management', 'kibana/settings', { basePath: `/s/custom_space`, ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await testSubjects.existOrFail('managementHome', { timeout: config.get('timeouts.waitFor'), diff --git a/x-pack/test/functional/apps/dashboard_mode/dashboard_view_mode.js b/x-pack/test/functional/apps/dashboard_mode/dashboard_view_mode.js index 612556b0c4728..00183113a4d59 100644 --- a/x-pack/test/functional/apps/dashboard_mode/dashboard_view_mode.js +++ b/x-pack/test/functional/apps/dashboard_mode/dashboard_view_mode.js @@ -115,7 +115,11 @@ export default function ({ getService, getPageObjects }) { }); it('shows only the dashboard app link', async () => { - await security.testUser.setRoles(['test_logstash_reader', 'kibana_dashboard_only_user']); + await security.testUser.setRoles( + ['test_logstash_reader', 'kibana_dashboard_only_user'], + false + ); + await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.security.forceLogout(); await PageObjects.security.login('test_user', 'changeme'); diff --git a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts index 1d45d00cbc894..cd892c4424290 100644 --- a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts +++ b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts @@ -187,9 +187,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`does not allow navigation to Index Patterns; redirects to management home`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/indexPatterns', { + await PageObjects.common.navigateToUrl('management', 'kibana/indexPatterns', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await testSubjects.existOrFail('managementHome', { timeout: config.get('timeouts.waitFor'), diff --git a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_spaces.ts b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_spaces.ts index 7e82dfab1d7a3..29929a978c8d8 100644 --- a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_spaces.ts +++ b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_spaces.ts @@ -70,10 +70,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`redirects to management home`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/indexPatterns', { + await PageObjects.common.navigateToUrl('management', 'kibana/indexPatterns', { basePath: `/s/custom_space`, ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await testSubjects.existOrFail('managementHome', { timeout: config.get('timeouts.waitFor'), diff --git a/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts b/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts index 383f97cb2ce43..9969608bd2a45 100644 --- a/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts +++ b/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts @@ -111,11 +111,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('edit visualization', () => { before(async () => { - await PageObjects.common.navigateToActualUrl( - 'kibana', - '/management/kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', + await PageObjects.common.navigateToUrl( + 'management', + 'kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', { shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, } ); }); @@ -229,11 +230,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('edit visualization', () => { before(async () => { - await PageObjects.common.navigateToActualUrl( - 'kibana', - '/management/kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', + await PageObjects.common.navigateToUrl( + 'management', + 'kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', { shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, } ); await testSubjects.existOrFail('savedObjectsEdit'); @@ -302,9 +304,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('listing', () => { it('redirects to Kibana home', async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/objects', { + await PageObjects.common.navigateToUrl('management', 'kibana/objects', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await PageObjects.header.waitUntilLoadingHasFinished(); await testSubjects.existOrFail('homeApp'); @@ -313,12 +316,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('edit visualization', () => { it('redirects to Kibana home', async () => { - await PageObjects.common.navigateToActualUrl( - 'kibana', - '/management/kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', + await PageObjects.common.navigateToUrl( + 'management', + 'kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', { shouldLoginIfPrompted: false, ensureCurrentUrl: false, + shouldUseHashForSubUrl: false, } ); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test/functional/apps/security/management.js b/x-pack/test/functional/apps/security/management.js index d761f4d1f785c..c263b26406857 100644 --- a/x-pack/test/functional/apps/security/management.js +++ b/x-pack/test/functional/apps/security/management.js @@ -5,13 +5,6 @@ */ import expect from '@kbn/expect'; -import { - USERS_PATH, - EDIT_USERS_PATH, - ROLES_PATH, - EDIT_ROLES_PATH, - CLONE_ROLES_PATH, -} from '../../../../plugins/security/public/management/management_urls'; export default function ({ getService, getPageObjects }) { const kibanaServer = getService('kibanaServer'); @@ -19,6 +12,13 @@ export default function ({ getService, getPageObjects }) { const browser = getService('browser'); const PageObjects = getPageObjects(['security', 'settings', 'common', 'header']); + const USERS_PATH = 'security/users'; + const EDIT_USERS_PATH = `${USERS_PATH}/edit`; + + const ROLES_PATH = 'security/roles'; + const EDIT_ROLES_PATH = `${ROLES_PATH}/edit`; + const CLONE_ROLES_PATH = `${ROLES_PATH}/clone`; + // FLAKY: https://github.com/elastic/kibana/issues/61173 describe.skip('Management', function () { this.tags(['skipFirefox']); diff --git a/x-pack/test/functional/apps/security/role_mappings.ts b/x-pack/test/functional/apps/security/role_mappings.ts index 16d8fdd4b3c1e..60c166d837933 100644 --- a/x-pack/test/functional/apps/security/role_mappings.ts +++ b/x-pack/test/functional/apps/security/role_mappings.ts @@ -83,17 +83,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('displays an error and returns to the listing page when navigating to a role mapping which does not exist', async () => { - await pageObjects.common.navigateToActualUrl( - 'kibana', - '#/management/security/role_mappings/edit/i-do-not-exist', - { ensureCurrentUrl: false } + await pageObjects.common.navigateToUrl( + 'management', + 'security/role_mappings/edit/i-do-not-exist', + { ensureCurrentUrl: false, shouldUseHashForSubUrl: false } ); await testSubjects.existOrFail('errorLoadingRoleMappingEditorToast'); const url = parse(await browser.getCurrentUrl()); - expect(url.hash).to.eql('#/management/security/role_mappings'); + expect(url.pathname).to.eql('/app/management/security/role_mappings/'); }); describe('with role mappings', () => { diff --git a/x-pack/test/functional/apps/security/trial_license/login_selector.ts b/x-pack/test/functional/apps/security/trial_license/login_selector.ts index 95e68eb9a1f6e..e0b776cd123c1 100644 --- a/x-pack/test/functional/apps/security/trial_license/login_selector.ts +++ b/x-pack/test/functional/apps/security/trial_license/login_selector.ts @@ -6,7 +6,6 @@ import expect from '@kbn/expect'; import { parse } from 'url'; -import { USERS_PATH } from '../../../../../plugins/security/public/management/management_urls'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { @@ -42,9 +41,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('can login with Login Form preserving original URL', async () => { - await PageObjects.common.navigateToActualUrl('kibana', USERS_PATH, { + await PageObjects.common.navigateToUrl('management', 'security/users', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await PageObjects.common.waitUntilUrlIncludes('next='); @@ -52,14 +52,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // We need to make sure that both path and hash are respected. const currentURL = parse(await browser.getCurrentUrl()); - expect(currentURL.pathname).to.eql('/app/kibana'); - expect(currentURL.hash).to.eql(`#${USERS_PATH}`); + + expect(currentURL.pathname).to.eql('/app/management/security/users'); }); it('can login with SSO preserving original URL', async () => { - await PageObjects.common.navigateToActualUrl('kibana', USERS_PATH, { + await PageObjects.common.navigateToUrl('management', 'security/users', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await PageObjects.common.waitUntilUrlIncludes('next='); @@ -67,8 +68,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // We need to make sure that both path and hash are respected. const currentURL = parse(await browser.getCurrentUrl()); - expect(currentURL.pathname).to.eql('/app/kibana'); - expect(currentURL.hash).to.eql(`#${USERS_PATH}`); + expect(currentURL.pathname).to.eql('/app/management/security/users'); }); it('should show toast with error if SSO fails', async () => { diff --git a/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts b/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts index d43d56ae22e36..d904c407b060b 100644 --- a/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts +++ b/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts @@ -65,32 +65,31 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it(`can navigate to spaces grid page`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/spaces', { + await PageObjects.common.navigateToUrl('management', 'kibana/spaces', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await testSubjects.existOrFail('spaces-grid-page'); }); it(`can navigate to create new space page`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/spaces/create', { + await PageObjects.common.navigateToUrl('management', 'kibana/spaces/create', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); await testSubjects.existOrFail('spaces-edit-page'); }); it(`can navigate to edit space page`, async () => { - await PageObjects.common.navigateToActualUrl( - 'kibana', - 'management/kibana/spaces/edit/default', - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.common.navigateToUrl('management', 'kibana/spaces/edit/default', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); await testSubjects.existOrFail('spaces-edit-page'); }); @@ -139,39 +138,38 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it(`doesn't display Spaces management section`, async () => { await PageObjects.settings.navigateTo(); - await testSubjects.existOrFail('management-landing'); // this ensures we've gotten to the management page + await testSubjects.existOrFail('managementHome'); // this ensures we've gotten to the management page await testSubjects.missingOrFail('spaces'); }); it(`can't navigate to spaces grid page`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/spaces', { + await PageObjects.common.navigateToUrl('management', 'kibana/spaces', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); - await testSubjects.existOrFail('management-landing'); + await testSubjects.existOrFail('managementHome'); }); it(`can't navigate to create new space page`, async () => { - await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/spaces/create', { + await PageObjects.common.navigateToUrl('management', 'kibana/spaces/create', { ensureCurrentUrl: false, shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, }); - await testSubjects.existOrFail('management-landing'); + await testSubjects.existOrFail('managementHome'); }); it(`can't navigate to edit space page`, async () => { - await PageObjects.common.navigateToActualUrl( - 'kibana', - 'management/kibana/spaces/edit/default', - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - } - ); + await PageObjects.common.navigateToUrl('management', 'kibana/spaces/edit/default', { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + }); - await testSubjects.existOrFail('management-landing'); + await testSubjects.existOrFail('managementHome'); }); }); }); diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 4ff9c5cd1df56..bcc7c978ca237 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -113,8 +113,7 @@ export default async function ({ readConfigFile }) { pathname: '/app/monitoring', }, logstashPipelines: { - pathname: '/app/kibana', - hash: '/management/ingest/pipelines', + pathname: '/app/management/ingest/pipelines', }, maps: { pathname: '/app/maps', @@ -150,59 +149,46 @@ export default async function ({ readConfigFile }) { pathname: '/app/ml', }, roleMappings: { - pathname: '/app/kibana', - hash: '/management/security/role_mappings', + pathname: '/app/management/security/role_mappings', }, rollupJob: { - pathname: '/app/kibana', - hash: '/management/data/rollup_jobs/', + pathname: '/app/management/data/rollup_jobs', }, apiKeys: { - pathname: '/app/kibana', - hash: '/management/security/api_keys/', + pathname: '/app/management/security/api_keys', }, licenseManagement: { - pathname: '/app/kibana', - hash: '/management/stack/license_management', + pathname: '/app/management/stack/license_management', }, indexManagement: { - pathname: '/app/kibana', - hash: '/management/data/index_management', + pathname: '/app/management/data/index_management', }, indexLifecycleManagement: { - pathname: '/app/kibana', - hash: '/management/data/index_lifecycle_management', + pathname: '/app/management/data/index_lifecycle_management', }, ingestPipelines: { - pathname: '/app/kibana', - hash: '/management/ingest/ingest_pipelines', + pathname: '/app/management/ingest/ingest_pipelines', }, snapshotRestore: { - pathname: '/app/kibana', - hash: '/management/data/snapshot_restore', - }, - crossClusterReplication: { - pathname: '/app/kibana', - hash: '/management/data/cross_cluster_replication', + pathname: '/app/management/data/snapshot_restore', }, remoteClusters: { - pathname: '/app/kibana', - hash: '/management/data/remote_clusters', + pathname: '/app/management/data/remote_clusters', + }, + crossClusterReplication: { + pathname: '/app/management/data/cross_cluster_replication', }, apm: { pathname: '/app/apm', }, watcher: { - pathname: '/app/kibana', - hash: '/management/insightsAndAlerting/watcher/watches/', + pathname: '/app/management/insightsAndAlerting/watcher/watches', }, transform: { - pathname: '/app/kibana/', - hash: '/management/data/transform', + pathname: '/app/management/data/transform', }, reporting: { - pathname: '/app/kibana/', - hash: '/management/insightsAndAlerting/reporting', + pathname: '/app/management/insightsAndAlerting/reporting', }, }, diff --git a/x-pack/test/functional_with_es_ssl/config.ts b/x-pack/test/functional_with_es_ssl/config.ts index b71c2cba03f21..3452caf0ccdb3 100644 --- a/x-pack/test/functional_with_es_ssl/config.ts +++ b/x-pack/test/functional_with_es_ssl/config.ts @@ -50,8 +50,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { apps: { ...xpackFunctionalConfig.get('apps'), triggersActions: { - pathname: '/app/kibana', - hash: '/management/insightsAndAlerting/triggersActions', + pathname: '/app/management/insightsAndAlerting/triggersActions', }, }, esTestCluster: {