Remove react references from core Notifications apis (#49573)

* add reactMount util to kibana_react

(kibana-react) properly export reactMount

* add MountPoint types and utility

* adapt toast API to no longer accept react elements

(toast API) properly export new Toast type

* adapt calls by using reactMount

createNotifications: do not wrap if text

* update generated doc

* add custom snapshot serializer for reactMount

* fix unit tests

fix xpack unit tests

* adapt non-ts calls

* do not add __reactMount__ property in production

* remove string check on createNotifications

* fix typo and small fix using obj spread

* improve react mount snapshot serializer

* simplify convertToEui

* rename reactMount to toMountPoint

* adapt newly added calls

* move mount types to proper file

* use new Mount types for OverlayBanner apis

* fixing typo

* adapt new calls

* use destructured imports
This commit is contained in:
Pierre Gayvallet 2019-11-14 08:40:14 +01:00 committed by GitHub
parent cd67addf15
commit e04adbe34b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 597 additions and 339 deletions

View file

@ -109,17 +109,18 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [HttpStart](./kibana-plugin-public.httpstart.md) | See [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) |
| [IContextProvider](./kibana-plugin-public.icontextprovider.md) | A function that returns a context value for a specific key of given context type. |
| [IToasts](./kibana-plugin-public.itoasts.md) | Methods for adding and removing global toast messages. See [ToastsApi](./kibana-plugin-public.toastsapi.md)<!-- -->. |
| [OverlayBannerMount](./kibana-plugin-public.overlaybannermount.md) | A function that will mount the banner inside the provided element. |
| [OverlayBannerUnmount](./kibana-plugin-public.overlaybannerunmount.md) | A function that will unmount the banner from the element. |
| [MountPoint](./kibana-plugin-public.mountpoint.md) | A function that should mount DOM content inside the provided container element and return a handler to unmount it. |
| [PluginInitializer](./kibana-plugin-public.plugininitializer.md) | The <code>plugin</code> export at the root of a plugin's <code>public</code> directory should conform to this interface. |
| [PluginOpaqueId](./kibana-plugin-public.pluginopaqueid.md) | |
| [RecursiveReadonly](./kibana-plugin-public.recursivereadonly.md) | |
| [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value |
| [SavedObjectAttributeSingle](./kibana-plugin-public.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) |
| [SavedObjectsClientContract](./kibana-plugin-public.savedobjectsclientcontract.md) | SavedObjectsClientContract as implemented by the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) |
| [Toast](./kibana-plugin-public.toast.md) | |
| [ToastInput](./kibana-plugin-public.toastinput.md) | Inputs for [IToasts](./kibana-plugin-public.itoasts.md) APIs. |
| [ToastInputFields](./kibana-plugin-public.toastinputfields.md) | Allowed fields for [ToastInput](./kibana-plugin-public.toastinput.md)<!-- -->. |
| [ToastsSetup](./kibana-plugin-public.toastssetup.md) | [IToasts](./kibana-plugin-public.itoasts.md) |
| [ToastsStart](./kibana-plugin-public.toastsstart.md) | [IToasts](./kibana-plugin-public.itoasts.md) |
| [UiSettingsClientContract](./kibana-plugin-public.uisettingsclientcontract.md) | Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) |
| [UnmountCallback](./kibana-plugin-public.unmountcallback.md) | A function that will unmount the element previously mounted by the associated [MountPoint](./kibana-plugin-public.mountpoint.md) |

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [MountPoint](./kibana-plugin-public.mountpoint.md)
## MountPoint type
A function that should mount DOM content inside the provided container element and return a handler to unmount it.
<b>Signature:</b>
```typescript
export declare type MountPoint = (element: HTMLElement) => UnmountCallback;
```

View file

@ -1,13 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [OverlayBannerMount](./kibana-plugin-public.overlaybannermount.md)
## OverlayBannerMount type
A function that will mount the banner inside the provided element.
<b>Signature:</b>
```typescript
export declare type OverlayBannerMount = (element: HTMLElement) => OverlayBannerUnmount;
```

View file

@ -9,14 +9,14 @@ Add a new banner
<b>Signature:</b>
```typescript
add(mount: OverlayBannerMount, priority?: number): string;
add(mount: MountPoint, priority?: number): string;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| mount | <code>OverlayBannerMount</code> | |
| mount | <code>MountPoint</code> | |
| priority | <code>number</code> | |
<b>Returns:</b>

View file

@ -9,7 +9,7 @@ Replace a banner in place
<b>Signature:</b>
```typescript
replace(id: string | undefined, mount: OverlayBannerMount, priority?: number): string;
replace(id: string | undefined, mount: MountPoint, priority?: number): string;
```
## Parameters
@ -17,7 +17,7 @@ replace(id: string | undefined, mount: OverlayBannerMount, priority?: number): s
| Parameter | Type | Description |
| --- | --- | --- |
| id | <code>string &#124; undefined</code> | |
| mount | <code>OverlayBannerMount</code> | |
| mount | <code>MountPoint</code> | |
| priority | <code>number</code> | |
<b>Returns:</b>

View file

@ -1,13 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [OverlayBannerUnmount](./kibana-plugin-public.overlaybannerunmount.md)
## OverlayBannerUnmount type
A function that will unmount the banner from the element.
<b>Signature:</b>
```typescript
export declare type OverlayBannerUnmount = () => void;
```

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [Toast](./kibana-plugin-public.toast.md)
## Toast type
<b>Signature:</b>
```typescript
export declare type Toast = ToastInputFields & {
id: string;
};
```

View file

@ -9,5 +9,5 @@ Inputs for [IToasts](./kibana-plugin-public.itoasts.md) APIs.
<b>Signature:</b>
```typescript
export declare type ToastInput = string | ToastInputFields | Promise<ToastInputFields>;
export declare type ToastInput = string | ToastInputFields;
```

View file

@ -9,7 +9,10 @@ Allowed fields for [ToastInput](./kibana-plugin-public.toastinput.md)<!-- -->.
<b>Signature:</b>
```typescript
export declare type ToastInputFields = Pick<Toast, Exclude<keyof Toast, 'id'>>;
export declare type ToastInputFields = Pick<EuiToast, Exclude<keyof EuiToast, 'id' | 'text' | 'title'>> & {
title?: string | MountPoint;
text?: string | MountPoint;
};
```
## Remarks

View file

@ -22,5 +22,5 @@ add(toastOrTitle: ToastInput): Toast;
`Toast`
a
a [Toast](./kibana-plugin-public.toast.md)

View file

@ -22,5 +22,5 @@ addDanger(toastOrTitle: ToastInput): Toast;
`Toast`
a
a [Toast](./kibana-plugin-public.toast.md)

View file

@ -23,5 +23,5 @@ addError(error: Error, options: ErrorToastOptions): Toast;
`Toast`
a
a [Toast](./kibana-plugin-public.toast.md)

View file

@ -22,5 +22,5 @@ addSuccess(toastOrTitle: ToastInput): Toast;
`Toast`
a
a [Toast](./kibana-plugin-public.toast.md)

View file

@ -22,5 +22,5 @@ addWarning(toastOrTitle: ToastInput): Toast;
`Toast`
a
a [Toast](./kibana-plugin-public.toast.md)

View file

@ -28,5 +28,5 @@ export declare class ToastsApi implements IToasts
| [addSuccess(toastOrTitle)](./kibana-plugin-public.toastsapi.addsuccess.md) | | Adds a new toast pre-configured with the success color and check icon. |
| [addWarning(toastOrTitle)](./kibana-plugin-public.toastsapi.addwarning.md) | | Adds a new toast pre-configured with the warning color and help icon. |
| [get$()](./kibana-plugin-public.toastsapi.get_.md) | | Observable of the toast messages to show to the user. |
| [remove(toast)](./kibana-plugin-public.toastsapi.remove.md) | | Removes a toast from the current array of toasts if present. |
| [remove(toastOrId)](./kibana-plugin-public.toastsapi.remove.md) | | Removes a toast from the current array of toasts if present. |

View file

@ -9,14 +9,14 @@ Removes a toast from the current array of toasts if present.
<b>Signature:</b>
```typescript
remove(toast: Toast): void;
remove(toastOrId: Toast | string): void;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| toast | <code>Toast</code> | a returned by |
| toastOrId | <code>Toast &#124; string</code> | a [Toast](./kibana-plugin-public.toast.md) returned by [ToastsApi.add()](./kibana-plugin-public.toastsapi.add.md) or its id |
<b>Returns:</b>

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [UnmountCallback](./kibana-plugin-public.unmountcallback.md)
## UnmountCallback type
A function that will unmount the element previously mounted by the associated [MountPoint](./kibana-plugin-public.mountpoint.md)
<b>Signature:</b>
```typescript
export declare type UnmountCallback = () => void;
```

View file

@ -117,13 +117,7 @@ export {
InterceptedHttpResponse,
} from './http';
export {
OverlayStart,
OverlayBannerMount,
OverlayBannerUnmount,
OverlayBannersStart,
OverlayRef,
} from './overlays';
export { OverlayStart, OverlayBannersStart, OverlayRef } from './overlays';
export {
Toast,
@ -136,6 +130,8 @@ export {
ErrorToastOptions,
} from './notifications';
export { MountPoint, UnmountCallback } from './types';
/**
* Core services exposed to the `Plugin` setup lifecycle
*

View file

@ -3,7 +3,7 @@
exports[`renders matching snapshot 1`] = `
<EuiGlobalToastList
data-test-subj="globalToastList"
dismissToast={[MockFunction]}
dismissToast={[Function]}
toastLifeTimeMs={Infinity}
toasts={Array []}
/>

View file

@ -57,9 +57,9 @@ it('subscribes to toasts$ on mount and unsubscribes on unmount', () => {
it('passes latest value from toasts$ to <EuiGlobalToastList />', () => {
const el = shallow(
render({
toasts$: Rx.from([[], [1], [1, 2]]) as any,
toasts$: Rx.from([[], [{ id: 1 }], [{ id: 1 }, { id: 2 }]]) as any,
})
);
expect(el.find(EuiGlobalToastList).prop('toasts')).toEqual([1, 2]);
expect(el.find(EuiGlobalToastList).prop('toasts')).toEqual([{ id: 1 }, { id: 2 }]);
});

View file

@ -17,20 +17,28 @@
* under the License.
*/
import { EuiGlobalToastList, EuiGlobalToastListToast as Toast } from '@elastic/eui';
import { EuiGlobalToastList, EuiGlobalToastListToast as EuiToast } from '@elastic/eui';
import React from 'react';
import * as Rx from 'rxjs';
import { MountWrapper } from '../../utils';
import { Toast } from './toasts_api';
interface Props {
toasts$: Rx.Observable<Toast[]>;
dismissToast: (t: Toast) => void;
dismissToast: (toastId: string) => void;
}
interface State {
toasts: Toast[];
}
const convertToEui = (toast: Toast): EuiToast => ({
...toast,
title: typeof toast.title === 'function' ? <MountWrapper mount={toast.title} /> : toast.title,
text: typeof toast.text === 'function' ? <MountWrapper mount={toast.text} /> : toast.text,
});
export class GlobalToastList extends React.Component<Props, State> {
public state: State = {
toasts: [],
@ -54,8 +62,8 @@ export class GlobalToastList extends React.Component<Props, State> {
return (
<EuiGlobalToastList
data-test-subj="globalToastList"
toasts={this.state.toasts}
dismissToast={this.props.dismissToast}
toasts={this.state.toasts.map(convertToEui)}
dismissToast={({ id }) => this.props.dismissToast(id)}
/**
* This prop is overriden by the individual toasts that are added.
* Use `Infinity` here so that it's obvious a timeout hasn't been

View file

@ -18,5 +18,11 @@
*/
export { ToastsService, ToastsSetup, ToastsStart } from './toasts_service';
export { ErrorToastOptions, ToastsApi, ToastInput, IToasts, ToastInputFields } from './toasts_api';
export { EuiGlobalToastListToast as Toast } from '@elastic/eui';
export {
ErrorToastOptions,
ToastsApi,
ToastInput,
IToasts,
ToastInputFields,
Toast,
} from './toasts_api';

View file

@ -91,7 +91,7 @@ describe('#get$()', () => {
toasts.add('foo');
onToasts.mockClear();
toasts.remove({ id: 'bar' });
toasts.remove('bar');
expect(onToasts).not.toHaveBeenCalled();
});
});
@ -136,7 +136,7 @@ describe('#remove()', () => {
it('ignores unknown toast', async () => {
const toasts = new ToastsApi(toastDeps());
toasts.add('Test');
toasts.remove({ id: 'foo' });
toasts.remove('foo');
const currentToasts = await getCurrentToasts(toasts);
expect(currentToasts).toHaveLength(1);

View file

@ -17,11 +17,13 @@
* under the License.
*/
import { EuiGlobalToastListToast as Toast } from '@elastic/eui';
import { EuiGlobalToastListToast as EuiToast } from '@elastic/eui';
import React from 'react';
import * as Rx from 'rxjs';
import { ErrorToast } from './error_toast';
import { MountPoint } from '../../types';
import { mountReactNode } from '../../utils';
import { UiSettingsClientContract } from '../../ui_settings';
import { OverlayStart } from '../../overlays';
@ -33,13 +35,20 @@ import { OverlayStart } from '../../overlays';
*
* @public
*/
export type ToastInputFields = Pick<Toast, Exclude<keyof Toast, 'id'>>;
export type ToastInputFields = Pick<EuiToast, Exclude<keyof EuiToast, 'id' | 'text' | 'title'>> & {
title?: string | MountPoint;
text?: string | MountPoint;
};
export type Toast = ToastInputFields & {
id: string;
};
/**
* Inputs for {@link IToasts} APIs.
* @public
*/
export type ToastInput = string | ToastInputFields | Promise<ToastInputFields>;
export type ToastInput = string | ToastInputFields;
/**
* Options available for {@link IToasts} APIs.
@ -59,13 +68,12 @@ export interface ErrorToastOptions {
toastMessage?: string;
}
const normalizeToast = (toastOrTitle: ToastInput) => {
const normalizeToast = (toastOrTitle: ToastInput): ToastInputFields => {
if (typeof toastOrTitle === 'string') {
return {
title: toastOrTitle,
};
}
return toastOrTitle;
};
@ -123,11 +131,12 @@ export class ToastsApi implements IToasts {
/**
* Removes a toast from the current array of toasts if present.
* @param toast - a {@link Toast} returned by {@link ToastApi.add}
* @param toastOrId - a {@link Toast} returned by {@link ToastsApi.add} or its id
*/
public remove(toast: Toast) {
public remove(toastOrId: Toast | string) {
const toRemove = typeof toastOrId === 'string' ? toastOrId : toastOrId.id;
const list = this.toasts$.getValue();
const listWithoutToast = list.filter(t => t !== toast);
const listWithoutToast = list.filter(t => t.id !== toRemove);
if (listWithoutToast.length !== list.length) {
this.toasts$.next(listWithoutToast);
}
@ -191,7 +200,7 @@ export class ToastsApi implements IToasts {
iconType: 'alert',
title: options.title,
toastLifeTimeMs: this.uiSettings.get('notifications:lifetime:error'),
text: (
text: mountReactNode(
<ErrorToast
openModal={this.openModal.bind(this)}
error={error}

View file

@ -20,7 +20,6 @@
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { EuiGlobalToastListToast as Toast } from '@elastic/eui';
import { I18nStart } from '../../i18n';
import { UiSettingsClientContract } from '../../ui_settings';
import { GlobalToastList } from './global_toast_list';
@ -65,7 +64,7 @@ export class ToastsService {
render(
<i18n.Context>
<GlobalToastList
dismissToast={(toast: Toast) => this.api!.remove(toast)}
dismissToast={(toastId: string) => this.api!.remove(toastId)}
toasts$={this.api!.get$()}
/>
</i18n.Context>,

View file

@ -25,33 +25,20 @@ import { PriorityMap } from './priority_map';
import { BannersList } from './banners_list';
import { UiSettingsClientContract } from '../../ui_settings';
import { I18nStart } from '../../i18n';
import { MountPoint } from '../../types';
import { UserBannerService } from './user_banner_service';
/**
* A function that will unmount the banner from the element.
* @public
*/
export type OverlayBannerUnmount = () => void;
/**
* A function that will mount the banner inside the provided element.
* @param element an element to render into
* @returns a {@link OverlayBannerUnmount}
* @public
*/
export type OverlayBannerMount = (element: HTMLElement) => OverlayBannerUnmount;
/** @public */
export interface OverlayBannersStart {
/**
* Add a new banner
*
* @param mount {@link OverlayBannerMount}
* @param mount {@link MountPoint}
* @param priority optional priority order to display this banner. Higher priority values are shown first.
* @returns a unique identifier for the given banner to be used with {@link OverlayBannersStart.remove} and
* {@link OverlayBannersStart.replace}
*/
add(mount: OverlayBannerMount, priority?: number): string;
add(mount: MountPoint, priority?: number): string;
/**
* Remove a banner
@ -65,12 +52,12 @@ export interface OverlayBannersStart {
* Replace a banner in place
*
* @param id the unique identifier for the banner returned by {@link OverlayBannersStart.add}
* @param mount {@link OverlayBannerMount}
* @param mount {@link MountPoint}
* @param priority optional priority order to display this banner. Higher priority values are shown first.
* @returns a new identifier for the given banner to be used with {@link OverlayBannersStart.remove} and
* {@link OverlayBannersStart.replace}
*/
replace(id: string | undefined, mount: OverlayBannerMount, priority?: number): string;
replace(id: string | undefined, mount: MountPoint, priority?: number): string;
/** @internal */
get$(): Observable<OverlayBanner[]>;
@ -80,7 +67,7 @@ export interface OverlayBannersStart {
/** @internal */
export interface OverlayBanner {
readonly id: string;
readonly mount: OverlayBannerMount;
readonly mount: MountPoint;
readonly priority: number;
}
@ -116,7 +103,7 @@ export class OverlayBannersService {
return true;
},
replace(id: string | undefined, mount: OverlayBannerMount, priority = 0) {
replace(id: string | undefined, mount: MountPoint, priority = 0) {
if (!id || !banners$.value.has(id)) {
return this.add(mount, priority);
}

View file

@ -17,9 +17,4 @@
* under the License.
*/
export {
OverlayBannerMount,
OverlayBannerUnmount,
OverlayBannersStart,
OverlayBannersService,
} from './banners_service';
export { OverlayBannersStart, OverlayBannersService } from './banners_service';

View file

@ -17,5 +17,5 @@
* under the License.
*/
export { OverlayBannerMount, OverlayBannerUnmount, OverlayBannersStart } from './banners';
export { OverlayBannersStart } from './banners';
export { OverlayService, OverlayStart, OverlayRef } from './overlay_service';

View file

@ -5,12 +5,12 @@
```ts
import { Breadcrumb } from '@elastic/eui';
import { EuiGlobalToastListToast } from '@elastic/eui';
import { IconType } from '@elastic/eui';
import { Observable } from 'rxjs';
import React from 'react';
import * as Rx from 'rxjs';
import { ShallowPromise } from '@kbn/utility-types';
import { EuiGlobalToastListToast as Toast } from '@elastic/eui';
import { UiSettingsParams as UiSettingsParams_2 } from 'src/core/server/types';
import { UserProvidedValues as UserProvidedValues_2 } from 'src/core/server/types';
@ -619,6 +619,9 @@ export interface LegacyNavLink {
url: string;
}
// @public
export type MountPoint = (element: HTMLElement) => UnmountCallback;
// @public (undocumented)
export interface NotificationsSetup {
// (undocumented)
@ -631,12 +634,9 @@ export interface NotificationsStart {
toasts: ToastsStart;
}
// @public
export type OverlayBannerMount = (element: HTMLElement) => OverlayBannerUnmount;
// @public (undocumented)
export interface OverlayBannersStart {
add(mount: OverlayBannerMount, priority?: number): string;
add(mount: MountPoint, priority?: number): string;
// Warning: (ae-forgotten-export) The symbol "OverlayBanner" needs to be exported by the entry point index.d.ts
//
// @internal (undocumented)
@ -644,12 +644,9 @@ export interface OverlayBannersStart {
// (undocumented)
getComponent(): JSX.Element;
remove(id: string): boolean;
replace(id: string | undefined, mount: OverlayBannerMount, priority?: number): string;
replace(id: string | undefined, mount: MountPoint, priority?: number): string;
}
// @public
export type OverlayBannerUnmount = () => void;
// @public
export interface OverlayRef {
close(): Promise<void>;
@ -917,35 +914,36 @@ export class SimpleSavedObject<T extends SavedObjectAttributes> {
_version?: SavedObject<T>['version'];
}
export { Toast }
// Warning: (ae-missing-release-tag) "Toast" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type Toast = ToastInputFields & {
id: string;
};
// @public
export type ToastInput = string | ToastInputFields | Promise<ToastInputFields>;
export type ToastInput = string | ToastInputFields;
// @public
export type ToastInputFields = Pick<Toast, Exclude<keyof Toast, 'id'>>;
export type ToastInputFields = Pick<EuiGlobalToastListToast, Exclude<keyof EuiGlobalToastListToast, 'id' | 'text' | 'title'>> & {
title?: string | MountPoint;
text?: string | MountPoint;
};
// @public
export class ToastsApi implements IToasts {
constructor(deps: {
uiSettings: UiSettingsClientContract;
});
// Warning: (ae-unresolved-link) The @link reference could not be resolved: Reexported declarations are not supported
add(toastOrTitle: ToastInput): Toast;
// Warning: (ae-unresolved-link) The @link reference could not be resolved: Reexported declarations are not supported
addDanger(toastOrTitle: ToastInput): Toast;
// Warning: (ae-unresolved-link) The @link reference could not be resolved: Reexported declarations are not supported
addError(error: Error, options: ErrorToastOptions): Toast;
// Warning: (ae-unresolved-link) The @link reference could not be resolved: Reexported declarations are not supported
addSuccess(toastOrTitle: ToastInput): Toast;
// Warning: (ae-unresolved-link) The @link reference could not be resolved: Reexported declarations are not supported
addWarning(toastOrTitle: ToastInput): Toast;
get$(): Rx.Observable<Toast[]>;
// @internal (undocumented)
registerOverlays(overlays: OverlayStart): void;
// Warning: (ae-unresolved-link) The @link reference could not be resolved: Reexported declarations are not supported
// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "ToastApi"
remove(toast: Toast): void;
remove(toastOrId: Toast | string): void;
}
// @public (undocumented)
@ -991,5 +989,8 @@ export interface UiSettingsState {
[key: string]: UiSettingsParams_2 & UserProvidedValues_2;
}
// @public
export type UnmountCallback = () => void;
```

37
src/core/public/types.ts Normal file
View file

@ -0,0 +1,37 @@
/*
* 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.
*/
/**
* A function that should mount DOM content inside the provided container element
* and return a handler to unmount it.
*
* @param element the container element to render into
* @returns a {@link UnmountCallback} that unmount the element on call.
*
* @public
*/
export type MountPoint = (element: HTMLElement) => UnmountCallback;
/**
* A function that will unmount the element previously mounted by
* the associated {@link MountPoint}
*
* @public
*/
export type UnmountCallback = () => void;

View file

@ -19,3 +19,4 @@
export { shareWeakReplay } from './share_weak_replay';
export { Sha256 } from './crypto';
export { MountWrapper, mountReactNode } from './mount';

View file

@ -0,0 +1,44 @@
/*
* 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, { useEffect, useRef } from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { I18nProvider } from '@kbn/i18n/react';
import { MountPoint } from '../types';
/**
* MountWrapper is a react component to mount a {@link MountPoint} inside a react tree.
*/
export const MountWrapper: React.FunctionComponent<{ mount: MountPoint }> = ({ mount }) => {
const element = useRef(null);
useEffect(() => mount(element.current!), [mount]);
return <div className="kbnMountWrapper" ref={element} />;
};
/**
* Mount converter for react components.
*
* @param component to get a mount for
*/
export const mountReactNode = (component: React.ReactNode): MountPoint => (
element: HTMLElement
) => {
render(<I18nProvider>{component}</I18nProvider>, element);
return () => unmountComponentAtNode(element);
};

View file

@ -103,6 +103,7 @@ export default {
'packages/kbn-pm/dist/index.js'
],
snapshotSerializers: [
'<rootDir>/src/plugins/kibana_react/public/util/test_helpers/react_mount_serializer.ts',
'<rootDir>/node_modules/enzyme-to-json/serializer',
],
reporters: [

View file

@ -42,6 +42,7 @@ import {
import {
withKibana,
KibanaReactContextValue,
toMountPoint,
} from '../../../../../../../plugins/kibana_react/public';
import { IndexPattern, StaticIndexPattern } from '../../../index_patterns';
import { Query, getQueryLog } from '../index';
@ -361,7 +362,7 @@ export class QueryBarInputUI extends Component<Props, State> {
id: 'data.query.queryBar.KQLNestedQuerySyntaxInfoTitle',
defaultMessage: 'KQL nested query syntax',
}),
text: (
text: toMountPoint(
<div>
<p>
<FormattedMessage

View file

@ -36,7 +36,7 @@ import { EuiSuperUpdateButton, OnRefreshProps } from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { Toast } from 'src/core/public';
import { TimeRange, TimeHistoryContract } from 'src/plugins/data/public';
import { useKibana } from '../../../../../../../plugins/kibana_react/public';
import { useKibana, toMountPoint } from '../../../../../../../plugins/kibana_react/public';
import { PersistedLog } from '../../../../../../../plugins/data/public';
import { IndexPattern } from '../../../index_patterns';
@ -298,7 +298,7 @@ function QueryBarTopRowUI(props: Props) {
id: 'data.query.queryBar.luceneSyntaxWarningTitle',
defaultMessage: 'Lucene syntax warning',
}),
text: (
text: toMountPoint(
<div>
<p>
<FormattedMessage

View file

@ -40,6 +40,7 @@ import { fatalError } from 'ui/notify';
import { capabilities } from 'ui/capabilities';
// @ts-ignore
import { modifyUrl } from 'ui/url';
import { toMountPoint } from '../../../../plugins/kibana_react/public';
// @ts-ignore
import { UrlOverflowService } from '../error_url_overflow';
import { npStart } from '../new_platform';
@ -329,7 +330,7 @@ const $setupUrlOverflowHandling = (newPlatform: CoreStart) => (
title: i18n.translate('common.ui.chrome.bigUrlWarningNotificationTitle', {
defaultMessage: 'The URL is big and Kibana might stop working',
}),
text: (
text: toMountPoint(
<Fragment>
<FormattedMessage
id="common.ui.chrome.bigUrlWarningNotificationMessage"

View file

@ -24,6 +24,7 @@ import {
EuiSpacer,
EuiButtonEmpty
} from '@elastic/eui';
import { toMountPoint } from '../../../../../plugins/kibana_react/public';
export const createZoomWarningMsg = (function () {
let disableZoomMsg = false;
@ -107,7 +108,7 @@ export const createZoomWarningMsg = (function () {
const zoomToast = ({
title: 'No additional zoom levels',
text: <ZoomWarning onChange={setZoomMsg}/>,
text: toMountPoint(<ZoomWarning onChange={setZoomMsg}/>),
'data-test-subj': 'maxZoomWarning',
});

View file

@ -19,15 +19,9 @@
import { i18n } from '@kbn/i18n';
import React from 'react';
import {
EuiFlyout,
EuiFlyoutBody,
EuiFlyoutHeader,
EuiTitle,
EuiGlobalToastListToast as Toast,
} from '@elastic/eui';
import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui';
import { DashboardPanelState } from '../embeddable';
import { NotificationsStart } from '../../../../core/public';
import { NotificationsStart, Toast } from '../../../../core/public';
import {
IContainer,
IEmbeddable,

View file

@ -24,3 +24,4 @@ export * from './overlays';
export * from './ui_settings';
export * from './field_icon';
export * from './table_list_view';
export { toMountPoint } from './util';

View file

@ -52,9 +52,20 @@ test('can display string element as title', () => {
wrapper.toasts.show({ title: 'foo' });
expect(notifications.toasts.add).toHaveBeenCalledTimes(1);
expect(notifications.toasts.add.mock.calls[0][0]).toMatchObject({
title: 'foo',
});
expect(notifications.toasts.add.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
"color": undefined,
"iconType": undefined,
"onClose": undefined,
"text": MountPoint {
"reactNode": <React.Fragment />,
},
"title": MountPoint {
"reactNode": "foo",
},
"toastLifeTimeMs": undefined,
}
`);
});
test('can display React element as title', () => {
@ -67,10 +78,12 @@ test('can display React element as title', () => {
expect(notifications.toasts.add).toHaveBeenCalledTimes(1);
expect((notifications.toasts.add.mock.calls[0][0] as any).title).toMatchInlineSnapshot(`
<div>
bar
</div>
`);
MountPoint {
"reactNode": <div>
bar
</div>,
}
`);
});
test('can display React element as toast body', () => {
@ -81,12 +94,14 @@ test('can display React element as toast body', () => {
expect(notifications.toasts.add).toHaveBeenCalledTimes(1);
expect((notifications.toasts.add.mock.calls[0][0] as any).text).toMatchInlineSnapshot(`
<React.Fragment>
<div>
baz
</div>
</React.Fragment>
`);
MountPoint {
"reactNode": <React.Fragment>
<div>
baz
</div>
</React.Fragment>,
}
`);
});
test('can set toast properties', () => {
@ -102,17 +117,21 @@ test('can set toast properties', () => {
});
expect(notifications.toasts.add.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
"color": "danger",
"iconType": "foo",
"onClose": undefined,
"text": <React.Fragment>
1
</React.Fragment>,
"title": "2",
"toastLifeTimeMs": 3,
}
`);
Object {
"color": "danger",
"iconType": "foo",
"onClose": undefined,
"text": MountPoint {
"reactNode": <React.Fragment>
1
</React.Fragment>,
},
"title": MountPoint {
"reactNode": "2",
},
"toastLifeTimeMs": 3,
}
`);
});
test('can display success, warning and danger toasts', () => {
@ -124,21 +143,48 @@ test('can display success, warning and danger toasts', () => {
wrapper.toasts.danger({ title: '3' });
expect(notifications.toasts.add).toHaveBeenCalledTimes(3);
expect(notifications.toasts.add.mock.calls[0][0]).toMatchObject({
title: '1',
color: 'success',
iconType: 'check',
});
expect(notifications.toasts.add.mock.calls[1][0]).toMatchObject({
title: '2',
color: 'warning',
iconType: 'help',
});
expect(notifications.toasts.add.mock.calls[2][0]).toMatchObject({
title: '3',
color: 'danger',
iconType: 'alert',
});
expect(notifications.toasts.add.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
"color": "success",
"iconType": "check",
"onClose": undefined,
"text": MountPoint {
"reactNode": <React.Fragment />,
},
"title": MountPoint {
"reactNode": "1",
},
"toastLifeTimeMs": undefined,
}
`);
expect(notifications.toasts.add.mock.calls[1][0]).toMatchInlineSnapshot(`
Object {
"color": "warning",
"iconType": "help",
"onClose": undefined,
"text": MountPoint {
"reactNode": <React.Fragment />,
},
"title": MountPoint {
"reactNode": "2",
},
"toastLifeTimeMs": undefined,
}
`);
expect(notifications.toasts.add.mock.calls[2][0]).toMatchInlineSnapshot(`
Object {
"color": "danger",
"iconType": "alert",
"onClose": undefined,
"text": MountPoint {
"reactNode": <React.Fragment />,
},
"title": MountPoint {
"reactNode": "3",
},
"toastLifeTimeMs": undefined,
}
`);
});
test('if body is not set, renders it empty', () => {
@ -147,7 +193,9 @@ test('if body is not set, renders it empty', () => {
wrapper.toasts.success({ title: '1' });
expect((notifications.toasts.add.mock.calls[0][0] as any).text).toMatchInlineSnapshot(
`<React.Fragment />`
);
expect((notifications.toasts.add.mock.calls[0][0] as any).text).toMatchInlineSnapshot(`
MountPoint {
"reactNode": <React.Fragment />,
}
`);
});

View file

@ -20,6 +20,7 @@
import * as React from 'react';
import { KibanaServices } from '../context/types';
import { KibanaReactNotifications } from './types';
import { toMountPoint } from '../util';
export const createNotifications = (services: KibanaServices): KibanaReactNotifications => {
const show: KibanaReactNotifications['toasts']['show'] = ({
@ -34,8 +35,8 @@ export const createNotifications = (services: KibanaServices): KibanaReactNotifi
throw new TypeError('Could not show notification as notifications service is not available.');
}
services.notifications!.toasts.add({
title,
text: <>{body || null}</>,
title: toMountPoint(title),
text: toMountPoint(<>{body || null}</>),
color,
iconType,
toastLifeTimeMs,

View file

@ -38,6 +38,7 @@ import {
EuiCallOut,
} from '@elastic/eui';
import { ToastsStart, UiSettingsClientContract } from 'kibana/public';
import { toMountPoint } from '../util';
export const EMPTY_FILTER = '';
@ -166,7 +167,7 @@ class TableListView extends React.Component<TableListViewProps, TableListViewSta
await this.props.deleteItems(this.state.selectedIds.map(id => itemsById[id]));
} catch (error) {
this.props.toastNotifications.addDanger({
title: (
title: toMountPoint(
<FormattedMessage
id="kibana-react.tableListView.listing.unableToDeleteDangerMessage"
defaultMessage="Unable to delete {entityName}(s)"

View file

@ -19,3 +19,4 @@
export * from './use_observable';
export * from './use_unmount';
export * from './react_mount';

View file

@ -0,0 +1,40 @@
/*
* 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 ReactDOM from 'react-dom';
import { I18nProvider } from '@kbn/i18n/react';
import { MountPoint } from 'kibana/public';
/**
* MountPoint converter for react nodes.
*
* @param node to get a mount point for
*/
export const toMountPoint = (node: React.ReactNode): MountPoint => {
const mount = (element: HTMLElement) => {
ReactDOM.render(<I18nProvider>{node}</I18nProvider>, element);
return () => ReactDOM.unmountComponentAtNode(element);
};
// only used for tests and snapshots serialization
if (process.env.NODE_ENV !== 'production') {
mount.__reactMount__ = node;
}
return mount;
};

View file

@ -0,0 +1,30 @@
/*
* 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 function test(value: any) {
return value && value.__reactMount__;
}
export function print(value: any, serialize: any) {
// there is no proper way to correctly indent multiline values
// so the trick here is to use the Object representation and rewriting the root object name
return serialize({
reactNode: value.__reactMount__,
}).replace('Object', 'MountPoint');
}

View file

@ -47,7 +47,10 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) {
// since ESM modules are not natively supported in Jest yet (https://github.com/facebook/jest/issues/4842)
'[/\\\\]node_modules(?![\\/\\\\]@elastic[\\/\\\\]eui)(?![\\/\\\\]monaco-editor)[/\\\\].+\\.js$',
],
snapshotSerializers: [`${kibanaDirectory}/node_modules/enzyme-to-json/serializer`],
snapshotSerializers: [
`${kibanaDirectory}/node_modules/enzyme-to-json/serializer`,
`${kibanaDirectory}/src/plugins/kibana_react/public/util/test_helpers/react_mount_serializer.ts`
],
reporters: [
'default',
[

View file

@ -6,6 +6,7 @@
import { i18n } from '@kbn/i18n';
import React, { Component } from 'react';
import { toMountPoint } from '../../../../../../../../../../src/plugins/kibana_react/public';
import { startMLJob } from '../../../../../services/rest/ml';
import { IUrlParams } from '../../../../../context/UrlParamsContext/types';
import { MLJobLink } from '../../../../shared/Links/MachineLearningLinks/MLJobLink';
@ -71,7 +72,7 @@ export class MachineLearningFlyout extends Component<Props, State> {
defaultMessage: 'Job creation failed'
}
),
text: (
text: toMountPoint(
<p>
{i18n.translate(
'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreationFailedNotificationText',
@ -105,7 +106,7 @@ export class MachineLearningFlyout extends Component<Props, State> {
defaultMessage: 'Job successfully created'
}
),
text: (
text: toMountPoint(
<p>
{i18n.translate(
'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreatedNotificationText',

View file

@ -30,6 +30,7 @@ import { padLeft, range } from 'lodash';
import moment from 'moment-timezone';
import React, { Component } from 'react';
import styled from 'styled-components';
import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_react/public';
import { KibanaCoreContext } from '../../../../../../observability/public';
import { IUrlParams } from '../../../../context/UrlParamsContext/types';
import { KibanaLink } from '../../../shared/Links/KibanaLink';
@ -219,7 +220,7 @@ export class WatcherFlyout extends Component<
defaultMessage: 'Watch creation failed'
}
),
text: (
text: toMountPoint(
<p>
{i18n.translate(
'xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreationFailedNotificationText',
@ -243,7 +244,7 @@ export class WatcherFlyout extends Component<
defaultMessage: 'New watch created!'
}
),
text: (
text: toMountPoint(
<p>
{i18n.translate(
'xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreatedNotificationText',

View file

@ -9,6 +9,7 @@ import { EuiLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useEffect, useMemo } from 'react';
import url from 'url';
import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public';
import { useFetcher } from '../../../hooks/useFetcher';
import { NoServicesMessage } from './NoServicesMessage';
import { ServiceList } from './ServiceList';
@ -55,7 +56,7 @@ export function ServiceOverview() {
defaultMessage:
'Legacy data was detected within the selected time range'
}),
text: (
text: toMountPoint(
<p>
{i18n.translate('xpack.apm.serviceOverview.toastText', {
defaultMessage:

View file

@ -8,6 +8,7 @@ import React, { useContext, useEffect, useState, useMemo } from 'react';
import { idx } from '@kbn/elastic-idx';
import { i18n } from '@kbn/i18n';
import { IHttpFetchError } from 'src/core/public';
import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public';
import { LoadingIndicatorContext } from '../context/LoadingIndicatorContext';
import { useComponentId } from './useComponentId';
import { useKibanaCore } from '../../../observability/public';
@ -92,7 +93,7 @@ export function useFetcher<TReturn>(
title: i18n.translate('xpack.apm.fetcher.error.title', {
defaultMessage: `Error while fetching resource`
}),
text: (
text: toMountPoint(
<div>
<h5>
{i18n.translate('xpack.apm.fetcher.error.status', {

View file

@ -10,6 +10,7 @@ import { toastNotifications } from 'ui/notify';
import { i18n } from '@kbn/i18n';
import { idx } from '@kbn/elastic-idx/target';
import { KFetchError } from 'ui/kfetch/kfetch_error';
import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public';
import { useTrackedPromise } from '../utils/use_tracked_promise';
export function useHTTPRequest<Response>(
pathname: string,
@ -36,7 +37,7 @@ export function useHTTPRequest<Response>(
title: i18n.translate('xpack.infra.useHTTPRequest.error.title', {
defaultMessage: `Error while fetching resource`,
}),
text: (
text: toMountPoint(
<div>
<h5>
{i18n.translate('xpack.infra.useHTTPRequest.error.status', {

View file

@ -24,6 +24,7 @@ import { EuiMonitoringTable } from 'plugins/monitoring/components/table';
import { AlertsIndicator } from 'plugins/monitoring/components/cluster/listing/alerts_indicator';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public';
import { STANDALONE_CLUSTER_CLUSTER_UUID } from '../../../../common/constants';
const IsClusterSupported = ({ isSupported, children }) => {
@ -271,14 +272,14 @@ const licenseWarning = (scope, { title, text }) => {
const handleClickIncompatibleLicense = (scope, clusterName) => {
licenseWarning(scope, {
title: (
title: toMountPoint(
<FormattedMessage
id="xpack.monitoring.cluster.listing.incompatibleLicense.warningMessageTitle"
defaultMessage="You can't view the {clusterName} cluster"
values={{ clusterName: '"' + clusterName + '"' }}
/>
),
text: (
text: toMountPoint(
<Fragment>
<p>
<FormattedMessage
@ -311,14 +312,14 @@ const handleClickInvalidLicense = (scope, clusterName) => {
const licensingPath = `${chrome.getBasePath()}/app/kibana#/management/elasticsearch/license_management/home`;
licenseWarning(scope, {
title: (
title: toMountPoint(
<FormattedMessage
id="xpack.monitoring.cluster.listing.invalidLicense.warningMessageTitle"
defaultMessage="You can't view the {clusterName} cluster"
values={{ clusterName: '"' + clusterName + '"' }}
/>
),
text: (
text: toMountPoint(
<Fragment>
<p>
<FormattedMessage

View file

@ -14,6 +14,7 @@ import {
EuiText,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public';
export function formatMonitoringError(err) {
// TODO: We should stop using Boom for errors and instead write a custom handler to return richer error objects
@ -47,12 +48,12 @@ export function ajaxErrorHandlersProvider($injector) {
kbnUrl.redirect('access-denied');
} else if (err.status === 404 && !contains(window.location.hash, 'no-data')) { // pass through if this is a 404 and we're already on the no-data page
toastNotifications.addDanger({
title: (
title: toMountPoint(
<FormattedMessage
id="xpack.monitoring.ajaxErrorHandler.requestFailedNotificationTitle"
defaultMessage="Monitoring Request Failed"
/>),
text: (
text: toMountPoint(
<div>
{ formatMonitoringError(err) }
<EuiSpacer />
@ -71,12 +72,12 @@ export function ajaxErrorHandlersProvider($injector) {
});
} else {
toastNotifications.addDanger({
title: (
title: toMountPoint(
<FormattedMessage
id="xpack.monitoring.ajaxErrorHandler.requestErrorNotificationTitle"
defaultMessage="Monitoring Request Error"
/>),
text: formatMonitoringError(err)
text: toMountPoint(formatMonitoringError(err))
});
}

View file

@ -10,6 +10,7 @@ import React, { Component, ReactElement } from 'react';
import { KFetchError } from 'ui/kfetch/kfetch_error';
import { toastNotifications } from 'ui/notify';
import url from 'url';
import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public';
import { reportingClient } from '../lib/reporting_client';
interface Props {
@ -209,7 +210,7 @@ class ReportingPanelContentUi extends Component<Props, State> {
},
{ objectType: this.props.objectType }
),
text: (
text: toMountPoint(
<FormattedMessage
id="xpack.reporting.panelContent.successfullyQueuedReportNotificationDescription"
defaultMessage="Track its progress in Management"
@ -229,7 +230,7 @@ class ReportingPanelContentUi extends Component<Props, State> {
},
{ objectType: this.props.objectType }
),
text: (
text: toMountPoint(
<FormattedMessage
id="xpack.reporting.panelContent.whatCanBeExportedWarningDescription"
defaultMessage="Please save your work first"
@ -256,7 +257,7 @@ class ReportingPanelContentUi extends Component<Props, State> {
id: 'xpack.reporting.panelContent.notification.reportingErrorTitle',
defaultMessage: 'Reporting error',
}),
text: kfetchError.message || defaultMessage,
text: toMountPoint(kfetchError.message || defaultMessage),
'data-test-subj': 'queueReportError',
});
});

View file

@ -8,6 +8,7 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
import { toastNotifications } from 'ui/notify';
import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public';
import { TransformListRow, refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common';
import { ToastNotificationText } from '../components';
@ -53,7 +54,7 @@ export const useDeleteTransforms = () => {
title: i18n.translate('xpack.transform.transformList.deleteTransformGenericErrorMessage', {
defaultMessage: 'An error occurred calling the API endpoint to delete transforms.',
}),
text: <ToastNotificationText text={e} />,
text: toMountPoint(<ToastNotificationText text={e} />),
});
}
};

View file

@ -29,6 +29,7 @@ import {
EuiText,
} from '@elastic/eui';
import { toMountPoint } from '../../../../../../../../../../src/plugins/kibana_react/public';
import { ToastNotificationText } from '../../../../components';
import { useApi } from '../../../../hooks/use_api';
import { isKibanaContextInitialized, KibanaContext } from '../../../../lib/kibana';
@ -114,7 +115,7 @@ export const StepCreateForm: SFC<Props> = React.memo(
defaultMessage: 'An error occurred creating the transform {transformId}:',
values: { transformId },
}),
text: <ToastNotificationText text={e} />,
text: toMountPoint(<ToastNotificationText text={e} />),
});
return false;
}
@ -144,7 +145,7 @@ export const StepCreateForm: SFC<Props> = React.memo(
defaultMessage: 'An error occurred starting the transform {transformId}:',
values: { transformId },
}),
text: <ToastNotificationText text={e} />,
text: toMountPoint(<ToastNotificationText text={e} />),
});
}
}
@ -203,7 +204,7 @@ export const StepCreateForm: SFC<Props> = React.memo(
'An error occurred creating the Kibana index pattern {indexPatternName}:',
values: { indexPatternName },
}),
text: <ToastNotificationText text={e} />,
text: toMountPoint(<ToastNotificationText text={e} />),
});
return false;
}
@ -234,7 +235,7 @@ export const StepCreateForm: SFC<Props> = React.memo(
title: i18n.translate('xpack.transform.stepCreateForm.progressErrorMessage', {
defaultMessage: 'An error occurred getting the progress percentage:',
}),
text: <ToastNotificationText text={e} />,
text: toMountPoint(<ToastNotificationText text={e} />),
});
clearInterval(interval);
}

View file

@ -12,6 +12,7 @@ import { toastNotifications } from 'ui/notify';
import { EuiLink, EuiSwitch, EuiFieldText, EuiForm, EuiFormRow, EuiSelect } from '@elastic/eui';
import { toMountPoint } from '../../../../../../../../../../src/plugins/kibana_react/public';
import { isKibanaContextInitialized, KibanaContext } from '../../../../lib/kibana';
import { isValidIndexName } from '../../../../../../common/utils/es_utils';
@ -91,7 +92,7 @@ export const StepDetailsForm: SFC<Props> = React.memo(({ overrides = {}, onChang
title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingTransformList', {
defaultMessage: 'An error occurred getting the existing transform IDs:',
}),
text: <ToastNotificationText text={e} />,
text: toMountPoint(<ToastNotificationText text={e} />),
});
}
@ -102,7 +103,7 @@ export const StepDetailsForm: SFC<Props> = React.memo(({ overrides = {}, onChang
title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIndexNames', {
defaultMessage: 'An error occurred getting the existing index names:',
}),
text: <ToastNotificationText text={e} />,
text: toMountPoint(<ToastNotificationText text={e} />),
});
}
@ -116,7 +117,7 @@ export const StepDetailsForm: SFC<Props> = React.memo(({ overrides = {}, onChang
defaultMessage: 'An error occurred getting the existing index pattern titles:',
}
),
text: <ToastNotificationText text={e} />,
text: toMountPoint(<ToastNotificationText text={e} />),
});
}
}

View file

@ -8,9 +8,10 @@ import React, { Fragment } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { ToastInput } from '../../../../../src/core/public';
import { toMountPoint } from '../../../../../src/plugins/kibana_react/public';
export const getGeneralErrorToast = (errorText: string, err: Error): ToastInput => ({
text: (
text: toMountPoint(
<Fragment>
<EuiCallOut title={errorText} color="danger" iconType="alert">
{err.toString()}

View file

@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { ToastInput } from '../../../../../src/core/public';
import { toMountPoint } from '../../../../../src/plugins/kibana_react/public';
import { JobSummary, ManagementLinkFn } from '../../index.d';
export const getFailureToast = (
@ -17,14 +18,14 @@ export const getFailureToast = (
getManagmenetLink: ManagementLinkFn
): ToastInput => {
return {
title: (
title: toMountPoint(
<FormattedMessage
id="xpack.reporting.publicNotifier.error.couldNotCreateReportTitle"
defaultMessage="Could not create report for {reportObjectType} '{reportObjectTitle}'."
values={{ reportObjectType: job.type, reportObjectTitle: job.title }}
/>
),
text: (
text: toMountPoint(
<Fragment>
<EuiCallOut
size="m"

View file

@ -7,6 +7,7 @@
import React, { Fragment } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { ToastInput } from '../../../../../src/core/public';
import { toMountPoint } from '../../../../../src/plugins/kibana_react/public';
import { JobId, JobSummary } from '../../index.d';
import { ReportLink } from './report_link';
import { DownloadButton } from './download_button';
@ -16,7 +17,7 @@ export const getSuccessToast = (
getReportLink: () => string,
getDownloadLink: (jobId: JobId) => string
): ToastInput => ({
title: (
title: toMountPoint(
<FormattedMessage
id="xpack.reporting.publicNotifier.successfullyCreatedReportNotificationTitle"
defaultMessage="Created report for {reportObjectType} '{reportObjectTitle}'"
@ -24,7 +25,7 @@ export const getSuccessToast = (
/>
),
color: 'success',
text: (
text: toMountPoint(
<Fragment>
<p>
<ReportLink getUrl={getReportLink} />

View file

@ -7,6 +7,7 @@
import React, { Fragment } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { ToastInput } from '../../../../../src/core/public';
import { toMountPoint } from '../../../../../src/plugins/kibana_react/public';
import { JobId, JobSummary } from '../../index.d';
import { ReportLink } from './report_link';
import { DownloadButton } from './download_button';
@ -16,14 +17,14 @@ export const getWarningFormulasToast = (
getReportLink: () => string,
getDownloadLink: (jobId: JobId) => string
): ToastInput => ({
title: (
title: toMountPoint(
<FormattedMessage
id="xpack.reporting.publicNotifier.csvContainsFormulas.formulaReportTitle"
defaultMessage="Report may contain formulas {reportObjectType} '{reportObjectTitle}'"
values={{ reportObjectType: job.type, reportObjectTitle: job.title }}
/>
),
text: (
text: toMountPoint(
<Fragment>
<p>
<FormattedMessage

View file

@ -7,6 +7,7 @@
import React, { Fragment } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { ToastInput } from '../../../../../src/core/public';
import { toMountPoint } from '../../../../../src/plugins/kibana_react/public';
import { JobId, JobSummary } from '../../index.d';
import { ReportLink } from './report_link';
import { DownloadButton } from './download_button';
@ -16,14 +17,14 @@ export const getWarningMaxSizeToast = (
getReportLink: () => string,
getDownloadLink: (jobId: JobId) => string
): ToastInput => ({
title: (
title: toMountPoint(
<FormattedMessage
id="xpack.reporting.publicNotifier.maxSizeReached.partialReportTitle"
defaultMessage="Created partial report for {reportObjectType} '{reportObjectTitle}'"
values={{ reportObjectType: job.type, reportObjectTitle: job.title }}
/>
),
text: (
text: toMountPoint(
<Fragment>
<p>
<FormattedMessage

View file

@ -29,42 +29,46 @@ exports[`stream handler showNotifications show csv formulas warning 1`] = `
Array [
Object {
"data-test-subj": "completeReportCsvFormulasWarning",
"text": <React.Fragment>
<p>
<FormattedMessage
defaultMessage="The report contains characters which spreadsheet applications can interpret as formulas."
id="xpack.reporting.publicNotifier.csvContainsFormulas.formulaReportMessage"
values={Object {}}
/>
</p>
<p>
<ReportLink
"text": MountPoint {
"reactNode": <React.Fragment>
<p>
<FormattedMessage
defaultMessage="The report contains characters which spreadsheet applications can interpret as formulas."
id="xpack.reporting.publicNotifier.csvContainsFormulas.formulaReportMessage"
values={Object {}}
/>
</p>
<p>
<ReportLink
getUrl={[Function]}
/>
</p>
<DownloadButton
getUrl={[Function]}
job={
Object {
"csvContainsFormulas": true,
"id": "yas3",
"status": "completed",
"title": "Yas",
"type": "yas",
}
}
/>
</p>
<DownloadButton
getUrl={[Function]}
job={
</React.Fragment>,
},
"title": MountPoint {
"reactNode": <FormattedMessage
defaultMessage="Report may contain formulas {reportObjectType} '{reportObjectTitle}'"
id="xpack.reporting.publicNotifier.csvContainsFormulas.formulaReportTitle"
values={
Object {
"csvContainsFormulas": true,
"id": "yas3",
"status": "completed",
"title": "Yas",
"type": "yas",
"reportObjectTitle": "Yas",
"reportObjectType": "yas",
}
}
/>
</React.Fragment>,
"title": <FormattedMessage
defaultMessage="Report may contain formulas {reportObjectType} '{reportObjectTitle}'"
id="xpack.reporting.publicNotifier.csvContainsFormulas.formulaReportTitle"
values={
Object {
"reportObjectTitle": "Yas",
"reportObjectType": "yas",
}
}
/>,
/>,
},
},
]
`;
@ -74,44 +78,48 @@ Array [
Object {
"data-test-subj": "completeReportFailure",
"iconType": undefined,
"text": <React.Fragment>
<EuiCallOut
color="danger"
iconType="alert"
size="m"
title="The reporting job failed"
>
this is the completed report data
</EuiCallOut>
<EuiSpacer />
<p>
<FormattedMessage
defaultMessage="More information is available at {path}."
id="xpack.reporting.publicNotifier.error.checkManagement"
values={
Object {
"path": <a>
<FormattedMessage
defaultMessage="Management > Kibana > Reporting"
id="xpack.reporting.publicNotifier.error.reportingSectionUrlLinkLabel"
values={Object {}}
/>
</a>,
"text": MountPoint {
"reactNode": <React.Fragment>
<EuiCallOut
color="danger"
iconType="alert"
size="m"
title="The reporting job failed"
>
this is the completed report data
</EuiCallOut>
<EuiSpacer />
<p>
<FormattedMessage
defaultMessage="More information is available at {path}."
id="xpack.reporting.publicNotifier.error.checkManagement"
values={
Object {
"path": <a>
<FormattedMessage
defaultMessage="Management > Kibana > Reporting"
id="xpack.reporting.publicNotifier.error.reportingSectionUrlLinkLabel"
values={Object {}}
/>
</a>,
}
}
/>
</p>
</React.Fragment>,
},
"title": MountPoint {
"reactNode": <FormattedMessage
defaultMessage="Could not create report for {reportObjectType} '{reportObjectTitle}'."
id="xpack.reporting.publicNotifier.error.couldNotCreateReportTitle"
values={
Object {
"reportObjectTitle": "Yas 7",
"reportObjectType": "yas",
}
/>
</p>
</React.Fragment>,
"title": <FormattedMessage
defaultMessage="Could not create report for {reportObjectType} '{reportObjectTitle}'."
id="xpack.reporting.publicNotifier.error.couldNotCreateReportTitle"
values={
Object {
"reportObjectTitle": "Yas 7",
"reportObjectType": "yas",
}
}
/>,
/>,
},
},
]
`;
@ -120,42 +128,46 @@ exports[`stream handler showNotifications show max length warning 1`] = `
Array [
Object {
"data-test-subj": "completeReportMaxSizeWarning",
"text": <React.Fragment>
<p>
<FormattedMessage
defaultMessage="The report reached the max size and contains partial data."
id="xpack.reporting.publicNotifier.maxSizeReached.partialReportDescription"
values={Object {}}
/>
</p>
<p>
<ReportLink
"text": MountPoint {
"reactNode": <React.Fragment>
<p>
<FormattedMessage
defaultMessage="The report reached the max size and contains partial data."
id="xpack.reporting.publicNotifier.maxSizeReached.partialReportDescription"
values={Object {}}
/>
</p>
<p>
<ReportLink
getUrl={[Function]}
/>
</p>
<DownloadButton
getUrl={[Function]}
job={
Object {
"id": "yas2",
"maxSizeReached": true,
"status": "completed",
"title": "Yas",
"type": "yas",
}
}
/>
</p>
<DownloadButton
getUrl={[Function]}
job={
</React.Fragment>,
},
"title": MountPoint {
"reactNode": <FormattedMessage
defaultMessage="Created partial report for {reportObjectType} '{reportObjectTitle}'"
id="xpack.reporting.publicNotifier.maxSizeReached.partialReportTitle"
values={
Object {
"id": "yas2",
"maxSizeReached": true,
"status": "completed",
"title": "Yas",
"type": "yas",
"reportObjectTitle": "Yas",
"reportObjectType": "yas",
}
}
/>
</React.Fragment>,
"title": <FormattedMessage
defaultMessage="Created partial report for {reportObjectType} '{reportObjectTitle}'"
id="xpack.reporting.publicNotifier.maxSizeReached.partialReportTitle"
values={
Object {
"reportObjectTitle": "Yas",
"reportObjectType": "yas",
}
}
/>,
/>,
},
},
]
`;
@ -165,34 +177,38 @@ Array [
Object {
"color": "success",
"data-test-subj": "completeReportSuccess",
"text": <React.Fragment>
<p>
<ReportLink
"text": MountPoint {
"reactNode": <React.Fragment>
<p>
<ReportLink
getUrl={[Function]}
/>
</p>
<DownloadButton
getUrl={[Function]}
job={
Object {
"id": "yas1",
"status": "completed",
"title": "Yas",
"type": "yas",
}
}
/>
</p>
<DownloadButton
getUrl={[Function]}
job={
</React.Fragment>,
},
"title": MountPoint {
"reactNode": <FormattedMessage
defaultMessage="Created report for {reportObjectType} '{reportObjectTitle}'"
id="xpack.reporting.publicNotifier.successfullyCreatedReportNotificationTitle"
values={
Object {
"id": "yas1",
"status": "completed",
"title": "Yas",
"type": "yas",
"reportObjectTitle": "Yas",
"reportObjectType": "yas",
}
}
/>
</React.Fragment>,
"title": <FormattedMessage
defaultMessage="Created report for {reportObjectType} '{reportObjectTitle}'"
id="xpack.reporting.publicNotifier.successfullyCreatedReportNotificationTitle"
values={
Object {
"reportObjectTitle": "Yas",
"reportObjectType": "yas",
}
}
/>,
/>,
},
},
]
`;

View file

@ -26,9 +26,11 @@ const expectWarningToast = (
Array [
Object {
"color": "warning",
"text": <SessionTimeoutWarning
onRefreshSession={[Function]}
/>,
"text": MountPoint {
"reactNode": <SessionTimeoutWarning
onRefreshSession={[Function]}
/>,
},
"title": "Warning",
"toastLifeTimeMs": ${toastLifeTimeMS},
},
@ -103,8 +105,8 @@ describe('warning toast', () => {
expect(http.get).not.toHaveBeenCalled();
const toastInput = notifications.toasts.add.mock.calls[0][0];
expect(toastInput).toHaveProperty('text');
const reactComponent = (toastInput as any).text;
const wrapper = mountWithIntl(reactComponent);
const mountPoint = (toastInput as any).text;
const wrapper = mountWithIntl(mountPoint.__reactMount__);
wrapper.find('EuiButton[data-test-subj="refreshSessionButton"]').simulate('click');
expect(http.get).toHaveBeenCalled();
});

View file

@ -7,6 +7,7 @@
import { NotificationsSetup, Toast, HttpSetup } from 'src/core/public';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { toMountPoint } from '../../../../../src/plugins/kibana_react/public';
import { SessionTimeoutWarning } from './session_timeout_warning';
import { ISessionExpired } from './session_expired';
@ -65,7 +66,7 @@ export class SessionTimeout {
private showWarning = () => {
this.warningToast = this.notifications.toasts.add({
color: 'warning',
text: <SessionTimeoutWarning onRefreshSession={this.refreshSession} />,
text: toMountPoint(<SessionTimeoutWarning onRefreshSession={this.refreshSession} />),
title: i18n.translate('xpack.security.components.sessionTimeoutWarning.title', {
defaultMessage: 'Warning',
}),