Skip to content

Commit

Permalink
Add warning toast when server.publicBaseUrl not configured correctly (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
joshdover authored Jun 28, 2021
1 parent 4f45535 commit 6ba26db
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

```typescript
readonly links: {
readonly settings: string;
readonly canvas: {
readonly guide: string;
};
Expand Down

Large diffs are not rendered by default.

14 changes: 11 additions & 3 deletions src/core/public/core_app/core_app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,6 +35,7 @@ interface SetupDeps {

interface StartDeps {
application: InternalApplicationStart;
docLinks: DocLinksStart;
http: HttpStart;
notifications: NotificationsStart;
uiSettings: IUiSettingsClient;
Expand All @@ -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',
Expand Down Expand Up @@ -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;
}
Expand All @@ -79,6 +85,8 @@ export class CoreApp {
toasts: notifications.toasts,
uiSettings,
});

setupPublicBaseUrlConfigWarning({ docLinks, http, notifications });
}

public stop() {
Expand Down
1 change: 1 addition & 0 deletions src/core/public/core_app/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@

export { renderApp } from './error_application';
export { setupUrlOverflowDetection, URL_MAX_LENGTH } from './url_overflow';
export { setupPublicBaseUrlConfigWarning } from './public_base_url';
114 changes: 114 additions & 0 deletions src/core/public/core_app/errors/public_base_url.test.tsx
Original file line number Diff line number Diff line change
@@ -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();
});
});
});
88 changes: 88 additions & 0 deletions src/core/public/core_app/errors/public_base_url.tsx
Original file line number Diff line number Diff line change
@@ -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>
</>
),
});
};
2 changes: 1 addition & 1 deletion src/core/public/core_system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions src/core/public/doc_links/doc_links_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
},
Expand Down Expand Up @@ -426,6 +427,7 @@ export interface DocLinksStart {
readonly DOC_LINK_VERSION: string;
readonly ELASTIC_WEBSITE_URL: string;
readonly links: {
readonly settings: string;
readonly canvas: {
readonly guide: string;
};
Expand Down
7 changes: 5 additions & 2 deletions src/core/public/http/http_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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(),
Expand Down
1 change: 1 addition & 0 deletions src/core/public/public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ export interface DocLinksStart {
readonly ELASTIC_WEBSITE_URL: string;
// (undocumented)
readonly links: {
readonly settings: string;
readonly canvas: {
readonly guide: string;
};
Expand Down

0 comments on commit 6ba26db

Please sign in to comment.