mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
parent
cc2d248294
commit
77f74ec571
33 changed files with 774 additions and 395 deletions
|
@ -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 |
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [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
|
||||
```
|
|
@ -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(),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -301,6 +301,7 @@ export class ApplicationService {
|
|||
distinctUntilChanged(),
|
||||
takeUntil(this.stop$)
|
||||
),
|
||||
history: this.history,
|
||||
registerMountContext: this.mountContext.registerContext,
|
||||
getUrlForApp: (
|
||||
appId,
|
||||
|
|
|
@ -43,5 +43,6 @@ export {
|
|||
PublicAppInfo,
|
||||
PublicLegacyAppInfo,
|
||||
// Internal types
|
||||
InternalApplicationSetup,
|
||||
InternalApplicationStart,
|
||||
} from './types';
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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],
|
||||
|
|
31
src/core/public/core_app/core_app.mock.ts
Normal file
31
src/core/public/core_app/core_app.mock.ts
Normal 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,
|
||||
};
|
83
src/core/public/core_app/core_app.ts
Normal file
83
src/core/public/core_app/core_app.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
61
src/core/public/core_app/errors/error_application.test.ts
Normal file
61
src/core/public/core_app/errors/error_application.test.ts
Normal 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');
|
||||
});
|
||||
});
|
102
src/core/public/core_app/errors/error_application.tsx
Normal file
102
src/core/public/core_app/errors/error_application.tsx
Normal 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);
|
||||
};
|
||||
};
|
21
src/core/public/core_app/errors/index.ts
Normal file
21
src/core/public/core_app/errors/index.ts
Normal 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';
|
127
src/core/public/core_app/errors/url_overflow.test.ts
Normal file
127
src/core/public/core_app/errors/url_overflow.test.ts
Normal 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();
|
||||
});
|
||||
});
|
96
src/core/public/core_app/errors/url_overflow.tsx
Normal file
96
src/core/public/core_app/errors/url_overflow.tsx
Normal 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>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
});
|
73
src/core/public/core_app/errors/url_overflow_ui.tsx
Normal file
73
src/core/public/core_app/errors/url_overflow_ui.tsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -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';
|
|
@ -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,
|
||||
}));
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -10,4 +10,3 @@
|
|||
|
||||
@import './accessibility/index';
|
||||
@import './directives/index';
|
||||
@import './error_url_overflow/index';
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -1,3 +0,0 @@
|
|||
.kbnError--url-overflow-app {
|
||||
padding: $euiSizeL;
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
@import './error_url_overflow';
|
|
@ -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>
|
|
@ -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());
|
||||
}
|
||||
},
|
||||
});
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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', {
|
||||
|
|
|
@ -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}",
|
||||
|
|
|
@ -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}",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue