in src/core/packages/chrome/browser-internal/src/chrome_service.tsx [243:647]
public async start({
application,
docLinks,
http,
injectedMetadata,
notifications,
customBranding,
i18n: i18nService,
theme,
userProfile,
uiSettings,
}: StartDeps): Promise<InternalChromeStart> {
this.initVisibility(application);
this.handleEuiFullScreenChanges();
handleSystemColorModeChange({
notifications,
coreStart: { i18n: i18nService, theme, userProfile },
stop$: this.stop$,
http,
uiSettings,
});
// commented out until https://github.com/elastic/kibana/issues/201805 can be fixed
// this.handleEuiDevProviderWarning(notifications);
const globalHelpExtensionMenuLinks$ = new BehaviorSubject<ChromeGlobalHelpExtensionMenuLink[]>(
[]
);
const helpExtension$ = new BehaviorSubject<ChromeHelpExtension | undefined>(undefined);
const breadcrumbs$ = new BehaviorSubject<ChromeBreadcrumb[]>([]);
const breadcrumbsAppendExtensions$ = new BehaviorSubject<ChromeBreadcrumbsAppendExtension[]>(
[]
);
const badge$ = new BehaviorSubject<ChromeBadge | undefined>(undefined);
const customNavLink$ = new BehaviorSubject<ChromeNavLink | undefined>(undefined);
const helpSupportUrl$ = new BehaviorSubject<string>(docLinks.links.kibana.askElastic);
const isNavDrawerLocked$ = new BehaviorSubject(localStorage.getItem(IS_LOCKED_KEY) === 'true');
// ChromeStyle is set to undefined by default, which means that no header will be rendered until
// setChromeStyle(). This is to avoid a flickering between the "classic" and "project" header meanwhile
// we load the user profile to check if the user opted out of the new solution navigation.
const chromeStyleSubject$ = new BehaviorSubject<ChromeStyle | undefined>(undefined);
const getKbnVersionClass = () => {
// we assume that the version is valid and has the form 'X.X.X'
// strip out `SNAPSHOT` and reformat to 'X-X-X'
const formattedVersionClass = this.params.kibanaVersion
.replace(SNAPSHOT_REGEX, '')
.split('.')
.join('-');
return `kbnVersion-${formattedVersionClass}`;
};
const chromeStyle$ = chromeStyleSubject$.pipe(
filter((style): style is ChromeStyle => style !== undefined),
takeUntil(this.stop$)
);
const setChromeStyle = (style: ChromeStyle) => {
if (style === chromeStyleSubject$.getValue()) return;
chromeStyleSubject$.next(style);
};
const headerBanner$ = new BehaviorSubject<ChromeUserBanner | undefined>(undefined);
const bodyClasses$ = combineLatest([
headerBanner$,
this.isVisible$!,
chromeStyleSubject$,
application.currentActionMenu$,
]).pipe(
map(([headerBanner, isVisible, chromeStyle, actionMenu]) => {
return [
'kbnBody',
headerBanner ? 'kbnBody--hasHeaderBanner' : 'kbnBody--noHeaderBanner',
isVisible ? 'kbnBody--chromeVisible' : 'kbnBody--chromeHidden',
chromeStyle === 'project' && actionMenu ? 'kbnBody--hasProjectActionMenu' : '',
getKbnVersionClass(),
].filter((className) => !!className);
})
);
const navControls = this.navControls.start();
const navLinks = this.navLinks.start({ application, http });
const projectNavigation = this.projectNavigation.start({
application,
navLinksService: navLinks,
http,
chromeBreadcrumbs$: breadcrumbs$,
logger: this.logger,
});
const recentlyAccessed = this.recentlyAccessed.start({ http, key: 'recentlyAccessed' });
const docTitle = this.docTitle.start();
const { customBranding$ } = customBranding;
const helpMenuLinks$ = navControls.getHelpMenuLinks$();
// erase chrome fields from a previous app while switching to a next app
application.currentAppId$.subscribe(() => {
helpExtension$.next(undefined);
breadcrumbs$.next([]);
badge$.next(undefined);
docTitle.reset();
});
const setIsNavDrawerLocked = (isLocked: boolean) => {
isNavDrawerLocked$.next(isLocked);
localStorage.setItem(IS_LOCKED_KEY, `${isLocked}`);
};
const getIsNavDrawerLocked$ = isNavDrawerLocked$.pipe(takeUntil(this.stop$));
const validateChromeStyle = () => {
const chromeStyle = chromeStyleSubject$.getValue();
if (chromeStyle !== 'project') {
// Helps ensure callers go through the serverless plugin to get here.
throw new Error(
`Invalid ChromeStyle value of "${chromeStyle}". This method requires ChromeStyle set to "project".`
);
}
};
const setProjectSideNavComponent = (component: ISideNavComponent | null) => {
validateChromeStyle();
projectNavigation.setSideNavComponent(component);
};
function initProjectNavigation<
LinkId extends AppDeepLinkId = AppDeepLinkId,
Id extends string = string,
ChildrenId extends string = Id
>(
id: SolutionId,
navigationTree$: Observable<NavigationTreeDefinition<LinkId, Id, ChildrenId>>
) {
validateChromeStyle();
projectNavigation.initNavigation(id, navigationTree$);
}
const setProjectBreadcrumbs = (
breadcrumbs: ChromeBreadcrumb[] | ChromeBreadcrumb,
params?: ChromeSetProjectBreadcrumbsParams
) => {
projectNavigation.setProjectBreadcrumbs(breadcrumbs, params);
};
const setClassicBreadcrumbs = (
newBreadcrumbs: ChromeBreadcrumb[],
{ project }: ChromeSetBreadcrumbsParams = {}
) => {
breadcrumbs$.next(newBreadcrumbs);
if (project) {
const { value: projectValue, absolute = false } = project;
setProjectBreadcrumbs(projectValue ?? [], { absolute });
}
};
const setProjectHome = (homeHref: string) => {
validateChromeStyle();
projectNavigation.setProjectHome(homeHref);
};
const setProjectName = (projectName: string) => {
validateChromeStyle();
projectNavigation.setProjectName(projectName);
};
const setIsSideNavCollapsed = (isCollapsed: boolean) => {
localStorage.setItem(IS_SIDENAV_COLLAPSED_KEY, JSON.stringify(isCollapsed));
this.isSideNavCollapsed$.next(isCollapsed);
};
if (!this.params.browserSupportsCsp && injectedMetadata.getCspConfig().warnLegacyBrowsers) {
notifications.toasts.addWarning({
title: mountReactNode(
<FormattedMessage
id="core.chrome.legacyBrowserWarning"
defaultMessage="Your browser does not meet the security requirements for Kibana."
/>
),
});
}
const getHeaderComponent = () => {
const defaultChromeStyle = chromeStyleSubject$.getValue();
const HeaderComponent = () => {
const isVisible = useObservable(this.isVisible$);
const chromeStyle = useObservable(chromeStyle$, defaultChromeStyle);
if (!isVisible) {
return (
<div data-test-subj="kibanaHeaderChromeless">
<LoadingIndicator loadingCount$={http.getLoadingCount$()} showAsBar />
<HeaderTopBanner headerBanner$={headerBanner$.pipe(takeUntil(this.stop$))} />
</div>
);
}
if (chromeStyle === undefined) return null;
// render header
if (chromeStyle === 'project') {
const projectNavigationComponent$ = projectNavigation.getProjectSideNavComponent$();
const projectBreadcrumbs$ = projectNavigation
.getProjectBreadcrumbs$()
.pipe(takeUntil(this.stop$));
const activeNodes$ = projectNavigation.getActiveNodes$();
const ProjectHeaderWithNavigationComponent = () => {
const CustomSideNavComponent = useObservable(projectNavigationComponent$, {
current: null,
});
const activeNodes = useObservable(activeNodes$, []);
const currentProjectBreadcrumbs$ = projectBreadcrumbs$;
const SideNavComponent = useMemo<ISideNavComponent>(() => {
if (CustomSideNavComponent.current) {
return CustomSideNavComponent.current;
}
return () => null;
}, [CustomSideNavComponent]);
return (
<ProjectHeader
isServerless={this.isServerless}
application={application}
globalHelpExtensionMenuLinks$={globalHelpExtensionMenuLinks$}
actionMenu$={application.currentActionMenu$}
breadcrumbs$={currentProjectBreadcrumbs$}
breadcrumbsAppendExtensions$={breadcrumbsAppendExtensions$.pipe(
takeUntil(this.stop$)
)}
customBranding$={customBranding$}
helpExtension$={helpExtension$.pipe(takeUntil(this.stop$))}
helpSupportUrl$={helpSupportUrl$.pipe(takeUntil(this.stop$))}
helpMenuLinks$={helpMenuLinks$}
navControlsLeft$={navControls.getLeft$()}
navControlsCenter$={navControls.getCenter$()}
navControlsRight$={navControls.getRight$()}
loadingCount$={http.getLoadingCount$()}
headerBanner$={headerBanner$.pipe(takeUntil(this.stop$))}
homeHref$={projectNavigation.getProjectHome$()}
docLinks={docLinks}
kibanaVersion={injectedMetadata.getKibanaVersion()}
prependBasePath={http.basePath.prepend}
isSideNavCollapsed$={this.isSideNavCollapsed$}
toggleSideNav={setIsSideNavCollapsed}
>
<SideNavComponent activeNodes={activeNodes} />
</ProjectHeader>
);
};
return <ProjectHeaderWithNavigationComponent />;
}
return (
<Header
isServerless={this.isServerless}
loadingCount$={http.getLoadingCount$()}
application={application}
headerBanner$={headerBanner$.pipe(takeUntil(this.stop$))}
badge$={badge$.pipe(takeUntil(this.stop$))}
basePath={http.basePath}
breadcrumbs$={breadcrumbs$.pipe(takeUntil(this.stop$))}
breadcrumbsAppendExtensions$={breadcrumbsAppendExtensions$.pipe(takeUntil(this.stop$))}
customNavLink$={customNavLink$.pipe(takeUntil(this.stop$))}
kibanaDocLink={docLinks.links.kibana.guide}
docLinks={docLinks}
forceAppSwitcherNavigation$={navLinks.getForceAppSwitcherNavigation$()}
globalHelpExtensionMenuLinks$={globalHelpExtensionMenuLinks$}
helpExtension$={helpExtension$.pipe(takeUntil(this.stop$))}
helpSupportUrl$={helpSupportUrl$.pipe(takeUntil(this.stop$))}
helpMenuLinks$={helpMenuLinks$}
homeHref={http.basePath.prepend('/app/home')}
kibanaVersion={injectedMetadata.getKibanaVersion()}
navLinks$={navLinks.getNavLinks$()}
recentlyAccessed$={recentlyAccessed.get$()}
navControlsLeft$={navControls.getLeft$()}
navControlsCenter$={navControls.getCenter$()}
navControlsRight$={navControls.getRight$()}
navControlsExtension$={navControls.getExtension$()}
onIsLockedUpdate={setIsNavDrawerLocked}
isLocked$={getIsNavDrawerLocked$}
customBranding$={customBranding$}
/>
);
};
return <HeaderComponent />;
};
return {
navControls,
navLinks,
recentlyAccessed,
docTitle,
getHeaderComponent,
getIsVisible$: () => this.isVisible$,
setIsVisible: this.setIsVisible.bind(this),
getBadge$: () => badge$.pipe(takeUntil(this.stop$)),
setBadge: (badge: ChromeBadge) => {
badge$.next(badge);
},
getBreadcrumbs$: () => breadcrumbs$.pipe(takeUntil(this.stop$)),
setBreadcrumbs: setClassicBreadcrumbs,
getBreadcrumbsAppendExtensions$: () =>
breadcrumbsAppendExtensions$.pipe(takeUntil(this.stop$)),
setBreadcrumbsAppendExtension: (
breadcrumbsAppendExtension: ChromeBreadcrumbsAppendExtension
) => {
breadcrumbsAppendExtensions$.next(
[...breadcrumbsAppendExtensions$.getValue(), breadcrumbsAppendExtension].sort(
({ order: orderA = 50 }, { order: orderB = 50 }) => orderA - orderB
)
);
return () => {
breadcrumbsAppendExtensions$.next(
breadcrumbsAppendExtensions$
.getValue()
.filter((ext) => ext !== breadcrumbsAppendExtension)
);
};
},
getGlobalHelpExtensionMenuLinks$: () => globalHelpExtensionMenuLinks$.asObservable(),
registerGlobalHelpExtensionMenuLink: (
globalHelpExtensionMenuLink: ChromeGlobalHelpExtensionMenuLink
) => {
globalHelpExtensionMenuLinks$.next([
...globalHelpExtensionMenuLinks$.value,
globalHelpExtensionMenuLink,
]);
},
getHelpExtension$: () => helpExtension$.pipe(takeUntil(this.stop$)),
setHelpExtension: (helpExtension?: ChromeHelpExtension) => {
helpExtension$.next(helpExtension);
},
setHelpSupportUrl: (url: string) => helpSupportUrl$.next(url),
getHelpSupportUrl$: () => helpSupportUrl$.pipe(takeUntil(this.stop$)),
getIsNavDrawerLocked$: () => getIsNavDrawerLocked$,
getCustomNavLink$: () => customNavLink$.pipe(takeUntil(this.stop$)),
setCustomNavLink: (customNavLink?: ChromeNavLink) => {
customNavLink$.next(customNavLink);
},
setHelpMenuLinks: (helpMenuLinks: ChromeHelpMenuLink[]) => {
navControls.setHelpMenuLinks(helpMenuLinks);
},
setHeaderBanner: (headerBanner?: ChromeUserBanner) => {
headerBanner$.next(headerBanner);
},
hasHeaderBanner$: () => {
return headerBanner$.pipe(
takeUntil(this.stop$),
map((banner) => Boolean(banner))
);
},
getBodyClasses$: () => bodyClasses$.pipe(takeUntil(this.stop$)),
setChromeStyle,
getChromeStyle$: () => chromeStyle$,
sideNav: {
getIsCollapsed$: () => this.isSideNavCollapsed$.asObservable(),
setIsCollapsed: setIsSideNavCollapsed,
getPanelSelectedNode$: projectNavigation.getPanelSelectedNode$.bind(projectNavigation),
setPanelSelectedNode: projectNavigation.setPanelSelectedNode.bind(projectNavigation),
getIsFeedbackBtnVisible$: () =>
combineLatest([this.isFeedbackBtnVisible$, this.isSideNavCollapsed$]).pipe(
map(([isVisible, isCollapsed]) => isVisible && !isCollapsed)
),
setIsFeedbackBtnVisible: (isVisible: boolean) => this.isFeedbackBtnVisible$.next(isVisible),
},
getActiveSolutionNavId$: () => projectNavigation.getActiveSolutionNavId$(),
project: {
setHome: setProjectHome,
setCloudUrls: projectNavigation.setCloudUrls.bind(projectNavigation),
setProjectName,
initNavigation: initProjectNavigation,
getNavigationTreeUi$: () => projectNavigation.getNavigationTreeUi$(),
setSideNavComponent: setProjectSideNavComponent,
setBreadcrumbs: setProjectBreadcrumbs,
getBreadcrumbs$: projectNavigation.getProjectBreadcrumbs$.bind(projectNavigation),
getActiveNavigationNodes$: () => projectNavigation.getActiveNodes$(),
updateSolutionNavigations: projectNavigation.updateSolutionNavigations,
changeActiveSolutionNavigation: projectNavigation.changeActiveSolutionNavigation,
},
};
}