public async start()

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,
      },
    };
  }