[7.x] Add url overflow handling to KP (#67899) (#68573)

This commit is contained in:
Josh Dover 2020-06-08 17:13:21 -06:00 committed by GitHub
parent cc2d248294
commit 77f74ec571
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 774 additions and 395 deletions

View file

@ -132,6 +132,12 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [URLMeaningfulParts](./kibana-plugin-core-public.urlmeaningfulparts.md) | We define our own typings because the current version of @<!-- -->types/node declares properties to be optional "hostname?: string". Although, parse call returns "hostname: null \| string". |
| [UserProvidedValues](./kibana-plugin-core-public.userprovidedvalues.md) | Describes the values explicitly set by user. |
## Variables
| Variable | Description |
| --- | --- |
| [URL\_MAX\_LENGTH](./kibana-plugin-core-public.url_max_length.md) | The max URL length allowed by the current browser. Should be used to display warnings to users when query parameters cause URL to exceed this limit. |
## Type Aliases
| Type Alias | Description |

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-public](./kibana-plugin-core-public.md) &gt; [URL\_MAX\_LENGTH](./kibana-plugin-core-public.url_max_length.md)
## URL\_MAX\_LENGTH variable
The max URL length allowed by the current browser. Should be used to display warnings to users when query parameters cause URL to exceed this limit.
<b>Signature:</b>
```typescript
URL_MAX_LENGTH: number
```

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { History } from 'history';
import { BehaviorSubject, Subject } from 'rxjs';
import { capabilitiesServiceMock } from './capabilities/capabilities_service.mock';
@ -57,6 +58,28 @@ const createStartContractMock = (): jest.Mocked<ApplicationStart> => {
};
};
const createHistoryMock = (): jest.Mocked<History> => {
return {
block: jest.fn(),
createHref: jest.fn(),
go: jest.fn(),
goBack: jest.fn(),
goForward: jest.fn(),
listen: jest.fn(),
push: jest.fn(),
replace: jest.fn(),
action: 'PUSH',
length: 1,
location: {
pathname: '/',
search: '',
hash: '',
key: '',
state: undefined,
},
};
};
const createInternalStartContractMock = (): jest.Mocked<InternalApplicationStart> => {
const currentAppId$ = new Subject<string | undefined>();
@ -69,6 +92,7 @@ const createInternalStartContractMock = (): jest.Mocked<InternalApplicationStart
navigateToApp: jest.fn().mockImplementation((appId) => currentAppId$.next(appId)),
navigateToUrl: jest.fn(),
registerMountContext: jest.fn(),
history: createHistoryMock(),
};
};

View file

@ -301,6 +301,7 @@ export class ApplicationService {
distinctUntilChanged(),
takeUntil(this.stop$)
),
history: this.history,
registerMountContext: this.mountContext.registerContext,
getUrlForApp: (
appId,

View file

@ -43,5 +43,6 @@ export {
PublicAppInfo,
PublicLegacyAppInfo,
// Internal types
InternalApplicationSetup,
InternalApplicationStart,
} from './types';

View file

@ -18,6 +18,7 @@
*/
import { Observable } from 'rxjs';
import { History } from 'history';
import { Capabilities } from './capabilities';
import { ChromeStart } from '../chrome';
@ -766,6 +767,12 @@ export interface InternalApplicationStart extends Omit<ApplicationStart, 'regist
// Internal APIs
getComponent(): JSX.Element | null;
/**
* The global history instance, exposed only to Core. Undefined when rendering a legacy application.
* @internal
*/
history: History<unknown> | undefined;
}
/** @internal */

View file

@ -42,6 +42,25 @@ exports[`Header renders 1`] = `
},
"getComponent": [MockFunction],
"getUrlForApp": [MockFunction],
"history": Object {
"action": "PUSH",
"block": [MockFunction],
"createHref": [MockFunction],
"go": [MockFunction],
"goBack": [MockFunction],
"goForward": [MockFunction],
"length": 1,
"listen": [MockFunction],
"location": Object {
"hash": "",
"key": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [MockFunction],
"replace": [MockFunction],
},
"navigateToApp": [MockFunction],
"navigateToUrl": [MockFunction],
"registerMountContext": [MockFunction],
@ -657,6 +676,25 @@ exports[`Header renders 2`] = `
},
"getComponent": [MockFunction],
"getUrlForApp": [MockFunction],
"history": Object {
"action": "PUSH",
"block": [MockFunction],
"createHref": [MockFunction],
"go": [MockFunction],
"goBack": [MockFunction],
"goForward": [MockFunction],
"length": 1,
"listen": [MockFunction],
"location": Object {
"hash": "",
"key": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [MockFunction],
"replace": [MockFunction],
},
"navigateToApp": [MockFunction],
"navigateToUrl": [MockFunction],
"registerMountContext": [MockFunction],
@ -4741,6 +4779,25 @@ exports[`Header renders 3`] = `
},
"getComponent": [MockFunction],
"getUrlForApp": [MockFunction],
"history": Object {
"action": "PUSH",
"block": [MockFunction],
"createHref": [MockFunction],
"go": [MockFunction],
"goBack": [MockFunction],
"goForward": [MockFunction],
"length": 1,
"listen": [MockFunction],
"location": Object {
"hash": "",
"key": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [MockFunction],
"replace": [MockFunction],
},
"navigateToApp": [MockFunction],
"navigateToUrl": [MockFunction],
"registerMountContext": [MockFunction],
@ -9897,6 +9954,25 @@ exports[`Header renders 4`] = `
},
"getComponent": [MockFunction],
"getUrlForApp": [MockFunction],
"history": Object {
"action": "PUSH",
"block": [MockFunction],
"createHref": [MockFunction],
"go": [MockFunction],
"goBack": [MockFunction],
"goForward": [MockFunction],
"length": 1,
"listen": [MockFunction],
"location": Object {
"hash": "",
"key": "",
"pathname": "/",
"search": "",
"state": undefined,
},
"push": [MockFunction],
"replace": [MockFunction],
},
"navigateToApp": [MockFunction],
"navigateToUrl": [MockFunction],
"registerMountContext": [MockFunction],

View file

@ -0,0 +1,31 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { CoreApp } from './core_app';
type CoreAppContract = PublicMethodsOf<CoreApp>;
const createMock = (): jest.Mocked<CoreAppContract> => ({
setup: jest.fn(),
start: jest.fn(),
stop: jest.fn(),
});
export const coreAppMock = {
create: createMock,
};

View file

@ -0,0 +1,83 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { UnregisterCallback } from 'history';
import {
InternalApplicationSetup,
InternalApplicationStart,
AppNavLinkStatus,
AppMountParameters,
} from '../application';
import { HttpSetup, HttpStart } from '../http';
import { CoreContext } from '../core_system';
import { renderApp, setupUrlOverflowDetection } from './errors';
import { NotificationsStart } from '../notifications';
import { IUiSettingsClient } from '../ui_settings';
interface SetupDeps {
application: InternalApplicationSetup;
http: HttpSetup;
}
interface StartDeps {
application: InternalApplicationStart;
http: HttpStart;
notifications: NotificationsStart;
uiSettings: IUiSettingsClient;
}
export class CoreApp {
private stopHistoryListening?: UnregisterCallback;
constructor(private readonly coreContext: CoreContext) {}
public setup({ http, application }: SetupDeps) {
application.register(this.coreContext.coreId, {
id: 'error',
title: 'App Error',
navLinkStatus: AppNavLinkStatus.hidden,
mount(params: AppMountParameters) {
// Do not use an async import here in order to ensure that network failures
// cannot prevent the error UI from displaying. This UI is tiny so an async
// import here is probably not useful anyways.
return renderApp(params, { basePath: http.basePath });
},
});
}
public start({ application, http, notifications, uiSettings }: StartDeps) {
if (!application.history) {
return;
}
this.stopHistoryListening = setupUrlOverflowDetection({
basePath: http.basePath,
history: application.history,
toasts: notifications.toasts,
uiSettings,
});
}
public stop() {
if (this.stopHistoryListening) {
this.stopHistoryListening();
this.stopHistoryListening = undefined;
}
}
}

View file

@ -0,0 +1,61 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { act } from 'react-dom/test-utils';
import { History, createMemoryHistory } from 'history';
import { IBasePath } from '../../http';
import { BasePath } from '../../http/base_path';
import { renderApp } from './error_application';
describe('renderApp', () => {
let basePath: IBasePath;
let element: HTMLDivElement;
let history: History;
let unmount: any;
beforeEach(() => {
basePath = new BasePath();
element = document.createElement('div');
history = createMemoryHistory();
unmount = renderApp({ element, history } as any, { basePath });
});
afterEach(() => unmount());
it('renders generic errors', () => {
act(() => {
history.push('/app/error');
});
// innerText not working in jsdom, so use innerHTML
expect(element.querySelector('.euiTitle')!.innerHTML).toMatchInlineSnapshot(
`"Application error"`
);
});
it('renders urlOverflow errors', () => {
act(() => {
history.push('/app/error?errorType=urlOverflow');
});
expect(element.querySelector('.euiTitle')!.innerHTML).toMatchInlineSnapshot(
`"The URL for this object is too long, and we can't display it"`
);
expect(element.innerHTML).toMatch('Things to try');
});
});

View file

@ -0,0 +1,102 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { ReactChild, useState, useLayoutEffect } from 'react';
import ReactDOM from 'react-dom';
import { History } from 'history';
import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n/react';
import { EuiEmptyPrompt, EuiPage, EuiPageBody, EuiPageContent } from '@elastic/eui';
import { UrlOverflowUi } from './url_overflow_ui';
import { IBasePath } from '../../http';
import { AppMountParameters } from '../../application';
interface Props {
title?: string;
children?: ReactChild;
}
const ErrorPage: React.FC<Props> = ({ title, children }) => {
title =
title ??
i18n.translate('core.application.appRenderError.defaultTitle', {
defaultMessage: 'Application error',
});
return (
<EuiPage style={{ minHeight: '100%' }} data-test-subj="appRenderErrorPageContent">
<EuiPageBody>
<EuiPageContent verticalPosition="center" horizontalPosition="center">
<EuiEmptyPrompt
iconType="alert"
iconColor="danger"
title={<h2>{title}</h2>}
body={children}
/>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
};
const ErrorApp: React.FC<{ basePath: IBasePath; history: History }> = ({ basePath, history }) => {
const [currentLocation, setCurrentLocation] = useState(history.location);
useLayoutEffect(() => {
return history.listen((location) => setCurrentLocation(location));
}, [history]);
const searchParams = new URLSearchParams(currentLocation.search);
const errorType = searchParams.get('errorType');
if (errorType === 'urlOverflow') {
return (
<ErrorPage
title={i18n.translate('core.ui.errorUrlOverflow.errorTitle', {
defaultMessage: "The URL for this object is too long, and we can't display it",
})}
>
<UrlOverflowUi basePath={basePath} />
</ErrorPage>
);
}
return <ErrorPage />;
};
interface Deps {
basePath: IBasePath;
}
/**
* Renders UI for displaying error messages.
* @internal
*/
export const renderApp = ({ element, history }: AppMountParameters, { basePath }: Deps) => {
ReactDOM.render(
<I18nProvider>
<ErrorApp history={history} basePath={basePath} />
</I18nProvider>,
element
);
return () => {
ReactDOM.unmountComponentAtNode(element);
};
};

View file

@ -0,0 +1,21 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { renderApp } from './error_application';
export { setupUrlOverflowDetection, URL_MAX_LENGTH } from './url_overflow';

View file

@ -0,0 +1,127 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { createMemoryHistory, History } from 'history';
import { BasePath } from '../../http/base_path';
import { notificationServiceMock } from '../../notifications/notifications_service.mock';
import { uiSettingsServiceMock } from '../../ui_settings/ui_settings_service.mock';
import { IBasePath } from '../../http';
import { IToasts } from '../../notifications';
import { IUiSettingsClient } from '../../ui_settings';
import { setupUrlOverflowDetection, URL_MAX_LENGTH, URL_WARNING_LENGTH } from './url_overflow';
const longUrl = '/' + 'a'.repeat(URL_MAX_LENGTH);
describe('url overflow detection', () => {
let basePath: IBasePath;
let history: History;
let toasts: jest.Mocked<IToasts>;
let uiSettings: jest.Mocked<IUiSettingsClient>;
let assignSpy: jest.SpyInstance<void, [string]>;
let unlisten: any;
beforeEach(() => {
basePath = new BasePath('/test-123');
history = createMemoryHistory();
toasts = notificationServiceMock.createStartContract().toasts;
uiSettings = uiSettingsServiceMock.createStartContract();
// No-op mock impl to avoid jsdom warning about navigation not being implemented
assignSpy = jest.spyOn(window.location, 'assign').mockImplementation(() => {});
unlisten = setupUrlOverflowDetection({
basePath,
history,
toasts,
uiSettings,
});
});
afterEach(() => {
unlisten();
assignSpy.mockRestore();
});
it('redirects to error page when URL is too long', () => {
history.push(longUrl);
expect(assignSpy).toHaveBeenCalledWith('/app/error?errorType=urlOverflow');
});
it('displays a toast if URL exceeds warning threshold', () => {
const warningUrl = '/' + 'a'.repeat(URL_WARNING_LENGTH);
history.push(warningUrl);
expect(history.location.pathname).toEqual(warningUrl);
expect(assignSpy).not.toHaveBeenCalled();
expect(toasts.addWarning).toHaveBeenCalledWith(
expect.objectContaining({
title: 'The URL is big and Kibana might stop working',
text: expect.any(Function),
})
);
// Verify toast can be rendered correctly
const { text: mountToast } = toasts.addWarning.mock.calls[0][0] as any;
const element = document.createElement('div');
const unmount = mountToast(element);
expect(element).toMatchInlineSnapshot(`
<div>
Either enable the
<code>
state:storeInSessionStorage
</code>
option in
<a
href="/test-123/app/management/kibana/settings"
>
advanced settings
</a>
or simplify the onscreen visuals.
</div>
`);
unmount();
});
it('does not redirect or show warning if URL is not too long', () => {
history.push('/regular-length-url');
expect(history.location.pathname).toEqual('/regular-length-url');
expect(assignSpy).not.toHaveBeenCalled();
expect(toasts.addWarning).not.toHaveBeenCalled();
});
it('does not redirect or show warning if state:storeInSessionStorage is set', () => {
uiSettings.get.mockReturnValue(true);
history.push(longUrl);
expect(history.location.pathname).toEqual(longUrl);
expect(assignSpy).not.toHaveBeenCalled();
expect(toasts.addWarning).not.toHaveBeenCalled();
});
it('does not redirect or show warning if already on the error page', () => {
history.push('/app/error');
const longQueryParam = 'a'.repeat(URL_MAX_LENGTH);
const longErrorUrl = `/app/error?q=${longQueryParam}`;
history.push(longErrorUrl);
expect(history.location.pathname).toEqual('/app/error');
expect(history.location.search).toEqual(`?q=${longQueryParam}`);
expect(assignSpy).not.toHaveBeenCalled();
expect(toasts.addWarning).not.toHaveBeenCalled();
});
});

View file

@ -0,0 +1,96 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { History, Location } from 'history';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { mountReactNode } from '../../utils';
import { IToasts } from '../../notifications';
import { IBasePath } from '../../http';
import { IUiSettingsClient } from '../../ui_settings';
const IE_REGEX = /(; ?MSIE |Edge\/\d|Trident\/[\d+\.]+;.*rv:*11\.\d+)/;
export const IS_IE = IE_REGEX.test(window.navigator.userAgent);
/**
* The max URL length allowed by the current browser. Should be used to display warnings to users when query parameters
* cause URL to exceed this limit.
* @public
*/
export const URL_MAX_LENGTH = IS_IE ? 2000 : 25000;
export const URL_WARNING_LENGTH = IS_IE ? 1000 : 24000;
const ERROR_ROUTE = '/app/error';
interface Deps {
basePath: IBasePath;
history: History;
toasts: IToasts;
uiSettings: IUiSettingsClient;
}
export const setupUrlOverflowDetection = ({ basePath, history, toasts, uiSettings }: Deps) =>
history.listen((location: Location) => {
// Bail if storeInSessionStorage is set or we're already on the error page
if (
uiSettings.get('state:storeInSessionStorage') ||
history.location.pathname.startsWith(ERROR_ROUTE)
) {
return;
}
const absUrl = history.createHref(location);
const absUrlLength = absUrl.length;
if (absUrlLength > URL_MAX_LENGTH) {
const href = history.createHref({
pathname: ERROR_ROUTE,
search: `errorType=urlOverflow`,
});
// Force the browser to reload so that any potentially unstable state is unloaded
window.location.assign(href);
// window.location.href = href;
// window.location.reload();
} else if (absUrlLength >= URL_WARNING_LENGTH) {
toasts.addWarning({
title: i18n.translate('core.ui.errorUrlOverflow.bigUrlWarningNotificationTitle', {
defaultMessage: 'The URL is big and Kibana might stop working',
}),
text: mountReactNode(
<FormattedMessage
id="core.ui.errorUrlOverflow.bigUrlWarningNotificationMessage"
defaultMessage="Either enable the {storeInSessionStorageParam} option
in {advancedSettingsLink} or simplify the onscreen visuals."
values={{
storeInSessionStorageParam: <code>state:storeInSessionStorage</code>,
advancedSettingsLink: (
<a href={basePath.prepend('/app/management/kibana/settings')}>
<FormattedMessage
id="core.ui.errorUrlOverflow.bigUrlWarningNotificationMessage.advancedSettingsLinkText"
defaultMessage="advanced settings"
/>
</a>
),
}}
/>
),
});
}
});

View file

@ -0,0 +1,73 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiText } from '@elastic/eui';
import { IBasePath } from '../../http';
import { IS_IE } from './url_overflow';
export const UrlOverflowUi: React.FC<{ basePath: IBasePath }> = ({ basePath }) => {
return (
<EuiText textAlign="left">
<p>
<FormattedMessage
id="core.ui.errorUrlOverflow.optionsToFixErrorDescription"
defaultMessage="Things to try:"
/>
</p>
<ul>
<li>
<FormattedMessage
id="core.ui.errorUrlOverflow.optionsToFixError.enableOptionText"
defaultMessage="Enable the {storeInSessionStorageConfig} option in {kibanaSettingsLink}."
values={{
storeInSessionStorageConfig: <code>state:storeInSessionStorage</code>,
kibanaSettingsLink: (
<a href={basePath.prepend('/app/management/kibana/settings')}>
<FormattedMessage
id="core.ui.errorUrlOverflow.optionsToFixError.enableOptionText.advancedSettingsLinkText"
defaultMessage="Advanced Settings"
/>
</a>
),
}}
/>
</li>
<li>
<FormattedMessage
id="core.ui.errorUrlOverflow.optionsToFixError.removeStuffFromDashboardText"
defaultMessage="Simplify the object you are editing by removing content or filters."
/>
</li>
{IS_IE && (
<li>
<FormattedMessage
id="core.ui.errorUrlOverflow.optionsToFixError.doNotUseIEText"
defaultMessage="Upgrade to a modern browser. Every other supported browser we know of doesn't have this limit."
/>
</li>
)}
</ul>
</EuiText>
);
};

View file

@ -17,5 +17,5 @@
* under the License.
*/
import './error_url_overflow';
export { UrlOverflowService } from '../../../../plugins/kibana_legacy/public';
export { CoreApp } from './core_app';
export { URL_MAX_LENGTH } from './errors';

View file

@ -32,6 +32,7 @@ import { docLinksServiceMock } from './doc_links/doc_links_service.mock';
import { renderingServiceMock } from './rendering/rendering_service.mock';
import { contextServiceMock } from './context/context_service.mock';
import { integrationsServiceMock } from './integrations/integrations_service.mock';
import { coreAppMock } from './core_app/core_app.mock';
export const MockLegacyPlatformService = legacyPlatformServiceMock.create();
export const LegacyPlatformServiceConstructor = jest
@ -136,3 +137,9 @@ export const IntegrationsServiceConstructor = jest
jest.doMock('./integrations', () => ({
IntegrationsService: IntegrationsServiceConstructor,
}));
export const MockCoreApp = coreAppMock.create();
export const CoreAppConstructor = jest.fn().mockImplementation(() => MockCoreApp);
jest.doMock('./core_app', () => ({
CoreApp: CoreAppConstructor,
}));

View file

@ -44,6 +44,8 @@ import {
MockContextService,
IntegrationsServiceConstructor,
MockIntegrationsService,
CoreAppConstructor,
MockCoreApp,
} from './core_system.test.mocks';
import { CoreSystem } from './core_system';
@ -88,6 +90,7 @@ describe('constructor', () => {
expect(OverlayServiceConstructor).toHaveBeenCalledTimes(1);
expect(RenderingServiceConstructor).toHaveBeenCalledTimes(1);
expect(IntegrationsServiceConstructor).toHaveBeenCalledTimes(1);
expect(CoreAppConstructor).toHaveBeenCalledTimes(1);
});
it('passes injectedMetadata param to InjectedMetadataService', () => {
@ -231,6 +234,11 @@ describe('#setup()', () => {
await setupCore();
expect(MockIntegrationsService.setup).toHaveBeenCalledTimes(1);
});
it('calls coreApp#setup()', async () => {
await setupCore();
expect(MockCoreApp.setup).toHaveBeenCalledTimes(1);
});
});
describe('#start()', () => {
@ -315,10 +323,15 @@ describe('#start()', () => {
});
});
it('calls start#setup()', async () => {
it('calls integrations#start()', async () => {
await startCore();
expect(MockIntegrationsService.start).toHaveBeenCalledTimes(1);
});
it('calls coreApp#start()', async () => {
await startCore();
expect(MockCoreApp.start).toHaveBeenCalledTimes(1);
});
});
describe('#stop()', () => {
@ -377,6 +390,14 @@ describe('#stop()', () => {
expect(MockIntegrationsService.stop).toHaveBeenCalled();
});
it('calls coreApp.stop()', () => {
const coreSystem = createCoreSystem();
expect(MockCoreApp.stop).not.toHaveBeenCalled();
coreSystem.stop();
expect(MockCoreApp.stop).toHaveBeenCalled();
});
it('clears the rootDomElement', async () => {
const rootDomElement = document.createElement('div');
const coreSystem = createCoreSystem({

View file

@ -43,6 +43,7 @@ import { SavedObjectsService } from './saved_objects';
import { ContextService } from './context';
import { IntegrationsService } from './integrations';
import { InternalApplicationSetup, InternalApplicationStart } from './application/types';
import { CoreApp } from './core_app';
interface Params {
rootDomElement: HTMLElement;
@ -99,6 +100,7 @@ export class CoreSystem {
private readonly rendering: RenderingService;
private readonly context: ContextService;
private readonly integrations: IntegrationsService;
private readonly coreApp: CoreApp;
private readonly rootDomElement: HTMLElement;
private readonly coreContext: CoreContext;
@ -142,6 +144,7 @@ export class CoreSystem {
this.context = new ContextService(this.coreContext);
this.plugins = new PluginsService(this.coreContext, injectedMetadata.uiPlugins);
this.coreApp = new CoreApp(this.coreContext);
this.legacy = new LegacyPlatformService({
requireLegacyFiles,
@ -177,6 +180,7 @@ export class CoreSystem {
]),
});
const application = this.application.setup({ context, http, injectedMetadata });
this.coreApp.setup({ application, http });
const core: InternalCoreSetup = {
application,
@ -245,6 +249,8 @@ export class CoreSystem {
uiSettings,
});
this.coreApp.start({ application, http, notifications, uiSettings });
application.registerMountContext(this.coreContext.coreId, 'core', () => ({
application: pick(application, ['capabilities', 'navigateToApp']),
chrome,
@ -308,6 +314,7 @@ export class CoreSystem {
public stop() {
this.legacy.stop();
this.plugins.stop();
this.coreApp.stop();
this.notifications.stop();
this.http.stop();
this.integrations.stop();

View file

@ -192,6 +192,8 @@ export {
export { MountPoint, UnmountCallback, PublicUiSettingsParams } from './types';
export { URL_MAX_LENGTH } from './core_app';
/**
* Core services exposed to the `Plugin` setup lifecycle
*

View file

@ -1424,6 +1424,9 @@ export type UiSettingsType = 'undefined' | 'json' | 'markdown' | 'number' | 'sel
// @public
export type UnmountCallback = () => void;
// @public
export const URL_MAX_LENGTH: number;
// @public
export interface URLMeaningfulParts {
// (undocumented)

View file

@ -10,4 +10,3 @@
@import './accessibility/index';
@import './directives/index';
@import './error_url_overflow/index';

View file

@ -1,81 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from '@kbn/expect';
import { IE_REGEX } from '../../../../../plugins/kibana_legacy/public';
describe('IE_REGEX', () => {
it('should detect IE 9', () => {
const userAgent = 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)';
expect(IE_REGEX.test(userAgent)).to.be(true);
});
it('should detect IE 10', () => {
const userAgent =
'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)';
expect(IE_REGEX.test(userAgent)).to.be(true);
});
it('should detect IE 11', () => {
const userAgent =
'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; ' +
'.NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; rv:11.0) like Gecko';
expect(IE_REGEX.test(userAgent)).to.be(true);
});
it('should detect Edge', () => {
const userAgent =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
'(KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/13.10586';
expect(IE_REGEX.test(userAgent)).to.be(true);
});
it('should not detect Chrome on MacOS', () => {
const userAgent =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 ' +
'(KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36';
expect(IE_REGEX.test(userAgent)).to.be(false);
});
it('should not detect Chrome on Windows', () => {
const userAgent =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
'(KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36';
expect(IE_REGEX.test(userAgent)).to.be(false);
});
it('should not detect Safari on MacOS', () => {
const userAgent =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/603.2.4 ' +
'(KHTML, like Gecko) Version/10.1.1 Safari/603.2.4';
expect(IE_REGEX.test(userAgent)).to.be(false);
});
it('should not detect Firefox on MacOS', () => {
const userAgent =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:46.0) Gecko/20100101 Firefox/46.0';
expect(IE_REGEX.test(userAgent)).to.be(false);
});
it('should not detect Firefox on Windows', () => {
const userAgent =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:46.0) Gecko/20100101 Firefox/46.0';
expect(IE_REGEX.test(userAgent)).to.be(false);
});
});

View file

@ -1,3 +0,0 @@
.kbnError--url-overflow-app {
padding: $euiSizeL;
}

View file

@ -1 +0,0 @@
@import './error_url_overflow';

View file

@ -1,55 +0,0 @@
<div class="app-container kbnError--url-overflow-app euiText">
<h3>
<icon aria-hidden="true" type="'alert'" color="'danger'"></icon>
<span
i18n-id="common.ui.errorUrlOverflow.errorTitle"
i18n-default-message="Woah there!">
</span>
</h3>
<p
i18n-id="common.ui.errorUrlOverflow.errorDescription"
i18n-default-message="That's a big URL you have there. I have some unfortunate news: Your browser doesn't play nice
with Kibana's bacon-double-cheese-burger-with-extra-fries sized URL. To keep you from running
into problems Kibana limits URLs in your browser to {urlCharacterLimit} characters for your
safety."
i18n-values="{ urlCharacterLimit: controller.limit }"
></p>
<h3
i18n-id="common.ui.errorUrlOverflow.howTofixErrorTitle"
i18n-default-message="Ok, how do I fix this?"
></h3>
<p
i18n-id="common.ui.errorUrlOverflow.howTofixErrorDescription"
i18n-default-message="This usually only happens with big, complex dashboards, so you have some options:"
></p>
<ol>
<li
i18n-id="common.ui.errorUrlOverflow.howTofixError.enableOptionText"
i18n-default-message="Enable the {storeInSessionStorageConfig} option in the {kibanaSettingsLink}. This will prevent the URLs from
getting long, but makes them a bit less portable."
i18n-values="{
html_storeInSessionStorageConfig: '<code>state:storeInSessionStorage</code>',
html_kibanaSettingsLink: '<a href=#/management/kibana/settings>' + controller.advancedSettingsLabel + '</a>'
}"
></li>
<li
i18n-id="common.ui.errorUrlOverflow.howTofixError.removeStuffFromDashboardText"
i18n-default-message="Remove some stuff from your dashboard. This will reduce the length of the URL and keep IE in a good place."
></li>
<li
i18n-id="common.ui.errorUrlOverflow.howTofixError.doNotUseIEText"
i18n-default-message="Don't use IE. Every other supported browser we know of doesn't have this limit."
></li>
</ol>
<br />
<br />
<p>
<small
i18n-id="common.ui.errorUrlOverflow.footnoteText"
i18n-default-message="Footnote: Party size candy bars are tiny. Party size sub sandwiches are massive. Really makes you think."
></small>
</p>
</div>

View file

@ -1,58 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { i18n } from '@kbn/i18n';
import uiRoutes from '../routes';
import { KbnUrlProvider } from '../url';
import template from './error_url_overflow.html';
import { UrlOverflowService } from '../../../../plugins/kibana_legacy/public';
export { UrlOverflowService };
uiRoutes.when('/error/url-overflow', {
template,
k7Breadcrumbs: () => [
{
text: i18n.translate('common.ui.errorUrlOverflow.breadcrumbs.errorText', {
defaultMessage: 'Error',
}),
},
],
controllerAs: 'controller',
controller: class OverflowController {
constructor(Private, $scope) {
const kbnUrl = Private(KbnUrlProvider);
const urlOverflow = new UrlOverflowService();
if (!urlOverflow.get()) {
kbnUrl.redirectPath('/');
return;
}
this.url = urlOverflow.get();
this.limit = urlOverflow.failLength();
this.advancedSettingsLabel = i18n.translate(
'common.ui.errorUrlOverflow.howTofixError.enableOptionText.advancedSettingsLinkText',
{ defaultMessage: 'advanced settings' }
);
$scope.$on('$destroy', () => urlOverflow.clear());
}
},
});

View file

@ -18,32 +18,23 @@
*/
import {
auto,
ICompileProvider,
IHttpProvider,
IHttpService,
ILocationProvider,
ILocationService,
IModule,
IRootScopeService,
} from 'angular';
import $ from 'jquery';
import { cloneDeep, forOwn, get, set } from 'lodash';
import React, { Fragment } from 'react';
import * as Rx from 'rxjs';
import { ChromeBreadcrumb, EnvironmentMode, PackageInfo } from 'kibana/public';
import { History } from 'history';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { CoreStart, LegacyCoreStart } from 'kibana/public';
import { modifyUrl } from '../../../../core/public';
import { toMountPoint } from '../../../kibana_react/public';
import { isSystemApiRequest, UrlOverflowService } from '../utils';
import { isSystemApiRequest } from '../utils';
import { formatAngularHttpError, isAngularHttpError } from '../notify/lib';
const URL_LIMIT_WARN_WITHIN = 1000;
export interface RouteConfiguration {
controller?: string | ((...args: any[]) => void);
redirectTo?: string;
@ -127,7 +118,6 @@ export const configureAppAngularModule = (
.run($setupBreadcrumbsAutoClear(core, isLocalAngular))
.run($setupBadgeAutoClear(core, isLocalAngular))
.run($setupHelpExtensionAutoClear(core, isLocalAngular))
.run($setupUrlOverflowHandling(core, isLocalAngular))
.run($setupUICapabilityRedirect(core));
};
@ -390,68 +380,3 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart, isLocalAngular: bo
newPlatform.chrome.setHelpExtension(current.helpExtension);
});
};
const $setupUrlOverflowHandling = (newPlatform: CoreStart, isLocalAngular: boolean) => (
$location: ILocationService,
$rootScope: IRootScopeService,
$injector: auto.IInjectorService
) => {
const $route = $injector.has('$route') ? $injector.get('$route') : {};
const urlOverflow = new UrlOverflowService();
const check = () => {
if (isDummyRoute($route, isLocalAngular)) {
return;
}
// disable long url checks when storing state in session storage
if (newPlatform.uiSettings.get('state:storeInSessionStorage')) {
return;
}
if ($location.path() === '/error/url-overflow') {
return;
}
try {
if (urlOverflow.check($location.absUrl()) <= URL_LIMIT_WARN_WITHIN) {
newPlatform.notifications.toasts.addWarning({
title: i18n.translate('kibana_legacy.bigUrlWarningNotificationTitle', {
defaultMessage: 'The URL is big and Kibana might stop working',
}),
text: toMountPoint(
<Fragment>
<FormattedMessage
id="kibana_legacy.bigUrlWarningNotificationMessage"
defaultMessage="Either enable the {storeInSessionStorageParam} option
in {advancedSettingsLink} or simplify the onscreen visuals."
values={{
storeInSessionStorageParam: <code>state:storeInSessionStorage</code>,
advancedSettingsLink: (
<a
href={newPlatform.application.getUrlForApp('management', {
path: 'kibana/settings',
})}
>
<FormattedMessage
id="kibana_legacy.bigUrlWarningNotificationMessage.advancedSettingsLinkText"
defaultMessage="advanced settings"
/>
</a>
),
}}
/>
</Fragment>
),
});
}
} catch (e) {
window.location.href = modifyUrl(window.location.href, (parts: any) => {
parts.hash = '#/error/url-overflow';
});
// force the browser to reload to that Kibana's potentially unstable state is unloaded
window.location.reload();
}
};
$rootScope.$on('$routeUpdate', check);
$rootScope.$on('$routeChangeStart', check);
};

View file

@ -19,7 +19,6 @@
export * from './migrate_legacy_query';
export * from './system_api';
export * from './url_overflow_service';
// @ts-ignore
export { KbnAccessibleClickProvider } from './kbn_accessible_click';
// @ts-ignore

View file

@ -1,86 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
const URL_MAX_IE = 2000;
const URL_MAX_OTHERS = 25000;
export const IE_REGEX = /(; ?MSIE |Edge\/\d|Trident\/[\d+\.]+;.*rv:*11\.\d+)/;
export class UrlOverflowService {
private readonly _ieLike: boolean;
private _val?: string | null;
private readonly _sync: () => void;
constructor() {
const key = 'error/url-overflow/url';
const store = window.sessionStorage || {
getItem() {},
setItem() {},
removeItem() {},
};
// FIXME: Couldn't find a way to test for browser compatibility without
// complex redirect and cookie based "feature-detection" page, so going
// with user-agent detection for now.
this._ieLike = IE_REGEX.test(window.navigator.userAgent);
this._val = store.getItem(key);
this._sync = () => {
if (typeof this._val === 'string') {
store.setItem(key, this._val);
} else {
store.removeItem(key);
}
};
}
failLength() {
return this._ieLike ? URL_MAX_IE : URL_MAX_OTHERS;
}
set(v: string) {
this._val = v;
this._sync();
}
get() {
return this._val;
}
check(absUrl: string) {
if (!this.get()) {
const urlLength = absUrl.length;
const remaining = this.failLength() - urlLength;
if (remaining > 0) {
return remaining;
}
this.set(absUrl);
}
throw new Error(`
The URL has gotten too big and kibana can no longer
continue. Please refresh to return to your previous state.
`);
}
clear() {
this._val = undefined;
this._sync();
}
}

View file

@ -9,18 +9,16 @@ import { EuiIcon } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { URL_MAX_LENGTH } from '../../../../../../../src/core/public';
import { createSpatialFilterWithGeometry } from '../../../elasticsearch_geo_utils';
import { GEO_JSON_TYPE } from '../../../../common/constants';
import { GeometryFilterForm } from '../../../components/geometry_filter_form';
import { UrlOverflowService } from '../../../../../../../src/plugins/kibana_legacy/public';
import rison from 'rison-node';
// over estimated and imprecise value to ensure filter has additional room for any meta keys added when filter is mapped.
const META_OVERHEAD = 100;
const urlOverflow = new UrlOverflowService();
export class FeatureGeometryFilterForm extends Component {
state = {
isLoading: false,
@ -82,7 +80,7 @@ export class FeatureGeometryFilterForm extends Component {
// No elasticsearch support for pre-indexed shapes and geo_point spatial queries.
if (
window.location.href.length + rison.encode(filter).length + META_OVERHEAD >
urlOverflow.failLength()
URL_MAX_LENGTH
) {
this.setState({
errorMsg: i18n.translate('xpack.maps.tooltip.geometryFilterForm.filterTooLargeMessage', {

View file

@ -138,16 +138,6 @@
"charts.controls.rangeErrorMessage": "値は{min}と{max}の間でなければなりません",
"charts.controls.vislibBasicOptions.legendPositionLabel": "凡例位置",
"charts.controls.vislibBasicOptions.showTooltipLabel": "ツールヒントを表示",
"common.ui.errorUrlOverflow.breadcrumbs.errorText": "エラー",
"common.ui.errorUrlOverflow.errorDescription": "とても長い URL ですね。残念なお知らせがあります。ご使用のブラウザは Kibana の超巨大 URL に対応していません。問題を避けるため、Kibana はご使用のブラウザでの URL を {urlCharacterLimit} 文字に制限します。",
"common.ui.errorUrlOverflow.errorTitle": "おっと!",
"common.ui.errorUrlOverflow.footnoteText": "脚注:パーティーサイズのキャンディバーはほんのちょこっと。パーティーサイズのロールサンドイッチは巨大。不思議なものですね。",
"common.ui.errorUrlOverflow.howTofixError.doNotUseIEText": "IE は避けましょう。他の対応ブラウザにはこの制限がありません。",
"common.ui.errorUrlOverflow.howTofixError.enableOptionText": "{kibanaSettingsLink} の {storeInSessionStorageConfig} オプションを有効にしてください。これにより URL が長くなるのを避けられますが、若干ポータビリティが損なわれます。",
"common.ui.errorUrlOverflow.howTofixError.enableOptionText.advancedSettingsLinkText": "高度な設定",
"common.ui.errorUrlOverflow.howTofixError.removeStuffFromDashboardText": "ダッシュボードからいくつか項目を取り除きましょう。これにより URL が短くなり、IE の動作が改善されます。",
"common.ui.errorUrlOverflow.howTofixErrorDescription": "これは大抵大規模で複雑なダッシュボードで起こるため、いくつかのオプションがあります。",
"common.ui.errorUrlOverflow.howTofixErrorTitle": "どうすれば良いのでしょう?",
"common.ui.flotCharts.aprLabel": "4 月",
"common.ui.flotCharts.augLabel": "8 月",
"common.ui.flotCharts.decLabel": "12 月",
@ -2181,9 +2171,9 @@
"kbn.advancedSettings.visualization.tileMap.wmsDefaultsTitle": "デフォルトの WMS プロパティ",
"visualizations.advancedSettings.visualizeEnableLabsText": "ユーザーが実験的なビジュアライゼーションを作成、表示、編集できるようになります。無効の場合、\n ユーザーは本番準備が整ったビジュアライゼーションのみを利用できます。",
"visualizations.advancedSettings.visualizeEnableLabsTitle": "実験的なビジュアライゼーションを有効にする",
"kibana_legacy.bigUrlWarningNotificationMessage": "{advancedSettingsLink}で{storeInSessionStorageParam}オプションを有効にするか、オンスクリーンビジュアルを簡素化してください。",
"kibana_legacy.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高度な設定",
"kibana_legacy.bigUrlWarningNotificationTitle": "URLが大きく、Kibanaの動作が停止する可能性があります",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationMessage": "{advancedSettingsLink}で{storeInSessionStorageParam}オプションを有効にするか、オンスクリーンビジュアルを簡素化してください。",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高度な設定",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationTitle": "URLが大きく、Kibanaの動作が停止する可能性があります",
"kibana_legacy.notify.fatalError.errorStatusMessage": "エラー {errStatus} {errStatusText}: {errMessage}",
"kibana_legacy.notify.fatalError.unavailableServerErrorMessage": "HTTP リクエストで接続に失敗しました。Kibana サーバーが実行されていて、ご使用のブラウザの接続が正常に動作していることを確認するか、システム管理者にお問い合わせください。",
"kibana_legacy.notify.toaster.errorMessage": "エラー: {errorMessage}\n {errorStack}",

View file

@ -138,16 +138,6 @@
"charts.controls.rangeErrorMessage": "值必须是在 {min} 到 {max} 的范围内",
"charts.controls.vislibBasicOptions.legendPositionLabel": "图例位置",
"charts.controls.vislibBasicOptions.showTooltipLabel": "显示工具提示",
"common.ui.errorUrlOverflow.breadcrumbs.errorText": "错误",
"common.ui.errorUrlOverflow.errorDescription": "您的 URL 真不小。我有一些不幸的消息:您的浏览器与 Kibana 的超长 URL 不太兼容。为了避免您遇到问题Kibana 在您的浏览器中将 URL 长度限制在 {urlCharacterLimit} 个字符。",
"common.ui.errorUrlOverflow.errorTitle": "喔哦!",
"common.ui.errorUrlOverflow.footnoteText": "脚注:聚会供应的糖果真够小的。聚会供应的小三明治真够大的。确实得让您好好想一想。",
"common.ui.errorUrlOverflow.howTofixError.doNotUseIEText": "切勿使用 IE。我们了解的所有其他受支持浏览器都没有这个限制。",
"common.ui.errorUrlOverflow.howTofixError.enableOptionText": "在 {kibanaSettingsLink} 中启用 {storeInSessionStorageConfig} 选项。这会阻止 URL 变长,但会使它们的可移植性差点。",
"common.ui.errorUrlOverflow.howTofixError.enableOptionText.advancedSettingsLinkText": "高级设置",
"common.ui.errorUrlOverflow.howTofixError.removeStuffFromDashboardText": "从您的仪表板中删除一些内容。这回减小 URL 的长度,使 IE 能够处理它。",
"common.ui.errorUrlOverflow.howTofixErrorDescription": "通常只有较大的、复杂的仪表板会发生此问题,因此您会有一些选项:",
"common.ui.errorUrlOverflow.howTofixErrorTitle": "那么,我如何解决此问题?",
"common.ui.flotCharts.aprLabel": "四月",
"common.ui.flotCharts.augLabel": "八月",
"common.ui.flotCharts.decLabel": "十二月",
@ -2184,9 +2174,9 @@
"kbn.advancedSettings.visualization.tileMap.wmsDefaultsTitle": "默认 WMS 属性",
"visualizations.advancedSettings.visualizeEnableLabsText": "允许用户创建、查看和编辑实验性可视化。如果禁用,\n 仅被视为生产就绪的可视化可供用户使用。",
"visualizations.advancedSettings.visualizeEnableLabsTitle": "启用实验性可视化",
"kibana_legacy.bigUrlWarningNotificationMessage": "在{advancedSettingsLink}中启用“{storeInSessionStorageParam}”选项或简化屏幕视觉效果。",
"kibana_legacy.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高级设置",
"kibana_legacy.bigUrlWarningNotificationTitle": "URL 过长Kibana 可能无法工作",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationMessage": "在{advancedSettingsLink}中启用“{storeInSessionStorageParam}”选项或简化屏幕视觉效果。",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高级设置",
"core.ui.errorUrlOverflow.bigUrlWarningNotificationTitle": "URL 过长Kibana 可能无法工作",
"kibana_legacy.notify.fatalError.errorStatusMessage": "错误 {errStatus} {errStatusText}{errMessage}",
"kibana_legacy.notify.fatalError.unavailableServerErrorMessage": "HTTP 请求无法连接。请检查 Kibana 服务器是否正在运行以及您的浏览器是否具有有效的连接,或请联系您的系统管理员。",
"kibana_legacy.notify.toaster.errorMessage": "错误:{errorMessage}\n {errorStack}",