mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[SharedUX] Custom branding service (#148273)
## Summary This PR adds a new `CustomBranding` service and exposes it from core, both on the server and client side. The purpose of the service is to retrieve custom branding properties and propagate them to the appropriate core service (`chrome` on the client-side and `rendering` on the server-side). The client side receives server-side properties through `injectedMetadata`. Note that the service itself is not responsible for reading the properties from `uiSettings`; this task is offloaded to `customBranding` plugin. I deployed one of the previous commits [here]([ttps://majagrubic-pr-148273-custom-branding-service-server.kbndev.co/aiy/app/home#/](https://majagrubic-pr-148273-custom-branding-service-server.kbndev.co/aiy/app/home#/)), so you can see a custom logo set (client-side) and page title set (server-side). ### Checklist Delete any items that are not applicable to this PR. - [X] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [X] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [X] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ~- [] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/))~ ~- [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))~ ~- [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)~ ~- [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))~ ~- [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers)~ ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
d2788bcad7
commit
4522e04287
114 changed files with 1252 additions and 52 deletions
7
.github/CODEOWNERS
vendored
7
.github/CODEOWNERS
vendored
|
@ -724,6 +724,13 @@ packages/core/chrome/core-chrome-browser @elastic/kibana-core
|
|||
packages/core/chrome/core-chrome-browser-internal @elastic/kibana-core
|
||||
packages/core/chrome/core-chrome-browser-mocks @elastic/kibana-core
|
||||
packages/core/config/core-config-server-internal @elastic/kibana-core
|
||||
packages/core/custom-branding/core-custom-branding-browser @elastic/kibana-global-experience
|
||||
packages/core/custom-branding/core-custom-branding-browser-internal @elastic/kibana-global-experience
|
||||
packages/core/custom-branding/core-custom-branding-browser-mocks @elastic/kibana-global-experience
|
||||
packages/core/custom-branding/core-custom-branding-common @elastic/kibana-global-experience
|
||||
packages/core/custom-branding/core-custom-branding-server @elastic/kibana-global-experience
|
||||
packages/core/custom-branding/core-custom-branding-server-internal @elastic/kibana-global-experience
|
||||
packages/core/custom-branding/core-custom-branding-server-mocks @elastic/kibana-global-experience
|
||||
packages/core/deprecations/core-deprecations-browser @elastic/kibana-core
|
||||
packages/core/deprecations/core-deprecations-browser-internal @elastic/kibana-core
|
||||
packages/core/deprecations/core-deprecations-browser-mocks @elastic/kibana-core
|
||||
|
|
|
@ -180,6 +180,13 @@
|
|||
"@kbn/core-chrome-browser-internal": "link:packages/core/chrome/core-chrome-browser-internal",
|
||||
"@kbn/core-chrome-browser-mocks": "link:packages/core/chrome/core-chrome-browser-mocks",
|
||||
"@kbn/core-config-server-internal": "link:packages/core/config/core-config-server-internal",
|
||||
"@kbn/core-custom-branding-browser": "link:packages/core/custom-branding/core-custom-branding-browser",
|
||||
"@kbn/core-custom-branding-browser-internal": "link:packages/core/custom-branding/core-custom-branding-browser-internal",
|
||||
"@kbn/core-custom-branding-browser-mocks": "link:packages/core/custom-branding/core-custom-branding-browser-mocks",
|
||||
"@kbn/core-custom-branding-common": "link:packages/core/custom-branding/core-custom-branding-common",
|
||||
"@kbn/core-custom-branding-server": "link:packages/core/custom-branding/core-custom-branding-server",
|
||||
"@kbn/core-custom-branding-server-internal": "link:packages/core/custom-branding/core-custom-branding-server-internal",
|
||||
"@kbn/core-custom-branding-server-mocks": "link:packages/core/custom-branding/core-custom-branding-server-mocks",
|
||||
"@kbn/core-deprecations-browser": "link:packages/core/deprecations/core-deprecations-browser",
|
||||
"@kbn/core-deprecations-browser-internal": "link:packages/core/deprecations/core-deprecations-browser-internal",
|
||||
"@kbn/core-deprecations-browser-mocks": "link:packages/core/deprecations/core-deprecations-browser-mocks",
|
||||
|
|
|
@ -15,6 +15,7 @@ import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
|||
import { themeServiceMock } from '@kbn/core-theme-browser-mocks';
|
||||
import type { AppMountParameters, AppUpdater } from '@kbn/core-application-browser';
|
||||
import { overlayServiceMock } from '@kbn/core-overlays-browser-mocks';
|
||||
import { customBrandingServiceMock } from '@kbn/core-custom-branding-browser-mocks';
|
||||
import type { MountPoint } from '@kbn/core-mount-utils-browser';
|
||||
import type { MockLifecycle } from '../src/test_helpers/test_types';
|
||||
import { ApplicationService } from '../src/application_service';
|
||||
|
@ -48,6 +49,7 @@ describe('ApplicationService', () => {
|
|||
http,
|
||||
overlays: overlayServiceMock.createStartContract(),
|
||||
theme: themeServiceMock.createStartContract(),
|
||||
customBranding: customBrandingServiceMock.createStartContract(),
|
||||
};
|
||||
service = new ApplicationService();
|
||||
});
|
||||
|
|
|
@ -67,6 +67,14 @@ exports[`#start() getComponent returns renderable JSX tree 1`] = `
|
|||
"thrownError": null,
|
||||
}
|
||||
}
|
||||
hasCustomBranding$={
|
||||
Observable {
|
||||
"operator": [Function],
|
||||
"source": Observable {
|
||||
"_subscribe": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
history={
|
||||
Object {
|
||||
"push": [MockFunction],
|
||||
|
|
|
@ -20,6 +20,7 @@ import { mount, shallow } from 'enzyme';
|
|||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
import { themeServiceMock } from '@kbn/core-theme-browser-mocks';
|
||||
import { overlayServiceMock } from '@kbn/core-overlays-browser-mocks';
|
||||
import { customBrandingServiceMock } from '@kbn/core-custom-branding-browser-mocks';
|
||||
import { MockLifecycle } from './test_helpers/test_types';
|
||||
import { ApplicationService } from './application_service';
|
||||
import {
|
||||
|
@ -56,6 +57,7 @@ describe('#setup()', () => {
|
|||
http,
|
||||
overlays: overlayServiceMock.createStartContract(),
|
||||
theme: themeServiceMock.createStartContract(),
|
||||
customBranding: customBrandingServiceMock.createStartContract(),
|
||||
};
|
||||
service = new ApplicationService();
|
||||
});
|
||||
|
@ -480,6 +482,7 @@ describe('#start()', () => {
|
|||
http,
|
||||
overlays: overlayServiceMock.createStartContract(),
|
||||
theme: themeServiceMock.createStartContract(),
|
||||
customBranding: customBrandingServiceMock.createStartContract(),
|
||||
};
|
||||
service = new ApplicationService();
|
||||
});
|
||||
|
@ -1189,6 +1192,7 @@ describe('#stop()', () => {
|
|||
http,
|
||||
overlays: overlayServiceMock.createStartContract(),
|
||||
theme: themeServiceMock.createStartContract(),
|
||||
customBranding: customBrandingServiceMock.createStartContract(),
|
||||
};
|
||||
service = new ApplicationService();
|
||||
});
|
||||
|
|
|
@ -29,6 +29,7 @@ import type {
|
|||
} from '@kbn/core-application-browser';
|
||||
import { CapabilitiesService } from '@kbn/core-capabilities-browser-internal';
|
||||
import { AppStatus, AppNavLinkStatus } from '@kbn/core-application-browser';
|
||||
import type { CustomBrandingStart } from '@kbn/core-custom-branding-browser';
|
||||
import { AppRouter } from './ui';
|
||||
import type { InternalApplicationSetup, InternalApplicationStart, Mounter } from './types';
|
||||
|
||||
|
@ -47,6 +48,7 @@ export interface StartDeps {
|
|||
http: HttpStart;
|
||||
theme: ThemeServiceStart;
|
||||
overlays: OverlayStart;
|
||||
customBranding: CustomBrandingStart;
|
||||
}
|
||||
|
||||
function filterAvailable<T>(m: Map<string, T>, capabilities: Capabilities) {
|
||||
|
@ -105,6 +107,7 @@ export class ApplicationService {
|
|||
private openInNewTab?: (url: string) => void;
|
||||
private redirectTo?: (url: string) => void;
|
||||
private overlayStart$ = new Subject<OverlayStart>();
|
||||
private hasCustomBranding$: Observable<boolean> | undefined;
|
||||
|
||||
public setup({
|
||||
http: { basePath },
|
||||
|
@ -204,13 +207,18 @@ export class ApplicationService {
|
|||
};
|
||||
}
|
||||
|
||||
public async start({ http, overlays, theme }: StartDeps): Promise<InternalApplicationStart> {
|
||||
public async start({
|
||||
http,
|
||||
overlays,
|
||||
theme,
|
||||
customBranding,
|
||||
}: StartDeps): Promise<InternalApplicationStart> {
|
||||
if (!this.redirectTo) {
|
||||
throw new Error('ApplicationService#setup() must be invoked before start.');
|
||||
}
|
||||
|
||||
this.overlayStart$.next(overlays);
|
||||
|
||||
this.hasCustomBranding$ = customBranding.hasCustomBranding$.pipe(takeUntil(this.stop$));
|
||||
const httpLoadingCount$ = new BehaviorSubject(0);
|
||||
http.addLoadingCountSource(httpLoadingCount$);
|
||||
|
||||
|
@ -345,6 +353,7 @@ export class ApplicationService {
|
|||
setAppLeaveHandler={this.setAppLeaveHandler}
|
||||
setAppActionMenu={this.setAppActionMenu}
|
||||
setIsMounting={(isMounting) => httpLoadingCount$.next(isMounting ? 1 : 0)}
|
||||
hasCustomBranding$={this.hasCustomBranding$}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -184,6 +184,30 @@ describe('AppContainer', () => {
|
|||
expect(setIsMounting).toHaveBeenLastCalledWith(false);
|
||||
});
|
||||
|
||||
it('should show plain spinner', async () => {
|
||||
const [waitPromise] = createResolver();
|
||||
const mounter = createMounter(waitPromise);
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<AppContainer
|
||||
appPath={`/app/${appId}`}
|
||||
appId={appId}
|
||||
appStatus={AppStatus.accessible}
|
||||
mounter={mounter}
|
||||
setAppLeaveHandler={setAppLeaveHandler}
|
||||
setAppActionMenu={setAppActionMenu}
|
||||
setIsMounting={setIsMounting}
|
||||
createScopedHistory={(appPath: string) =>
|
||||
// Create a history using the appPath as the current location
|
||||
new ScopedHistory(createMemoryHistory({ initialEntries: [appPath] }), appPath)
|
||||
}
|
||||
theme$={theme$}
|
||||
showPlainSpinner={true}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="appContainer-loadingSpinner"]').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call setIsMounting(false) if mounting throws', async () => {
|
||||
const [waitPromise, resolvePromise] = createResolver();
|
||||
const mounter = {
|
||||
|
|
|
@ -10,7 +10,7 @@ import './app_container.scss';
|
|||
|
||||
import { Observable } from 'rxjs';
|
||||
import React, { Fragment, FC, useLayoutEffect, useRef, useState, MutableRefObject } from 'react';
|
||||
import { EuiLoadingElastic } from '@elastic/eui';
|
||||
import { EuiLoadingElastic, EuiLoadingSpinner } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { CoreTheme } from '@kbn/core-theme-browser';
|
||||
|
@ -36,6 +36,7 @@ interface Props {
|
|||
setAppActionMenu: (appId: string, mount: MountPoint | undefined) => void;
|
||||
createScopedHistory: (appUrl: string) => ScopedHistory;
|
||||
setIsMounting: (isMounting: boolean) => void;
|
||||
showPlainSpinner?: boolean;
|
||||
}
|
||||
|
||||
export const AppContainer: FC<Props> = ({
|
||||
|
@ -48,6 +49,7 @@ export const AppContainer: FC<Props> = ({
|
|||
appStatus,
|
||||
setIsMounting,
|
||||
theme$,
|
||||
showPlainSpinner,
|
||||
}: Props) => {
|
||||
const [showSpinner, setShowSpinner] = useState(true);
|
||||
const [appNotFound, setAppNotFound] = useState(false);
|
||||
|
@ -114,13 +116,18 @@ export const AppContainer: FC<Props> = ({
|
|||
return (
|
||||
<Fragment>
|
||||
{appNotFound && <AppNotFound />}
|
||||
{showSpinner && !appNotFound && <AppLoadingPlaceholder />}
|
||||
{showSpinner && !appNotFound && (
|
||||
<AppLoadingPlaceholder showPlainSpinner={Boolean(showPlainSpinner)} />
|
||||
)}
|
||||
<div className={APP_WRAPPER_CLASS} key={appId} ref={elementRef} aria-busy={showSpinner} />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const AppLoadingPlaceholder: FC = () => {
|
||||
const AppLoadingPlaceholder: FC<{ showPlainSpinner: boolean }> = ({ showPlainSpinner }) => {
|
||||
if (showPlainSpinner) {
|
||||
return <EuiLoadingSpinner size={'xxl'} data-test-subj="appContainer-loadingSpinner" />;
|
||||
}
|
||||
return (
|
||||
<EuiLoadingElastic
|
||||
className="appContainer__loading"
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import React, { FunctionComponent, useMemo } from 'react';
|
||||
import { Route, RouteComponentProps, Router, Switch } from 'react-router-dom';
|
||||
import { History } from 'history';
|
||||
import { Observable } from 'rxjs';
|
||||
import { EMPTY, Observable } from 'rxjs';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
|
||||
import type { CoreTheme } from '@kbn/core-theme-browser';
|
||||
|
@ -27,6 +27,7 @@ interface Props {
|
|||
setAppLeaveHandler: (appId: string, handler: AppLeaveHandler) => void;
|
||||
setAppActionMenu: (appId: string, mount: MountPoint | undefined) => void;
|
||||
setIsMounting: (isMounting: boolean) => void;
|
||||
hasCustomBranding$?: Observable<boolean>;
|
||||
}
|
||||
|
||||
interface Params {
|
||||
|
@ -41,6 +42,7 @@ export const AppRouter: FunctionComponent<Props> = ({
|
|||
setAppActionMenu,
|
||||
appStatuses$,
|
||||
setIsMounting,
|
||||
hasCustomBranding$,
|
||||
}) => {
|
||||
const appStatuses = useObservable(appStatuses$, new Map());
|
||||
const createScopedHistory = useMemo(
|
||||
|
@ -48,6 +50,8 @@ export const AppRouter: FunctionComponent<Props> = ({
|
|||
[history]
|
||||
);
|
||||
|
||||
const showPlainSpinner = useObservable(hasCustomBranding$ ?? EMPTY, false);
|
||||
|
||||
return (
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
|
@ -61,7 +65,15 @@ export const AppRouter: FunctionComponent<Props> = ({
|
|||
appPath={path}
|
||||
appStatus={appStatuses.get(appId) ?? AppStatus.inaccessible}
|
||||
createScopedHistory={createScopedHistory}
|
||||
{...{ appId, mounter, setAppLeaveHandler, setAppActionMenu, setIsMounting, theme$ }}
|
||||
{...{
|
||||
appId,
|
||||
mounter,
|
||||
setAppLeaveHandler,
|
||||
setAppActionMenu,
|
||||
setIsMounting,
|
||||
theme$,
|
||||
showPlainSpinner,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
@ -83,7 +95,14 @@ export const AppRouter: FunctionComponent<Props> = ({
|
|||
appId={id ?? appId}
|
||||
appStatus={appStatuses.get(appId) ?? AppStatus.inaccessible}
|
||||
createScopedHistory={createScopedHistory}
|
||||
{...{ mounter, setAppLeaveHandler, setAppActionMenu, setIsMounting, theme$ }}
|
||||
{...{
|
||||
mounter,
|
||||
setAppLeaveHandler,
|
||||
setAppActionMenu,
|
||||
setIsMounting,
|
||||
theme$,
|
||||
showPlainSpinner,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
"@kbn/core-theme-browser-mocks",
|
||||
"@kbn/core-http-browser-internal",
|
||||
"@kbn/test-jest-helpers",
|
||||
"@kbn/core-custom-branding-browser",
|
||||
"@kbn/core-custom-branding-browser-mocks",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -17,6 +17,7 @@ import type { App, PublicAppInfo } from '@kbn/core-application-browser';
|
|||
import { applicationServiceMock } from '@kbn/core-application-browser-mocks';
|
||||
import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
|
||||
import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks';
|
||||
import { customBrandingServiceMock } from '@kbn/core-custom-branding-browser-mocks';
|
||||
import { getAppInfo } from '@kbn/core-application-browser-internal';
|
||||
import { ChromeService } from './chrome_service';
|
||||
|
||||
|
@ -46,6 +47,7 @@ function defaultStartDeps(availableApps?: App[]) {
|
|||
injectedMetadata: injectedMetadataServiceMock.createStartContract(),
|
||||
notifications: notificationServiceMock.createStartContract(),
|
||||
uiSettings: uiSettingsServiceMock.createStartContract(),
|
||||
customBranding: customBrandingServiceMock.createStartContract(),
|
||||
};
|
||||
|
||||
if (availableApps) {
|
||||
|
|
|
@ -27,6 +27,7 @@ import type {
|
|||
ChromeHelpExtension,
|
||||
ChromeUserBanner,
|
||||
} from '@kbn/core-chrome-browser';
|
||||
import type { CustomBrandingStart } from '@kbn/core-custom-branding-browser';
|
||||
import { KIBANA_ASK_ELASTIC_LINK } from './constants';
|
||||
import { DocTitleService } from './doc_title';
|
||||
import { NavControlsService } from './nav_controls';
|
||||
|
@ -49,6 +50,7 @@ export interface StartDeps {
|
|||
http: HttpStart;
|
||||
injectedMetadata: InternalInjectedMetadataStart;
|
||||
notifications: NotificationsStart;
|
||||
customBranding: CustomBrandingStart;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -101,6 +103,7 @@ export class ChromeService {
|
|||
http,
|
||||
injectedMetadata,
|
||||
notifications,
|
||||
customBranding,
|
||||
}: StartDeps): Promise<InternalChromeStart> {
|
||||
this.initVisibility(application);
|
||||
|
||||
|
@ -143,6 +146,7 @@ export class ChromeService {
|
|||
const navLinks = this.navLinks.start({ application, http });
|
||||
const recentlyAccessed = await this.recentlyAccessed.start({ http });
|
||||
const docTitle = this.docTitle.start({ document: window.document });
|
||||
const { customBranding$ } = customBranding;
|
||||
|
||||
// erase chrome fields from a previous app while switching to a next app
|
||||
application.currentAppId$.subscribe(() => {
|
||||
|
@ -231,6 +235,7 @@ export class ChromeService {
|
|||
navControlsExtension$={navControls.getExtension$()}
|
||||
onIsLockedUpdate={setIsNavDrawerLocked}
|
||||
isLocked$={getIsNavDrawerLocked$}
|
||||
customBranding$={customBranding$}
|
||||
/>
|
||||
),
|
||||
|
||||
|
|
|
@ -19,3 +19,12 @@ exports[`kbnLoadingIndicator is visible when loadingCount is > 0 1`] = `
|
|||
type="logoElastic"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`kbnLoadingIndicator shows EuiLoadingSpinner when showPlainSpinner is true 1`] = `
|
||||
<EuiLoadingSpinner
|
||||
aria-hidden={false}
|
||||
aria-label="Loading content"
|
||||
data-test-subj="globalLoadingIndicator"
|
||||
size="l"
|
||||
/>
|
||||
`;
|
||||
|
|
|
@ -28,6 +28,7 @@ function mockProps() {
|
|||
breadcrumbsAppendExtension$: new BehaviorSubject(undefined),
|
||||
homeHref: '/',
|
||||
isVisible$: new BehaviorSubject(true),
|
||||
customBranding$: new BehaviorSubject({}),
|
||||
kibanaDocLink: '/docs',
|
||||
navLinks$: new BehaviorSubject([]),
|
||||
customNavLink$: new BehaviorSubject(undefined),
|
||||
|
|
|
@ -35,6 +35,7 @@ import type {
|
|||
ChromeGlobalHelpExtensionMenuLink,
|
||||
ChromeUserBanner,
|
||||
} from '@kbn/core-chrome-browser';
|
||||
import { CustomBranding } from '@kbn/core-custom-branding-common';
|
||||
import { LoadingIndicator } from '../loading_indicator';
|
||||
import type { OnIsLockedUpdate } from './types';
|
||||
import { CollapsibleNav } from './collapsible_nav';
|
||||
|
@ -72,6 +73,7 @@ export interface HeaderProps {
|
|||
isLocked$: Observable<boolean>;
|
||||
loadingCount$: ReturnType<HttpStart['getLoadingCount$']>;
|
||||
onIsLockedUpdate: OnIsLockedUpdate;
|
||||
customBranding$: Observable<CustomBranding>;
|
||||
}
|
||||
|
||||
export function Header({
|
||||
|
@ -83,6 +85,7 @@ export function Header({
|
|||
homeHref,
|
||||
breadcrumbsAppendExtension$,
|
||||
globalHelpExtensionMenuLinks$,
|
||||
customBranding$,
|
||||
...observables
|
||||
}: HeaderProps) {
|
||||
const isVisible = useObservable(observables.isVisible$, false);
|
||||
|
@ -122,6 +125,7 @@ export function Header({
|
|||
navLinks$={observables.navLinks$}
|
||||
navigateToApp={application.navigateToApp}
|
||||
loadingCount$={observables.loadingCount$}
|
||||
customBranding$={customBranding$}
|
||||
/>,
|
||||
],
|
||||
borders: 'none',
|
||||
|
|
|
@ -12,6 +12,7 @@ import React from 'react';
|
|||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { Observable } from 'rxjs';
|
||||
import Url from 'url';
|
||||
import { CustomBranding } from '@kbn/core-custom-branding-common';
|
||||
import type { HttpStart } from '@kbn/core-http-browser';
|
||||
import type { ChromeNavLink } from '@kbn/core-chrome-browser';
|
||||
import { ElasticMark } from './elastic_mark';
|
||||
|
@ -83,12 +84,14 @@ interface Props {
|
|||
forceNavigation$: Observable<boolean>;
|
||||
navigateToApp: (appId: string) => void;
|
||||
loadingCount$?: ReturnType<HttpStart['getLoadingCount$']>;
|
||||
customBranding$: Observable<CustomBranding>;
|
||||
}
|
||||
|
||||
export function HeaderLogo({ href, navigateToApp, loadingCount$, ...observables }: Props) {
|
||||
const forceNavigation = useObservable(observables.forceNavigation$, false);
|
||||
const navLinks = useObservable(observables.navLinks$, []);
|
||||
|
||||
const customBranding = useObservable(observables.customBranding$, {});
|
||||
const { customizedLogo, logo } = customBranding;
|
||||
return (
|
||||
<a
|
||||
onClick={(e) => onClick(e, forceNavigation, navLinks, navigateToApp)}
|
||||
|
@ -99,8 +102,15 @@ export function HeaderLogo({ href, navigateToApp, loadingCount$, ...observables
|
|||
defaultMessage: 'Elastic home',
|
||||
})}
|
||||
>
|
||||
<LoadingIndicator loadingCount$={loadingCount$!} />
|
||||
<ElasticMark className="chrHeaderLogo__mark" aria-hidden={true} />
|
||||
<LoadingIndicator
|
||||
loadingCount$={loadingCount$!}
|
||||
showPlainSpinner={Boolean(logo || customizedLogo)}
|
||||
/>
|
||||
{customizedLogo ? (
|
||||
<img src={customizedLogo} width="200" height="84" alt="custom mark" />
|
||||
) : (
|
||||
<ElasticMark className="chrHeaderLogo__mark" aria-hidden={true} />
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -27,4 +27,15 @@ describe('kbnLoadingIndicator', () => {
|
|||
}, 300);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('shows EuiLoadingSpinner when showPlainSpinner is true', () => {
|
||||
const wrapper = shallow(
|
||||
<LoadingIndicator loadingCount$={new BehaviorSubject(1)} showPlainSpinner={true} />
|
||||
);
|
||||
// Pause the check beyond the 250ms delay that it has
|
||||
setTimeout(() => {
|
||||
expect(wrapper.prop('data-test-subj')).toBe('globalLoadingIndicator');
|
||||
}, 300);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@ import './loading_indicator.scss';
|
|||
export interface LoadingIndicatorProps {
|
||||
loadingCount$: ReturnType<HttpStart['getLoadingCount$']>;
|
||||
showAsBar?: boolean;
|
||||
showPlainSpinner?: boolean;
|
||||
}
|
||||
|
||||
export class LoadingIndicator extends React.Component<LoadingIndicatorProps, { visible: boolean }> {
|
||||
|
@ -57,34 +58,36 @@ export class LoadingIndicator extends React.Component<LoadingIndicatorProps, { v
|
|||
render() {
|
||||
const className = classNames(!this.state.visible && 'kbnLoadingIndicator-hidden');
|
||||
|
||||
const testSubj = this.state.visible
|
||||
? 'globalLoadingIndicator'
|
||||
: 'globalLoadingIndicator-hidden';
|
||||
const testSubj =
|
||||
this.state.visible || this.props.showPlainSpinner
|
||||
? 'globalLoadingIndicator'
|
||||
: 'globalLoadingIndicator-hidden';
|
||||
|
||||
const ariaHidden = this.state.visible ? false : true;
|
||||
const ariaHidden = !this.state.visible;
|
||||
|
||||
const ariaLabel = i18n.translate('core.ui.loadingIndicatorAriaLabel', {
|
||||
defaultMessage: 'Loading content',
|
||||
});
|
||||
|
||||
const logo = this.state.visible ? (
|
||||
<EuiLoadingSpinner
|
||||
size="l"
|
||||
data-test-subj={testSubj}
|
||||
aria-hidden={false}
|
||||
aria-label={ariaLabel}
|
||||
/>
|
||||
) : (
|
||||
<EuiIcon
|
||||
type="logoElastic"
|
||||
size="l"
|
||||
data-test-subj={testSubj}
|
||||
className="chrHeaderLogo__cluster"
|
||||
aria-label={i18n.translate('core.ui.chrome.headerGlobalNav.logoAriaLabel', {
|
||||
defaultMessage: 'Elastic Logo',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
const logo =
|
||||
this.state.visible || this.props.showPlainSpinner ? (
|
||||
<EuiLoadingSpinner
|
||||
size="l"
|
||||
data-test-subj={testSubj}
|
||||
aria-hidden={false}
|
||||
aria-label={ariaLabel}
|
||||
/>
|
||||
) : (
|
||||
<EuiIcon
|
||||
type={'logoElastic'}
|
||||
size="l"
|
||||
data-test-subj={testSubj}
|
||||
className="chrHeaderLogo__cluster"
|
||||
aria-label={i18n.translate('core.ui.chrome.headerGlobalNav.logoAriaLabel', {
|
||||
defaultMessage: 'Elastic Logo',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
return !this.props.showAsBar ? (
|
||||
logo
|
||||
|
|
|
@ -33,6 +33,9 @@
|
|||
"@kbn/test-jest-helpers",
|
||||
"@kbn/core-application-common",
|
||||
"@kbn/core-mount-utils-browser",
|
||||
"@kbn/core-custom-branding-browser-mocks",
|
||||
"@kbn/core-custom-branding-browser",
|
||||
"@kbn/core-custom-branding-common",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# @kbn/core-custom-branding-browser-internal
|
||||
|
||||
This package contains the implementation and internal types of the browser-side `customBranding` service.
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { CustomBrandingService } from './src/custom_branding_service';
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../../../',
|
||||
roots: ['<rootDir>/packages/core/custom-branding/core-custom-branding-browser-internal'],
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/core-custom-branding-browser-internal",
|
||||
"owner": "@elastic/kibana-global-experience",
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/core-custom-branding-browser-internal",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { take } from 'rxjs';
|
||||
import { CustomBrandingSetupDeps } from '@kbn/core-custom-branding-browser';
|
||||
import { CustomBrandingService } from './custom_branding_service';
|
||||
|
||||
describe('custom branding service', () => {
|
||||
const injectedMetadata = {
|
||||
getCustomBranding: () => {
|
||||
return { customizedLogo: 'customizedLogo' };
|
||||
},
|
||||
};
|
||||
|
||||
describe('#start', () => {
|
||||
let service: CustomBrandingService;
|
||||
|
||||
beforeEach(() => {
|
||||
service = new CustomBrandingService();
|
||||
service.setup({ injectedMetadata } as CustomBrandingSetupDeps);
|
||||
});
|
||||
|
||||
it('hasCustomBranding$ returns the correct value', async () => {
|
||||
const { hasCustomBranding$ } = service.start();
|
||||
const hasCustomBranding = await hasCustomBranding$.pipe(take(1)).toPromise();
|
||||
expect(hasCustomBranding).toEqual(true);
|
||||
});
|
||||
|
||||
it('customBranding$ returns the correct value', async () => {
|
||||
const { customBranding$ } = service.start();
|
||||
const customBranding = await customBranding$.pipe(take(1)).toPromise();
|
||||
expect(customBranding).toEqual({ customizedLogo: 'customizedLogo' });
|
||||
});
|
||||
|
||||
it('throws if called before setup', async () => {
|
||||
const customBrandingService = new CustomBrandingService();
|
||||
expect(() => customBrandingService.start()).toThrow('Setup needs to be called before start');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setup', () => {
|
||||
it('customBranding$ returns the correct value', async () => {
|
||||
const service = new CustomBrandingService();
|
||||
const { customBranding$, hasCustomBranding$ } = service.setup({
|
||||
injectedMetadata,
|
||||
} as CustomBrandingSetupDeps);
|
||||
const customBranding = await customBranding$.pipe(take(1)).toPromise();
|
||||
expect(customBranding).toEqual({ customizedLogo: 'customizedLogo' });
|
||||
const hasCustomBranding = await hasCustomBranding$.pipe(take(1)).toPromise();
|
||||
expect(hasCustomBranding).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#stop', () => {
|
||||
it('runs fine if service never set up', () => {
|
||||
const service = new CustomBrandingService();
|
||||
expect(() => service.stop()).not.toThrowError();
|
||||
});
|
||||
|
||||
it('stops customBranding$ and hasCustomBranding$', async () => {
|
||||
const service = new CustomBrandingService();
|
||||
service.setup({ injectedMetadata } as CustomBrandingSetupDeps);
|
||||
const { hasCustomBranding$, customBranding$ } = service.start();
|
||||
|
||||
let hasCustomBrandingCompleted = false;
|
||||
let customBrandingCompleted = false;
|
||||
|
||||
hasCustomBranding$.subscribe({
|
||||
complete: () => {
|
||||
hasCustomBrandingCompleted = true;
|
||||
},
|
||||
});
|
||||
|
||||
customBranding$.subscribe({
|
||||
complete: () => {
|
||||
customBrandingCompleted = true;
|
||||
},
|
||||
});
|
||||
|
||||
service.stop();
|
||||
|
||||
expect(customBrandingCompleted).toEqual(true);
|
||||
expect(hasCustomBrandingCompleted).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Subject, BehaviorSubject } from 'rxjs';
|
||||
import { shareReplay, takeUntil, map } from 'rxjs/operators';
|
||||
import type {
|
||||
CustomBrandingStart,
|
||||
CustomBrandingSetup,
|
||||
CustomBrandingSetupDeps,
|
||||
} from '@kbn/core-custom-branding-browser';
|
||||
import type { CustomBranding } from '@kbn/core-custom-branding-common';
|
||||
|
||||
export class CustomBrandingService {
|
||||
private customBranding$: BehaviorSubject<CustomBranding> | undefined;
|
||||
private stop$ = new Subject<void>();
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
public setup({ injectedMetadata }: CustomBrandingSetupDeps): CustomBrandingSetup {
|
||||
const customBranding = injectedMetadata.getCustomBranding();
|
||||
this.customBranding$ = new BehaviorSubject<CustomBranding>(customBranding);
|
||||
return {
|
||||
customBranding$: this.customBranding$.pipe(takeUntil(this.stop$), shareReplay(1)),
|
||||
hasCustomBranding$: this.customBranding$.pipe(
|
||||
takeUntil(this.stop$),
|
||||
map((cb) => Object.keys(cb).length > 0),
|
||||
shareReplay(1)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
public start(): CustomBrandingStart {
|
||||
if (!this.customBranding$) {
|
||||
throw new Error('Setup needs to be called before start');
|
||||
}
|
||||
return {
|
||||
customBranding$: this.customBranding$.pipe(takeUntil(this.stop$), shareReplay(1)),
|
||||
hasCustomBranding$: this.customBranding$.pipe(
|
||||
takeUntil(this.stop$),
|
||||
map((cb) => Object.keys(cb).length > 0),
|
||||
shareReplay(1)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
public stop() {
|
||||
this.stop$.next();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
]
|
||||
},
|
||||
"kbn_references": [
|
||||
"@kbn/core-custom-branding-browser",
|
||||
"@kbn/core-custom-branding-common"
|
||||
],
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
]
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
# @kbn/core-custom-branding-browser-mocks
|
||||
|
||||
Contains the mocks for Core's internal `customBranding` browser-side service.
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { of } from 'rxjs';
|
||||
import type { CustomBranding } from '@kbn/core-custom-branding-common';
|
||||
import { serviceContractMock } from './service_contract.mock';
|
||||
|
||||
const mockCustomBranding: CustomBranding = {
|
||||
logo: 'img.jpg',
|
||||
};
|
||||
|
||||
const createCustomBrandingMock = (): CustomBranding => {
|
||||
return { ...mockCustomBranding };
|
||||
};
|
||||
|
||||
const createSetupContractMock = () => {
|
||||
return {
|
||||
customBranding$: of(createCustomBrandingMock()),
|
||||
hasCustomBranding$: of(false),
|
||||
};
|
||||
};
|
||||
|
||||
const createStartContractMock = () => {
|
||||
return {
|
||||
customBranding$: of(createCustomBrandingMock()),
|
||||
hasCustomBranding$: of(false),
|
||||
};
|
||||
};
|
||||
|
||||
const createMock = () => {
|
||||
const mocked = serviceContractMock();
|
||||
mocked.setup.mockReturnValue(createSetupContractMock());
|
||||
mocked.start.mockReturnValue(createStartContractMock());
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const customBrandingServiceMock = {
|
||||
create: createMock,
|
||||
createSetupContract: createSetupContractMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { customBrandingServiceMock } from './custom_branding_service.mock';
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../../..',
|
||||
roots: ['<rootDir>/packages/core/custom-branding/core-custom-branding-browser-mocks'],
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/core-custom-branding-browser-mocks",
|
||||
"owner": "@elastic/kibana-global-experience",
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/core-custom-branding-browser-mocks",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export const serviceContractMock = (): jest.Mocked<any> => {
|
||||
return {
|
||||
setup: jest.fn(),
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
};
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
]
|
||||
},
|
||||
"kbn_references": [
|
||||
"@kbn/core-custom-branding-common",
|
||||
],
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# @kbn/core-custom-branding-browser
|
||||
|
||||
Contains the public types of Core's browser-side `customBranding` service.
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export type { CustomBrandingStart, CustomBrandingSetup, CustomBrandingSetupDeps } from './types';
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/core-custom-branding-browser",
|
||||
"owner": "@elastic/kibana-global-experience",
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/core-custom-branding-browser",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
]
|
||||
},
|
||||
"kbn_references": [
|
||||
"@kbn/core-injected-metadata-browser-internal",
|
||||
"@kbn/core-custom-branding-common"
|
||||
],
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
]
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import type { CustomBranding } from '@kbn/core-custom-branding-common';
|
||||
import type { InternalInjectedMetadataSetup } from '@kbn/core-injected-metadata-browser-internal';
|
||||
|
||||
export interface CustomBrandingStart {
|
||||
customBranding$: Observable<CustomBranding>;
|
||||
hasCustomBranding$: Observable<boolean>;
|
||||
}
|
||||
|
||||
export interface CustomBrandingSetup {
|
||||
customBranding$: Observable<CustomBranding>;
|
||||
hasCustomBranding$: Observable<boolean>;
|
||||
}
|
||||
|
||||
export interface CustomBrandingSetupDeps {
|
||||
injectedMetadata: InternalInjectedMetadataSetup;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# @kbn/core-custom-branding-common
|
||||
|
||||
Contains public types to be used on both the server and the browser for Core\'s `customBranding` service.
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export interface CustomBranding {
|
||||
/**
|
||||
* Custom replacement for the Elastic logo in the top lef *
|
||||
* */
|
||||
logo?: string;
|
||||
/**
|
||||
* Custom replacement for favicon in SVG format
|
||||
*/
|
||||
faviconSVG?: string;
|
||||
/**
|
||||
* Custom page title
|
||||
*/
|
||||
pageTitle?: string;
|
||||
/**
|
||||
* Custom replacement for Elastic Mark
|
||||
* @link packages/core/chrome/core-chrome-browser-internal/src/ui/header/elastic_mark.tsx
|
||||
*/
|
||||
customizedLogo?: string;
|
||||
/**
|
||||
* Custom replacement for favicon in PNG format
|
||||
*/
|
||||
faviconPNG?: string;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/core-custom-branding-common",
|
||||
"owner": "@elastic/kibana-global-experience",
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/core-custom-branding-common",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"kbn_references": [
|
||||
],
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# @kbn/core-custom-branding-server-internal
|
||||
|
||||
Contains the implementation and internal types of the server-side `customBranding` service.
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { mockCoreContext } from '@kbn/core-base-server-mocks';
|
||||
import type { KibanaRequest } from '@kbn/core-http-server';
|
||||
import { CustomBrandingService } from './custom_branding_service';
|
||||
|
||||
describe('#setup', () => {
|
||||
const coreContext: ReturnType<typeof mockCoreContext.create> = mockCoreContext.create();
|
||||
|
||||
it('registers plugin correctly', () => {
|
||||
const service = new CustomBrandingService(coreContext);
|
||||
const { register } = service.setup();
|
||||
const fetchFn = jest.fn();
|
||||
register('pluginName', fetchFn);
|
||||
expect(() => {
|
||||
register('anotherPlugin', fetchFn);
|
||||
}).toThrow('Another plugin already registered');
|
||||
});
|
||||
|
||||
it('throws if `getBrandingFor` called before #start', async () => {
|
||||
const service = new CustomBrandingService(coreContext);
|
||||
const { register, getBrandingFor } = service.setup();
|
||||
const fetchFn = jest.fn();
|
||||
register('customBranding', fetchFn);
|
||||
const kibanaRequest: jest.Mocked<KibanaRequest> = {} as unknown as jest.Mocked<KibanaRequest>;
|
||||
try {
|
||||
await getBrandingFor(kibanaRequest);
|
||||
} catch (e) {
|
||||
expect(e.message).toMatch('Cannot be called before #start');
|
||||
}
|
||||
});
|
||||
|
||||
it('throws if `fetchFn` not provided with register', async () => {
|
||||
const service = new CustomBrandingService(coreContext);
|
||||
const { register } = service.setup();
|
||||
try {
|
||||
// @ts-expect-error
|
||||
register('customBranding');
|
||||
} catch (e) {
|
||||
expect(e.message).toMatch(
|
||||
'Both plugin name and fetch function need to be provided when registering a plugin'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('calls fetchFn correctly', async () => {
|
||||
const service = new CustomBrandingService(coreContext);
|
||||
const { register, getBrandingFor } = service.setup();
|
||||
service.start();
|
||||
const fetchFn = jest.fn();
|
||||
fetchFn.mockImplementation(() => Promise.resolve({ logo: 'myLogo' }));
|
||||
register('customBranding', fetchFn);
|
||||
const kibanaRequest: jest.Mocked<KibanaRequest> = {} as unknown as jest.Mocked<KibanaRequest>;
|
||||
const customBranding = await getBrandingFor(kibanaRequest);
|
||||
expect(fetchFn).toHaveBeenCalledTimes(1);
|
||||
expect(customBranding).toEqual({ logo: 'myLogo' });
|
||||
});
|
||||
});
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { CustomBrandingFetchFn, CustomBrandingStart } from '@kbn/core-custom-branding-server';
|
||||
import type { CustomBranding } from '@kbn/core-custom-branding-common';
|
||||
import type { KibanaRequest } from '@kbn/core-http-server';
|
||||
import type { CoreContext } from '@kbn/core-base-server-internal';
|
||||
import type { Logger } from '@kbn/logging';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface InternalCustomBrandingSetup {
|
||||
register: (pluginName: string, fetchFn: CustomBrandingFetchFn) => void;
|
||||
getBrandingFor: (request: KibanaRequest) => Promise<CustomBranding>;
|
||||
}
|
||||
|
||||
export class CustomBrandingService {
|
||||
private pluginName?: string;
|
||||
private logger: Logger;
|
||||
private fetchFn?: CustomBrandingFetchFn;
|
||||
private startCalled: boolean = false;
|
||||
|
||||
constructor(coreContext: CoreContext) {
|
||||
this.logger = coreContext.logger.get('custom-branding-service');
|
||||
}
|
||||
|
||||
public setup(): InternalCustomBrandingSetup {
|
||||
return {
|
||||
register: (pluginName, fetchFn) => {
|
||||
this.logger.info('CustomBrandingService registering plugin: ' + pluginName);
|
||||
if (this.pluginName) {
|
||||
throw new Error('Another plugin already registered');
|
||||
}
|
||||
if (!pluginName || !fetchFn) {
|
||||
throw new Error(
|
||||
'Both plugin name and fetch function need to be provided when registering a plugin'
|
||||
);
|
||||
}
|
||||
this.pluginName = pluginName;
|
||||
this.fetchFn = fetchFn;
|
||||
},
|
||||
getBrandingFor: this.getBrandingFor,
|
||||
};
|
||||
}
|
||||
|
||||
public start(): CustomBrandingStart {
|
||||
this.startCalled = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
|
||||
private getBrandingFor = async (request: KibanaRequest): Promise<CustomBranding> => {
|
||||
if (!this.startCalled) {
|
||||
throw new Error('Cannot be called before #start');
|
||||
}
|
||||
if (!this.pluginName || this.pluginName !== 'customBranding' || !this.fetchFn) {
|
||||
return {};
|
||||
}
|
||||
return this.fetchFn!(request);
|
||||
};
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { CustomBrandingService } from './custom_branding_service';
|
||||
export type { InternalCustomBrandingSetup } from './custom_branding_service';
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_node',
|
||||
rootDir: '../../../../',
|
||||
roots: ['<rootDir>/packages/core/custom-branding/core-custom-branding-server-internal'],
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/core-custom-branding-server-internal",
|
||||
"owner": "@elastic/kibana-global-experience",
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/core-custom-branding-server-internal",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"kbn_references": [
|
||||
"@kbn/core-custom-branding-common",
|
||||
"@kbn/core-custom-branding-server",
|
||||
"@kbn/core-base-server-mocks",
|
||||
"@kbn/core-http-server",
|
||||
"@kbn/core-base-server-internal",
|
||||
"@kbn/logging",
|
||||
],
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# @kbn/core-custom-branding-server-mocks
|
||||
|
||||
Contains the mocks for Core's internal `customBranding` server-side service.
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { customBrandingServiceMock } from './src/custom_branding_service.mock';
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_node',
|
||||
rootDir: '../../../..',
|
||||
roots: ['<rootDir>/packages/core/custom-branding/core-custom-branding-server-mocks'],
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/core-custom-branding-server-mocks",
|
||||
"owner": "@elastic/kibana-global-experience",
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/core-custom-branding-server-mocks",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { serviceContractMock } from './service_contract.mock';
|
||||
|
||||
const createSetupContractMock = () => {
|
||||
return {
|
||||
register: jest.fn(),
|
||||
getBrandingFor: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
const createStartContractMock = () => {
|
||||
return {};
|
||||
};
|
||||
|
||||
const createMock = () => {
|
||||
const mocked = serviceContractMock();
|
||||
mocked.setup.mockReturnValue(createSetupContractMock());
|
||||
mocked.start.mockReturnValue(createStartContractMock());
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const customBrandingServiceMock = {
|
||||
create: createMock,
|
||||
createSetupContract: createSetupContractMock,
|
||||
createStartContract: createStartContractMock,
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { CustomBrandingService } from '@kbn/core-custom-branding-server-internal';
|
||||
|
||||
export const serviceContractMock = (): jest.Mocked<CustomBrandingService> => {
|
||||
return {
|
||||
setup: jest.fn(),
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
} as unknown as jest.Mocked<CustomBrandingService>;
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"kbn_references": [
|
||||
"@kbn/core-custom-branding-server-internal",
|
||||
],
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# @kbn/core-custom-branding-server
|
||||
|
||||
Contains the public types of Core's server-side `customBranding` service.
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export type { CustomBrandingStart, CustomBrandingSetup, CustomBrandingFetchFn } from './types';
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/core-custom-branding-server",
|
||||
"owner": "@elastic/kibana-global-experience",
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/core-custom-branding-server",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"kbn_references": [
|
||||
"@kbn/core-custom-branding-common",
|
||||
"@kbn/core-http-server",
|
||||
"@kbn/utility-types",
|
||||
],
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
]
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { KibanaRequest } from '@kbn/core-http-server';
|
||||
import type { CustomBranding } from '@kbn/core-custom-branding-common';
|
||||
import type { MaybePromise } from '@kbn/utility-types';
|
||||
|
||||
/** @public */
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface CustomBrandingStart {}
|
||||
|
||||
export type CustomBrandingFetchFn = (request: KibanaRequest) => MaybePromise<CustomBranding>;
|
||||
|
||||
/** @public */
|
||||
export interface CustomBrandingSetup {
|
||||
register: (fetchFn: CustomBrandingFetchFn) => void;
|
||||
}
|
|
@ -95,6 +95,10 @@ export class InjectedMetadataService {
|
|||
getElasticsearchInfo: () => {
|
||||
return this.state.clusterInfo;
|
||||
},
|
||||
|
||||
getCustomBranding: () => {
|
||||
return this.state.customBranding;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
InjectedMetadataExternalUrlPolicy,
|
||||
InjectedMetadataPlugin,
|
||||
} from '@kbn/core-injected-metadata-common-internal';
|
||||
import type { CustomBranding } from '@kbn/core-custom-branding-common';
|
||||
|
||||
/** @internal */
|
||||
export interface InjectedMetadataParams {
|
||||
|
@ -61,6 +62,7 @@ export interface InternalInjectedMetadataSetup {
|
|||
getInjectedVars: () => {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
getCustomBranding: () => CustomBranding;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
|
|
@ -15,9 +15,10 @@
|
|||
"@kbn/std",
|
||||
"@kbn/ui-shared-deps-npm",
|
||||
"@kbn/core-base-common",
|
||||
"@kbn/core-injected-metadata-common-internal"
|
||||
"@kbn/core-injected-metadata-common-internal",
|
||||
"@kbn/core-custom-branding-common",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
"target/**/*"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ const createSetupContractMock = () => {
|
|||
getInjectedVar: jest.fn(),
|
||||
getInjectedVars: jest.fn(),
|
||||
getKibanaBuildNumber: jest.fn(),
|
||||
getCustomBranding: jest.fn(),
|
||||
};
|
||||
setupContract.getCspConfig.mockReturnValue({ warnLegacyBrowsers: true });
|
||||
setupContract.getExternalUrlConfig.mockReturnValue({ policy: [] });
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import type { PluginName, DiscoveredPlugin } from '@kbn/core-base-common';
|
||||
import type { ThemeVersion } from '@kbn/ui-shared-deps-npm';
|
||||
import type { EnvironmentMode, PackageInfo } from '@kbn/config';
|
||||
import type { CustomBranding } from '@kbn/core-custom-branding-common';
|
||||
|
||||
/** @internal */
|
||||
export interface InjectedMetadataClusterInfo {
|
||||
|
@ -70,4 +71,5 @@ export interface InjectedMetadata {
|
|||
user: Record<string, any>; // unreferencing UserProvidedValues here
|
||||
};
|
||||
};
|
||||
customBranding: Pick<CustomBranding, 'logo' | 'customizedLogo'>;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
"kbn_references": [
|
||||
"@kbn/config",
|
||||
"@kbn/ui-shared-deps-npm",
|
||||
"@kbn/core-base-common"
|
||||
"@kbn/core-base-common",
|
||||
"@kbn/core-custom-branding-common"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -16,6 +16,7 @@ import { uiSettingsServiceMock, settingsServiceMock } from '@kbn/core-ui-setting
|
|||
import { deprecationsServiceMock } from '@kbn/core-deprecations-browser-mocks';
|
||||
import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
|
||||
import { applicationServiceMock } from '@kbn/core-application-browser-mocks';
|
||||
import { customBrandingServiceMock } from '@kbn/core-custom-branding-browser-mocks';
|
||||
import { createCoreStartMock } from './core_start.mock';
|
||||
|
||||
export function createCoreSetupMock({
|
||||
|
@ -30,6 +31,7 @@ export function createCoreSetupMock({
|
|||
const mock = {
|
||||
analytics: analyticsServiceMock.createAnalyticsServiceSetup(),
|
||||
application: applicationServiceMock.createSetupContract(),
|
||||
customBranding: customBrandingServiceMock.createSetupContract(),
|
||||
docLinks: docLinksServiceMock.createSetupContract(),
|
||||
executionContext: executionContextServiceMock.createSetupContract(),
|
||||
fatalErrors: fatalErrorsServiceMock.createSetupContract(),
|
||||
|
|
|
@ -20,12 +20,14 @@ import { savedObjectsServiceMock } from '@kbn/core-saved-objects-browser-mocks';
|
|||
import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
|
||||
import { applicationServiceMock } from '@kbn/core-application-browser-mocks';
|
||||
import { chromeServiceMock } from '@kbn/core-chrome-browser-mocks';
|
||||
import { customBrandingServiceMock } from '@kbn/core-custom-branding-browser-mocks';
|
||||
|
||||
export function createCoreStartMock({ basePath = '' } = {}) {
|
||||
const mock = {
|
||||
analytics: analyticsServiceMock.createAnalyticsServiceStart(),
|
||||
application: applicationServiceMock.createStartContract(),
|
||||
chrome: chromeServiceMock.createStartContract(),
|
||||
customBranding: customBrandingServiceMock.createStartContract(),
|
||||
docLinks: docLinksServiceMock.createStartContract(),
|
||||
executionContext: executionContextServiceMock.createStartContract(),
|
||||
http: httpServiceMock.createStartContract({ basePath }),
|
||||
|
|
|
@ -25,7 +25,8 @@
|
|||
"@kbn/core-saved-objects-browser-mocks",
|
||||
"@kbn/core-notifications-browser-mocks",
|
||||
"@kbn/core-application-browser-mocks",
|
||||
"@kbn/core-chrome-browser-mocks"
|
||||
"@kbn/core-chrome-browser-mocks",
|
||||
"@kbn/core-custom-branding-browser-mocks"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -14,6 +14,7 @@ import type { FatalErrorsSetup } from '@kbn/core-fatal-errors-browser';
|
|||
import type { IUiSettingsClient, SettingsStart } from '@kbn/core-ui-settings-browser';
|
||||
import type { NotificationsSetup } from '@kbn/core-notifications-browser';
|
||||
import type { ApplicationSetup } from '@kbn/core-application-browser';
|
||||
import type { CustomBrandingSetup } from '@kbn/core-custom-branding-browser';
|
||||
import type { CoreStart } from './core_start';
|
||||
|
||||
/**
|
||||
|
@ -35,6 +36,8 @@ export interface CoreSetup<TPluginsStart extends object = object, TStart = unkno
|
|||
analytics: AnalyticsServiceSetup;
|
||||
/** {@link ApplicationSetup} */
|
||||
application: ApplicationSetup;
|
||||
/** {@link CustomBrandingSetup} */
|
||||
customBranding: CustomBrandingSetup;
|
||||
/** {@link FatalErrorsSetup} */
|
||||
fatalErrors: FatalErrorsSetup;
|
||||
/** {@link HttpSetup} */
|
||||
|
|
|
@ -20,6 +20,7 @@ import type { SavedObjectsStart } from '@kbn/core-saved-objects-browser';
|
|||
import type { NotificationsStart } from '@kbn/core-notifications-browser';
|
||||
import type { ApplicationStart } from '@kbn/core-application-browser';
|
||||
import type { ChromeStart } from '@kbn/core-chrome-browser';
|
||||
import type { CustomBrandingStart } from '@kbn/core-custom-branding-browser';
|
||||
|
||||
/**
|
||||
* Core services exposed to the `Plugin` start lifecycle
|
||||
|
@ -37,6 +38,8 @@ export interface CoreStart {
|
|||
application: ApplicationStart;
|
||||
/** {@link ChromeStart} */
|
||||
chrome: ChromeStart;
|
||||
/** {@link CustomBrandingStart} */
|
||||
customBranding: CustomBrandingStart;
|
||||
/** {@link DocLinksStart} */
|
||||
docLinks: DocLinksStart;
|
||||
/** {@link ExecutionContextStart} */
|
||||
|
|
|
@ -25,7 +25,8 @@
|
|||
"@kbn/core-deprecations-browser",
|
||||
"@kbn/core-overlays-browser",
|
||||
"@kbn/core-saved-objects-browser",
|
||||
"@kbn/core-chrome-browser"
|
||||
"@kbn/core-chrome-browser",
|
||||
"@kbn/core-custom-branding-browser"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -24,6 +24,7 @@ import type { InternalSavedObjectsServiceSetup } from '@kbn/core-saved-objects-s
|
|||
import type { InternalStatusServiceSetup } from '@kbn/core-status-server-internal';
|
||||
import type { InternalUiSettingsServiceSetup } from '@kbn/core-ui-settings-server-internal';
|
||||
import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal';
|
||||
import type { InternalCustomBrandingSetup } from '@kbn/core-custom-branding-server-internal';
|
||||
|
||||
/** @internal */
|
||||
export interface InternalCoreSetup {
|
||||
|
@ -45,4 +46,5 @@ export interface InternalCoreSetup {
|
|||
metrics: InternalMetricsServiceSetup;
|
||||
deprecations: InternalDeprecationsServiceSetup;
|
||||
coreUsageData: InternalCoreUsageDataSetup;
|
||||
customBranding: InternalCustomBrandingSetup;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import type { InternalMetricsServiceStart } from '@kbn/core-metrics-server-inter
|
|||
import type { InternalSavedObjectsServiceStart } from '@kbn/core-saved-objects-server-internal';
|
||||
import type { InternalUiSettingsServiceStart } from '@kbn/core-ui-settings-server-internal';
|
||||
import type { CoreUsageDataStart } from '@kbn/core-usage-data-server';
|
||||
import type { CustomBrandingStart } from '@kbn/core-custom-branding-server';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
@ -33,4 +34,5 @@ export interface InternalCoreStart {
|
|||
coreUsageData: CoreUsageDataStart;
|
||||
executionContext: InternalExecutionContextStart;
|
||||
deprecations: InternalDeprecationsServiceStart;
|
||||
customBranding: CustomBrandingStart;
|
||||
}
|
||||
|
|
|
@ -30,7 +30,9 @@
|
|||
"@kbn/core-saved-objects-server-internal",
|
||||
"@kbn/core-status-server-internal",
|
||||
"@kbn/core-usage-data-base-server-internal",
|
||||
"@kbn/core-usage-data-server"
|
||||
"@kbn/core-usage-data-server",
|
||||
"@kbn/core-custom-branding-server-internal",
|
||||
"@kbn/core-custom-branding-server"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -24,6 +24,7 @@ import { metricsServiceMock } from '@kbn/core-metrics-server-mocks';
|
|||
import { deprecationsServiceMock } from '@kbn/core-deprecations-server-mocks';
|
||||
import { executionContextServiceMock } from '@kbn/core-execution-context-server-mocks';
|
||||
import { coreUsageDataServiceMock } from '@kbn/core-usage-data-server-mocks';
|
||||
import { customBrandingServiceMock } from '@kbn/core-custom-branding-server-mocks';
|
||||
import { createCoreStartMock } from './core_start.mock';
|
||||
|
||||
type CoreSetupMockType = MockedKeys<CoreSetup> & {
|
||||
|
@ -51,6 +52,7 @@ export function createCoreSetupMock({
|
|||
const mock: CoreSetupMockType = {
|
||||
analytics: analyticsServiceMock.createAnalyticsServiceSetup(),
|
||||
capabilities: capabilitiesServiceMock.createSetupContract(),
|
||||
customBranding: customBrandingServiceMock.createSetupContract(),
|
||||
docLinks: docLinksServiceMock.createSetupContract(),
|
||||
elasticsearch: elasticsearchServiceMock.createSetup(),
|
||||
http: httpMock,
|
||||
|
|
|
@ -18,6 +18,7 @@ import { savedObjectsServiceMock } from '@kbn/core-saved-objects-server-mocks';
|
|||
import { uiSettingsServiceMock } from '@kbn/core-ui-settings-server-mocks';
|
||||
import { coreUsageDataServiceMock } from '@kbn/core-usage-data-server-mocks';
|
||||
import type { MockedKeys } from '@kbn/utility-types-jest';
|
||||
import { customBrandingServiceMock } from '@kbn/core-custom-branding-server-mocks';
|
||||
|
||||
export function createCoreStartMock() {
|
||||
const mock: MockedKeys<CoreStart> = {
|
||||
|
@ -31,6 +32,7 @@ export function createCoreStartMock() {
|
|||
uiSettings: uiSettingsServiceMock.createStartContract(),
|
||||
coreUsageData: coreUsageDataServiceMock.createStartContract(),
|
||||
executionContext: executionContextServiceMock.createInternalStartContract(),
|
||||
customBranding: customBrandingServiceMock.createStartContract(),
|
||||
};
|
||||
|
||||
return mock;
|
||||
|
|
|
@ -24,6 +24,7 @@ import { savedObjectsServiceMock } from '@kbn/core-saved-objects-server-mocks';
|
|||
import { statusServiceMock } from '@kbn/core-status-server-mocks';
|
||||
import { uiSettingsServiceMock } from '@kbn/core-ui-settings-server-mocks';
|
||||
import { coreUsageDataServiceMock } from '@kbn/core-usage-data-server-mocks';
|
||||
import { customBrandingServiceMock } from '@kbn/core-custom-branding-server-mocks';
|
||||
|
||||
export function createInternalCoreSetupMock() {
|
||||
const setupDeps = {
|
||||
|
@ -45,6 +46,7 @@ export function createInternalCoreSetupMock() {
|
|||
deprecations: deprecationsServiceMock.createInternalSetupContract(),
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
coreUsageData: coreUsageDataServiceMock.createSetupContract(),
|
||||
customBranding: customBrandingServiceMock.createSetupContract(),
|
||||
};
|
||||
return setupDeps;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import { metricsServiceMock } from '@kbn/core-metrics-server-mocks';
|
|||
import { savedObjectsServiceMock } from '@kbn/core-saved-objects-server-mocks';
|
||||
import { uiSettingsServiceMock } from '@kbn/core-ui-settings-server-mocks';
|
||||
import { coreUsageDataServiceMock } from '@kbn/core-usage-data-server-mocks';
|
||||
import { customBrandingServiceMock } from '@kbn/core-custom-branding-server-mocks';
|
||||
|
||||
export function createInternalCoreStartMock() {
|
||||
const startDeps = {
|
||||
|
@ -31,6 +32,7 @@ export function createInternalCoreStartMock() {
|
|||
coreUsageData: coreUsageDataServiceMock.createStartContract(),
|
||||
executionContext: executionContextServiceMock.createInternalStartContract(),
|
||||
deprecations: deprecationsServiceMock.createInternalStartContract(),
|
||||
customBranding: customBrandingServiceMock.createStartContract(),
|
||||
};
|
||||
return startDeps;
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"@kbn/core-usage-data-server-mocks",
|
||||
"@kbn/core-http-request-handler-context-server",
|
||||
"@kbn/core-logging-server-mocks",
|
||||
"@kbn/core-custom-branding-server-mocks",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -22,6 +22,7 @@ import { SavedObjectsServiceSetup } from '@kbn/core-saved-objects-server';
|
|||
import { StatusServiceSetup } from '@kbn/core-status-server';
|
||||
import { UiSettingsServiceSetup } from '@kbn/core-ui-settings-server';
|
||||
import { CoreUsageDataSetup } from '@kbn/core-usage-data-server';
|
||||
import { CustomBrandingSetup } from '@kbn/core-custom-branding-server';
|
||||
import { CoreStart } from './core_start';
|
||||
|
||||
/**
|
||||
|
@ -38,6 +39,8 @@ export interface CoreSetup<TPluginsStart extends object = object, TStart = unkno
|
|||
analytics: AnalyticsServiceSetup;
|
||||
/** {@link CapabilitiesSetup} */
|
||||
capabilities: CapabilitiesSetup;
|
||||
/** {@link CustomBrandingSetup} */
|
||||
customBranding: CustomBrandingSetup;
|
||||
/** {@link DocLinksServiceSetup} */
|
||||
docLinks: DocLinksServiceSetup;
|
||||
/** {@link ElasticsearchServiceSetup} */
|
||||
|
|
|
@ -16,6 +16,7 @@ import { MetricsServiceStart } from '@kbn/core-metrics-server';
|
|||
import { SavedObjectsServiceStart } from '@kbn/core-saved-objects-server';
|
||||
import { UiSettingsServiceStart } from '@kbn/core-ui-settings-server';
|
||||
import { CoreUsageDataStart } from '@kbn/core-usage-data-server';
|
||||
import { CustomBrandingStart } from '@kbn/core-custom-branding-server';
|
||||
|
||||
/**
|
||||
* Context passed to the plugins `start` method.
|
||||
|
@ -27,6 +28,8 @@ export interface CoreStart {
|
|||
analytics: AnalyticsServiceStart;
|
||||
/** {@link CapabilitiesStart} */
|
||||
capabilities: CapabilitiesStart;
|
||||
/** {@link CustomBrandingStart} */
|
||||
customBranding: CustomBrandingStart;
|
||||
/** {@link DocLinksServiceStart} */
|
||||
docLinks: DocLinksServiceStart;
|
||||
/** {@link ElasticsearchServiceStart} */
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
"@kbn/core-saved-objects-server",
|
||||
"@kbn/core-status-server",
|
||||
"@kbn/core-ui-settings-server",
|
||||
"@kbn/core-usage-data-server"
|
||||
"@kbn/core-usage-data-server",
|
||||
"@kbn/core-custom-branding-server"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -74,6 +74,7 @@ export function createPluginSetupContext<
|
|||
register: (app) => deps.application.register(plugin.opaqueId, app),
|
||||
registerAppUpdater: (statusUpdater$) => deps.application.registerAppUpdater(statusUpdater$),
|
||||
},
|
||||
customBranding: deps.customBranding,
|
||||
fatalErrors: deps.fatalErrors,
|
||||
executionContext: deps.executionContext,
|
||||
http: deps.http,
|
||||
|
@ -115,6 +116,7 @@ export function createPluginStartContext<
|
|||
navigateToUrl: deps.application.navigateToUrl,
|
||||
getUrlForApp: deps.application.getUrlForApp,
|
||||
},
|
||||
customBranding: deps.customBranding,
|
||||
docLinks: deps.docLinks,
|
||||
executionContext: deps.executionContext,
|
||||
http: deps.http,
|
||||
|
|
|
@ -194,6 +194,11 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
|
|||
registerProvider: deps.capabilities.registerProvider,
|
||||
registerSwitcher: deps.capabilities.registerSwitcher,
|
||||
},
|
||||
customBranding: {
|
||||
register: (fetchFn) => {
|
||||
deps.customBranding.register(plugin.name, fetchFn);
|
||||
},
|
||||
},
|
||||
docLinks: deps.docLinks,
|
||||
elasticsearch: {
|
||||
legacy: deps.elasticsearch.legacy,
|
||||
|
@ -286,6 +291,7 @@ export function createPluginStartContext<TPlugin, TPluginDependencies>(
|
|||
capabilities: {
|
||||
resolveCapabilities: deps.capabilities.resolveCapabilities,
|
||||
},
|
||||
customBranding: deps.customBranding,
|
||||
docLinks: deps.docLinks,
|
||||
elasticsearch: {
|
||||
client: deps.elasticsearch.client,
|
||||
|
|
|
@ -10,6 +10,7 @@ Object {
|
|||
"csp": Object {
|
||||
"warnLegacyBrowsers": true,
|
||||
},
|
||||
"customBranding": Object {},
|
||||
"env": Object {
|
||||
"mode": Object {
|
||||
"dev": Any<Boolean>,
|
||||
|
@ -70,6 +71,7 @@ Object {
|
|||
"csp": Object {
|
||||
"warnLegacyBrowsers": true,
|
||||
},
|
||||
"customBranding": Object {},
|
||||
"env": Object {
|
||||
"mode": Object {
|
||||
"dev": Any<Boolean>,
|
||||
|
@ -134,6 +136,7 @@ Object {
|
|||
"csp": Object {
|
||||
"warnLegacyBrowsers": true,
|
||||
},
|
||||
"customBranding": Object {},
|
||||
"env": Object {
|
||||
"mode": Object {
|
||||
"dev": Any<Boolean>,
|
||||
|
@ -194,6 +197,7 @@ Object {
|
|||
"csp": Object {
|
||||
"warnLegacyBrowsers": true,
|
||||
},
|
||||
"customBranding": Object {},
|
||||
"env": Object {
|
||||
"mode": Object {
|
||||
"dev": Any<Boolean>,
|
||||
|
@ -254,6 +258,7 @@ Object {
|
|||
"csp": Object {
|
||||
"warnLegacyBrowsers": true,
|
||||
},
|
||||
"customBranding": Object {},
|
||||
"env": Object {
|
||||
"mode": Object {
|
||||
"dev": Any<Boolean>,
|
||||
|
@ -318,6 +323,7 @@ Object {
|
|||
"csp": Object {
|
||||
"warnLegacyBrowsers": true,
|
||||
},
|
||||
"customBranding": Object {},
|
||||
"env": Object {
|
||||
"mode": Object {
|
||||
"dev": Any<Boolean>,
|
||||
|
@ -378,6 +384,7 @@ Object {
|
|||
"csp": Object {
|
||||
"warnLegacyBrowsers": true,
|
||||
},
|
||||
"customBranding": Object {},
|
||||
"env": Object {
|
||||
"mode": Object {
|
||||
"dev": Any<Boolean>,
|
||||
|
@ -438,6 +445,7 @@ Object {
|
|||
"csp": Object {
|
||||
"warnLegacyBrowsers": true,
|
||||
},
|
||||
"customBranding": Object {},
|
||||
"env": Object {
|
||||
"mode": Object {
|
||||
"dev": Any<Boolean>,
|
||||
|
@ -502,6 +510,7 @@ Object {
|
|||
"csp": Object {
|
||||
"warnLegacyBrowsers": true,
|
||||
},
|
||||
"customBranding": Object {},
|
||||
"env": Object {
|
||||
"mode": Object {
|
||||
"dev": Any<Boolean>,
|
||||
|
@ -570,6 +579,7 @@ Object {
|
|||
"csp": Object {
|
||||
"warnLegacyBrowsers": true,
|
||||
},
|
||||
"customBranding": Object {},
|
||||
"env": Object {
|
||||
"mode": Object {
|
||||
"dev": Any<Boolean>,
|
||||
|
@ -630,6 +640,7 @@ Object {
|
|||
"csp": Object {
|
||||
"warnLegacyBrowsers": true,
|
||||
},
|
||||
"customBranding": Object {},
|
||||
"env": Object {
|
||||
"mode": Object {
|
||||
"dev": Any<Boolean>,
|
||||
|
@ -694,6 +705,7 @@ Object {
|
|||
"csp": Object {
|
||||
"warnLegacyBrowsers": true,
|
||||
},
|
||||
"customBranding": Object {},
|
||||
"env": Object {
|
||||
"mode": Object {
|
||||
"dev": Any<Boolean>,
|
||||
|
@ -762,6 +774,7 @@ Object {
|
|||
"csp": Object {
|
||||
"warnLegacyBrowsers": true,
|
||||
},
|
||||
"customBranding": Object {},
|
||||
"env": Object {
|
||||
"mode": Object {
|
||||
"dev": Any<Boolean>,
|
||||
|
@ -826,6 +839,7 @@ Object {
|
|||
"csp": Object {
|
||||
"warnLegacyBrowsers": true,
|
||||
},
|
||||
"customBranding": Object {},
|
||||
"env": Object {
|
||||
"mode": Object {
|
||||
"dev": Any<Boolean>,
|
||||
|
|
|
@ -17,6 +17,7 @@ import type { CoreContext } from '@kbn/core-base-server-internal';
|
|||
import type { KibanaRequest, HttpAuth } from '@kbn/core-http-server';
|
||||
import type { IUiSettingsClient } from '@kbn/core-ui-settings-server';
|
||||
import type { UiPlugins } from '@kbn/core-plugins-base-server-internal';
|
||||
import { CustomBranding } from '@kbn/core-custom-branding-common';
|
||||
import { Template } from './views';
|
||||
import {
|
||||
IRenderOptions,
|
||||
|
@ -32,8 +33,8 @@ import { filterUiPlugins } from './filter_ui_plugins';
|
|||
import type { InternalRenderingRequestHandlerContext } from './internal_types';
|
||||
|
||||
type RenderOptions =
|
||||
| (RenderingPrebootDeps & { status?: never; elasticsearch?: never })
|
||||
| RenderingSetupDeps;
|
||||
| RenderingSetupDeps
|
||||
| (RenderingPrebootDeps & { status?: never; elasticsearch?: never; customBranding?: never });
|
||||
|
||||
/** @internal */
|
||||
export class RenderingService {
|
||||
|
@ -65,6 +66,7 @@ export class RenderingService {
|
|||
http,
|
||||
status,
|
||||
uiPlugins,
|
||||
customBranding,
|
||||
}: RenderingSetupDeps): Promise<InternalRenderingServiceSetup> {
|
||||
registerBootstrapRoute({
|
||||
router: http.createRouter<InternalRenderingRequestHandlerContext>(''),
|
||||
|
@ -77,12 +79,12 @@ export class RenderingService {
|
|||
});
|
||||
|
||||
return {
|
||||
render: this.render.bind(this, { elasticsearch, http, uiPlugins, status }),
|
||||
render: this.render.bind(this, { elasticsearch, http, uiPlugins, status, customBranding }),
|
||||
};
|
||||
}
|
||||
|
||||
private async render(
|
||||
{ elasticsearch, http, uiPlugins, status }: RenderOptions,
|
||||
renderOptions: RenderOptions,
|
||||
request: KibanaRequest,
|
||||
uiSettings: {
|
||||
client: IUiSettingsClient;
|
||||
|
@ -90,6 +92,8 @@ export class RenderingService {
|
|||
},
|
||||
{ isAnonymousPage = false, vars, includeExposedConfigKeys }: IRenderOptions = {}
|
||||
) {
|
||||
const { elasticsearch, http, uiPlugins, status, customBranding } = renderOptions;
|
||||
|
||||
const env = {
|
||||
mode: this.coreContext.env.mode,
|
||||
packageInfo: this.coreContext.env.packageInfo,
|
||||
|
@ -105,8 +109,8 @@ export class RenderingService {
|
|||
defaults: uiSettings.globalClient?.getRegistered() ?? {},
|
||||
user: isAnonymousPage ? {} : await uiSettings.globalClient?.getUserProvided(),
|
||||
};
|
||||
|
||||
let clusterInfo = {};
|
||||
let branding: CustomBranding = {};
|
||||
try {
|
||||
// Only provide the clusterInfo if the request is authenticated and the elasticsearch service is available.
|
||||
if (isAuthenticated(http.auth, request) && elasticsearch) {
|
||||
|
@ -116,6 +120,7 @@ export class RenderingService {
|
|||
catchError(() => of({}))
|
||||
)
|
||||
);
|
||||
branding = await customBranding?.getBrandingFor(request);
|
||||
}
|
||||
} catch (err) {
|
||||
// swallow error
|
||||
|
@ -142,6 +147,11 @@ export class RenderingService {
|
|||
darkMode,
|
||||
themeVersion,
|
||||
stylesheetPaths,
|
||||
customBranding: {
|
||||
faviconSVG: branding?.faviconSVG,
|
||||
faviconPNG: branding?.faviconPNG,
|
||||
pageTitle: branding?.pageTitle,
|
||||
},
|
||||
injectedMetadata: {
|
||||
version: env.packageInfo.version,
|
||||
buildNumber: env.packageInfo.buildNum,
|
||||
|
@ -159,6 +169,10 @@ export class RenderingService {
|
|||
darkMode,
|
||||
version: themeVersion,
|
||||
},
|
||||
customBranding: {
|
||||
logo: branding?.logo,
|
||||
customizedLogo: branding?.customizedLogo,
|
||||
},
|
||||
csp: { warnLegacyBrowsers: http.csp.warnLegacyBrowsers },
|
||||
externalUrl: http.externalUrl,
|
||||
vars: vars ?? {},
|
||||
|
|
|
@ -10,12 +10,14 @@ import { mockCoreContext } from '@kbn/core-base-server-mocks';
|
|||
import { httpServiceMock } from '@kbn/core-http-server-mocks';
|
||||
import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks';
|
||||
import { statusServiceMock } from '@kbn/core-status-server-mocks';
|
||||
import { customBrandingServiceMock } from '@kbn/core-custom-branding-server-mocks';
|
||||
|
||||
const context = mockCoreContext.create();
|
||||
const httpPreboot = httpServiceMock.createInternalPrebootContract();
|
||||
const httpSetup = httpServiceMock.createInternalSetupContract();
|
||||
const status = statusServiceMock.createInternalSetupContract();
|
||||
const elasticsearch = elasticsearchServiceMock.createInternalSetup();
|
||||
const customBranding = customBrandingServiceMock.createSetupContract();
|
||||
|
||||
function createUiPlugins() {
|
||||
return {
|
||||
|
@ -34,5 +36,6 @@ export const mockRenderingSetupDeps = {
|
|||
elasticsearch,
|
||||
http: httpSetup,
|
||||
uiPlugins: createUiPlugins(),
|
||||
customBranding,
|
||||
status,
|
||||
};
|
||||
|
|
|
@ -18,6 +18,8 @@ import type { InternalElasticsearchServiceSetup } from '@kbn/core-elasticsearch-
|
|||
import type { InternalStatusServiceSetup } from '@kbn/core-status-server-internal';
|
||||
import type { IUiSettingsClient } from '@kbn/core-ui-settings-server';
|
||||
import type { UiPlugins } from '@kbn/core-plugins-base-server-internal';
|
||||
import type { InternalCustomBrandingSetup } from '@kbn/core-custom-branding-server-internal';
|
||||
import type { CustomBranding } from '@kbn/core-custom-branding-common';
|
||||
|
||||
/** @internal */
|
||||
export interface RenderingMetadata {
|
||||
|
@ -30,6 +32,7 @@ export interface RenderingMetadata {
|
|||
themeVersion: ThemeVersion;
|
||||
stylesheetPaths: string[];
|
||||
injectedMetadata: InjectedMetadata;
|
||||
customBranding: CustomBranding;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -44,6 +47,7 @@ export interface RenderingSetupDeps {
|
|||
http: InternalHttpServiceSetup;
|
||||
status: InternalStatusServiceSetup;
|
||||
uiPlugins: UiPlugins;
|
||||
customBranding: InternalCustomBrandingSetup;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
|
|
@ -28,19 +28,23 @@ export const Template: FunctionComponent<Props> = ({
|
|||
i18n,
|
||||
bootstrapScriptUrl,
|
||||
strictCsp,
|
||||
customBranding,
|
||||
},
|
||||
}) => {
|
||||
const title = customBranding.pageTitle ?? 'Elastic';
|
||||
const favIcon = customBranding.faviconSVG ?? `${uiPublicUrl}/favicons/favicon.svg`;
|
||||
const favIconPng = customBranding.faviconPNG ?? `${uiPublicUrl}/favicons/favicon.png`;
|
||||
return (
|
||||
<html lang={locale}>
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>Elastic</title>
|
||||
<title>{title}</title>
|
||||
<Fonts url={uiPublicUrl} />
|
||||
{/* The alternate icon is a fallback for Safari which does not yet support SVG favicons */}
|
||||
<link rel="alternate icon" type="image/png" href={`${uiPublicUrl}/favicons/favicon.png`} />
|
||||
<link rel="icon" type="image/svg+xml" href={`${uiPublicUrl}/favicons/favicon.svg`} />
|
||||
<link rel="alternate icon" type="image/png" href={favIconPng} />
|
||||
<link rel="icon" type="image/svg+xml" href={favIcon} />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
{/* Inject EUI reset and global styles before all other component styles */}
|
||||
|
|
|
@ -34,6 +34,9 @@
|
|||
"@kbn/core-elasticsearch-server-mocks",
|
||||
"@kbn/core-status-server-mocks",
|
||||
"@kbn/utility-types",
|
||||
"@kbn/core-custom-branding-server-internal",
|
||||
"@kbn/core-custom-branding-common",
|
||||
"@kbn/core-custom-branding-server-mocks",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -24,6 +24,7 @@ import { renderingServiceMock } from '@kbn/core-rendering-browser-mocks';
|
|||
import { integrationsServiceMock } from '@kbn/core-integrations-browser-mocks';
|
||||
import { coreAppsMock } from '@kbn/core-apps-browser-mocks';
|
||||
import { loggingSystemMock } from '@kbn/core-logging-browser-mocks';
|
||||
import { customBrandingServiceMock } from '@kbn/core-custom-branding-browser-mocks';
|
||||
|
||||
export const analyticsServiceStartMock = analyticsServiceMock.createAnalyticsServiceStart();
|
||||
export const MockAnalyticsService = analyticsServiceMock.create();
|
||||
|
@ -85,6 +86,13 @@ jest.doMock('@kbn/core-ui-settings-browser-internal', () => ({
|
|||
SettingsService: SettingsServiceConstructor,
|
||||
}));
|
||||
|
||||
export const MockCustomBrandingService = customBrandingServiceMock.create();
|
||||
export const CustomBrandingServiceConstructor = jest
|
||||
.fn()
|
||||
.mockImplementation(() => MockCustomBrandingService);
|
||||
jest.doMock('@kbn/core-custom-branding-browser-internal', () => ({
|
||||
CustomBrandingService: CustomBrandingServiceConstructor,
|
||||
}));
|
||||
export const MockChromeService = chromeServiceMock.create();
|
||||
export const ChromeServiceConstructor = jest.fn().mockImplementation(() => MockChromeService);
|
||||
jest.doMock('@kbn/core-chrome-browser-internal', () => ({
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue