Add warning toast when server.publicBaseUrl not configured correctly (#85344) (#103489)

Co-authored-by: Josh Dover <1813008+joshdover@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2021-06-28 12:27:54 -04:00 committed by GitHub
parent 4237e7f184
commit 2998d9c971
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 225 additions and 7 deletions

View file

@ -8,6 +8,7 @@
```typescript
readonly links: {
readonly settings: string;
readonly canvas: {
readonly guide: string;
};

File diff suppressed because one or more lines are too long

View file

@ -18,8 +18,13 @@ import type { CoreContext } from '../core_system';
import type { NotificationsSetup, NotificationsStart } from '../notifications';
import type { IUiSettingsClient } from '../ui_settings';
import type { InjectedMetadataSetup } from '../injected_metadata';
import { renderApp as renderErrorApp, setupUrlOverflowDetection } from './errors';
import {
renderApp as renderErrorApp,
setupPublicBaseUrlConfigWarning,
setupUrlOverflowDetection,
} from './errors';
import { renderApp as renderStatusApp } from './status';
import { DocLinksStart } from '../doc_links';
interface SetupDeps {
application: InternalApplicationSetup;
@ -30,6 +35,7 @@ interface SetupDeps {
interface StartDeps {
application: InternalApplicationStart;
docLinks: DocLinksStart;
http: HttpStart;
notifications: NotificationsStart;
uiSettings: IUiSettingsClient;
@ -40,7 +46,7 @@ export class CoreApp {
constructor(private readonly coreContext: CoreContext) {}
public setup({ http, application, injectedMetadata, notifications }: SetupDeps) {
public setup({ application, http, injectedMetadata, notifications }: SetupDeps) {
application.register(this.coreContext.coreId, {
id: 'error',
title: 'App Error',
@ -68,7 +74,7 @@ export class CoreApp {
});
}
public start({ application, http, notifications, uiSettings }: StartDeps) {
public start({ application, docLinks, http, notifications, uiSettings }: StartDeps) {
if (!application.history) {
return;
}
@ -79,6 +85,8 @@ export class CoreApp {
toasts: notifications.toasts,
uiSettings,
});
setupPublicBaseUrlConfigWarning({ docLinks, http, notifications });
}
public stop() {

View file

@ -8,3 +8,4 @@
export { renderApp } from './error_application';
export { setupUrlOverflowDetection, URL_MAX_LENGTH } from './url_overflow';
export { setupPublicBaseUrlConfigWarning } from './public_base_url';

View file

@ -0,0 +1,114 @@
/*
* 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 { docLinksServiceMock } from '../../doc_links/doc_links_service.mock';
import { httpServiceMock } from '../../http/http_service.mock';
import { notificationServiceMock } from '../../notifications/notifications_service.mock';
import { setupPublicBaseUrlConfigWarning } from './public_base_url';
describe('publicBaseUrl warning', () => {
const docLinks = docLinksServiceMock.createStartContract();
const notifications = notificationServiceMock.createStartContract();
beforeEach(() => {
jest.resetAllMocks();
});
it('does not show any toast on localhost', () => {
const http = httpServiceMock.createStartContract();
setupPublicBaseUrlConfigWarning({
docLinks,
notifications,
http,
location: {
hostname: 'localhost',
} as Location,
});
expect(notifications.toasts.addWarning).not.toHaveBeenCalled();
});
it('does not show any toast on 127.0.0.1', () => {
const http = httpServiceMock.createStartContract();
setupPublicBaseUrlConfigWarning({
docLinks,
notifications,
http,
location: {
hostname: '127.0.0.1',
} as Location,
});
expect(notifications.toasts.addWarning).not.toHaveBeenCalled();
});
it('does not show toast if configured correctly', () => {
const http = httpServiceMock.createStartContract({ publicBaseUrl: 'http://myhost.com' });
setupPublicBaseUrlConfigWarning({
docLinks,
notifications,
http,
location: {
hostname: 'myhost.com',
toString() {
return 'http://myhost.com/';
},
} as Location,
});
expect(notifications.toasts.addWarning).not.toHaveBeenCalled();
});
describe('config missing toast', () => {
it('adds toast if publicBaseUrl is missing', () => {
const http = httpServiceMock.createStartContract({ publicBaseUrl: undefined });
setupPublicBaseUrlConfigWarning({
docLinks,
notifications,
http,
location: {
hostname: 'myhost.com',
toString() {
return 'http://myhost.com/';
},
} as Location,
});
expect(notifications.toasts.addWarning).toHaveBeenCalledWith({
title: 'Configuration missing',
text: expect.any(Function),
});
});
it('does not add toast if storage key set', () => {
const http = httpServiceMock.createStartContract({ publicBaseUrl: undefined });
setupPublicBaseUrlConfigWarning({
docLinks,
notifications,
http,
location: {
hostname: 'myhost.com',
toString() {
return 'http://myhost.com/';
},
} as Location,
storage: {
getItem: (id: string) => 'true',
} as Storage,
});
expect(notifications.toasts.addWarning).not.toHaveBeenCalled();
});
});
});

View file

@ -0,0 +1,88 @@
/*
* 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 React from 'react';
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import type { HttpStart, NotificationsStart } from '../..';
import type { DocLinksStart } from '../../doc_links';
import { mountReactNode } from '../../utils';
/** Only exported for tests */
export const MISSING_CONFIG_STORAGE_KEY = `core.warnings.publicBaseUrlMissingDismissed`;
interface Deps {
docLinks: DocLinksStart;
http: HttpStart;
notifications: NotificationsStart;
// Exposed for easier testing
storage?: Storage;
location?: Location;
}
export const setupPublicBaseUrlConfigWarning = ({
docLinks,
http,
notifications,
storage = window.localStorage,
location = window.location,
}: Deps) => {
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
return;
}
const missingWarningSeen = storage.getItem(MISSING_CONFIG_STORAGE_KEY) === 'true';
if (missingWarningSeen || http.basePath.publicBaseUrl) {
return;
}
const toast = notifications.toasts.addWarning({
title: i18n.translate('core.ui.publicBaseUrlWarning.configMissingTitle', {
defaultMessage: 'Configuration missing',
}),
text: mountReactNode(
<>
<p>
<FormattedMessage
id="core.ui.publicBaseUrlWarning.configMissingDescription"
defaultMessage="{configKey} is missing and should be configured when running in a production environment. Some features may not behave correctly."
values={{
configKey: <code>server.publicBaseUrl</code>,
}}
/>{' '}
<a href={`${docLinks.links.settings}#server-publicBaseUrl`} target="_blank">
<FormattedMessage
id="core.ui.publicBaseUrlWarning.seeDocumentationLinkLabel"
defaultMessage="See the documentation."
/>
</a>
</p>
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiButton
size="s"
onClick={() => {
notifications.toasts.remove(toast);
storage.setItem(MISSING_CONFIG_STORAGE_KEY, 'true');
}}
id="mute"
>
<FormattedMessage
id="core.ui.publicBaseUrlWarning.muteWarningButtonLabel"
defaultMessage="Mute warning"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</>
),
});
};

View file

@ -202,7 +202,7 @@ export class CoreSystem {
});
const deprecations = this.deprecations.start({ http });
this.coreApp.start({ application, http, notifications, uiSettings });
this.coreApp.start({ application, docLinks, http, notifications, uiSettings });
const core: InternalCoreStart = {
application,

View file

@ -29,6 +29,7 @@ export class DocLinksService {
DOC_LINK_VERSION,
ELASTIC_WEBSITE_URL,
links: {
settings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/settings.html`,
canvas: {
guide: `${KIBANA_DOCS}canvas.html`,
},
@ -428,6 +429,7 @@ export interface DocLinksStart {
readonly DOC_LINK_VERSION: string;
readonly ELASTIC_WEBSITE_URL: string;
readonly links: {
readonly settings: string;
readonly canvas: {
readonly guide: string;
};

View file

@ -18,7 +18,10 @@ export type HttpSetupMock = jest.Mocked<HttpSetup> & {
anonymousPaths: jest.Mocked<HttpSetup['anonymousPaths']>;
};
const createServiceMock = ({ basePath = '' } = {}): HttpSetupMock => ({
const createServiceMock = ({
basePath = '',
publicBaseUrl,
}: { basePath?: string; publicBaseUrl?: string } = {}): HttpSetupMock => ({
fetch: jest.fn(),
get: jest.fn(),
head: jest.fn(),
@ -27,7 +30,7 @@ const createServiceMock = ({ basePath = '' } = {}): HttpSetupMock => ({
patch: jest.fn(),
delete: jest.fn(),
options: jest.fn(),
basePath: new BasePath(basePath),
basePath: new BasePath(basePath, undefined, publicBaseUrl),
anonymousPaths: {
register: jest.fn(),
isAnonymous: jest.fn(),

View file

@ -487,6 +487,7 @@ export interface DocLinksStart {
readonly ELASTIC_WEBSITE_URL: string;
// (undocumented)
readonly links: {
readonly settings: string;
readonly canvas: {
readonly guide: string;
};