Merge branch 'master' of github.com:elastic/kibana into feature-integrations-manager
5
.github/CODEOWNERS
vendored
|
@ -28,11 +28,6 @@
|
|||
# Canvas
|
||||
/x-pack/legacy/plugins/canvas/ @elastic/kibana-canvas
|
||||
|
||||
# Code
|
||||
/x-pack/legacy/plugins/code/ @teams/code
|
||||
/x-pack/test/functional/apps/code/ @teams/code
|
||||
/x-pack/test/api_integration/apis/code/ @teams/code
|
||||
|
||||
# Logs & Metrics UI
|
||||
/x-pack/legacy/plugins/infra/ @elastic/logs-metrics-ui
|
||||
/x-pack/legacy/plugins/integrations_manager/ @elastic/epm
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"kibana_react": "src/legacy/core_plugins/kibana_react",
|
||||
"kibana-react": "src/plugins/kibana_react",
|
||||
"navigation": "src/legacy/core_plugins/navigation",
|
||||
"newsfeed": "src/plugins/newsfeed",
|
||||
"regionMap": "src/legacy/core_plugins/region_map",
|
||||
"server": "src/legacy/server",
|
||||
"statusPage": "src/legacy/core_plugins/status_page",
|
||||
|
|
|
@ -2,7 +2,6 @@ files:
|
|||
include:
|
||||
- 'src/legacy/core_plugins/metrics/**/*.s+(a|c)ss'
|
||||
- 'src/legacy/core_plugins/timelion/**/*.s+(a|c)ss'
|
||||
- 'src/legacy/ui/public/query_bar/**/*.s+(a|c)ss'
|
||||
- 'src/legacy/ui/public/vislib/**/*.s+(a|c)ss'
|
||||
- 'x-pack/legacy/plugins/rollup/**/*.s+(a|c)ss'
|
||||
- 'x-pack/legacy/plugins/security/**/*.s+(a|c)ss'
|
||||
|
|
|
@ -120,6 +120,7 @@ You should prefer modern language features in a lot of cases, e.g.:
|
|||
* Prefer arrow function over storing `this` (no `const self = this;`)
|
||||
* Prefer template strings over string concatenation
|
||||
* Prefer the spread operator for copying arrays (`[...arr]`) over `arr.slice()`
|
||||
* Use optional chaining (`?.`) and nullish Coalescing (`??`) over `lodash.get` (and similar utilities)
|
||||
|
||||
### Avoid mutability and state
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [App](./kibana-plugin-public.app.md) > [chromeless](./kibana-plugin-public.app.chromeless.md)
|
||||
|
||||
## App.chromeless property
|
||||
|
||||
Hide the UI chrome when the application is mounted. Defaults to `false`<!-- -->. Takes precedence over chrome service visibility settings.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
chromeless?: boolean;
|
||||
```
|
|
@ -16,5 +16,6 @@ export interface App extends AppBase
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [chromeless](./kibana-plugin-public.app.chromeless.md) | <code>boolean</code> | Hide the UI chrome when the application is mounted. Defaults to <code>false</code>. Takes precedence over chrome service visibility settings. |
|
||||
| [mount](./kibana-plugin-public.app.mount.md) | <code>(context: AppMountContext, params: AppMountParameters) => AppUnmount | Promise<AppUnmount></code> | A mount function called when the user navigates to this app's route. |
|
||||
|
||||
|
|
|
@ -21,12 +21,13 @@ How to configure react-router with a base path:
|
|||
export class MyPlugin implements Plugin {
|
||||
setup({ application }) {
|
||||
application.register({
|
||||
id: 'my-app',
|
||||
async mount(context, params) {
|
||||
const { renderApp } = await import('./application');
|
||||
return renderApp(context, params);
|
||||
},
|
||||
});
|
||||
id: 'my-app',
|
||||
async mount(context, params) {
|
||||
const { renderApp } = await import('./application');
|
||||
return renderApp(context, params);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
|
|
@ -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) |
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [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;
|
||||
```
|
|
@ -1,13 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [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;
|
||||
```
|
|
@ -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>
|
||||
|
|
|
@ -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 | undefined</code> | |
|
||||
| mount | <code>OverlayBannerMount</code> | |
|
||||
| mount | <code>MountPoint</code> | |
|
||||
| priority | <code>number</code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [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;
|
||||
```
|
13
docs/development/core/public/kibana-plugin-public.toast.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [Toast](./kibana-plugin-public.toast.md)
|
||||
|
||||
## Toast type
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type Toast = ToastInputFields & {
|
||||
id: string;
|
||||
};
|
||||
```
|
|
@ -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;
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -22,5 +22,5 @@ add(toastOrTitle: ToastInput): Toast;
|
|||
|
||||
`Toast`
|
||||
|
||||
a
|
||||
a [Toast](./kibana-plugin-public.toast.md)
|
||||
|
||||
|
|
|
@ -22,5 +22,5 @@ addDanger(toastOrTitle: ToastInput): Toast;
|
|||
|
||||
`Toast`
|
||||
|
||||
a
|
||||
a [Toast](./kibana-plugin-public.toast.md)
|
||||
|
||||
|
|
|
@ -23,5 +23,5 @@ addError(error: Error, options: ErrorToastOptions): Toast;
|
|||
|
||||
`Toast`
|
||||
|
||||
a
|
||||
a [Toast](./kibana-plugin-public.toast.md)
|
||||
|
||||
|
|
|
@ -22,5 +22,5 @@ addSuccess(toastOrTitle: ToastInput): Toast;
|
|||
|
||||
`Toast`
|
||||
|
||||
a
|
||||
a [Toast](./kibana-plugin-public.toast.md)
|
||||
|
||||
|
|
|
@ -22,5 +22,5 @@ addWarning(toastOrTitle: ToastInput): Toast;
|
|||
|
||||
`Toast`
|
||||
|
||||
a
|
||||
a [Toast](./kibana-plugin-public.toast.md)
|
||||
|
||||
|
|
|
@ -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. |
|
||||
|
||||
|
|
|
@ -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 | 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>
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [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;
|
||||
```
|
|
@ -9,11 +9,12 @@ for displayed decimal values.
|
|||
. Scroll or search for the setting you want to modify.
|
||||
. Enter a new value for the setting.
|
||||
|
||||
|
||||
[float]
|
||||
[[settings-read-only-access]]
|
||||
=== [xpack]#Read only access#
|
||||
When you have insufficient privileges to edit advanced settings, the following
|
||||
indicator in Kibana will be displayed. The buttons to edit settings won't be visible.
|
||||
When you have insufficient privileges to edit advanced settings, the following
|
||||
indicator in Kibana will be displayed. The buttons to edit settings won't be visible.
|
||||
For more information on granting access to Kibana see <<xpack-security-authorization>>.
|
||||
|
||||
[role="screenshot"]
|
||||
|
@ -25,9 +26,9 @@ image::images/settings-read-only-badge.png[Example of Advanced Settings Manageme
|
|||
|
||||
WARNING: Modifying a setting can affect {kib}
|
||||
performance and cause problems that are
|
||||
difficult to diagnose. Setting a property value to a blank field reverts
|
||||
difficult to diagnose. Setting a property value to a blank field reverts
|
||||
to the default behavior, which might not be
|
||||
compatible with other configuration settings. Deleting a custom setting
|
||||
compatible with other configuration settings. Deleting a custom setting
|
||||
removes it from {kib} permanently.
|
||||
|
||||
|
||||
|
@ -44,7 +45,7 @@ removes it from {kib} permanently.
|
|||
adapt to the interval between measurements. Keys are http://en.wikipedia.org/wiki/ISO_8601#Time_intervals[ISO8601 intervals].
|
||||
`dateFormat:tz`:: The timezone that Kibana uses. The default value of `Browser` uses the timezone detected by the browser.
|
||||
`dateNanosFormat`:: The format to use for displaying https://momentjs.com/docs/#/displaying/format/[pretty formatted dates] of {ref}/date_nanos.html[Elasticsearch date_nanos type].
|
||||
`defaultIndex`:: The index to access if no index is set. The default is `null`.
|
||||
`defaultIndex`:: The index to access if no index is set. The default is `null`.
|
||||
`fields:popularLimit`:: The top N most popular fields to show.
|
||||
`filterEditor:suggestValues`:: Set this property to `false` to prevent the filter editor from suggesting values for fields.
|
||||
`filters:pinnedByDefault`:: Set this property to `true` to make filters have a global state (be pinned) by default.
|
||||
|
@ -59,46 +60,46 @@ mentioned use "\_default_".
|
|||
`histogram:maxBars`:: Date histograms are not generated with more bars than the value of this property, scaling values
|
||||
when necessary.
|
||||
`history:limit`:: In fields that have history, such as query inputs, show this many recent values.
|
||||
`indexPattern:fieldMapping:lookBack`:: For index patterns containing timestamps in their names,
|
||||
`indexPattern:fieldMapping:lookBack`:: For index patterns containing timestamps in their names,
|
||||
look for this many recent matching patterns from which to query the field mapping.
|
||||
`indexPattern:placeholder`:: The default placeholder value to use in Management > Index Patterns > Create Index Pattern.
|
||||
`metaFields`:: Fields that exist outside of `_source`. Kibana merges these fields
|
||||
`metaFields`:: Fields that exist outside of `_source`. Kibana merges these fields
|
||||
into the document when displaying it.
|
||||
`metrics:max_buckets`:: The maximum numbers of buckets that a single
|
||||
data source can return. This might arise when the user selects a
|
||||
data source can return. This might arise when the user selects a
|
||||
short interval (for example, 1s) for a long time period (1 year).
|
||||
`query:allowLeadingWildcards`:: Allows a wildcard (*) as the first character
|
||||
in a query clause. Only applies when experimental query features are
|
||||
enabled in the query bar. To disallow leading wildcards in Lucene queries,
|
||||
`query:allowLeadingWildcards`:: Allows a wildcard (*) as the first character
|
||||
in a query clause. Only applies when experimental query features are
|
||||
enabled in the query bar. To disallow leading wildcards in Lucene queries,
|
||||
use `query:queryString:options`.
|
||||
`query:queryString:options`:: Options for the Lucene query string parser. Only
|
||||
used when "Query language" is set to Lucene.
|
||||
`savedObjects:listingLimit`:: The number of objects to fetch for lists of saved objects.
|
||||
`savedObjects:listingLimit`:: The number of objects to fetch for lists of saved objects.
|
||||
The default value is 1000. Do not set above 10000.
|
||||
`savedObjects:perPage`:: The number of objects to show on each page of the
|
||||
`savedObjects:perPage`:: The number of objects to show on each page of the
|
||||
list of saved objects. The default is 5.
|
||||
`search:queryLanguage`:: The query language to use in the query bar.
|
||||
Choices are <<kuery-query, KQL>>, a language built specifically for {kib}, and the <<lucene-query, Lucene
|
||||
Choices are <<kuery-query, KQL>>, a language built specifically for {kib}, and the <<lucene-query, Lucene
|
||||
query syntax>>.
|
||||
`shortDots:enable`:: Set this property to `true` to shorten long
|
||||
`shortDots:enable`:: Set this property to `true` to shorten long
|
||||
field names in visualizations. For example, show `f.b.baz` instead of `foo.bar.baz`.
|
||||
`sort:options`:: Options for the Elasticsearch {ref}/search-request-body.html#request-body-search-sort[sort] parameter.
|
||||
`state:storeInSessionStorage`:: [experimental] Kibana tracks UI state in the
|
||||
URL, which can lead to problems when there is a lot of state information,
|
||||
and the URL gets very long.
|
||||
Enabling this setting stores part of the URL in your browser session to keep the
|
||||
`state:storeInSessionStorage`:: [experimental] Kibana tracks UI state in the
|
||||
URL, which can lead to problems when there is a lot of state information,
|
||||
and the URL gets very long.
|
||||
Enabling this setting stores part of the URL in your browser session to keep the
|
||||
URL short.
|
||||
`theme:darkMode`:: Set to `true` to enable a dark mode for the {kib} UI. You must
|
||||
refresh the page to apply the setting.
|
||||
`timepicker:quickRanges`:: The list of ranges to show in the Quick section of
|
||||
the time filter. This should be an array of objects, with each object containing
|
||||
`from`, `to` (see {ref}/common-options.html#date-math[accepted formats]),
|
||||
`timepicker:quickRanges`:: The list of ranges to show in the Quick section of
|
||||
the time filter. This should be an array of objects, with each object containing
|
||||
`from`, `to` (see {ref}/common-options.html#date-math[accepted formats]),
|
||||
and `display` (the title to be displayed).
|
||||
`timepicker:refreshIntervalDefaults`:: The default refresh interval for the time filter. Example: `{ "display": "15 seconds", "pause": true, "value": 15000 }`.
|
||||
`timepicker:timeDefaults`:: The default selection in the time filter.
|
||||
`truncate:maxHeight`:: The maximum height that a cell occupies in a table. Set to 0 to disable
|
||||
truncation.
|
||||
`xPack:defaultAdminEmail`:: Email address for X-Pack admin operations, such as
|
||||
`xPack:defaultAdminEmail`:: Email address for X-Pack admin operations, such as
|
||||
cluster alert notifications from Monitoring.
|
||||
|
||||
|
||||
|
@ -107,7 +108,7 @@ cluster alert notifications from Monitoring.
|
|||
=== Accessibility settings
|
||||
|
||||
[horizontal]
|
||||
`accessibility:disableAnimations`:: Turns off all unnecessary animations in the
|
||||
`accessibility:disableAnimations`:: Turns off all unnecessary animations in the
|
||||
{kib} UI. Refresh the page to apply the changes.
|
||||
|
||||
[float]
|
||||
|
@ -124,21 +125,21 @@ cluster alert notifications from Monitoring.
|
|||
[horizontal]
|
||||
`context:defaultSize`:: The number of surrounding entries to display in the context view. The default value is 5.
|
||||
`context:step`:: The number by which to increment or decrement the context size. The default value is 5.
|
||||
`context:tieBreakerFields`:: A comma-separated list of fields to use
|
||||
for breaking a tie between documents that have the same timestamp value. The first
|
||||
`context:tieBreakerFields`:: A comma-separated list of fields to use
|
||||
for breaking a tie between documents that have the same timestamp value. The first
|
||||
field that is present and sortable in the current index pattern is used.
|
||||
`defaultColumns`:: The columns that appear by default on the Discover page.
|
||||
The default is `_source`.
|
||||
`discover:aggs:terms:size`:: The number terms that are visualized when clicking
|
||||
The default is `_source`.
|
||||
`discover:aggs:terms:size`:: The number terms that are visualized when clicking
|
||||
the Visualize button in the field drop down. The default is `20`.
|
||||
`discover:sampleSize`:: The number of rows to show in the Discover table.
|
||||
`discover:sort:defaultOrder`:: The default sort direction for time-based index patterns.
|
||||
`discover:searchOnPageLoad`:: Controls whether a search is executed when Discover first loads.
|
||||
`discover:searchOnPageLoad`:: Controls whether a search is executed when Discover first loads.
|
||||
This setting does not have an effect when loading a saved search.
|
||||
`doc_table:hideTimeColumn`:: Hides the "Time" column in Discover and in all saved searches on dashboards.
|
||||
`doc_table:highlight`:: Highlights results in Discover and saved searches on dashboards.
|
||||
`doc_table:highlight`:: Highlights results in Discover and saved searches on dashboards.
|
||||
Highlighting slows requests when
|
||||
working on big documents.
|
||||
working on big documents.
|
||||
|
||||
|
||||
|
||||
|
@ -150,14 +151,14 @@ working on big documents.
|
|||
[horizontal]
|
||||
`notifications:banner`:: A custom banner intended for temporary notices to all users.
|
||||
Supports https://help.github.com/en/articles/basic-writing-and-formatting-syntax[Markdown].
|
||||
`notifications:lifetime:banner`:: The duration, in milliseconds, for banner
|
||||
notification displays. The default value is 3000000. Set this field to `Infinity`
|
||||
`notifications:lifetime:banner`:: The duration, in milliseconds, for banner
|
||||
notification displays. The default value is 3000000. Set this field to `Infinity`
|
||||
to disable banner notifications.
|
||||
`notifications:lifetime:error`:: The duration, in milliseconds, for error
|
||||
`notifications:lifetime:error`:: The duration, in milliseconds, for error
|
||||
notification displays. The default value is 300000. Set this field to `Infinity` to disable error notifications.
|
||||
`notifications:lifetime:info`:: The duration, in milliseconds, for information notification displays.
|
||||
`notifications:lifetime:info`:: The duration, in milliseconds, for information notification displays.
|
||||
The default value is 5000. Set this field to `Infinity` to disable information notifications.
|
||||
`notifications:lifetime:warning`:: The duration, in milliseconds, for warning notification
|
||||
`notifications:lifetime:warning`:: The duration, in milliseconds, for warning notification
|
||||
displays. The default value is 10000. Set this field to `Infinity` to disable warning notifications.
|
||||
|
||||
|
||||
|
@ -175,8 +176,8 @@ displays. The default value is 10000. Set this field to `Infinity` to disable wa
|
|||
=== Rollup settings
|
||||
|
||||
[horizontal]
|
||||
`rollups:enableIndexPatterns`:: Enables the creation of index patterns that
|
||||
capture rollup indices, which in turn enables visualizations based on rollup data.
|
||||
`rollups:enableIndexPatterns`:: Enables the creation of index patterns that
|
||||
capture rollup indices, which in turn enables visualizations based on rollup data.
|
||||
Refresh the page to apply the changes.
|
||||
|
||||
|
||||
|
@ -188,22 +189,22 @@ Refresh the page to apply the changes.
|
|||
`courier:batchSearches`:: When disabled, dashboard panels will load individually, and search requests will terminate when
|
||||
users navigate away or update the query. When enabled, dashboard panels will load together when all of the data is loaded,
|
||||
and searches will not terminate.
|
||||
`courier:customRequestPreference`:: {ref}/search-request-body.html#request-body-search-preference[Request preference]
|
||||
`courier:customRequestPreference`:: {ref}/search-request-body.html#request-body-search-preference[Request preference]
|
||||
to use when `courier:setRequestPreference` is set to "custom".
|
||||
`courier:ignoreFilterIfFieldNotInIndex`:: Skips filters that apply to fields that don't exist in the index for a visualization.
|
||||
`courier:ignoreFilterIfFieldNotInIndex`:: Skips filters that apply to fields that don't exist in the index for a visualization.
|
||||
Useful when dashboards consist of visualizations from multiple index patterns.
|
||||
`courier:maxConcurrentShardRequests`:: Controls the {ref}/search-multi-search.html[max_concurrent_shard_requests]
|
||||
setting used for `_msearch` requests sent by {kib}. Set to 0 to disable this
|
||||
`courier:maxConcurrentShardRequests`:: Controls the {ref}/search-multi-search.html[max_concurrent_shard_requests]
|
||||
setting used for `_msearch` requests sent by {kib}. Set to 0 to disable this
|
||||
config and use the {es} default.
|
||||
`courier:setRequestPreference`:: Enables you to set which shards handle your search requests.
|
||||
* *Session ID:* Restricts operations to execute all search requests on the same shards.
|
||||
* *Session ID:* Restricts operations to execute all search requests on the same shards.
|
||||
This has the benefit of reusing shard caches across requests.
|
||||
* *Custom:* Allows you to define your own preference. Use `courier:customRequestPreference`
|
||||
* *Custom:* Allows you to define your own preference. Use `courier:customRequestPreference`
|
||||
to customize your preference value.
|
||||
* *None:* Do not set a preference. This might provide better performance
|
||||
because requests can be spread across all shard copies. However, results might
|
||||
* *None:* Do not set a preference. This might provide better performance
|
||||
because requests can be spread across all shard copies. However, results might
|
||||
be inconsistent because different shards might be in different refresh states.
|
||||
`search:includeFrozen`:: Includes {ref}/frozen-indices.html[frozen indices] in results.
|
||||
`search:includeFrozen`:: Includes {ref}/frozen-indices.html[frozen indices] in results.
|
||||
Searching through frozen indices
|
||||
might increase the search time. This setting is off by default. Users must opt-in to include frozen indices.
|
||||
|
||||
|
@ -212,8 +213,8 @@ might increase the search time. This setting is off by default. Users must opt-i
|
|||
=== SIEM settings
|
||||
|
||||
[horizontal]
|
||||
`siem:defaultAnomalyScore`:: The threshold above which anomalies are displayed in the SIEM app.
|
||||
`siem:defaultIndex`:: A comma-delimited list of Elasticsearch indices from which the SIEM app collects events.
|
||||
`siem:defaultAnomalyScore`:: The threshold above which Machine Learning job anomalies are displayed in the SIEM app.
|
||||
`siem:defaultIndex`:: A comma-delimited list of Elasticsearch indices from which the SIEM app collects events.
|
||||
`siem:refreshIntervalDefaults`:: The default refresh interval for the SIEM time filter, in milliseconds.
|
||||
`siem:timeDefaults`:: The default period of time in the SIEM time filter.
|
||||
|
||||
|
@ -226,16 +227,16 @@ might increase the search time. This setting is off by default. Users must opt-i
|
|||
`timelion:default_rows`:: The default number of rows to use on a Timelion sheet.
|
||||
`timelion:es.default_index`:: The default index when using the `.es()` query.
|
||||
`timelion:es.timefield`:: The default field containing a timestamp when using the `.es()` query.
|
||||
`timelion:graphite.url`:: [experimental] Used with graphite queries, this is the URL of your graphite host
|
||||
in the form https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite. This URL can be
|
||||
`timelion:graphite.url`:: [experimental] Used with graphite queries, this is the URL of your graphite host
|
||||
in the form https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite. This URL can be
|
||||
selected from a whitelist configured in the `kibana.yml` under `timelion.graphiteUrls`.
|
||||
`timelion:max_buckets`:: The maximum number of buckets a single data source can return.
|
||||
This value is used for calculating automatic intervals in visualizations.
|
||||
`timelion:min_interval`:: The smallest interval to calculate when using "auto".
|
||||
`timelion:quandl.key`:: [experimental] Used with quandl queries, this is your API key from https://www.quandl.com/[www.quandl.com].
|
||||
`timelion:showTutorial`:: Shows the Timelion tutorial
|
||||
`timelion:showTutorial`:: Shows the Timelion tutorial
|
||||
to users when they first open the Timelion app.
|
||||
`timelion:target_buckets`:: Used for calculating automatic intervals in visualizations,
|
||||
`timelion:target_buckets`:: Used for calculating automatic intervals in visualizations,
|
||||
this is the number of buckets to try to represent.
|
||||
|
||||
|
||||
|
@ -246,18 +247,18 @@ this is the number of buckets to try to represent.
|
|||
|
||||
[horizontal]
|
||||
`visualization:colorMapping`:: Maps values to specified colors in visualizations.
|
||||
`visualization:dimmingOpacity`:: The opacity of the chart items that are dimmed
|
||||
when highlighting another element of the chart. The lower this number, the more
|
||||
`visualization:dimmingOpacity`:: The opacity of the chart items that are dimmed
|
||||
when highlighting another element of the chart. The lower this number, the more
|
||||
the highlighted element stands out. This must be a number between 0 and 1.
|
||||
`visualization:loadingDelay`:: The time to wait before dimming visualizations
|
||||
`visualization:loadingDelay`:: The time to wait before dimming visualizations
|
||||
during a query.
|
||||
`visualization:regionmap:showWarnings`:: Shows
|
||||
`visualization:regionmap:showWarnings`:: Shows
|
||||
a warning in a region map when terms cannot be joined to a shape.
|
||||
`visualization:tileMap:WMSdefaults`:: The default properties for the WMS map server support in the coordinate map.
|
||||
`visualization:tileMap:maxPrecision`:: The maximum geoHash precision displayed on tile maps: 7 is high, 10 is very high,
|
||||
and 12 is the maximum. See this
|
||||
and 12 is the maximum. See this
|
||||
{ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[explanation of cell dimensions].
|
||||
`visualize:enableLabs`:: Enables users to create, view, and edit experimental visualizations.
|
||||
`visualize:enableLabs`:: Enables users to create, view, and edit experimental visualizations.
|
||||
If disabled, only visualizations that are considered production-ready are available to the user.
|
||||
|
||||
|
||||
|
@ -265,6 +266,5 @@ If disabled, only visualizations that are considered production-ready are availa
|
|||
[[kibana-telemetry-settings]]
|
||||
=== Usage data settings
|
||||
|
||||
Helps improve the Elastic Stack by providing usage statistics for
|
||||
Helps improve the Elastic Stack by providing usage statistics for
|
||||
basic features. This data will not be shared outside of Elastic.
|
||||
|
||||
|
|
Before Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 164 KiB |
Before Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 264 KiB |
|
@ -1,85 +0,0 @@
|
|||
[role="xpack"]
|
||||
[[xpack-dashboard-only-mode]]
|
||||
== Dashboard-only mode
|
||||
|
||||
deprecated[7.4.0, "Using the `kibana_dashboard_only_user` role is deprecated. Use <<kibana-feature-privileges,feature privileges>> instead."]
|
||||
|
||||
In dashboard-only mode, users have access to only the *Dashboard* app.
|
||||
Users can view and filter the dashboards, but cannot create, edit, or delete
|
||||
them. This enables you to:
|
||||
|
||||
* Show off your dashboards without giving users access to all of {kib}
|
||||
|
||||
* Share your {kib} dashboards without the risk of users accidentally
|
||||
editing or deleting them
|
||||
|
||||
Dashboard-only mode pairs well with fullscreen mode.
|
||||
You can share your dashboard with the team responsible
|
||||
for showing the dashboard on a big-screen monitor, and not worry about it being modified.
|
||||
|
||||
[role="screenshot"]
|
||||
image:management/dashboard_only_mode/images/view_only_dashboard.png["View Only Dashboard"]
|
||||
|
||||
[[setup-dashboard-only-mode]]
|
||||
[float]
|
||||
=== Assign dashboard-only mode
|
||||
With {security} enabled, you can restrict users to dashboard-only mode by assigning
|
||||
them the built-in `kibana_dashboard_only_user` role.
|
||||
|
||||
. Go to *Management > Security > Users*.
|
||||
. Create or edit a user.
|
||||
. Assign the `kibana_dashboard_only_user` role and a role that <<grant-read-access-to-indices, grants `read` access to the data indices>>.
|
||||
+
|
||||
For example,
|
||||
to enable users to view the dashboards in the sample data sets, you must assign them
|
||||
the `kibana_dashboard_only_user` role and a role that has
|
||||
`read` access to the kibana_* indices.
|
||||
+
|
||||
[role="screenshot"]
|
||||
image:management/dashboard_only_mode/images/dashboard-only-user-role.png["Dashboard Only mode has no editing controls"]
|
||||
|
||||
[IMPORTANT]
|
||||
===========================================
|
||||
* If you assign users the `kibana_dashboard_only_user` role and a role
|
||||
with write permissions to {kib}, they *will* have write access,
|
||||
even though the controls remain hidden in {kib}.
|
||||
|
||||
* If you also assign users the reserved `superuser` role, they will have full
|
||||
access to {kib}.
|
||||
|
||||
===========================================
|
||||
|
||||
[float]
|
||||
[[grant-read-access-to-indices]]
|
||||
=== Grant read access to indices
|
||||
|
||||
The `kibana_dashboard_only_user` role
|
||||
does not provide access to data indices.
|
||||
You must also assign the user a role that grants `read` access
|
||||
to each index you are using. Use *Management > Security > Roles* to create or edit a
|
||||
role and assign index privileges.
|
||||
For information on roles and privileges, see {ref}/authorization.html[User authorization].
|
||||
|
||||
[role="screenshot"]
|
||||
image:management/dashboard_only_mode/images/custom_dashboard_mode_role.png["Dashboard Only mode has no editing controls"]
|
||||
|
||||
|
||||
[float]
|
||||
[[advanced-dashboard-mode-configuration]]
|
||||
=== Advanced settings for dashboard only mode
|
||||
|
||||
The `kibana_dashboard_only_user` role grants access to all spaces.
|
||||
If your setup requires access to a
|
||||
subset of spaces, you can create a custom role, and then tag it as Dashboard only mode.
|
||||
|
||||
. Go to *Management > Advanced Settings*, and search for `xpackDashboardMode:roles`.
|
||||
+
|
||||
By
|
||||
default, this is set to
|
||||
`kibana_dashboard_only_user`.
|
||||
|
||||
. Add as many roles as you require.
|
||||
+
|
||||
[role="screenshot"]
|
||||
image:management/dashboard_only_mode/images/advanced_dashboard_mode_role_setup.png["Advanced dashboard mode role setup"]
|
||||
|
BIN
docs/maps/images/grid_to_docs.gif
Normal file
After Width: | Height: | Size: 894 KiB |
|
@ -7,6 +7,14 @@ Use {ref}/search-aggregations.html[aggregations] to plot large data sets without
|
|||
Aggregations group your documents into buckets and calculate metrics for each bucket.
|
||||
Your documents stay in Elasticsearch and only the metrics for each group are returned to your computer.
|
||||
|
||||
Use aggregated layers with document layers to show aggregated views when the map shows larger
|
||||
amounts of the globe and individual documents when the map shows smaller regions.
|
||||
|
||||
In the following example, the Grid aggregation layer is only visible when the map is at zoom levels 0 through 5. The Documents layer is only visible when the map is at zoom levels 4 through 24.
|
||||
See the <<maps-add-elasticsearch-layer, Getting started>> tutorial for more details on configuring the layers.
|
||||
|
||||
[role="screenshot"]
|
||||
image::maps/images/grid_to_docs.gif[]
|
||||
|
||||
[role="xpack"]
|
||||
[[maps-grid-aggregation]]
|
||||
|
|
|
@ -150,6 +150,7 @@ image::maps/images/grid_metrics_both.png[]
|
|||
. In the map legend, click *Add layer*.
|
||||
. Click the *Grid aggregation* data source.
|
||||
. Set *Index pattern* to *kibana_sample_data_logs*.
|
||||
. Set *Show as* to *points*.
|
||||
. Click the *Add layer* button.
|
||||
. Set *Layer name* to `Total Requests and Bytes`.
|
||||
. Set *Zoom range for layer visibility* to the range [0, 9].
|
||||
|
@ -181,7 +182,7 @@ Now that your map is complete, you'll want to save it so others can use it.
|
|||
|
||||
. In the application toolbar, click *Save*.
|
||||
. Enter `Tutorial web logs map` for the title.
|
||||
. Click *Confirm Save*.
|
||||
. Click *Save*.
|
||||
+
|
||||
You have completed the steps for re-creating the sample data map.
|
||||
|
||||
|
|
|
@ -38,4 +38,10 @@ This page has moved. Please see <<xpack-logs-configuring>>.
|
|||
[role="exclude",id="extend"]
|
||||
== Extend your use case
|
||||
|
||||
This page was deleted. See <<xpack-graph>> and <<xpack-ml>>.
|
||||
This page was deleted. See <<xpack-graph>> and <<xpack-ml>>.
|
||||
|
||||
[role="exclude",id="xpack-dashboard-only-mode"]
|
||||
== Dashboard-only mode
|
||||
|
||||
Using the `kibana_dashboard_only_user` role is deprecated.
|
||||
Use <<kibana-feature-privileges,feature privileges>> instead.
|
||||
|
|
|
@ -32,7 +32,8 @@ strongly recommend that you keep the default CSP rules that ship with Kibana.
|
|||
|
||||
`csp.strict:`:: *Default: `true`* Blocks access to Kibana to any browser that
|
||||
does not enforce even rudimentary CSP rules. In practice, this will disable
|
||||
support for older, less safe browsers like Internet Explorer.
|
||||
support for older, less safe browsers like Internet Explorer.
|
||||
See <<csp-strict-mode, Content Security Policy>> for more information.
|
||||
|
||||
`csp.warnLegacyBrowsers:`:: *Default: `true`* Shows a warning message after
|
||||
loading Kibana to any browser that does not enforce even rudimentary CSP rules,
|
||||
|
@ -240,6 +241,10 @@ Kibana reads this url from an external metadata service, but users can still
|
|||
override this parameter to use their own Tile Map Service. For example:
|
||||
`"https://tiles.elastic.co/v2/default/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana"`
|
||||
|
||||
`newsfeed.enabled:` :: *Default: `true`* Controls whether to enable the newsfeed
|
||||
system for the Kibana UI notification center. Set to `false` to disable the
|
||||
newsfeed system.
|
||||
|
||||
`path.data:`:: *Default: `data`* The path where Kibana stores persistent data
|
||||
not saved in Elasticsearch.
|
||||
|
||||
|
@ -315,6 +320,18 @@ supported protocols with versions. Valid protocols: `TLSv1`, `TLSv1.1`, `TLSv1.2
|
|||
setting this to `true` enables unauthenticated users to access the Kibana
|
||||
server status API and status page.
|
||||
|
||||
`telemetry.allowChangingOptInStatus`:: *Default: true*. If `true`,
|
||||
users are able to change the telemetry setting at a later time in
|
||||
<<advanced-options, Advanced Settings>>. If `false`,
|
||||
{kib} looks at the value of `telemetry.optIn` to determine whether to send
|
||||
telemetry data or not. `telemetry.allowChangingOptInStatus` and `telemetry.optIn`
|
||||
cannot be `false` at the same time.
|
||||
|
||||
`telemetry.optIn`:: *Default: true* If `true`, telemetry data is sent to Elastic.
|
||||
If `false`, collection of telemetry data is disabled.
|
||||
To enable telemetry and prevent users from disabling it,
|
||||
set `telemetry.allowChangingOptInStatus` to `false` and `telemetry.optIn` to `true`.
|
||||
|
||||
`vega.enableExternalUrls:`:: *Default: false* Set this value to true to allow Vega to use any URL to access external data sources and images. If false, Vega can only get data from Elasticsearch.
|
||||
|
||||
`xpack.license_management.enabled`:: *Default: true* Set this value to false to
|
||||
|
|
|
@ -2,7 +2,15 @@
|
|||
== Upgrading {kib}
|
||||
|
||||
Depending on the {kib} version you're upgrading from, the upgrade process to 7.0
|
||||
varies.
|
||||
varies.
|
||||
|
||||
NOTE: {kib} upgrades automatically when starting a new version, as described in
|
||||
<<upgrade-migrations, this document>>.
|
||||
Although you do not need to manually back up {kib} before upgrading, we recommend
|
||||
that you have a backup on hand. You can use
|
||||
<<snapshot-repositories, Snapshot and Restore>> to back up {kib}
|
||||
data by targeting `.kibana*` indices. If you are using the Reporting plugin,
|
||||
you can also target `.reporting*` indices.
|
||||
|
||||
[float]
|
||||
[[upgrade-before-you-begin]]
|
||||
|
@ -12,7 +20,7 @@ Before you upgrade {kib}:
|
|||
|
||||
* Consult the <<breaking-changes,breaking changes>>.
|
||||
* Before you upgrade production servers, test the upgrades in a dev environment.
|
||||
* Backup your data with {es} {ref}/modules-snapshots.html[snapshots].
|
||||
* Back up your data with {es} {ref}/modules-snapshots.html[snapshots].
|
||||
To roll back to an earlier version, you **must** have a backup of your data.
|
||||
* If you are using custom plugins, check that a compatible version is
|
||||
available.
|
||||
|
|
|
@ -154,7 +154,6 @@ To open an element for editing, put the dashboard in *Edit* mode,
|
|||
and then select *Edit* from the panel menu. The changes you make appear in
|
||||
every dashboard that uses the element.
|
||||
|
||||
include::{kib-repo-dir}/management/dashboard_only_mode/index.asciidoc[]
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -59,14 +59,14 @@ image::user/graph/images/graph-read-only-badge.png[Example of Graph's read only
|
|||
|
||||
[discrete]
|
||||
[[disable-drill-down]]
|
||||
=== Disabling drill down configuration
|
||||
=== Disabling drilldown configuration
|
||||
|
||||
By default, users can configure _drill down_ URLs to display additional
|
||||
By default, users can configure _drilldown_ URLs to display additional
|
||||
information about a selected vertex in a new browser window. For example,
|
||||
you could configure a drill down URL to perform a web search for the selected
|
||||
you could configure a drilldown URL to perform a web search for the selected
|
||||
vertex term.
|
||||
|
||||
To prevent users from adding drill down URLs, set
|
||||
To prevent users from adding drilldown URLs, set
|
||||
`xpack.graph.canEditDrillDownUrls` to `false` in `kibana.yml`:
|
||||
|
||||
[source,yaml]
|
||||
|
|
|
@ -2,30 +2,30 @@
|
|||
[[graph-getting-started]]
|
||||
== Using Graph
|
||||
|
||||
Graph is automatically enabled in {es} and {kib}.
|
||||
You must index data into {es} before you can create a graph.
|
||||
<<index-patterns, Learn how>> or get started with a <<add-sample-data, sample data set>>.
|
||||
|
||||
[float]
|
||||
[[exploring-connections]]
|
||||
To start exploring connections in your data:
|
||||
=== Graph connections in your data
|
||||
|
||||
. From the side navigation, open the graph explorer.
|
||||
|
||||
. Select an index pattern to specify what indices you want to explore.
|
||||
. From the side navigation, open *Graph*.
|
||||
+
|
||||
For example, if you are indexing log data with Logstash, you could select the
|
||||
`logstash-*` index pattern to visualize connections within the log entries.
|
||||
If this is your first graph, follow the prompts to create it.
|
||||
For subsequent graphs, click *New*.
|
||||
|
||||
. Select one or more multi-value fields that contain the terms you want to
|
||||
. Select a data source to explore.
|
||||
|
||||
. Add one or more multi-value fields that contain the terms you want to
|
||||
graph.
|
||||
+
|
||||
The vertices in the graph are selected from these terms. If you're
|
||||
visualizing connections between Apache log entries, you could select the
|
||||
`url.raw` field and the `geo.src` field so you can look at which pages are
|
||||
being accessed from different locations.
|
||||
The vertices in the graph are selected from these terms.
|
||||
|
||||
. Enter a search query to discover relationships between terms in the selected
|
||||
fields.
|
||||
+
|
||||
For example, to generate a graph of the successful requests to
|
||||
For example, if you are using the {kib} sample web logs data set, and you want
|
||||
to generate a graph of the successful requests to
|
||||
particular pages from different locations, you could search for the 200
|
||||
response code. The weight of the connection between two vertices indicates how strongly they
|
||||
are related.
|
||||
|
@ -38,25 +38,86 @@ image::user/graph/images/graph-url-connections.png["URL connections"]
|
|||
[role="screenshot"]
|
||||
image::user/graph/images/graph-link-summary.png["Link summary"]
|
||||
|
||||
. Use the toolbar buttons to explore
|
||||
. Use the control bar on the right to explore
|
||||
additional connections:
|
||||
+
|
||||
* To display additional vertices that connect to your graph, click Expand
|
||||
image:user/graph/images/graph-expand-button.jpg[Expand Selection].
|
||||
* To display additional vertices that connect to your graph, click the expand icon
|
||||
image:user/graph/images/graph-expand-button.png[Expand Selection].
|
||||
* To display additional
|
||||
connections between the displayed vertices, click Link
|
||||
image:user/graph/images/graph-link-button.jpg[Add links to existing terms]
|
||||
connections between the displayed vertices, click the link icon
|
||||
image:user/graph/images/graph-link-button.png[Add links to existing terms].
|
||||
* To explore a particular area of the
|
||||
graph, select the vertices you are interested in, and click Expand or Link.
|
||||
* To step back through your changes to the graph, click Undo
|
||||
image:user/graph/images/graph-undo-button.jpg[Undo].
|
||||
graph, select the vertices you are interested in, and then click expand or link.
|
||||
* To step back through your changes to the graph, click undo
|
||||
image:user/graph/images/graph-undo-button.png[Undo] and redo
|
||||
image:user/graph/images/graph-redo-button.png[Redo].
|
||||
|
||||
. To see more relationships in your data, submit additional queries.
|
||||
+
|
||||
[role="screenshot"]
|
||||
image::user/graph/images/graph-add-query.png["Adding networks"]
|
||||
|
||||
. *Save* your graph.
|
||||
|
||||
NOTE: By default, when you submit a search query, Graph searches all available
|
||||
fields. You can constrain your search to a particular field using the Lucene
|
||||
query syntax. For example, `machine.os: osx`.
|
||||
[float]
|
||||
[[style-vertex-properties]]
|
||||
=== Style vertex properties
|
||||
|
||||
Each vertex has a color, icon, and label. To change
|
||||
the color or icon of all vertices
|
||||
of a certain field, click the field badge below the search bar, and then
|
||||
select *Edit settings*.
|
||||
|
||||
To change the color and label of selected vertices,
|
||||
click the style icon image:user/graph/images/graph-style-button.png[Style]
|
||||
in the control bar on the right.
|
||||
|
||||
|
||||
[float]
|
||||
[[edit-graph-settings]]
|
||||
=== Edit graph settings
|
||||
|
||||
By default, *Graph* is configured to tune out noise in your data.
|
||||
If this isn't a good fit for your data, use *Settings > Advanced settings*
|
||||
to adjust the way *Graph* queries your data. You can tune the graph to show
|
||||
only the results relevant to you and to improve performance.
|
||||
For more information, see <<graph-troubleshooting, Graph troubleshooting>>.
|
||||
|
||||
You can configure the number of vertices that a search or
|
||||
expand operation adds to the graph.
|
||||
By default, only the five most relevant terms for any given field are added
|
||||
at a time. This keeps the graph from overflowing. To increase this number, click
|
||||
a field below the search bar, select *Edit Settings*, and change *Terms per hop*.
|
||||
|
||||
[float]
|
||||
[[graph-block-terms]]
|
||||
=== Block terms from the graph
|
||||
Documents that match a blocked term are not allowed in the graph.
|
||||
To block a term, select its vertex and click
|
||||
the block icon
|
||||
image:user/graph/images/graph-block-button.png[Block selection]
|
||||
in the control panel.
|
||||
For a list of blocked terms, go to *Settings > Blocked terms*.
|
||||
|
||||
[float]
|
||||
[[graph-drill-down]]
|
||||
=== Drill down into raw documents
|
||||
With drilldowns, you can display additional information about a
|
||||
selected vertex in a new browser window. For example, you might
|
||||
configure a drilldown URL to perform a web search for the selected vertex term.
|
||||
|
||||
Use the drilldown icon image:user/graph/images/graph-info-icon.png[Drilldown selection]
|
||||
in the control panel to show the drilldown buttons for the selected vertices.
|
||||
To configure drilldowns, go to *Settings > Drilldowns*. See also
|
||||
<<disable-drill-down, Disabling drilldown configuration>>.
|
||||
|
||||
[float]
|
||||
[[graph-run-layout]]
|
||||
=== Run and pause layout
|
||||
Graph uses a "force layout", where vertices behave like magnets,
|
||||
pushing off of one another. By default, when you add a new vertex to
|
||||
the graph, all vertices begin moving. In some cases, the movement might
|
||||
go on for some time. To freeze the current vertex position,
|
||||
click the pause icon
|
||||
image:user/graph/images/graph-pause-button.png[Block selection]
|
||||
in the control panel.
|
||||
|
|
BIN
docs/user/graph/images/graph-add-query.png
Normal file → Executable file
Before Width: | Height: | Size: 376 KiB After Width: | Height: | Size: 303 KiB |
BIN
docs/user/graph/images/graph-block-button.png
Executable file
After Width: | Height: | Size: 807 B |
Before Width: | Height: | Size: 4.8 KiB |
BIN
docs/user/graph/images/graph-expand-button.png
Executable file
After Width: | Height: | Size: 616 B |
BIN
docs/user/graph/images/graph-info-icon.png
Normal file
After Width: | Height: | Size: 492 B |
Before Width: | Height: | Size: 4.8 KiB |
BIN
docs/user/graph/images/graph-link-button.png
Executable file
After Width: | Height: | Size: 756 B |
BIN
docs/user/graph/images/graph-link-summary.png
Normal file → Executable file
Before Width: | Height: | Size: 295 KiB After Width: | Height: | Size: 242 KiB |
BIN
docs/user/graph/images/graph-pause-button.png
Executable file
After Width: | Height: | Size: 493 B |
BIN
docs/user/graph/images/graph-redo-button.png
Executable file
After Width: | Height: | Size: 801 B |
BIN
docs/user/graph/images/graph-style-button.png
Normal file
After Width: | Height: | Size: 732 B |
Before Width: | Height: | Size: 4.8 KiB |
BIN
docs/user/graph/images/graph-undo-button.png
Executable file
After Width: | Height: | Size: 830 B |
BIN
docs/user/graph/images/graph-url-connections.png
Normal file → Executable file
Before Width: | Height: | Size: 296 KiB After Width: | Height: | Size: 221 KiB |
|
@ -1,6 +1,6 @@
|
|||
[role="xpack"]
|
||||
[[graph-troubleshooting]]
|
||||
== Graph Troubleshooting
|
||||
== Graph troubleshooting
|
||||
++++
|
||||
<titleabbrev>Troubleshooting</titleabbrev>
|
||||
++++
|
||||
|
|
|
@ -23,6 +23,23 @@ You cannot monitor a version 6.3 or later cluster from {kib} version 6.2 or earl
|
|||
To resolve this issue, upgrade {kib} to 6.3 or later. See
|
||||
{stack-ref}/upgrading-elastic-stack.html[Upgrading the {stack}].
|
||||
|
||||
[float]
|
||||
=== {filebeat} index is corrupt
|
||||
|
||||
*Symptoms:*
|
||||
|
||||
The *Stack Monitoring* application displays a Monitoring Request error indicating
|
||||
that an illegal argument exception has occurred because fielddata is disabled on
|
||||
text fields by default.
|
||||
|
||||
*Resolution*
|
||||
|
||||
. Stop all your {filebeat} instances.
|
||||
. Delete indices beginning with `filebeat-$VERSION`, where `VERSION` corresponds
|
||||
to the version of {filebeat} running.
|
||||
. Restart all your {filebeat} instances.
|
||||
|
||||
|
||||
[float]
|
||||
=== No monitoring data is visible in {kib}
|
||||
|
||||
|
|
14
package.json
|
@ -82,7 +82,7 @@
|
|||
"**/@types/react": "16.8.3",
|
||||
"**/@types/hapi": "^17.0.18",
|
||||
"**/@types/angular": "^1.6.56",
|
||||
"**/typescript": "3.5.3",
|
||||
"**/typescript": "3.7.2",
|
||||
"**/graphql-toolkit/lodash": "^4.17.13",
|
||||
"**/isomorphic-git/**/base64-js": "^1.2.1",
|
||||
"**/image-diff/gm/debug": "^2.6.9"
|
||||
|
@ -109,7 +109,7 @@
|
|||
"@elastic/charts": "^14.0.0",
|
||||
"@elastic/datemath": "5.0.2",
|
||||
"@elastic/ems-client": "1.0.5",
|
||||
"@elastic/eui": "14.8.0",
|
||||
"@elastic/eui": "14.9.0",
|
||||
"@elastic/filesaver": "1.1.2",
|
||||
"@elastic/good": "8.1.1-kibana2",
|
||||
"@elastic/numeral": "2.3.3",
|
||||
|
@ -348,8 +348,8 @@
|
|||
"@types/uuid": "^3.4.4",
|
||||
"@types/vinyl-fs": "^2.4.11",
|
||||
"@types/zen-observable": "^0.8.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.5.0",
|
||||
"@typescript-eslint/parser": "^2.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.7.0",
|
||||
"@typescript-eslint/parser": "^2.7.0",
|
||||
"angular-mocks": "^1.7.8",
|
||||
"archiver": "^3.1.1",
|
||||
"axe-core": "^3.3.2",
|
||||
|
@ -362,7 +362,7 @@
|
|||
"chance": "1.0.18",
|
||||
"cheerio": "0.22.0",
|
||||
"chokidar": "3.2.1",
|
||||
"chromedriver": "^77.0.0",
|
||||
"chromedriver": "78.0.1",
|
||||
"classnames": "2.2.6",
|
||||
"dedent": "^0.7.0",
|
||||
"delete-empty": "^2.0.0",
|
||||
|
@ -436,7 +436,7 @@
|
|||
"pngjs": "^3.4.0",
|
||||
"postcss": "^7.0.5",
|
||||
"postcss-url": "^8.0.0",
|
||||
"prettier": "^1.18.2",
|
||||
"prettier": "^1.19.1",
|
||||
"proxyquire": "1.8.0",
|
||||
"regenerate": "^1.4.0",
|
||||
"sass-lint": "^1.12.1",
|
||||
|
@ -447,7 +447,7 @@
|
|||
"supertest": "^3.1.0",
|
||||
"supertest-as-promised": "^4.0.2",
|
||||
"tree-kill": "^1.2.1",
|
||||
"typescript": "3.5.3",
|
||||
"typescript": "3.7.2",
|
||||
"typings-tester": "^0.3.2",
|
||||
"vinyl-fs": "^3.0.3",
|
||||
"xml2js": "^0.4.22",
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
},
|
||||
"homepage": "https://github.com/elastic/eslint-config-kibana#readme",
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^2.5.0",
|
||||
"@typescript-eslint/parser": "^2.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.7.0",
|
||||
"@typescript-eslint/parser": "^2.7.0",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"eslint": "^6.5.1",
|
||||
"eslint-plugin-babel": "^5.3.0",
|
||||
|
|
|
@ -17,6 +17,6 @@
|
|||
"@babel/cli": "7.5.5",
|
||||
"@kbn/dev-utils": "1.0.0",
|
||||
"@kbn/babel-preset": "1.0.0",
|
||||
"typescript": "3.5.3"
|
||||
"typescript": "3.7.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"kbn:bootstrap": "yarn build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "3.5.3"
|
||||
"typescript": "3.7.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"joi": "^13.5.2",
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`#scheme returns error when shorter string 1`] = `"expected URI with scheme [http|https] but but got [ftp://elastic.co]."`;
|
||||
exports[`#scheme returns error when shorter string 1`] = `"expected URI with scheme [http|https] but got [ftp://elastic.co]."`;
|
||||
|
||||
exports[`#scheme returns error when shorter string 2`] = `"expected URI with scheme [http|https] but but got [file:///kibana.log]."`;
|
||||
exports[`#scheme returns error when shorter string 2`] = `"expected URI with scheme [http|https] but got [file:///kibana.log]."`;
|
||||
|
||||
exports[`#validate throws when returns string 1`] = `"validator failure"`;
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ export class URIType extends Type<string> {
|
|||
case 'string.base':
|
||||
return `expected value of type [string] but got [${typeDetect(value)}].`;
|
||||
case 'string.uriCustomScheme':
|
||||
return `expected URI with scheme [${scheme}] but but got [${value}].`;
|
||||
return `expected URI with scheme [${scheme}] but got [${value}].`;
|
||||
case 'string.uri':
|
||||
return `value is [${value}] but it must be a valid URI (see RFC 3986).`;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"tslib": "^1.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "3.5.3",
|
||||
"typescript": "3.7.2",
|
||||
"@kbn/expect": "1.0.0",
|
||||
"chance": "1.0.18"
|
||||
}
|
||||
|
|
|
@ -29,10 +29,7 @@ import { first, ignoreElements, mergeMap } from 'rxjs/operators';
|
|||
*/
|
||||
export function observeReadable(readable: Readable): Rx.Observable<never> {
|
||||
return Rx.race(
|
||||
Rx.fromEvent(readable, 'end').pipe(
|
||||
first(),
|
||||
ignoreElements()
|
||||
),
|
||||
Rx.fromEvent(readable, 'end').pipe(first(), ignoreElements()),
|
||||
|
||||
Rx.fromEvent(readable, 'error').pipe(
|
||||
first(),
|
||||
|
|
|
@ -112,10 +112,7 @@ describe('#getWritten$()', () => {
|
|||
const done$ = new Rx.Subject();
|
||||
const promise = log
|
||||
.getWritten$()
|
||||
.pipe(
|
||||
takeUntil(done$),
|
||||
toArray()
|
||||
)
|
||||
.pipe(takeUntil(done$), toArray())
|
||||
.toPromise();
|
||||
|
||||
log.debug('foo');
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
"@babel/core": "^7.5.5",
|
||||
"@babel/plugin-transform-async-to-generator": "^7.5.0",
|
||||
"jest": "^24.9.0",
|
||||
"typescript": "3.5.3"
|
||||
"typescript": "3.7.2"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node"
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"del": "^5.1.0",
|
||||
"getopts": "^2.2.4",
|
||||
"supports-color": "^7.0.0",
|
||||
"typescript": "3.5.3"
|
||||
"typescript": "3.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"intl-format-cache": "^2.1.0",
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
"log-symbols": "^2.2.0",
|
||||
"ncp": "^2.0.0",
|
||||
"ora": "^1.4.0",
|
||||
"prettier": "^1.18.2",
|
||||
"prettier": "^1.19.1",
|
||||
"read-pkg": "^5.2.0",
|
||||
"rxjs": "^6.5.3",
|
||||
"spawn-sync": "^1.0.15",
|
||||
|
@ -58,7 +58,7 @@
|
|||
"strip-ansi": "^4.0.0",
|
||||
"strong-log-transformer": "^2.1.0",
|
||||
"tempy": "^0.3.0",
|
||||
"typescript": "3.5.3",
|
||||
"typescript": "3.7.2",
|
||||
"unlazy-loader": "^0.1.3",
|
||||
"webpack": "^4.41.0",
|
||||
"webpack-cli": "^3.3.9",
|
||||
|
|
|
@ -101,7 +101,12 @@ test('handles dependencies of dependencies', async () => {
|
|||
'packages/baz'
|
||||
);
|
||||
|
||||
const projects = new Map([['kibana', kibana], ['foo', foo], ['bar', bar], ['baz', baz]]);
|
||||
const projects = new Map([
|
||||
['kibana', kibana],
|
||||
['foo', foo],
|
||||
['bar', bar],
|
||||
['baz', baz],
|
||||
]);
|
||||
const projectGraph = buildProjectGraph(projects);
|
||||
|
||||
const logMock = jest.spyOn(console, 'log').mockImplementation(noop);
|
||||
|
@ -133,7 +138,10 @@ test('does not run installer if no deps in package', async () => {
|
|||
'packages/bar'
|
||||
);
|
||||
|
||||
const projects = new Map([['kibana', kibana], ['bar', bar]]);
|
||||
const projects = new Map([
|
||||
['kibana', kibana],
|
||||
['bar', bar],
|
||||
]);
|
||||
const projectGraph = buildProjectGraph(projects);
|
||||
|
||||
const logMock = jest.spyOn(console, 'log').mockImplementation(noop);
|
||||
|
@ -193,7 +201,10 @@ test('calls "kbn:bootstrap" scripts and links executables after installing deps'
|
|||
'packages/bar'
|
||||
);
|
||||
|
||||
const projects = new Map([['kibana', kibana], ['bar', bar]]);
|
||||
const projects = new Map([
|
||||
['kibana', kibana],
|
||||
['bar', bar],
|
||||
]);
|
||||
const projectGraph = buildProjectGraph(projects);
|
||||
|
||||
jest.spyOn(console, 'log').mockImplementation(noop);
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"homepage": "https://github.com/jbudz/spec-to-console#readme",
|
||||
"devDependencies": {
|
||||
"jest": "^24.9.0",
|
||||
"prettier": "^1.18.2"
|
||||
"prettier": "^1.19.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^3.0.0",
|
||||
|
|
|
@ -23,4 +23,5 @@ require('@kbn/test').runTestsCli([
|
|||
require.resolve('../test/api_integration/config.js'),
|
||||
require.resolve('../test/plugin_functional/config.js'),
|
||||
require.resolve('../test/interpreter_functional/config.js'),
|
||||
require.resolve('../test/ui_capabilities/newsfeed_err/config.ts'),
|
||||
]);
|
||||
|
|
|
@ -1130,7 +1130,7 @@ import { setup, start } from '../core_plugins/visualizations/public/legacy';
|
|||
| ------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `import 'ui/apply_filters'` | `import { ApplyFiltersPopover } from '../data/public'` | `import '../data/public/legacy` should be called to load legacy directives |
|
||||
| `import 'ui/filter_bar'` | `import { FilterBar } from '../data/public'` | `import '../data/public/legacy` should be called to load legacy directives |
|
||||
| `import 'ui/query_bar'` | `import { QueryBar, QueryBarInput } from '../data/public'` | Directives are deprecated. |
|
||||
| `import 'ui/query_bar'` | `import { QueryBarInput } from '../data/public'` | Directives are deprecated. |
|
||||
| `import 'ui/search_bar'` | `import { SearchBar } from '../data/public'` | Directive is deprecated. |
|
||||
| `import 'ui/kbn_top_nav'` | `import { TopNavMenu } from '../navigation/public'` | Directive is still available in `ui/kbn_top_nav`. |
|
||||
| `ui/saved_objects/components/saved_object_finder` | `import { SavedObjectFinder } from '../kibana_react/public'` | |
|
||||
|
|
|
@ -80,6 +80,12 @@ export interface App extends AppBase {
|
|||
* @returns An unmounting function that will be called to unmount the application.
|
||||
*/
|
||||
mount: (context: AppMountContext, params: AppMountParameters) => AppUnmount | Promise<AppUnmount>;
|
||||
|
||||
/**
|
||||
* Hide the UI chrome when the application is mounted. Defaults to `false`.
|
||||
* Takes precedence over chrome service visibility settings.
|
||||
*/
|
||||
chromeless?: boolean;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -145,12 +151,13 @@ export interface AppMountParameters {
|
|||
* export class MyPlugin implements Plugin {
|
||||
* setup({ application }) {
|
||||
* application.register({
|
||||
* id: 'my-app',
|
||||
* async mount(context, params) {
|
||||
* const { renderApp } = await import('./application');
|
||||
* return renderApp(context, params);
|
||||
* },
|
||||
* });
|
||||
* id: 'my-app',
|
||||
* async mount(context, params) {
|
||||
* const { renderApp } = await import('./application');
|
||||
* return renderApp(context, params);
|
||||
* },
|
||||
* });
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
|
|
|
@ -26,351 +26,423 @@ import { applicationServiceMock } from '../application/application_service.mock'
|
|||
import { httpServiceMock } from '../http/http_service.mock';
|
||||
import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock';
|
||||
import { notificationServiceMock } from '../notifications/notifications_service.mock';
|
||||
import { ChromeService } from './chrome_service';
|
||||
import { docLinksServiceMock } from '../doc_links/doc_links_service.mock';
|
||||
import { ChromeService } from './chrome_service';
|
||||
import { App } from '../application';
|
||||
|
||||
class FakeApp implements App {
|
||||
public title = `${this.id} App`;
|
||||
public mount = () => () => {};
|
||||
constructor(public id: string, public chromeless?: boolean) {}
|
||||
}
|
||||
const store = new Map();
|
||||
const originalLocalStorage = window.localStorage;
|
||||
|
||||
(window as any).localStorage = {
|
||||
setItem: (key: string, value: string) => store.set(String(key), String(value)),
|
||||
getItem: (key: string) => store.get(String(key)),
|
||||
removeItem: (key: string) => store.delete(String(key)),
|
||||
};
|
||||
|
||||
function defaultStartDeps() {
|
||||
return {
|
||||
function defaultStartDeps(availableApps?: App[]) {
|
||||
const deps = {
|
||||
application: applicationServiceMock.createInternalStartContract(),
|
||||
docLinks: docLinksServiceMock.createStartContract(),
|
||||
http: httpServiceMock.createStartContract(),
|
||||
injectedMetadata: injectedMetadataServiceMock.createStartContract(),
|
||||
notifications: notificationServiceMock.createStartContract(),
|
||||
};
|
||||
|
||||
if (availableApps) {
|
||||
deps.application.availableApps = new Map(availableApps.map(app => [app.id, app]));
|
||||
}
|
||||
|
||||
return deps;
|
||||
}
|
||||
|
||||
async function start({
|
||||
options = { browserSupportsCsp: true },
|
||||
cspConfigMock = { warnLegacyBrowsers: true },
|
||||
startDeps = defaultStartDeps(),
|
||||
}: { options?: any; cspConfigMock?: any; startDeps?: ReturnType<typeof defaultStartDeps> } = {}) {
|
||||
const service = new ChromeService(options);
|
||||
|
||||
if (cspConfigMock) {
|
||||
startDeps.injectedMetadata.getCspConfig.mockReturnValue(cspConfigMock);
|
||||
}
|
||||
|
||||
return {
|
||||
service,
|
||||
startDeps,
|
||||
chrome: await service.start(startDeps),
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
store.clear();
|
||||
window.history.pushState(undefined, '', '#/home?a=b');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
(window as any).localStorage = originalLocalStorage;
|
||||
});
|
||||
|
||||
describe('start', () => {
|
||||
it('adds legacy browser warning if browserSupportsCsp is disabled and warnLegacyBrowsers is enabled', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: false });
|
||||
const startDeps = defaultStartDeps();
|
||||
startDeps.injectedMetadata.getCspConfig.mockReturnValue({ warnLegacyBrowsers: true });
|
||||
await service.start(startDeps);
|
||||
const { startDeps } = await start({ options: { browserSupportsCsp: false } });
|
||||
|
||||
expect(startDeps.notifications.toasts.addWarning.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"Your browser does not meet the security requirements for Kibana.",
|
||||
],
|
||||
]
|
||||
`);
|
||||
Array [
|
||||
Array [
|
||||
"Your browser does not meet the security requirements for Kibana.",
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('does not add legacy browser warning if browser supports CSP', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: true });
|
||||
const startDeps = defaultStartDeps();
|
||||
startDeps.injectedMetadata.getCspConfig.mockReturnValue({ warnLegacyBrowsers: true });
|
||||
await service.start(startDeps);
|
||||
const { startDeps } = await start();
|
||||
|
||||
expect(startDeps.notifications.toasts.addWarning).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does not add legacy browser warning if warnLegacyBrowsers is disabled', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: false });
|
||||
const startDeps = defaultStartDeps();
|
||||
startDeps.injectedMetadata.getCspConfig.mockReturnValue({ warnLegacyBrowsers: false });
|
||||
await service.start(startDeps);
|
||||
const { startDeps } = await start({
|
||||
options: { browserSupportsCsp: false },
|
||||
cspConfigMock: { warnLegacyBrowsers: false },
|
||||
});
|
||||
|
||||
expect(startDeps.notifications.toasts.addWarning).not.toBeCalled();
|
||||
});
|
||||
|
||||
describe('getComponent', () => {
|
||||
it('returns a renderable React component', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: true });
|
||||
const start = await service.start(defaultStartDeps());
|
||||
const { chrome } = await start();
|
||||
|
||||
// Have to do some fanagling to get the type system and enzyme to accept this.
|
||||
// Don't capture the snapshot because it's 600+ lines long.
|
||||
expect(shallow(React.createElement(() => start.getHeaderComponent()))).toBeDefined();
|
||||
expect(shallow(React.createElement(() => chrome.getHeaderComponent()))).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('brand', () => {
|
||||
it('updates/emits the brand as it changes', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: true });
|
||||
const start = await service.start(defaultStartDeps());
|
||||
const promise = start
|
||||
const { chrome, service } = await start();
|
||||
const promise = chrome
|
||||
.getBrand$()
|
||||
.pipe(toArray())
|
||||
.toPromise();
|
||||
|
||||
start.setBrand({
|
||||
chrome.setBrand({
|
||||
logo: 'big logo',
|
||||
smallLogo: 'not so big logo',
|
||||
});
|
||||
start.setBrand({
|
||||
chrome.setBrand({
|
||||
logo: 'big logo without small logo',
|
||||
});
|
||||
service.stop();
|
||||
|
||||
await expect(promise).resolves.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {},
|
||||
Object {
|
||||
"logo": "big logo",
|
||||
"smallLogo": "not so big logo",
|
||||
},
|
||||
Object {
|
||||
"logo": "big logo without small logo",
|
||||
"smallLogo": undefined,
|
||||
},
|
||||
]
|
||||
`);
|
||||
Array [
|
||||
Object {},
|
||||
Object {
|
||||
"logo": "big logo",
|
||||
"smallLogo": "not so big logo",
|
||||
},
|
||||
Object {
|
||||
"logo": "big logo without small logo",
|
||||
"smallLogo": undefined,
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('visibility', () => {
|
||||
it('updates/emits the visibility', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: true });
|
||||
const start = await service.start(defaultStartDeps());
|
||||
const promise = start
|
||||
const { chrome, service } = await start();
|
||||
const promise = chrome
|
||||
.getIsVisible$()
|
||||
.pipe(toArray())
|
||||
.toPromise();
|
||||
|
||||
start.setIsVisible(true);
|
||||
start.setIsVisible(false);
|
||||
start.setIsVisible(true);
|
||||
chrome.setIsVisible(true);
|
||||
chrome.setIsVisible(false);
|
||||
chrome.setIsVisible(true);
|
||||
service.stop();
|
||||
|
||||
await expect(promise).resolves.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
]
|
||||
`);
|
||||
Array [
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('always emits false if embed query string is in hash when set up', async () => {
|
||||
it('always emits false if embed query string is preset when set up', async () => {
|
||||
window.history.pushState(undefined, '', '#/home?a=b&embed=true');
|
||||
|
||||
const service = new ChromeService({ browserSupportsCsp: true });
|
||||
const start = await service.start(defaultStartDeps());
|
||||
const promise = start
|
||||
const { chrome, service } = await start();
|
||||
const promise = chrome
|
||||
.getIsVisible$()
|
||||
.pipe(toArray())
|
||||
.toPromise();
|
||||
|
||||
start.setIsVisible(true);
|
||||
start.setIsVisible(false);
|
||||
start.setIsVisible(true);
|
||||
chrome.setIsVisible(true);
|
||||
chrome.setIsVisible(false);
|
||||
chrome.setIsVisible(true);
|
||||
service.stop();
|
||||
|
||||
await expect(promise).resolves.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
]
|
||||
`);
|
||||
Array [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('application-specified visibility on mount', async () => {
|
||||
const startDeps = defaultStartDeps([
|
||||
new FakeApp('alpha'), // An undefined `chromeless` is the same as setting to false.
|
||||
new FakeApp('beta', true),
|
||||
new FakeApp('gamma', false),
|
||||
]);
|
||||
const { availableApps, currentAppId$ } = startDeps.application;
|
||||
const { chrome, service } = await start({ startDeps });
|
||||
const promise = chrome
|
||||
.getIsVisible$()
|
||||
.pipe(toArray())
|
||||
.toPromise();
|
||||
|
||||
[...availableApps.keys()].forEach(appId => currentAppId$.next(appId));
|
||||
service.stop();
|
||||
|
||||
await expect(promise).resolves.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('changing visibility has no effect on chrome-hiding application', async () => {
|
||||
const startDeps = defaultStartDeps([new FakeApp('alpha', true)]);
|
||||
const { currentAppId$ } = startDeps.application;
|
||||
const { chrome, service } = await start({ startDeps });
|
||||
const promise = chrome
|
||||
.getIsVisible$()
|
||||
.pipe(toArray())
|
||||
.toPromise();
|
||||
|
||||
currentAppId$.next('alpha');
|
||||
chrome.setIsVisible(true);
|
||||
service.stop();
|
||||
|
||||
await expect(promise).resolves.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('is collapsed', () => {
|
||||
it('updates/emits isCollapsed', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: true });
|
||||
const start = await service.start(defaultStartDeps());
|
||||
const promise = start
|
||||
const { chrome, service } = await start();
|
||||
const promise = chrome
|
||||
.getIsCollapsed$()
|
||||
.pipe(toArray())
|
||||
.toPromise();
|
||||
|
||||
start.setIsCollapsed(true);
|
||||
start.setIsCollapsed(false);
|
||||
start.setIsCollapsed(true);
|
||||
chrome.setIsCollapsed(true);
|
||||
chrome.setIsCollapsed(false);
|
||||
chrome.setIsCollapsed(true);
|
||||
service.stop();
|
||||
|
||||
await expect(promise).resolves.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
]
|
||||
`);
|
||||
Array [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('only stores true in localStorage', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: true });
|
||||
const start = await service.start(defaultStartDeps());
|
||||
const { chrome } = await start();
|
||||
|
||||
start.setIsCollapsed(true);
|
||||
chrome.setIsCollapsed(true);
|
||||
expect(store.size).toBe(1);
|
||||
|
||||
start.setIsCollapsed(false);
|
||||
chrome.setIsCollapsed(false);
|
||||
expect(store.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('application classes', () => {
|
||||
it('updates/emits the application classes', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: true });
|
||||
const start = await service.start(defaultStartDeps());
|
||||
const promise = start
|
||||
const { chrome, service } = await start();
|
||||
const promise = chrome
|
||||
.getApplicationClasses$()
|
||||
.pipe(toArray())
|
||||
.toPromise();
|
||||
|
||||
start.addApplicationClass('foo');
|
||||
start.addApplicationClass('foo');
|
||||
start.addApplicationClass('bar');
|
||||
start.addApplicationClass('bar');
|
||||
start.addApplicationClass('baz');
|
||||
start.removeApplicationClass('bar');
|
||||
start.removeApplicationClass('foo');
|
||||
chrome.addApplicationClass('foo');
|
||||
chrome.addApplicationClass('foo');
|
||||
chrome.addApplicationClass('bar');
|
||||
chrome.addApplicationClass('bar');
|
||||
chrome.addApplicationClass('baz');
|
||||
chrome.removeApplicationClass('bar');
|
||||
chrome.removeApplicationClass('foo');
|
||||
service.stop();
|
||||
|
||||
await expect(promise).resolves.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [],
|
||||
Array [
|
||||
"foo",
|
||||
],
|
||||
Array [
|
||||
"foo",
|
||||
],
|
||||
Array [
|
||||
"foo",
|
||||
"bar",
|
||||
],
|
||||
Array [
|
||||
"foo",
|
||||
"bar",
|
||||
],
|
||||
Array [
|
||||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
],
|
||||
Array [
|
||||
"foo",
|
||||
"baz",
|
||||
],
|
||||
Array [
|
||||
"baz",
|
||||
],
|
||||
]
|
||||
`);
|
||||
Array [
|
||||
Array [],
|
||||
Array [
|
||||
"foo",
|
||||
],
|
||||
Array [
|
||||
"foo",
|
||||
],
|
||||
Array [
|
||||
"foo",
|
||||
"bar",
|
||||
],
|
||||
Array [
|
||||
"foo",
|
||||
"bar",
|
||||
],
|
||||
Array [
|
||||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
],
|
||||
Array [
|
||||
"foo",
|
||||
"baz",
|
||||
],
|
||||
Array [
|
||||
"baz",
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('badge', () => {
|
||||
it('updates/emits the current badge', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: true });
|
||||
const start = await service.start(defaultStartDeps());
|
||||
const promise = start
|
||||
const { chrome, service } = await start();
|
||||
const promise = chrome
|
||||
.getBadge$()
|
||||
.pipe(toArray())
|
||||
.toPromise();
|
||||
|
||||
start.setBadge({ text: 'foo', tooltip: `foo's tooltip` });
|
||||
start.setBadge({ text: 'bar', tooltip: `bar's tooltip` });
|
||||
start.setBadge(undefined);
|
||||
chrome.setBadge({ text: 'foo', tooltip: `foo's tooltip` });
|
||||
chrome.setBadge({ text: 'bar', tooltip: `bar's tooltip` });
|
||||
chrome.setBadge(undefined);
|
||||
service.stop();
|
||||
|
||||
await expect(promise).resolves.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
undefined,
|
||||
Object {
|
||||
"text": "foo",
|
||||
"tooltip": "foo's tooltip",
|
||||
},
|
||||
Object {
|
||||
"text": "bar",
|
||||
"tooltip": "bar's tooltip",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
`);
|
||||
Array [
|
||||
undefined,
|
||||
Object {
|
||||
"text": "foo",
|
||||
"tooltip": "foo's tooltip",
|
||||
},
|
||||
Object {
|
||||
"text": "bar",
|
||||
"tooltip": "bar's tooltip",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('breadcrumbs', () => {
|
||||
it('updates/emits the current set of breadcrumbs', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: true });
|
||||
const start = await service.start(defaultStartDeps());
|
||||
const promise = start
|
||||
const { chrome, service } = await start();
|
||||
const promise = chrome
|
||||
.getBreadcrumbs$()
|
||||
.pipe(toArray())
|
||||
.toPromise();
|
||||
|
||||
start.setBreadcrumbs([{ text: 'foo' }, { text: 'bar' }]);
|
||||
start.setBreadcrumbs([{ text: 'foo' }]);
|
||||
start.setBreadcrumbs([{ text: 'bar' }]);
|
||||
start.setBreadcrumbs([]);
|
||||
chrome.setBreadcrumbs([{ text: 'foo' }, { text: 'bar' }]);
|
||||
chrome.setBreadcrumbs([{ text: 'foo' }]);
|
||||
chrome.setBreadcrumbs([{ text: 'bar' }]);
|
||||
chrome.setBreadcrumbs([]);
|
||||
service.stop();
|
||||
|
||||
await expect(promise).resolves.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [],
|
||||
Array [
|
||||
Object {
|
||||
"text": "foo",
|
||||
},
|
||||
Object {
|
||||
"text": "bar",
|
||||
},
|
||||
],
|
||||
Array [
|
||||
Object {
|
||||
"text": "foo",
|
||||
},
|
||||
],
|
||||
Array [
|
||||
Object {
|
||||
"text": "bar",
|
||||
},
|
||||
],
|
||||
Array [],
|
||||
]
|
||||
`);
|
||||
Array [
|
||||
Array [],
|
||||
Array [
|
||||
Object {
|
||||
"text": "foo",
|
||||
},
|
||||
Object {
|
||||
"text": "bar",
|
||||
},
|
||||
],
|
||||
Array [
|
||||
Object {
|
||||
"text": "foo",
|
||||
},
|
||||
],
|
||||
Array [
|
||||
Object {
|
||||
"text": "bar",
|
||||
},
|
||||
],
|
||||
Array [],
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('help extension', () => {
|
||||
it('updates/emits the current help extension', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: true });
|
||||
const start = await service.start(defaultStartDeps());
|
||||
const promise = start
|
||||
const { chrome, service } = await start();
|
||||
const promise = chrome
|
||||
.getHelpExtension$()
|
||||
.pipe(toArray())
|
||||
.toPromise();
|
||||
|
||||
start.setHelpExtension(() => () => undefined);
|
||||
start.setHelpExtension(undefined);
|
||||
chrome.setHelpExtension(() => () => undefined);
|
||||
chrome.setHelpExtension(undefined);
|
||||
service.stop();
|
||||
|
||||
await expect(promise).resolves.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
undefined,
|
||||
[Function],
|
||||
undefined,
|
||||
]
|
||||
`);
|
||||
Array [
|
||||
undefined,
|
||||
[Function],
|
||||
undefined,
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('stop', () => {
|
||||
it('completes applicationClass$, isCollapsed$, breadcrumbs$, isVisible$, and brand$ observables', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: true });
|
||||
const start = await service.start(defaultStartDeps());
|
||||
const { chrome, service } = await start();
|
||||
const promise = Rx.combineLatest(
|
||||
start.getBrand$(),
|
||||
start.getApplicationClasses$(),
|
||||
start.getIsCollapsed$(),
|
||||
start.getBreadcrumbs$(),
|
||||
start.getIsVisible$(),
|
||||
start.getHelpExtension$()
|
||||
chrome.getBrand$(),
|
||||
chrome.getApplicationClasses$(),
|
||||
chrome.getIsCollapsed$(),
|
||||
chrome.getBreadcrumbs$(),
|
||||
chrome.getIsVisible$(),
|
||||
chrome.getHelpExtension$()
|
||||
).toPromise();
|
||||
|
||||
service.stop();
|
||||
|
@ -378,18 +450,17 @@ describe('stop', () => {
|
|||
});
|
||||
|
||||
it('completes immediately if service already stopped', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: true });
|
||||
const start = await service.start(defaultStartDeps());
|
||||
const { chrome, service } = await start();
|
||||
service.stop();
|
||||
|
||||
await expect(
|
||||
Rx.combineLatest(
|
||||
start.getBrand$(),
|
||||
start.getApplicationClasses$(),
|
||||
start.getIsCollapsed$(),
|
||||
start.getBreadcrumbs$(),
|
||||
start.getIsVisible$(),
|
||||
start.getHelpExtension$()
|
||||
chrome.getBrand$(),
|
||||
chrome.getApplicationClasses$(),
|
||||
chrome.getIsCollapsed$(),
|
||||
chrome.getBreadcrumbs$(),
|
||||
chrome.getIsVisible$(),
|
||||
chrome.getHelpExtension$()
|
||||
).toPromise()
|
||||
).resolves.toBe(undefined);
|
||||
});
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { BehaviorSubject, Observable, ReplaySubject } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, ReplaySubject, combineLatest, of, merge } from 'rxjs';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
import * as Url from 'url';
|
||||
import { parse } from 'url';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { IconType, Breadcrumb as EuiBreadcrumb } from '@elastic/eui';
|
||||
|
@ -41,11 +41,6 @@ export { ChromeNavControls, ChromeRecentlyAccessed, ChromeDocTitle };
|
|||
|
||||
const IS_COLLAPSED_KEY = 'core.chrome.isCollapsed';
|
||||
|
||||
function isEmbedParamInHash() {
|
||||
const { query } = Url.parse(String(window.location.hash).slice(1), true);
|
||||
return Boolean(query.embed);
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface ChromeBadge {
|
||||
text: string;
|
||||
|
@ -79,6 +74,9 @@ interface StartDeps {
|
|||
|
||||
/** @internal */
|
||||
export class ChromeService {
|
||||
private isVisible$!: Observable<boolean>;
|
||||
private appHidden$!: Observable<boolean>;
|
||||
private toggleHidden$!: BehaviorSubject<boolean>;
|
||||
private readonly stop$ = new ReplaySubject(1);
|
||||
private readonly navControls = new NavControlsService();
|
||||
private readonly navLinks = new NavLinksService();
|
||||
|
@ -87,6 +85,38 @@ export class ChromeService {
|
|||
|
||||
constructor(private readonly params: ConstructorParams) {}
|
||||
|
||||
/**
|
||||
* These observables allow consumers to toggle the chrome visibility via either:
|
||||
* 1. Using setIsVisible() to trigger the next chromeHidden$
|
||||
* 2. Setting `chromeless` when registering an application, which will
|
||||
* reset the visibility whenever the next application is mounted
|
||||
* 3. Having "embed" in the query string
|
||||
*/
|
||||
private initVisibility(application: StartDeps['application']) {
|
||||
// Start off the chrome service hidden if "embed" is in the hash query string.
|
||||
const isEmbedded = 'embed' in parse(location.hash.slice(1), true).query;
|
||||
|
||||
this.toggleHidden$ = new BehaviorSubject(isEmbedded);
|
||||
this.appHidden$ = merge(
|
||||
// Default the app being hidden to the same value initial value as the chrome visibility
|
||||
// in case the application service has not emitted an app ID yet, since we want to trigger
|
||||
// combineLatest below regardless of having an application value yet.
|
||||
of(isEmbedded),
|
||||
application.currentAppId$.pipe(
|
||||
map(
|
||||
appId =>
|
||||
!!appId &&
|
||||
application.availableApps.has(appId) &&
|
||||
!!application.availableApps.get(appId)!.chromeless
|
||||
)
|
||||
)
|
||||
);
|
||||
this.isVisible$ = combineLatest(this.appHidden$, this.toggleHidden$).pipe(
|
||||
map(([appHidden, chromeHidden]) => !(appHidden || chromeHidden)),
|
||||
takeUntil(this.stop$)
|
||||
);
|
||||
}
|
||||
|
||||
public async start({
|
||||
application,
|
||||
docLinks,
|
||||
|
@ -94,11 +124,10 @@ export class ChromeService {
|
|||
injectedMetadata,
|
||||
notifications,
|
||||
}: StartDeps): Promise<InternalChromeStart> {
|
||||
const FORCE_HIDDEN = isEmbedParamInHash();
|
||||
this.initVisibility(application);
|
||||
|
||||
const appTitle$ = new BehaviorSubject<string>('Kibana');
|
||||
const brand$ = new BehaviorSubject<ChromeBrand>({});
|
||||
const isVisible$ = new BehaviorSubject(true);
|
||||
const isCollapsed$ = new BehaviorSubject(!!localStorage.getItem(IS_COLLAPSED_KEY));
|
||||
const applicationClasses$ = new BehaviorSubject<Set<string>>(new Set());
|
||||
const helpExtension$ = new BehaviorSubject<ChromeHelpExtension | undefined>(undefined);
|
||||
|
@ -139,10 +168,7 @@ export class ChromeService {
|
|||
forceAppSwitcherNavigation$={navLinks.getForceAppSwitcherNavigation$()}
|
||||
helpExtension$={helpExtension$.pipe(takeUntil(this.stop$))}
|
||||
homeHref={http.basePath.prepend('/app/kibana#/home')}
|
||||
isVisible$={isVisible$.pipe(
|
||||
map(visibility => (FORCE_HIDDEN ? false : visibility)),
|
||||
takeUntil(this.stop$)
|
||||
)}
|
||||
isVisible$={this.isVisible$}
|
||||
kibanaVersion={injectedMetadata.getKibanaVersion()}
|
||||
legacyMode={injectedMetadata.getLegacyMode()}
|
||||
navLinks$={navLinks.getNavLinks$()}
|
||||
|
@ -166,15 +192,9 @@ export class ChromeService {
|
|||
);
|
||||
},
|
||||
|
||||
getIsVisible$: () =>
|
||||
isVisible$.pipe(
|
||||
map(visibility => (FORCE_HIDDEN ? false : visibility)),
|
||||
takeUntil(this.stop$)
|
||||
),
|
||||
getIsVisible$: () => this.isVisible$,
|
||||
|
||||
setIsVisible: (visibility: boolean) => {
|
||||
isVisible$.next(visibility);
|
||||
},
|
||||
setIsVisible: (isVisible: boolean) => this.toggleHidden$.next(!isVisible),
|
||||
|
||||
getIsCollapsed$: () => isCollapsed$.pipe(takeUntil(this.stop$)),
|
||||
|
||||
|
|
|
@ -18,6 +18,6 @@
|
|||
*/
|
||||
|
||||
export const ELASTIC_SUPPORT_LINK = 'https://support.elastic.co/';
|
||||
export const KIBANA_FEEDBACK_LINK = 'https://www.elastic.co/kibana/feedback';
|
||||
export const KIBANA_ASK_ELASTIC_LINK = 'https://www.elastic.co/kibana/ask-elastic';
|
||||
export const KIBANA_FEEDBACK_LINK = 'https://www.elastic.co/products/kibana/feedback';
|
||||
export const KIBANA_ASK_ELASTIC_LINK = 'https://www.elastic.co/products/kibana/ask-elastic';
|
||||
export const GITHUB_CREATE_ISSUE_LINK = 'https://github.com/elastic/kibana/issues/new/choose';
|
||||
|
|
|
@ -19,27 +19,34 @@
|
|||
|
||||
import { NavLinksService } from './nav_links_service';
|
||||
import { take, map, takeLast } from 'rxjs/operators';
|
||||
import { LegacyApp } from '../../application';
|
||||
import { App, LegacyApp } from '../../application';
|
||||
|
||||
const mockAppService = {
|
||||
availableApps: new Map(),
|
||||
availableLegacyApps: new Map<string, LegacyApp>([
|
||||
[
|
||||
'legacyApp1',
|
||||
{ id: 'legacyApp1', order: 0, title: 'Legacy App 1', icon: 'legacyApp1', appUrl: '/app1' },
|
||||
],
|
||||
[
|
||||
'legacyApp2',
|
||||
availableApps: new Map<string, App>(
|
||||
([
|
||||
{ id: 'app1', order: 0, title: 'App 1', icon: 'app1' },
|
||||
{
|
||||
id: 'app2',
|
||||
order: -10,
|
||||
title: 'App 2',
|
||||
euiIconType: 'canvasApp',
|
||||
},
|
||||
{ id: 'chromelessApp', order: 20, title: 'Chromless App', chromeless: true },
|
||||
] as App[]).map(app => [app.id, app])
|
||||
),
|
||||
availableLegacyApps: new Map<string, LegacyApp>(
|
||||
([
|
||||
{ id: 'legacyApp1', order: 5, title: 'Legacy App 1', icon: 'legacyApp1', appUrl: '/app1' },
|
||||
{
|
||||
id: 'legacyApp2',
|
||||
order: -10,
|
||||
order: -5,
|
||||
title: 'Legacy App 2',
|
||||
euiIconType: 'canvasApp',
|
||||
appUrl: '/app2',
|
||||
},
|
||||
],
|
||||
['legacyApp3', { id: 'legacyApp3', order: 20, title: 'Legacy App 3', appUrl: '/app3' }],
|
||||
]),
|
||||
{ id: 'legacyApp3', order: 15, title: 'Legacy App 3', appUrl: '/app3' },
|
||||
] as LegacyApp[]).map(app => [app.id, app])
|
||||
),
|
||||
} as any;
|
||||
|
||||
const mockHttp = {
|
||||
|
@ -58,6 +65,18 @@ describe('NavLinksService', () => {
|
|||
});
|
||||
|
||||
describe('#getNavLinks$()', () => {
|
||||
it('does not include `chromeless` applications', async () => {
|
||||
expect(
|
||||
await start
|
||||
.getNavLinks$()
|
||||
.pipe(
|
||||
take(1),
|
||||
map(links => links.map(l => l.id))
|
||||
)
|
||||
.toPromise()
|
||||
).not.toContain('chromelessApp');
|
||||
});
|
||||
|
||||
it('sorts navlinks by `order` property', async () => {
|
||||
expect(
|
||||
await start
|
||||
|
@ -67,7 +86,7 @@ describe('NavLinksService', () => {
|
|||
map(links => links.map(l => l.id))
|
||||
)
|
||||
.toPromise()
|
||||
).toEqual(['legacyApp2', 'legacyApp1', 'legacyApp3']);
|
||||
).toEqual(['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3']);
|
||||
});
|
||||
|
||||
it('emits multiple values', async () => {
|
||||
|
@ -78,8 +97,8 @@ describe('NavLinksService', () => {
|
|||
|
||||
service.stop();
|
||||
expect(emittedLinks).toEqual([
|
||||
['legacyApp2', 'legacyApp1', 'legacyApp3'],
|
||||
['legacyApp2', 'legacyApp1', 'legacyApp3'],
|
||||
['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3'],
|
||||
['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3'],
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -105,7 +124,13 @@ describe('NavLinksService', () => {
|
|||
|
||||
describe('#getAll()', () => {
|
||||
it('returns a sorted array of navlinks', () => {
|
||||
expect(start.getAll().map(l => l.id)).toEqual(['legacyApp2', 'legacyApp1', 'legacyApp3']);
|
||||
expect(start.getAll().map(l => l.id)).toEqual([
|
||||
'app2',
|
||||
'legacyApp2',
|
||||
'app1',
|
||||
'legacyApp1',
|
||||
'legacyApp3',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -130,7 +155,20 @@ describe('NavLinksService', () => {
|
|||
map(links => links.map(l => l.id))
|
||||
)
|
||||
.toPromise()
|
||||
).toEqual(['legacyApp2', 'legacyApp1', 'legacyApp3']);
|
||||
).toEqual(['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3']);
|
||||
});
|
||||
|
||||
it('does nothing on chromeless applications', async () => {
|
||||
start.showOnly('chromelessApp');
|
||||
expect(
|
||||
await start
|
||||
.getNavLinks$()
|
||||
.pipe(
|
||||
take(1),
|
||||
map(links => links.map(l => l.id))
|
||||
)
|
||||
.toPromise()
|
||||
).toEqual(['app2', 'legacyApp2', 'app1', 'legacyApp1', 'legacyApp3']);
|
||||
});
|
||||
|
||||
it('removes all other links', async () => {
|
||||
|
@ -157,7 +195,7 @@ describe('NavLinksService', () => {
|
|||
"icon": "legacyApp1",
|
||||
"id": "legacyApp1",
|
||||
"legacy": true,
|
||||
"order": 0,
|
||||
"order": 5,
|
||||
"title": "Legacy App 1",
|
||||
}
|
||||
`);
|
||||
|
|
|
@ -99,17 +99,19 @@ export class NavLinksService {
|
|||
private readonly stop$ = new ReplaySubject(1);
|
||||
|
||||
public start({ application, http }: StartDeps): ChromeNavLinks {
|
||||
const appLinks = [...application.availableApps].map(
|
||||
([appId, app]) =>
|
||||
[
|
||||
appId,
|
||||
new NavLinkWrapper({
|
||||
...app,
|
||||
legacy: false,
|
||||
baseUrl: relativeToAbsolute(http.basePath.prepend(`/app/${appId}`)),
|
||||
}),
|
||||
] as [string, NavLinkWrapper]
|
||||
);
|
||||
const appLinks = [...application.availableApps]
|
||||
.filter(([, app]) => !app.chromeless)
|
||||
.map(
|
||||
([appId, app]) =>
|
||||
[
|
||||
appId,
|
||||
new NavLinkWrapper({
|
||||
...app,
|
||||
legacy: false,
|
||||
baseUrl: relativeToAbsolute(http.basePath.prepend(`/app/${appId}`)),
|
||||
}),
|
||||
] as [string, NavLinkWrapper]
|
||||
);
|
||||
|
||||
const legacyAppLinks = [...application.availableLegacyApps].map(
|
||||
([appId, app]) =>
|
||||
|
@ -130,10 +132,7 @@ export class NavLinksService {
|
|||
|
||||
return {
|
||||
getNavLinks$: () => {
|
||||
return navLinks$.pipe(
|
||||
map(sortNavLinks),
|
||||
takeUntil(this.stop$)
|
||||
);
|
||||
return navLinks$.pipe(map(sortNavLinks), takeUntil(this.stop$));
|
||||
},
|
||||
|
||||
get(id: string) {
|
||||
|
@ -192,7 +191,10 @@ export class NavLinksService {
|
|||
}
|
||||
|
||||
function sortNavLinks(navLinks: ReadonlyMap<string, NavLinkWrapper>) {
|
||||
return sortBy([...navLinks.values()].map(link => link.properties), 'order');
|
||||
return sortBy(
|
||||
[...navLinks.values()].map(link => link.properties),
|
||||
'order'
|
||||
);
|
||||
}
|
||||
|
||||
function relativeToAbsolute(url: string) {
|
||||
|
|
|
@ -106,10 +106,7 @@ describe('RecentlyAccessed#start()', () => {
|
|||
const stop$ = new Subject();
|
||||
const observedValues$ = recentlyAccessed
|
||||
.get$()
|
||||
.pipe(
|
||||
bufferCount(3),
|
||||
takeUntil(stop$)
|
||||
)
|
||||
.pipe(bufferCount(3), takeUntil(stop$))
|
||||
.toPromise();
|
||||
recentlyAccessed.add('/app/item1', 'Item 1', 'item1');
|
||||
recentlyAccessed.add('/app/item2', 'Item 2', 'item2');
|
||||
|
|
|
@ -174,7 +174,10 @@ describe('#setup()', () => {
|
|||
it('injects legacy dependency to context#setup()', async () => {
|
||||
const pluginA = Symbol();
|
||||
const pluginB = Symbol();
|
||||
const pluginDependencies = new Map<symbol, symbol[]>([[pluginA, []], [pluginB, [pluginA]]]);
|
||||
const pluginDependencies = new Map<symbol, symbol[]>([
|
||||
[pluginA, []],
|
||||
[pluginB, [pluginA]],
|
||||
]);
|
||||
MockPluginsService.getOpaqueIds.mockReturnValue(pluginDependencies);
|
||||
await setupCore();
|
||||
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -68,18 +68,27 @@ describe('setup.getPlugins()', () => {
|
|||
it('returns injectedMetadata.uiPlugins', () => {
|
||||
const injectedMetadata = new InjectedMetadataService({
|
||||
injectedMetadata: {
|
||||
uiPlugins: [{ id: 'plugin-1', plugin: {} }, { id: 'plugin-2', plugin: {} }],
|
||||
uiPlugins: [
|
||||
{ id: 'plugin-1', plugin: {} },
|
||||
{ id: 'plugin-2', plugin: {} },
|
||||
],
|
||||
},
|
||||
} as any);
|
||||
|
||||
const plugins = injectedMetadata.setup().getPlugins();
|
||||
expect(plugins).toEqual([{ id: 'plugin-1', plugin: {} }, { id: 'plugin-2', plugin: {} }]);
|
||||
expect(plugins).toEqual([
|
||||
{ id: 'plugin-1', plugin: {} },
|
||||
{ id: 'plugin-2', plugin: {} },
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns frozen version of uiPlugins', () => {
|
||||
const injectedMetadata = new InjectedMetadataService({
|
||||
injectedMetadata: {
|
||||
uiPlugins: [{ id: 'plugin-1', plugin: {} }, { id: 'plugin-2', plugin: {} }],
|
||||
uiPlugins: [
|
||||
{ id: 'plugin-1', plugin: {} },
|
||||
{ id: 'plugin-2', plugin: {} },
|
||||
],
|
||||
},
|
||||
} as any);
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
exports[`renders matching snapshot 1`] = `
|
||||
<EuiGlobalToastList
|
||||
data-test-subj="globalToastList"
|
||||
dismissToast={[MockFunction]}
|
||||
dismissToast={[Function]}
|
||||
toastLifeTimeMs={Infinity}
|
||||
toasts={Array []}
|
||||
/>
|
||||
|
|
|
@ -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 }]);
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -17,9 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
OverlayBannerMount,
|
||||
OverlayBannerUnmount,
|
||||
OverlayBannersStart,
|
||||
OverlayBannersService,
|
||||
} from './banners_service';
|
||||
export { OverlayBannersStart, OverlayBannersService } from './banners_service';
|
||||
|
|
|
@ -42,7 +42,10 @@ describe('PriorityMap', () => {
|
|||
map = map.add('b', { priority: 3 });
|
||||
map = map.add('c', { priority: 2 });
|
||||
map = map.remove('c');
|
||||
expect([...map]).toEqual([['b', { priority: 3 }], ['a', { priority: 1 }]]);
|
||||
expect([...map]).toEqual([
|
||||
['b', { priority: 3 }],
|
||||
['a', { priority: 1 }],
|
||||
]);
|
||||
});
|
||||
|
||||
it('adds duplicate priorities to end', () => {
|
||||
|
|
|
@ -17,5 +17,5 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { OverlayBannerMount, OverlayBannerUnmount, OverlayBannersStart } from './banners';
|
||||
export { OverlayBannersStart } from './banners';
|
||||
export { OverlayService, OverlayStart, OverlayRef } from './overlay_service';
|
||||
|
|
|
@ -223,10 +223,13 @@ test('`PluginsService.setup` exposes dependent setup contracts to plugins', asyn
|
|||
|
||||
test('`PluginsService.setup` does not set missing dependent setup contracts', async () => {
|
||||
plugins = [{ id: 'pluginD', plugin: createManifest('pluginD', { optional: ['missing'] }) }];
|
||||
mockPluginInitializers.set('pluginD', jest.fn(() => ({
|
||||
setup: jest.fn(),
|
||||
start: jest.fn(),
|
||||
})) as any);
|
||||
mockPluginInitializers.set(
|
||||
'pluginD',
|
||||
jest.fn(() => ({
|
||||
setup: jest.fn(),
|
||||
start: jest.fn(),
|
||||
})) as any
|
||||
);
|
||||
|
||||
const pluginsService = new PluginsService(mockCoreContext, plugins);
|
||||
await pluginsService.setup(mockSetupDeps);
|
||||
|
@ -268,10 +271,13 @@ test('`PluginsService.start` exposes dependent start contracts to plugins', asyn
|
|||
|
||||
test('`PluginsService.start` does not set missing dependent start contracts', async () => {
|
||||
plugins = [{ id: 'pluginD', plugin: createManifest('pluginD', { optional: ['missing'] }) }];
|
||||
mockPluginInitializers.set('pluginD', jest.fn(() => ({
|
||||
setup: jest.fn(),
|
||||
start: jest.fn(),
|
||||
})) as any);
|
||||
mockPluginInitializers.set(
|
||||
'pluginD',
|
||||
jest.fn(() => ({
|
||||
setup: jest.fn(),
|
||||
start: jest.fn(),
|
||||
})) as any
|
||||
);
|
||||
|
||||
const pluginsService = new PluginsService(mockCoreContext, plugins);
|
||||
await pluginsService.setup(mockSetupDeps);
|
||||
|
|
|
@ -5,17 +5,18 @@
|
|||
```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';
|
||||
|
||||
// @public
|
||||
export interface App extends AppBase {
|
||||
chromeless?: boolean;
|
||||
mount: (context: AppMountContext, params: AppMountParameters) => AppUnmount | Promise<AppUnmount>;
|
||||
}
|
||||
|
||||
|
@ -618,6 +619,9 @@ export interface LegacyNavLink {
|
|||
url: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type MountPoint = (element: HTMLElement) => UnmountCallback;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface NotificationsSetup {
|
||||
// (undocumented)
|
||||
|
@ -630,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)
|
||||
|
@ -643,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>;
|
||||
|
@ -916,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)
|
||||
|
@ -990,5 +989,8 @@ export interface UiSettingsState {
|
|||
[key: string]: UiSettingsParams_2 & UserProvidedValues_2;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type UnmountCallback = () => void;
|
||||
|
||||
|
||||
```
|
||||
|
|
|
@ -17,34 +17,21 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { fromUser, toUser, getQueryLog } from './query_bar';
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Query Service
|
||||
* A function that will unmount the element previously mounted by
|
||||
* the associated {@link MountPoint}
|
||||
*
|
||||
* @internal
|
||||
* @public
|
||||
*/
|
||||
export class QueryService {
|
||||
public setup() {
|
||||
return {
|
||||
helpers: {
|
||||
fromUser,
|
||||
toUser,
|
||||
getQueryLog,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public start() {
|
||||
// nothing to do here yet
|
||||
}
|
||||
|
||||
public stop() {
|
||||
// nothing to do here yet
|
||||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type QuerySetup = ReturnType<QueryService['setup']>;
|
||||
|
||||
export * from './query_bar';
|
||||
export type UnmountCallback = () => void;
|
|
@ -183,10 +183,7 @@ describe('#getLoadingCount$()', () => {
|
|||
const done$ = new Rx.Subject();
|
||||
const promise = uiSettingsApi
|
||||
.getLoadingCount$()
|
||||
.pipe(
|
||||
takeUntil(done$),
|
||||
toArray()
|
||||
)
|
||||
.pipe(takeUntil(done$), toArray())
|
||||
.toPromise();
|
||||
|
||||
await uiSettingsApi.batchSet('foo', 'bar');
|
||||
|
@ -214,10 +211,7 @@ describe('#getLoadingCount$()', () => {
|
|||
const done$ = new Rx.Subject();
|
||||
const promise = uiSettingsApi
|
||||
.getLoadingCount$()
|
||||
.pipe(
|
||||
takeUntil(done$),
|
||||
toArray()
|
||||
)
|
||||
.pipe(takeUntil(done$), toArray())
|
||||
.toPromise();
|
||||
|
||||
await uiSettingsApi.batchSet('foo', 'bar');
|
||||
|
@ -250,7 +244,10 @@ describe('#stop', () => {
|
|||
uiSettingsApi.stop();
|
||||
|
||||
// both observables should emit the same values, and complete before the request is done loading
|
||||
await expect(promise).resolves.toEqual([[0, 1], [0, 1]]);
|
||||
await expect(promise).resolves.toEqual([
|
||||
[0, 1],
|
||||
[0, 1],
|
||||
]);
|
||||
await batchSetPromise;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -83,10 +83,7 @@ describe('#get$', () => {
|
|||
const { config } = setup();
|
||||
const values = await config
|
||||
.get$('dateFormat')
|
||||
.pipe(
|
||||
take(1),
|
||||
toArray()
|
||||
)
|
||||
.pipe(take(1), toArray())
|
||||
.toPromise();
|
||||
|
||||
expect(values).toEqual(['Browser']);
|
||||
|
@ -122,10 +119,7 @@ You can use \`config.get("unknown key", defaultValue)\`, which will just return
|
|||
|
||||
const values = await config
|
||||
.get$('dateFormat')
|
||||
.pipe(
|
||||
take(2),
|
||||
toArray()
|
||||
)
|
||||
.pipe(take(2), toArray())
|
||||
.toPromise();
|
||||
|
||||
expect(values).toEqual(['Browser', 'new format']);
|
||||
|
@ -144,10 +138,7 @@ You can use \`config.get("unknown key", defaultValue)\`, which will just return
|
|||
|
||||
const values = await config
|
||||
.get$('dateFormat', 'my default')
|
||||
.pipe(
|
||||
take(3),
|
||||
toArray()
|
||||
)
|
||||
.pipe(take(3), toArray())
|
||||
.toPromise();
|
||||
|
||||
expect(values).toEqual(['my default', 'new format', 'my default']);
|
||||
|
|
|
@ -19,3 +19,4 @@
|
|||
|
||||
export { shareWeakReplay } from './share_weak_replay';
|
||||
export { Sha256 } from './crypto';
|
||||
export { MountWrapper, mountReactNode } from './mount';
|
||||
|
|
44
src/core/public/utils/mount.tsx
Normal 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);
|
||||
};
|
|
@ -153,10 +153,7 @@ Array [
|
|||
});
|
||||
|
||||
it('resubscribes if parent completes', async () => {
|
||||
const shared = counter().pipe(
|
||||
take(4),
|
||||
shareWeakReplay(4)
|
||||
);
|
||||
const shared = counter().pipe(take(4), shareWeakReplay(4));
|
||||
|
||||
await expect(Promise.all([record(shared.pipe(take(1))), record(shared)])).resolves
|
||||
.toMatchInlineSnapshot(`
|
||||
|
@ -199,10 +196,7 @@ Array [
|
|||
it('supports parents that complete synchronously', async () => {
|
||||
const next = jest.fn();
|
||||
const complete = jest.fn();
|
||||
const shared = counter({ async: false }).pipe(
|
||||
take(3),
|
||||
shareWeakReplay(1)
|
||||
);
|
||||
const shared = counter({ async: false }).pipe(take(3), shareWeakReplay(1));
|
||||
|
||||
shared.subscribe({ next, complete });
|
||||
expect(next.mock.calls).toMatchInlineSnapshot(`
|
||||
|
|