mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Serverless] Improve breadcrumbs in management (#166259)
## Summary
Close https://github.com/elastic/kibana/issues/164507
This PR improves management breadcrumbs in serverless project.

- **Management**: I removed dependency from serverless -> management.
details:
https://github.com/elastic/kibana/pull/166259#discussion_r1324412333
- **Search**: Search project links directly to some management sub-apps
from the side nav. In some cases I hid the breadcrumb that comes from
the navigation config to avoid duplication: for example there was`Index
Management > Index Management` where the first came from the nav and the
second from the management sub-app.
- **Security**: For security I disabled setting management sub-app
breadcrumbs from the navigation config as they are set from the apps.
This allows for deeper breadcrumbs, beyond just nav.
https://github.com/elastic/kibana/pull/166259#discussion_r1324411585
This commit is contained in:
parent
36a7a80f38
commit
d91fe9fc92
14 changed files with 71 additions and 13 deletions
|
@ -10,7 +10,8 @@
|
|||
"share"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"home"
|
||||
"home",
|
||||
"serverless"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"kibanaReact",
|
||||
|
|
|
@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { BehaviorSubject } from 'rxjs';
|
||||
import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import { HomePublicPluginSetup } from '@kbn/home-plugin/public';
|
||||
import { ServerlessPluginStart } from '@kbn/serverless/public';
|
||||
import {
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
|
@ -39,6 +40,7 @@ interface ManagementSetupDependencies {
|
|||
|
||||
interface ManagementStartDependencies {
|
||||
share: SharePluginStart;
|
||||
serverless?: ServerlessPluginStart;
|
||||
}
|
||||
|
||||
export class ManagementPlugin
|
||||
|
@ -122,13 +124,21 @@ export class ManagementPlugin
|
|||
updater$: this.appUpdater,
|
||||
async mount(params: AppMountParameters) {
|
||||
const { renderApp } = await import('./application');
|
||||
const [coreStart] = await core.getStartServices();
|
||||
const [coreStart, deps] = await core.getStartServices();
|
||||
|
||||
return renderApp(params, {
|
||||
sections: getSectionsServiceStartPrivate(),
|
||||
kibanaVersion,
|
||||
coreStart,
|
||||
setBreadcrumbs: coreStart.chrome.setBreadcrumbs,
|
||||
setBreadcrumbs: (newBreadcrumbs) => {
|
||||
if (deps.serverless) {
|
||||
// drop the root management breadcrumb in serverless because it comes from the navigation tree
|
||||
const [, ...trailingBreadcrumbs] = newBreadcrumbs;
|
||||
deps.serverless.setBreadcrumbs(trailingBreadcrumbs);
|
||||
} else {
|
||||
coreStart.chrome.setBreadcrumbs(newBreadcrumbs);
|
||||
}
|
||||
},
|
||||
isSidebarEnabled$: managementPlugin.isSidebarEnabled$,
|
||||
cardsNavigationConfig$: managementPlugin.cardsNavigationConfig$,
|
||||
landingPageRedirect$: managementPlugin.landingPageRedirect$,
|
||||
|
|
|
@ -25,7 +25,8 @@
|
|||
"@kbn/test-jest-helpers",
|
||||
"@kbn/config-schema",
|
||||
"@kbn/core-application-browser",
|
||||
"@kbn/core-http-browser"
|
||||
"@kbn/core-http-browser",
|
||||
"@kbn/serverless"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
|
|
|
@ -27,6 +27,7 @@ export const configureNavigation = (
|
|||
if (!serverConfig.developer.disableManagementUrlRedirect) {
|
||||
management.setLandingPageRedirect(SECURITY_PROJECT_SETTINGS_PATH);
|
||||
}
|
||||
management.setIsSidebarEnabled(false);
|
||||
|
||||
serverless.setProjectHome(APP_PATH);
|
||||
serverless.setSideNavComponent(getSecuritySideNavComponent(services));
|
||||
|
|
|
@ -35,6 +35,10 @@ const HIDDEN_BREADCRUMBS = new Set<ProjectPageName>([
|
|||
SecurityPageName.sessions,
|
||||
]);
|
||||
|
||||
const isBreadcrumbHidden = (id: ProjectPageName): boolean =>
|
||||
HIDDEN_BREADCRUMBS.has(id) ||
|
||||
id.startsWith('management:'); /* management sub-pages set their breadcrumbs themselves */
|
||||
|
||||
export const subscribeNavigationTree = (services: Services): void => {
|
||||
const { serverless, getProjectNavLinks$ } = services;
|
||||
|
||||
|
@ -59,13 +63,12 @@ export const getFormatChromeProjectNavNodes = (services: Services) => {
|
|||
const navLinkId = getNavLinkIdFromProjectPageName(id);
|
||||
|
||||
if (chrome.navLinks.has(navLinkId)) {
|
||||
const breadcrumbHidden = HIDDEN_BREADCRUMBS.has(id);
|
||||
const link: ChromeProjectNavigationNode = {
|
||||
id: navLinkId,
|
||||
title,
|
||||
path: [...path, navLinkId],
|
||||
deepLink: chrome.navLinks.get(navLinkId),
|
||||
...(breadcrumbHidden && { breadcrumbStatus: 'hidden' }),
|
||||
...(isBreadcrumbHidden(id) && { breadcrumbStatus: 'hidden' }),
|
||||
};
|
||||
// check default navigation for children
|
||||
const defaultChildrenNav = getDefaultChildrenNav(id, link);
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
],
|
||||
"requiredPlugins": [
|
||||
"kibanaReact",
|
||||
"management",
|
||||
"cloud"
|
||||
],
|
||||
"optionalPlugins": [],
|
||||
|
|
|
@ -49,7 +49,6 @@ export class ServerlessPlugin
|
|||
dependencies: ServerlessPluginStartDependencies
|
||||
): ServerlessPluginStart {
|
||||
const { developer } = this.config;
|
||||
const { management } = dependencies;
|
||||
|
||||
if (developer && developer.projectSwitcher && developer.projectSwitcher.enabled) {
|
||||
const { currentType } = developer.projectSwitcher;
|
||||
|
@ -61,7 +60,6 @@ export class ServerlessPlugin
|
|||
}
|
||||
|
||||
core.chrome.setChromeStyle('project');
|
||||
management.setIsSidebarEnabled(false);
|
||||
|
||||
// Casting the "chrome.projects" service to an "internal" type: this is intentional to obscure the property from Typescript.
|
||||
const { project } = core.chrome as InternalChromeStart;
|
||||
|
|
|
@ -12,7 +12,6 @@ import type {
|
|||
SideNavComponent,
|
||||
ChromeProjectNavigationNode,
|
||||
} from '@kbn/core-chrome-browser';
|
||||
import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public';
|
||||
import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
|
||||
import type { Observable } from 'rxjs';
|
||||
|
||||
|
@ -31,11 +30,9 @@ export interface ServerlessPluginStart {
|
|||
}
|
||||
|
||||
export interface ServerlessPluginSetupDependencies {
|
||||
management: ManagementSetup;
|
||||
cloud: CloudSetup;
|
||||
}
|
||||
|
||||
export interface ServerlessPluginStartDependencies {
|
||||
management: ManagementStart;
|
||||
cloud: CloudStart;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
"@kbn/config-schema",
|
||||
"@kbn/core",
|
||||
"@kbn/kibana-react-plugin",
|
||||
"@kbn/management-plugin",
|
||||
"@kbn/serverless-project-switcher",
|
||||
"@kbn/serverless-types",
|
||||
"@kbn/utils",
|
||||
|
|
|
@ -45,6 +45,7 @@ export class ServerlessObservabilityPlugin
|
|||
observabilityShared.setIsSidebarEnabled(false);
|
||||
serverless.setProjectHome('/app/observability/landing');
|
||||
serverless.setSideNavComponent(getObservabilitySideNavComponent(core, { serverless, cloud }));
|
||||
management.setIsSidebarEnabled(false);
|
||||
management.setupCardsNavigation({
|
||||
enabled: true,
|
||||
hideLinksTo: [appIds.RULES],
|
||||
|
|
|
@ -93,12 +93,16 @@ const navigationTree: NavigationTreeDefinition = {
|
|||
defaultMessage: 'Index Management',
|
||||
}),
|
||||
link: 'management:index_management',
|
||||
breadcrumbStatus:
|
||||
'hidden' /* management sub-pages set their breadcrumbs themselves */,
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.serverlessSearch.nav.content.pipelines', {
|
||||
defaultMessage: 'Pipelines',
|
||||
}),
|
||||
link: 'management:ingest_pipelines',
|
||||
breadcrumbStatus:
|
||||
'hidden' /* management sub-pages set their breadcrumbs themselves */,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -110,6 +114,8 @@ const navigationTree: NavigationTreeDefinition = {
|
|||
children: [
|
||||
{
|
||||
link: 'management:api_keys',
|
||||
breadcrumbStatus:
|
||||
'hidden' /* management sub-pages set their breadcrumbs themselves */,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -79,6 +79,7 @@ export class ServerlessSearchPlugin
|
|||
): ServerlessSearchPluginStart {
|
||||
serverless.setProjectHome('/app/elasticsearch');
|
||||
serverless.setSideNavComponent(createComponent(core, { serverless, cloud }));
|
||||
management.setIsSidebarEnabled(false);
|
||||
management.setupCardsNavigation({
|
||||
enabled: true,
|
||||
hideLinksTo: [appIds.MAINTENANCE_WINDOWS],
|
||||
|
|
|
@ -151,6 +151,25 @@ export function SvlCommonNavigationProvider(ctx: FtrProviderContext) {
|
|||
});
|
||||
}
|
||||
},
|
||||
async expectBreadcrumbMissing(by: { deepLinkId: AppDeepLinkId } | { text: string }) {
|
||||
if ('deepLinkId' in by) {
|
||||
await testSubjects.missingOrFail(`~breadcrumb-deepLinkId-${by.deepLinkId}`);
|
||||
} else {
|
||||
await retry.try(async () => {
|
||||
expect(await getByVisibleText('~breadcrumb', by.text)).be(null);
|
||||
});
|
||||
}
|
||||
},
|
||||
async expectBreadcrumbTexts(expectedBreadcrumbTexts: string[]) {
|
||||
await retry.try(async () => {
|
||||
const breadcrumbsContainer = await testSubjects.find('breadcrumbs');
|
||||
const breadcrumbs = await breadcrumbsContainer.findAllByTestSubject('~breadcrumb');
|
||||
breadcrumbs.shift(); // remove home
|
||||
expect(expectedBreadcrumbTexts.length).to.eql(breadcrumbs.length);
|
||||
const texts = await Promise.all(breadcrumbs.map((b) => b.getVisibleText()));
|
||||
expect(expectedBreadcrumbTexts).to.eql(texts);
|
||||
});
|
||||
},
|
||||
},
|
||||
search: new SvlNavigationSearchPageObject(ctx),
|
||||
recent: {
|
||||
|
|
|
@ -71,6 +71,28 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
|
|||
await expectNoPageReload();
|
||||
});
|
||||
|
||||
it("management apps from the sidenav hide the 'stack management' root from the breadcrumbs", async () => {
|
||||
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:triggersActions' });
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Explore', 'Alerts', 'Rules']);
|
||||
|
||||
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:index_management' });
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Content', 'Index Management']);
|
||||
|
||||
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:ingest_pipelines' });
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Content', 'Ingest Pipelines']);
|
||||
|
||||
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management:api_keys' });
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Security', 'API keys']);
|
||||
});
|
||||
|
||||
it('navigate management', async () => {
|
||||
await svlCommonNavigation.sidenav.openSection('project_settings_project_nav');
|
||||
await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'management' });
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Management']);
|
||||
await testSubjects.click('app-card-dataViews');
|
||||
await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Management', 'Data views']);
|
||||
});
|
||||
|
||||
it('navigate using search', async () => {
|
||||
await svlCommonNavigation.search.showSearch();
|
||||
// TODO: test something search project specific instead of generic discover
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue