[new-platform] only use Setup API's (#35733) (#36276)

* Core: only use Setup API's

* Fix linter issues

* Review feedback

* Update core API docs

* Make comment less coupled to Core calling code
This commit is contained in:
Rudolf Meijering 2019-05-09 08:19:15 +02:00 committed by GitHub
parent dd901777fa
commit dce0f3b978
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 108 additions and 170 deletions

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [InjectedMetadataSetup](./kibana-plugin-public.injectedmetadatasetup.md) &gt; [getKibanaBuildNumber](./kibana-plugin-public.injectedmetadatasetup.getkibanabuildnumber.md)
## InjectedMetadataSetup.getKibanaBuildNumber property
<b>Signature:</b>
```typescript
getKibanaBuildNumber: () => number;
```

View file

@ -20,6 +20,7 @@ export interface InjectedMetadataSetup
| [getCspConfig](./kibana-plugin-public.injectedmetadatasetup.getcspconfig.md) | <code>() =&gt; {`<p/>` warnLegacyBrowsers: boolean;`<p/>` }</code> | | | [getCspConfig](./kibana-plugin-public.injectedmetadatasetup.getcspconfig.md) | <code>() =&gt; {`<p/>` warnLegacyBrowsers: boolean;`<p/>` }</code> | |
| [getInjectedVar](./kibana-plugin-public.injectedmetadatasetup.getinjectedvar.md) | <code>(name: string, defaultValue?: any) =&gt; unknown</code> | | | [getInjectedVar](./kibana-plugin-public.injectedmetadatasetup.getinjectedvar.md) | <code>(name: string, defaultValue?: any) =&gt; unknown</code> | |
| [getInjectedVars](./kibana-plugin-public.injectedmetadatasetup.getinjectedvars.md) | <code>() =&gt; {`<p/>` [key: string]: unknown;`<p/>` }</code> | | | [getInjectedVars](./kibana-plugin-public.injectedmetadatasetup.getinjectedvars.md) | <code>() =&gt; {`<p/>` [key: string]: unknown;`<p/>` }</code> | |
| [getKibanaBuildNumber](./kibana-plugin-public.injectedmetadatasetup.getkibanabuildnumber.md) | <code>() =&gt; number</code> | |
| [getKibanaVersion](./kibana-plugin-public.injectedmetadatasetup.getkibanaversion.md) | <code>() =&gt; string</code> | | | [getKibanaVersion](./kibana-plugin-public.injectedmetadatasetup.getkibanaversion.md) | <code>() =&gt; string</code> | |
| [getLegacyMetadata](./kibana-plugin-public.injectedmetadatasetup.getlegacymetadata.md) | <code>() =&gt; {`<p/>` app: unknown;`<p/>` translations: unknown;`<p/>` bundleId: string;`<p/>` nav: LegacyNavLink[];`<p/>` version: string;`<p/>` branch: string;`<p/>` buildNum: number;`<p/>` buildSha: string;`<p/>` basePath: string;`<p/>` serverName: string;`<p/>` devMode: boolean;`<p/>` uiSettings: {`<p/>` defaults: UiSettingsState;`<p/>` user?: UiSettingsState &#124; undefined;`<p/>` };`<p/>` }</code> | | | [getLegacyMetadata](./kibana-plugin-public.injectedmetadatasetup.getlegacymetadata.md) | <code>() =&gt; {`<p/>` app: unknown;`<p/>` translations: unknown;`<p/>` bundleId: string;`<p/>` nav: LegacyNavLink[];`<p/>` version: string;`<p/>` branch: string;`<p/>` buildNum: number;`<p/>` buildSha: string;`<p/>` basePath: string;`<p/>` serverName: string;`<p/>` devMode: boolean;`<p/>` uiSettings: {`<p/>` defaults: UiSettingsState;`<p/>` user?: UiSettingsState &#124; undefined;`<p/>` };`<p/>` }</code> | |
| [getPlugins](./kibana-plugin-public.injectedmetadatasetup.getplugins.md) | <code>() =&gt; Array&lt;{`<p/>` id: string;`<p/>` plugin: DiscoveredPlugin;`<p/>` }&gt;</code> | An array of frontend plugins in topological order. | | [getPlugins](./kibana-plugin-public.injectedmetadatasetup.getplugins.md) | <code>() =&gt; Array&lt;{`<p/>` id: string;`<p/>` plugin: DiscoveredPlugin;`<p/>` }&gt;</code> | An array of frontend plugins in topological order. |

View file

@ -131,13 +131,12 @@ describe('constructor', () => {
expect(FatalErrorsServiceConstructor).toHaveBeenCalledTimes(1); expect(FatalErrorsServiceConstructor).toHaveBeenCalledTimes(1);
expect(FatalErrorsServiceConstructor).toHaveBeenLastCalledWith({ expect(FatalErrorsServiceConstructor).toHaveBeenLastCalledWith(
rootDomElement, rootDomElement,
injectedMetadata: MockInjectedMetadataService, expect.any(Function)
stopCoreSystem: expect.any(Function), );
});
const [{ stopCoreSystem }] = FatalErrorsServiceConstructor.mock.calls[0]; const [, stopCoreSystem] = FatalErrorsServiceConstructor.mock.calls[0];
expect(coreSystem.stop).not.toHaveBeenCalled(); expect(coreSystem.stop).not.toHaveBeenCalled();
stopCoreSystem(); stopCoreSystem();

View file

@ -22,7 +22,7 @@ import './core.css';
import { CoreSetup, CoreStart } from '.'; import { CoreSetup, CoreStart } from '.';
import { BasePathService } from './base_path'; import { BasePathService } from './base_path';
import { ChromeService } from './chrome'; import { ChromeService } from './chrome';
import { FatalErrorsService } from './fatal_errors'; import { FatalErrorsService, FatalErrorsSetup } from './fatal_errors';
import { HttpService } from './http'; import { HttpService } from './http';
import { I18nService } from './i18n'; import { I18nService } from './i18n';
import { InjectedMetadataParams, InjectedMetadataService } from './injected_metadata'; import { InjectedMetadataParams, InjectedMetadataService } from './injected_metadata';
@ -69,6 +69,7 @@ export class CoreSystem {
private readonly rootDomElement: HTMLElement; private readonly rootDomElement: HTMLElement;
private readonly overlayTargetDomElement: HTMLDivElement; private readonly overlayTargetDomElement: HTMLDivElement;
private fatalErrorsSetup: FatalErrorsSetup | null = null;
constructor(params: Params) { constructor(params: Params) {
const { const {
@ -87,12 +88,9 @@ export class CoreSystem {
injectedMetadata, injectedMetadata,
}); });
this.fatalErrors = new FatalErrorsService({ this.fatalErrors = new FatalErrorsService(rootDomElement, () => {
rootDomElement, // Stop Core before rendering any fatal errors into the DOM
injectedMetadata: this.injectedMetadata, this.stop();
stopCoreSystem: () => {
this.stop();
},
}); });
this.notifications = new NotificationsService(); this.notifications = new NotificationsService();
@ -115,10 +113,13 @@ export class CoreSystem {
public async setup() { public async setup() {
try { try {
// Setup FatalErrorsService and it's dependencies first so that we're
// able to render any errors.
const i18n = this.i18n.setup(); const i18n = this.i18n.setup();
const injectedMetadata = this.injectedMetadata.setup(); const injectedMetadata = this.injectedMetadata.setup();
const fatalErrors = this.fatalErrors.setup({ i18n }); this.fatalErrorsSetup = this.fatalErrors.setup({ injectedMetadata, i18n });
const http = this.http.setup({ fatalErrors });
const http = this.http.setup({ fatalErrors: this.fatalErrorsSetup });
const basePath = this.basePath.setup({ injectedMetadata }); const basePath = this.basePath.setup({ injectedMetadata });
const uiSettings = this.uiSettings.setup({ const uiSettings = this.uiSettings.setup({
http, http,
@ -136,7 +137,7 @@ export class CoreSystem {
application, application,
basePath, basePath,
chrome, chrome,
fatalErrors, fatalErrors: this.fatalErrorsSetup,
http, http,
i18n, i18n,
injectedMetadata, injectedMetadata,
@ -148,9 +149,15 @@ export class CoreSystem {
await this.plugins.setup(core); await this.plugins.setup(core);
await this.legacyPlatform.setup({ core }); await this.legacyPlatform.setup({ core });
return { fatalErrors }; return { fatalErrors: this.fatalErrorsSetup };
} catch (error) { } catch (error) {
this.fatalErrors.add(error); if (this.fatalErrorsSetup) {
this.fatalErrorsSetup.add(error);
} else {
// If the FatalErrorsService has not yet been setup, log error to console
// eslint-disable-next-line no-console
console.log(error);
}
} }
} }
@ -189,7 +196,13 @@ export class CoreSystem {
await this.plugins.start(core); await this.plugins.start(core);
await this.legacyPlatform.start({ core, targetDomElement: legacyPlatformTargetDomElement }); await this.legacyPlatform.start({ core, targetDomElement: legacyPlatformTargetDomElement });
} catch (error) { } catch (error) {
this.fatalErrors.add(error); if (this.fatalErrorsSetup) {
this.fatalErrorsSetup.add(error);
} else {
// If the FatalErrorsService has not yet been setup, log error to console
// eslint-disable-next-line no-console
console.error(error);
}
} }
} }

View file

@ -3,13 +3,12 @@
exports[`#add() deletes all children of rootDomElement and renders <FatalErrorScreen /> into it: fatal error screen component 1`] = ` exports[`#add() deletes all children of rootDomElement and renders <FatalErrorScreen /> into it: fatal error screen component 1`] = `
Array [ Array [
Array [ Array [
<React.Fragment> <I18nContext>
<FatalErrorsScreen <FatalErrorsScreen
buildNumber="kibanaBuildNumber"
errorInfo$={Rx.Observable} errorInfo$={Rx.Observable}
kibanaVersion="kibanaVersion" kibanaVersion="kibanaVersion"
/> />
</React.Fragment>, </I18nContext>,
<div />, <div />,
], ],
] ]
@ -20,24 +19,3 @@ exports[`#add() deletes all children of rootDomElement and renders <FatalErrorSc
<div /> <div />
</div> </div>
`; `;
exports[`setup.add() deletes all children of rootDomElement and renders <FatalErrorScreen /> into it: fatal error screen component 1`] = `
Array [
Array [
<I18nContext>
<FatalErrorsScreen
buildNumber="kibanaBuildNumber"
errorInfo$={Rx.Observable}
kibanaVersion="kibanaVersion"
/>
</I18nContext>,
<div />,
],
]
`;
exports[`setup.add() deletes all children of rootDomElement and renders <FatalErrorScreen /> into it: fatal error screen container 1`] = `
<div>
<div />
</div>
`;

View file

@ -31,7 +31,6 @@ type FatalErrorsServiceContract = PublicMethodsOf<FatalErrorsService>;
const createMock = () => { const createMock = () => {
const mocked: jest.Mocked<FatalErrorsServiceContract> = { const mocked: jest.Mocked<FatalErrorsServiceContract> = {
setup: jest.fn(), setup: jest.fn(),
add: jest.fn<never, any>(() => undefined as never),
}; };
mocked.setup.mockReturnValue(createSetupContractMock()); mocked.setup.mockReturnValue(createSetupContractMock());

View file

@ -25,16 +25,14 @@ expect.addSnapshotSerializer({
}); });
import { mockRender } from './fatal_errors_service.test.mocks'; import { mockRender } from './fatal_errors_service.test.mocks';
import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock';
import { FatalErrorsService } from './fatal_errors_service'; import { FatalErrorsService } from './fatal_errors_service';
function setupService() { function setupService() {
const rootDomElement = document.createElement('div'); const rootDomElement = document.createElement('div');
const injectedMetadata = { const injectedMetadata = injectedMetadataServiceMock.createSetupContract();
getKibanaBuildNumber: jest.fn().mockReturnValue('kibanaBuildNumber'),
getKibanaVersion: jest.fn().mockReturnValue('kibanaVersion'),
};
const stopCoreSystem = jest.fn(); const stopCoreSystem = jest.fn();
@ -44,16 +42,13 @@ function setupService() {
}, },
}; };
const fatalErrorsService = new FatalErrorsService(rootDomElement, stopCoreSystem);
return { return {
rootDomElement, rootDomElement,
injectedMetadata, injectedMetadata,
i18n,
stopCoreSystem, stopCoreSystem,
fatalErrors: new FatalErrorsService({ fatalErrors: fatalErrorsService.setup({ injectedMetadata, i18n }),
injectedMetadata: injectedMetadata as any,
rootDomElement,
stopCoreSystem,
}),
}; };
} }
@ -91,46 +86,12 @@ describe('#add()', () => {
}); });
}); });
describe('setup.add()', () => {
it('exposes a function that passes its two arguments to fatalErrors.add()', () => {
const { fatalErrors, i18n } = setupService();
jest.spyOn(fatalErrors, 'add').mockImplementation(() => undefined as never);
expect(fatalErrors.add).not.toHaveBeenCalled();
const { add } = fatalErrors.setup({ i18n });
add('foo', 'bar');
expect(fatalErrors.add).toHaveBeenCalledTimes(1);
expect(fatalErrors.add).toHaveBeenCalledWith('foo', 'bar');
});
it('deletes all children of rootDomElement and renders <FatalErrorScreen /> into it', () => {
const { fatalErrors, i18n, rootDomElement } = setupService();
rootDomElement.innerHTML = `
<h1>Loading...</h1>
<div class="someSpinner"></div>
`;
expect(mockRender).not.toHaveBeenCalled();
expect(rootDomElement.children).toHaveLength(2);
const { add } = fatalErrors.setup({ i18n });
expect(() => add(new Error('foo'))).toThrowError();
expect(rootDomElement).toMatchSnapshot('fatal error screen container');
expect(mockRender.mock.calls).toMatchSnapshot('fatal error screen component');
});
});
describe('setup.get$()', () => { describe('setup.get$()', () => {
it('provides info about the errors passed to fatalErrors.add()', () => { it('provides info about the errors passed to fatalErrors.add()', () => {
const { fatalErrors, i18n } = setupService(); const { fatalErrors } = setupService();
const setup = fatalErrors.setup({ i18n });
const onError = jest.fn(); const onError = jest.fn();
setup.get$().subscribe(onError); fatalErrors.get$().subscribe(onError);
expect(onError).not.toHaveBeenCalled(); expect(onError).not.toHaveBeenCalled();
expect(() => { expect(() => {

View file

@ -23,18 +23,13 @@ import * as Rx from 'rxjs';
import { first, tap } from 'rxjs/operators'; import { first, tap } from 'rxjs/operators';
import { I18nSetup } from '../i18n'; import { I18nSetup } from '../i18n';
import { InjectedMetadataService } from '../injected_metadata'; import { InjectedMetadataSetup } from '../';
import { FatalErrorsScreen } from './fatal_errors_screen'; import { FatalErrorsScreen } from './fatal_errors_screen';
import { ErrorInfo, getErrorInfo } from './get_error_info'; import { ErrorInfo, getErrorInfo } from './get_error_info';
export interface FatalErrorsParams {
rootDomElement: HTMLElement;
injectedMetadata: InjectedMetadataService;
stopCoreSystem: () => void;
}
interface Deps { interface Deps {
i18n: I18nSetup; i18n: I18nSetup;
injectedMetadata: InjectedMetadataSetup;
} }
/** /**
@ -62,41 +57,45 @@ export interface FatalErrorsSetup {
/** @interal */ /** @interal */
export class FatalErrorsService { export class FatalErrorsService {
private readonly errorInfo$ = new Rx.ReplaySubject<ErrorInfo>(); private readonly errorInfo$ = new Rx.ReplaySubject<ErrorInfo>();
private i18n?: I18nSetup;
constructor(private params: FatalErrorsParams) { /**
*
* @param rootDomElement
* @param onFirstErrorCb - Callback function that gets executed after the first error,
* but before the FatalErrorsService renders the error to the DOM.
*/
constructor(private rootDomElement: HTMLElement, private onFirstErrorCb: () => void) {}
public setup({ i18n, injectedMetadata }: Deps) {
this.errorInfo$ this.errorInfo$
.pipe( .pipe(
first(), first(),
tap(() => this.onFirstError()) tap(() => {
this.onFirstErrorCb();
this.renderError(injectedMetadata, i18n);
})
) )
.subscribe({ .subscribe({
error: error => { error: error => {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error('Uncaught error in fatal error screen internals', error); console.error('Uncaught error in fatal error service internals', error);
}, },
}); });
}
public add: FatalErrorsSetup['add'] = (error, source?) => {
const errorInfo = getErrorInfo(error, source);
this.errorInfo$.next(errorInfo);
if (error instanceof Error) {
// make stack traces clickable by putting whole error in the console
// eslint-disable-next-line no-console
console.error(error);
}
throw error;
};
public setup({ i18n }: Deps) {
this.i18n = i18n;
const fatalErrorsSetup: FatalErrorsSetup = { const fatalErrorsSetup: FatalErrorsSetup = {
add: this.add, add: (error, source?) => {
const errorInfo = getErrorInfo(error, source);
this.errorInfo$.next(errorInfo);
if (error instanceof Error) {
// make stack traces clickable by putting whole error in the console
// eslint-disable-next-line no-console
console.error(error);
}
throw error;
},
get$: () => { get$: () => {
return this.errorInfo$.asObservable(); return this.errorInfo$.asObservable();
}, },
@ -105,30 +104,22 @@ export class FatalErrorsService {
return fatalErrorsSetup; return fatalErrorsSetup;
} }
private onFirstError() { private renderError(injectedMetadata: InjectedMetadataSetup, i18n: I18nSetup) {
// stop the core systems so that things like the legacy platform are stopped
// and angular/react components are unmounted;
this.params.stopCoreSystem();
// delete all content in the rootDomElement // delete all content in the rootDomElement
this.params.rootDomElement.textContent = ''; this.rootDomElement.textContent = '';
// create and mount a container for the <FatalErrorScreen> // create and mount a container for the <FatalErrorScreen>
const container = document.createElement('div'); const container = document.createElement('div');
this.params.rootDomElement.appendChild(container); this.rootDomElement.appendChild(container);
// If error occurred before I18nService has been set up we don't have any
// i18n context to provide.
const I18nContext = this.i18n ? this.i18n.Context : React.Fragment;
render( render(
<I18nContext> <i18n.Context>
<FatalErrorsScreen <FatalErrorsScreen
buildNumber={this.params.injectedMetadata.getKibanaBuildNumber()} buildNumber={injectedMetadata.getKibanaBuildNumber()}
kibanaVersion={this.params.injectedMetadata.getKibanaVersion()} kibanaVersion={injectedMetadata.getKibanaVersion()}
errorInfo$={this.errorInfo$} errorInfo$={this.errorInfo$}
/> />
</I18nContext>, </i18n.Context>,
container container
); );
} }

View file

@ -27,6 +27,7 @@ const createSetupContractMock = () => {
getPlugins: jest.fn(), getPlugins: jest.fn(),
getInjectedVar: jest.fn(), getInjectedVar: jest.fn(),
getInjectedVars: jest.fn(), getInjectedVars: jest.fn(),
getKibanaBuildNumber: jest.fn(),
}; };
setupContract.getCspConfig.mockReturnValue({ warnLegacyBrowsers: true }); setupContract.getCspConfig.mockReturnValue({ warnLegacyBrowsers: true });
setupContract.getKibanaVersion.mockReturnValue('kibanaVersion'); setupContract.getKibanaVersion.mockReturnValue('kibanaVersion');
@ -47,8 +48,6 @@ type InjectedMetadataServiceContract = PublicMethodsOf<InjectedMetadataService>;
const createMock = (): jest.Mocked<InjectedMetadataServiceContract> => ({ const createMock = (): jest.Mocked<InjectedMetadataServiceContract> => ({
setup: jest.fn().mockReturnValue(createSetupContractMock()), setup: jest.fn().mockReturnValue(createSetupContractMock()),
start: jest.fn().mockReturnValue(createStartContractMock()), start: jest.fn().mockReturnValue(createStartContractMock()),
getKibanaVersion: jest.fn(),
getKibanaBuildNumber: jest.fn(),
}); });
export const injectedMetadataServiceMock = { export const injectedMetadataServiceMock = {

View file

@ -20,42 +20,29 @@
import { DiscoveredPlugin } from '../../server'; import { DiscoveredPlugin } from '../../server';
import { InjectedMetadataService } from './injected_metadata_service'; import { InjectedMetadataService } from './injected_metadata_service';
describe('#getKibanaVersion', () => { describe('setup.getKibanaBuildNumber()', () => {
it('returns version from injectedMetadata', () => {
const injectedMetadata = new InjectedMetadataService({
injectedMetadata: {
version: 'foo',
},
} as any);
expect(injectedMetadata.getKibanaVersion()).toBe('foo');
});
});
describe('#getKibanaBuildNumber', () => {
it('returns buildNumber from injectedMetadata', () => { it('returns buildNumber from injectedMetadata', () => {
const injectedMetadata = new InjectedMetadataService({ const setup = new InjectedMetadataService({
injectedMetadata: { injectedMetadata: {
buildNumber: 'foo', buildNumber: 'foo',
}, },
} as any); } as any).setup();
expect(injectedMetadata.getKibanaBuildNumber()).toBe('foo'); expect(setup.getKibanaBuildNumber()).toBe('foo');
}); });
}); });
describe('setup.getCspConfig()', () => { describe('setup.getCspConfig()', () => {
it('returns injectedMetadata.csp', () => { it('returns injectedMetadata.csp', () => {
const injectedMetadata = new InjectedMetadataService({ const setup = new InjectedMetadataService({
injectedMetadata: { injectedMetadata: {
csp: { csp: {
warnLegacyBrowsers: true, warnLegacyBrowsers: true,
}, },
}, },
} as any); } as any).setup();
const contract = injectedMetadata.setup(); expect(setup.getCspConfig()).toEqual({
expect(contract.getCspConfig()).toEqual({
warnLegacyBrowsers: true, warnLegacyBrowsers: true,
}); });
}); });

View file

@ -83,6 +83,10 @@ export class InjectedMetadataService {
constructor(private readonly params: InjectedMetadataParams) {} constructor(private readonly params: InjectedMetadataParams) {}
public start(): InjectedMetadataStart {
return this.setup();
}
public setup(): InjectedMetadataSetup { public setup(): InjectedMetadataSetup {
return { return {
getBasePath: () => { getBasePath: () => {
@ -90,7 +94,7 @@ export class InjectedMetadataService {
}, },
getKibanaVersion: () => { getKibanaVersion: () => {
return this.getKibanaVersion(); return this.state.version;
}, },
getCspConfig: () => { getCspConfig: () => {
@ -112,20 +116,12 @@ export class InjectedMetadataService {
getInjectedVars: () => { getInjectedVars: () => {
return this.state.vars; return this.state.vars;
}, },
getKibanaBuildNumber: () => {
return this.state.buildNumber;
},
}; };
} }
public start(): InjectedMetadataStart {
return this.setup();
}
public getKibanaVersion() {
return this.state.version;
}
public getKibanaBuildNumber() {
return this.state.buildNumber;
}
} }
/** /**
@ -135,6 +131,7 @@ export class InjectedMetadataService {
*/ */
export interface InjectedMetadataSetup { export interface InjectedMetadataSetup {
getBasePath: () => string; getBasePath: () => string;
getKibanaBuildNumber: () => number;
getKibanaVersion: () => string; getKibanaVersion: () => string;
getCspConfig: () => { getCspConfig: () => {
warnLegacyBrowsers: boolean; warnLegacyBrowsers: boolean;

View file

@ -142,7 +142,7 @@ export class CoreSystem {
constructor(params: Params); constructor(params: Params);
// (undocumented) // (undocumented)
setup(): Promise<{ setup(): Promise<{
fatalErrors: import(".").FatalErrorsSetup; fatalErrors: FatalErrorsSetup;
} | undefined>; } | undefined>;
// (undocumented) // (undocumented)
start(): Promise<void>; start(): Promise<void>;
@ -233,6 +233,8 @@ export interface InjectedMetadataSetup {
[key: string]: unknown; [key: string]: unknown;
}; };
// (undocumented) // (undocumented)
getKibanaBuildNumber: () => number;
// (undocumented)
getKibanaVersion: () => string; getKibanaVersion: () => string;
// (undocumented) // (undocumented)
getLegacyMetadata: () => { getLegacyMetadata: () => {