diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 28380549c751c..c179dbadac533 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -39,7 +39,6 @@ #CC# /src/legacy/core_plugins/vis_type_vislib/ @elastic/kibana-app #CC# /src/legacy/server/url_shortening/ @elastic/kibana-app #CC# /src/legacy/ui/public/state_management @elastic/kibana-app -#CC# /src/plugins/index_pattern_management/public @elastic/kibana-app # App Architecture /examples/bfetch_explorer/ @elastic/kibana-app-arch @@ -70,23 +69,10 @@ /x-pack/plugins/data_enhanced/ @elastic/kibana-app-arch /x-pack/plugins/embeddable_enhanced/ @elastic/kibana-app-arch /x-pack/plugins/ui_actions_enhanced/ @elastic/kibana-app-arch -#CC# /src/legacy/core_plugins/kibana/public/management/ @elastic/kibana-app-arch -#CC# /src/legacy/core_plugins/kibana/server/routes/api/management/ @elastic/kibana-app-arch -#CC# /src/legacy/core_plugins/embeddable_api/ @elastic/kibana-app-arch -#CC# /src/legacy/core_plugins/interpreter/ @elastic/kibana-app-arch -#CC# /src/legacy/core_plugins/kibana_react/ @elastic/kibana-app-arch -#CC# /src/legacy/core_plugins/status_page/public @elastic/kibana-app-arch -#CC# /src/legacy/server/index_patterns/ @elastic/kibana-app-arch -#CC# /src/legacy/ui/public/field_editor @elastic/kibana-app-arch -#CC# /src/legacy/ui/public/management @elastic/kibana-app-arch -#CC# /src/plugins/advanced_settings/ @elastic/kibana-app-arch #CC# /src/plugins/bfetch/ @elastic/kibana-app-arch -#CC# /src/plugins/charts/ @elastic/kibana-app-arch -#CC# /src/plugins/index_pattern_management/public/service @elastic/kibana-app-arch +#CC# /src/plugins/index_pattern_management/ @elastic/kibana-app-arch #CC# /src/plugins/inspector/ @elastic/kibana-app-arch -#CC# /src/plugins/saved_objects/ @elastic/kibana-app-arch #CC# /src/plugins/share/ @elastic/kibana-app-arch -#CC# /src/plugins/vis_default_editor @elastic/kibana-app-arch #CC# /x-pack/plugins/advanced_ui_actions/ @elastic/kibana-app-arch #CC# /x-pack/plugins/drilldowns/ @elastic/kibana-app-arch #CC# /packages/kbn-interpreter/ @elastic/kibana-app-arch @@ -243,6 +229,7 @@ #CC# /src/legacy/ui/public/documentation_links @elastic/kibana-platform #CC# /src/legacy/ui/public/autoload @elastic/kibana-platform #CC# /src/plugins/legacy_export/ @elastic/kibana-platform +#CC# /src/plugins/saved_objects/ @elastic/kibana-platform #CC# /src/plugins/status_page/ @elastic/kibana-platform #CC# /src/plugins/testbed/server/ @elastic/kibana-platform #CC# /x-pack/legacy/plugins/xpack_main/server/ @elastic/kibana-platform diff --git a/docs/api/saved-objects/rotate_encryption_key.asciidoc b/docs/api/saved-objects/rotate_encryption_key.asciidoc index 0a66ed2b4b361..efc57ddb4308d 100644 --- a/docs/api/saved-objects/rotate_encryption_key.asciidoc +++ b/docs/api/saved-objects/rotate_encryption_key.asciidoc @@ -25,7 +25,7 @@ Bulk key rotation can consume a considerable amount of resources and hence only `type`:: (Optional, string) Limits encryption key rotation only to the saved objects with the specified type. By default, {kib} tries to rotate the encryption key for all saved object types that may contain encrypted attributes. -`batchSize`:: +`batch_size`:: (Optional, number) Specifies a maximum number of saved objects that {kib} can process in a single batch. Bulk key rotation is an iterative process since {kib} may not be able to fetch and process all required saved objects in one go and splits processing into consequent batches. By default, the batch size is 10000, which is also a maximum allowed value. [[saved-objects-api-rotate-encryption-key-response-body]] @@ -91,7 +91,7 @@ In this example, key rotation is performed for all saved objects with the `alert [source,sh] -------------------------------------------------- -$ curl -X POST /api/encrypted_saved_objects/_rotate_key?type=alert&batchSize=5000 +$ curl -X POST /api/encrypted_saved_objects/_rotate_key?type=alert&batch_size=5000 -------------------------------------------------- // KIBANA diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index dee6e4777884c..d0808d73151b0 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -22,8 +22,10 @@ NOTE: |Description -|{kib-repo}blob/{branch}/src/plugins/advanced_settings[advancedSettings] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/src/plugins/advanced_settings/README.md[advancedSettings] +|This plugin contains the advanced settings management section +allowing users to configure their advanced settings, also known +as uiSettings within the code. |{kib-repo}blob/{branch}/src/plugins/apm_oss[apmOss] @@ -130,8 +132,10 @@ in Kibana, e.g. visualizations. It has the form of a flyout panel. |WARNING: Missing README. -|{kib-repo}blob/{branch}/src/plugins/management[management] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/src/plugins/management/README.md[management] +|This plugins contains the "Stack Management" page framework. It offers navigation and an API +to link individual managment section into it. This plugin does not contain any individual +management section itself. |{kib-repo}blob/{branch}/src/plugins/maps_legacy/README.md[mapsLegacy] @@ -323,8 +327,8 @@ Failure to have auth enabled in Kibana will make for a broken UI. UI-based error |The deprecated dashboard only mode. -|{kib-repo}blob/{branch}/x-pack/plugins/data_enhanced[dataEnhanced] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/x-pack/plugins/data_enhanced/README.md[dataEnhanced] +|The data_enhanced plugin is the x-pack counterpart to the OSS data plugin. |{kib-repo}blob/{branch}/x-pack/plugins/discover_enhanced/README.md[discoverEnhanced] diff --git a/docs/development/core/public/kibana-plugin-core-public.chromestart.getbreadcrumbsappendextension_.md b/docs/development/core/public/kibana-plugin-core-public.chromestart.getbreadcrumbsappendextension_.md new file mode 100644 index 0000000000000..dfe25c5c9e42d --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.chromestart.getbreadcrumbsappendextension_.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeStart](./kibana-plugin-core-public.chromestart.md) > [getBreadcrumbsAppendExtension$](./kibana-plugin-core-public.chromestart.getbreadcrumbsappendextension_.md) + +## ChromeStart.getBreadcrumbsAppendExtension$() method + +Get an observable of the current extension appended to breadcrumbs + +Signature: + +```typescript +getBreadcrumbsAppendExtension$(): Observable; +``` +Returns: + +`Observable` + diff --git a/docs/development/core/public/kibana-plugin-core-public.chromestart.md b/docs/development/core/public/kibana-plugin-core-public.chromestart.md index 2594848ef0847..663b326193de5 100644 --- a/docs/development/core/public/kibana-plugin-core-public.chromestart.md +++ b/docs/development/core/public/kibana-plugin-core-public.chromestart.md @@ -55,6 +55,7 @@ core.chrome.setHelpExtension(elem => { | [getBadge$()](./kibana-plugin-core-public.chromestart.getbadge_.md) | Get an observable of the current badge | | [getBrand$()](./kibana-plugin-core-public.chromestart.getbrand_.md) | Get an observable of the current brand information. | | [getBreadcrumbs$()](./kibana-plugin-core-public.chromestart.getbreadcrumbs_.md) | Get an observable of the current list of breadcrumbs | +| [getBreadcrumbsAppendExtension$()](./kibana-plugin-core-public.chromestart.getbreadcrumbsappendextension_.md) | Get an observable of the current extension appended to breadcrumbs | | [getCustomNavLink$()](./kibana-plugin-core-public.chromestart.getcustomnavlink_.md) | Get an observable of the current custom nav link | | [getHelpExtension$()](./kibana-plugin-core-public.chromestart.gethelpextension_.md) | Get an observable of the current custom help conttent | | [getIsNavDrawerLocked$()](./kibana-plugin-core-public.chromestart.getisnavdrawerlocked_.md) | Get an observable of the current locked state of the nav drawer. | @@ -64,6 +65,7 @@ core.chrome.setHelpExtension(elem => { | [setBadge(badge)](./kibana-plugin-core-public.chromestart.setbadge.md) | Override the current badge | | [setBrand(brand)](./kibana-plugin-core-public.chromestart.setbrand.md) | Set the brand configuration. | | [setBreadcrumbs(newBreadcrumbs)](./kibana-plugin-core-public.chromestart.setbreadcrumbs.md) | Override the current set of breadcrumbs | +| [setBreadcrumbsAppendExtension(breadcrumbsAppendExtension)](./kibana-plugin-core-public.chromestart.setbreadcrumbsappendextension.md) | Mount an element next to the last breadcrumb | | [setCustomNavLink(newCustomNavLink)](./kibana-plugin-core-public.chromestart.setcustomnavlink.md) | Override the current set of custom nav link | | [setHelpExtension(helpExtension)](./kibana-plugin-core-public.chromestart.sethelpextension.md) | Override the current set of custom help content | | [setHelpSupportUrl(url)](./kibana-plugin-core-public.chromestart.sethelpsupporturl.md) | Override the default support URL shown in the help menu | diff --git a/docs/development/core/public/kibana-plugin-core-public.chromestart.setbreadcrumbsappendextension.md b/docs/development/core/public/kibana-plugin-core-public.chromestart.setbreadcrumbsappendextension.md new file mode 100644 index 0000000000000..02adb9b4d325d --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.chromestart.setbreadcrumbsappendextension.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ChromeStart](./kibana-plugin-core-public.chromestart.md) > [setBreadcrumbsAppendExtension](./kibana-plugin-core-public.chromestart.setbreadcrumbsappendextension.md) + +## ChromeStart.setBreadcrumbsAppendExtension() method + +Mount an element next to the last breadcrumb + +Signature: + +```typescript +setBreadcrumbsAppendExtension(breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| breadcrumbsAppendExtension | ChromeBreadcrumbsAppendExtension | | + +Returns: + +`void` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md index bbf856480aedd..b2f8e83d8e654 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md @@ -17,6 +17,6 @@ export interface ISearchSetup | Property | Type | Description | | --- | --- | --- | | [aggs](./kibana-plugin-plugins-data-public.isearchsetup.aggs.md) | AggsSetup | | -| [session](./kibana-plugin-plugins-data-public.isearchsetup.session.md) | ISessionService | session management | +| [session](./kibana-plugin-plugins-data-public.isearchsetup.session.md) | ISessionService | session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) | | [usageCollector](./kibana-plugin-plugins-data-public.isearchsetup.usagecollector.md) | SearchUsageCollector | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.session.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.session.md index 7f39d9714a3a3..739fdfdeb5fc3 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.session.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.session.md @@ -4,7 +4,7 @@ ## ISearchSetup.session property -session management +session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) Signature: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md index 4a69e94dd6f58..dba60c7bdf147 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md @@ -19,6 +19,6 @@ export interface ISearchStart | [aggs](./kibana-plugin-plugins-data-public.isearchstart.aggs.md) | AggsStart | agg config sub service [AggsStart](./kibana-plugin-plugins-data-public.aggsstart.md) | | [search](./kibana-plugin-plugins-data-public.isearchstart.search.md) | ISearchGeneric | low level search [ISearchGeneric](./kibana-plugin-plugins-data-public.isearchgeneric.md) | | [searchSource](./kibana-plugin-plugins-data-public.isearchstart.searchsource.md) | ISearchStartSearchSource | high level search [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) | -| [session](./kibana-plugin-plugins-data-public.isearchstart.session.md) | ISessionService | session management | +| [session](./kibana-plugin-plugins-data-public.isearchstart.session.md) | ISessionService | session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) | | [showError](./kibana-plugin-plugins-data-public.isearchstart.showerror.md) | (e: Error) => void | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.session.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.session.md index de25cccd6d27a..1ad194a9bec86 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.session.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.session.md @@ -4,7 +4,7 @@ ## ISearchStart.session property -session management +session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) Signature: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.clear.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.clear.md new file mode 100644 index 0000000000000..fc3d214eb4cad --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.clear.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [clear](./kibana-plugin-plugins-data-public.isessionservice.clear.md) + +## ISessionService.clear property + +Clears the active session. + +Signature: + +```typescript +clear: () => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsession_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsession_.md new file mode 100644 index 0000000000000..e30c89fb1a9fd --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsession_.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [getSession$](./kibana-plugin-plugins-data-public.isessionservice.getsession_.md) + +## ISessionService.getSession$ property + +Returns the observable that emits an update every time the session ID changes + +Signature: + +```typescript +getSession$: () => Observable; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsessionid.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsessionid.md new file mode 100644 index 0000000000000..838023ff1d8b9 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsessionid.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [getSessionId](./kibana-plugin-plugins-data-public.isessionservice.getsessionid.md) + +## ISessionService.getSessionId property + +Returns the active session ID + +Signature: + +```typescript +getSessionId: () => string | undefined; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md new file mode 100644 index 0000000000000..174f9dbe66bf4 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) + +## ISessionService interface + +Signature: + +```typescript +export interface ISessionService +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [clear](./kibana-plugin-plugins-data-public.isessionservice.clear.md) | () => void | Clears the active session. | +| [getSession$](./kibana-plugin-plugins-data-public.isessionservice.getsession_.md) | () => Observable<string | undefined> | Returns the observable that emits an update every time the session ID changes | +| [getSessionId](./kibana-plugin-plugins-data-public.isessionservice.getsessionid.md) | () => string | undefined | Returns the active session ID | +| [restore](./kibana-plugin-plugins-data-public.isessionservice.restore.md) | (sessionId: string) => void | Restores existing session | +| [start](./kibana-plugin-plugins-data-public.isessionservice.start.md) | () => string | Starts a new session | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md new file mode 100644 index 0000000000000..857e85bbd30eb --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [restore](./kibana-plugin-plugins-data-public.isessionservice.restore.md) + +## ISessionService.restore property + +Restores existing session + +Signature: + +```typescript +restore: (sessionId: string) => void; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.start.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.start.md new file mode 100644 index 0000000000000..9e14c5ed26765 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.start.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [start](./kibana-plugin-plugins-data-public.isessionservice.start.md) + +## ISessionService.start property + +Starts a new session + +Signature: + +```typescript +start: () => string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 6a3c437305cc8..ac6923fd12f96 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -74,6 +74,7 @@ | [ISearchSetup](./kibana-plugin-plugins-data-public.isearchsetup.md) | The setup contract exposed by the Search plugin exposes the search strategy extension point. | | [ISearchStart](./kibana-plugin-plugins-data-public.isearchstart.md) | search service | | [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) | high level search service | +| [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) | | | [KueryNode](./kibana-plugin-plugins-data-public.kuerynode.md) | | | [OptionedValueProp](./kibana-plugin-plugins-data-public.optionedvalueprop.md) | | | [QueryState](./kibana-plugin-plugins-data-public.querystate.md) | All query state service state | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md new file mode 100644 index 0000000000000..62610624655a1 --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [isContextMenuTriggerContext](./kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md) + +## isContextMenuTriggerContext variable + +Signature: + +```typescript +isContextMenuTriggerContext: (context: unknown) => context is EmbeddableContext +``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md index df67eda5074b9..06f792837e4fe 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md @@ -77,6 +77,7 @@ | [contextMenuTrigger](./kibana-plugin-plugins-embeddable-public.contextmenutrigger.md) | | | [defaultEmbeddableFactoryProvider](./kibana-plugin-plugins-embeddable-public.defaultembeddablefactoryprovider.md) | | | [EmbeddableRenderer](./kibana-plugin-plugins-embeddable-public.embeddablerenderer.md) | Helper react component to render an embeddable Can be used if you have an embeddable object or an embeddable factory Supports updating input by passing input prop | +| [isContextMenuTriggerContext](./kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md) | | | [isRangeSelectTriggerContext](./kibana-plugin-plugins-embeddable-public.israngeselecttriggercontext.md) | | | [isValueClickTriggerContext](./kibana-plugin-plugins-embeddable-public.isvalueclicktriggercontext.md) | | | [PANEL\_BADGE\_TRIGGER](./kibana-plugin-plugins-embeddable-public.panel_badge_trigger.md) | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.md index aefd04112dc1c..3cc38a0cbdc0f 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.md @@ -39,6 +39,8 @@ export declare class Executor = Record + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [Executor](./kibana-plugin-plugins-expressions-public.executor.md) > [migrate](./kibana-plugin-plugins-expressions-public.executor.migrate.md) + +## Executor.migrate() method + +Signature: + +```typescript +migrate(ast: SerializableState, version: string): ExpressionAstExpression; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| ast | SerializableState | | +| version | string | | + +Returns: + +`ExpressionAstExpression` + diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.migratetolatest.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.migratetolatest.md new file mode 100644 index 0000000000000..23b7e6035a0ae --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.migratetolatest.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [Executor](./kibana-plugin-plugins-expressions-public.executor.md) > [migrateToLatest](./kibana-plugin-plugins-expressions-public.executor.migratetolatest.md) + +## Executor.migrateToLatest() method + +Signature: + +```typescript +migrateToLatest(ast: unknown, version: string): ExpressionAstExpression; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| ast | unknown | | +| version | string | | + +Returns: + +`ExpressionAstExpression` + diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunction.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunction.md index 1815d63d804b1..3e75e9ab3ef6f 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunction.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunction.md @@ -29,6 +29,7 @@ export declare class ExpressionFunction implements PersistableStatestring | A short help text. | | [inject](./kibana-plugin-plugins-expressions-public.expressionfunction.inject.md) | | (state: ExpressionAstFunction['arguments'], references: SavedObjectReference[]) => ExpressionAstFunction['arguments'] | | | [inputTypes](./kibana-plugin-plugins-expressions-public.expressionfunction.inputtypes.md) | | string[] | undefined | Type of inputs that this function supports. | +| [migrations](./kibana-plugin-plugins-expressions-public.expressionfunction.migrations.md) | | {
[key: string]: (state: SerializableState) => SerializableState;
} | | | [name](./kibana-plugin-plugins-expressions-public.expressionfunction.name.md) | | string | Name of function | | [telemetry](./kibana-plugin-plugins-expressions-public.expressionfunction.telemetry.md) | | (state: ExpressionAstFunction['arguments'], telemetryData: Record<string, any>) => Record<string, any> | | | [type](./kibana-plugin-plugins-expressions-public.expressionfunction.type.md) | | string | Return type of function. This SHOULD be supplied. We use it for UI and autocomplete hinting. We may also use it for optimizations in the future. | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunction.migrations.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunction.migrations.md new file mode 100644 index 0000000000000..28d521f4b3fe1 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunction.migrations.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionFunction](./kibana-plugin-plugins-expressions-public.expressionfunction.md) > [migrations](./kibana-plugin-plugins-expressions-public.expressionfunction.migrations.md) + +## ExpressionFunction.migrations property + +Signature: + +```typescript +migrations: { + [key: string]: (state: SerializableState) => SerializableState; + }; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.derivative.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.derivative.md new file mode 100644 index 0000000000000..cce7a463d1561 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.derivative.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionFunctionDefinitions](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md) > [derivative](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.derivative.md) + +## ExpressionFunctionDefinitions.derivative property + +Signature: + +```typescript +derivative: ExpressionFunctionDerivative; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md index d1703a1e019e6..0e180d1fabe39 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.md @@ -18,6 +18,7 @@ export interface ExpressionFunctionDefinitions | --- | --- | --- | | [clog](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.clog.md) | ExpressionFunctionClog | | | [cumulative\_sum](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.cumulative_sum.md) | ExpressionFunctionCumulativeSum | | +| [derivative](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.derivative.md) | ExpressionFunctionDerivative | | | [font](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.font.md) | ExpressionFunctionFont | | | [kibana\_context](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana_context.md) | ExpressionFunctionKibanaContext | | | [kibana](./kibana-plugin-plugins-expressions-public.expressionfunctiondefinitions.kibana.md) | ExpressionFunctionKibana | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.md index 041d66b22dd50..307fc73ec6e9c 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.md @@ -39,6 +39,8 @@ export declare class ExpressionsService implements PersistableStateExpressionsServiceStart['getType'] | | | [getTypes](./kibana-plugin-plugins-expressions-public.expressionsservice.gettypes.md) | | () => ReturnType<Executor['getTypes']> | Returns POJO map of all registered expression types, where keys are names of the types and values are ExpressionType instances. | | [inject](./kibana-plugin-plugins-expressions-public.expressionsservice.inject.md) | | (state: ExpressionAstExpression, references: SavedObjectReference[]) => ExpressionAstExpression | Injects saved object references into expression AST | +| [migrate](./kibana-plugin-plugins-expressions-public.expressionsservice.migrate.md) | | (state: SerializableState, version: string) => ExpressionAstExpression | Injects saved object references into expression AST | +| [migrateToLatest](./kibana-plugin-plugins-expressions-public.expressionsservice.migratetolatest.md) | | (state: unknown, version: string) => ExpressionAstExpression | Injects saved object references into expression AST | | [registerFunction](./kibana-plugin-plugins-expressions-public.expressionsservice.registerfunction.md) | | (functionDefinition: AnyExpressionFunctionDefinition | (() => AnyExpressionFunctionDefinition)) => void | Register an expression function, which will be possible to execute as part of the expression pipeline.Below we register a function which simply sleeps for given number of milliseconds to delay the execution and outputs its input as-is. ```ts expressions.registerFunction({ diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migrate.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migrate.md new file mode 100644 index 0000000000000..88a6bda4ee3f5 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migrate.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionsService](./kibana-plugin-plugins-expressions-public.expressionsservice.md) > [migrate](./kibana-plugin-plugins-expressions-public.expressionsservice.migrate.md) + +## ExpressionsService.migrate property + +Injects saved object references into expression AST + +Signature: + +```typescript +readonly migrate: (state: SerializableState, version: string) => ExpressionAstExpression; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migratetolatest.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migratetolatest.md new file mode 100644 index 0000000000000..e6860df19fd3f --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migratetolatest.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionsService](./kibana-plugin-plugins-expressions-public.expressionsservice.md) > [migrateToLatest](./kibana-plugin-plugins-expressions-public.expressionsservice.migratetolatest.md) + +## ExpressionsService.migrateToLatest property + +Injects saved object references into expression AST + +Signature: + +```typescript +readonly migrateToLatest: (state: unknown, version: string) => ExpressionAstExpression; +``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.md index 97bb3ac895084..da20ae4aa892e 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.md @@ -39,6 +39,8 @@ export declare class Executor = Record + +[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [Executor](./kibana-plugin-plugins-expressions-server.executor.md) > [migrate](./kibana-plugin-plugins-expressions-server.executor.migrate.md) + +## Executor.migrate() method + +Signature: + +```typescript +migrate(ast: SerializableState, version: string): ExpressionAstExpression; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| ast | SerializableState | | +| version | string | | + +Returns: + +`ExpressionAstExpression` + diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.migratetolatest.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.migratetolatest.md new file mode 100644 index 0000000000000..72e3f8d8b7edc --- /dev/null +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.migratetolatest.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [Executor](./kibana-plugin-plugins-expressions-server.executor.md) > [migrateToLatest](./kibana-plugin-plugins-expressions-server.executor.migratetolatest.md) + +## Executor.migrateToLatest() method + +Signature: + +```typescript +migrateToLatest(ast: unknown, version: string): ExpressionAstExpression; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| ast | unknown | | +| version | string | | + +Returns: + +`ExpressionAstExpression` + diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunction.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunction.md index 7fcda94968d13..00c8aa63bfbd8 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunction.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunction.md @@ -29,6 +29,7 @@ export declare class ExpressionFunction implements PersistableStatestring | A short help text. | | [inject](./kibana-plugin-plugins-expressions-server.expressionfunction.inject.md) | | (state: ExpressionAstFunction['arguments'], references: SavedObjectReference[]) => ExpressionAstFunction['arguments'] | | | [inputTypes](./kibana-plugin-plugins-expressions-server.expressionfunction.inputtypes.md) | | string[] | undefined | Type of inputs that this function supports. | +| [migrations](./kibana-plugin-plugins-expressions-server.expressionfunction.migrations.md) | | {
[key: string]: (state: SerializableState) => SerializableState;
} | | | [name](./kibana-plugin-plugins-expressions-server.expressionfunction.name.md) | | string | Name of function | | [telemetry](./kibana-plugin-plugins-expressions-server.expressionfunction.telemetry.md) | | (state: ExpressionAstFunction['arguments'], telemetryData: Record<string, any>) => Record<string, any> | | | [type](./kibana-plugin-plugins-expressions-server.expressionfunction.type.md) | | string | Return type of function. This SHOULD be supplied. We use it for UI and autocomplete hinting. We may also use it for optimizations in the future. | diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunction.migrations.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunction.migrations.md new file mode 100644 index 0000000000000..29031a9306b2f --- /dev/null +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunction.migrations.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [ExpressionFunction](./kibana-plugin-plugins-expressions-server.expressionfunction.md) > [migrations](./kibana-plugin-plugins-expressions-server.expressionfunction.migrations.md) + +## ExpressionFunction.migrations property + +Signature: + +```typescript +migrations: { + [key: string]: (state: SerializableState) => SerializableState; + }; +``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.derivative.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.derivative.md new file mode 100644 index 0000000000000..6c51f1eb97750 --- /dev/null +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.derivative.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [ExpressionFunctionDefinitions](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md) > [derivative](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.derivative.md) + +## ExpressionFunctionDefinitions.derivative property + +Signature: + +```typescript +derivative: ExpressionFunctionDerivative; +``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md index 05b4ddce4ccde..d4b71a36e0de1 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.md @@ -18,6 +18,7 @@ export interface ExpressionFunctionDefinitions | --- | --- | --- | | [clog](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.clog.md) | ExpressionFunctionClog | | | [cumulative\_sum](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.cumulative_sum.md) | ExpressionFunctionCumulativeSum | | +| [derivative](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.derivative.md) | ExpressionFunctionDerivative | | | [font](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.font.md) | ExpressionFunctionFont | | | [kibana\_context](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana_context.md) | ExpressionFunctionKibanaContext | | | [kibana](./kibana-plugin-plugins-expressions-server.expressionfunctiondefinitions.kibana.md) | ExpressionFunctionKibana | | diff --git a/docs/management/images/management-index-templates-mappings.png b/docs/management/images/management-index-templates-mappings.png deleted file mode 100644 index beb964b348171..0000000000000 Binary files a/docs/management/images/management-index-templates-mappings.png and /dev/null differ diff --git a/docs/management/images/management-index-templates.png b/docs/management/images/management-index-templates.png deleted file mode 100644 index 07f1fb9a7add1..0000000000000 Binary files a/docs/management/images/management-index-templates.png and /dev/null differ diff --git a/docs/management/images/management_index_component_template.png b/docs/management/images/management_index_component_template.png deleted file mode 100644 index c03029fd172f0..0000000000000 Binary files a/docs/management/images/management_index_component_template.png and /dev/null differ diff --git a/docs/management/images/management_index_create_wizard.png b/docs/management/images/management_index_create_wizard.png deleted file mode 100644 index bff1dd4cd0e7a..0000000000000 Binary files a/docs/management/images/management_index_create_wizard.png and /dev/null differ diff --git a/docs/management/images/management_index_data_stream_backing_index.png b/docs/management/images/management_index_data_stream_backing_index.png deleted file mode 100644 index a5c577affbbb2..0000000000000 Binary files a/docs/management/images/management_index_data_stream_backing_index.png and /dev/null differ diff --git a/docs/management/images/management_index_data_stream_stats.png b/docs/management/images/management_index_data_stream_stats.png deleted file mode 100644 index a67ab4a7deb32..0000000000000 Binary files a/docs/management/images/management_index_data_stream_stats.png and /dev/null differ diff --git a/docs/management/images/management_index_details.png b/docs/management/images/management_index_details.png deleted file mode 100644 index b199d13218f5a..0000000000000 Binary files a/docs/management/images/management_index_details.png and /dev/null differ diff --git a/docs/management/images/management_index_labels.png b/docs/management/images/management_index_labels.png deleted file mode 100644 index a89c32e08beff..0000000000000 Binary files a/docs/management/images/management_index_labels.png and /dev/null differ diff --git a/docs/management/managing-indices.asciidoc b/docs/management/managing-indices.asciidoc deleted file mode 100644 index 8416c164c6c51..0000000000000 --- a/docs/management/managing-indices.asciidoc +++ /dev/null @@ -1,258 +0,0 @@ -[role="xpack"] -[[managing-indices]] -== Index Management - -*Index Management* features are an easy, convenient way to manage your -{es} cluster's indices, data streams, and index templates. Practicing good index -management ensures your data is stored correctly and in the most cost-effective -way possible. - -[float] -=== What you'll learn - -This page shows you how to use *Index Management* features to: - -To manage your indices, open the main menu, then click *Stack Management > Index Management*. - -[role="screenshot"] -image::images/management_index_labels.png[Index Management UI] - -[float] -=== Before you start - -Before using this feature, you should be familiar with index management -operations. Refer to the {ref}/indices.html[index management APIs], the -{ref}/indices-templates.html[index template APIs], and the -{ref}/data-streams.html[data streams documentation]. - -[float] -=== Required permissions - -The minimum required permissions to access *Index Management* are -the `monitor` cluster privilege and the `view_index_metadata` -and `manage` index privileges to view the data. -For index templates, you must have the `manage_index_templates` cluster privilege. -See {ref}/security-privileges.html[Security privileges] for more -information. - -You can add these privileges in *Stack Management > Security > Roles*. - -[float] -=== View and edit indices - -When you open *Index Management*, you’re presented an overview of your configured indices. -Badges indicate if an index is {ref}/frozen-indices.html[frozen], -a {ref}/ccr-put-follow.html[follower index], -or a {ref}/rollup-get-rollup-index-caps.html[rollup index]. - -Clicking a badge narrows the list to only indices of that type. -You can also filter your indices using the search bar. - -You can drill down into each index to investigate the index -{ref}/index-modules.html#index-modules-settings[settings], {ref}/mapping.html[mapping], and statistics. -From this view, you can also edit the index settings. - -[role="screenshot"] -image::images/management_index_details.png[Index Management UI] - -[float] -=== Perform index-level operations - -Use the *Manage* menu to perform index-level operations. This menu -is available in the index details view, or when you select the checkbox of one or more -indices on the overview page. The menu includes the following actions: - -* *Close index*. Blocks the index from read/write operations. -A closed index exists in the cluster, but doesn't consume resources -other than disk space. If you reopen a closed index, it goes through the -normal recovery process. - -* *Force merge index*. Reduces the number of segments in your shard by -merging smaller files and clearing deleted ones. Only force merge a read-only index. - -* *Refresh index*. Writes the operations in the indexing buffer to the -filesystem cache. This action is automatically performed once per second. Forcing a manual -refresh is useful during testing, but should not be routinely done in -production because it has a performance impact. - -* *Clear index cache*. Clears all caches associated with the index. - -* *Flush index*. Frees memory by syncing the filesystem cache to disk and -clearing the cache. Once the sync is complete, the internal transaction log is reset. - -* *Freeze index*. Makes the index read-only and reduces its memory footprint -by moving shards to disk. Frozen indices remain -searchable, but queries take longer. - -* *Delete index*. Permanently removes the index and all of its documents. - -* *Add lifecycle policy*. Specifies a policy for managing the lifecycle of the -index. - -[float] -[[manage-data-streams]] -=== Manage data streams - -A {ref}/data-streams.html[data stream] lets you store time series data across -multiple backing indices while giving you a single named resource to use in -requests. The *Data Streams* view lists your data streams and lets you examine -or delete them. - -To view more information about a data stream, such as its generation or its -current index lifecycle policy, click the stream's name. - -[role="screenshot"] -image::images/management_index_data_stream_stats.png[Data stream details] - -To view information about the stream's backing indices, click the number in the -*Indices* column. - -[role="screenshot"] -image::images/management_index_data_stream_backing_index.png[Backing index] - -[float] -[[manage-index-templates]] -=== Manage index templates - -An index template defines {ref}/index-modules.html#index-modules-settings[settings], -{ref}/mapping.html[mappings], and {ref}/indices-add-alias.html[aliases] -that you can automatically apply when creating a new index. {es} applies a -template to a new index based on an index pattern that matches the index name. - -The *Index Templates* view lists your templates and enables you to examine, edit, clone, and -delete them. Changes you make to an index template -do not affect existing indices. - -[role="screenshot"] -image::images/management-index-templates.png[Index templates] - -If you don't have any templates, you can create one using the *Create template* wizard. -Index templates are applied during index creation, -so you must create the -template before you create the indices. - -[float] -==== Try it: Create an index template - -In this tutorial, you’ll create an index template for randomly generated log -files. You'll then use the template to configure two new indices. - -*Step 1. Add a name and index pattern* - -. In the *Index Templates* view, open the *Create template* wizard. -+ -[role="screenshot"] -image::images/management_index_create_wizard.png[Create wizard] - -. In the *Name* field, enter `my-index-template`. - -. Set *Index pattern* to `my-index-*` so the template matches any index -with that index pattern. - -. Leave *Data Stream*, *Priority*, *Version*, and *_meta field* as-is or blank. - -. Click *Next*. - -*Step 2. Add settings, mappings, and index aliases* - -. Add component templates to your index template. -+ -{ref}/indices-component-template.html[Component templates] are pre-configured -sets of mappings, index settings, and index aliases you can reuse across -multiple index templates. Badges indicate whether a component template contains -mappings (*M*), index settings (*S*), index aliases (*A*), or a combination of -the three. -+ -Component templates are optional. For this tutorial, do not add any component -templates. -+ -[role="screenshot"] -image::images/management_index_component_template.png[Component templates page] - -. Define index settings. These are optional. For this tutorial, leave this -section blank. - -. Define a mapping that contains an object field named `geo` with a child -geo-point field named `coordinates`: -+ -[role="screenshot"] -image::images/management-index-templates-mappings.png[Mapped fields page] -+ -Alternatively, you can click the *Load JSON* link and define the mapping as JSON: -+ -[source,js] ----- -{ - "properties": { - "geo": { - "properties": { - "coordinates": { - "type": "geo_point" - } - } - } - } -} ----- -+ -You can create additional mapping configurations in the *Dynamic templates* and -*Advanced options* tabs. No additional mappings are required for this tutorial. - -. Define an index alias named `my-index`: -+ -[source,js] ----- -{ - "my-index": {} -} ----- - -. On the review page, check the summary. If everything looks right, click -*Create template*. - -*Step 3. Create new indices* - -You’re now ready to load the logs data and create new indices using your index -template. - -. In the {kib} *Console*, index the following documents: -+ -[source,js] ----- -POST /my-index-000001/_doc -{ - "@timestamp": "2019-05-18T15:57:27.541Z", - "ip": "225.44.217.191", - "extension": "jpg", - "response": "200", - "geo": { - "coordinates": { - "lat": 38.53146222, - "lon": -121.7864906 - } - }, - "url": "https://media-for-the-masses.theacademyofperformingartsandscience.org/uploads/charles-fullerton.jpg" -} - -POST /my-index-000002/_doc -{ - "@timestamp": "2019-05-20T03:44:20.844Z", - "ip": "198.247.165.49", - "extension": "php", - "response": "200", - "geo": { - "coordinates": { - "lat": 37.13189556, - "lon": -76.4929875 - } - }, - "memory": 241720, - "url": "https://theacademyofperformingartsandscience.org/people/type:astronauts/name:laurel-b-clark/profile" -} ----- -+ -These requests create two indices: `my-index-000001` and `my-index-000002`. - -. Use the {es} {ref}/indices-get-index.html#indices-get-index[get index API] to -view one of the newly created indices. The index's mappings and alias are -configured automatically based on the template. diff --git a/docs/management/watcher-ui/index.asciidoc b/docs/management/watcher-ui/index.asciidoc index 69c33aa7a1dac..aded7a45022db 100644 --- a/docs/management/watcher-ui/index.asciidoc +++ b/docs/management/watcher-ui/index.asciidoc @@ -46,7 +46,7 @@ To manage roles, open then main menu, then click *Stack Management > Roles*, or all users with the same role. NOTE: If you are creating a threshold watch, you must also have the `view_index_metadata` index privilege. See -<> for detailed information. +{ref}/index-mgmt.html[Index management] for detailed information. [float] [[watcher-create-threshold-alert]] diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 7fc8fe5114e1e..833273c7b3ef0 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -143,3 +143,8 @@ This content has moved. See This content has moved. See <>. +[role="exclude",id="managing-indices"] +== Index management + +This content has moved. See {ref}/index-mgmt.html[Index management]. + diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index 9054a97c90496..aa680720fc8ff 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -43,6 +43,12 @@ Changing these settings may disable features of the APM App. | `xpack.apm.enabled` | Set to `false` to disable the APM app. Defaults to `true`. +| `xpack.apm.serviceMapFingerprintBucketSize` + | Maximum number of unique transaction combinations sampled for generating service map focused on a specific service. Defaults to `100`. + +| `xpack.apm.serviceMapFingerprintGlobalBucketSize` + | Maximum number of unique transaction combinations sampled for generating the global service map. Defaults to `100`. + | `xpack.apm.ui.enabled` {ess-icon} | Set to `false` to hide the APM app from the main menu. Defaults to `true`. diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc index c968ca6f35029..03a728a15351e 100644 --- a/docs/setup/connect-to-elasticsearch.asciidoc +++ b/docs/setup/connect-to-elasticsearch.asciidoc @@ -1,108 +1,73 @@ [[connect-to-elasticsearch]] -== Add data to {kib} +== Add data +++++ +Add data +++++ -To start working with your data in {kib}, you can: +To start working with your data in {kib}, use one of the many ingest options, +available from the home page. +You can collect data from an app or service +or upload a file that contains your data. If you're not ready to use your own data, +add a sample data set and give {kib} a test drive. -* Upload a CSV, JSON, or log file with the File Data Visualizer. - -* Upload geospatial data with the GeoJSON Upload feature. - -* Index logs, metrics, events, or application data by setting up a Beats module. +[role="screenshot"] +image::images/add-data-home.png[Built-in options for adding data to Kibana: Add data, Add Elastic Agent, Upload a file] -* Connect {kib} with existing {es} indices. +[float] +[[add-data-tutorial-kibana]] +=== Add data -If you're not ready to use your own data, you can add a <> -to see all that you can do in {kib}. +Want to ingest logs, metrics, security, or application data? +Install and configure a Beats data shipper or other module to periodically collect the data +and send it to {es}. You can then use the pre-built dashboards to explore and analyze the data. -[float] -[[upload-data-kibana]] -=== Upload a CSV, JSON, or log file +[role="screenshot"] +image::images/add-data-tutorials.png[Add Data tutorials] -experimental[] +[discrete] +=== Add Elastic Agent -To visualize data in a CSV, JSON, or log file, you can upload it using the File -Data Visualizer. On the home page, click *Upload a file*, and -then drag your file onto the *File Data Visualizer*. Alternatively, you can open -it by navigating to *Machine Learning* from the side navigation and selecting +beta[] *Elastic Agent* is a sneak peek at the next generation of +data integration modules, offering +a centralized way to set up your integrations. +With *Fleet*, you can add +and manage integrations for popular services and platforms, providing +an easy way to collect your data. The integrations +ship with dashboards and visualizations, +so you can quickly get insights into your data. -*Data Visualizer*. +To get started, refer to +{ingest-guide}/ingest-management-getting-started.html[Quick start: Get logs and metrics into the Elastic Stack]. [role="screenshot"] -image::images/ingest-data.png[File Data Visualizer on the home page] +image::images/add-data-fleet.png[Add data using Fleet] + +[discrete] +[[upload-data-kibana]] +=== Upload a file -You can upload a file up to 100 MB. This value is configurable up to 1 GB in -<>. +experimental[] If your data is in a CSV, JSON, or log file, you can upload it using the File +Data Visualizer. You can upload a file up to 100 MB. This value is configurable up to 1 GB in +<>. To upload a file with geospatial data, +refer to <>. [role="screenshot"] image::images/add-data-fv.png[File Data Visualizer] -The File Data Visualizer uses the {ref}/ml-find-file-structure.html[find_file_structure API] to analyze -the uploaded file and to suggest ingest pipelines and mappings for your data. + NOTE: This feature is not intended for use as part of a repeated production process, but rather for the initial exploration of your data. -[float] -[[upload-geoipdata-kibana]] -=== Upload geospatial data - -To visualize geospatial data in a point or shape file, you can upload it using the <> -feature in Maps, and then use that data as a layer in a map. -The data is also available for use in the broader Kibana ecosystem, for example, -in visualizations and Canvas workpads. -With GeoJSON Upload, you can upload a file up to 50 MB. - -[float] -[[add-data-tutorial-kibana]] -=== Index metrics, log, security, and application data -The built-in data tutorials can help you quickly get up and running with -metrics data, log analytics, security events, and application data. -These tutorials walk you through installing and configuring a -Beats data shipper to periodically collect and send data to {es}. -You can then use the pre-built dashboards to explore and analyze the data. +[discrete] +=== Additional options for loading your data -You access the tutorials from the home page. -If a tutorial doesn’t exist for your data, go to the {beats-ref}/beats-reference.html[Beats overview] -to learn about other data shippers in the Beats family. +If the {kib} ingest options don't work for you, you can index your +data into Elasticsearch with {ref}/getting-started-index.html[REST APIs] +or https://www.elastic.co/guide/en/elasticsearch/client/index.html[client libraries]. +After you add your data, you're required to create an <> to tell +{kib} where to find the data. -[role="screenshot"] -image::images/add-data-tutorials.png[Add Data tutorials] - - -[float] -[[connect-to-es]] -=== Connect with {es} indices - -To visualize data in existing {es} indices, you must -create an index pattern that matches the names of the indices that you want to explore. -When you add data with the File Data Visualizer, GeoJSON Upload feature, -or built-in tutorial, an index pattern is created for you. - -. Open the main menu, then click *Stack Management > Index Patterns*. - -. Click *Create index pattern*. - -. Specify an index pattern that matches the name of one or more of your Elasticsearch indices. -+ -For example, an index pattern can point to your Apache data from yesterday, -`filebeat-apache-4-3-2022`, or any index that matches the pattern, `filebeat-*`. -Using a wildcard is the more popular approach. - - -. Click *Next Step*, and then select the index field that contains the timestamp you want to use to perform time-based -comparisons. -+ -Kibana reads the index mapping and lists all fields that contain a timestamp. If your -index doesn't have time-based data, choose *I don't want to use the time filter*. -+ -You must select a time field to use global time filters on your dashboards. - -. Click *Create index pattern*. -+ -{kib} is now configured to access your {es} indices. -You’ll see a list of fields configured for the matching index. -You can designate your index pattern as the default by clicking the star icon on this page. -+ -When searching in *Discover* and creating visualizations, you choose a pattern -from the index pattern menu to specify the {es} indices that contain the data you want to explore. +* To add data for Elastic Observability, refer to {observability-guide}/add-observability-data.html[Send data to Elasticsearch]. +* To add data for Elastic Security, refer to https://www.elastic.co/guide/en/security/current/ingest-data.html[Ingest data to Elastic Security]. diff --git a/docs/setup/images/add-data-fleet.png b/docs/setup/images/add-data-fleet.png new file mode 100644 index 0000000000000..b6d49cfaf8d3a Binary files /dev/null and b/docs/setup/images/add-data-fleet.png differ diff --git a/docs/setup/images/add-data-home-page.png b/docs/setup/images/add-data-home-page.png new file mode 100644 index 0000000000000..a3373757683be Binary files /dev/null and b/docs/setup/images/add-data-home-page.png differ diff --git a/docs/setup/images/add-data-home.png b/docs/setup/images/add-data-home.png new file mode 100644 index 0000000000000..3a844b1c40de9 Binary files /dev/null and b/docs/setup/images/add-data-home.png differ diff --git a/docs/user/introduction.asciidoc b/docs/user/introduction.asciidoc index 91f149d5cdb3c..9d580187edff4 100644 --- a/docs/user/introduction.asciidoc +++ b/docs/user/introduction.asciidoc @@ -26,30 +26,16 @@ image::images/intro-kibana.png[Kibana home page] [[get-data-into-kibana]] === Ingest data -{kib} is designed to use {es} as a data source. Think of {es} as the engine that stores +{kib} is designed to use {es} as a data source. Think of Elasticsearch as the engine that stores and processes the data, with {kib} sitting on top. -From the home page, {kib} provides these options for ingesting data: - -* Import data using the -https://www.elastic.co/blog/importing-csv-and-log-data-into-elasticsearch-with-file-data-visualizer[File Data visualizer]. -* Set up a data flow to Elasticsearch using our built-in tutorials. -If a tutorial doesn’t exist for your data, go to the -{beats-ref}/beats-reference.html[Beats overview] to learn about other data shippers -in the {beats} family. -* <> and take {kib} for a test drive without loading data yourself. -* Index your data into Elasticsearch with {ref}/getting-started-index.html[REST APIs] - or https://www.elastic.co/guide/en/elasticsearch/client/index.html[client libraries]. -+ -[role="screenshot"] -image::images/intro-data-tutorial.png[Ways to get data in from the home page] - +To start working with your data in Kibana, use one of the many ingest options, +available from the home page. You can collect data from an app or service or upload a file that contains your data. +If you're not ready to use your own data, you can add a sample data set +to give {kib} a test drive. -{kib} uses an -<> to tell it which {es} indices to explore. -If you add upload a file, run a built-in tutorial, or add sample data, you get an index pattern for free, -and are good to start exploring. If you load your own data, you can create -an index pattern in <>. +[role="screenshot"] +image::setup/images/add-data-home.png[Built-in options for adding data to Kibana: Add data, Add Elastic Agent, Upload a file] [float] [[explore-and-query]] @@ -94,7 +80,7 @@ and dynamic client-side styling. * <> allows you to combine an infinite number of aggregations to display complex data. -With TSVB, you can analyze multiple index patterns and customize +With TSVB, you can customize every aspect of your visualization. Choose your own date format and color gradients, and easily switch your data view between time series, metric, top N, gauge, and markdown. @@ -124,7 +110,7 @@ dashboards in one space, but full access to all of Kibana’s features in anothe === Manage all things Elastic Stack <> provides guided processes for managing all -things Elastic Stack — indices, clusters, licenses, UI settings, index patterns, +things Elastic Stack — indices, clusters, licenses, UI settings, and more. Want to update your {es} indices? Set user roles and privileges? Turn on dark mode? Kibana has UIs for all that. diff --git a/docs/user/management.asciidoc b/docs/user/management.asciidoc index ee85819b4fd98..503f97aabd13f 100644 --- a/docs/user/management.asciidoc +++ b/docs/user/management.asciidoc @@ -39,7 +39,7 @@ quickly deploy configuration changes to all Beats running across your enterprise [cols="50, 50"] |=== -a| <> +a| {ref}/index-mgmt.html[Index Management] | View index settings, mappings, and statistics and perform operations, such as refreshing, flushing, and clearing the cache. Practicing good index management ensures that your data is stored cost effectively. @@ -184,8 +184,6 @@ include::{kib-repo-dir}/management/alerting/connector-management.asciidoc[] include::{kib-repo-dir}/management/managing-beats.asciidoc[] -include::{kib-repo-dir}/management/managing-indices.asciidoc[] - include::{kib-repo-dir}/management/ingest-pipelines/ingest-pipelines.asciidoc[] include::{kib-repo-dir}/management/managing-fields.asciidoc[] diff --git a/docs/user/monitoring/beats-details.asciidoc b/docs/user/monitoring/beats-details.asciidoc index 3d7a726d2f8a2..dbd9aecd04768 100644 --- a/docs/user/monitoring/beats-details.asciidoc +++ b/docs/user/monitoring/beats-details.asciidoc @@ -9,7 +9,7 @@ If you are monitoring Beats, the *Stack Monitoring* page in {kib} contains a panel for Beats in the cluster overview. [role="screenshot"] -image::user/monitoring/images/monitoring-beats.jpg["Monitoring Beats",link="images/monitoring-beats.jpg"] +image::user/monitoring/images/monitoring-beats.jpg["Monitoring Beats",link="user/monitoring/images/monitoring-beats.jpg"] To view an overview of the Beats data in the cluster, click *Overview*. The overview page has a section for activity in the last day, which is a real-time @@ -24,7 +24,7 @@ cluster. All columns are sortable. Clicking a Beat name takes you to the detail page. For example: [role="screenshot"] -image::user/monitoring/images/monitoring-beats-detail.jpg["Monitoring details for Filebeat",link="images/monitoring-beats-detail.jpg"] +image::user/monitoring/images/monitoring-beats-detail.jpg["Monitoring details for Filebeat",link="user/monitoring/images/monitoring-beats-detail.jpg"] The detail page contains a summary bar and charts. There are more charts on this page than the overview page and they are specific to a single Beat instance. diff --git a/examples/bfetch_explorer/README.md b/examples/bfetch_explorer/README.md new file mode 100644 index 0000000000000..33723e7cabe07 --- /dev/null +++ b/examples/bfetch_explorer/README.md @@ -0,0 +1,11 @@ +## bfetch explorer + +bfetch is a service that allows you to batch HTTP requests and stream responses +back. + +This example app demonstrates: + - How you can create a streaming response route and consume it from the + client + - How you can create a batch processing route and consume it from the client + +To run this example, use the command `yarn start --run-examples`. diff --git a/examples/embeddable_examples/README.md b/examples/embeddable_examples/README.md new file mode 100644 index 0000000000000..d05c315d31817 --- /dev/null +++ b/examples/embeddable_examples/README.md @@ -0,0 +1,4 @@ +## Embeddable examples + +This example plugin exists to support the `embeddable_explorer` app. + diff --git a/examples/embeddable_explorer/README.md b/examples/embeddable_explorer/README.md new file mode 100644 index 0000000000000..0425790f07487 --- /dev/null +++ b/examples/embeddable_explorer/README.md @@ -0,0 +1,10 @@ +## Embeddable explorer + +This example app shows how to: + - Create a basic "hello world" embeddable + - Create embeddables that accept inputs and use an EmbeddableRenderer + - Nest embeddables inside a container + - Dynamically add children to embeddable containers + - Work with the EmbeddablePanel component + +To run this example, use the command `yarn start --run-examples`. diff --git a/examples/state_containers_examples/README.md b/examples/state_containers_examples/README.md new file mode 100644 index 0000000000000..c4c6642789bd9 --- /dev/null +++ b/examples/state_containers_examples/README.md @@ -0,0 +1,8 @@ +## State containers examples + +This example app shows how to: + - Use state containers to manage your application state + - Integrate with browser history and hash history routing + - Sync your state container with the URL + +To run this example, use the command `yarn start --run-examples`. diff --git a/package.json b/package.json index 3a2d13fd5ef3b..a613a7a9337bd 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,10 @@ "url": "https://github.com/elastic/kibana.git" }, "resolutions": { + "**/@hapi/iron": "^5.1.4", + "**/@types/hapi__boom": "^7.4.1", + "**/@types/hapi__hapi": "^18.2.6", + "**/@types/hapi__mimos": "4.1.0", "**/@types/node": ">=10.17.17 <10.20.0", "**/cross-fetch/node-fetch": "^2.6.1", "**/deepmerge": "^4.2.2", @@ -110,19 +114,30 @@ "**/grunt-*/**", "x-pack/typescript", "@elastic/eui/rehype-react", + "@elastic/eui/remark-parse", "@elastic/eui/remark-rehype", - "@elastic/eui/remark-rehype/**" + "@elastic/eui/remark-rehype/**", + "@elastic/eui/unified" ] }, "dependencies": { "@elastic/datemath": "5.0.3", "@elastic/elasticsearch": "7.10.0-rc.1", - "@elastic/eui": "29.5.0", + "@elastic/eui": "30.1.1", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "^2.5.0", "@elastic/request-crypto": "1.1.4", "@elastic/safer-lodash-set": "0.0.0", + "@hapi/boom": "^7.4.11", + "@hapi/cookie": "^10.1.2", "@hapi/good-squeeze": "5.2.1", + "@hapi/h2o2": "^8.3.2", + "@hapi/hapi": "^18.4.1", + "@hapi/hoek": "^8.5.1", + "@hapi/inert": "^5.2.2", + "@hapi/podium": "^3.4.3", + "@hapi/statehood": "^6.1.2", + "@hapi/vision": "^5.5.4", "@hapi/wreck": "^15.0.2", "@kbn/ace": "1.0.0", "@kbn/analytics": "1.0.0", @@ -145,7 +160,6 @@ "angular-elastic": "^2.5.1", "angular-sanitize": "^1.8.0", "bluebird": "3.5.5", - "boom": "^7.2.0", "chalk": "^4.1.0", "check-disk-space": "^2.1.0", "chokidar": "^3.4.2", @@ -165,15 +179,10 @@ "glob": "^7.1.2", "glob-all": "^3.2.1", "globby": "^8.0.1", - "h2o2": "^8.1.2", "handlebars": "4.7.6", - "hapi": "^17.5.3", - "hapi-auth-cookie": "^9.0.0", "hjson": "3.2.1", - "hoek": "^5.0.4", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^5.0.0", - "inert": "^5.1.0", "inline-style": "^2.0.0", "joi": "^13.5.2", "js-yaml": "^3.14.0", @@ -218,7 +227,6 @@ "tslib": "^2.0.0", "type-detect": "^4.0.8", "uuid": "3.3.2", - "vision": "^5.3.3", "whatwg-fetch": "^3.0.0", "yauzl": "^2.10.0" }, @@ -272,7 +280,6 @@ "@types/archiver": "^3.1.0", "@types/babel__core": "^7.1.10", "@types/bluebird": "^3.1.1", - "@types/boom": "^7.2.0", "@types/chance": "^1.0.0", "@types/cheerio": "^0.22.10", "@types/chromedriver": "^81.0.0", @@ -292,14 +299,16 @@ "@types/glob": "^7.1.2", "@types/globby": "^8.0.0", "@types/graphql": "^0.13.2", - "@types/h2o2": "^8.1.1", - "@types/hapi": "^17.0.18", - "@types/hapi-auth-cookie": "^9.1.0", + "@types/hapi__boom": "^7.4.1", + "@types/hapi__cookie": "^10.1.1", + "@types/hapi__h2o2": "8.3.0", + "@types/hapi__hapi": "^18.2.6", + "@types/hapi__hoek": "^6.2.0", + "@types/hapi__inert": "^5.2.1", + "@types/hapi__podium": "^3.4.1", "@types/has-ansi": "^3.0.0", "@types/history": "^4.7.3", "@types/hjson": "^2.4.2", - "@types/hoek": "^4.1.3", - "@types/inert": "^5.1.2", "@types/jest": "^26.0.14", "@types/jest-when": "^2.7.1", "@types/joi": "^13.4.2", @@ -324,7 +333,6 @@ "@types/opn": "^5.1.0", "@types/pegjs": "^0.10.1", "@types/pngjs": "^3.4.0", - "@types/podium": "^1.0.0", "@types/prop-types": "^15.7.3", "@types/reach__router": "^1.2.6", "@types/react": "^16.9.36", diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 3f9fdb164e759..770bb4f510301 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -54,7 +54,7 @@ pageLoadAssetSize: mapsLegacy: 116817 mapsLegacyLicensing: 20214 ml: 82187 - monitoring: 268612 + monitoring: 50000 navigation: 37269 newsfeed: 42228 observability: 89709 diff --git a/packages/kbn-telemetry-tools/GUIDELINE.md b/packages/kbn-telemetry-tools/GUIDELINE.md index e7d09babbf9e2..a22196bb5dc74 100644 --- a/packages/kbn-telemetry-tools/GUIDELINE.md +++ b/packages/kbn-telemetry-tools/GUIDELINE.md @@ -148,14 +148,7 @@ usageCollection.makeUsageCollector({ Any field property in the schema accepts a `type` field. By default the type is `object` which accepts nested properties under it. Currently we accept the following property types: ``` -AllowedSchemaTypes = - | 'keyword' - | 'text' - | 'number' - | 'boolean' - | 'long' - | 'date' - | 'float'; +'long', 'integer', 'short', 'byte', 'double', 'float', 'keyword', 'text', 'boolean', 'date' ``` diff --git a/packages/kbn-telemetry-tools/src/tools/__fixture__/mock_schema.json b/packages/kbn-telemetry-tools/src/tools/__fixture__/mock_schema.json index 51e5df9bf7dc0..a385cd6798365 100644 --- a/packages/kbn-telemetry-tools/src/tools/__fixture__/mock_schema.json +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/mock_schema.json @@ -8,16 +8,16 @@ "my_index_signature_prop": { "properties": { "avg": { - "type": "number" + "type": "float" }, "count": { - "type": "number" + "type": "long" }, "max": { - "type": "number" + "type": "long" }, "min": { - "type": "number" + "type": "long" } } }, @@ -27,7 +27,7 @@ "my_objects": { "properties": { "total": { - "type": "number" + "type": "long" }, "type": { "type": "boolean" @@ -39,7 +39,7 @@ "items": { "properties": { "total": { - "type": "number" + "type": "long" }, "type": { "type": "boolean" diff --git a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_indexed_interface_with_not_matching_schema.ts b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_indexed_interface_with_not_matching_schema.ts index 83866a2b6afec..109fc045b6ee0 100644 --- a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_indexed_interface_with_not_matching_schema.ts +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_indexed_interface_with_not_matching_schema.ts @@ -28,7 +28,7 @@ export const parsedIndexedInterfaceWithNoMatchingSchema: ParsedUsageCollection = value: { something: { count_1: { - type: 'number', + type: 'long', }, }, }, diff --git a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_schema_defined_with_spreads_collector.ts b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_schema_defined_with_spreads_collector.ts index 833344fa368b0..4a1a622e23f36 100644 --- a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_schema_defined_with_spreads_collector.ts +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_schema_defined_with_spreads_collector.ts @@ -34,7 +34,7 @@ export const parsedSchemaDefinedWithSpreadsCollector: ParsedUsageCollection = [ }, my_objects: { total: { - type: 'number', + type: 'long', }, type: { type: 'boolean', diff --git a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_working_collector.ts b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_working_collector.ts index acf984b7d10ee..ef6227cf35c37 100644 --- a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_working_collector.ts +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_working_collector.ts @@ -34,21 +34,21 @@ export const parsedWorkingCollector: ParsedUsageCollection = [ }, my_index_signature_prop: { avg: { - type: 'number', + type: 'float', }, count: { - type: 'number', + type: 'long', }, max: { - type: 'number', + type: 'long', }, min: { - type: 'number', + type: 'long', }, }, my_objects: { total: { - type: 'number', + type: 'long', }, type: { type: 'boolean', @@ -58,7 +58,7 @@ export const parsedWorkingCollector: ParsedUsageCollection = [ type: 'array', items: { total: { - type: 'number', + type: 'long', }, type: { type: 'boolean' }, }, diff --git a/packages/kbn-telemetry-tools/src/tools/__snapshots__/extract_collectors.test.ts.snap b/packages/kbn-telemetry-tools/src/tools/__snapshots__/extract_collectors.test.ts.snap index 4725be77533af..fe589be7993d0 100644 --- a/packages/kbn-telemetry-tools/src/tools/__snapshots__/extract_collectors.test.ts.snap +++ b/packages/kbn-telemetry-tools/src/tools/__snapshots__/extract_collectors.test.ts.snap @@ -176,7 +176,7 @@ Array [ }, "my_objects": Object { "total": Object { - "type": "number", + "type": "long", }, "type": Object { "type": "boolean", @@ -248,7 +248,7 @@ Array [ "my_array": Object { "items": Object { "total": Object { - "type": "number", + "type": "long", }, "type": Object { "type": "boolean", @@ -258,21 +258,21 @@ Array [ }, "my_index_signature_prop": Object { "avg": Object { - "type": "number", + "type": "float", }, "count": Object { - "type": "number", + "type": "long", }, "max": Object { - "type": "number", + "type": "long", }, "min": Object { - "type": "number", + "type": "long", }, }, "my_objects": Object { "total": Object { - "type": "number", + "type": "long", }, "type": Object { "type": "boolean", diff --git a/packages/kbn-telemetry-tools/src/tools/check_collector__integrity.test.ts b/packages/kbn-telemetry-tools/src/tools/check_collector__integrity.test.ts index a101210185a63..b6ea9d49cf6d0 100644 --- a/packages/kbn-telemetry-tools/src/tools/check_collector__integrity.test.ts +++ b/packages/kbn-telemetry-tools/src/tools/check_collector__integrity.test.ts @@ -44,7 +44,7 @@ describe('checkMatchingMapping', () => { it('returns diff on mismatching parsedCollections and stored mapping', async () => { const mockSchema = await parseJsonFile('mock_schema.json'); const malformedParsedCollector = cloneDeep(parsedWorkingCollector); - const fieldMapping = { type: 'number' }; + const fieldMapping = { type: 'long' }; malformedParsedCollector[1].schema.value.flat = fieldMapping; const diffs = checkMatchingMapping([malformedParsedCollector], mockSchema); @@ -61,9 +61,9 @@ describe('checkMatchingMapping', () => { const mockSchema = await parseJsonFile('mock_schema.json'); const malformedParsedCollector = cloneDeep(parsedWorkingCollector); const collectorName = 'New Collector in town!'; - const collectorMapping = { some_usage: { type: 'number' } }; + const collectorMapping = { some_usage: { type: 'long' } }; malformedParsedCollector[1].collectorName = collectorName; - malformedParsedCollector[1].schema.value = { some_usage: { type: 'number' } }; + malformedParsedCollector[1].schema.value = { some_usage: { type: 'long' } }; const diffs = checkMatchingMapping([malformedParsedCollector], mockSchema); expect(diffs).toEqual({ diff --git a/packages/kbn-telemetry-tools/src/tools/manage_schema.ts b/packages/kbn-telemetry-tools/src/tools/manage_schema.ts index 7721492fdb691..e2bfca34a6487 100644 --- a/packages/kbn-telemetry-tools/src/tools/manage_schema.ts +++ b/packages/kbn-telemetry-tools/src/tools/manage_schema.ts @@ -19,14 +19,9 @@ import { ParsedUsageCollection } from './ts_parser'; -export type AllowedSchemaTypes = - | 'keyword' - | 'text' - | 'number' - | 'boolean' - | 'long' - | 'date' - | 'float'; +export type AllowedSchemaNumberTypes = 'long' | 'integer' | 'short' | 'byte' | 'double' | 'float'; + +export type AllowedSchemaTypes = AllowedSchemaNumberTypes | 'keyword' | 'text' | 'boolean' | 'date'; export function compatibleSchemaTypes(type: AllowedSchemaTypes | 'array') { switch (type) { @@ -36,9 +31,12 @@ export function compatibleSchemaTypes(type: AllowedSchemaTypes | 'array') { return 'string'; case 'boolean': return 'boolean'; - case 'number': - case 'float': case 'long': + case 'integer': + case 'short': + case 'byte': + case 'double': + case 'float': return 'number'; case 'array': return 'array'; diff --git a/packages/kbn-ui-framework/package.json b/packages/kbn-ui-framework/package.json index d954ae0823caf..b47adb29f1e68 100644 --- a/packages/kbn-ui-framework/package.json +++ b/packages/kbn-ui-framework/package.json @@ -31,7 +31,7 @@ }, "devDependencies": { "@babel/core": "^7.11.6", - "@elastic/eui": "29.5.0", + "@elastic/eui": "30.1.1", "@kbn/babel-preset": "1.0.0", "@kbn/optimizer": "1.0.0", "babel-loader": "^8.0.6", diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index b1b5d6e2b419e..e6883be307bb2 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@elastic/charts": "24.0.0", - "@elastic/eui": "29.5.0", + "@elastic/eui": "30.1.1", "@elastic/numeral": "^2.5.0", "@kbn/i18n": "1.0.0", "@kbn/monaco": "1.0.0", diff --git a/src/core/public/chrome/chrome_service.mock.ts b/src/core/public/chrome/chrome_service.mock.ts index cbcd23615d34c..6df8d57c8c574 100644 --- a/src/core/public/chrome/chrome_service.mock.ts +++ b/src/core/public/chrome/chrome_service.mock.ts @@ -63,6 +63,8 @@ const createStartContractMock = () => { setBadge: jest.fn(), getBreadcrumbs$: jest.fn(), setBreadcrumbs: jest.fn(), + getBreadcrumbsAppendExtension$: jest.fn(), + setBreadcrumbsAppendExtension: jest.fn(), getHelpExtension$: jest.fn(), setHelpExtension: jest.fn(), setHelpSupportUrl: jest.fn(), @@ -76,6 +78,7 @@ const createStartContractMock = () => { startContract.getApplicationClasses$.mockReturnValue(new BehaviorSubject(['class-name'])); startContract.getBadge$.mockReturnValue(new BehaviorSubject({} as ChromeBadge)); startContract.getBreadcrumbs$.mockReturnValue(new BehaviorSubject([{} as ChromeBreadcrumb])); + startContract.getBreadcrumbsAppendExtension$.mockReturnValue(new BehaviorSubject(undefined)); startContract.getCustomNavLink$.mockReturnValue(new BehaviorSubject(undefined)); startContract.getHelpExtension$.mockReturnValue(new BehaviorSubject(undefined)); startContract.getIsNavDrawerLocked$.mockReturnValue(new BehaviorSubject(false)); diff --git a/src/core/public/chrome/chrome_service.test.ts b/src/core/public/chrome/chrome_service.test.ts index 0150554a60906..3783f3bd9b65e 100644 --- a/src/core/public/chrome/chrome_service.test.ts +++ b/src/core/public/chrome/chrome_service.test.ts @@ -363,6 +363,25 @@ describe('start', () => { }); }); + describe('breadcrumbsAppendExtension$', () => { + it('updates the breadcrumbsAppendExtension$', async () => { + const { chrome, service } = await start(); + const promise = chrome.getBreadcrumbsAppendExtension$().pipe(toArray()).toPromise(); + + chrome.setBreadcrumbsAppendExtension({ content: (element) => () => {} }); + service.stop(); + + await expect(promise).resolves.toMatchInlineSnapshot(` + Array [ + undefined, + Object { + "content": [Function], + }, + ] + `); + }); + }); + describe('custom nav link', () => { it('updates/emits the current custom nav link', async () => { const { chrome, service } = await start(); @@ -429,33 +448,33 @@ describe('start', () => { expect(docTitleResetSpy).toBeCalledTimes(1); await expect(promises).resolves.toMatchInlineSnapshot(` - Array [ - Array [ - undefined, - Object { - "appName": "App name", - }, - undefined, - ], - Array [ - Array [], - Array [ - Object { - "text": "App breadcrumb", - }, - ], - Array [], - ], - Array [ - undefined, - Object { - "text": "App badge", - "tooltip": "App tooltip", - }, - undefined, - ], - ] - `); + Array [ + Array [ + undefined, + Object { + "appName": "App name", + }, + undefined, + ], + Array [ + Array [], + Array [ + Object { + "text": "App breadcrumb", + }, + ], + Array [], + ], + Array [ + undefined, + Object { + "text": "App badge", + "tooltip": "App tooltip", + }, + undefined, + ], + ] + `); }); }); }); diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index b01f120b81305..b39c83498859c 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -24,6 +24,7 @@ import { BehaviorSubject, combineLatest, merge, Observable, of, ReplaySubject } import { flatMap, map, takeUntil } from 'rxjs/operators'; import { parse } from 'url'; import { EuiLink } from '@elastic/eui'; +import { MountPoint } from '../types'; import { mountReactNode } from '../utils/mount'; import { InternalApplicationStart } from '../application'; import { DocLinksStart } from '../doc_links'; @@ -58,6 +59,11 @@ export interface ChromeBrand { /** @public */ export type ChromeBreadcrumb = EuiBreadcrumb; +/** @public */ +export interface ChromeBreadcrumbsAppendExtension { + content: MountPoint; +} + /** @public */ export interface ChromeHelpExtension { /** @@ -146,6 +152,9 @@ export class ChromeService { const applicationClasses$ = new BehaviorSubject>(new Set()); const helpExtension$ = new BehaviorSubject(undefined); const breadcrumbs$ = new BehaviorSubject([]); + const breadcrumbsAppendExtension$ = new BehaviorSubject< + ChromeBreadcrumbsAppendExtension | undefined + >(undefined); const badge$ = new BehaviorSubject(undefined); const customNavLink$ = new BehaviorSubject(undefined); const helpSupportUrl$ = new BehaviorSubject(KIBANA_ASK_ELASTIC_LINK); @@ -225,6 +234,7 @@ export class ChromeService { badge$={badge$.pipe(takeUntil(this.stop$))} basePath={http.basePath} breadcrumbs$={breadcrumbs$.pipe(takeUntil(this.stop$))} + breadcrumbsAppendExtension$={breadcrumbsAppendExtension$.pipe(takeUntil(this.stop$))} customNavLink$={customNavLink$.pipe(takeUntil(this.stop$))} kibanaDocLink={docLinks.links.kibana} forceAppSwitcherNavigation$={navLinks.getForceAppSwitcherNavigation$()} @@ -290,6 +300,14 @@ export class ChromeService { breadcrumbs$.next(newBreadcrumbs); }, + getBreadcrumbsAppendExtension$: () => breadcrumbsAppendExtension$.pipe(takeUntil(this.stop$)), + + setBreadcrumbsAppendExtension: ( + breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension + ) => { + breadcrumbsAppendExtension$.next(breadcrumbsAppendExtension); + }, + getHelpExtension$: () => helpExtension$.pipe(takeUntil(this.stop$)), setHelpExtension: (helpExtension?: ChromeHelpExtension) => { @@ -431,6 +449,18 @@ export interface ChromeStart { */ setBreadcrumbs(newBreadcrumbs: ChromeBreadcrumb[]): void; + /** + * Get an observable of the current extension appended to breadcrumbs + */ + getBreadcrumbsAppendExtension$(): Observable; + + /** + * Mount an element next to the last breadcrumb + */ + setBreadcrumbsAppendExtension( + breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension + ): void; + /** * Get an observable of the current custom nav link */ diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index 34c5f213a7221..ee2fcbd5078af 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -300,6 +300,55 @@ exports[`Header renders 1`] = ` "thrownError": null, } } + breadcrumbsAppendExtension$={ + BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [ + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + ], + "thrownError": null, + } + } customNavLink$={ BehaviorSubject { "_isScalar": false, @@ -5029,6 +5078,55 @@ exports[`Header renders 1`] = ` "thrownError": null, } } + breadcrumbsAppendExtension$={ + BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [ + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + ], + "thrownError": null, + } + } > ; badge$: Observable; breadcrumbs$: Observable; + breadcrumbsAppendExtension$: Observable; customNavLink$: Observable; homeHref: string; isVisible$: Observable; @@ -169,6 +170,7 @@ export function Header({ diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx index 7fe2c91087090..64401171d142a 100644 --- a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx +++ b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx @@ -22,12 +22,17 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { BehaviorSubject } from 'rxjs'; import { HeaderBreadcrumbs } from './header_breadcrumbs'; +import { ChromeBreadcrumbsAppendExtension } from '../../chrome_service'; describe('HeaderBreadcrumbs', () => { it('renders updates to the breadcrumbs$ observable', () => { const breadcrumbs$ = new BehaviorSubject([{ text: 'First' }]); const wrapper = mount( - + ); expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); @@ -39,4 +44,29 @@ describe('HeaderBreadcrumbs', () => { wrapper.update(); expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); }); + + it('renders breadcrumbs extension', () => { + const breadcrumbs$ = new BehaviorSubject([{ text: 'First' }]); + const breadcrumbsAppendExtension$ = new BehaviorSubject< + undefined | ChromeBreadcrumbsAppendExtension + >({ + content: (root: HTMLDivElement) => { + root.innerHTML = '
__render__
'; + return () => (root.innerHTML = ''); + }, + }); + + const wrapper = mount( + + ); + + expect(wrapper.find('.euiBreadcrumb').getDOMNode().querySelector('my-extension')).toBeDefined(); + act(() => breadcrumbsAppendExtension$.next(undefined)); + wrapper.update(); + expect(wrapper.find('.euiBreadcrumb').getDOMNode().querySelector('my-extension')).toBeNull(); + }); }); diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.tsx b/src/core/public/chrome/ui/header/header_breadcrumbs.tsx index 52412f8990c7a..d52faa87cfecd 100644 --- a/src/core/public/chrome/ui/header/header_breadcrumbs.tsx +++ b/src/core/public/chrome/ui/header/header_breadcrumbs.tsx @@ -22,16 +22,19 @@ import classNames from 'classnames'; import React from 'react'; import useObservable from 'react-use/lib/useObservable'; import { Observable } from 'rxjs'; -import { ChromeBreadcrumb } from '../../chrome_service'; +import { ChromeBreadcrumb, ChromeBreadcrumbsAppendExtension } from '../../chrome_service'; +import { HeaderExtension } from './header_extension'; interface Props { appTitle$: Observable; breadcrumbs$: Observable; + breadcrumbsAppendExtension$: Observable; } -export function HeaderBreadcrumbs({ appTitle$, breadcrumbs$ }: Props) { +export function HeaderBreadcrumbs({ appTitle$, breadcrumbs$, breadcrumbsAppendExtension$ }: Props) { const appTitle = useObservable(appTitle$, 'Kibana'); const breadcrumbs = useObservable(breadcrumbs$, []); + const breadcrumbsAppendExtension = useObservable(breadcrumbsAppendExtension$); let crumbs = breadcrumbs; if (breadcrumbs.length === 0 && appTitle) { @@ -48,5 +51,15 @@ export function HeaderBreadcrumbs({ appTitle$, breadcrumbs$ }: Props) { ), })); + if (breadcrumbsAppendExtension) { + const lastCrumb = crumbs[crumbs.length - 1]; + lastCrumb.text = ( + <> + {lastCrumb.text} + + + ); + } + return ; } diff --git a/src/core/public/chrome/ui/header/header_extension.test.tsx b/src/core/public/chrome/ui/header/header_extension.test.tsx index 3d5678b8bb7ef..ba00c74b81cfa 100644 --- a/src/core/public/chrome/ui/header/header_extension.test.tsx +++ b/src/core/public/chrome/ui/header/header_extension.test.tsx @@ -32,6 +32,14 @@ describe('HeaderExtension', () => { expect(divNode).toBeInstanceOf(HTMLElement); }); + it('calls navControl.render with div node as inlineBlock', () => { + const renderSpy = jest.fn(); + mount(); + + const [divNode] = renderSpy.mock.calls[0]; + expect(divNode).toHaveAttribute('style', 'display: inline-block;'); + }); + it('calls unrender callback when unmounted', () => { const unrenderSpy = jest.fn(); const render = () => unrenderSpy; diff --git a/src/core/public/chrome/ui/header/header_extension.tsx b/src/core/public/chrome/ui/header/header_extension.tsx index 76413a0ea0317..97cf38f44c3f1 100644 --- a/src/core/public/chrome/ui/header/header_extension.tsx +++ b/src/core/public/chrome/ui/header/header_extension.tsx @@ -22,6 +22,7 @@ import { MountPoint } from '../../../types'; interface Props { extension?: MountPoint; + display?: 'block' | 'inlineBlock'; } export class HeaderExtension extends React.Component { @@ -46,7 +47,12 @@ export class HeaderExtension extends React.Component { } public render() { - return
; + return ( +
+ ); } private renderExtension() { diff --git a/src/core/public/chrome/ui/header/recent_links.tsx b/src/core/public/chrome/ui/header/recent_links.tsx deleted file mode 100644 index 0e068a3465b0e..0000000000000 --- a/src/core/public/chrome/ui/header/recent_links.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiNavDrawerGroup } from '@elastic/eui'; -import { RecentNavLink } from './nav_link'; - -interface Props { - recentNavLinks: RecentNavLink[]; -} - -export function RecentLinks({ recentNavLinks }: Props) { - return ( - - ); -} diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index f52d5b6fbd6a5..da4a7f446add7 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -6,7 +6,7 @@ import { Action } from 'history'; import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { ConfigPath } from '@kbn/config'; import { EnvironmentMode } from '@kbn/config'; import { EuiBreadcrumb } from '@elastic/eui'; @@ -343,6 +343,8 @@ export interface ChromeStart { getBadge$(): Observable; getBrand$(): Observable; getBreadcrumbs$(): Observable; + // Warning: (ae-forgotten-export) The symbol "ChromeBreadcrumbsAppendExtension" needs to be exported by the entry point index.d.ts + getBreadcrumbsAppendExtension$(): Observable; getCustomNavLink$(): Observable | undefined>; getHelpExtension$(): Observable; getIsNavDrawerLocked$(): Observable; @@ -355,6 +357,7 @@ export interface ChromeStart { setBadge(badge?: ChromeBadge): void; setBrand(brand: ChromeBrand): void; setBreadcrumbs(newBreadcrumbs: ChromeBreadcrumb[]): void; + setBreadcrumbsAppendExtension(breadcrumbsAppendExtension?: ChromeBreadcrumbsAppendExtension): void; setCustomNavLink(newCustomNavLink?: Partial): void; setHelpExtension(helpExtension?: ChromeHelpExtension): void; setHelpSupportUrl(url: string): void; diff --git a/src/core/server/elasticsearch/legacy/errors.test.ts b/src/core/server/elasticsearch/legacy/errors.test.ts index 85e5eba721b34..4b315bf8aff45 100644 --- a/src/core/server/elasticsearch/legacy/errors.test.ts +++ b/src/core/server/elasticsearch/legacy/errors.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { LegacyElasticsearchErrorHelpers } from './errors'; diff --git a/src/core/server/elasticsearch/legacy/errors.ts b/src/core/server/elasticsearch/legacy/errors.ts index de4d2739977bb..e557e7395fe56 100644 --- a/src/core/server/elasticsearch/legacy/errors.ts +++ b/src/core/server/elasticsearch/legacy/errors.ts @@ -17,7 +17,7 @@ * under the License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { get } from 'lodash'; const code = Symbol('ElasticsearchError'); diff --git a/src/core/server/http/base_path_proxy_server.ts b/src/core/server/http/base_path_proxy_server.ts index acb83962bd457..42841377e7369 100644 --- a/src/core/server/http/base_path_proxy_server.ts +++ b/src/core/server/http/base_path_proxy_server.ts @@ -22,8 +22,8 @@ import { Agent as HttpsAgent, ServerOptions as TlsOptions } from 'https'; import apm from 'elastic-apm-node'; import { ByteSizeValue } from '@kbn/config-schema'; -import { Server, Request } from 'hapi'; -import HapiProxy from 'h2o2'; +import { Server, Request } from '@hapi/hapi'; +import HapiProxy from '@hapi/h2o2'; import { sampleSize } from 'lodash'; import * as Rx from 'rxjs'; import { take } from 'rxjs/operators'; diff --git a/src/core/server/http/cookie_session_storage.ts b/src/core/server/http/cookie_session_storage.ts index 5ca70045f81db..1ff0670d78f4e 100644 --- a/src/core/server/http/cookie_session_storage.ts +++ b/src/core/server/http/cookie_session_storage.ts @@ -17,10 +17,10 @@ * under the License. */ -import { Request, Server } from 'hapi'; -import hapiAuthCookie from 'hapi-auth-cookie'; +import { Request, Server } from '@hapi/hapi'; +import hapiAuthCookie from '@hapi/cookie'; // @ts-expect-error no TS definitions -import Statehood from 'statehood'; +import Statehood from '@hapi/statehood'; import { KibanaRequest, ensureRawRequest } from './router'; import { SessionStorageFactory, SessionStorage } from './session_storage'; @@ -80,7 +80,7 @@ class ScopedCookieSessionStorage> implements Sessi const session = await this.server.auth.test('security-cookie', this.request); // A browser can send several cookies, if it's not an array, just return the session value if (!Array.isArray(session)) { - return session as T; + return session.credentials as T; } // If we have an array with one value, we're good also @@ -141,20 +141,22 @@ export async function createCookieSessionStorageFactory( await server.register({ plugin: hapiAuthCookie }); server.auth.strategy('security-cookie', 'cookie', { - cookie: cookieOptions.name, - password: cookieOptions.encryptionKey, - validateFunc: async (req, session: T | T[]) => { + cookie: { + name: cookieOptions.name, + password: cookieOptions.encryptionKey, + isSecure: cookieOptions.isSecure, + path: basePath === undefined ? '/' : basePath, + clearInvalid: false, + isHttpOnly: true, + isSameSite: cookieOptions.sameSite === 'None' ? false : cookieOptions.sameSite ?? false, + }, + validateFunc: async (req: Request, session: T | T[]) => { const result = cookieOptions.validate(session); if (!result.isValid) { clearInvalidCookie(req, result.path); } return { valid: result.isValid }; }, - isSecure: cookieOptions.isSecure, - path: basePath, - clearInvalid: false, - isHttpOnly: true, - isSameSite: cookieOptions.sameSite === 'None' ? false : cookieOptions.sameSite ?? false, }); // A hack to support SameSite: 'None'. diff --git a/src/core/server/http/http_server.mocks.ts b/src/core/server/http/http_server.mocks.ts index d615e799f383f..8e8eaf46a7064 100644 --- a/src/core/server/http/http_server.mocks.ts +++ b/src/core/server/http/http_server.mocks.ts @@ -16,8 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import { parse as parseUrl } from 'url'; -import { Request } from 'hapi'; +import { URL, format as formatUrl } from 'url'; +import { Request } from '@hapi/hapi'; import { merge } from 'lodash'; import { Socket } from 'net'; import { stringify } from 'query-string'; @@ -73,7 +73,7 @@ function createKibanaRequestMock

({ auth = { isAuthenticated: true }, }: RequestFixtureOptions = {}) { const queryString = stringify(query, { sort: false }); - const url = parseUrl(`${path}${queryString ? `?${queryString}` : ''}`); + const url = new URL(`${path}${queryString ? `?${queryString}` : ''}`, 'http://localhost'); return KibanaRequest.from( createRawRequestMock({ @@ -87,6 +87,9 @@ function createKibanaRequestMock

({ method, url, route: { + // @ts-expect-error According to types/hapi__hapi the following settings-fields have problems: + // - `auth` can't be a boolean, but it can according to the @hapi/hapi source (https://github.com/hapijs/hapi/blob/v18.4.2/lib/route.js#L139) + // - `app` isn't a valid property, but it is and this was fixed in the types in v19.0.1 (https://github.com/DefinitelyTyped/DefinitelyTyped/pull/41968) settings: { tags: routeTags, auth: routeAuthRequired, app: kibanaRouteOptions }, }, raw: { @@ -120,9 +123,11 @@ type DeepPartialObject = { [P in keyof T]+?: DeepPartial }; function createRawRequestMock(customization: DeepPartial = {}) { const pathname = customization.url?.pathname || '/'; const path = `${pathname}${customization.url?.search || ''}`; - const url = Object.assign({ pathname, path, href: path }, customization.url); + const url = new URL( + formatUrl(Object.assign({ pathname, path, href: path }, customization.url)), + 'http://localhost' + ); - // @ts-expect-error _core isn't supposed to be accessed - remove once we upgrade to hapi v18 return merge( {}, { @@ -140,12 +145,6 @@ function createRawRequestMock(customization: DeepPartial = {}) { socket: {}, }, }, - // TODO: Remove once we upgrade to hapi v18 - _core: { - info: { - uri: 'http://localhost', - }, - }, }, customization ) as Request; diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index d94bce12fb439..ced728e685f3b 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -16,8 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import { Server } from 'hapi'; -import HapiStaticFiles from 'inert'; +import { Server } from '@hapi/hapi'; +import HapiStaticFiles from '@hapi/inert'; import url from 'url'; import uuid from 'uuid'; diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index df837dc35505a..4fc972c9679bb 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Server } from 'hapi'; +import { Server } from '@hapi/hapi'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { CspConfig } from '../csp'; @@ -88,6 +88,7 @@ const createInternalSetupContractMock = () => { start: jest.fn(), stop: jest.fn(), config: jest.fn().mockReturnValue(configMock.create()), + // @ts-expect-error somehow it thinks that `Server` isn't a `Construtable` } as unknown) as jest.MockedClass, createCookieSessionStorageFactory: jest.fn(), registerOnPreRouting: jest.fn(), diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index 82b141c8e50dd..0127a6493e7fd 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -19,7 +19,7 @@ import { Observable, Subscription, combineLatest } from 'rxjs'; import { first, map } from 'rxjs/operators'; -import { Server } from 'hapi'; +import { Server } from '@hapi/hapi'; import { pick } from '@kbn/std'; import { CoreService } from '../../types'; diff --git a/src/core/server/http/http_tools.test.ts b/src/core/server/http/http_tools.test.ts index bdeca3a87799a..336c491a131d6 100644 --- a/src/core/server/http/http_tools.test.ts +++ b/src/core/server/http/http_tools.test.ts @@ -31,7 +31,7 @@ jest.mock('uuid', () => ({ })); import supertest from 'supertest'; -import { Request, ResponseToolkit } from 'hapi'; +import { Request, ResponseToolkit } from '@hapi/hapi'; import Joi from 'joi'; import { diff --git a/src/core/server/http/http_tools.ts b/src/core/server/http/http_tools.ts index b70680594151b..14adbced95c7a 100644 --- a/src/core/server/http/http_tools.ts +++ b/src/core/server/http/http_tools.ts @@ -17,8 +17,8 @@ * under the License. */ -import { Lifecycle, Request, ResponseToolkit, Server, ServerOptions, Util } from 'hapi'; -import Hoek from 'hoek'; +import { Lifecycle, Request, ResponseToolkit, Server, ServerOptions, Util } from '@hapi/hapi'; +import Hoek from '@hapi/hoek'; import { ServerOptions as TLSOptions } from 'https'; import { ValidationError } from 'joi'; import uuid from 'uuid'; diff --git a/src/core/server/http/https_redirect_server.ts b/src/core/server/http/https_redirect_server.ts index 7e1086752023f..81c50c1325dd3 100644 --- a/src/core/server/http/https_redirect_server.ts +++ b/src/core/server/http/https_redirect_server.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Request, ResponseToolkit, Server } from 'hapi'; +import { Request, ResponseToolkit, Server } from '@hapi/hapi'; import { format as formatUrl } from 'url'; import { Logger } from '../logging'; diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts index f30ff66ed803a..7f785d4d67c29 100644 --- a/src/core/server/http/integration_tests/core_services.test.ts +++ b/src/core/server/http/integration_tests/core_services.test.ts @@ -23,8 +23,8 @@ import { legacyClusterClientInstanceMock, } from './core_service.test.mocks'; -import Boom from 'boom'; -import { Request } from 'hapi'; +import Boom from '@hapi/boom'; +import { Request } from '@hapi/hapi'; import { errors as esErrors } from 'elasticsearch'; import { LegacyElasticsearchErrorHelpers } from '../../elasticsearch/legacy'; diff --git a/src/core/server/http/integration_tests/request.test.ts b/src/core/server/http/integration_tests/request.test.ts index 3d0eba6de632e..710d7915c60a1 100644 --- a/src/core/server/http/integration_tests/request.test.ts +++ b/src/core/server/http/integration_tests/request.test.ts @@ -124,6 +124,54 @@ describe('KibanaRequest', () => { }); }); }); + + describe('route options', () => { + describe('authRequired', () => { + it('returns false if a route configured with "authRequired": false', async () => { + const { server: innerServer, createRouter, registerAuth } = await server.setup(setupDeps); + registerAuth((req, res, t) => t.authenticated()); + const router = createRouter('/'); + router.get( + { path: '/', validate: false, options: { authRequired: false } }, + (context, req, res) => res.ok({ body: { authRequired: req.route.options.authRequired } }) + ); + await server.start(); + + await supertest(innerServer.listener).get('/').expect(200, { + authRequired: false, + }); + }); + it('returns "optional" if a route configured with "authRequired": optional', async () => { + const { server: innerServer, createRouter, registerAuth } = await server.setup(setupDeps); + registerAuth((req, res, t) => t.authenticated()); + const router = createRouter('/'); + router.get( + { path: '/', validate: false, options: { authRequired: 'optional' } }, + (context, req, res) => res.ok({ body: { authRequired: req.route.options.authRequired } }) + ); + await server.start(); + + await supertest(innerServer.listener).get('/').expect(200, { + authRequired: 'optional', + }); + }); + it('returns true if a route configured with "authRequired": true', async () => { + const { server: innerServer, createRouter, registerAuth } = await server.setup(setupDeps); + registerAuth((req, res, t) => t.authenticated()); + const router = createRouter('/'); + router.get( + { path: '/', validate: false, options: { authRequired: true } }, + (context, req, res) => res.ok({ body: { authRequired: req.route.options.authRequired } }) + ); + await server.start(); + + await supertest(innerServer.listener).get('/').expect(200, { + authRequired: true, + }); + }); + }); + }); + describe('events', () => { describe('aborted$', () => { it('emits once and completes when request aborted', async (done) => { diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts index ad228d9b0bb9d..e6a47fa54c12e 100644 --- a/src/core/server/http/integration_tests/router.test.ts +++ b/src/core/server/http/integration_tests/router.test.ts @@ -17,7 +17,7 @@ * under the License. */ import { Stream } from 'stream'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import supertest from 'supertest'; import { schema } from '@kbn/config-schema'; @@ -769,7 +769,7 @@ describe('Response factory', () => { await supertest(innerServer.listener).get('/').expect(200); }); - it('supports answering with Stream', async () => { + it('supports answering with Stream (without custom Content-Type)', async () => { const { server: innerServer, createRouter } = await server.setup(setupDeps); const router = createRouter('/'); @@ -790,8 +790,39 @@ describe('Response factory', () => { const result = await supertest(innerServer.listener).get('/').expect(200); + expect(result.text).toBe(undefined); + expect(result.body.toString()).toBe('abc'); + expect(result.header['content-type']).toBe('application/octet-stream'); + }); + + it('supports answering with Stream (with custom Content-Type)', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.get({ path: '/', validate: false }, (context, req, res) => { + const stream = new Stream.Readable({ + read() { + this.push('a'); + this.push('b'); + this.push('c'); + this.push(null); + }, + }); + + return res.ok({ + body: stream, + headers: { + 'Content-Type': 'text/plain', + }, + }); + }); + + await server.start(); + + const result = await supertest(innerServer.listener).get('/').expect(200); + expect(result.text).toBe('abc'); - expect(result.header['content-type']).toBe(undefined); + expect(result.header['content-type']).toBe('text/plain; charset=utf-8'); }); it('supports answering with chunked Stream', async () => { @@ -807,7 +838,12 @@ describe('Response factory', () => { stream.end(); }, 100); - return res.ok({ body: stream }); + return res.ok({ + body: stream, + headers: { + 'Content-Type': 'text/plain', + }, + }); }); await server.start(); diff --git a/src/core/server/http/lifecycle/auth.ts b/src/core/server/http/lifecycle/auth.ts index 2eaf7e0f6fbfe..5fa88fd2ed3f6 100644 --- a/src/core/server/http/lifecycle/auth.ts +++ b/src/core/server/http/lifecycle/auth.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { Lifecycle, Request, ResponseToolkit } from 'hapi'; +import { Lifecycle, Request, ResponseToolkit } from '@hapi/hapi'; import { Logger } from '../../logging'; import { HapiResponseAdapter, diff --git a/src/core/server/http/lifecycle/on_post_auth.ts b/src/core/server/http/lifecycle/on_post_auth.ts index 6ce70af2082c1..047dd660ae5d1 100644 --- a/src/core/server/http/lifecycle/on_post_auth.ts +++ b/src/core/server/http/lifecycle/on_post_auth.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Lifecycle, Request, ResponseToolkit as HapiResponseToolkit } from 'hapi'; +import { Lifecycle, Request, ResponseToolkit as HapiResponseToolkit } from '@hapi/hapi'; import { Logger } from '../../logging'; import { HapiResponseAdapter, diff --git a/src/core/server/http/lifecycle/on_pre_auth.ts b/src/core/server/http/lifecycle/on_pre_auth.ts index f76fe87fd14a3..b5f0cf39d2773 100644 --- a/src/core/server/http/lifecycle/on_pre_auth.ts +++ b/src/core/server/http/lifecycle/on_pre_auth.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Lifecycle, Request, ResponseToolkit as HapiResponseToolkit } from 'hapi'; +import { Lifecycle, Request, ResponseToolkit as HapiResponseToolkit } from '@hapi/hapi'; import { Logger } from '../../logging'; import { HapiResponseAdapter, diff --git a/src/core/server/http/lifecycle/on_pre_response.ts b/src/core/server/http/lifecycle/on_pre_response.ts index 37dddf4dd4767..42179374ec672 100644 --- a/src/core/server/http/lifecycle/on_pre_response.ts +++ b/src/core/server/http/lifecycle/on_pre_response.ts @@ -17,8 +17,13 @@ * under the License. */ -import { Lifecycle, Request, ResponseObject, ResponseToolkit as HapiResponseToolkit } from 'hapi'; -import Boom from 'boom'; +import { + Lifecycle, + Request, + ResponseObject, + ResponseToolkit as HapiResponseToolkit, +} from '@hapi/hapi'; +import Boom from '@hapi/boom'; import { Logger } from '../../logging'; import { HapiResponseAdapter, KibanaRequest, ResponseHeaders } from '../router'; diff --git a/src/core/server/http/lifecycle/on_pre_routing.ts b/src/core/server/http/lifecycle/on_pre_routing.ts index e553f113a7cf8..620ad9ed276b9 100644 --- a/src/core/server/http/lifecycle/on_pre_routing.ts +++ b/src/core/server/http/lifecycle/on_pre_routing.ts @@ -17,8 +17,7 @@ * under the License. */ -import { URL } from 'url'; -import { Lifecycle, Request, ResponseToolkit as HapiResponseToolkit } from 'hapi'; +import { Lifecycle, Request, ResponseToolkit as HapiResponseToolkit } from '@hapi/hapi'; import { Logger } from '../../logging'; import { HapiResponseAdapter, @@ -111,9 +110,7 @@ export function adoptToHapiOnRequest(fn: OnPreRoutingHandler, log: Logger) { if (preRoutingResult.isRewriteUrl(result)) { const appState = request.app as KibanaRequestState; - appState.rewrittenUrl = - // @ts-expect-error request._core isn't supposed to be accessed - remove once we upgrade to hapi v18 - appState.rewrittenUrl ?? new URL(request.url.href!, request._core.info.uri); + appState.rewrittenUrl = appState.rewrittenUrl ?? request.url; const { url } = result; diff --git a/src/core/server/http/router/error_wrapper.test.ts b/src/core/server/http/router/error_wrapper.test.ts index aa20b49dc9c91..33c652c6789ad 100644 --- a/src/core/server/http/router/error_wrapper.test.ts +++ b/src/core/server/http/router/error_wrapper.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { KibanaResponse, KibanaResponseFactory, kibanaResponseFactory } from './response'; import { wrapErrors } from './error_wrapper'; import { KibanaRequest, RequestHandler, RequestHandlerContext } from 'kibana/server'; diff --git a/src/core/server/http/router/error_wrapper.ts b/src/core/server/http/router/error_wrapper.ts index 75d0e0d630296..5a4b7e9f77582 100644 --- a/src/core/server/http/router/error_wrapper.ts +++ b/src/core/server/http/router/error_wrapper.ts @@ -17,7 +17,7 @@ * under the License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { RequestHandlerWrapper } from './router'; export const wrapErrors: RequestHandlerWrapper = (handler) => { diff --git a/src/core/server/http/router/request.test.ts b/src/core/server/http/router/request.test.ts index 0bf81a7aca852..8e8ef2d692e32 100644 --- a/src/core/server/http/router/request.test.ts +++ b/src/core/server/http/router/request.test.ts @@ -21,7 +21,7 @@ jest.mock('uuid', () => ({ v4: jest.fn().mockReturnValue('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'), })); -import { RouteOptions } from 'hapi'; +import { RouteOptions } from '@hapi/hapi'; import { KibanaRequest } from './request'; import { httpServerMock } from '../http_server.mocks'; import { schema } from '@kbn/config-schema'; @@ -198,6 +198,7 @@ describe('KibanaRequest', () => { const request = httpServerMock.createRawRequest({ route: { settings: { + // @ts-expect-error According to types/hapi__hapi, `auth` can't be a boolean, but it can according to the @hapi/hapi source (https://github.com/hapijs/hapi/blob/v18.4.2/lib/route.js#L139) auth, }, }, @@ -207,11 +208,10 @@ describe('KibanaRequest', () => { expect(kibanaRequest.route.options.authRequired).toBe(false); }); it('handles required auth: { mode: "required" }', () => { - const auth: RouteOptions['auth'] = { mode: 'required' }; const request = httpServerMock.createRawRequest({ route: { settings: { - auth, + auth: { mode: 'required' }, }, }, }); @@ -221,11 +221,10 @@ describe('KibanaRequest', () => { }); it('handles required auth: { mode: "optional" }', () => { - const auth: RouteOptions['auth'] = { mode: 'optional' }; const request = httpServerMock.createRawRequest({ route: { settings: { - auth, + auth: { mode: 'optional' }, }, }, }); @@ -235,11 +234,10 @@ describe('KibanaRequest', () => { }); it('handles required auth: { mode: "try" } as "optional"', () => { - const auth: RouteOptions['auth'] = { mode: 'try' }; const request = httpServerMock.createRawRequest({ route: { settings: { - auth, + auth: { mode: 'try' }, }, }, }); @@ -249,26 +247,24 @@ describe('KibanaRequest', () => { }); it('throws on auth: strategy name', () => { - const auth: RouteOptions['auth'] = 'session'; const request = httpServerMock.createRawRequest({ route: { settings: { - auth, + auth: { strategies: ['session'] }, }, }, }); expect(() => KibanaRequest.from(request)).toThrowErrorMatchingInlineSnapshot( - `"unexpected authentication options: \\"session\\" for route: /"` + `"unexpected authentication options: {\\"strategies\\":[\\"session\\"]} for route: /"` ); }); it('throws on auth: { mode: unexpected mode }', () => { - const auth: RouteOptions['auth'] = { mode: undefined }; const request = httpServerMock.createRawRequest({ route: { settings: { - auth, + auth: { mode: undefined }, }, }, }); diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts index 561bf742050c3..57665a4fad594 100644 --- a/src/core/server/http/router/request.ts +++ b/src/core/server/http/router/request.ts @@ -19,7 +19,7 @@ import { URL } from 'url'; import uuid from 'uuid'; -import { Request, RouteOptionsApp, ApplicationState } from 'hapi'; +import { Request, RouteOptionsApp, RequestApplicationState, RouteOptions } from '@hapi/hapi'; import { Observable, fromEvent, merge } from 'rxjs'; import { shareReplay, first, takeUntil } from 'rxjs/operators'; import { RecursiveReadonly } from '@kbn/utility-types'; @@ -42,7 +42,7 @@ export interface KibanaRouteOptions extends RouteOptionsApp { /** * @internal */ -export interface KibanaRequestState extends ApplicationState { +export interface KibanaRequestState extends RequestApplicationState { requestId: string; requestUuid: string; rewrittenUrl?: URL; @@ -212,8 +212,7 @@ export class KibanaRequest< this.uuid = appState?.requestUuid ?? uuid.v4(); this.rewrittenUrl = appState?.rewrittenUrl; - // @ts-expect-error request._core isn't supposed to be accessed - remove once we upgrade to hapi v18 - this.url = new URL(request.url.href!, request._core.info.uri); + this.url = request.url; this.headers = deepFreeze({ ...request.headers }); this.isSystemRequest = request.headers['kbn-system-request'] === 'true' || @@ -261,8 +260,16 @@ export class KibanaRequest< const socketTimeout = (request.raw.req.socket as any)?.timeout; const options = ({ authRequired: this.getAuthRequired(request), - // some places in LP call KibanaRequest.from(request) manually. remove fallback to true before v8 - xsrfRequired: (request.route.settings.app as KibanaRouteOptions)?.xsrfRequired ?? true, + // TypeScript note: Casting to `RouterOptions` to fix the following error: + // + // Property 'app' does not exist on type 'RouteSettings' + // + // In @types/hapi__hapi v18, `request.route.settings` is of type + // `RouteSettings`, which doesn't have an `app` property. I think this is + // a mistake. In v19, the `RouteSettings` interface does have an `app` + // property. + xsrfRequired: + ((request.route.settings as RouteOptions).app as KibanaRouteOptions)?.xsrfRequired ?? true, // some places in LP call KibanaRequest.from(request) manually. remove fallback to true before v8 tags: request.route.settings.tags || [], timeout: { payload: payloadTimeout, @@ -302,6 +309,7 @@ export class KibanaRequest< return true; } + // @ts-expect-error According to @types/hapi__hapi, `route.settings` should be of type `RouteSettings`, but it seems that it's actually `RouteOptions` (https://github.com/hapijs/hapi/blob/v18.4.2/lib/route.js#L139) if (authOptions === false) return false; throw new Error( `unexpected authentication options: ${JSON.stringify(authOptions)} for route: ${ diff --git a/src/core/server/http/router/response_adapter.ts b/src/core/server/http/router/response_adapter.ts index 948b4596e2658..63acd2207ac3a 100644 --- a/src/core/server/http/router/response_adapter.ts +++ b/src/core/server/http/router/response_adapter.ts @@ -16,9 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -import { ResponseObject as HapiResponseObject, ResponseToolkit as HapiResponseToolkit } from 'hapi'; +import { + ResponseObject as HapiResponseObject, + ResponseToolkit as HapiResponseToolkit, +} from '@hapi/hapi'; import typeDetect from 'type-detect'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import * as stream from 'stream'; import { diff --git a/src/core/server/http/router/router.ts b/src/core/server/http/router/router.ts index cc5279a396163..b1e092ba5786a 100644 --- a/src/core/server/http/router/router.ts +++ b/src/core/server/http/router/router.ts @@ -17,8 +17,8 @@ * under the License. */ -import { Request, ResponseObject, ResponseToolkit } from 'hapi'; -import Boom from 'boom'; +import { Request, ResponseObject, ResponseToolkit } from '@hapi/hapi'; +import Boom from '@hapi/boom'; import { isConfigSchema } from '@kbn/config-schema'; import { Logger } from '../../logging'; diff --git a/src/core/server/legacy/logging/legacy_logging_server.ts b/src/core/server/legacy/logging/legacy_logging_server.ts index 096dbe54565e1..690c9c0bfe21d 100644 --- a/src/core/server/legacy/logging/legacy_logging_server.ts +++ b/src/core/server/legacy/logging/legacy_logging_server.ts @@ -17,8 +17,8 @@ * under the License. */ -import { ServerExtType } from 'hapi'; -import Podium from 'podium'; +import { ServerExtType } from '@hapi/hapi'; +import Podium from '@hapi/podium'; // @ts-expect-error: implicit any for JS file import { Config } from '../../../../legacy/server/config'; // @ts-expect-error: implicit any for JS file diff --git a/src/core/server/metrics/collectors/process.ts b/src/core/server/metrics/collectors/process.ts index b020eebcbbd0b..8b9cabe6227b6 100644 --- a/src/core/server/metrics/collectors/process.ts +++ b/src/core/server/metrics/collectors/process.ts @@ -18,7 +18,7 @@ */ import v8 from 'v8'; -import { Bench } from 'hoek'; +import { Bench } from '@hapi/hoek'; import { OpsProcessMetrics, MetricsCollector } from './types'; export class ProcessMetricsCollector implements MetricsCollector { diff --git a/src/core/server/metrics/collectors/server.ts b/src/core/server/metrics/collectors/server.ts index 036332c24c34f..e3dac1d1de897 100644 --- a/src/core/server/metrics/collectors/server.ts +++ b/src/core/server/metrics/collectors/server.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { ResponseObject, Server as HapiServer } from 'hapi'; +import { ResponseObject, Server as HapiServer } from '@hapi/hapi'; import { OpsServerMetrics, MetricsCollector } from './types'; interface ServerResponseTime { diff --git a/src/core/server/metrics/integration_tests/server_collector.test.ts b/src/core/server/metrics/integration_tests/server_collector.test.ts index 4476b3c26a2e1..ee3a066505a16 100644 --- a/src/core/server/metrics/integration_tests/server_collector.test.ts +++ b/src/core/server/metrics/integration_tests/server_collector.test.ts @@ -20,7 +20,7 @@ import { BehaviorSubject, Subject } from 'rxjs'; import { take, filter } from 'rxjs/operators'; import supertest from 'supertest'; -import { Server as HapiServer } from 'hapi'; +import { Server as HapiServer } from '@hapi/hapi'; import { createHttpServer } from '../../http/test_utils'; import { HttpService, IRouter } from '../../http'; import { contextServiceMock } from '../../context/context_service.mock'; diff --git a/src/core/server/metrics/ops_metrics_collector.ts b/src/core/server/metrics/ops_metrics_collector.ts index af74caa6cb386..184a4f3451c36 100644 --- a/src/core/server/metrics/ops_metrics_collector.ts +++ b/src/core/server/metrics/ops_metrics_collector.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Server as HapiServer } from 'hapi'; +import { Server as HapiServer } from '@hapi/hapi'; import { ProcessMetricsCollector, OsMetricsCollector, diff --git a/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts b/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts index 214b51db7dd6b..9b13c9b24ffdc 100644 --- a/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts +++ b/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts @@ -17,7 +17,7 @@ * under the License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { createListStream } from '../../utils/streams'; import { SavedObjectsClientContract, SavedObject } from '../types'; import { fetchNestedDependencies } from './inject_nested_depdendencies'; diff --git a/src/core/server/saved_objects/import/create_limit_stream.ts b/src/core/server/saved_objects/import/create_limit_stream.ts index 8f6ac91d0d315..709bb3b2d0065 100644 --- a/src/core/server/saved_objects/import/create_limit_stream.ts +++ b/src/core/server/saved_objects/import/create_limit_stream.ts @@ -17,7 +17,7 @@ * under the License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { Transform } from 'stream'; export function createLimitStream(limit: number) { diff --git a/src/core/server/saved_objects/import/validate_references.ts b/src/core/server/saved_objects/import/validate_references.ts index 89fe8ec8c0901..b0686215c00dd 100644 --- a/src/core/server/saved_objects/import/validate_references.ts +++ b/src/core/server/saved_objects/import/validate_references.ts @@ -17,7 +17,7 @@ * under the License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { SavedObject, SavedObjectsClientContract } from '../types'; import { SavedObjectsImportError, SavedObjectsImportRetry } from './types'; diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.ts b/src/core/server/saved_objects/migrations/core/document_migrator.ts index 345704fbfd783..ccda72702b53c 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.ts @@ -60,7 +60,7 @@ * given an empty migrationVersion property {} if no such property exists. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { set } from '@elastic/safer-lodash-set'; import _ from 'lodash'; import Semver from 'semver'; diff --git a/src/core/server/saved_objects/service/lib/errors.test.ts b/src/core/server/saved_objects/service/lib/errors.test.ts index 931d9f725e412..497dc1bbe4c45 100644 --- a/src/core/server/saved_objects/service/lib/errors.test.ts +++ b/src/core/server/saved_objects/service/lib/errors.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { SavedObjectsErrorHelpers } from './errors'; diff --git a/src/core/server/saved_objects/service/lib/errors.ts b/src/core/server/saved_objects/service/lib/errors.ts index 6fd5bc9de0ec5..78956ca8d6868 100644 --- a/src/core/server/saved_objects/service/lib/errors.ts +++ b/src/core/server/saved_objects/service/lib/errors.ts @@ -17,7 +17,7 @@ * under the License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; // 400 - badRequest const CODE_BAD_REQUEST = 'SavedObjectsClient/badRequest'; diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts index 858770579fb9e..fca361b8ffda0 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts @@ -17,7 +17,7 @@ * under the License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { IndexMapping } from '../../../mappings'; import { getQueryParams } from './query_params'; diff --git a/src/core/server/saved_objects/service/lib/search_dsl/sorting_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/sorting_params.ts index ccf5ccd50bb75..a56cf6462dbe7 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/sorting_params.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/sorting_params.ts @@ -17,7 +17,7 @@ * under the License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { getProperty, IndexMapping } from '../../../mappings'; const TOP_LEVEL_FIELDS = ['_id', '_score']; diff --git a/src/core/server/saved_objects/version/decode_version.test.ts b/src/core/server/saved_objects/version/decode_version.test.ts index b157d97ae8a23..987af3e693823 100644 --- a/src/core/server/saved_objects/version/decode_version.test.ts +++ b/src/core/server/saved_objects/version/decode_version.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { decodeVersion } from './decode_version'; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 914b5fbdb5196..4562b6d696c87 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -5,7 +5,7 @@ ```ts import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { BulkIndexDocumentsParams } from 'elasticsearch'; import { CatAliasesParams } from 'elasticsearch'; import { CatAllocationParams } from 'elasticsearch'; @@ -129,16 +129,16 @@ import { RecursiveReadonly } from '@kbn/utility-types'; import { ReindexParams } from 'elasticsearch'; import { ReindexRethrottleParams } from 'elasticsearch'; import { RenderSearchTemplateParams } from 'elasticsearch'; -import { Request } from 'hapi'; -import { ResponseObject } from 'hapi'; -import { ResponseToolkit } from 'hapi'; +import { Request } from '@hapi/hapi'; +import { ResponseObject } from '@hapi/hapi'; +import { ResponseToolkit } from '@hapi/hapi'; import { SchemaTypeError } from '@kbn/config-schema'; import { ScrollParams } from 'elasticsearch'; import { SearchParams } from 'elasticsearch'; import { SearchResponse as SearchResponse_2 } from 'elasticsearch'; import { SearchShardsParams } from 'elasticsearch'; import { SearchTemplateParams } from 'elasticsearch'; -import { Server } from 'hapi'; +import { Server } from '@hapi/hapi'; import { ShallowPromise } from '@kbn/utility-types'; import { SnapshotCreateParams } from 'elasticsearch'; import { SnapshotCreateRepositoryParams } from 'elasticsearch'; diff --git a/src/fixtures/telemetry_collectors/schema_defined_with_spreads_collector.ts b/src/fixtures/telemetry_collectors/schema_defined_with_spreads_collector.ts index af9fef0bbd297..791c366199d74 100644 --- a/src/fixtures/telemetry_collectors/schema_defined_with_spreads_collector.ts +++ b/src/fixtures/telemetry_collectors/schema_defined_with_spreads_collector.ts @@ -49,7 +49,7 @@ const someSchema: MakeSchemaFrom> = { const someOtherSchema: MakeSchemaFrom> = { my_objects: { total: { - type: 'number', + type: 'long', }, type: { type: 'boolean' }, }, diff --git a/src/fixtures/telemetry_collectors/working_collector.ts b/src/fixtures/telemetry_collectors/working_collector.ts index 0a3bf49638a7b..f9cb3bb568673 100644 --- a/src/fixtures/telemetry_collectors/working_collector.ts +++ b/src/fixtures/telemetry_collectors/working_collector.ts @@ -85,7 +85,7 @@ export const myCollector = makeUsageCollector({ }, my_objects: { total: { - type: 'number', + type: 'long', }, type: { type: 'boolean' }, }, @@ -93,17 +93,17 @@ export const myCollector = makeUsageCollector({ type: 'array', items: { total: { - type: 'number', + type: 'long', }, type: { type: 'boolean' }, }, }, my_str_array: { type: 'array', items: { type: 'keyword' } }, my_index_signature_prop: { - count: { type: 'number' }, - avg: { type: 'number' }, - max: { type: 'number' }, - min: { type: 'number' }, + count: { type: 'long' }, + avg: { type: 'float' }, + max: { type: 'long' }, + min: { type: 'long' }, }, }, }); diff --git a/src/legacy/server/core/index.ts b/src/legacy/server/core/index.ts index de5391d33cada..bfa7853de233d 100644 --- a/src/legacy/server/core/index.ts +++ b/src/legacy/server/core/index.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Server } from 'hapi'; +import { Server } from '@hapi/hapi'; import KbnServer from '../kbn_server'; /** diff --git a/src/legacy/server/http/index.js b/src/legacy/server/http/index.js index 0cab1a1609287..16705e9241be8 100644 --- a/src/legacy/server/http/index.js +++ b/src/legacy/server/http/index.js @@ -18,7 +18,7 @@ */ import { format } from 'url'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { registerHapiPlugins } from './register_hapi_plugins'; import { setupBasePathProvider } from './setup_base_path_provider'; diff --git a/src/legacy/server/http/register_hapi_plugins.js b/src/legacy/server/http/register_hapi_plugins.js index 103e4a77cb4b2..19560351f3624 100644 --- a/src/legacy/server/http/register_hapi_plugins.js +++ b/src/legacy/server/http/register_hapi_plugins.js @@ -17,9 +17,9 @@ * under the License. */ -import HapiTemplates from 'vision'; -import HapiStaticFiles from 'inert'; -import HapiProxy from 'h2o2'; +import HapiTemplates from '@hapi/vision'; +import HapiStaticFiles from '@hapi/inert'; +import HapiProxy from '@hapi/h2o2'; const plugins = [HapiTemplates, HapiStaticFiles, HapiProxy]; diff --git a/src/legacy/server/i18n/i18n_mixin.ts b/src/legacy/server/i18n/i18n_mixin.ts index 4f77fa8df96cd..0b3879073c164 100644 --- a/src/legacy/server/i18n/i18n_mixin.ts +++ b/src/legacy/server/i18n/i18n_mixin.ts @@ -19,7 +19,7 @@ import { i18n, i18nLoader } from '@kbn/i18n'; import { basename } from 'path'; -import { Server } from 'hapi'; +import { Server } from '@hapi/hapi'; import type { UsageCollectionSetup } from '../../../plugins/usage_collection/server'; import { getKibanaTranslationPaths } from './get_kibana_translation_paths'; import KbnServer, { KibanaConfig } from '../kbn_server'; diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 1718a9a8f55da..224398e1603ea 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Server } from 'hapi'; +import { Server } from '@hapi/hapi'; import { CoreSetup, @@ -104,4 +104,4 @@ export default class KbnServer { } // Re-export commonly used hapi types. -export { Server, Request, ResponseToolkit } from 'hapi'; +export { Server, Request, ResponseToolkit } from '@hapi/hapi'; diff --git a/src/legacy/server/logging/rotate/index.ts b/src/legacy/server/logging/rotate/index.ts index 646c89efe8e20..d6b7cfa76f9ee 100644 --- a/src/legacy/server/logging/rotate/index.ts +++ b/src/legacy/server/logging/rotate/index.ts @@ -18,7 +18,7 @@ */ import { isMaster, isWorker } from 'cluster'; -import { Server } from 'hapi'; +import { Server } from '@hapi/hapi'; import { LogRotator } from './log_rotator'; import { KibanaConfig } from '../../kbn_server'; diff --git a/src/legacy/server/logging/rotate/log_rotator.ts b/src/legacy/server/logging/rotate/log_rotator.ts index 22183b2f0777a..c4054b2daed45 100644 --- a/src/legacy/server/logging/rotate/log_rotator.ts +++ b/src/legacy/server/logging/rotate/log_rotator.ts @@ -20,7 +20,7 @@ import * as chokidar from 'chokidar'; import { isMaster } from 'cluster'; import fs from 'fs'; -import { Server } from 'hapi'; +import { Server } from '@hapi/hapi'; import { throttle } from 'lodash'; import { tmpdir } from 'os'; import { basename, dirname, join, sep } from 'path'; diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 2983dbbc28667..a02c2fca14c18 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -18,7 +18,7 @@ */ import { createHash } from 'crypto'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { i18n } from '@kbn/i18n'; import * as UiSharedDeps from '@kbn/ui-shared-deps'; import { KibanaRequest } from '../../../core/server'; diff --git a/src/optimize/bundles_route/bundles_route.test.ts b/src/optimize/bundles_route/bundles_route.test.ts index 8cf4e9d72685c..cbf9db2f3cd4b 100644 --- a/src/optimize/bundles_route/bundles_route.test.ts +++ b/src/optimize/bundles_route/bundles_route.test.ts @@ -22,8 +22,8 @@ import { readFileSync } from 'fs'; import crypto from 'crypto'; import Chance from 'chance'; -import Hapi from 'hapi'; -import Inert from 'inert'; +import Hapi from '@hapi/hapi'; +import Inert from '@hapi/inert'; import { createBundlesRoute } from './bundles_route'; diff --git a/src/optimize/bundles_route/bundles_route.ts b/src/optimize/bundles_route/bundles_route.ts index 6d618fba50ccf..0546ebda22761 100644 --- a/src/optimize/bundles_route/bundles_route.ts +++ b/src/optimize/bundles_route/bundles_route.ts @@ -19,7 +19,7 @@ import { extname, join } from 'path'; -import Hapi from 'hapi'; +import Hapi from '@hapi/hapi'; import * as UiSharedDeps from '@kbn/ui-shared-deps'; import { createDynamicAssetResponse } from './dynamic_asset_response'; diff --git a/src/optimize/bundles_route/dynamic_asset_response.ts b/src/optimize/bundles_route/dynamic_asset_response.ts index ce839b33ccac0..43560c08f4919 100644 --- a/src/optimize/bundles_route/dynamic_asset_response.ts +++ b/src/optimize/bundles_route/dynamic_asset_response.ts @@ -22,8 +22,8 @@ import { resolve } from 'path'; import { promisify } from 'util'; import Accept from 'accept'; -import Boom from 'boom'; -import Hapi from 'hapi'; +import Boom from '@hapi/boom'; +import Hapi from '@hapi/hapi'; import { FileHashCache } from './file_hash_cache'; import { getFileHash } from './file_hash'; diff --git a/src/optimize/optimize_mixin.ts b/src/optimize/optimize_mixin.ts index 37f8b08dde3b8..8bcb2c0a68a4a 100644 --- a/src/optimize/optimize_mixin.ts +++ b/src/optimize/optimize_mixin.ts @@ -17,7 +17,7 @@ * under the License. */ -import Hapi from 'hapi'; +import Hapi from '@hapi/hapi'; import { createBundlesRoute } from './bundles_route'; import { getNpUiPluginPublicDirs } from './np_ui_plugin_public_dirs'; diff --git a/src/plugins/advanced_settings/README.md b/src/plugins/advanced_settings/README.md new file mode 100644 index 0000000000000..a364348a8e99a --- /dev/null +++ b/src/plugins/advanced_settings/README.md @@ -0,0 +1,5 @@ +# Advanced Settings + +This plugin contains the advanced settings management section +allowing users to configure their advanced settings, also known +as uiSettings within the code. \ No newline at end of file diff --git a/src/plugins/charts/public/static/components/number_input.tsx b/src/plugins/charts/public/static/components/number_input.tsx index 8c2874f522902..68e292861c32e 100644 --- a/src/plugins/charts/public/static/components/number_input.tsx +++ b/src/plugins/charts/public/static/components/number_input.tsx @@ -54,7 +54,7 @@ function NumberInputOption({ 'data-test-subj': dataTestSubj, }: NumberInputOptionProps) { return ( - + ({ } }; return ( - + ({ ); return ( - + ({ setValue, }: SwitchOptionProps) { return ( - + ({ setValue, }: TextInputOptionProps) { return ( - + { return { clear: jest.fn(), start: jest.fn(), + restore: jest.fn(), getSessionId: jest.fn(), getSession$: jest.fn(), }; diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/common/search/session/types.ts index 80ab74f1aa14d..6660b8395547f 100644 --- a/src/plugins/data/common/search/session/types.ts +++ b/src/plugins/data/common/search/session/types.ts @@ -34,6 +34,12 @@ export interface ISessionService { * Starts a new session */ start: () => string; + + /** + * Restores existing session + */ + restore: (sessionId: string) => void; + /** * Clears the active session. */ diff --git a/src/plugins/data/common/utils/abort_utils.test.ts b/src/plugins/data/common/utils/abort_utils.test.ts index ca187e95f528b..358f00e5e82bd 100644 --- a/src/plugins/data/common/utils/abort_utils.test.ts +++ b/src/plugins/data/common/utils/abort_utils.test.ts @@ -41,7 +41,7 @@ describe('AbortUtils', () => { describe('rejects', () => { test('should not reject if the signal does not abort', async () => { const controller = new AbortController(); - const promise = toPromise(controller.signal); + const promise = toPromise(controller.signal).promise; const whenRejected = jest.fn(); promise.catch(whenRejected); await flushPromises(); @@ -50,7 +50,7 @@ describe('AbortUtils', () => { test('should reject if the signal does abort', async () => { const controller = new AbortController(); - const promise = toPromise(controller.signal); + const promise = toPromise(controller.signal).promise; const whenRejected = jest.fn(); promise.catch(whenRejected); controller.abort(); @@ -58,13 +58,30 @@ describe('AbortUtils', () => { expect(whenRejected).toBeCalled(); expect(whenRejected.mock.calls[0][0]).toBeInstanceOf(AbortError); }); + + test('should expose cleanup handler', () => { + const controller = new AbortController(); + const promise = toPromise(controller.signal); + expect(promise.cleanup).toBeDefined(); + }); + + test('calling clean up handler prevents rejects', async () => { + const controller = new AbortController(); + const { promise, cleanup } = toPromise(controller.signal); + const whenRejected = jest.fn(); + promise.catch(whenRejected); + cleanup(); + controller.abort(); + await flushPromises(); + expect(whenRejected).not.toBeCalled(); + }); }); }); describe('getCombinedSignal', () => { test('should return an AbortSignal', () => { - const signal = getCombinedSignal([]); - expect(signal instanceof AbortSignal).toBe(true); + const signal = getCombinedSignal([]).signal; + expect(signal).toBeInstanceOf(AbortSignal); }); test('should not abort if none of the signals abort', async () => { @@ -72,7 +89,7 @@ describe('AbortUtils', () => { const controller2 = new AbortController(); setTimeout(() => controller1.abort(), 2000); setTimeout(() => controller2.abort(), 1000); - const signal = getCombinedSignal([controller1.signal, controller2.signal]); + const signal = getCombinedSignal([controller1.signal, controller2.signal]).signal; expect(signal.aborted).toBe(false); jest.advanceTimersByTime(500); await flushPromises(); @@ -84,7 +101,7 @@ describe('AbortUtils', () => { const controller2 = new AbortController(); setTimeout(() => controller1.abort(), 2000); setTimeout(() => controller2.abort(), 1000); - const signal = getCombinedSignal([controller1.signal, controller2.signal]); + const signal = getCombinedSignal([controller1.signal, controller2.signal]).signal; expect(signal.aborted).toBe(false); jest.advanceTimersByTime(1000); await flushPromises(); @@ -95,8 +112,56 @@ describe('AbortUtils', () => { const controller1 = new AbortController(); const controller2 = new AbortController(); controller1.abort(); - const signal = getCombinedSignal([controller1.signal, controller2.signal]); + const signal = getCombinedSignal([controller1.signal, controller2.signal]).signal; expect(signal.aborted).toBe(true); }); + + describe('cleanup listener', () => { + const createMockController = () => { + const controller = new AbortController(); + const spyAddListener = jest.spyOn(controller.signal, 'addEventListener'); + const spyRemoveListener = jest.spyOn(controller.signal, 'removeEventListener'); + return { + controller, + getTotalListeners: () => + Math.max(spyAddListener.mock.calls.length - spyRemoveListener.mock.calls.length, 0), + }; + }; + + test('cleanup should cleanup inner listeners', () => { + const controller1 = createMockController(); + const controller2 = createMockController(); + + const { cleanup } = getCombinedSignal([ + controller1.controller.signal, + controller2.controller.signal, + ]); + + expect(controller1.getTotalListeners()).toBe(1); + expect(controller2.getTotalListeners()).toBe(1); + + cleanup(); + + expect(controller1.getTotalListeners()).toBe(0); + expect(controller2.getTotalListeners()).toBe(0); + }); + + test('abort should cleanup inner listeners', async () => { + const controller1 = createMockController(); + const controller2 = createMockController(); + + getCombinedSignal([controller1.controller.signal, controller2.controller.signal]); + + expect(controller1.getTotalListeners()).toBe(1); + expect(controller2.getTotalListeners()).toBe(1); + + controller1.controller.abort(); + + await flushPromises(); + + expect(controller1.getTotalListeners()).toBe(0); + expect(controller2.getTotalListeners()).toBe(0); + }); + }); }); }); diff --git a/src/plugins/data/common/utils/abort_utils.ts b/src/plugins/data/common/utils/abort_utils.ts index a26fec9423f83..81f30b7454c7b 100644 --- a/src/plugins/data/common/utils/abort_utils.ts +++ b/src/plugins/data/common/utils/abort_utils.ts @@ -36,15 +36,23 @@ export class AbortError extends Error { * * @param signal The `AbortSignal` to generate the `Promise` from */ -export function toPromise(signal: AbortSignal): Promise { - return new Promise((resolve, reject) => { - if (signal.aborted) reject(new AbortError()); - const abortHandler = () => { +export function toPromise(signal: AbortSignal): { promise: Promise; cleanup: () => void } { + let abortHandler: () => void; + const cleanup = () => { + if (abortHandler) { signal.removeEventListener('abort', abortHandler); + } + }; + const promise = new Promise((resolve, reject) => { + if (signal.aborted) reject(new AbortError()); + abortHandler = () => { + cleanup(); reject(new AbortError()); }; signal.addEventListener('abort', abortHandler); }); + + return { promise, cleanup }; } /** @@ -52,13 +60,26 @@ export function toPromise(signal: AbortSignal): Promise { * * @param signals */ -export function getCombinedSignal(signals: AbortSignal[]) { +export function getCombinedSignal( + signals: AbortSignal[] +): { signal: AbortSignal; cleanup: () => void } { const controller = new AbortController(); + let cleanup = () => {}; + if (signals.some((signal) => signal.aborted)) { controller.abort(); } else { const promises = signals.map((signal) => toPromise(signal)); - Promise.race(promises).catch(() => controller.abort()); + cleanup = () => { + promises.forEach((p) => p.cleanup()); + controller.signal.removeEventListener('abort', cleanup); + }; + controller.signal.addEventListener('abort', cleanup); + Promise.race(promises.map((p) => p.promise)).catch(() => { + cleanup(); + controller.abort(); + }); } - return controller.signal; + + return { signal: controller.signal, cleanup }; } diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index c041511745be2..c54cb36142cbd 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -380,7 +380,7 @@ export { PainlessError, } from './search'; -export type { SearchSource } from './search'; +export type { SearchSource, ISessionService } from './search'; export { ISearchOptions, isErrorResponse, isCompleteResponse, isPartialResponse } from '../common'; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 7ee21236c1c79..86a8f73c1e8ff 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -11,7 +11,7 @@ import { ApiResponse as ApiResponse_2 } from '@elastic/elasticsearch/lib/Transpo import { ApplicationStart } from 'kibana/public'; import { Assign } from '@kbn/utility-types'; import { BehaviorSubject } from 'rxjs'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { CoreSetup } from 'src/core/public'; import { CoreSetup as CoreSetup_2 } from 'kibana/public'; import { CoreStart } from 'kibana/public'; @@ -1415,8 +1415,6 @@ export interface ISearchSetup { // // (undocumented) aggs: AggsSetup; - // Warning: (ae-forgotten-export) The symbol "ISessionService" needs to be exported by the entry point index.d.ts - // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "ISessionService" session: ISessionService; // Warning: (ae-forgotten-export) The symbol "SearchUsageCollector" needs to be exported by the entry point index.d.ts // @@ -1432,7 +1430,6 @@ export interface ISearchStart { aggs: AggsStart; search: ISearchGeneric; searchSource: ISearchStartSearchSource; - // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "ISessionService" session: ISessionService; // (undocumented) showError: (e: Error) => void; @@ -1449,6 +1446,17 @@ export interface ISearchStartSearchSource { // @public (undocumented) export const isErrorResponse: (response?: IKibanaSearchResponse | undefined) => boolean | undefined; +// Warning: (ae-missing-release-tag) "ISessionService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ISessionService { + clear: () => void; + getSession$: () => Observable; + getSessionId: () => string | undefined; + restore: (sessionId: string) => void; + start: () => string; +} + // Warning: (ae-missing-release-tag) "isFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 86804a819cb0e..1abf3192a4846 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -41,6 +41,7 @@ export { SearchSourceDependencies, SearchSourceFields, SortDirection, + ISessionService, } from '../../common/search'; export { getEsPreference } from './es_search'; diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts index 087ca9e4f5c47..0f069d54ee9c8 100644 --- a/src/plugins/data/public/search/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor.ts @@ -24,13 +24,13 @@ import { PublicMethodsOf } from '@kbn/utility-types'; import { CoreStart, CoreSetup, ToastsSetup } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { - getCombinedSignal, AbortError, IKibanaSearchRequest, IKibanaSearchResponse, ISearchOptions, ES_SEARCH_STRATEGY, ISessionService, + getCombinedSignal, } from '../../common'; import { SearchUsageCollector } from './collectors'; import { @@ -171,11 +171,12 @@ export class SearchInterceptor { ...(abortSignal ? [abortSignal] : []), ]; - const combinedSignal = getCombinedSignal(signals); + const { signal: combinedSignal, cleanup: cleanupCombinedSignal } = getCombinedSignal(signals); const cleanup = () => { subscription.unsubscribe(); + combinedSignal.removeEventListener('abort', cleanup); + cleanupCombinedSignal(); }; - combinedSignal.addEventListener('abort', cleanup); return { diff --git a/src/plugins/data/public/search/session_service.test.ts b/src/plugins/data/public/search/session_service.test.ts index dd64d187f47d6..bcfd06944d983 100644 --- a/src/plugins/data/public/search/session_service.test.ts +++ b/src/plugins/data/public/search/session_service.test.ts @@ -20,6 +20,7 @@ import { SessionService } from './session_service'; import { ISessionService } from '../../common'; import { coreMock } from '../../../../core/public/mocks'; +import { take, toArray } from 'rxjs/operators'; describe('Session service', () => { let sessionService: ISessionService; @@ -39,5 +40,20 @@ describe('Session service', () => { sessionService.clear(); expect(sessionService.getSessionId()).toBeUndefined(); }); + + it('Restores a session', async () => { + const sessionId = 'sessionId'; + sessionService.restore(sessionId); + expect(sessionService.getSessionId()).toBe(sessionId); + }); + + it('sessionId$ observable emits current value', async () => { + sessionService.restore('1'); + const emittedValues = sessionService.getSession$().pipe(take(3), toArray()).toPromise(); + sessionService.restore('2'); + sessionService.clear(); + + expect(await emittedValues).toEqual(['1', '2', undefined]); + }); }); }); diff --git a/src/plugins/data/public/search/session_service.ts b/src/plugins/data/public/search/session_service.ts index 31524434af302..a172738812937 100644 --- a/src/plugins/data/public/search/session_service.ts +++ b/src/plugins/data/public/search/session_service.ts @@ -18,14 +18,16 @@ */ import uuid from 'uuid'; -import { Subject, Subscription } from 'rxjs'; +import { BehaviorSubject, Subscription } from 'rxjs'; import { PluginInitializerContext, StartServicesAccessor } from 'kibana/public'; -import { ISessionService } from '../../common/search'; import { ConfigSchema } from '../../config'; +import { ISessionService } from '../../common/search'; export class SessionService implements ISessionService { - private sessionId?: string; - private session$: Subject = new Subject(); + private session$ = new BehaviorSubject(undefined); + private get sessionId() { + return this.session$.getValue(); + } private appChangeSubscription$?: Subscription; private curApp?: string; @@ -68,13 +70,15 @@ export class SessionService implements ISessionService { } public start() { - this.sessionId = uuid.v4(); - this.session$.next(this.sessionId); - return this.sessionId; + this.session$.next(uuid.v4()); + return this.sessionId!; + } + + public restore(sessionId: string) { + this.session$.next(sessionId); } public clear() { - this.sessionId = undefined; - this.session$.next(this.sessionId); + this.session$.next(undefined); } } diff --git a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx index 0d544ac9ad16a..8c009576ff280 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_bar.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_bar.tsx @@ -105,7 +105,6 @@ function FilterBarUI(props: Props) { isOpen={isAddFilterPopoverOpen} closePopover={() => setIsAddFilterPopoverOpen(false)} anchorPosition="downLeft" - withTitle panelPaddingSize="none" ownFocus={true} initialFocus=".filterEditor__hiddenItem" diff --git a/src/plugins/data/public/ui/filter_bar/filter_item.tsx b/src/plugins/data/public/ui/filter_bar/filter_item.tsx index 018f41ab82bfc..48dbfea634256 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_item.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_item.tsx @@ -358,7 +358,6 @@ export function FilterItem(props: Props) { }} button={badge} anchorPosition="downLeft" - withTitle={true} panelPaddingSize="none" > diff --git a/src/plugins/data/public/ui/filter_bar/filter_options.tsx b/src/plugins/data/public/ui/filter_bar/filter_options.tsx index b97e0e33f2400..46dda2382a5ca 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_options.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_options.tsx @@ -166,7 +166,6 @@ class FilterOptionsUI extends Component { } anchorPosition="rightUp" panelPaddingSize="none" - withTitle repositionOnScroll > diff --git a/src/plugins/data/public/ui/query_string_input/language_switcher.tsx b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx index 4d51b173f6743..3957e59388acf 100644 --- a/src/plugins/data/public/ui/query_string_input/language_switcher.tsx +++ b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx @@ -76,7 +76,6 @@ export function QueryLanguageSwitcher(props: Props) { button={button} isOpen={isPopoverOpen} closePopover={() => setIsPopoverOpen(false)} - withTitle repositionOnScroll > diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/errors.ts b/src/plugins/data/server/index_patterns/fetcher/lib/errors.ts index e5a96c67c56b5..cbb3c2fbf3442 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/errors.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/errors.ts @@ -17,7 +17,7 @@ * under the License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { get } from 'lodash'; const ERR_ES_INDEX_NOT_FOUND = 'index_not_found_exception'; diff --git a/src/plugins/data/server/plugins_data_server.api.md b/src/plugins/data/server/plugins_data_server.api.md index 0f0abe7df8a39..9faa7439d70a4 100644 --- a/src/plugins/data/server/plugins_data_server.api.md +++ b/src/plugins/data/server/plugins_data_server.api.md @@ -5,7 +5,7 @@ ```ts import { APICaller as APICaller_2 } from 'kibana/server'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { BulkIndexDocumentsParams } from 'elasticsearch'; import { CallCluster as CallCluster_2 } from 'src/legacy/core_plugins/elasticsearch'; import { CatAliasesParams } from 'elasticsearch'; @@ -116,9 +116,9 @@ import { RecursiveReadonly } from 'kibana/public'; import { ReindexParams } from 'elasticsearch'; import { ReindexRethrottleParams } from 'elasticsearch'; import { RenderSearchTemplateParams } from 'elasticsearch'; -import { Request } from 'hapi'; -import { ResponseObject } from 'hapi'; -import { ResponseToolkit } from 'hapi'; +import { Request } from '@hapi/hapi'; +import { ResponseObject } from '@hapi/hapi'; +import { ResponseToolkit } from '@hapi/hapi'; import { SchemaTypeError } from '@kbn/config-schema'; import { ScrollParams } from 'elasticsearch'; import { SearchParams } from 'elasticsearch'; diff --git a/src/plugins/data/server/search/collectors/register.ts b/src/plugins/data/server/search/collectors/register.ts index ab0ea93edd49e..5db4f52169350 100644 --- a/src/plugins/data/server/search/collectors/register.ts +++ b/src/plugins/data/server/search/collectors/register.ts @@ -37,9 +37,9 @@ export async function registerUsageCollector( isReady: () => true, fetch: fetchProvider(context.config.legacy.globalConfig$), schema: { - successCount: { type: 'number' }, - errorCount: { type: 'number' }, - averageDuration: { type: 'long' }, + successCount: { type: 'long' }, + errorCount: { type: 'long' }, + averageDuration: { type: 'float' }, }, }); usageCollection.registerCollector(collector); diff --git a/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.tsx b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.tsx index ac88d2aa36696..d294ffca86341 100644 --- a/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.tsx +++ b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { @@ -86,7 +86,11 @@ export function ActionBar({ onChangeCount(newDocCount); } }; - + useEffect(() => { + if (newDocCount !== docCount && newDocCount === 0) { + setNewDocCount(docCount); + } + }, [docCount, newDocCount]); return (

@@ -145,7 +149,7 @@ export function ActionBar({ - + {isSuccessor ? ( - - + - - -
- - -
+ reason="contextApp.state.loadingStatus.anchor.reason" + default-step-size="contextApp.state.queryParameters.defaultStepSize" + predecessor-count="contextApp.state.queryParameters.predecessorCount" + predecessor-available="contextApp.state.rows.predecessors.length" + predecessor-status="contextApp.state.loadingStatus.predecessors.status" + on-change-predecessor-count="contextApp.actions.fetchGivenPredecessorRows" + successor-count="contextApp.state.queryParameters.successorCount" + successor-available="contextApp.state.rows.successors.length" + successor-status="contextApp.state.loadingStatus.successors.status" + on-change-successor-count="contextApp.actions.fetchGivenSuccessorRows" +> diff --git a/src/plugins/discover/public/application/components/context_app/context_app_legacy.scss b/src/plugins/discover/public/application/components/context_app/context_app_legacy.scss new file mode 100644 index 0000000000000..87194d834827b --- /dev/null +++ b/src/plugins/discover/public/application/components/context_app/context_app_legacy.scss @@ -0,0 +1,5 @@ +.dscCxtAppContent { + border: none; + background-color: transparent; + box-shadow: none; +} diff --git a/src/plugins/discover/public/application/components/context_app/context_app_legacy.test.tsx b/src/plugins/discover/public/application/components/context_app/context_app_legacy.test.tsx index 25576a9072944..77dd0a6d647c6 100644 --- a/src/plugins/discover/public/application/components/context_app/context_app_legacy.test.tsx +++ b/src/plugins/discover/public/application/components/context_app/context_app_legacy.test.tsx @@ -24,6 +24,7 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { DocTableLegacy } from '../../angular/doc_table/create_doc_table_react'; import { findTestSubject } from '@elastic/eui/lib/test'; import { ActionBar } from '../../angular/context/components/action_bar/action_bar'; +import { ContextErrorMessage } from '../context_error_message'; describe('ContextAppLegacy test', () => { const hit = { @@ -53,6 +54,7 @@ describe('ContextAppLegacy test', () => { minimumVisibleRows: 5, indexPattern, status: 'loaded', + reason: 'no reason', defaultStepSize: 5, predecessorCount: 10, successorCount: 10, @@ -76,9 +78,19 @@ describe('ContextAppLegacy test', () => { const props = { ...defaultProps }; props.status = 'loading'; const component = mountWithIntl(); - expect(component.find('DocTableLegacy').length).toBe(0); + expect(component.find(DocTableLegacy).length).toBe(0); const loadingIndicator = findTestSubject(component, 'contextApp_loadingIndicator'); expect(loadingIndicator.length).toBe(1); expect(component.find(ActionBar).length).toBe(2); }); + + it('renders error message', () => { + const props = { ...defaultProps }; + props.status = 'failed'; + props.reason = 'something went wrong'; + const component = mountWithIntl(); + expect(component.find(DocTableLegacy).length).toBe(0); + const errorMessage = component.find(ContextErrorMessage); + expect(errorMessage.length).toBe(1); + }); }); diff --git a/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx b/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx index afb4a9a981e21..b5387ec51db81 100644 --- a/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx +++ b/src/plugins/discover/public/application/components/context_app/context_app_legacy.tsx @@ -16,9 +16,11 @@ * specific language governing permissions and limitations * under the License. */ +import './context_app_legacy.scss'; import React from 'react'; import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; -import { EuiPanel, EuiText } from '@elastic/eui'; +import { EuiPanel, EuiText, EuiPageContent, EuiPage } from '@elastic/eui'; +import { ContextErrorMessage } from '../context_error_message'; import { DocTableLegacy, DocTableLegacyProps, @@ -35,6 +37,7 @@ export interface ContextAppProps { minimumVisibleRows: number; sorting: string[]; status: string; + reason: string; defaultStepSize: number; predecessorCount: number; successorCount: number; @@ -56,6 +59,7 @@ function isLoading(status: string) { export function ContextAppLegacy(renderProps: ContextAppProps) { const status = renderProps.status; const isLoaded = status === LOADING_STATUS.LOADED; + const isFailed = status === LOADING_STATUS.FAILED; const actionBarProps = (type: string) => { const { @@ -111,18 +115,24 @@ export function ContextAppLegacy(renderProps: ContextAppProps) { return ( - - - {loadingFeedback()} - {isLoaded ? ( - -
- -
-
- ) : null} - -
+ {isFailed ? ( + + ) : ( + + + + {loadingFeedback()} + {isLoaded ? ( + +
+ +
+
+ ) : null} + +
+
+ )}
); } diff --git a/src/plugins/discover/public/application/components/context_app/context_app_legacy_directive.ts b/src/plugins/discover/public/application/components/context_app/context_app_legacy_directive.ts index 4a315be513a0d..bc4b7c4babd21 100644 --- a/src/plugins/discover/public/application/components/context_app/context_app_legacy_directive.ts +++ b/src/plugins/discover/public/application/components/context_app/context_app_legacy_directive.ts @@ -27,6 +27,7 @@ export function createContextAppLegacy(reactDirective: any) { ['columns', { watchDepth: 'collection' }], ['minimumVisibleRows', { watchDepth: 'reference' }], ['status', { watchDepth: 'reference' }], + ['reason', { watchDepth: 'reference' }], ['defaultStepSize', { watchDepth: 'reference' }], ['predecessorCount', { watchDepth: 'reference' }], ['predecessorAvailable', { watchDepth: 'reference' }], diff --git a/src/plugins/discover/public/application/components/context_error_message/context_error_message_directive.ts b/src/plugins/discover/public/application/components/context_error_message/context_error_message_directive.ts deleted file mode 100644 index 925d560761a84..0000000000000 --- a/src/plugins/discover/public/application/components/context_error_message/context_error_message_directive.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { ContextErrorMessage } from './context_error_message'; - -export function createContextErrorMessageDirective(reactDirective: any) { - return reactDirective(ContextErrorMessage, [ - ['status', { watchDepth: 'reference' }], - ['reason', { watchDepth: 'reference' }], - ]); -} diff --git a/src/plugins/discover/public/application/components/context_error_message/index.ts b/src/plugins/discover/public/application/components/context_error_message/index.ts index f20f2ccf8afa0..dfa9602408f8f 100644 --- a/src/plugins/discover/public/application/components/context_error_message/index.ts +++ b/src/plugins/discover/public/application/components/context_error_message/index.ts @@ -18,4 +18,3 @@ */ export { ContextErrorMessage } from './context_error_message'; -export { createContextErrorMessageDirective } from './context_error_message_directive'; diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx index 99dad418c04bd..a42e2412ae928 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx @@ -224,7 +224,7 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) { legend={legend} options={toggleButtons(id)} idSelected={`${id}-${values[id]}`} - onChange={(optionId) => handleValueChange(id, optionId.replace(`${id}-`, ''))} + onChange={(optionId: string) => handleValueChange(id, optionId.replace(`${id}-`, ''))} buttonSize="compressed" isFullWidth data-test-subj={`${id}ButtonGroup`} diff --git a/src/plugins/discover/public/get_inner_angular.ts b/src/plugins/discover/public/get_inner_angular.ts index 55a75240909bf..651a26cad755d 100644 --- a/src/plugins/discover/public/get_inner_angular.ts +++ b/src/plugins/discover/public/get_inner_angular.ts @@ -52,7 +52,6 @@ import { createTopNavDirective, createTopNavHelper, } from '../../kibana_legacy/public'; -import { createContextErrorMessageDirective } from './application/components/context_error_message'; import { DiscoverStartPlugins } from './plugin'; import { getScopedHistory } from './kibana_services'; import { createDiscoverLegacyDirective } from './application/components/create_discover_legacy_directive'; @@ -137,8 +136,7 @@ export function initializeInnerAngularModule( .config(watchMultiDecorator) .run(registerListenEventListener) .directive('renderComplete', createRenderCompleteDirective) - .directive('discoverLegacy', createDiscoverLegacyDirective) - .directive('contextErrorMessage', createContextErrorMessageDirective); + .directive('discoverLegacy', createDiscoverLegacyDirective); } function createLocalPromiseModule() { diff --git a/src/plugins/embeddable/public/components/panel_options_menu/index.tsx b/src/plugins/embeddable/public/components/panel_options_menu/index.tsx index 7790646a88a68..9b9d95628f19d 100644 --- a/src/plugins/embeddable/public/components/panel_options_menu/index.tsx +++ b/src/plugins/embeddable/public/components/panel_options_menu/index.tsx @@ -83,7 +83,6 @@ export const PanelOptionsMenu: React.FC = ({ panelPaddingSize="none" anchorPosition="downRight" data-test-subj={open ? 'embeddablePanelContextMenuOpen' : 'embeddablePanelContextMenuClosed'} - withTitle > diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 789353ca4abd7..0d2dcf208f2ef 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -70,6 +70,7 @@ export { isSavedObjectEmbeddableInput, isRangeSelectTriggerContext, isValueClickTriggerContext, + isContextMenuTriggerContext, EmbeddableStateTransfer, EmbeddableEditorState, EmbeddablePackageState, diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_options_menu.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_options_menu.tsx index 629a5f8c880e8..82bfb9b47e1a7 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_options_menu.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_options_menu.tsx @@ -113,7 +113,6 @@ export class PanelOptionsMenu extends React.Component = | ValueClickContext | RangeSelectContext; -export const isValueClickTriggerContext = ( - context: ChartActionContext -): context is ValueClickContext => context.data && 'data' in context.data; - -export const isRangeSelectTriggerContext = ( - context: ChartActionContext -): context is RangeSelectContext => context.data && 'range' in context.data; - export const CONTEXT_MENU_TRIGGER = 'CONTEXT_MENU_TRIGGER'; export const contextMenuTrigger: Trigger<'CONTEXT_MENU_TRIGGER'> = { id: CONTEXT_MENU_TRIGGER, - title: 'Context menu', - description: 'Triggered on top-right corner context-menu select.', + title: i18n.translate('embeddableApi.contextMenuTrigger.title', { + defaultMessage: 'Context menu', + }), + description: i18n.translate('embeddableApi.contextMenuTrigger.description', { + defaultMessage: 'A panel top-right corner context menu click.', + }), }; export const PANEL_BADGE_TRIGGER = 'PANEL_BADGE_TRIGGER'; export const panelBadgeTrigger: Trigger<'PANEL_BADGE_TRIGGER'> = { id: PANEL_BADGE_TRIGGER, - title: 'Panel badges', - description: 'Actions appear in title bar when an embeddable loads in a panel.', + title: i18n.translate('embeddableApi.panelBadgeTrigger.title', { + defaultMessage: 'Panel badges', + }), + description: i18n.translate('embeddableApi.panelBadgeTrigger.description', { + defaultMessage: 'Actions appear in title bar when an embeddable loads in a panel.', + }), }; export const PANEL_NOTIFICATION_TRIGGER = 'PANEL_NOTIFICATION_TRIGGER'; export const panelNotificationTrigger: Trigger<'PANEL_NOTIFICATION_TRIGGER'> = { id: PANEL_NOTIFICATION_TRIGGER, - title: 'Panel notifications', - description: 'Actions appear in top-right corner of a panel.', + title: i18n.translate('embeddableApi.panelNotificationTrigger.title', { + defaultMessage: 'Panel notifications', + }), + description: i18n.translate('embeddableApi.panelNotificationTrigger.description', { + defaultMessage: 'Actions appear in top-right corner of a panel.', + }), }; + +export const isValueClickTriggerContext = ( + context: ChartActionContext +): context is ValueClickContext => context.data && 'data' in context.data; + +export const isRangeSelectTriggerContext = ( + context: ChartActionContext +): context is RangeSelectContext => context.data && 'range' in context.data; + +export const isContextMenuTriggerContext = (context: unknown): context is EmbeddableContext => + !!context && + typeof context === 'object' && + !!(context as EmbeddableContext).embeddable && + typeof (context as EmbeddableContext).embeddable === 'object'; diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index 00971ed37db3a..983daa4d5af73 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -12,7 +12,7 @@ import { ApiResponse as ApiResponse_2 } from '@elastic/elasticsearch'; import { ApplicationStart as ApplicationStart_2 } from 'kibana/public'; import { Assign } from '@kbn/utility-types'; import { BehaviorSubject } from 'rxjs'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { CoreSetup as CoreSetup_2 } from 'src/core/public'; import { CoreSetup as CoreSetup_3 } from 'kibana/public'; import { CoreStart as CoreStart_2 } from 'kibana/public'; @@ -695,6 +695,11 @@ export interface IEmbeddable): void; } +// Warning: (ae-missing-release-tag) "isContextMenuTriggerContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isContextMenuTriggerContext: (context: unknown) => context is EmbeddableContext; + // Warning: (ae-missing-release-tag) "isErrorEmbeddable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -884,7 +889,7 @@ export const withEmbeddableSubscription: (promise: Promise): Promise { - return Promise.race([this.abortRejection, promise]); + return Promise.race([this.abortRejection.promise, promise]); } /** @@ -189,14 +189,18 @@ export class Execution< else reject(error); }); - this.firstResultFuture.promise.then( - (result) => { - this.state.transitions.setResult(result); - }, - (error) => { - this.state.transitions.setError(error); - } - ); + this.firstResultFuture.promise + .then( + (result) => { + this.state.transitions.setResult(result); + }, + (error) => { + this.state.transitions.setError(error); + } + ) + .finally(() => { + this.abortRejection.cleanup(); + }); } async invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Promise { diff --git a/src/plugins/expressions/common/execution/execution_contract.test.ts b/src/plugins/expressions/common/execution/execution_contract.test.ts index 856b22470d782..eaf7e6ea862eb 100644 --- a/src/plugins/expressions/common/execution/execution_contract.test.ts +++ b/src/plugins/expressions/common/execution/execution_contract.test.ts @@ -59,7 +59,7 @@ describe('ExecutionContract', () => { test('can cancel execution', () => { const execution = createExecution('foo bar=123'); - const spy = jest.spyOn(execution, 'cancel'); + const spy = jest.spyOn(execution, 'cancel').mockImplementation(() => {}); const contract = new ExecutionContract(execution); expect(spy).toHaveBeenCalledTimes(0); diff --git a/src/plugins/expressions/common/executor/executor.test.ts b/src/plugins/expressions/common/executor/executor.test.ts index a658d3457407c..308d6f7e71814 100644 --- a/src/plugins/expressions/common/executor/executor.test.ts +++ b/src/plugins/expressions/common/executor/executor.test.ts @@ -22,6 +22,7 @@ import * as expressionTypes from '../expression_types'; import * as expressionFunctions from '../expression_functions'; import { Execution } from '../execution'; import { ExpressionAstFunction, parseExpression } from '../ast'; +import { MigrateFunction } from '../../../kibana_utils/common/persistable_state'; describe('Executor', () => { test('can instantiate', () => { @@ -158,6 +159,7 @@ describe('Executor', () => { const injectFn = jest.fn().mockImplementation((args, references) => args); const extractFn = jest.fn().mockReturnValue({ args: {}, references: [] }); + const migrateFn = jest.fn().mockImplementation((args) => args); const fooFn = { name: 'foo', @@ -174,6 +176,14 @@ describe('Executor', () => { inject: (state: ExpressionAstFunction['arguments']) => { return injectFn(state); }, + migrations: { + '7.10.0': (((state: ExpressionAstFunction, version: string): ExpressionAstFunction => { + return migrateFn(state, version); + }) as any) as MigrateFunction, + '7.10.1': (((state: ExpressionAstFunction, version: string): ExpressionAstFunction => { + return migrateFn(state, version); + }) as any) as MigrateFunction, + }, fn: jest.fn(), }; executor.registerFunction(fooFn); @@ -194,5 +204,26 @@ describe('Executor', () => { expect(extractFn).toBeCalledTimes(5); }); }); + + describe('.migrate', () => { + test('calls migrate function for every expression function in expression', () => { + executor.migrate( + parseExpression('foo bar="baz" | foo bar={foo bar="baz" | foo bar={foo bar="baz"}}'), + '7.10.0' + ); + expect(migrateFn).toBeCalledTimes(5); + }); + }); + + describe('.migrateToLatest', () => { + test('calls extract function for every expression function in expression', () => { + migrateFn.mockClear(); + executor.migrateToLatest( + parseExpression('foo bar="baz" | foo bar={foo bar="baz" | foo bar={foo bar="baz"}}'), + '7.10.0' + ); + expect(migrateFn).toBeCalledTimes(10); + }); + }); }); }); diff --git a/src/plugins/expressions/common/executor/executor.ts b/src/plugins/expressions/common/executor/executor.ts index 85b5589b593af..19fc4cf5a14a2 100644 --- a/src/plugins/expressions/common/executor/executor.ts +++ b/src/plugins/expressions/common/executor/executor.ts @@ -32,7 +32,7 @@ import { typeSpecs } from '../expression_types/specs'; import { functionSpecs } from '../expression_functions/specs'; import { getByAlias } from '../util'; import { SavedObjectReference } from '../../../../core/types'; -import { PersistableState } from '../../../kibana_utils/common'; +import { PersistableState, SerializableState } from '../../../kibana_utils/common'; import { ExpressionExecutionParams } from '../service'; export interface ExpressionExecOptions { @@ -88,6 +88,20 @@ export class FunctionsRegistry implements IRegistry { } } +const semverGte = (semver1: string, semver2: string) => { + const regex = /^([0-9]+)\.([0-9]+)\.([0-9]+)$/; + const matches1 = regex.exec(semver1) as RegExpMatchArray; + const matches2 = regex.exec(semver2) as RegExpMatchArray; + + const [, major1, minor1, patch1] = matches1; + const [, major2, minor2, patch2] = matches2; + + return ( + major1 > major2 || + (major1 === major2 && (minor1 > minor2 || (minor1 === minor2 && patch1 >= patch2))) + ); +}; + export class Executor = Record> implements PersistableState { static createWithDefaults = Record>( @@ -249,6 +263,27 @@ export class Executor = Record { + if (!fn.migrations[version]) return link; + const updatedAst = fn.migrations[version](link) as ExpressionAstFunction; + link.arguments = updatedAst.arguments; + link.type = updatedAst.type; + }); + } + + public migrateToLatest(ast: unknown, version: string) { + return this.walkAst(cloneDeep(ast) as ExpressionAstExpression, (fn, link) => { + for (const key of Object.keys(fn.migrations)) { + if (semverGte(key, version)) { + const updatedAst = fn.migrations[key](link) as ExpressionAstFunction; + link.arguments = updatedAst.arguments; + link.type = updatedAst.type; + } + } + }); + } + public fork(): Executor { const initialState = this.state.get(); const fork = new Executor(initialState); diff --git a/src/plugins/expressions/common/expression_functions/expression_function.ts b/src/plugins/expressions/common/expression_functions/expression_function.ts index 0b56d3c169ff4..2879cc8e3632c 100644 --- a/src/plugins/expressions/common/expression_functions/expression_function.ts +++ b/src/plugins/expressions/common/expression_functions/expression_function.ts @@ -24,7 +24,7 @@ import { ExpressionValue } from '../expression_types/types'; import { ExecutionContext } from '../execution'; import { ExpressionAstFunction } from '../ast'; import { SavedObjectReference } from '../../../../core/types'; -import { PersistableState } from '../../../kibana_utils/common'; +import { PersistableState, SerializableState } from '../../../kibana_utils/common'; export class ExpressionFunction implements PersistableState { /** @@ -76,6 +76,9 @@ export class ExpressionFunction implements PersistableState ExpressionAstFunction['arguments']; + migrations: { + [key: string]: (state: SerializableState) => SerializableState; + }; constructor(functionDefinition: AnyExpressionFunctionDefinition) { const { @@ -91,6 +94,7 @@ export class ExpressionFunction implements PersistableState c); this.inject = inject || identity; this.extract = extract || ((s) => ({ state: s, references: [] })); + this.migrations = migrations || {}; for (const [key, arg] of Object.entries(args || {})) { this.args[key] = new ExpressionFunctionParameter(key, arg); diff --git a/src/plugins/expressions/common/expression_functions/specs/cumulative_sum.ts b/src/plugins/expressions/common/expression_functions/specs/cumulative_sum.ts index 970015638794f..672abadd3c016 100644 --- a/src/plugins/expressions/common/expression_functions/specs/cumulative_sum.ts +++ b/src/plugins/expressions/common/expression_functions/specs/cumulative_sum.ts @@ -19,7 +19,8 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition } from '../types'; -import { Datatable, DatatableRow } from '../../expression_types'; +import { Datatable } from '../../expression_types'; +import { buildResultColumns, getBucketIdentifier } from './series_calculation_helpers'; export interface CumulativeSumArgs { by?: string[]; @@ -35,15 +36,6 @@ export type ExpressionFunctionCumulativeSum = ExpressionFunctionDefinition< Datatable >; -/** - * Returns a string identifying the group of a row by a list of columns to group by - */ -function getBucketIdentifier(row: DatatableRow, groupColumns?: string[]) { - return (groupColumns || []) - .map((groupColumnId) => (row[groupColumnId] == null ? '' : String(row[groupColumnId]))) - .join('|'); -} - /** * Calculates the cumulative sum of a specified column in the data table. * @@ -114,38 +106,17 @@ export const cumulativeSum: ExpressionFunctionCumulativeSum = { }, fn(input, { by, inputColumnId, outputColumnId, outputColumnName }) { - if (input.columns.some((column) => column.id === outputColumnId)) { - throw new Error( - i18n.translate('expressions.functions.cumulativeSum.columnConflictMessage', { - defaultMessage: - 'Specified outputColumnId {columnId} already exists. Please pick another column id.', - values: { - columnId: outputColumnId, - }, - }) - ); - } - - const inputColumnDefinition = input.columns.find((column) => column.id === inputColumnId); + const resultColumns = buildResultColumns( + input, + outputColumnId, + inputColumnId, + outputColumnName + ); - if (!inputColumnDefinition) { + if (!resultColumns) { return input; } - const outputColumnDefinition = { - ...inputColumnDefinition, - id: outputColumnId, - name: outputColumnName || outputColumnId, - }; - - const resultColumns = [...input.columns]; - // add output column after input column in the table - resultColumns.splice( - resultColumns.indexOf(inputColumnDefinition) + 1, - 0, - outputColumnDefinition - ); - const accumulators: Partial> = {}; return { ...input, diff --git a/src/plugins/expressions/common/expression_functions/specs/derivative.ts b/src/plugins/expressions/common/expression_functions/specs/derivative.ts new file mode 100644 index 0000000000000..44ac198e2d17c --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/specs/derivative.ts @@ -0,0 +1,148 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition } from '../types'; +import { Datatable } from '../../expression_types'; +import { buildResultColumns, getBucketIdentifier } from './series_calculation_helpers'; + +export interface DerivativeArgs { + by?: string[]; + inputColumnId: string; + outputColumnId: string; + outputColumnName?: string; +} + +export type ExpressionFunctionDerivative = ExpressionFunctionDefinition< + 'derivative', + Datatable, + DerivativeArgs, + Datatable +>; + +/** + * Calculates the derivative of a specified column in the data table. + * + * Also supports multiple series in a single data table - use the `by` argument + * to specify the columns to split the calculation by. + * For each unique combination of all `by` columns a separate derivative will be calculated. + * The order of rows won't be changed - this function is not modifying any existing columns, it's only + * adding the specified `outputColumnId` column to every row of the table without adding or removing rows. + * + * Behavior: + * * Will write the derivative of `inputColumnId` into `outputColumnId` + * * If provided will use `outputColumnName` as name for the newly created column. Otherwise falls back to `outputColumnId` + * * Derivative always start with an undefined value for the first row of a series, a cell will contain its own value minus the + * value of the previous cell of the same series. + * + * Edge cases: + * * Will return the input table if `inputColumnId` does not exist + * * Will throw an error if `outputColumnId` exists already in provided data table + * * If there is no previous row of the current series with a non `null` or `undefined` value, the output cell of the current row + * will be set to `undefined`. + * * If the row value contains `null` or `undefined`, it will be ignored and the output cell will be set to `undefined` + * * If the value of the previous row of the same series contains `null` or `undefined`, the output cell of the current row will be set to `undefined` as well + * * For all values besides `null` and `undefined`, the value will be cast to a number before it's used in the + * calculation of the current series even if this results in `NaN` (like in case of objects). + * * To determine separate series defined by the `by` columns, the values of these columns will be cast to strings + * before comparison. If the values are objects, the return value of their `toString` method will be used for comparison. + * Missing values (`null` and `undefined`) will be treated as empty strings. + */ +export const derivative: ExpressionFunctionDerivative = { + name: 'derivative', + type: 'datatable', + + inputTypes: ['datatable'], + + help: i18n.translate('expressions.functions.derivative.help', { + defaultMessage: 'Calculates the derivative of a column in a data table', + }), + + args: { + by: { + help: i18n.translate('expressions.functions.derivative.args.byHelpText', { + defaultMessage: 'Column to split the derivative calculation by', + }), + multi: true, + types: ['string'], + required: false, + }, + inputColumnId: { + help: i18n.translate('expressions.functions.derivative.args.inputColumnIdHelpText', { + defaultMessage: 'Column to calculate the derivative of', + }), + types: ['string'], + required: true, + }, + outputColumnId: { + help: i18n.translate('expressions.functions.derivative.args.outputColumnIdHelpText', { + defaultMessage: 'Column to store the resulting derivative in', + }), + types: ['string'], + required: true, + }, + outputColumnName: { + help: i18n.translate('expressions.functions.derivative.args.outputColumnNameHelpText', { + defaultMessage: 'Name of the column to store the resulting derivative in', + }), + types: ['string'], + required: false, + }, + }, + + fn(input, { by, inputColumnId, outputColumnId, outputColumnName }) { + const resultColumns = buildResultColumns( + input, + outputColumnId, + inputColumnId, + outputColumnName + ); + + if (!resultColumns) { + return input; + } + + const previousValues: Partial> = {}; + return { + ...input, + columns: resultColumns, + rows: input.rows.map((row) => { + const newRow = { ...row }; + + const bucketIdentifier = getBucketIdentifier(row, by); + const previousValue = previousValues[bucketIdentifier]; + const currentValue = newRow[inputColumnId]; + + if (currentValue != null && previousValue != null) { + newRow[outputColumnId] = Number(currentValue) - previousValue; + } else { + newRow[outputColumnId] = undefined; + } + + if (currentValue != null) { + previousValues[bucketIdentifier] = Number(currentValue); + } else { + previousValues[bucketIdentifier] = undefined; + } + + return newRow; + }), + }; + }, +}; diff --git a/src/plugins/expressions/common/expression_functions/specs/index.ts b/src/plugins/expressions/common/expression_functions/specs/index.ts index aadea5882b9c0..d414057598f12 100644 --- a/src/plugins/expressions/common/expression_functions/specs/index.ts +++ b/src/plugins/expressions/common/expression_functions/specs/index.ts @@ -26,6 +26,7 @@ import { variable } from './var'; import { AnyExpressionFunctionDefinition } from '../types'; import { theme } from './theme'; import { cumulativeSum } from './cumulative_sum'; +import { derivative } from './derivative'; export const functionSpecs: AnyExpressionFunctionDefinition[] = [ clog, @@ -36,6 +37,7 @@ export const functionSpecs: AnyExpressionFunctionDefinition[] = [ variable, theme, cumulativeSum, + derivative, ]; export * from './clog'; @@ -46,3 +48,4 @@ export * from './var_set'; export * from './var'; export * from './theme'; export * from './cumulative_sum'; +export * from './derivative'; diff --git a/src/plugins/expressions/common/expression_functions/specs/series_calculation_helpers.ts b/src/plugins/expressions/common/expression_functions/specs/series_calculation_helpers.ts new file mode 100644 index 0000000000000..8ba9d527d4c59 --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/specs/series_calculation_helpers.ts @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { Datatable, DatatableRow } from '../../expression_types'; + +/** + * Returns a string identifying the group of a row by a list of columns to group by + */ +export function getBucketIdentifier(row: DatatableRow, groupColumns?: string[]) { + return (groupColumns || []) + .map((groupColumnId) => (row[groupColumnId] == null ? '' : String(row[groupColumnId]))) + .join('|'); +} + +/** + * Checks whether input and output columns are defined properly + * and builds column array of the output table if that's the case. + * + * * Throws an error if the output column exists already. + * * Returns undefined if the input column doesn't exist. + * @param input Input datatable + * @param outputColumnId Id of the output column + * @param inputColumnId Id of the input column + * @param outputColumnName Optional name of the output column + */ +export function buildResultColumns( + input: Datatable, + outputColumnId: string, + inputColumnId: string, + outputColumnName: string | undefined +) { + if (input.columns.some((column) => column.id === outputColumnId)) { + throw new Error( + i18n.translate('expressions.functions.seriesCalculations.columnConflictMessage', { + defaultMessage: + 'Specified outputColumnId {columnId} already exists. Please pick another column id.', + values: { + columnId: outputColumnId, + }, + }) + ); + } + + const inputColumnDefinition = input.columns.find((column) => column.id === inputColumnId); + + if (!inputColumnDefinition) { + return; + } + + const outputColumnDefinition = { + ...inputColumnDefinition, + id: outputColumnId, + name: outputColumnName || outputColumnId, + }; + + const resultColumns = [...input.columns]; + // add output column after input column in the table + resultColumns.splice(resultColumns.indexOf(inputColumnDefinition) + 1, 0, outputColumnDefinition); + return resultColumns; +} diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/derivative.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/derivative.test.ts new file mode 100644 index 0000000000000..63b2df5382557 --- /dev/null +++ b/src/plugins/expressions/common/expression_functions/specs/tests/derivative.test.ts @@ -0,0 +1,406 @@ +/* + * 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 { functionWrapper } from './utils'; +import { derivative, DerivativeArgs } from '../derivative'; +import { ExecutionContext } from '../../../execution/types'; +import { Datatable } from '../../../expression_types/specs/datatable'; + +describe('interpreter/functions#derivative', () => { + const fn = functionWrapper(derivative); + const runFn = (input: Datatable, args: DerivativeArgs) => + fn(input, args, {} as ExecutionContext) as Datatable; + + it('calculates derivative', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [{ val: 5 }, { val: 7 }, { val: 3 }, { val: 2 }], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.columns).toContainEqual({ + id: 'output', + name: 'output', + meta: { type: 'number' }, + }); + expect(result.rows.map((row) => row.output)).toEqual([undefined, 2, -4, -1]); + }); + + it('skips null or undefined values until there is real data', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [ + {}, + { val: null }, + { val: undefined }, + { val: 1 }, + { val: 2 }, + { val: undefined }, + { val: undefined }, + { val: 4 }, + { val: 8 }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.columns).toContainEqual({ + id: 'output', + name: 'output', + meta: { type: 'number' }, + }); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + undefined, + 2 - 1, + undefined, + undefined, + undefined, + 8 - 4, + ]); + }); + + it('treats 0 as real data', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [ + {}, + { val: null }, + { val: undefined }, + { val: 1 }, + { val: 2 }, + { val: 0 }, + { val: undefined }, + { val: 0 }, + { val: undefined }, + { val: 0 }, + { val: 8 }, + { val: 0 }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + undefined, + 2 - 1, + 0 - 2, + undefined, + undefined, + undefined, + undefined, + 8 - 0, + 0 - 8, + ]); + }); + + it('calculates derivative for multiple series', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { id: 'val', name: 'val', meta: { type: 'number' } }, + { id: 'split', name: 'split', meta: { type: 'string' } }, + ], + rows: [ + { val: 1, split: 'A' }, + { val: 2, split: 'B' }, + { val: 3, split: 'B' }, + { val: 4, split: 'A' }, + { val: 5, split: 'A' }, + { val: 6, split: 'A' }, + { val: 7, split: 'B' }, + { val: 8, split: 'B' }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output', by: ['split'] } + ); + + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + 3 - 2, + 4 - 1, + 5 - 4, + 6 - 5, + 7 - 3, + 8 - 7, + ]); + }); + + it('treats missing split column as separate series', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { id: 'val', name: 'val', meta: { type: 'number' } }, + { id: 'split', name: 'split', meta: { type: 'string' } }, + ], + rows: [ + { val: 1, split: 'A' }, + { val: 2, split: 'B' }, + { val: 3 }, + { val: 4, split: 'A' }, + { val: 5 }, + { val: 6, split: 'A' }, + { val: 7, split: 'B' }, + { val: 8, split: 'B' }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output', by: ['split'] } + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + 4 - 1, + 5 - 3, + 6 - 4, + 7 - 2, + 8 - 7, + ]); + }); + + it('treats null like undefined and empty string for split columns', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { id: 'val', name: 'val', meta: { type: 'number' } }, + { id: 'split', name: 'split', meta: { type: 'string' } }, + ], + rows: [ + { val: 1, split: 'A' }, + { val: 2, split: 'B' }, + { val: 3 }, + { val: 4, split: 'A' }, + { val: 5 }, + { val: 6, split: 'A' }, + { val: 7, split: null }, + { val: 8, split: 'B' }, + { val: 9, split: '' }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output', by: ['split'] } + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + 4 - 1, + 5 - 3, + 6 - 4, + 7 - 5, + 8 - 2, + 9 - 7, + ]); + }); + + it('calculates derivative for multiple series by multiple split columns', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { id: 'val', name: 'val', meta: { type: 'number' } }, + { id: 'split', name: 'split', meta: { type: 'string' } }, + { id: 'split2', name: 'split2', meta: { type: 'string' } }, + ], + rows: [ + { val: 1, split: 'A', split2: 'C' }, + { val: 2, split: 'B', split2: 'C' }, + { val: 3, split2: 'C' }, + { val: 4, split: 'A', split2: 'C' }, + { val: 5 }, + { val: 6, split: 'A', split2: 'D' }, + { val: 7, split: 'B', split2: 'D' }, + { val: 8, split: 'B', split2: 'D' }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output', by: ['split', 'split2'] } + ); + expect(result.rows.map((row) => row.output)).toEqual([ + undefined, + undefined, + undefined, + 4 - 1, + undefined, + undefined, + undefined, + 8 - 7, + ]); + }); + + it('splits separate series by the string representation of the cell values', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { id: 'val', name: 'val', meta: { type: 'number' } }, + { id: 'split', name: 'split', meta: { type: 'string' } }, + ], + rows: [ + { val: 1, split: { anObj: 3 } }, + { val: 2, split: { anotherObj: 5 } }, + { val: 10, split: 5 }, + { val: 11, split: '5' }, + ], + }, + { inputColumnId: 'val', outputColumnId: 'output', by: ['split'] } + ); + + expect(result.rows.map((row) => row.output)).toEqual([undefined, 2 - 1, undefined, 11 - 10]); + }); + + it('casts values to number before calculating derivative', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [{ val: 5 }, { val: '7' }, { val: '3' }, { val: 2 }], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.rows.map((row) => row.output)).toEqual([undefined, 7 - 5, 3 - 7, 2 - 3]); + }); + + it('casts values to number before calculating derivative for NaN like values', () => { + const result = runFn( + { + type: 'datatable', + columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }], + rows: [{ val: 5 }, { val: '7' }, { val: {} }, { val: 2 }, { val: 5 }], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.rows.map((row) => row.output)).toEqual([undefined, 7 - 5, NaN, NaN, 5 - 2]); + }); + + it('copies over meta information from the source column', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { + id: 'val', + name: 'val', + meta: { + type: 'number', + + field: 'afield', + index: 'anindex', + params: { id: 'number', params: { pattern: '000' } }, + source: 'synthetic', + sourceParams: { + some: 'params', + }, + }, + }, + ], + rows: [{ val: 5 }], + }, + { inputColumnId: 'val', outputColumnId: 'output' } + ); + expect(result.columns).toContainEqual({ + id: 'output', + name: 'output', + meta: { + type: 'number', + + field: 'afield', + index: 'anindex', + params: { id: 'number', params: { pattern: '000' } }, + source: 'synthetic', + sourceParams: { + some: 'params', + }, + }, + }); + }); + + it('sets output name on output column if specified', () => { + const result = runFn( + { + type: 'datatable', + columns: [ + { + id: 'val', + name: 'val', + meta: { + type: 'number', + }, + }, + ], + rows: [{ val: 5 }], + }, + { inputColumnId: 'val', outputColumnId: 'output', outputColumnName: 'Output name' } + ); + expect(result.columns).toContainEqual({ + id: 'output', + name: 'Output name', + meta: { type: 'number' }, + }); + }); + + it('returns source table if input column does not exist', () => { + const input: Datatable = { + type: 'datatable', + columns: [ + { + id: 'val', + name: 'val', + meta: { + type: 'number', + }, + }, + ], + rows: [{ val: 5 }], + }; + expect(runFn(input, { inputColumnId: 'nonexisting', outputColumnId: 'output' })).toBe(input); + }); + + it('throws an error if output column exists already', () => { + expect(() => + runFn( + { + type: 'datatable', + columns: [ + { + id: 'val', + name: 'val', + meta: { + type: 'number', + }, + }, + ], + rows: [{ val: 5 }], + }, + { inputColumnId: 'val', outputColumnId: 'val' } + ) + ).toThrow(); + }); +}); diff --git a/src/plugins/expressions/common/expression_functions/types.ts b/src/plugins/expressions/common/expression_functions/types.ts index fb1823e85b391..134e9e3a63502 100644 --- a/src/plugins/expressions/common/expression_functions/types.ts +++ b/src/plugins/expressions/common/expression_functions/types.ts @@ -30,6 +30,7 @@ import { ExpressionFunctionVar, ExpressionFunctionTheme, ExpressionFunctionCumulativeSum, + ExpressionFunctionDerivative, } from './specs'; import { ExpressionAstFunction } from '../ast'; import { PersistableStateDefinition } from '../../../kibana_utils/common'; @@ -133,4 +134,5 @@ export interface ExpressionFunctionDefinitions { var: ExpressionFunctionVar; theme: ExpressionFunctionTheme; cumulative_sum: ExpressionFunctionCumulativeSum; + derivative: ExpressionFunctionDerivative; } diff --git a/src/plugins/expressions/common/service/expressions_services.ts b/src/plugins/expressions/common/service/expressions_services.ts index abbba433ab3ca..0f898563c3d0e 100644 --- a/src/plugins/expressions/common/service/expressions_services.ts +++ b/src/plugins/expressions/common/service/expressions_services.ts @@ -24,7 +24,7 @@ import { ExecutionContract } from '../execution/execution_contract'; import { AnyExpressionTypeDefinition } from '../expression_types'; import { AnyExpressionFunctionDefinition } from '../expression_functions'; import { SavedObjectReference } from '../../../../core/types'; -import { PersistableState } from '../../../kibana_utils/common'; +import { PersistableState, SerializableState } from '../../../kibana_utils/common'; import { Adapters } from '../../../inspector/common/adapters'; import { ExecutionContextSearch } from '../execution'; @@ -303,6 +303,26 @@ export class ExpressionsService implements PersistableState { + return this.executor.migrate(state, version); + }; + + /** + * Migrates expression to the latest version + * @param state expression AST to update + * @param version the version of kibana in which expression was created + * @returns migrated expression AST + */ + public readonly migrateToLatest = (state: unknown, version: string) => { + return this.executor.migrateToLatest(state, version); + }; + /** * Returns Kibana Platform *setup* life-cycle contract. Useful to return the * same contract on server-side and browser-side. diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index 68a3507bbf166..06b7bc2142447 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -222,6 +222,12 @@ export class Executor = Record AnyExpressionFunctionDefinition)): void; // (undocumented) @@ -342,6 +348,10 @@ export class ExpressionFunction implements PersistableState ExpressionAstFunction['arguments']; inputTypes: string[] | undefined; + // (undocumented) + migrations: { + [key: string]: (state: SerializableState) => SerializableState; + }; name: string; // (undocumented) telemetry: (state: ExpressionAstFunction['arguments'], telemetryData: Record) => Record; @@ -379,6 +389,10 @@ export interface ExpressionFunctionDefinitions { // // (undocumented) cumulative_sum: ExpressionFunctionCumulativeSum; + // Warning: (ae-forgotten-export) The symbol "ExpressionFunctionDerivative" needs to be exported by the entry point index.d.ts + // + // (undocumented) + derivative: ExpressionFunctionDerivative; // Warning: (ae-forgotten-export) The symbol "ExpressionFunctionFont" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -586,6 +600,8 @@ export class ExpressionsService implements PersistableState ReturnType; readonly inject: (state: ExpressionAstExpression, references: SavedObjectReference[]) => ExpressionAstExpression; + readonly migrate: (state: SerializableState, version: string) => ExpressionAstExpression; + readonly migrateToLatest: (state: unknown, version: string) => ExpressionAstExpression; readonly registerFunction: (functionDefinition: AnyExpressionFunctionDefinition | (() => AnyExpressionFunctionDefinition)) => void; // (undocumented) readonly registerRenderer: (definition: AnyExpressionRenderDefinition | (() => AnyExpressionRenderDefinition)) => void; @@ -1149,7 +1165,6 @@ export type UnmappedTypeStrings = 'date' | 'filter'; // // src/plugins/expressions/common/ast/types.ts:40:3 - (ae-forgotten-export) The symbol "ExpressionAstFunctionDebug" needs to be exported by the entry point index.d.ts // src/plugins/expressions/common/expression_types/specs/error.ts:31:5 - (ae-forgotten-export) The symbol "ErrorLike" needs to be exported by the entry point index.d.ts -// src/plugins/expressions/common/expression_types/specs/error.ts:32:5 - (ae-forgotten-export) The symbol "SerializableState" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/expressions/server/server.api.md b/src/plugins/expressions/server/server.api.md index d6925a027358c..cacd61a8638a6 100644 --- a/src/plugins/expressions/server/server.api.md +++ b/src/plugins/expressions/server/server.api.md @@ -204,6 +204,12 @@ export class Executor = Record AnyExpressionFunctionDefinition)): void; // (undocumented) @@ -314,6 +320,10 @@ export class ExpressionFunction implements PersistableState ExpressionAstFunction['arguments']; inputTypes: string[] | undefined; + // (undocumented) + migrations: { + [key: string]: (state: SerializableState) => SerializableState; + }; name: string; // (undocumented) telemetry: (state: ExpressionAstFunction['arguments'], telemetryData: Record) => Record; @@ -351,6 +361,10 @@ export interface ExpressionFunctionDefinitions { // // (undocumented) cumulative_sum: ExpressionFunctionCumulativeSum; + // Warning: (ae-forgotten-export) The symbol "ExpressionFunctionDerivative" needs to be exported by the entry point index.d.ts + // + // (undocumented) + derivative: ExpressionFunctionDerivative; // Warning: (ae-forgotten-export) The symbol "ExpressionFunctionFont" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -939,7 +953,6 @@ export type UnmappedTypeStrings = 'date' | 'filter'; // // src/plugins/expressions/common/ast/types.ts:40:3 - (ae-forgotten-export) The symbol "ExpressionAstFunctionDebug" needs to be exported by the entry point index.d.ts // src/plugins/expressions/common/expression_types/specs/error.ts:31:5 - (ae-forgotten-export) The symbol "ErrorLike" needs to be exported by the entry point index.d.ts -// src/plugins/expressions/common/expression_types/specs/error.ts:32:5 - (ae-forgotten-export) The symbol "SerializableState" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/home/public/application/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap b/src/plugins/home/public/application/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap index 9992fe2c8cf5f..2a4fb9e48a92b 100644 --- a/src/plugins/home/public/application/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap +++ b/src/plugins/home/public/application/components/tutorial/__snapshots__/saved_objects_installer.test.js.snap @@ -175,29 +175,25 @@ exports[`bulkCreate should display error message when bulkCreate request fails 1
- - -
- - + + + Step 1 is incomplete + + + + - - -
- - - - - -
-
-
+ + + + diff --git a/src/plugins/home/public/application/components/tutorial/tutorial.test.js b/src/plugins/home/public/application/components/tutorial/tutorial.test.js index 9944ac4848bc6..65e11170e0e42 100644 --- a/src/plugins/home/public/application/components/tutorial/tutorial.test.js +++ b/src/plugins/home/public/application/components/tutorial/tutorial.test.js @@ -133,7 +133,7 @@ describe('isCloudEnabled is false', () => { ); await loadTutorialPromise; component.update(); - component.find('button#onPremElasticCloud').closest('div').find('input').simulate('change'); + component.find('#onPremElasticCloud').first().simulate('click'); component.update(); expect(component.state('visibleInstructions')).toBe('onPremElasticCloud'); }); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/__snapshots__/indices_list.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/__snapshots__/indices_list.test.tsx.snap index 6631a9bbd1d02..db527ea81b2cb 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/__snapshots__/indices_list.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/__snapshots__/indices_list.test.tsx.snap @@ -63,7 +63,6 @@ exports[`IndicesList should change pages 1`] = ` isOpen={false} ownFocus={false} panelPaddingSize="none" - withTitle={true} > diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx index e0e295723a69d..b6efa40c5e40b 100644 --- a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx +++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx @@ -427,6 +427,7 @@ class TableListView extends React.Component !error, onClick: this.props.editItem, }, ]; diff --git a/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx b/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx index 832ea70f0460e..5d26925d34088 100644 --- a/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx +++ b/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx @@ -105,15 +105,19 @@ export class ValidatedDualRange extends Component { allowEmptyRange, ...rest // TODO: Consider alternatives for spread operator in component } = this.props; + // Ensure the form row is display as compressed if compressed is true + let evaluatedDisplay = formRowDisplay; + if (!evaluatedDisplay) { + evaluatedDisplay = compressed ? 'rowCompressed' : 'row'; + } return ( = (state: FromVersion) => ToVersion; + export interface PersistableState

{ /** * function to extract telemetry information @@ -47,8 +52,29 @@ export interface PersistableState

{ state: P; references: SavedObjectReference[] }; + + /** + * migrateToLatest function receives state of older version and should migrate to the latest version + * @param state + * @param version + */ + migrateToLatest?: (state: SerializableState, version: string) => P; + + /** + * migrate function runs the specified migration + * @param state + * @param version + */ + migrate?: (state: SerializableState, version: string) => SerializableState; } export type PersistableStateDefinition

= Partial< - PersistableState

->; + Omit, 'migrate'> +> & { + /** + * list of all migrations per semver + */ + migrations?: { + [key: string]: MigrateFunction; + }; +}; diff --git a/src/plugins/management/README.md b/src/plugins/management/README.md new file mode 100644 index 0000000000000..097ecd95ea8fa --- /dev/null +++ b/src/plugins/management/README.md @@ -0,0 +1,5 @@ +# Management Plugin + +This plugins contains the "Stack Management" page framework. It offers navigation and an API +to link individual managment section into it. This plugin does not contain any individual +management section itself. \ No newline at end of file diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap index b86a1aa966dc6..328ef0e95f744 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/__snapshots__/header.test.tsx.snap @@ -59,6 +59,7 @@ exports[`Intro component renders correctly 1`] = ` size="s" > { conflictedSavedObjectsLinkedToSavedSearches: undefined, conflictedSearchDocs: undefined, unmatchedReferences: undefined, + unmatchedReferencesTablePagination: { + pageIndex: 0, + pageSize: 5, + }, conflictingRecord: undefined, error: undefined, file: undefined, @@ -467,7 +472,7 @@ export class Flyout extends Component { }; renderUnmatchedReferences() { - const { unmatchedReferences } = this.state; + const { unmatchedReferences, unmatchedReferencesTablePagination: tablePagination } = this.state; if (!unmatchedReferences) { return null; @@ -527,22 +532,28 @@ export class Flyout extends Component { { defaultMessage: 'New index pattern' } ), render: (id: string) => { - const options = this.state.indexPatterns!.map( - (indexPattern) => - ({ - text: indexPattern.title, - value: indexPattern.id, - 'data-test-subj': `indexPatternOption-${indexPattern.title}`, - } as { text: string; value: string; 'data-test-subj'?: string }) - ); - - options.unshift({ - text: '-- Skip Import --', - value: '', - }); + const options = [ + { + text: '-- Skip Import --', + value: '', + }, + ...this.state.indexPatterns!.map( + (indexPattern) => + ({ + text: indexPattern.title, + value: indexPattern.id, + 'data-test-subj': `indexPatternOption-${indexPattern.title}`, + } as { text: string; value: string; 'data-test-subj'?: string }) + ), + ]; + + const selectedValue = + unmatchedReferences?.find((unmatchedRef) => unmatchedRef.existingIndexPatternId === id) + ?.newIndexPatternId ?? ''; return ( this.onIndexChanged(id, e)} options={options} @@ -553,6 +564,7 @@ export class Flyout extends Component { ]; const pagination = { + ...tablePagination, pageSizeOptions: [5, 10, 25], }; @@ -561,6 +573,16 @@ export class Flyout extends Component { items={unmatchedReferences as any[]} columns={columns} pagination={pagination} + onTableChange={({ page }) => { + if (page) { + this.setState({ + unmatchedReferencesTablePagination: { + pageSize: page.size, + pageIndex: page.index, + }, + }); + } + }} /> ); } diff --git a/src/plugins/share/public/services/share_menu_manager.tsx b/src/plugins/share/public/services/share_menu_manager.tsx index 3325c5503fe89..c1f8d55dfc76f 100644 --- a/src/plugins/share/public/services/share_menu_manager.tsx +++ b/src/plugins/share/public/services/share_menu_manager.tsx @@ -89,7 +89,6 @@ export class ShareMenuManager { isOpen={true} closePopover={this.onClose} panelPaddingSize="none" - withTitle anchorPosition="downLeft" > { - return { - toISOString: jest.fn(), - }; -}); - -const mockClone = jest.fn().mockImplementation(() => { - return { - clone: mockClone, - subtract: mockSubtract, - toISOString: jest.fn(), - }; -}); +const mockMomentValueOf = jest.fn(); jest.mock('moment', () => { return jest.fn().mockImplementation(() => { return { - clone: mockClone, - subtract: mockSubtract, - toISOString: jest.fn(), + valueOf: mockMomentValueOf, }; }); }); +import { mockTelemetryService } from '../mocks'; + describe('TelemetryService', () => { describe('fetchTelemetry', () => { it('calls expected URL with 20 minutes - now', async () => { + const timestamp = Date.now(); + mockMomentValueOf.mockReturnValueOnce(timestamp); const telemetryService = mockTelemetryService(); + await telemetryService.fetchTelemetry(); expect(telemetryService['http'].post).toBeCalledWith('/api/telemetry/v2/clusters/_stats', { - body: JSON.stringify({ unencrypted: false, timeRange: {} }), + body: JSON.stringify({ unencrypted: false, timestamp }), }); - expect(mockClone).toBeCalled(); - expect(mockSubtract).toBeCalledWith(20, 'minutes'); + expect(mockMomentValueOf).toBeCalled(); }); }); diff --git a/src/plugins/telemetry/public/services/telemetry_service.ts b/src/plugins/telemetry/public/services/telemetry_service.ts index 658f02bfba395..fade614be6c65 100644 --- a/src/plugins/telemetry/public/services/telemetry_service.ts +++ b/src/plugins/telemetry/public/services/telemetry_service.ts @@ -121,17 +121,10 @@ export class TelemetryService { }; public fetchTelemetry = async ({ unencrypted = false } = {}) => { - const now = moment(); return this.http.post('/api/telemetry/v2/clusters/_stats', { body: JSON.stringify({ unencrypted, - timeRange: { - min: now - .clone() // Need to clone it to avoid mutation (and max being the same value) - .subtract(20, 'minutes') - .toISOString(), - max: now.toISOString(), - }, + timestamp: moment().valueOf(), }), }); }; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 160e99a40790c..c840cbe8fc94d 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -16,13 +16,13 @@ "search": { "properties": { "successCount": { - "type": "number" + "type": "long" }, "errorCount": { - "type": "number" + "type": "long" }, "averageDuration": { - "type": "long" + "type": "float" } } }, diff --git a/src/plugins/telemetry/server/fetcher.ts b/src/plugins/telemetry/server/fetcher.ts index 8d865cd20cde1..a3649f51577ac 100644 --- a/src/plugins/telemetry/server/fetcher.ts +++ b/src/plugins/telemetry/server/fetcher.ts @@ -213,8 +213,7 @@ export class FetcherTask { private async fetchTelemetry() { return await this.telemetryCollectionManager!.getStats({ unencrypted: false, - start: moment().subtract(20, 'minutes').toISOString(), - end: moment().toISOString(), + timestamp: moment().valueOf(), }); } diff --git a/src/plugins/telemetry/server/routes/telemetry_opt_in.ts b/src/plugins/telemetry/server/routes/telemetry_opt_in.ts index b9a6d10489200..83a35b22d2f30 100644 --- a/src/plugins/telemetry/server/routes/telemetry_opt_in.ts +++ b/src/plugins/telemetry/server/routes/telemetry_opt_in.ts @@ -86,8 +86,7 @@ export function registerTelemetryOptInRoutes({ } const statsGetterConfig: StatsGetterConfig = { - start: moment().subtract(20, 'minutes').toISOString(), - end: moment().toISOString(), + timestamp: moment().valueOf(), unencrypted: false, }; diff --git a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts index 6a217085b13e7..ff2e0be1aa1ae 100644 --- a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts +++ b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts @@ -72,8 +72,7 @@ export function registerTelemetryOptInStatsRoutes( const unencrypted = req.body.unencrypted; const statsGetterConfig: StatsGetterConfig = { - start: moment().subtract(20, 'minutes').toISOString(), - end: moment().toISOString(), + timestamp: moment().valueOf(), unencrypted, request: req, }; diff --git a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts index d47a2a0780efe..fd888cbe087f7 100644 --- a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts +++ b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts @@ -32,8 +32,6 @@ const validate: TypeOptions['validate'] = (value) => { } }; -const dateSchema = schema.oneOf([schema.string({ validate }), schema.number({ validate })]); - export function registerTelemetryUsageStatsRoutes( router: IRouter, telemetryCollectionManager: TelemetryCollectionManagerPluginSetup, @@ -45,25 +43,20 @@ export function registerTelemetryUsageStatsRoutes( validate: { body: schema.object({ unencrypted: schema.boolean({ defaultValue: false }), - timeRange: schema.object({ - min: dateSchema, - max: dateSchema, - }), + timestamp: schema.oneOf([schema.string({ validate }), schema.number({ validate })]), }), }, }, async (context, req, res) => { - const start = moment(req.body.timeRange.min).toISOString(); - const end = moment(req.body.timeRange.max).toISOString(); - const unencrypted = req.body.unencrypted; + const { unencrypted, timestamp } = req.body; try { const statsConfig: StatsGetterConfig = { - unencrypted, - start, - end, + timestamp: moment(timestamp).valueOf(), request: req, + unencrypted, }; + const stats = await telemetryCollectionManager.getStats(statsConfig); return res.ok({ body: stats }); } catch (err) { diff --git a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.test.ts b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.test.ts index fcecbca23038e..9298b36ac3ea6 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.test.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.test.ts @@ -87,8 +87,7 @@ function mockStatsCollectionConfig(clusterInfo: any, clusterStats: any, kibana: ...createCollectorFetchContextMock(), esClient: mockGetLocalStats(clusterInfo, clusterStats), usageCollection: mockUsageCollection(kibana), - start: '', - end: '', + timestamp: Date.now(), }; } diff --git a/src/plugins/telemetry_collection_manager/server/plugin.ts b/src/plugins/telemetry_collection_manager/server/plugin.ts index 4900e75a1936b..e1e1379097adf 100644 --- a/src/plugins/telemetry_collection_manager/server/plugin.ts +++ b/src/plugins/telemetry_collection_manager/server/plugin.ts @@ -144,7 +144,7 @@ export class TelemetryCollectionManagerPlugin collectionSoService: SavedObjectsServiceStart, usageCollection: UsageCollectionSetup ): StatsCollectionConfig { - const { start, end, request } = config; + const { timestamp, request } = config; const callCluster = config.unencrypted ? collection.esCluster.asScoped(request).callAsCurrentUser @@ -157,7 +157,7 @@ export class TelemetryCollectionManagerPlugin const soClient = config.unencrypted ? collectionSoService.getScopedClient(config.request) : collectionSoService.createInternalRepository(); - return { callCluster, start, end, usageCollection, esClient, soClient }; + return { callCluster, timestamp, usageCollection, esClient, soClient }; } private async getOptInStats(optInStatus: boolean, config: StatsGetterConfig) { diff --git a/src/plugins/telemetry_collection_manager/server/types.ts b/src/plugins/telemetry_collection_manager/server/types.ts index d6e4fdce2b188..0100fdbbb3970 100644 --- a/src/plugins/telemetry_collection_manager/server/types.ts +++ b/src/plugins/telemetry_collection_manager/server/types.ts @@ -56,8 +56,7 @@ export interface TelemetryOptInStats { export interface BaseStatsGetterConfig { unencrypted: boolean; - start: string; - end: string; + timestamp: number; request?: KibanaRequest; } @@ -77,8 +76,7 @@ export interface ClusterDetails { export interface StatsCollectionConfig { usageCollection: UsageCollectionSetup; callCluster: LegacyAPICaller; - start: string | number; - end: string | number; + timestamp: number; esClient: ElasticsearchClient; soClient: SavedObjectsClientContract | ISavedObjectsRepository; } diff --git a/src/plugins/ui_actions/public/context_menu/open_context_menu.tsx b/src/plugins/ui_actions/public/context_menu/open_context_menu.tsx index 0d9a4c7be5670..80a8668a8a5b6 100644 --- a/src/plugins/ui_actions/public/context_menu/open_context_menu.tsx +++ b/src/plugins/ui_actions/public/context_menu/open_context_menu.tsx @@ -186,7 +186,6 @@ export function openContextMenu( closePopover={onClose} panelPaddingSize="none" anchorPosition="downRight" - withTitle ownFocus={true} > ({ }, some_obj: { total: { - type: 'number', + type: 'long', }, }, some_array: { @@ -182,7 +182,7 @@ export const myCollector = makeUsageCollector({ type: 'array', items: { total: { - type: 'number', + type: 'long', }, }, }, diff --git a/src/plugins/usage_collection/server/collector/collector.ts b/src/plugins/usage_collection/server/collector/collector.ts index 951418d448cbd..73febc0183fc5 100644 --- a/src/plugins/usage_collection/server/collector/collector.ts +++ b/src/plugins/usage_collection/server/collector/collector.ts @@ -27,14 +27,9 @@ import { export type CollectorFormatForBulkUpload = (result: T) => { type: string; payload: U }; -export type AllowedSchemaTypes = - | 'keyword' - | 'text' - | 'number' - | 'boolean' - | 'long' - | 'date' - | 'float'; +export type AllowedSchemaNumberTypes = 'long' | 'integer' | 'short' | 'byte' | 'double' | 'float'; + +export type AllowedSchemaTypes = AllowedSchemaNumberTypes | 'keyword' | 'text' | 'boolean' | 'date'; export interface SchemaField { type: string; diff --git a/src/plugins/usage_collection/server/collector/index.ts b/src/plugins/usage_collection/server/collector/index.ts index da85f9ab181c9..2f8be884a8a7b 100644 --- a/src/plugins/usage_collection/server/collector/index.ts +++ b/src/plugins/usage_collection/server/collector/index.ts @@ -21,6 +21,7 @@ export { CollectorSet } from './collector_set'; export { Collector, AllowedSchemaTypes, + AllowedSchemaNumberTypes, SchemaField, MakeSchemaFrom, CollectorOptions, diff --git a/src/plugins/vis_default_editor/public/components/agg_select.tsx b/src/plugins/vis_default_editor/public/components/agg_select.tsx index cccfb043cf0af..9d45b72d35cc0 100644 --- a/src/plugins/vis_default_editor/public/components/agg_select.tsx +++ b/src/plugins/vis_default_editor/public/components/agg_select.tsx @@ -141,7 +141,7 @@ function DefaultEditorAggSelect({ error={errors} isInvalid={showValidation ? !isValid : false} fullWidth={true} - compressed + display="rowCompressed" > + <> {numbers.map((number, arrayIndex) => ( diff --git a/src/plugins/vis_default_editor/public/components/controls/date_ranges.tsx b/src/plugins/vis_default_editor/public/components/controls/date_ranges.tsx index 4afc85ba3b5c2..785ef1b83a23d 100644 --- a/src/plugins/vis_default_editor/public/components/controls/date_ranges.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/date_ranges.tsx @@ -125,7 +125,7 @@ function DateRangesParamEditor({ ); return ( - + <> diff --git a/src/plugins/vis_default_editor/public/components/controls/field.tsx b/src/plugins/vis_default_editor/public/components/controls/field.tsx index 41d6db25da5e2..9529adfe12720 100644 --- a/src/plugins/vis_default_editor/public/components/controls/field.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/field.tsx @@ -108,7 +108,7 @@ function FieldParamEditor({ isInvalid={showErrorMessage} fullWidth={true} error={errors} - compressed + display="rowCompressed" > + {agg.params.ipRangeType === IpRangeTypes.MASK ? ( setValue(ev.target.value), [setValue]); return ( - + + + - + + <> {ranges.map(({ from, to, id }, index) => { const deleteBtnTitle = i18n.translate( diff --git a/src/plugins/vis_default_editor/public/components/controls/raw_json.tsx b/src/plugins/vis_default_editor/public/components/controls/raw_json.tsx index b433b704330a7..714bb3f3294ab 100644 --- a/src/plugins/vis_default_editor/public/components/controls/raw_json.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/raw_json.tsx @@ -82,7 +82,7 @@ function RawJsonParamEditor({ label={label} isInvalid={showValidation ? !isFieldValid : false} fullWidth={true} - compressed + display="rowCompressed" > <> - + + setAutoApplyEnabled(e.target.checked), []); + const toggleAutoApply = useCallback( + (nextAutoApplyEnabled) => setAutoApplyEnabled(nextAutoApplyEnabled), + [] + ); const onClickDiscard = useCallback(() => dispatch(discardChanges(vis)), [dispatch, vis]); useDebounce( @@ -131,19 +127,33 @@ function DefaultEditorControls({ defaultMessage: 'Auto updates the visualization on every change.', })} > - toggleAutoApply(!autoApplyEnabled)} size="s" - isIconOnly - /> + minWidth={80} + aria-label={ + autoApplyEnabled + ? i18n.translate('visDefaultEditor.sidebar.autoApplyChangesLabelOn', { + defaultMessage: 'Auto apply is on', + }) + : i18n.translate('visDefaultEditor.sidebar.autoApplyChangesLabelOff', { + defaultMessage: 'Auto apply is off', + }) + } + > + {autoApplyEnabled + ? i18n.translate('visDefaultEditor.sidebar.autoApplyChangesOn', { + defaultMessage: 'On', + }) + : i18n.translate('visDefaultEditor.sidebar.autoApplyChangesOff', { + defaultMessage: 'Off', + })} + )}

diff --git a/src/plugins/vis_type_metric/public/components/metric_vis_options.tsx b/src/plugins/vis_type_metric/public/components/metric_vis_options.tsx index ac6ab9e9e69a8..d0a7412238871 100644 --- a/src/plugins/vis_type_metric/public/components/metric_vis_options.tsx +++ b/src/plugins/vis_type_metric/public/components/metric_vis_options.tsx @@ -86,7 +86,7 @@ function MetricVisOptions({ ); const setColorMode: EuiButtonGroupProps['onChange'] = useCallback( - (id) => setMetricValue('metricColorMode', id as ColorModes), + (id: string) => setMetricValue('metricColorMode', id as ColorModes), [setMetricValue] ); diff --git a/src/plugins/vis_type_timelion/public/components/timelion_interval.tsx b/src/plugins/vis_type_timelion/public/components/timelion_interval.tsx index 7ed98f0fb802e..e0a76316669f7 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_interval.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_interval.tsx @@ -108,7 +108,7 @@ function TimelionInterval({ value, setValue, setValidity }: TimelionIntervalProp return ( ( 'IndexPatterns' ); +export const [getDataSearch, setDataSearch] = createGetterSetter('Search'); + export const [getSavedObjectsClient, setSavedObjectsClient] = createGetterSetter< SavedObjectsClientContract >('SavedObjectsClient'); diff --git a/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts b/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts index 975d12a152d89..1f0ac8b2b9392 100644 --- a/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts +++ b/src/plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts @@ -23,6 +23,7 @@ import { TimeRange, Filter, esQuery, Query } from '../../../data/public'; import { TimelionVisDependencies } from '../plugin'; import { getTimezone } from './get_timezone'; import { TimelionVisParams } from '../timelion_vis_fn'; +import { getDataSearch } from '../helpers/plugin_services'; interface Stats { cacheCount: number; @@ -93,6 +94,7 @@ export function getTimelionRequestHandler({ // parse the time range client side to make sure it behaves like other charts const timeRangeBounds = timefilter.calculateBounds(timeRange); + const sessionId = getDataSearch().session.getSessionId(); try { return await http.post('/api/timelion/run', { @@ -109,6 +111,7 @@ export function getTimelionRequestHandler({ interval: visParams.interval, timezone, }, + sessionId, }), }); } catch (e) { diff --git a/src/plugins/vis_type_timelion/public/plugin.ts b/src/plugins/vis_type_timelion/public/plugin.ts index bb8fb6b298a07..d74c127dce881 100644 --- a/src/plugins/vis_type_timelion/public/plugin.ts +++ b/src/plugins/vis_type_timelion/public/plugin.ts @@ -36,7 +36,7 @@ import { VisualizationsSetup } from '../../visualizations/public'; import { getTimelionVisualizationConfig } from './timelion_vis_fn'; import { getTimelionVisDefinition } from './timelion_vis_type'; -import { setIndexPatterns, setSavedObjectsClient } from './helpers/plugin_services'; +import { setIndexPatterns, setSavedObjectsClient, setDataSearch } from './helpers/plugin_services'; import { ConfigSchema } from '../config'; import { getArgValueSuggestions } from './helpers/arg_value_suggestions'; @@ -104,6 +104,7 @@ export class TimelionVisPlugin public start(core: CoreStart, plugins: TimelionVisStartDependencies) { setIndexPatterns(plugins.data.indexPatterns); setSavedObjectsClient(core.savedObjects.client); + setDataSearch(plugins.data.search); return { getArgValueSuggestions, diff --git a/src/plugins/vis_type_timelion/server/routes/run.ts b/src/plugins/vis_type_timelion/server/routes/run.ts index 19bb5238f9de0..5766705d9873d 100644 --- a/src/plugins/vis_type_timelion/server/routes/run.ts +++ b/src/plugins/vis_type_timelion/server/routes/run.ts @@ -75,6 +75,7 @@ export function runRoute( to: schema.maybe(schema.string()), }) ), + sessionId: schema.maybe(schema.string()), }), }, }, diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js b/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js index 8be3cf5171c65..e10b3f7e438db 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js +++ b/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js @@ -26,36 +26,39 @@ import createDateAgg from './lib/create_date_agg'; import esResponse from '../fixtures/es_response'; import _ from 'lodash'; -import { expect } from 'chai'; import sinon from 'sinon'; import invoke from '../helpers/invoke_series_fn.js'; import { UI_SETTINGS } from '../../../../data/server'; -function stubRequestAndServer(response, indexPatternSavedObjects = []) { - return { - getStartServices: sinon - .stub() - .returns( - Promise.resolve([ - {}, - { data: { search: { search: () => from(Promise.resolve(response)) } } }, - ]) - ), - savedObjectsClient: { - find: function () { - return Promise.resolve({ - saved_objects: indexPatternSavedObjects, - }); - }, - }, - }; -} - describe('es', () => { let tlConfig; + let dataSearchStub; + let mockResponse; + + beforeEach(() => { + dataSearchStub = { + data: { + search: { search: jest.fn(() => from(Promise.resolve(mockResponse))) }, + }, + }; + }); + + function stubRequestAndServer(response, indexPatternSavedObjects = []) { + mockResponse = response; + return { + getStartServices: sinon.stub().returns(Promise.resolve([{}, dataSearchStub])), + savedObjectsClient: { + find: function () { + return Promise.resolve({ + saved_objects: indexPatternSavedObjects, + }); + }, + }, + }; + } describe('seriesList processor', () => { - it('throws an error then the index is missing', () => { + test('throws an error then the index is missing', () => { tlConfig = stubRequestAndServer({ rawResponse: { _shards: { total: 0 }, @@ -64,14 +67,29 @@ describe('es', () => { return invoke(es, [5], tlConfig) .then(expect.fail) .catch((e) => { - expect(e).to.be.an('error'); + expect(e instanceof Error).toBeTruthy(); }); }); - it('returns a seriesList', () => { + test('should call data search with sessionId', async () => { + tlConfig = { + ...stubRequestAndServer({ rawResponse: esResponse }), + request: { + body: { + sessionId: 1, + }, + }, + }; + + await invoke(es, [5], tlConfig); + + expect(dataSearchStub.data.search.search.mock.calls[0][1]).toHaveProperty('sessionId', 1); + }); + + test('returns a seriesList', () => { tlConfig = stubRequestAndServer({ rawResponse: esResponse }); return invoke(es, [5], tlConfig).then((r) => { - expect(r.output.type).to.eql('seriesList'); + expect(r.output.type).toEqual('seriesList'); }); }); }); @@ -89,51 +107,51 @@ describe('es', () => { agg = createDateAgg(config, tlConfig); }); - it('creates a date_histogram with meta.type of time_buckets', () => { - expect(agg.time_buckets.meta.type).to.eql('time_buckets'); - expect(agg.time_buckets.date_histogram).to.be.an('object'); + test('creates a date_histogram with meta.type of time_buckets', () => { + expect(agg.time_buckets.meta.type).toEqual('time_buckets'); + expect(typeof agg.time_buckets.date_histogram).toBe('object'); }); - it('has extended_bounds that match tlConfig', () => { - expect(agg.time_buckets.date_histogram.extended_bounds.min).to.equal(tlConfig.time.from); - expect(agg.time_buckets.date_histogram.extended_bounds.max).to.equal(tlConfig.time.to); + test('has extended_bounds that match tlConfig', () => { + expect(agg.time_buckets.date_histogram.extended_bounds.min).toEqual(tlConfig.time.from); + expect(agg.time_buckets.date_histogram.extended_bounds.max).toEqual(tlConfig.time.to); }); - it('sets the timezone', () => { - expect(agg.time_buckets.date_histogram.time_zone).to.equal('Etc/UTC'); + test('sets the timezone', () => { + expect(agg.time_buckets.date_histogram.time_zone).toEqual('Etc/UTC'); }); - it('sets the field', () => { - expect(agg.time_buckets.date_histogram.field).to.equal('@timestamp'); + test('sets the field', () => { + expect(agg.time_buckets.date_histogram.field).toEqual('@timestamp'); }); - it('sets the interval for calendar_interval correctly', () => { - expect(agg.time_buckets.date_histogram).to.have.property('calendar_interval', '1y'); + test('sets the interval for calendar_interval correctly', () => { + expect(agg.time_buckets.date_histogram).toHaveProperty('calendar_interval', '1y'); }); - it('sets the interval for fixed_interval correctly', () => { + test('sets the interval for fixed_interval correctly', () => { const a = createDateAgg({ timefield: '@timestamp', interval: '24h' }, tlConfig); - expect(a.time_buckets.date_histogram).to.have.property('fixed_interval', '24h'); + expect(a.time_buckets.date_histogram).toHaveProperty('fixed_interval', '24h'); }); - it('sets min_doc_count to 0', () => { - expect(agg.time_buckets.date_histogram.min_doc_count).to.equal(0); + test('sets min_doc_count to 0', () => { + expect(agg.time_buckets.date_histogram.min_doc_count).toEqual(0); }); describe('metric aggs', () => { const emptyScriptedFields = []; - it('adds a metric agg for each metric', () => { + test('adds a metric agg for each metric', () => { config.metric = ['sum:beer', 'avg:bytes', 'percentiles:bytes']; agg = createDateAgg(config, tlConfig, emptyScriptedFields); - expect(agg.time_buckets.aggs['sum(beer)']).to.eql({ sum: { field: 'beer' } }); - expect(agg.time_buckets.aggs['avg(bytes)']).to.eql({ avg: { field: 'bytes' } }); - expect(agg.time_buckets.aggs['percentiles(bytes)']).to.eql({ + expect(agg.time_buckets.aggs['sum(beer)']).toEqual({ sum: { field: 'beer' } }); + expect(agg.time_buckets.aggs['avg(bytes)']).toEqual({ avg: { field: 'bytes' } }); + expect(agg.time_buckets.aggs['percentiles(bytes)']).toEqual({ percentiles: { field: 'bytes' }, }); }); - it('adds a scripted metric agg for each scripted metric', () => { + test('adds a scripted metric agg for each scripted metric', () => { config.metric = ['avg:scriptedBytes']; const scriptedFields = [ { @@ -143,7 +161,7 @@ describe('es', () => { }, ]; agg = createDateAgg(config, tlConfig, scriptedFields); - expect(agg.time_buckets.aggs['avg(scriptedBytes)']).to.eql({ + expect(agg.time_buckets.aggs['avg(scriptedBytes)']).toEqual({ avg: { script: { source: 'doc["bytes"].value', @@ -153,11 +171,11 @@ describe('es', () => { }); }); - it('has a special `count` metric that uses a script', () => { + test('has a special `count` metric that uses a script', () => { config.metric = ['count']; agg = createDateAgg(config, tlConfig, emptyScriptedFields); - expect(agg.time_buckets.aggs.count.bucket_script).to.be.an('object'); - expect(agg.time_buckets.aggs.count.bucket_script.buckets_path).to.eql('_count'); + expect(typeof agg.time_buckets.aggs.count.bucket_script).toBe('object'); + expect(agg.time_buckets.aggs.count.bucket_script.buckets_path).toEqual('_count'); }); }); }); @@ -176,43 +194,43 @@ describe('es', () => { }; }); - it('sets the index on the request', () => { + test('sets the index on the request', () => { config.index = 'beer'; const request = fn(config, tlConfig, emptyScriptedFields); - expect(request.params.index).to.equal('beer'); + expect(request.params.index).toEqual('beer'); }); - it('always sets body.size to 0', () => { + test('always sets body.size to 0', () => { const request = fn(config, tlConfig, emptyScriptedFields); - expect(request.params.body.size).to.equal(0); + expect(request.params.body.size).toEqual(0); }); - it('creates a filters agg that contains each of the queries passed', () => { + test('creates a filters agg that contains each of the queries passed', () => { config.q = ['foo', 'bar']; const request = fn(config, tlConfig, emptyScriptedFields); - expect(request.params.body.aggs.q.meta.type).to.equal('split'); + expect(request.params.body.aggs.q.meta.type).toEqual('split'); const filters = request.params.body.aggs.q.filters.filters; - expect(filters.foo.query_string.query).to.eql('foo'); - expect(filters.bar.query_string.query).to.eql('bar'); + expect(filters.foo.query_string.query).toEqual('foo'); + expect(filters.bar.query_string.query).toEqual('bar'); }); describe('timeouts', () => { - it('sets the timeout on the request', () => { + test('sets the timeout on the request', () => { config.index = 'beer'; const request = fn(config, tlConfig, emptyScriptedFields, 30000); - expect(request.params.timeout).to.equal('30000ms'); + expect(request.params.timeout).toEqual('30000ms'); }); - it('sets no timeout if elasticsearch.shardTimeout is set to 0', () => { + test('sets no timeout if elasticsearch.shardTimeout is set to 0', () => { config.index = 'beer'; const request = fn(config, tlConfig, emptyScriptedFields, 0); - expect(request.params).to.not.have.property('timeout'); + expect(request.params).not.toHaveProperty('timeout'); }); }); @@ -227,20 +245,20 @@ describe('es', () => { sandbox.restore(); }); - it('sets ignore_throttled=true on the request', () => { + test('sets ignore_throttled=true on the request', () => { config.index = 'beer'; tlConfig.settings[UI_SETTINGS.SEARCH_INCLUDE_FROZEN] = false; const request = fn(config, tlConfig, emptyScriptedFields); - expect(request.params.ignore_throttled).to.equal(true); + expect(request.params.ignore_throttled).toEqual(true); }); - it('sets no timeout if elasticsearch.shardTimeout is set to 0', () => { + test('sets no timeout if elasticsearch.shardTimeout is set to 0', () => { tlConfig.settings[UI_SETTINGS.SEARCH_INCLUDE_FROZEN] = true; config.index = 'beer'; const request = fn(config, tlConfig, emptyScriptedFields); - expect(request.params.ignore_throttled).to.equal(false); + expect(request.params.ignore_throttled).toEqual(false); }); }); @@ -271,24 +289,24 @@ describe('es', () => { }); }); - it('adds the contents of body.extended.es.filter to a filter clause of the bool', () => { + test('adds the contents of body.extended.es.filter to a filter clause of the bool', () => { config.kibana = true; const request = fn(config, tlConfig, emptyScriptedFields); const filter = request.params.body.query.bool.filter.bool; - expect(filter.must.length).to.eql(1); - expect(filter.must_not.length).to.eql(2); + expect(filter.must.length).toEqual(1); + expect(filter.must_not.length).toEqual(2); }); - it('does not include filters if config.kibana = false', () => { + test('does not include filters if config.kibana = false', () => { config.kibana = false; const request = fn(config, tlConfig, emptyScriptedFields); - expect(request.params.body.query.bool.filter).to.eql(undefined); + expect(request.params.body.query.bool.filter).toEqual(undefined); }); - it('adds a time filter to the bool querys must clause', () => { + test('adds a time filter to the bool querys must clause', () => { let request = fn(config, tlConfig, emptyScriptedFields); - expect(request.params.body.query.bool.must.length).to.eql(1); - expect(request.params.body.query.bool.must[0]).to.eql({ + expect(request.params.body.query.bool.must.length).toEqual(1); + expect(request.params.body.query.bool.must[0]).toEqual({ range: { '@timestamp': { format: 'strict_date_optional_time', @@ -300,27 +318,27 @@ describe('es', () => { config.kibana = true; request = fn(config, tlConfig, emptyScriptedFields); - expect(request.params.body.query.bool.must.length).to.eql(1); + expect(request.params.body.query.bool.must.length).toEqual(1); }); }); describe('config.split', () => { - it('adds terms aggs, in order, under the filters agg', () => { + test('adds terms aggs, in order, under the filters agg', () => { config.split = ['beer:5', 'wine:10']; const request = fn(config, tlConfig, emptyScriptedFields); const aggs = request.params.body.aggs.q.aggs; - expect(aggs.beer.meta.type).to.eql('split'); - expect(aggs.beer.terms.field).to.eql('beer'); - expect(aggs.beer.terms.size).to.eql(5); + expect(aggs.beer.meta.type).toEqual('split'); + expect(aggs.beer.terms.field).toEqual('beer'); + expect(aggs.beer.terms.size).toEqual(5); - expect(aggs.beer.aggs.wine.meta.type).to.eql('split'); - expect(aggs.beer.aggs.wine.terms.field).to.eql('wine'); - expect(aggs.beer.aggs.wine.terms.size).to.eql(10); + expect(aggs.beer.aggs.wine.meta.type).toEqual('split'); + expect(aggs.beer.aggs.wine.terms.field).toEqual('wine'); + expect(aggs.beer.aggs.wine.terms.size).toEqual(10); }); - it('adds scripted terms aggs, in order, under the filters agg', () => { + test('adds scripted terms aggs, in order, under the filters agg', () => { config.split = ['scriptedBeer:5', 'scriptedWine:10']; const scriptedFields = [ { @@ -338,19 +356,19 @@ describe('es', () => { const aggs = request.params.body.aggs.q.aggs; - expect(aggs.scriptedBeer.meta.type).to.eql('split'); - expect(aggs.scriptedBeer.terms.script).to.eql({ + expect(aggs.scriptedBeer.meta.type).toEqual('split'); + expect(aggs.scriptedBeer.terms.script).toEqual({ source: 'doc["beer"].value', lang: 'painless', }); - expect(aggs.scriptedBeer.terms.size).to.eql(5); + expect(aggs.scriptedBeer.terms.size).toEqual(5); - expect(aggs.scriptedBeer.aggs.scriptedWine.meta.type).to.eql('split'); - expect(aggs.scriptedBeer.aggs.scriptedWine.terms.script).to.eql({ + expect(aggs.scriptedBeer.aggs.scriptedWine.meta.type).toEqual('split'); + expect(aggs.scriptedBeer.aggs.scriptedWine.terms.script).toEqual({ source: 'doc["wine"].value', lang: 'painless', }); - expect(aggs.scriptedBeer.aggs.scriptedWine.terms.size).to.eql(10); + expect(aggs.scriptedBeer.aggs.scriptedWine.terms.size).toEqual(10); }); }); }); @@ -364,14 +382,14 @@ describe('es', () => { describe('timeBucketsToPairs', () => { const fn = aggResponse.timeBucketsToPairs; - it('Should convert a single metric agg', () => { + test('Should convert a single metric agg', () => { const buckets = [ { key: 1000, count: { value: 3 } }, { key: 2000, count: { value: 14 } }, { key: 3000, count: { value: 15 } }, ]; - expect(fn(buckets)).to.eql({ + expect(fn(buckets)).toEqual({ count: [ [1000, 3], [2000, 14], @@ -380,14 +398,14 @@ describe('es', () => { }); }); - it('Should convert multiple metric aggs', () => { + test('Should convert multiple metric aggs', () => { const buckets = [ { key: 1000, count: { value: 3 }, max: { value: 92 } }, { key: 2000, count: { value: 14 }, max: { value: 65 } }, { key: 3000, count: { value: 15 }, max: { value: 35 } }, ]; - expect(fn(buckets)).to.eql({ + expect(fn(buckets)).toEqual({ count: [ [1000, 3], [2000, 14], @@ -401,7 +419,7 @@ describe('es', () => { }); }); - it('Should convert percentiles metric aggs', () => { + test('Should convert percentiles metric aggs', () => { const buckets = [ { key: 1000, @@ -417,7 +435,7 @@ describe('es', () => { }, ]; - expect(fn(buckets)).to.eql({ + expect(fn(buckets)).toEqual({ 'percentiles:50.0': [ [1000, NaN], [2000, 25], @@ -442,8 +460,8 @@ describe('es', () => { }); }); - it('should throw an error', () => { - expect(aggResponse.default(esResponse.aggregations, config)).to.eql([ + test('should throw an error', () => { + expect(aggResponse.default(esResponse.aggregations, config)).toEqual([ { data: [ [1000, 264], diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/index.js b/src/plugins/vis_type_timelion/server/series_functions/es/index.js index fc3250f0d4726..75ca1d74f0bf8 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/es/index.js +++ b/src/plugins/vis_type_timelion/server/series_functions/es/index.js @@ -129,7 +129,6 @@ export default new Datasource('es', { const esShardTimeout = tlConfig.esShardTimeout; const body = buildRequest(config, tlConfig, scriptedFields, esShardTimeout); - const deps = (await tlConfig.getStartServices())[1]; const resp = await deps.data.search @@ -137,6 +136,7 @@ export default new Datasource('es', { body, { strategy: ES_SEARCH_STRATEGY, + sessionId: tlConfig.request?.body.sessionId, }, tlConfig.context ) diff --git a/src/plugins/vis_type_timeseries/common/agg_lookup.js b/src/plugins/vis_type_timeseries/common/agg_lookup.js index 432da03e3d45d..0a71ab34082f8 100644 --- a/src/plugins/vis_type_timeseries/common/agg_lookup.js +++ b/src/plugins/vis_type_timeseries/common/agg_lookup.js @@ -98,7 +98,7 @@ export const lookup = { }), top_hit: i18n.translate('visTypeTimeseries.aggLookup.topHitLabel', { defaultMessage: 'Top Hit' }), positive_rate: i18n.translate('visTypeTimeseries.aggLookup.positiveRateLabel', { - defaultMessage: 'Positive Rate', + defaultMessage: 'Counter Rate', }), }; diff --git a/src/plugins/vis_type_timeseries/common/calculate_label.js b/src/plugins/vis_type_timeseries/common/calculate_label.js index 9f3030eeb6eae..96e9fa0825b25 100644 --- a/src/plugins/vis_type_timeseries/common/calculate_label.js +++ b/src/plugins/vis_type_timeseries/common/calculate_label.js @@ -72,7 +72,7 @@ export function calculateLabel(metric, metrics) { } if (metric.type === 'positive_rate') { return i18n.translate('visTypeTimeseries.calculateLabel.positiveRateLabel', { - defaultMessage: 'Positive Rate of {field}', + defaultMessage: 'Counter Rate of {field}', values: { field: metric.field }, }); } diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.test.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.test.tsx index 968fa5384e1d8..6c75e081429de 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.test.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.test.tsx @@ -63,7 +63,7 @@ describe('TSVB AggSelect', () => { "value": "count", }, Object { - "label": "Positive Rate", + "label": "Counter Rate", "value": "positive_rate", }, Object { @@ -131,7 +131,7 @@ describe('TSVB AggSelect', () => { "value": "filter_ratio", }, Object { - "label": "Positive Rate", + "label": "Counter Rate", "value": "positive_rate", }, Object { diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx index 7701d351e5478..5c8049e363694 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/agg_select.tsx @@ -54,7 +54,7 @@ const metricAggs: AggSelectOption[] = [ }, { label: i18n.translate('visTypeTimeseries.aggSelect.metricsAggs.positiveRateLabel', { - defaultMessage: 'Positive Rate', + defaultMessage: 'Counter Rate', }), value: 'positive_rate', }, diff --git a/src/plugins/vis_type_timeseries/server/plugin.ts b/src/plugins/vis_type_timeseries/server/plugin.ts index 678ba2b371978..aabfa8a40e064 100644 --- a/src/plugins/vis_type_timeseries/server/plugin.ts +++ b/src/plugins/vis_type_timeseries/server/plugin.ts @@ -28,7 +28,7 @@ import { FakeRequest, } from 'src/core/server'; import { Observable } from 'rxjs'; -import { Server } from 'hapi'; +import { Server } from '@hapi/hapi'; import { VisTypeTimeseriesConfig } from './config'; import { getVisData, GetVisData, GetVisDataOptions } from './lib/get_vis_data'; import { ValidationTelemetryService } from './validation_telemetry'; diff --git a/src/plugins/vis_type_timeseries/server/routes/fields.ts b/src/plugins/vis_type_timeseries/server/routes/fields.ts index 255c85c9eefa7..f9a600fa4b1f3 100644 --- a/src/plugins/vis_type_timeseries/server/routes/fields.ts +++ b/src/plugins/vis_type_timeseries/server/routes/fields.ts @@ -17,7 +17,7 @@ * under the License. */ -import { isBoom } from 'boom'; +import { isBoom } from '@hapi/boom'; import { schema } from '@kbn/config-schema'; import { getFields } from '../lib/get_fields'; import { Framework } from '../plugin'; diff --git a/src/plugins/vis_type_vega/public/data_model/search_api.ts b/src/plugins/vis_type_vega/public/data_model/search_api.ts index 4ea25af549249..be58e3e3951eb 100644 --- a/src/plugins/vis_type_vega/public/data_model/search_api.ts +++ b/src/plugins/vis_type_vega/public/data_model/search_api.ts @@ -44,7 +44,7 @@ export class SearchAPI { ) {} search(searchRequests: SearchRequest[]) { - const { search } = this.dependencies.search; + const { search } = this.dependencies; const requestResponders: any = {}; return combineLatest( @@ -59,13 +59,18 @@ export class SearchAPI { requestResponders[requestId].json(params.body); } - return search({ params }, { abortSignal: this.abortSignal }).pipe( - tap((data) => this.inspectSearchResult(data, requestResponders[requestId])), - map((data) => ({ - name: requestId, - rawResponse: data.rawResponse, - })) - ); + return search + .search( + { params }, + { abortSignal: this.abortSignal, sessionId: search.session.getSessionId() } + ) + .pipe( + tap((data) => this.inspectSearchResult(data, requestResponders[requestId])), + map((data) => ({ + name: requestId, + rawResponse: data.rawResponse, + })) + ); }) ); } diff --git a/src/plugins/vis_type_vislib/public/components/common/truncate_labels.tsx b/src/plugins/vis_type_vislib/public/components/common/truncate_labels.tsx index 2f0cb701848d0..6f55f6f070d00 100644 --- a/src/plugins/vis_type_vislib/public/components/common/truncate_labels.tsx +++ b/src/plugins/vis_type_vislib/public/components/common/truncate_labels.tsx @@ -37,7 +37,7 @@ function TruncateLabelsOption({ disabled, value = null, setValue }: TruncateLabe defaultMessage: 'Truncate', })} fullWidth - compressed + display="rowCompressed" > + <> diff --git a/src/plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx b/src/plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx index 0823180300756..964bb7d569b08 100644 --- a/src/plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx +++ b/src/plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx @@ -114,7 +114,7 @@ function ThresholdPanel({ defaultMessage: 'Line color', })} fullWidth - compressed + display="rowCompressed" > { + const [idToSelectedMap, setIdToSelectedMap] = useState({}); /** * Keydown listener for a legend entry. * This will close the details panel of this legend entry when pressing Escape. @@ -74,7 +75,7 @@ const VisLegendItemComponent = ({ } }; - const filterOptions: EuiButtonGroupOption[] = [ + const filterOptions: EuiButtonGroupOptionProps[] = [ { id: 'filterIn', label: i18n.translate('visTypeVislib.vislib.legend.filterForValueButtonAriaLabel', { @@ -96,6 +97,7 @@ const VisLegendItemComponent = ({ ]; const handleFilterChange = (id: string) => { + setIdToSelectedMap({ filterIn: id === 'filterIn', filterOut: id === 'filterOut' }); onFilter(item, id !== 'filterIn'); }; @@ -112,6 +114,7 @@ const VisLegendItemComponent = ({ options={filterOptions} onChange={handleFilterChange} data-test-subj={`legend-${item.label}-filters`} + idToSelectedMap={idToSelectedMap} /> diff --git a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts index f6d27b54c7c64..9733e9fd68549 100644 --- a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts +++ b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts @@ -21,6 +21,7 @@ import { TriggerContextMapping } from '../../../ui_actions/public'; export interface VisualizationListItem { editUrl: string; editApp?: string; + error?: string; icon: string; id: string; stage: 'experimental' | 'beta' | 'production'; diff --git a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap index 02c0b36d12689..2089289b372a2 100644 --- a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap +++ b/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap @@ -1032,6 +1032,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` size="s" > { .findListItems(filter, listingLimit) .then(({ total, hits }: { total: number; hits: object[] }) => ({ total, - hits: hits.filter((result: any) => isLabsEnabled || result.type.stage !== 'experimental'), + hits: hits.filter( + (result: any) => isLabsEnabled || result.type?.stage !== 'experimental' + ), })); }, [listingLimit, savedVisualizations, uiSettings] diff --git a/src/plugins/visualize/public/application/utils/get_table_columns.tsx b/src/plugins/visualize/public/application/utils/get_table_columns.tsx index 805c039d9773b..3541c0dc31db2 100644 --- a/src/plugins/visualize/public/application/utils/get_table_columns.tsx +++ b/src/plugins/visualize/public/application/utils/get_table_columns.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { History } from 'history'; -import { EuiBetaBadge, EuiButton, EuiEmptyPrompt, EuiIcon, EuiLink } from '@elastic/eui'; +import { EuiBetaBadge, EuiButton, EuiEmptyPrompt, EuiIcon, EuiLink, EuiBadge } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -87,20 +87,24 @@ export const getTableColumns = (application: ApplicationStart, history: History) defaultMessage: 'Title', }), sortable: true, - render: (field: string, { editApp, editUrl, title }: VisualizationListItem) => ( - { - if (editApp) { - application.navigateToApp(editApp, { path: editUrl }); - } else if (editUrl) { - history.push(editUrl); - } - }} - data-test-subj={`visListingTitleLink-${title.split(' ').join('-')}`} - > - {field} - - ), + render: (field: string, { editApp, editUrl, title, error }: VisualizationListItem) => + // In case an error occurs i.e. the vis has wrong type, we render the vis but without the link + !error ? ( + { + if (editApp) { + application.navigateToApp(editApp, { path: editUrl }); + } else if (editUrl) { + history.push(editUrl); + } + }} + data-test-subj={`visListingTitleLink-${title.split(' ').join('-')}`} + > + {field} + + ) : ( + field + ), }, { field: 'typeTitle', @@ -108,13 +112,18 @@ export const getTableColumns = (application: ApplicationStart, history: History) defaultMessage: 'Type', }), sortable: true, - render: (field: string, record: VisualizationListItem) => ( - - {renderItemTypeIcon(record)} - {record.typeTitle} - {getBadge(record)} - - ), + render: (field: string, record: VisualizationListItem) => + !record.error ? ( + + {renderItemTypeIcon(record)} + {record.typeTitle} + {getBadge(record)} + + ) : ( + + {record.error} + + ), }, { field: 'description', diff --git a/test/api_integration/apis/index_patterns/es_errors/errors.js b/test/api_integration/apis/index_patterns/es_errors/errors.js index 9da29a0d3a3da..49a64f835fb35 100644 --- a/test/api_integration/apis/index_patterns/es_errors/errors.js +++ b/test/api_integration/apis/index_patterns/es_errors/errors.js @@ -19,7 +19,7 @@ import expect from '@kbn/expect'; import { errors as esErrors } from 'elasticsearch'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { isEsIndexNotFoundError, diff --git a/test/api_integration/apis/telemetry/telemetry_local.js b/test/api_integration/apis/telemetry/telemetry_local.js index 9a5467e622ff3..854b462be7704 100644 --- a/test/api_integration/apis/telemetry/telemetry_local.js +++ b/test/api_integration/apis/telemetry/telemetry_local.js @@ -53,15 +53,12 @@ export default function ({ getService }) { }); it('should pull local stats and validate data types', async () => { - const timeRange = { - min: '2018-07-23T22:07:00Z', - max: '2018-07-23T22:13:00Z', - }; + const timestamp = '2018-07-23T22:13:00Z'; const { body } = await supertest .post('/api/telemetry/v2/clusters/_stats') .set('kbn-xsrf', 'xxx') - .send({ timeRange, unencrypted: true }) + .send({ timestamp, unencrypted: true }) .expect(200); expect(body.length).to.be(1); @@ -98,15 +95,12 @@ export default function ({ getService }) { }); it('should pull local stats and validate fields', async () => { - const timeRange = { - min: '2018-07-23T22:07:00Z', - max: '2018-07-23T22:13:00Z', - }; + const timestamp = '2018-07-23T22:13:00Z'; const { body } = await supertest .post('/api/telemetry/v2/clusters/_stats') .set('kbn-xsrf', 'xxx') - .send({ timeRange, unencrypted: true }) + .send({ timestamp, unencrypted: true }) .expect(200); const stats = body[0]; @@ -156,10 +150,7 @@ export default function ({ getService }) { }); describe('application usage limits', () => { - const timeRange = { - min: '2018-07-23T22:07:00Z', - max: '2018-07-23T22:13:00Z', - }; + const timestamp = '2018-07-23T22:13:00Z'; function createSavedObject() { return supertest @@ -191,7 +182,7 @@ export default function ({ getService }) { const { body } = await supertest .post('/api/telemetry/v2/clusters/_stats') .set('kbn-xsrf', 'xxx') - .send({ timeRange, unencrypted: true }) + .send({ timestamp, unencrypted: true }) .expect(200); expect(body.length).to.be(1); @@ -242,7 +233,7 @@ export default function ({ getService }) { const { body } = await supertest .post('/api/telemetry/v2/clusters/_stats') .set('kbn-xsrf', 'xxx') - .send({ timeRange, unencrypted: true }) + .send({ timestamp, unencrypted: true }) .expect(200); expect(body.length).to.be(1); diff --git a/test/functional/apps/getting_started/_shakespeare.js b/test/functional/apps/getting_started/_shakespeare.js index e727949da5ad3..38ed3edc7deb5 100644 --- a/test/functional/apps/getting_started/_shakespeare.js +++ b/test/functional/apps/getting_started/_shakespeare.js @@ -35,7 +35,8 @@ export default function ({ getService, getPageObjects }) { // https://www.elastic.co/guide/en/kibana/current/tutorial-load-dataset.html - describe('Shakespeare', function describeIndexTests() { + // Failing: See https://github.com/elastic/kibana/issues/82206 + describe.skip('Shakespeare', function describeIndexTests() { // index starts on the first "count" metric at 1 // Each new metric or aggregation added to a visualization gets the next index. // So to modify a metric or aggregation tests need to keep track of the diff --git a/test/functional/apps/management/_import_objects.ts b/test/functional/apps/management/_import_objects.ts index 0b417d7d23e93..ddabb32bf5909 100644 --- a/test/functional/apps/management/_import_objects.ts +++ b/test/functional/apps/management/_import_objects.ts @@ -421,21 +421,40 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(isSavedObjectImported).to.be(true); }); - it('should import saved objects with index patterns when index patterns does not exists', async () => { - // First, we need to delete the index pattern - await PageObjects.savedObjects.clickCheckboxByTitle('logstash-*'); - await PageObjects.savedObjects.clickDelete(); - - // Then, import the objects + it('should preserve index patterns selection when switching between pages', async () => { await PageObjects.savedObjects.importFile( - path.join(__dirname, 'exports', '_import_objects_with_index_patterns.json') + path.join(__dirname, 'exports', '_import_objects_missing_all_index_patterns.json') ); - await PageObjects.savedObjects.checkImportSucceeded(); - await PageObjects.savedObjects.clickImportDone(); - const objects = await PageObjects.savedObjects.getRowTitles(); - const isSavedObjectImported = objects.includes('saved object imported with index pattern'); - expect(isSavedObjectImported).to.be(true); + await PageObjects.savedObjects.setOverriddenIndexPatternValue( + 'missing-index-pattern-1', + 'index-pattern-test-1' + ); + + await testSubjects.click('pagination-button-next'); + + await PageObjects.savedObjects.setOverriddenIndexPatternValue( + 'missing-index-pattern-7', + 'index-pattern-test-2' + ); + + await testSubjects.click('pagination-button-previous'); + + const selectedIdForMissingIndexPattern1 = await testSubjects.getAttribute( + 'managementChangeIndexSelection-missing-index-pattern-1', + 'value' + ); + + expect(selectedIdForMissingIndexPattern1).to.eql('f1e4c910-a2e6-11e7-bb30-233be9be6a20'); + + await testSubjects.click('pagination-button-next'); + + const selectedIdForMissingIndexPattern7 = await testSubjects.getAttribute( + 'managementChangeIndexSelection-missing-index-pattern-7', + 'value' + ); + + expect(selectedIdForMissingIndexPattern7).to.eql('f1e4c910-a2e6-11e7-bb30-233be9be6a87'); }); }); }); diff --git a/test/functional/apps/management/exports/_import_objects_missing_all_index_patterns.json b/test/functional/apps/management/exports/_import_objects_missing_all_index_patterns.json new file mode 100644 index 0000000000000..45572b0bf34fe --- /dev/null +++ b/test/functional/apps/management/exports/_import_objects_missing_all_index_patterns.json @@ -0,0 +1,121 @@ +[ + { + "_id": "test-vis-1", + "_type": "visualization", + "_source": { + "title": "Test VIS 1", + "visState": "{\"title\":\"test vis 1\",\"type\":\"histogram\"}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"missing-index-pattern-1\",\"query\":{}}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + }, + { + "_id": "test-vis-2", + "_type": "visualization", + "_source": { + "title": "Test VIS 2", + "visState": "{\"title\":\"test vis 2\",\"type\":\"histogram\"}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"missing-index-pattern-2\",\"query\":{}}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + }, + { + "_id": "test-vis-3", + "_type": "visualization", + "_source": { + "title": "Test VIS 3", + "visState": "{\"title\":\"test vis 3\",\"type\":\"histogram\"}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"missing-index-pattern-3\",\"query\":{}}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + }, + { + "_id": "test-vis-4", + "_type": "visualization", + "_source": { + "title": "Test VIS 4", + "visState": "{\"title\":\"test vis 4\",\"type\":\"histogram\"}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"missing-index-pattern-4\",\"query\":{}}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + }, + { + "_id": "test-vis-5", + "_type": "visualization", + "_source": { + "title": "Test VIS 5", + "visState": "{\"title\":\"test vis 5\",\"type\":\"histogram\"}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"missing-index-pattern-5\",\"query\":{}}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + }, + { + "_id": "test-vis-6", + "_type": "visualization", + "_source": { + "title": "Test VIS 6", + "visState": "{\"title\":\"test vis 6\",\"type\":\"histogram\"}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"missing-index-pattern-6\",\"query\":{}}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + }, + { + "_id": "test-vis-7", + "_type": "visualization", + "_source": { + "title": "Test VIS 7", + "visState": "{\"title\":\"test vis 7\",\"type\":\"histogram\"}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"missing-index-pattern-7\",\"query\":{}}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + } +] diff --git a/test/functional/page_objects/management/saved_objects_page.ts b/test/functional/page_objects/management/saved_objects_page.ts index 8e65b6488836c..0742f14b2eeb4 100644 --- a/test/functional/page_objects/management/saved_objects_page.ts +++ b/test/functional/page_objects/management/saved_objects_page.ts @@ -126,6 +126,12 @@ export function SavedObjectsPageProvider({ getService, getPageObjects }: FtrProv } } + async setOverriddenIndexPatternValue(oldName: string, newName: string) { + const select = await testSubjects.find(`managementChangeIndexSelection-${oldName}`); + const option = await testSubjects.findDescendant(`indexPatternOption-${newName}`, select); + await option.click(); + } + async clickCopyToSpaceByTitle(title: string) { const table = keyBy(await this.getElementsInTable(), 'title'); // should we check if table size > 0 and log error if not? diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts index 8cac43d97317b..cdbbeb9188732 100644 --- a/test/functional/page_objects/visualize_editor_page.ts +++ b/test/functional/page_objects/visualize_editor_page.ts @@ -97,9 +97,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP } public async clickSplitDirection(direction: string) { - const radioBtn = await find.byCssSelector( - `[data-test-subj="visEditorSplitBy"][title="${direction}"]` - ); + const radioBtn = await find.byCssSelector(`[data-test-subj="visEditorSplitBy-${direction}"]`); await radioBtn.click(); } @@ -334,10 +332,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP } public async toggleAutoMode() { - // this is a temporary solution, should be replaced with initial after fixing the EuiToggleButton - // passing the data-test-subj attribute to a checkbox - await find.clickByCssSelector('.visEditorSidebar__controls input[type="checkbox"]'); - // await testSubjects.click('visualizeEditorAutoButton'); + await testSubjects.click('visualizeEditorAutoButton'); } public async isApplyEnabled() { diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json index 7236ac8afbda6..1edec496f1054 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json @@ -12,7 +12,7 @@ "build": "rm -rf './target' && tsc" }, "devDependencies": { - "@elastic/eui": "29.5.0", + "@elastic/eui": "30.1.1", "@kbn/plugin-helpers": "1.0.0", "react": "^16.12.0", "react-dom": "^16.12.0", diff --git a/test/plugin_functional/plugins/kbn_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_sample_panel_action/package.json index 98a8f050bddd3..58cb4de0d63a4 100644 --- a/test/plugin_functional/plugins/kbn_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/kbn_sample_panel_action/package.json @@ -12,7 +12,7 @@ "build": "rm -rf './target' && tsc" }, "devDependencies": { - "@elastic/eui": "29.5.0", + "@elastic/eui": "30.1.1", "react": "^16.12.0", "typescript": "4.0.2" } diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json index 8c0815b896a0e..7513e763bad7e 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -12,7 +12,7 @@ "build": "rm -rf './target' && tsc" }, "devDependencies": { - "@elastic/eui": "29.5.0", + "@elastic/eui": "30.1.1", "@kbn/plugin-helpers": "1.0.0", "react": "^16.12.0", "typescript": "4.0.2" diff --git a/tsconfig.json b/tsconfig.json index 30b38d0fc2dd3..2e4d5730d9320 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,7 @@ "src/plugins/kibana_usage_collection/**/*", "src/plugins/kibana_utils/**/*", "src/plugins/newsfeed/**/*", + "src/plugins/share/**/*", "src/plugins/telemetry_collection_manager/**/*", "src/plugins/telemetry/**/*", "src/plugins/url_forwarding/**/*", @@ -31,6 +32,7 @@ { "path": "./src/plugins/kibana_usage_collection/tsconfig.json" }, { "path": "./src/plugins/kibana_utils/tsconfig.json" }, { "path": "./src/plugins/newsfeed/tsconfig.json" }, + { "path": "./src/plugins/share/tsconfig.json" }, { "path": "./src/plugins/telemetry_collection_manager/tsconfig.json" }, { "path": "./src/plugins/telemetry/tsconfig.json" }, { "path": "./src/plugins/usage_collection/tsconfig.json" }, diff --git a/tsconfig.refs.json b/tsconfig.refs.json index c16e7a5e1b0f1..a37aa7b5b57fb 100644 --- a/tsconfig.refs.json +++ b/tsconfig.refs.json @@ -8,6 +8,7 @@ { "path": "./src/plugins/kibana_usage_collection/tsconfig.json" }, { "path": "./src/plugins/kibana_utils/tsconfig.json" }, { "path": "./src/plugins/newsfeed/tsconfig.json" }, + { "path": "./src/plugins/share/tsconfig.json" }, { "path": "./src/plugins/telemetry_collection_manager/tsconfig.json" }, { "path": "./src/plugins/telemetry/tsconfig.json" }, { "path": "./src/plugins/url_forwarding/tsconfig.json" }, diff --git a/x-pack/dev-tools/api_debug/apis/telemetry/index.js b/x-pack/dev-tools/api_debug/apis/telemetry/index.js index 79b8c643b61fa..5cee4cdf45d6d 100644 --- a/x-pack/dev-tools/api_debug/apis/telemetry/index.js +++ b/x-pack/dev-tools/api_debug/apis/telemetry/index.js @@ -7,18 +7,8 @@ import moment from 'moment'; export const name = 'telemetry'; -export const description = 'Get the clusters stats for the last 1 hour from the Kibana server'; +export const description = 'Get the clusters stats from the Kibana server'; export const method = 'POST'; export const path = '/api/telemetry/v2/clusters/_stats'; -// Get an object with start and end times for the last 1 hour, ISO format, in UTC -function getTimeRange() { - const end = moment(); - const start = moment(end).subtract(1, 'hour'); - return { - min: moment.utc(start).format(), - max: moment.utc(end).format(), - }; -} - -export const body = { timeRange: getTimeRange(), unencrypted: true }; +export const body = { timeRange: moment().valueOf(), unencrypted: true }; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx index a90147d01e8b6..48b64a1c84588 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx @@ -89,7 +89,6 @@ export const DrilldownsWithEmbeddableExample: React.FC = () => { isOpen={openPopup} closePopover={() => setOpenPopup(false)} panelPaddingSize="none" - withTitle anchorPosition="downLeft" > diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_without_embeddable_example/drilldowns_without_embeddable_example.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_without_embeddable_example/drilldowns_without_embeddable_example.tsx index fb22e98e4a6d9..8f1582aaacff5 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_without_embeddable_example/drilldowns_without_embeddable_example.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_without_embeddable_example/drilldowns_without_embeddable_example.tsx @@ -77,7 +77,6 @@ export const DrilldownsWithoutEmbeddableExample: React.FC = () => { isOpen={openPopup} closePopover={() => setOpenPopup(false)} panelPaddingSize="none" - withTitle anchorPosition="downLeft" > diff --git a/x-pack/examples/ui_actions_enhanced_examples/tsconfig.json b/x-pack/examples/ui_actions_enhanced_examples/tsconfig.json index f8c1a6b53dac5..05e5f39d4d628 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/tsconfig.json +++ b/x-pack/examples/ui_actions_enhanced_examples/tsconfig.json @@ -16,5 +16,6 @@ { "path": "../../../src/core/tsconfig.json" }, { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, + { "path": "../../../src/plugins/share/tsconfig.json" } ] } diff --git a/x-pack/package.json b/x-pack/package.json index ba464a21263d7..bc58cc5516cf9 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -32,6 +32,7 @@ "@cypress/webpack-preprocessor": "^5.4.1", "@elastic/apm-rum-react": "^1.2.5", "@elastic/maki": "6.3.0", + "@hapi/hapi": "^18.4.1", "@kbn/babel-preset": "1.0.0", "@kbn/dev-utils": "1.0.0", "@kbn/es": "1.0.0", @@ -57,7 +58,6 @@ "@types/angular": "^1.6.56", "@types/archiver": "^3.1.0", "@types/base64-js": "^1.2.5", - "@types/boom": "^7.2.0", "@types/cheerio": "^0.22.10", "@types/chroma-js": "^1.4.2", "@types/color": "^3.0.0", @@ -174,7 +174,6 @@ "graphql-codegen-typescript-resolvers": "^0.18.2", "graphql-codegen-typescript-server": "^0.18.2", "gulp": "4.0.2", - "hapi": "^17.5.3", "he": "^1.2.0", "history-extra": "^5.0.1", "hoist-non-react-statics": "^3.3.2", @@ -267,11 +266,13 @@ "@babel/runtime": "^7.11.2", "@elastic/datemath": "5.0.3", "@elastic/ems-client": "7.10.0", - "@elastic/eui": "29.5.0", + "@elastic/eui": "30.1.1", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.0", "@elastic/safer-lodash-set": "0.0.0", + "@hapi/boom": "^7.4.11", + "@hapi/h2o2": "^8.3.2", "@kbn/config-schema": "1.0.0", "@kbn/i18n": "1.0.0", "@kbn/interpreter": "1.0.0", @@ -289,7 +290,6 @@ "archiver": "^3.1.1", "axios": "^0.19.2", "bluebird": "3.5.5", - "boom": "^7.2.0", "chroma-js": "^1.4.1", "classnames": "2.2.6", "concat-stream": "1.6.2", @@ -314,7 +314,6 @@ "graphql-fields": "^1.0.2", "graphql-tag": "^2.10.3", "graphql-tools": "^3.0.2", - "h2o2": "^8.1.2", "handlebars": "4.7.6", "history": "^4.9.0", "idx": "^2.5.6", diff --git a/x-pack/plugins/actions/server/action_type_registry.ts b/x-pack/plugins/actions/server/action_type_registry.ts index cacf7166b96ba..15ff7e558025e 100644 --- a/x-pack/plugins/actions/server/action_type_registry.ts +++ b/x-pack/plugins/actions/server/action_type_registry.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { i18n } from '@kbn/i18n'; import { RunContext, TaskManagerSetupContract } from '../../task_manager/server'; import { ActionType as CommonActionType } from '../common'; diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index e565d420d772e..2735a1ab44521 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { ILegacyScopedClusterClient, SavedObjectsClientContract, diff --git a/x-pack/plugins/actions/server/authorization/actions_authorization.ts b/x-pack/plugins/actions/server/authorization/actions_authorization.ts index cad58bed50981..e8d7bc89f2dbf 100644 --- a/x-pack/plugins/actions/server/authorization/actions_authorization.ts +++ b/x-pack/plugins/actions/server/authorization/actions_authorization.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { KibanaRequest } from 'src/core/server'; import { SecurityPluginSetup } from '../../../security/server'; import { ActionsAuthorizationAuditLogger } from './audit_logger'; diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts index c8e2684651598..18cbd9f9c5fad 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts @@ -145,12 +145,6 @@ test('executes the task by calling the executor with proper parameters', async ( url: '/', }, }, - // TODO: Remove once we upgrade to hapi v18 - _core: { - info: { - uri: 'http://localhost', - }, - }, }, }); }); @@ -277,12 +271,6 @@ test('uses API key when provided', async () => { url: '/', }, }, - // TODO: Remove once we upgrade to hapi v18 - _core: { - info: { - uri: 'http://localhost', - }, - }, }, }); }); @@ -322,12 +310,6 @@ test(`doesn't use API key when not provided`, async () => { url: '/', }, }, - // TODO: Remove once we upgrade to hapi v18 - _core: { - info: { - uri: 'http://localhost', - }, - }, }, }); }); diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.ts index 93ae5a2c807f9..aeeeb4ed7d520 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.ts @@ -102,12 +102,6 @@ export class TaskRunnerFactory { url: '/', }, }, - // TODO: Remove once we upgrade to hapi v18 - _core: { - info: { - uri: 'http://localhost', - }, - }, } as unknown) as KibanaRequest; let executorResult: ActionTypeExecutorResult; diff --git a/x-pack/plugins/actions/server/lib/validate_with_schema.ts b/x-pack/plugins/actions/server/lib/validate_with_schema.ts index 50231f1c9a3a1..4cf5e924e3f24 100644 --- a/x-pack/plugins/actions/server/lib/validate_with_schema.ts +++ b/x-pack/plugins/actions/server/lib/validate_with_schema.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { ActionType, ActionTypeConfig, ActionTypeSecrets, ActionTypeParams } from '../types'; export function validateParams< diff --git a/x-pack/plugins/actions/server/lib/verify_api_access.ts b/x-pack/plugins/actions/server/lib/verify_api_access.ts index 2055c66865c4e..ddbcaa90dcee1 100644 --- a/x-pack/plugins/actions/server/lib/verify_api_access.ts +++ b/x-pack/plugins/actions/server/lib/verify_api_access.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { ILicenseState } from './license_state'; export function verifyApiAccess(licenseState: ILicenseState) { diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index d0c7bf350504b..599e7461ea312 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -86,9 +86,10 @@ export interface PluginSetupContract { registerType< Config extends ActionTypeConfig = ActionTypeConfig, Secrets extends ActionTypeSecrets = ActionTypeSecrets, - Params extends ActionTypeParams = ActionTypeParams + Params extends ActionTypeParams = ActionTypeParams, + ExecutorResultData = void >( - actionType: ActionType + actionType: ActionType ): void; } @@ -254,9 +255,10 @@ export class ActionsPlugin implements Plugin, Plugi registerType: < Config extends ActionTypeConfig = ActionTypeConfig, Secrets extends ActionTypeSecrets = ActionTypeSecrets, - Params extends ActionTypeParams = ActionTypeParams + Params extends ActionTypeParams = ActionTypeParams, + ExecutorResultData = void >( - actionType: ActionType + actionType: ActionType ) => { if (!(actionType.minimumLicenseRequired in LICENSE_TYPE)) { throw new Error(`"${actionType.minimumLicenseRequired}" is not a valid license type`); diff --git a/x-pack/plugins/alerts/server/alert_type_registry.ts b/x-pack/plugins/alerts/server/alert_type_registry.ts index 0cd218571035a..1d2d9981faeaa 100644 --- a/x-pack/plugins/alerts/server/alert_type_registry.ts +++ b/x-pack/plugins/alerts/server/alert_type_registry.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; import typeDetect from 'type-detect'; diff --git a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts index 88abce7298622..a455872d23702 100644 --- a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { omit, isEqual, map, uniq, pick, truncate, trim } from 'lodash'; import { i18n } from '@kbn/i18n'; import { diff --git a/x-pack/plugins/alerts/server/alerts_client_factory.test.ts b/x-pack/plugins/alerts/server/alerts_client_factory.test.ts index 4e457cdb12bd3..3cf6666e90eb0 100644 --- a/x-pack/plugins/alerts/server/alerts_client_factory.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client_factory.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Request } from 'hapi'; +import { Request } from '@hapi/hapi'; import { AlertsClientFactory, AlertsClientFactoryOpts } from './alerts_client_factory'; import { alertTypeRegistryMock } from './alert_type_registry.mock'; import { taskManagerMock } from '../../task_manager/server/mocks'; @@ -60,12 +60,6 @@ const fakeRequest = ({ url: '/', }, }, - // TODO: Remove once we upgrade to hapi v18 - _core: { - info: { - uri: 'http://localhost', - }, - }, getSavedObjectsClient: () => savedObjectsClient, } as unknown) as Request; diff --git a/x-pack/plugins/alerts/server/authorization/alerts_authorization.ts b/x-pack/plugins/alerts/server/authorization/alerts_authorization.ts index 17691baa25af8..6814e4ac1cc1b 100644 --- a/x-pack/plugins/alerts/server/authorization/alerts_authorization.ts +++ b/x-pack/plugins/alerts/server/authorization/alerts_authorization.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { map, mapValues, fromPairs, has } from 'lodash'; import { KibanaRequest } from 'src/core/server'; import { ALERTS_FEATURE_ID } from '../../common'; diff --git a/x-pack/plugins/alerts/server/lib/license_api_access.ts b/x-pack/plugins/alerts/server/lib/license_api_access.ts index 2e650ebf5eb17..f9ef51f6b3c9a 100644 --- a/x-pack/plugins/alerts/server/lib/license_api_access.ts +++ b/x-pack/plugins/alerts/server/lib/license_api_access.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { LicenseState } from './license_state'; export function verifyApiAccess(licenseState: LicenseState) { diff --git a/x-pack/plugins/alerts/server/lib/license_state.ts b/x-pack/plugins/alerts/server/lib/license_state.ts index b3204d886960f..ead6b743f1719 100644 --- a/x-pack/plugins/alerts/server/lib/license_state.ts +++ b/x-pack/plugins/alerts/server/lib/license_state.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { i18n } from '@kbn/i18n'; import { assertNever } from '@kbn/std'; import { Observable, Subscription } from 'rxjs'; diff --git a/x-pack/plugins/alerts/server/lib/validate_alert_type_params.ts b/x-pack/plugins/alerts/server/lib/validate_alert_type_params.ts index b6dcb522f0925..a443143d8cbde 100644 --- a/x-pack/plugins/alerts/server/lib/validate_alert_type_params.ts +++ b/x-pack/plugins/alerts/server/lib/validate_alert_type_params.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { AlertType, AlertExecutorOptions } from '../types'; export function validateAlertTypeParams( diff --git a/x-pack/plugins/alerts/server/plugin.test.ts b/x-pack/plugins/alerts/server/plugin.test.ts index ece7d31d2f7fd..b13a1c62f6602 100644 --- a/x-pack/plugins/alerts/server/plugin.test.ts +++ b/x-pack/plugins/alerts/server/plugin.test.ts @@ -149,12 +149,6 @@ describe('Alerting Plugin', () => { url: '/', }, }, - // TODO: Remove once we upgrade to hapi v18 - _core: { - info: { - uri: 'http://localhost', - }, - }, getSavedObjectsClient: jest.fn(), } as unknown) as KibanaRequest; await startContract.getAlertsClientWithRequest(fakeRequest); diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index 17d5fcd31b745..8e345d6ff66a8 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -364,12 +364,6 @@ describe('Task Runner', () => { url: '/', }, }, - // TODO: Remove once we upgrade to hapi v18 - _core: { - info: { - uri: 'http://localhost', - }, - }, }); expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(1); expect(actionsClient.enqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` @@ -668,12 +662,6 @@ describe('Task Runner', () => { url: '/', }, }, - // TODO: Remove once we upgrade to hapi v18 - _core: { - info: { - uri: 'http://localhost', - }, - }, }); }); @@ -706,12 +694,6 @@ describe('Task Runner', () => { url: '/', }, }, - // TODO: Remove once we upgrade to hapi v18 - _core: { - info: { - uri: 'http://localhost', - }, - }, }); }); diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts index 76125da20d552..954c5675df89c 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -101,12 +101,6 @@ export class TaskRunner { url: '/', }, }, - // TODO: Remove once we upgrade to hapi v18 - _core: { - info: { - uri: 'http://localhost', - }, - }, } as unknown) as KibanaRequest; } diff --git a/x-pack/plugins/apm/common/service_map.ts b/x-pack/plugins/apm/common/service_map.ts index 02456f9b2050f..6edf56fb9a1ae 100644 --- a/x-pack/plugins/apm/common/service_map.ts +++ b/x-pack/plugins/apm/common/service_map.ts @@ -91,3 +91,5 @@ export function isSpanGroupingSupported(type?: string, subtype?: string) { nongroupedSubType === 'all' || nongroupedSubType === subtype ); } + +export const SERVICE_MAP_TIMEOUT_ERROR = 'ServiceMapTimeoutError'; diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts index ab2bf20b36ed4..50c620dca9ddf 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/apm.ts @@ -11,7 +11,7 @@ import { loginAndWaitForPage } from '../../integration/helpers'; export const DEFAULT_TIMEOUT = 60 * 1000; Given(`a user browses the APM UI application`, () => { - // open service overview page + // Open service inventory page loginAndWaitForPage(`/app/apm/services`, { from: '2020-06-01T14:59:32.686Z', to: '2020-06-16T16:59:36.219Z', diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts index d8540c3f3efd7..452d8b719b3cb 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts @@ -12,7 +12,7 @@ import { verifyClientMetrics } from './client_metrics_helper'; export const DEFAULT_TIMEOUT = { timeout: 60 * 1000 }; Given(`a user browses the APM UI application for RUM Data`, () => { - // open service overview page + // Open UX landing page const RANGE_FROM = 'now-24h'; const RANGE_TO = 'now'; loginAndWaitForPage( diff --git a/x-pack/plugins/apm/public/components/alerting/ServiceAlertTrigger/PopoverExpression/index.tsx b/x-pack/plugins/apm/public/components/alerting/ServiceAlertTrigger/PopoverExpression/index.tsx index ac96951ab54ca..a95ea3cf11e7a 100644 --- a/x-pack/plugins/apm/public/components/alerting/ServiceAlertTrigger/PopoverExpression/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/ServiceAlertTrigger/PopoverExpression/index.tsx @@ -20,7 +20,6 @@ export function PopoverExpression(props: Props) { return ( setPopoverOpen(false)} button={ diff --git a/x-pack/plugins/apm/public/components/app/Home/index.tsx b/x-pack/plugins/apm/public/components/app/Home/index.tsx index 446f7b978a434..c4224bf676abb 100644 --- a/x-pack/plugins/apm/public/components/app/Home/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Home/index.tsx @@ -20,12 +20,12 @@ import { ApmHeader } from '../../shared/ApmHeader'; import { EuiTabLink } from '../../shared/EuiTabLink'; import { AnomalyDetectionSetupLink } from '../../shared/Links/apm/AnomalyDetectionSetupLink'; import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink'; -import { ServiceOverviewLink } from '../../shared/Links/apm/ServiceOverviewLink'; +import { ServiceInventoryLink } from '../../shared/Links/apm/service_inventory_link'; import { SettingsLink } from '../../shared/Links/apm/SettingsLink'; import { TraceOverviewLink } from '../../shared/Links/apm/TraceOverviewLink'; import { SetupInstructionsLink } from '../../shared/Links/SetupInstructionsLink'; import { ServiceMap } from '../ServiceMap'; -import { ServiceOverview } from '../ServiceOverview'; +import { ServiceInventory } from '../service_inventory'; import { TraceOverview } from '../TraceOverview'; import { AlertingPopoverAndFlyout } from './alerting_popover_flyout'; @@ -37,13 +37,13 @@ function getHomeTabs({ const homeTabs = [ { link: ( - + {i18n.translate('xpack.apm.home.servicesTabLabel', { defaultMessage: 'Services', })} - + ), - render: () => , + render: () => , name: 'services', }, { diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx index d167b6a9a0565..752f9b7fda243 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -10,6 +10,7 @@ import { useTrackPageview } from '../../../../../observability/public'; import { invalidLicenseMessage, isActivePlatinumLicense, + SERVICE_MAP_TIMEOUT_ERROR, } from '../../../../common/service_map'; import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; import { useLicense } from '../../../hooks/useLicense'; @@ -22,6 +23,7 @@ import { Cytoscape } from './Cytoscape'; import { getCytoscapeDivStyle } from './cytoscape_options'; import { EmptyBanner } from './EmptyBanner'; import { EmptyPrompt } from './empty_prompt'; +import { TimeoutPrompt } from './timeout_prompt'; import { Popover } from './Popover'; import { useRefDimensions } from './useRefDimensions'; @@ -61,7 +63,7 @@ export function ServiceMap({ serviceName }: ServiceMapProps) { const license = useLicense(); const { urlParams } = useUrlParams(); - const { data = { elements: [] }, status } = useFetcher(() => { + const { data = { elements: [] }, status, error } = useFetcher(() => { // When we don't have a license or a valid license, don't make the request. if (!license || !isActivePlatinumLicense(license)) { return; @@ -109,6 +111,20 @@ export function ServiceMap({ serviceName }: ServiceMapProps) { ); } + if ( + status === FETCH_STATUS.FAILURE && + error && + 'body' in error && + error.body.statusCode === 500 && + error.body.message === SERVICE_MAP_TIMEOUT_ERROR + ) { + return ( + + + + ); + } + return (
+ {i18n.translate('xpack.apm.serviceMap.timeoutPromptTitle', { + defaultMessage: 'Service map timeout', + })} + + } + body={ +

+ {i18n.translate('xpack.apm.serviceMap.timeoutPromptDescription', { + defaultMessage: `Timed out while fetching data for service map. Limit the scope by selecting a smaller time range, or use configuration setting '{configName}' with a reduced value.`, + values: { + configName: isGlobalServiceMap + ? 'xpack.apm.serviceMapFingerprintGlobalBucketSize' + : 'xpack.apm.serviceMapFingerprintBucketSize', + }, + })} +

+ } + actions={} + /> + ); +} + +function ApmSettingsDocLink() { + return ( + + {i18n.translate('xpack.apm.serviceMap.timeoutPrompt.docsLink', { + defaultMessage: 'Learn more about APM settings in the docs', + })} + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/NoServicesMessage.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/NoServicesMessage.test.tsx deleted file mode 100644 index 21681f42d4b52..0000000000000 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/NoServicesMessage.test.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { shallow } from 'enzyme'; -import React from 'react'; -import { NoServicesMessage } from '../NoServicesMessage'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; - -describe('NoServicesMessage', () => { - Object.values(FETCH_STATUS).forEach((status) => { - [true, false].forEach((historicalDataFound) => { - it(`status: ${status} and historicalDataFound: ${historicalDataFound}`, () => { - const wrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); - }); - }); - }); -}); diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/NoServicesMessage.test.tsx.snap b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/NoServicesMessage.test.tsx.snap deleted file mode 100644 index d027422961c99..0000000000000 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/NoServicesMessage.test.tsx.snap +++ /dev/null @@ -1,99 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NoServicesMessage status: failure and historicalDataFound: false 1`] = ``; - -exports[`NoServicesMessage status: failure and historicalDataFound: true 1`] = ``; - -exports[`NoServicesMessage status: loading and historicalDataFound: false 1`] = ``; - -exports[`NoServicesMessage status: loading and historicalDataFound: true 1`] = ``; - -exports[`NoServicesMessage status: pending and historicalDataFound: false 1`] = ` - - } - body={ - -

- Upgrading from a pre-7.x version? Make sure you've also upgraded - your APM Server instance(s) to at least 7.0. -

-

- You may also have old data that needs to be migrated. - - - Learn more by visiting the Kibana Upgrade Assistant - - . -

-
- } - title={ -
- Looks like you don't have any APM services installed. Let's add some! -
- } - titleSize="s" -/> -`; - -exports[`NoServicesMessage status: pending and historicalDataFound: true 1`] = ` - - No services found -
- } - titleSize="s" -/> -`; - -exports[`NoServicesMessage status: success and historicalDataFound: false 1`] = ` - - } - body={ - -

- Upgrading from a pre-7.x version? Make sure you've also upgraded - your APM Server instance(s) to at least 7.0. -

-

- You may also have old data that needs to be migrated. - - - Learn more by visiting the Kibana Upgrade Assistant - - . -

-
- } - title={ -
- Looks like you don't have any APM services installed. Let's add some! -
- } - titleSize="s" -/> -`; - -exports[`NoServicesMessage status: success and historicalDataFound: true 1`] = ` - - No services found -
- } - titleSize="s" -/> -`; diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/service_overview.test.tsx.snap b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/service_overview.test.tsx.snap deleted file mode 100644 index 49f30910ee0f1..0000000000000 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/service_overview.test.tsx.snap +++ /dev/null @@ -1,690 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Service Overview -> View should render empty message, when list is empty and historical data is found 1`] = ` -NodeList [ - - -
- -
- -
- No services found -
-
- -
-
-
- - , -] -`; - -exports[`Service Overview -> View should render getting started message, when list is empty and no historical data is found 1`] = ` -NodeList [ - - -
- -
- -
- Looks like you don't have any APM services installed. Let's add some! -
-
-
-

- Upgrading from a pre-7.x version? Make sure you've also upgraded - your APM Server instance(s) to at least 7.0. -

-

- You may also have old data that needs to be migrated. - - - Learn more by visiting the Kibana Upgrade Assistant - - . -

-
- - - - , -] -`; - -exports[`Service Overview -> View should render services, when list is not empty 1`] = ` -NodeList [ - .c1 { - font-size: 16px; - max-width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.c0 { - width: 100%; -} - -.c0 .apmServiceList__serviceNameTooltip { - width: 100%; -} - -.c0 .apmServiceList__serviceNameTooltip .apmServiceList__serviceNameContainer { - width: calc(100% - 24px); -} - - - -
- Health -
-
- - - - Warning - - - -
- - -
- Name -
-
- - - - - -
- - -
- Environment -
-
- - - - - 2 environments - - - - -
- - -
- Avg. response time -
-
-
-
-
-
- -
-
-
-
- N/A -
-
-
-
-
-
- 0 ms -
-
-
- - -
- Trans. per minute -
-
-
-
-
-
- -
-
-
-
- N/A -
-
-
-
-
-
- 0 tpm -
-
-
- - -
- Error rate % -
-
-
-
-
-
- -
-
-
-
- N/A -
-
-
-
-
-
-
-
- - , - .c1 { - font-size: 16px; - max-width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.c0 { - width: 100%; -} - -.c0 .apmServiceList__serviceNameTooltip { - width: 100%; -} - -.c0 .apmServiceList__serviceNameTooltip .apmServiceList__serviceNameContainer { - width: calc(100% - 24px); -} - - - -
- Health -
-
- - - - Unknown - - - -
- - -
- Name -
-
- - -
-
- go -
- -
-
-
-
- - -
- Environment -
-
- - -
- Avg. response time -
-
-
-
-
-
- -
-
-
-
- N/A -
-
-
-
-
-
- 0 ms -
-
-
- - -
- Trans. per minute -
-
-
-
-
-
- -
-
-
-
- N/A -
-
-
-
-
-
- 0 tpm -
-
-
- - -
- Error rate % -
-
-
-
-
-
- -
-
-
-
- N/A -
-
-
-
-
-
-
-
- - , -] -`; diff --git a/x-pack/plugins/apm/public/components/app/Settings/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/index.tsx index aa71050802215..e770116ac2759 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/index.tsx @@ -36,8 +36,8 @@ export function Settings({ children, location }: SettingsProps) { <> - {i18n.translate('xpack.apm.settings.returnToOverviewLinkLabel', { - defaultMessage: 'Return to overview', + {i18n.translate('xpack.apm.settings.returnLinkLabel', { + defaultMessage: 'Return to inventory', })} diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/HealthBadge.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/HealthBadge.tsx rename to x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/MLCallout.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/MLCallout.tsx similarity index 96% rename from x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/MLCallout.tsx rename to x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/MLCallout.tsx index dd632db0f15fe..67eb22d48aae6 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/MLCallout.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/MLCallout.tsx @@ -15,7 +15,7 @@ import { APMLink } from '../../../shared/Links/apm/APMLink'; export function MLCallout({ onDismiss }: { onDismiss: () => void }) { return ( - {i18n.translate('xpack.apm.serviceOverview.toastText', { + {i18n.translate('xpack.apm.serviceInventory.toastText', { defaultMessage: "You're running Elastic Stack 7.0+ and we've detected incompatible data from a previous 6.x version. If you want to view this data in APM, you should migrate it. See more in ", })} @@ -74,7 +74,7 @@ export function ServiceOverview() { })} > {i18n.translate( - 'xpack.apm.serviceOverview.upgradeAssistantLink', + 'xpack.apm.serviceInventory.upgradeAssistantLinkText', { defaultMessage: 'the upgrade assistant', } @@ -86,6 +86,10 @@ export function ServiceOverview() { } }, [data.hasLegacyData, core.http.basePath, core.notifications.toasts]); + // The page is called "service inventory" to avoid confusion with the + // "service overview", but this is tracked in some dashboards because it's the + // initial landing page for APM, so it stays as "services_overview" (plural.) + // for backward compatibility. useTrackPageview({ app: 'apm', path: 'services_overview' }); useTrackPageview({ app: 'apm', path: 'services_overview', delay: 15000 }); diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx new file mode 100644 index 0000000000000..0fc2a2b4cdcef --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { render } from '@testing-library/react'; +import React, { ReactNode } from 'react'; +import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { NoServicesMessage } from './no_services_message'; + +function Wrapper({ children }: { children?: ReactNode }) { + return {children}; +} + +describe('NoServicesMessage', () => { + Object.values(FETCH_STATUS).forEach((status) => { + [true, false].forEach((historicalDataFound) => { + describe(`when status is ${status}`, () => { + describe(`when historicalDataFound is ${historicalDataFound}`, () => { + it('renders', () => { + expect(() => + render( + , + { wrapper: Wrapper } + ) + ).not.toThrowError(); + }); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/NoServicesMessage.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/ServiceOverview/NoServicesMessage.tsx rename to x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/service_overview.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx similarity index 79% rename from x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/service_overview.test.tsx rename to x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx index 06e9008d5aebe..d394c7db62554 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/service_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx @@ -7,22 +7,22 @@ import { render, waitFor } from '@testing-library/react'; import { CoreStart } from 'kibana/public'; import { merge } from 'lodash'; -import React, { FunctionComponent, ReactChild } from 'react'; +import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; -import { ServiceHealthStatus } from '../../../../../common/service_health_status'; -import { ServiceOverview } from '..'; -import { EuiThemeProvider } from '../../../../../../observability/public'; -import { ApmPluginContextValue } from '../../../../context/ApmPluginContext'; +import { ServiceHealthStatus } from '../../../../common/service_health_status'; +import { ServiceInventory } from '.'; +import { EuiThemeProvider } from '../../../../../observability/public'; +import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../../../../context/ApmPluginContext/MockApmPluginContext'; -import * as useAnomalyDetectionJobs from '../../../../hooks/useAnomalyDetectionJobs'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; -import * as useLocalUIFilters from '../../../../hooks/useLocalUIFilters'; -import * as urlParamsHooks from '../../../../hooks/useUrlParams'; -import { SessionStorageMock } from '../../../../services/__test__/SessionStorageMock'; +} from '../../../context/ApmPluginContext/MockApmPluginContext'; +import * as useAnomalyDetectionJobs from '../../../hooks/useAnomalyDetectionJobs'; +import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import * as useLocalUIFilters from '../../../hooks/useLocalUIFilters'; +import * as urlParamsHooks from '../../../hooks/useUrlParams'; +import { SessionStorageMock } from '../../../services/__test__/SessionStorageMock'; const KibanaReactContext = createKibanaReactContext({ usageCollection: { reportUiStats: () => {} }, @@ -31,7 +31,7 @@ const KibanaReactContext = createKibanaReactContext({ const addWarning = jest.fn(); const httpGet = jest.fn(); -function wrapper({ children }: { children: ReactChild }) { +function wrapper({ children }: { children?: ReactNode }) { const mockPluginContext = (merge({}, mockApmPluginContextValue, { core: { http: { @@ -58,13 +58,7 @@ function wrapper({ children }: { children: ReactChild }) { ); } -function renderServiceOverview() { - return render(, { wrapper } as { - wrapper: FunctionComponent<{}>; - }); -} - -describe('Service Overview -> View', () => { +describe('ServiceInventory', () => { beforeEach(() => { // @ts-expect-error global.sessionStorage = new SessionStorageMock(); @@ -129,13 +123,13 @@ describe('Service Overview -> View', () => { ], }); - const { container, findByText } = renderServiceOverview(); + const { container, findByText } = render(, { wrapper }); // wait for requests to be made await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1)); await findByText('My Python Service'); - expect(container.querySelectorAll('.euiTableRow')).toMatchSnapshot(); + expect(container.querySelectorAll('.euiTableRow')).toHaveLength(2); }); it('should render getting started message, when list is empty and no historical data is found', async () => { @@ -145,17 +139,17 @@ describe('Service Overview -> View', () => { items: [], }); - const { container, findByText } = renderServiceOverview(); + const { findByText } = render(, { wrapper }); // wait for requests to be made await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1)); // wait for elements to be rendered - await findByText( + const gettingStartedMessage = await findByText( "Looks like you don't have any APM services installed. Let's add some!" ); - expect(container.querySelectorAll('.euiTableRow')).toMatchSnapshot(); + expect(gettingStartedMessage).not.toBeEmpty(); }); it('should render empty message, when list is empty and historical data is found', async () => { @@ -165,13 +159,13 @@ describe('Service Overview -> View', () => { items: [], }); - const { container, findByText } = renderServiceOverview(); + const { findByText } = render(, { wrapper }); // wait for requests to be made await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1)); - await findByText('No services found'); + const noServicesText = await findByText('No services found'); - expect(container.querySelectorAll('.euiTableRow')).toMatchSnapshot(); + expect(noServicesText).not.toBeEmpty(); }); describe('when legacy data is found', () => { @@ -182,7 +176,7 @@ describe('Service Overview -> View', () => { items: [], }); - renderServiceOverview(); + render(, { wrapper }); // wait for requests to be made await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1)); @@ -203,7 +197,7 @@ describe('Service Overview -> View', () => { items: [], }); - renderServiceOverview(); + render(, { wrapper }); // wait for requests to be made await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1)); @@ -229,7 +223,7 @@ describe('Service Overview -> View', () => { ], }); - const { queryByText } = renderServiceOverview(); + const { queryByText } = render(, { wrapper }); // wait for requests to be made await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1)); @@ -256,7 +250,7 @@ describe('Service Overview -> View', () => { ], }); - const { queryAllByText } = renderServiceOverview(); + const { queryAllByText } = render(, { wrapper }); // wait for requests to be made await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1)); diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx similarity index 90% rename from x-pack/plugins/apm/public/components/shared/Links/apm/ServiceOverviewLink.tsx rename to x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx index 2081fc4767903..e3fa03a4d4f86 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx @@ -14,7 +14,7 @@ import { APMLink, APMLinkExtendProps } from './APMLink'; import { useUrlParams } from '../../../../hooks/useUrlParams'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -function ServiceOverviewLink(props: APMLinkExtendProps) { +function ServiceInventoryLink(props: APMLinkExtendProps) { const { urlParams } = useUrlParams(); const persistedFilters = pickKeys(urlParams, 'host', 'agentName'); @@ -22,4 +22,4 @@ function ServiceOverviewLink(props: APMLinkExtendProps) { return ; } -export { ServiceOverviewLink }; +export { ServiceInventoryLink }; diff --git a/x-pack/plugins/apm/public/hooks/useFetcher.tsx b/x-pack/plugins/apm/public/hooks/useFetcher.tsx index 5d65424844c5a..6add0e8a2b480 100644 --- a/x-pack/plugins/apm/public/hooks/useFetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/useFetcher.tsx @@ -21,7 +21,7 @@ export enum FETCH_STATUS { export interface FetcherResult { data?: Data; status: FETCH_STATUS; - error?: Error; + error?: IHttpFetchError; } // fetcher functions can return undefined OR a promise. Previously we had a more simple type diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index 560a1a077931b..cc0151afba63c 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -81,14 +81,14 @@ export class ApmPlugin implements Plugin { if (plugins.observability) { const getApmDataHelper = async () => { const { - fetchOverviewPageData, + fetchObservabilityOverviewPageData, hasData, createCallApmApi, - } = await import('./services/rest/apm_overview_fetchers'); + } = await import('./services/rest/apm_observability_overview_fetchers'); // have to do this here as well in case app isn't mounted yet createCallApmApi(core.http); - return { fetchOverviewPageData, hasData }; + return { fetchObservabilityOverviewPageData, hasData }; }; plugins.observability.dashboard.register({ appName: 'apm', @@ -98,7 +98,7 @@ export class ApmPlugin implements Plugin { }, fetchData: async (params: FetchDataParams) => { const dataHelper = await getApmDataHelper(); - return await dataHelper.fetchOverviewPageData(params); + return await dataHelper.fetchObservabilityOverviewPageData(params); }, }); diff --git a/x-pack/plugins/apm/public/services/rest/apm_overview_fetchers.test.ts b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.test.ts similarity index 89% rename from x-pack/plugins/apm/public/services/rest/apm_overview_fetchers.test.ts rename to x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.test.ts index 4e306c93805d0..22ec317fca64b 100644 --- a/x-pack/plugins/apm/public/services/rest/apm_overview_fetchers.test.ts +++ b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.test.ts @@ -5,7 +5,10 @@ */ import moment from 'moment'; -import { fetchOverviewPageData, hasData } from './apm_overview_fetchers'; +import { + fetchObservabilityOverviewPageData, + hasData, +} from './apm_observability_overview_fetchers'; import * as createCallApmApi from './createCallApmApi'; describe('Observability dashboard data', () => { @@ -37,7 +40,7 @@ describe('Observability dashboard data', () => { }); }); - describe('fetchOverviewPageData', () => { + describe('fetchObservabilityOverviewPageData', () => { it('returns APM data with series and stats', async () => { callApmApiMock.mockImplementation(() => Promise.resolve({ @@ -49,7 +52,7 @@ describe('Observability dashboard data', () => { ], }) ); - const response = await fetchOverviewPageData(params); + const response = await fetchObservabilityOverviewPageData(params); expect(response).toEqual({ appLink: '/app/apm/services?rangeFrom=now-15m&rangeTo=now', stats: { @@ -80,7 +83,7 @@ describe('Observability dashboard data', () => { transactionCoordinates: [], }) ); - const response = await fetchOverviewPageData(params); + const response = await fetchObservabilityOverviewPageData(params); expect(response).toEqual({ appLink: '/app/apm/services?rangeFrom=now-15m&rangeTo=now', stats: { @@ -107,7 +110,7 @@ describe('Observability dashboard data', () => { transactionCoordinates: [{ x: 1 }, { x: 2 }, { x: 3 }], }) ); - const response = await fetchOverviewPageData(params); + const response = await fetchObservabilityOverviewPageData(params); expect(response).toEqual({ appLink: '/app/apm/services?rangeFrom=now-15m&rangeTo=now', stats: { diff --git a/x-pack/plugins/apm/public/services/rest/apm_overview_fetchers.ts b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts similarity index 96% rename from x-pack/plugins/apm/public/services/rest/apm_overview_fetchers.ts rename to x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts index 422c7b882e5dc..bc1db4eed1d9e 100644 --- a/x-pack/plugins/apm/public/services/rest/apm_overview_fetchers.ts +++ b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts @@ -13,7 +13,7 @@ import { callApmApi } from './createCallApmApi'; export { createCallApmApi } from './createCallApmApi'; -export const fetchOverviewPageData = async ({ +export const fetchObservabilityOverviewPageData = async ({ absoluteTime, relativeTime, bucketSize, diff --git a/x-pack/plugins/apm/public/setHelpExtension.ts b/x-pack/plugins/apm/public/setHelpExtension.ts index b6e745dcf1655..f895fbc36ed03 100644 --- a/x-pack/plugins/apm/public/setHelpExtension.ts +++ b/x-pack/plugins/apm/public/setHelpExtension.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import url from 'url'; import { i18n } from '@kbn/i18n'; import { CoreStart } from 'kibana/public'; @@ -20,10 +19,7 @@ export function setHelpExtension({ chrome, http }: CoreStart) { }, { linkType: 'custom', - href: url.format({ - pathname: http.basePath.prepend('/app/kibana'), - hash: '/management/stack/upgrade_assistant', - }), + href: http.basePath.prepend('/app/management/stack/upgrade_assistant'), content: i18n.translate('xpack.apm.helpMenu.upgradeAssistantLink', { defaultMessage: 'Upgrade assistant', }), diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts index d0673335387c6..73e590064bac0 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/create_anomaly_detection_jobs.ts @@ -7,7 +7,7 @@ import { Logger } from 'kibana/server'; import uuid from 'uuid/v4'; import { snakeCase } from 'lodash'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { ProcessorEvent } from '../../../common/processor_event'; import { ML_ERRORS } from '../../../common/anomaly_detection'; import { PromiseReturnType } from '../../../../observability/typings/common'; diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts index ead0c79a02836..88da6f619d610 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/get_anomaly_detection_jobs.ts @@ -5,7 +5,7 @@ */ import { Logger } from 'kibana/server'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { ML_ERRORS } from '../../../common/anomaly_detection'; import { Setup } from '../helpers/setup_request'; import { getMlJobsWithAPMGroup } from './get_ml_jobs_with_apm_group'; diff --git a/x-pack/plugins/apm/server/lib/anomaly_detection/has_legacy_jobs.ts b/x-pack/plugins/apm/server/lib/anomaly_detection/has_legacy_jobs.ts index c1f346aa30e1f..eca8ad273a53d 100644 --- a/x-pack/plugins/apm/server/lib/anomaly_detection/has_legacy_jobs.ts +++ b/x-pack/plugins/apm/server/lib/anomaly_detection/has_legacy_jobs.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { ML_ERRORS } from '../../../common/anomaly_detection'; import { Setup } from '../helpers/setup_request'; import { getMlJobsWithAPMGroup } from './get_ml_jobs_with_apm_group'; diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts index 895fc70d76af1..19864cb420b82 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { getServiceHealthStatus } from '../../../common/service_health_status'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { PromiseReturnType } from '../../../typings/common'; diff --git a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts index dfc4e02c25a7f..22c655bb96f50 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_trace_sample_ids.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { uniq, take, sortBy } from 'lodash'; +import Boom from '@hapi/boom'; import { ProcessorEvent } from '../../../common/processor_event'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { rangeFilter } from '../../../common/utils/range_filter'; @@ -15,6 +16,7 @@ import { SPAN_DESTINATION_SERVICE_RESOURCE, } from '../../../common/elasticsearch_fieldnames'; import { getEnvironmentUiFilterES } from '../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { SERVICE_MAP_TIMEOUT_ERROR } from '../../../common/service_map'; const MAX_TRACES_TO_INSPECT = 1000; @@ -122,26 +124,30 @@ export async function getTraceSampleIds({ }, }; - const tracesSampleResponse = await apmEventClient.search(params); + try { + const tracesSampleResponse = await apmEventClient.search(params); + // make sure at least one trace per composite/connection bucket + // is queried + const traceIdsWithPriority = + tracesSampleResponse.aggregations?.connections.buckets.flatMap((bucket) => + bucket.sample.trace_ids.buckets.map((sampleDocBucket, index) => ({ + traceId: sampleDocBucket.key as string, + priority: index, + })) + ) || []; - // make sure at least one trace per composite/connection bucket - // is queried - const traceIdsWithPriority = - tracesSampleResponse.aggregations?.connections.buckets.flatMap((bucket) => - bucket.sample.trace_ids.buckets.map((sampleDocBucket, index) => ({ - traceId: sampleDocBucket.key as string, - priority: index, - })) - ) || []; + const traceIds = take( + uniq( + sortBy(traceIdsWithPriority, 'priority').map(({ traceId }) => traceId) + ), + MAX_TRACES_TO_INSPECT + ); - const traceIds = take( - uniq( - sortBy(traceIdsWithPriority, 'priority').map(({ traceId }) => traceId) - ), - MAX_TRACES_TO_INSPECT - ); - - return { - traceIds, - }; + return { traceIds }; + } catch (error) { + if ('displayName' in error && error.displayName === 'RequestTimeout') { + throw Boom.internal(SERVICE_MAP_TIMEOUT_ERROR); + } + throw error; + } } diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts index 7afb7427c210f..e5d6aad693869 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts @@ -58,12 +58,10 @@ export function getTpmBuckets({ const buckets = transactionResultBuckets.map( ({ key: resultKey, timeseries }) => { const dataPoints = timeseries.buckets.map((bucket) => { - // calculate request/minute. Avoid up-scaling numbers if bucketSize is below 60s (1 minute). - // Eg. 1 request during a 10 second window should be displayed as "1 rpm" instead of "6 rpm". - const tmpValue = bucket.count.value * (60 / Math.max(60, bucketSize)); return { x: bucket.key, - y: tmpValue, + // divide by minutes + y: bucket.count.value / (bucketSize / 60), }; }); diff --git a/x-pack/plugins/apm/server/routes/create_api/index.ts b/x-pack/plugins/apm/server/routes/create_api/index.ts index 42eebc51463db..cecb4f6ed3367 100644 --- a/x-pack/plugins/apm/server/routes/create_api/index.ts +++ b/x-pack/plugins/apm/server/routes/create_api/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { pick, difference } from 'lodash'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { schema } from '@kbn/config-schema'; import * as t from 'io-ts'; import { PathReporter } from 'io-ts/lib/PathReporter'; diff --git a/x-pack/plugins/apm/server/routes/service_map.ts b/x-pack/plugins/apm/server/routes/service_map.ts index 6e86ececd1bfe..ffc8cb84b690a 100644 --- a/x-pack/plugins/apm/server/routes/service_map.ts +++ b/x-pack/plugins/apm/server/routes/service_map.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import * as t from 'io-ts'; import { invalidLicenseMessage, diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index 5673aa123b6aa..590b6c49d71bf 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -5,7 +5,7 @@ */ import * as t from 'io-ts'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { uniq } from 'lodash'; import { setupRequest } from '../lib/helpers/setup_request'; import { getServiceAgentName } from '../lib/services/get_service_agent_name'; diff --git a/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts b/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts index beab6b6c850e3..7ed5ef442b6fc 100644 --- a/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts +++ b/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts @@ -5,7 +5,7 @@ */ import * as t from 'io-ts'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { setupRequest } from '../../lib/helpers/setup_request'; import { getServiceNames } from '../../lib/settings/agent_configuration/get_service_names'; import { createOrUpdateConfiguration } from '../../lib/settings/agent_configuration/create_or_update_configuration'; diff --git a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts index f0a22356d074b..3e5a9ee725991 100644 --- a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts +++ b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts @@ -5,7 +5,7 @@ */ import * as t from 'io-ts'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { isActivePlatinumLicense } from '../../../common/service_map'; import { ML_ERRORS } from '../../../common/anomaly_detection'; import { createRoute } from '../create_route'; diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link.ts b/x-pack/plugins/apm/server/routes/settings/custom_link.ts index 7882383d78ab0..33769ac1d1c6f 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_link.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_link.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import * as t from 'io-ts'; import { pick } from 'lodash'; import { INVALID_LICENSE } from '../../../common/custom_link'; diff --git a/x-pack/plugins/apm/server/routes/transaction_groups.ts b/x-pack/plugins/apm/server/routes/transaction_groups.ts index 18fc73b468cd4..a3a73222210bb 100644 --- a/x-pack/plugins/apm/server/routes/transaction_groups.ts +++ b/x-pack/plugins/apm/server/routes/transaction_groups.ts @@ -5,7 +5,7 @@ */ import * as t from 'io-ts'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { setupRequest } from '../lib/helpers/setup_request'; import { getTransactionCharts } from '../lib/transactions/charts'; import { getTransactionDistribution } from '../lib/transactions/distribution'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js index d7c7cd9e1a32f..9bdb66cc16c06 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js @@ -131,6 +131,7 @@ class ImageUpload extends React.Component { onChange={this.changeUrlType} isFullWidth className="canvasSidebar__buttonGroup" + legend={strings.getUrlTypeChangeLegend()} /> ); diff --git a/x-pack/plugins/canvas/i18n/components.ts b/x-pack/plugins/canvas/i18n/components.ts index 51c86f6604330..b7d25a32818f8 100644 --- a/x-pack/plugins/canvas/i18n/components.ts +++ b/x-pack/plugins/canvas/i18n/components.ts @@ -870,6 +870,10 @@ export const ComponentStrings = { i18n.translate('xpack.canvas.textStylePicker.alignRightOption', { defaultMessage: 'Align right', }), + getAlignmentOptionsControlLegend: () => + i18n.translate('xpack.canvas.textStylePicker.alignmentOptionsControl', { + defaultMessage: 'Alignment options', + }), getFontColorLabel: () => i18n.translate('xpack.canvas.textStylePicker.fontColorLabel', { defaultMessage: 'Font Color', @@ -886,6 +890,10 @@ export const ComponentStrings = { i18n.translate('xpack.canvas.textStylePicker.styleUnderlineOption', { defaultMessage: 'Underline', }), + getStyleOptionsControlLegend: () => + i18n.translate('xpack.canvas.textStylePicker.styleOptionsControl', { + defaultMessage: 'Style options', + }), }, TimePicker: { getApplyButtonLabel: () => @@ -1061,6 +1069,10 @@ export const ComponentStrings = { }), }, VarConfigVarValueField: { + getBooleanOptionsLegend: () => + i18n.translate('xpack.canvas.varConfigVarValueField.booleanOptionsLegend', { + defaultMessage: 'Boolean value', + }), getFalseOption: () => i18n.translate('xpack.canvas.varConfigVarValueField.falseOption', { defaultMessage: 'False', diff --git a/x-pack/plugins/canvas/i18n/ui.ts b/x-pack/plugins/canvas/i18n/ui.ts index bc282db203be2..0347a9c5444aa 100644 --- a/x-pack/plugins/canvas/i18n/ui.ts +++ b/x-pack/plugins/canvas/i18n/ui.ts @@ -181,6 +181,10 @@ export const ArgumentStrings = { url: URL, }, }), + getUrlTypeChangeLegend: () => + i18n.translate('xpack.canvas.uis.arguments.imageUpload.urlTypes.changeLegend', { + defaultMessage: 'Image upload type', + }), }, Number: { getDisplayName: () => diff --git a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot index 8225f6414385d..a6dbe675ee9d6 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/asset_manager/__stories__/__snapshots__/asset_manager.stories.storyshot @@ -146,6 +146,7 @@ exports[`Storyshots components/Assets/AssetManager no assets 1`] = ` aria-labelledby="CanvasAssetManagerLabel" className="euiProgress euiProgress--native euiProgress--s euiProgress--secondary" max={25000} + style={null} value={0} />
@@ -163,6 +164,11 @@ exports[`Storyshots components/Assets/AssetManager no assets 1`] = `
@@ -654,6 +661,11 @@ exports[`Storyshots components/Assets/AssetManager two assets 1`] = ` -
-
+ + -
-
+ + -
+ +
@@ -366,110 +354,122 @@ exports[`Storyshots components/TextStylePicker default 1`] = ` className="euiFlexItem euiFlexItem--flexGrowZero" >
+ + Alignment options +
-
- - -
-
+ +
-
+ +
+ +
@@ -733,110 +733,98 @@ exports[`Storyshots components/TextStylePicker interactive 1`] = ` className="euiFlexItem euiFlexItem--flexGrowZero" >
+ + Style options +
-
- - -
-
+ + -
-
+ + -
+ +
@@ -844,110 +832,122 @@ exports[`Storyshots components/TextStylePicker interactive 1`] = ` className="euiFlexItem euiFlexItem--flexGrowZero" >
+ + Alignment options +
-
- - -
-
+ +
-
+ +
+ +
diff --git a/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx b/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx index c501e78a5e338..7371eacd92fd1 100644 --- a/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx +++ b/x-pack/plugins/canvas/public/components/text_style_picker/text_style_picker.tsx @@ -162,6 +162,7 @@ export const TextStylePicker: FC = ({ type="multi" isIconOnly className="canvasSidebar__buttonGroup" + legend={strings.getStyleOptionsControlLegend()} /> @@ -172,6 +173,7 @@ export const TextStylePicker: FC = ({ idSelected={align} onChange={(optionId: string) => doChange('align', optionId)} className="canvasSidebar__buttonGroup" + legend={strings.getAlignmentOptionsControlLegend()} /> diff --git a/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/delete_var.stories.storyshot b/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/delete_var.stories.storyshot index f1fac7e6ce477..5c9e0b6224126 100644 --- a/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/delete_var.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/var_config/__stories__/__snapshots__/delete_var.stories.storyshot @@ -64,6 +64,11 @@ Array [ -
-
+ +
+ + @@ -307,6 +315,11 @@ Array [ className="euiButton euiButton--secondary euiButton--small euiButton--fill" disabled={false} onClick={[Function]} + style={ + Object { + "minWidth": undefined, + } + } type="button" > = ({ type, value, onChange }) => { }} buttonSize="compressed" isFullWidth + legend={strings.getBooleanOptionsLegend()} /> ); } diff --git a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/__stories__/__snapshots__/element_menu.stories.storyshot b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/__stories__/__snapshots__/element_menu.stories.storyshot index 6bce2be335b78..de78772bcb124 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/__stories__/__snapshots__/element_menu.stories.storyshot +++ b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/__stories__/__snapshots__/element_menu.stories.storyshot @@ -14,9 +14,14 @@ exports[`Storyshots components/WorkpadHeader/ElementMenu default 1`] = ` >
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with height specified 1`] = `"
"`; @@ -21,7 +21,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with height specified
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with page specified 1`] = `"
"`; @@ -33,7 +33,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with page specified 2`
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with width and height specified 1`] = `"
"`; @@ -45,7 +45,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with width and height
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with width specified 1`] = `"
"`; @@ -57,5 +57,5 @@ exports[`Canvas Shareable Workpad API Placed successfully with width specified 2
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot index 73d350a9c1ee1..7d7b2a0ae9341 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot +++ b/x-pack/plugins/canvas/shareable_runtime/components/__stories__/__snapshots__/canvas.stories.storyshot @@ -1445,7 +1445,7 @@ exports[`Storyshots shareables/Canvas component 1`] = ` className="euiFlexItem euiFlexItem--flexGrowZero" >
App renders properly 1`] = `
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/__stories__/__snapshots__/footer.stories.storyshot b/x-pack/plugins/canvas/shareable_runtime/components/footer/__stories__/__snapshots__/footer.stories.storyshot index 2fe222b4238a3..28c01c3fba8e5 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/footer/__stories__/__snapshots__/footer.stories.storyshot +++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/__stories__/__snapshots__/footer.stories.storyshot @@ -1398,7 +1398,7 @@ exports[`Storyshots shareables/Footer contextual: austin 1`] = ` className="euiFlexItem euiFlexItem--flexGrowZero" >
can navigate Autoplay Settings 1`] = `
"`; +exports[` can navigate Toolbar Settings, closes when activated 3`] = `"
Settings
Hide Toolbar
Hide the toolbar when the mouse is not within the Canvas?
"`; diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/settings.tsx b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/settings.tsx index 4d827da0c3639..4e67b1c67a27f 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/settings.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/settings.tsx @@ -81,7 +81,6 @@ export const SettingsComponent: FC = ({ refs }) => { isOpen={isPopoverOpen} button={button} panelPaddingSize="none" - withTitle anchorPosition="upRight" insert={ refs.stage.current ? { sibling: refs.stage.current, position: 'after' } : undefined diff --git a/x-pack/plugins/case/server/client/cases/create.ts b/x-pack/plugins/case/server/client/cases/create.ts index 3379099419a75..59222be062c75 100644 --- a/x-pack/plugins/case/server/client/cases/create.ts +++ b/x-pack/plugins/case/server/client/cases/create.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; diff --git a/x-pack/plugins/case/server/client/cases/update.ts b/x-pack/plugins/case/server/client/cases/update.ts index 424f51ee40f08..a754ce27c5e41 100644 --- a/x-pack/plugins/case/server/client/cases/update.ts +++ b/x-pack/plugins/case/server/client/cases/update.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; diff --git a/x-pack/plugins/case/server/client/comments/add.ts b/x-pack/plugins/case/server/client/comments/add.ts index 765eb2c873765..a95b7833a5232 100644 --- a/x-pack/plugins/case/server/client/comments/add.ts +++ b/x-pack/plugins/case/server/client/comments/add.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts b/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts index 70c0d8c2f84f9..c7770810d172f 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/delete_comment.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { schema } from '@kbn/config-schema'; import { CASE_SAVED_OBJECT } from '../../../../saved_object_types'; diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/find_comments.ts b/x-pack/plugins/case/server/routes/api/cases/comments/find_comments.ts index 3df9fdb80ba8a..a4b89353ef3fe 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/find_comments.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/find_comments.ts @@ -5,7 +5,7 @@ */ import { schema } from '@kbn/config-schema'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts index 61d7382fca808..e75e89fa207b9 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/patch_comment.ts @@ -5,7 +5,7 @@ */ import { schema } from '@kbn/config-schema'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts index c3e565a404e97..547e67379ad6c 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { RouteDeps } from '../../types'; import { wrapError } from '../../utils'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.ts b/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.ts index d3a64559e5a2b..b3305a2c0b8e4 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts b/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts index 48b4582b2447b..97856c84d60fc 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; diff --git a/x-pack/plugins/case/server/routes/api/cases/find_cases.ts b/x-pack/plugins/case/server/routes/api/cases/find_cases.ts index 4cdafca1cc9e7..e70225456d5a8 100644 --- a/x-pack/plugins/case/server/routes/api/cases/find_cases.ts +++ b/x-pack/plugins/case/server/routes/api/cases/find_cases.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; diff --git a/x-pack/plugins/case/server/routes/api/cases/push_case.ts b/x-pack/plugins/case/server/routes/api/cases/push_case.ts index 11e501bf8f71f..80b65b54468fc 100644 --- a/x-pack/plugins/case/server/routes/api/cases/push_case.ts +++ b/x-pack/plugins/case/server/routes/api/cases/push_case.ts @@ -5,7 +5,7 @@ */ import { schema } from '@kbn/config-schema'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import isEmpty from 'lodash/isEmpty'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; diff --git a/x-pack/plugins/case/server/routes/api/utils.test.ts b/x-pack/plugins/case/server/routes/api/utils.test.ts index bb38fae35ad50..00584a9d7431f 100644 --- a/x-pack/plugins/case/server/routes/api/utils.test.ts +++ b/x-pack/plugins/case/server/routes/api/utils.test.ts @@ -17,7 +17,7 @@ import { sortToSnake, } from './utils'; import { newCase } from './__mocks__/request_responses'; -import { isBoom, boomify } from 'boom'; +import { isBoom, boomify } from '@hapi/boom'; import { mockCases, mockCaseComments, diff --git a/x-pack/plugins/case/server/routes/api/utils.ts b/x-pack/plugins/case/server/routes/api/utils.ts index 90066bf29ae66..3f82dac96a70e 100644 --- a/x-pack/plugins/case/server/routes/api/utils.ts +++ b/x-pack/plugins/case/server/routes/api/utils.ts @@ -5,7 +5,7 @@ */ import { schema } from '@kbn/config-schema'; -import { boomify, isBoom } from 'boom'; +import { boomify, isBoom } from '@hapi/boom'; import { CustomHttpResponseOptions, ResponseError, diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx index ce26e4e71a5db..7edb6276b8158 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_action_menu/auto_follow_pattern_action_menu.tsx @@ -143,7 +143,6 @@ const AutoFollowPatternActionMenuUI: FunctionComponent = ({ closePopover={() => setShowPopover(false)} button={button} panelPaddingSize="none" - withTitle repositionOnScroll > diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts index d0c597532f6ed..e1d8a2b3671a2 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts @@ -12,8 +12,9 @@ import { } from '../../../../../../../src/plugins/ui_actions/public'; /** - * We know that VALUE_CLICK_TRIGGER and SELECT_RANGE_TRIGGER are also triggering APPLY_FILTER_TRIGGER - * This function appends APPLY_FILTER_TRIGGER to list of triggers if VALUE_CLICK_TRIGGER or SELECT_RANGE_TRIGGER + * We know that VALUE_CLICK_TRIGGER and SELECT_RANGE_TRIGGER are also triggering APPLY_FILTER_TRIGGER. + * This function appends APPLY_FILTER_TRIGGER to the list of triggers if either VALUE_CLICK_TRIGGER + * or SELECT_RANGE_TRIGGER was executed. * * TODO: this probably should be part of uiActions infrastructure, * but dynamic implementation of nested trigger doesn't allow to statically express such relations diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx index d5547ff8097cc..ff54e0812975d 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx @@ -129,7 +129,7 @@ describe('isCompatible', () => { }); }); - test('not compatible if no triggers intersection', async () => { + test('not compatible if no triggers intersect', async () => { await assertNonCompatibility({ actionFactoriesTriggers: [], }); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx index a2192808c2d40..a417deb47db53 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx @@ -10,9 +10,12 @@ import { ActionByType } from '../../../../../../../../src/plugins/ui_actions/pub import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public'; import { isEnhancedEmbeddable, - embeddableEnhancedContextMenuDrilldownGrouping, + embeddableEnhancedDrilldownGrouping, } from '../../../../../../embeddable_enhanced/public'; -import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public'; +import { + CONTEXT_MENU_TRIGGER, + EmbeddableContext, +} from '../../../../../../../../src/plugins/embeddable/public'; import { StartDependencies } from '../../../../plugin'; import { StartServicesGetter } from '../../../../../../../../src/plugins/kibana_utils/public'; import { ensureNestedTriggers } from '../drilldown_shared'; @@ -27,7 +30,7 @@ export class FlyoutCreateDrilldownAction implements ActionByType handle.close()} viewMode={'create'} dynamicActionManager={embeddable.enhancements.dynamicActions} - triggers={ensureNestedTriggers(embeddable.supportedTriggers())} + triggers={[...ensureNestedTriggers(embeddable.supportedTriggers()), CONTEXT_MENU_TRIGGER]} placeContext={{ embeddable }} /> ), diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx index 56ef25005078b..1f0570445a8fc 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx @@ -10,12 +10,16 @@ import { reactToUiComponent, toMountPoint, } from '../../../../../../../../src/plugins/kibana_react/public'; -import { EmbeddableContext, ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; +import { + EmbeddableContext, + ViewMode, + CONTEXT_MENU_TRIGGER, +} from '../../../../../../../../src/plugins/embeddable/public'; import { txtDisplayName } from './i18n'; import { MenuItem } from './menu_item'; import { isEnhancedEmbeddable, - embeddableEnhancedContextMenuDrilldownGrouping, + embeddableEnhancedDrilldownGrouping, } from '../../../../../../embeddable_enhanced/public'; import { StartDependencies } from '../../../../plugin'; import { StartServicesGetter } from '../../../../../../../../src/plugins/kibana_utils/public'; @@ -31,7 +35,7 @@ export class FlyoutEditDrilldownAction implements ActionByType handle.close()} viewMode={'manage'} dynamicActionManager={embeddable.enhancements.dynamicActions} - triggers={ensureNestedTriggers(embeddable.supportedTriggers())} + triggers={[...ensureNestedTriggers(embeddable.supportedTriggers()), CONTEXT_MENU_TRIGGER]} placeContext={{ embeddable }} /> ), diff --git a/x-pack/plugins/data_enhanced/README.md b/x-pack/plugins/data_enhanced/README.md new file mode 100644 index 0000000000000..8f3ae7ac3cd13 --- /dev/null +++ b/x-pack/plugins/data_enhanced/README.md @@ -0,0 +1,25 @@ +# data_enhanced + +The `data_enhanced` plugin is the x-pack counterpart to the OSS `data` plugin. + +It exists to provide Elastic-licensed services, or parts of services, which +enhance existing OSS functionality from `data`. + +Currently the `data_enhanced` plugin doesn't return any APIs which you can +consume directly, however it is possible that you are indirectly relying on the +enhanced functionality that it provides via the OSS `data` plugin. + +Here is the functionality it adds: + +## KQL Autocomplete + +The OSS autocomplete service provides suggestions for field names and values +based on suggestion providers which are registered to the service. This plugin +registers the autocomplete provider for KQL to the OSS service. + +## Async, Rollup, and EQL Search Strategies + +This plugin enhances the OSS search service with an ES search strategy that +uses async search (or rollups) behind the scenes. It also registers an EQL +search strategy. + diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts index aee32a7c62759..3226ecf6f0dda 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts @@ -64,7 +64,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { abortSignal: options.abortSignal, timeout: this.searchTimeout, }); - const aborted$ = from(toPromise(combinedSignal)); + const abortedPromise = toPromise(combinedSignal); const strategy = options?.strategy || ENHANCED_ES_SEARCH_STRATEGY; this.pendingCount$.next(this.pendingCount$.getValue() + 1); @@ -90,7 +90,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { }) ); }), - takeUntil(aborted$), + takeUntil(from(abortedPromise.promise)), catchError((e: any) => { // If we haven't received the response to the initial request, including the ID, then // we don't need to send a follow-up request to delete this search. Otherwise, we @@ -103,6 +103,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { finalize(() => { this.pendingCount$.next(this.pendingCount$.getValue() - 1); cleanup(); + abortedPromise.cleanup(); }) ); } diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx index 85e92d0827daa..807dfeed21d1f 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx @@ -6,7 +6,11 @@ import React from 'react'; import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public'; -import { ChartActionContext, IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; +import { + ChartActionContext, + CONTEXT_MENU_TRIGGER, + IEmbeddable, +} from '../../../../../../src/plugins/embeddable/public'; import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../../src/plugins/kibana_utils/public'; import { SELECT_RANGE_TRIGGER, @@ -34,7 +38,10 @@ interface UrlDrilldownDeps { export type ActionContext = ChartActionContext; export type Config = UrlDrilldownConfig; -export type UrlTrigger = typeof VALUE_CLICK_TRIGGER | typeof SELECT_RANGE_TRIGGER; +export type UrlTrigger = + | typeof CONTEXT_MENU_TRIGGER + | typeof VALUE_CLICK_TRIGGER + | typeof SELECT_RANGE_TRIGGER; export interface ActionFactoryContext extends BaseActionFactoryContext { embeddable?: IEmbeddable; } @@ -58,7 +65,7 @@ export class UrlDrilldown implements Drilldown = ({ diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts index 6989819da2b0b..a93e150deee8f 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts @@ -87,25 +87,25 @@ describe('VALUE_CLICK_TRIGGER', () => { ]) as ValueClickTriggerEventScope; expect(mockEventScope.points.length).toBeGreaterThan(3); expect(mockEventScope.points).toMatchInlineSnapshot(` - Array [ - Object { - "key": "event.points.0.key", - "value": "event.points.0.value", - }, - Object { - "key": "event.points.1.key", - "value": "event.points.1.value", - }, - Object { - "key": "event.points.2.key", - "value": "event.points.2.value", - }, - Object { - "key": "event.points.3.key", - "value": "event.points.3.value", - }, - ] - `); + Array [ + Object { + "key": "event.points.0.key", + "value": "event.points.0.value", + }, + Object { + "key": "event.points.1.key", + "value": "event.points.1.value", + }, + Object { + "key": "event.points.2.key", + "value": "event.points.2.value", + }, + Object { + "key": "event.points.3.key", + "value": "event.points.3.value", + }, + ] + `); }); }); @@ -130,3 +130,12 @@ describe('VALUE_CLICK_TRIGGER', () => { }); }); }); + +describe('CONTEXT_MENU_TRIGGER', () => { + test('getMockEventScope() results in empty scope', () => { + const mockEventScope = getMockEventScope([ + 'CONTEXT_MENU_TRIGGER', + ]) as ValueClickTriggerEventScope; + expect(mockEventScope).toEqual({}); + }); +}); diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts index 0f66cb144c967..234af380689e9 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts @@ -14,11 +14,15 @@ import { IEmbeddable, isRangeSelectTriggerContext, isValueClickTriggerContext, + isContextMenuTriggerContext, RangeSelectContext, ValueClickContext, } from '../../../../../../src/plugins/embeddable/public'; import type { ActionContext, ActionFactoryContext, UrlTrigger } from './url_drilldown'; -import { SELECT_RANGE_TRIGGER } from '../../../../../../src/plugins/ui_actions/public'; +import { + SELECT_RANGE_TRIGGER, + VALUE_CLICK_TRIGGER, +} from '../../../../../../src/plugins/ui_actions/public'; type ContextScopeInput = ActionContext | ActionFactoryContext; @@ -101,7 +105,10 @@ export function getContextScope(contextScopeInput: ContextScopeInput): UrlDrilld * URL drilldown event scope, * available as {{event.$}} */ -export type UrlDrilldownEventScope = ValueClickTriggerEventScope | RangeSelectTriggerEventScope; +export type UrlDrilldownEventScope = + | ValueClickTriggerEventScope + | RangeSelectTriggerEventScope + | ContextMenuTriggerEventScope; export type EventScopeInput = ActionContext; export interface ValueClickTriggerEventScope { key?: string; @@ -115,11 +122,15 @@ export interface RangeSelectTriggerEventScope { to?: string | number; } +export type ContextMenuTriggerEventScope = object; + export function getEventScope(eventScopeInput: EventScopeInput): UrlDrilldownEventScope { if (isRangeSelectTriggerContext(eventScopeInput)) { return getEventScopeFromRangeSelectTriggerContext(eventScopeInput); } else if (isValueClickTriggerContext(eventScopeInput)) { return getEventScopeFromValueClickTriggerContext(eventScopeInput); + } else if (isContextMenuTriggerContext(eventScopeInput)) { + return {}; } else { throw new Error("UrlDrilldown [getEventScope] can't build scope from not supported trigger"); } @@ -169,7 +180,9 @@ export function getMockEventScope([trigger]: UrlTrigger[]): UrlDrilldownEventSco from: new Date(Date.now() - 15 * 60 * 1000).toISOString(), // 15 minutes ago to: new Date().toISOString(), }; - } else { + } + + if (trigger === VALUE_CLICK_TRIGGER) { // number of mock points to generate // should be larger or equal of any possible data points length emitted by VALUE_CLICK_TRIGGER const nPoints = 4; @@ -184,6 +197,8 @@ export function getMockEventScope([trigger]: UrlTrigger[]): UrlDrilldownEventSco points, }; } + + return {}; } type Primitive = string | number | boolean | null; diff --git a/x-pack/plugins/embeddable_enhanced/public/actions/drilldown_grouping.ts b/x-pack/plugins/embeddable_enhanced/public/actions/drilldown_grouping.ts index 5ea8928532c28..0aa1c0e6f08ae 100644 --- a/x-pack/plugins/embeddable_enhanced/public/actions/drilldown_grouping.ts +++ b/x-pack/plugins/embeddable_enhanced/public/actions/drilldown_grouping.ts @@ -4,15 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; import { IEmbeddable } from '../../../../../src/plugins/embeddable/public'; import { UiActionsPresentableGrouping as PresentableGrouping } from '../../../../../src/plugins/ui_actions/public'; -export const contextMenuDrilldownGrouping: PresentableGrouping<{ +export const drilldownGrouping: PresentableGrouping<{ embeddable?: IEmbeddable; }> = [ { id: 'drilldowns', - getDisplayName: () => 'Drilldowns', + getDisplayName: () => + i18n.translate('xpack.embeddableEnhanced.Drilldowns', { + defaultMessage: 'Drilldowns', + }), getIconType: () => 'symlink', order: 25, }, diff --git a/x-pack/plugins/embeddable_enhanced/public/index.ts b/x-pack/plugins/embeddable_enhanced/public/index.ts index a7916685239df..24f8eb623abe0 100644 --- a/x-pack/plugins/embeddable_enhanced/public/index.ts +++ b/x-pack/plugins/embeddable_enhanced/public/index.ts @@ -20,4 +20,4 @@ export function plugin(context: PluginInitializerContext) { export { EnhancedEmbeddable, EnhancedEmbeddableContext } from './types'; export { isEnhancedEmbeddable } from './embeddables'; -export { contextMenuDrilldownGrouping as embeddableEnhancedContextMenuDrilldownGrouping } from './actions'; +export { drilldownGrouping as embeddableEnhancedDrilldownGrouping } from './actions'; diff --git a/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.test.ts b/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.test.ts index ced4dda48fcd2..acc2d2247efcb 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.test.ts @@ -57,24 +57,24 @@ describe('Key rotation routes', () => { const queryValidator = (routeConfig.validate as any).query as Type; expect( queryValidator.validate({ - batchSize: 100, + batch_size: 100, type: 'some-type', }) ).toEqual({ - batchSize: 100, + batch_size: 100, type: 'some-type', }); - expect(queryValidator.validate({ batchSize: 1 })).toEqual({ batchSize: 1 }); - expect(queryValidator.validate({ batchSize: 10000 })).toEqual({ batchSize: 10000 }); - expect(queryValidator.validate({})).toEqual({ batchSize: 10000 }); + expect(queryValidator.validate({ batch_size: 1 })).toEqual({ batch_size: 1 }); + expect(queryValidator.validate({ batch_size: 10000 })).toEqual({ batch_size: 10000 }); + expect(queryValidator.validate({})).toEqual({ batch_size: 10000 }); - expect(() => queryValidator.validate({ batchSize: 0 })).toThrowErrorMatchingInlineSnapshot( - `"[batchSize]: Value must be equal to or greater than [1]."` + expect(() => queryValidator.validate({ batch_size: 0 })).toThrowErrorMatchingInlineSnapshot( + `"[batch_size]: Value must be equal to or greater than [1]."` ); expect(() => - queryValidator.validate({ batchSize: 10001 }) + queryValidator.validate({ batch_size: 10001 }) ).toThrowErrorMatchingInlineSnapshot( - `"[batchSize]: Value must be equal to or lower than [10000]."` + `"[batch_size]: Value must be equal to or lower than [10000]."` ); expect(() => queryValidator.validate({ type: 100 })).toThrowErrorMatchingInlineSnapshot( @@ -106,7 +106,7 @@ describe('Key rotation routes', () => { const unhandledException = new Error('Something went wrong.'); mockEncryptionKeyRotationService.rotate.mockRejectedValue(unhandledException); - const mockRequest = httpServerMock.createKibanaRequest({ query: { batchSize: 1234 } }); + const mockRequest = httpServerMock.createKibanaRequest({ query: { batch_size: 1234 } }); const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(500); @@ -117,7 +117,7 @@ describe('Key rotation routes', () => { }); it('returns whatever `rotate` returns.', async () => { - const mockRequest = httpServerMock.createKibanaRequest({ query: { batchSize: 1234 } }); + const mockRequest = httpServerMock.createKibanaRequest({ query: { batch_size: 1234 } }); mockEncryptionKeyRotationService.rotate.mockResolvedValue({ total: 3, successful: 6, @@ -132,7 +132,7 @@ describe('Key rotation routes', () => { }); it('returns 429 if called while rotation is in progress.', async () => { - const mockRequest = httpServerMock.createKibanaRequest({ query: { batchSize: 1234 } }); + const mockRequest = httpServerMock.createKibanaRequest({ query: { batch_size: 1234 } }); mockEncryptionKeyRotationService.rotate.mockResolvedValue({ total: 3, successful: 6, diff --git a/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.ts b/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.ts index 48b29387106ee..c74f67220472c 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.ts @@ -28,7 +28,7 @@ export function defineKeyRotationRoutes({ path: '/api/encrypted_saved_objects/_rotate_key', validate: { query: schema.object({ - batchSize: schema.number({ + batch_size: schema.number({ min: 1, max: DEFAULT_MAX_RESULT_WINDOW, defaultValue: DEFAULT_MAX_RESULT_WINDOW, @@ -60,7 +60,7 @@ export function defineKeyRotationRoutes({ try { return response.ok({ body: await encryptionKeyRotationService.rotate(request, { - batchSize: request.query.batchSize, + batchSize: request.query.batch_size, type: request.query.type, }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/filterable_users_popover.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/filterable_users_popover.tsx index e5fdcc3089059..c9bf03c5d4700 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/filterable_users_popover.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/filterable_users_popover.tsx @@ -45,7 +45,6 @@ export const FilterableUsersPopover: React.FC = ( isOpen={isPopoverOpen} closePopover={closePopover} panelPaddingSize="none" - withTitle={true} > = isOpen={isPopoverOpen} closePopover={closePopover} panelPaddingSize="none" - withTitle={true} > {contentSourceCountHeading}
{sources}
diff --git a/x-pack/plugins/event_log/server/event_log_client.test.ts b/x-pack/plugins/event_log/server/event_log_client.test.ts index d9846428b9488..d6793be425585 100644 --- a/x-pack/plugins/event_log/server/event_log_client.test.ts +++ b/x-pack/plugins/event_log/server/event_log_client.test.ts @@ -322,12 +322,6 @@ function FakeRequest(): KibanaRequest { url: '/', }, }, - // TODO: Remove once we upgrade to hapi v18 - _core: { - info: { - uri: 'http://localhost', - }, - }, getSavedObjectsClient: () => savedObjectGetter, } as unknown) as KibanaRequest; } diff --git a/x-pack/plugins/event_log/server/event_log_start_service.test.ts b/x-pack/plugins/event_log/server/event_log_start_service.test.ts index db6f4a1ad0f27..0a5b169e87d4d 100644 --- a/x-pack/plugins/event_log/server/event_log_start_service.test.ts +++ b/x-pack/plugins/event_log/server/event_log_start_service.test.ts @@ -56,12 +56,6 @@ function fakeRequest(): KibanaRequest { url: '/', }, }, - // TODO: Remove once we upgrade to hapi v18 - _core: { - info: { - uri: 'http://localhost', - }, - }, getSavedObjectsClient: () => savedObjectsClient, } as unknown) as KibanaRequest; } diff --git a/x-pack/plugins/event_log/server/saved_object_provider_registry.test.ts b/x-pack/plugins/event_log/server/saved_object_provider_registry.test.ts index 076260ab2fe53..6a02d54c87514 100644 --- a/x-pack/plugins/event_log/server/saved_object_provider_registry.test.ts +++ b/x-pack/plugins/event_log/server/saved_object_provider_registry.test.ts @@ -93,12 +93,6 @@ function fakeRequest(): KibanaRequest { url: '/', }, }, - // TODO: Remove once we upgrade to hapi v18 - _core: { - info: { - uri: 'http://localhost', - }, - }, getSavedObjectsClient: () => savedObjectsClient, } as unknown) as KibanaRequest; } diff --git a/x-pack/plugins/file_upload/server/client/errors.js b/x-pack/plugins/file_upload/server/client/errors.js index 0f8a0f0d8c0a9..a86edb330dec1 100644 --- a/x-pack/plugins/file_upload/server/client/errors.js +++ b/x-pack/plugins/file_upload/server/client/errors.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { boomify } from 'boom'; +import { boomify } from '@hapi/boom'; export function wrapError(error) { return boomify(error, { statusCode: error.status }); diff --git a/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx b/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx index f4006d6bf142b..ec5da1f44223c 100644 --- a/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx +++ b/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx @@ -116,7 +116,7 @@ export function FieldEditor({ return (
= ({ isOpen={isPolicyPopoverOpen(policy.name)} closePopover={closePolicyPopover} panelPaddingSize="none" - withTitle anchorPosition="rightUp" repositionOnScroll > diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx index d711863c309e9..afdf726ea02f9 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/components/index_lifecycle_summary.tsx @@ -158,7 +158,6 @@ export class IndexLifecycleSummary extends Component { button={button} isOpen={this.state.showPhaseExecutionPopover} closePopover={this.closePhaseExecutionPopover} - withTitle > = ({ isOpen={isPopoverOpen} closePopover={() => setIsPopOverOpen(false)} panelPaddingSize="none" - withTitle anchorPosition="rightUp" repositionOnScroll > diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/components/create_button_popover.tsx b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/components/create_button_popover.tsx index 941e8ec362de2..8939e58ffd94f 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/components/create_button_popover.tsx +++ b/x-pack/plugins/index_management/public/application/components/component_templates/component_template_selector/components/create_button_popover.tsx @@ -36,7 +36,6 @@ export const CreateButtonPopOver = ({ anchorPosition = 'upCenter' }: Props) => { isOpen={isPopoverOpen} closePopover={() => setIsPopOverOpen(false)} panelPaddingSize="none" - withTitle anchorPosition={anchorPosition} repositionOnScroll > diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js index 6e96ef56d683f..cf6fcfc238e06 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_actions_context_menu/index_actions_context_menu.js @@ -746,7 +746,6 @@ export class IndexActionsContextMenu extends Component { isOpen={this.state.isPopoverOpen} closePopover={this.closePopover} panelPaddingSize="none" - withTitle anchorPosition={anchorPosition} repositionOnScroll > diff --git a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx index 4899c5c664eba..1b1b9ad013c37 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/template_list/template_details/template_details_content.tsx @@ -262,7 +262,6 @@ export const TemplateDetailsContent = ({ isOpen={isPopoverOpen} closePopover={() => setIsPopOverOpen(false)} panelPaddingSize="none" - withTitle anchorPosition="rightUp" repositionOnScroll > diff --git a/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx b/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx index 02c3ea29c1846..7b15acfb6c4e2 100644 --- a/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx +++ b/x-pack/plugins/infra/public/alerting/common/components/alert_preview.tsx @@ -127,7 +127,7 @@ export const AlertPreview: React.FC = (props) => { defaultMessage: 'Preview', })} fullWidth - compressed + display="rowCompressed" > <> diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx index f47f30c280b2a..66d547eb50d9c 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx @@ -365,7 +365,7 @@ export const Expressions: React.FC = (props) => { defaultMessage: 'Use a KQL expression to limit the scope of your alert trigger.', })} fullWidth - compressed + display="rowCompressed" > {(alertsContext.metadata && ( { setAggFieldPopoverOpen(false); }} - withTitle anchorPosition={popupPosition ?? 'downRight'} zIndex={8000} > diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/node_type.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/node_type.tsx index 9c215b89f4634..4dca479271832 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/node_type.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/node_type.tsx @@ -61,7 +61,6 @@ export const NodeTypeExpression = ({ setAggTypePopoverOpen(false); }} ownFocus - withTitle anchorPosition={popupPosition ?? 'downLeft'} >
diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx index c71a3b6b13338..92c0172703423 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx @@ -364,7 +364,7 @@ export const Expressions: React.FC = (props) => { defaultMessage: 'Use a KQL expression to limit the scope of your alert trigger.', })} fullWidth - compressed + display="rowCompressed" > {(alertsContext.metadata && ( = (props) => { 'Create an alert for every unique value. For example: "host.id" or "cloud.region".', })} fullWidth - compressed + display="rowCompressed" > = ({ fill aria-label={ariaLabel || DEFAULT_MENU_LABEL} onClick={onOpen} - style={{ minWidth: 'auto' }} + minWidth="auto" > diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx index 92b21d676c9bb..e270b5650854d 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/job_setup_screen.tsx @@ -227,7 +227,7 @@ export const JobSetupScreen = (props: Props) => { defaultMessage="Partition field" /> } - compressed + display="rowCompressed" > Legend Options diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_inventory_switcher.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_inventory_switcher.tsx index c88446eaf3f6a..28f13d582082b 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_inventory_switcher.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/waffle_inventory_switcher.tsx @@ -135,7 +135,6 @@ export const WaffleInventorySwitcher: React.FC = () => { isOpen={isOpen} closePopover={closePopover} panelPaddingSize="none" - withTitle anchorPosition="downLeft" > diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_options.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_options.tsx index d21454dba5178..c6344e03d2cfd 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_options.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_options.tsx @@ -121,7 +121,7 @@ export const MetricsExplorerChartOptions = ({ chartOptions, onChange }: Props) = > isPipeline(path)); // get and save pipeline refs before installing pipelines @@ -50,6 +53,20 @@ export const installPipelines = async ( acc.push(...pipelineObjectRefs); return acc; }, []); + + // check that we don't duplicate the pipeline refs if the user is reinstalling + const installedPkg = await getInstallationObject({ + savedObjectsClient, + pkgName, + }); + if (!installedPkg) throw new Error("integration wasn't found while installing pipelines"); + // remove the current pipeline refs, if any exist, associated with this version before saving new ones so no duplicates occur + await deletePipelineRefs( + savedObjectsClient, + installedPkg.attributes.installed_es, + pkgName, + pkgVersion + ); await saveInstalledEsRefs(savedObjectsClient, installablePackage.name, pipelineRefs); const pipelines = dataStreams.reduce>>((acc, dataStream) => { if (dataStream.ingest_pipeline) { diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts index d32d5b8093c52..6a4d1ca0e1d0a 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/install.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { SavedObjectsClientContract } from 'src/core/server'; import { RegistryDataStream, diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts index 9651eafbf1e1c..fdd6db9396da8 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts @@ -6,7 +6,7 @@ import { SavedObject, SavedObjectsClientContract } from 'src/core/server'; import semver from 'semver'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { UnwrapPromise } from '@kbn/utility-types'; import { BulkInstallPackageInfo, InstallSource } from '../../../../common'; import { PACKAGES_SAVED_OBJECT_TYPE, MAX_TIME_COMPLETE_INSTALL } from '../../../constants'; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts index 8d5995f92c95f..4b4fe9540dd95 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts @@ -5,7 +5,7 @@ */ import { SavedObjectsClientContract } from 'src/core/server'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE, PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants'; import { AssetReference, diff --git a/x-pack/plugins/ingest_manager/server/services/settings.ts b/x-pack/plugins/ingest_manager/server/services/settings.ts index 44aece1c83a18..1b82be09ee62d 100644 --- a/x-pack/plugins/ingest_manager/server/services/settings.ts +++ b/x-pack/plugins/ingest_manager/server/services/settings.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { SavedObjectsClientContract } from 'kibana/server'; import url from 'url'; import { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx index 72c25d6dff72d..135a5854f36cd 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx @@ -105,7 +105,7 @@ const createActions = (testBed: TestBed) => { moveProcessor(processorSelector: string, dropZoneSelector: string) { act(() => { - find(`${processorSelector}.moveItemButton`).simulate('change'); + find(`${processorSelector}.moveItemButton`).simulate('click'); }); component.update(); act(() => { @@ -137,11 +137,11 @@ const createActions = (testBed: TestBed) => { startAndCancelMove(processorSelector: string) { act(() => { - find(`${processorSelector}.moveItemButton`).simulate('change'); + find(`${processorSelector}.moveItemButton`).simulate('click'); }); component.update(); act(() => { - find(`${processorSelector}.cancelMoveItemButton`).simulate('change'); + find(`${processorSelector}.cancelMoveItemButton`).simulate('click'); }); component.update(); }, diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx index dd7798a37dd4e..707f6e7f1e2c9 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx @@ -7,7 +7,7 @@ import classNames from 'classnames'; import React, { FunctionComponent, memo, useCallback } from 'react'; import { - EuiButtonToggle, + EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiLink, @@ -127,17 +127,14 @@ export const PipelineProcessorsEditorItem: FunctionComponent = memo( const icon = isMovingThisProcessor ? 'cross' : 'sortable'; const disabled = isEditorNotInIdleMode && !isMovingThisProcessor; const moveButton = ( - { + onClick={() => { if (isMovingThisProcessor) { onCancelMove(); } else { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.tsx index 19c3c49396c6e..6d0b2a1dcb2cf 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.tsx @@ -78,7 +78,6 @@ export const DocumentsDropdown: FunctionComponent = ({ closePopover={() => setShowPopover(false)} button={managePipelineButton} panelPaddingSize="none" - withTitle repositionOnScroll data-test-subj="documentsDropdown" panelClassName="documentsDropdownPanel" diff --git a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/details_flyout.tsx b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/details_flyout.tsx index 3122aa6da7396..bbb7603c53967 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/details_flyout.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/sections/pipelines_list/details_flyout.tsx @@ -187,7 +187,6 @@ export const PipelineDetailsFlyout: FunctionComponent = ({ closePopover={() => setShowPopover(false)} button={managePipelineButton} panelPaddingSize="none" - withTitle repositionOnScroll >
- {(!emptyExpression || title) && ( + {!emptyExpression || title ? ( - +

{title || i18n.translate('xpack.lens.chartTitle.unsaved', { defaultMessage: 'Unsaved' })} - +

+ ) : ( + +

+ {title || + i18n.translate('xpack.lens.chartTitle.unsaved', { defaultMessage: 'Unsaved' })} +

+
)} {children} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index 72263f124f0b1..fa4b5637f11f3 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -325,7 +325,7 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { id: 'histogram', }, ]} - onChange={(optionId) => { + onChange={(optionId: string) => { setShowingHistogram(optionId === 'histogram'); }} idSelected={showingHistogram ? 'histogram' : 'topValues'} diff --git a/x-pack/plugins/lens/server/routes/existing_fields.ts b/x-pack/plugins/lens/server/routes/existing_fields.ts index d3b2314a199cb..844c7b16e1eaa 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { schema } from '@kbn/config-schema'; import { ILegacyScopedClusterClient, SavedObject, RequestHandlerContext } from 'src/core/server'; import { CoreSetup, Logger } from 'src/core/server'; diff --git a/x-pack/plugins/lens/server/routes/field_stats.ts b/x-pack/plugins/lens/server/routes/field_stats.ts index a7368a12f0e2c..a884aeffa6134 100644 --- a/x-pack/plugins/lens/server/routes/field_stats.ts +++ b/x-pack/plugins/lens/server/routes/field_stats.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import DateMath from '@elastic/datemath'; import { schema } from '@kbn/config-schema'; import { CoreSetup } from 'src/core/server'; diff --git a/x-pack/plugins/lens/server/routes/telemetry.ts b/x-pack/plugins/lens/server/routes/telemetry.ts index 06a7091104871..306c631cd78a7 100644 --- a/x-pack/plugins/lens/server/routes/telemetry.ts +++ b/x-pack/plugins/lens/server/routes/telemetry.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { CoreSetup } from 'src/core/server'; import { schema } from '@kbn/config-schema'; import { BASE_API_URL } from '../../common'; diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap index 2767642254322..72c04992566bd 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap @@ -589,6 +589,7 @@ exports[`UploadLicense should display a modal when license requires acknowledgem onClick={[Function]} > ) { const colorForm = props.styleProperty.isDynamic() ? ( ) : ( @@ -19,7 +22,7 @@ export function VectorStyleColorEditor(props) { ); return ( - {...props} customStaticOptionLabel={i18n.translate( 'xpack.maps.styles.color.staticDynamicSelect.staticLabel', diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/field_select.js b/x-pack/plugins/maps/public/classes/styles/vector/components/field_select.tsx similarity index 56% rename from x-pack/plugins/maps/public/classes/styles/vector/components/field_select.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/field_select.tsx index dcc1f1eadbd54..57c63413aecda 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/field_select.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/field_select.tsx @@ -4,20 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ -import PropTypes from 'prop-types'; import React from 'react'; -import { EuiComboBox, EuiHighlight, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { FIELD_ORIGIN } from '../../../../../common/constants'; +import { + EuiComboBox, + EuiComboBoxProps, + EuiComboBoxOptionOption, + EuiHighlight, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FIELD_ORIGIN, VECTOR_STYLES } from '../../../../../common/constants'; import { FieldIcon } from '../../../../../../../../src/plugins/kibana_react/public'; +import { StyleField } from '../style_fields_helper'; -function renderOption(option, searchValue, contentClassName) { +function renderOption( + option: EuiComboBoxOptionOption, + searchValue: string, + contentClassName: string +) { + const fieldIcon = option.value ? : null; return ( - - - + {fieldIcon} {option.label} @@ -25,11 +35,11 @@ function renderOption(option, searchValue, contentClassName) { ); } -function groupFieldsByOrigin(fields) { - const fieldsByOriginMap = new Map(); +function groupFieldsByOrigin(fields: StyleField[]) { + const fieldsByOriginMap = new Map(); fields.forEach((field) => { if (fieldsByOriginMap.has(field.origin)) { - const fieldsList = fieldsByOriginMap.get(field.origin); + const fieldsList = fieldsByOriginMap.get(field.origin)!; fieldsList.push(field); fieldsByOriginMap.set(field.origin, fieldsList); } else { @@ -37,7 +47,7 @@ function groupFieldsByOrigin(fields) { } }); - function fieldsListToOptions(fieldsList) { + function fieldsListToOptions(fieldsList: StyleField[]) { return fieldsList .map((field) => { return { value: field, label: field.label }; @@ -50,11 +60,14 @@ function groupFieldsByOrigin(fields) { if (fieldsByOriginMap.size === 1) { // do not show origin group if all fields are from same origin const onlyOriginKey = fieldsByOriginMap.keys().next().value; - const fieldsList = fieldsByOriginMap.get(onlyOriginKey); + const fieldsList = fieldsByOriginMap.get(onlyOriginKey)!; return fieldsListToOptions(fieldsList); } - const optionGroups = []; + const optionGroups: Array<{ + label: string; + options: Array>; + }> = []; fieldsByOriginMap.forEach((fieldsList, fieldOrigin) => { optionGroups.push({ label: i18n.translate('xpack.maps.style.fieldSelect.OriginLabel', { @@ -65,29 +78,46 @@ function groupFieldsByOrigin(fields) { }); }); - optionGroups.sort((a, b) => { - return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); - }); + optionGroups.sort( + (a: EuiComboBoxOptionOption, b: EuiComboBoxOptionOption) => { + return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); + } + ); return optionGroups; } -export function FieldSelect({ fields, selectedFieldName, onChange, styleName, ...rest }) { - const onFieldChange = (selectedFields) => { +type Props = { + fields: StyleField[]; + selectedFieldName: string; + onChange: ({ field }: { field: StyleField | null }) => void; + styleName: VECTOR_STYLES; +} & Omit< + EuiComboBoxProps, + | 'selectedOptions' + | 'options' + | 'onChange' + | 'singleSelection' + | 'isClearable' + | 'fullWidth' + | 'renderOption' +>; + +export function FieldSelect({ fields, selectedFieldName, onChange, styleName, ...rest }: Props) { + const onFieldChange = (selectedFields: Array>) => { onChange({ - field: selectedFields.length > 0 ? selectedFields[0].value : null, + field: selectedFields.length > 0 && selectedFields[0].value ? selectedFields[0].value : null, }); }; let selectedOption; if (selectedFieldName) { - const field = fields.find((field) => { - return field.name === selectedFieldName; + const field = fields.find((f) => { + return f.name === selectedFieldName; }); - //Do not spread in all the other unused values (e.g. type, supportsAutoDomain etc...) if (field) { selectedOption = { - value: field.value, + value: field, label: field.label, }; } @@ -110,15 +140,3 @@ export function FieldSelect({ fields, selectedFieldName, onChange, styleName, .. /> ); } - -export const fieldShape = PropTypes.shape({ - name: PropTypes.string.isRequired, - origin: PropTypes.oneOf(Object.values(FIELD_ORIGIN)).isRequired, - type: PropTypes.string.isRequired, -}); - -FieldSelect.propTypes = { - selectedFieldName: PropTypes.string, - fields: PropTypes.arrayOf(fieldShape).isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/get_vector_style_label.js b/x-pack/plugins/maps/public/classes/styles/vector/components/get_vector_style_label.ts similarity index 94% rename from x-pack/plugins/maps/public/classes/styles/vector/components/get_vector_style_label.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/get_vector_style_label.ts index 5d39b423e56e6..1b5f3f47d2204 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/get_vector_style_label.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/get_vector_style_label.ts @@ -8,14 +8,14 @@ import { i18n } from '@kbn/i18n'; import { VECTOR_STYLES } from '../../../../../common/constants'; -export function getDisabledByMessage(styleName) { +export function getDisabledByMessage(styleName: VECTOR_STYLES) { return i18n.translate('xpack.maps.styles.vector.disabledByMessage', { defaultMessage: `Set '{styleLabel}' to enable`, values: { styleLabel: getVectorStyleLabel(styleName) }, }); } -export function getVectorStyleLabel(styleName) { +export function getVectorStyleLabel(styleName: VECTOR_STYLES) { switch (styleName) { case VECTOR_STYLES.FILL_COLOR: return i18n.translate('xpack.maps.styles.vector.fillColorLabel', { diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/stop_input.js b/x-pack/plugins/maps/public/classes/styles/vector/components/stop_input.tsx similarity index 76% rename from x-pack/plugins/maps/public/classes/styles/vector/components/stop_input.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/stop_input.tsx index 64a5f806e34c4..765626329a7c5 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/stop_input.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/stop_input.tsx @@ -5,18 +5,37 @@ */ import _ from 'lodash'; -import React, { Component } from 'react'; +import React, { ChangeEvent, Component } from 'react'; +import { EuiComboBox, EuiComboBoxOptionOption, EuiFieldText } from '@elastic/eui'; +import { IField } from '../../../fields/field'; + +interface Props { + dataTestSubj: string; + field: IField; + getValueSuggestions: (query: string) => Promise; + onChange: (value: string) => void; + value: string; +} + +interface State { + suggestions: string[]; + isLoadingSuggestions: boolean; + hasPrevFocus: boolean; + fieldDataType: string | null; + localFieldTextValue: string; + searchValue?: string; +} -import { EuiComboBox, EuiFieldText } from '@elastic/eui'; +export class StopInput extends Component { + private _isMounted: boolean = false; -export class StopInput extends Component { - constructor(props) { + constructor(props: Props) { super(props); this.state = { suggestions: [], isLoadingSuggestions: false, hasPrevFocus: false, - fieldDataType: undefined, + fieldDataType: null, localFieldTextValue: props.value, }; } @@ -45,15 +64,15 @@ export class StopInput extends Component { } }; - _onChange = (selectedOptions) => { + _onChange = (selectedOptions: Array>) => { this.props.onChange(_.get(selectedOptions, '[0].label', '')); }; - _onCreateOption = (newValue) => { + _onCreateOption = (newValue: string) => { this.props.onChange(newValue); }; - _onSearchChange = async (searchValue) => { + _onSearchChange = async (searchValue: string) => { this.setState( { isLoadingSuggestions: true, @@ -65,8 +84,8 @@ export class StopInput extends Component { ); }; - _loadSuggestions = _.debounce(async (searchValue) => { - let suggestions = []; + _loadSuggestions = _.debounce(async (searchValue: string) => { + let suggestions: string[] = []; try { suggestions = await this.props.getValueSuggestions(searchValue); } catch (error) { @@ -81,7 +100,7 @@ export class StopInput extends Component { } }, 300); - _onFieldTextChange = (event) => { + _onFieldTextChange = (event: ChangeEvent) => { this.setState({ localFieldTextValue: event.target.value }); // onChange can cause UI lag, ensure smooth input typing by debouncing onChange this._debouncedOnFieldTextChange(); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/style_prop_editor.js b/x-pack/plugins/maps/public/classes/styles/vector/components/style_prop_editor.tsx similarity index 59% rename from x-pack/plugins/maps/public/classes/styles/vector/components/style_prop_editor.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/style_prop_editor.tsx index e7df47bc6d4cb..43b088074a30e 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/style_prop_editor.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/style_prop_editor.tsx @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component, Fragment } from 'react'; -import { getVectorStyleLabel, getDisabledByMessage } from './get_vector_style_label'; +import React, { Component, Fragment, ReactElement } from 'react'; import { EuiFormRow, EuiSelect, @@ -14,12 +13,31 @@ import { EuiFieldText, EuiToolTip, } from '@elastic/eui'; -import { STYLE_TYPE } from '../../../../../common/constants'; import { i18n } from '@kbn/i18n'; +import { getVectorStyleLabel, getDisabledByMessage } from './get_vector_style_label'; +import { STYLE_TYPE, VECTOR_STYLES } from '../../../../../common/constants'; +import { FieldMetaOptions } from '../../../../../common/descriptor_types'; +import { IStyleProperty } from '../properties/style_property'; +import { StyleField } from '../style_fields_helper'; + +export interface Props { + children: ReactElement; + customStaticOptionLabel?: string; + defaultStaticStyleOptions: StaticOptions; + defaultDynamicStyleOptions: DynamicOptions; + disabled: boolean; + disabledBy?: VECTOR_STYLES; + fields: StyleField[]; + onDynamicStyleChange: (propertyName: VECTOR_STYLES, options: DynamicOptions) => void; + onStaticStyleChange: (propertyName: VECTOR_STYLES, options: StaticOptions) => void; + styleProperty: IStyleProperty; +} -export class StylePropEditor extends Component { - _prevStaticStyleOptions = this.props.defaultStaticStyleOptions; - _prevDynamicStyleOptions = this.props.defaultDynamicStyleOptions; +export class StylePropEditor extends Component< + Props +> { + private _prevStaticStyleOptions = this.props.defaultStaticStyleOptions; + private _prevDynamicStyleOptions = this.props.defaultDynamicStyleOptions; _onTypeToggle = () => { if (this.props.styleProperty.isDynamic()) { @@ -41,7 +59,7 @@ export class StylePropEditor extends Component { } }; - _onFieldMetaOptionsChange = (fieldMetaOptions) => { + _onFieldMetaOptionsChange = (fieldMetaOptions: FieldMetaOptions) => { const options = { ...this.props.styleProperty.getOptions(), fieldMetaOptions, @@ -89,28 +107,29 @@ export class StylePropEditor extends Component { const staticDynamicSelect = this.renderStaticDynamicSelect(); - const stylePropertyForm = this.props.disabled ? ( - - - - {staticDynamicSelect} - - - - - - - ) : ( - - {React.cloneElement(this.props.children, { - staticDynamicSelect, - })} - {fieldMetaOptionsPopover} - - ); + const stylePropertyForm = + this.props.disabled && this.props.disabledBy ? ( + + + + {staticDynamicSelect} + + + + + + + ) : ( + + {React.cloneElement(this.props.children, { + staticDynamicSelect, + })} + {fieldMetaOptionsPopover} + + ); return ( + ); diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js index 60f8c8005fe4c..c6c784481436c 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js +++ b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js @@ -175,7 +175,6 @@ export class JoinExpression extends Component { closePopover={this._closePopover} ownFocus initialFocus="body" /* avoid initialFocus on Combobox */ - withTitle anchorPosition="leftCenter" button={ { isOpen={this.state.isPopoverOpen} closePopover={this._closePopover} panelPaddingSize="none" - withTitle anchorPosition="leftUp" > diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap index 0f620bdeb1c0e..456414889c732 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/__snapshots__/toc_entry_actions_popover.test.tsx.snap @@ -57,7 +57,6 @@ exports[`TOCEntryActionsPopover is rendered 1`] = ` isOpen={false} ownFocus={false} panelPaddingSize="none" - withTitle={true} > { isOpen={this.state.isPopoverOpen} closePopover={this._closePopover} panelPaddingSize="none" - withTitle anchorPosition="leftUp" anchorClassName="mapLayTocActions__popoverAnchor" > diff --git a/x-pack/plugins/ml/common/types/data_frame_analytics.ts b/x-pack/plugins/ml/common/types/data_frame_analytics.ts index cbf2acd152476..8874b0f7e111a 100644 --- a/x-pack/plugins/ml/common/types/data_frame_analytics.ts +++ b/x-pack/plugins/ml/common/types/data_frame_analytics.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { EsErrorBody } from '../util/errors'; import { ANALYSIS_CONFIG_TYPE } from '../constants/data_frame_analytics'; diff --git a/x-pack/plugins/ml/common/types/feature_importance.ts b/x-pack/plugins/ml/common/types/feature_importance.ts index 4f5619cf3ab7b..1ae4c7832390c 100644 --- a/x-pack/plugins/ml/common/types/feature_importance.ts +++ b/x-pack/plugins/ml/common/types/feature_importance.ts @@ -8,10 +8,13 @@ export interface ClassFeatureImportance { class_name: string | boolean; importance: number; } + +// TODO We should separate the interface because classes/importance +// isn't both optional but either/or. export interface FeatureImportance { feature_name: string; - importance?: number; classes?: ClassFeatureImportance[]; + importance?: number; } export interface TopClass { diff --git a/x-pack/plugins/ml/common/util/errors/errors.test.ts b/x-pack/plugins/ml/common/util/errors/errors.test.ts index ddaf0b04dd12b..166264ebddee1 100644 --- a/x-pack/plugins/ml/common/util/errors/errors.test.ts +++ b/x-pack/plugins/ml/common/util/errors/errors.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { extractErrorMessage, MLHttpFetchError, MLResponseError, EsErrorBody } from './index'; diff --git a/x-pack/plugins/ml/common/util/errors/types.ts b/x-pack/plugins/ml/common/util/errors/types.ts index fec35141f1b5a..667e6e34a5640 100644 --- a/x-pack/plugins/ml/common/util/errors/types.ts +++ b/x-pack/plugins/ml/common/util/errors/types.ts @@ -5,7 +5,7 @@ */ import { HttpFetchError } from 'kibana/public'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; export interface EsErrorRootCause { type: string; diff --git a/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.test.tsx b/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.test.tsx index d54a7fe81e858..449c017d533cc 100644 --- a/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.test.tsx +++ b/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.test.tsx @@ -37,7 +37,9 @@ describe('AnomalyResultsViewSelector', () => { // Check the Single Metric Viewer element exists in the selector, and that it is checked. expect(getByTestId('mlAnomalyResultsViewSelectorSingleMetricViewer')).toBeInTheDocument(); expect( - getByTestId('mlAnomalyResultsViewSelectorSingleMetricViewer').hasAttribute('checked') + getByTestId('mlAnomalyResultsViewSelectorSingleMetricViewer') + .querySelector('input')! + .hasAttribute('checked') ).toBe(true); }); }); diff --git a/x-pack/plugins/ml/public/application/components/data_grid/common.test.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.test.ts index 4bb670ad02dfc..aaf6f90b00f4d 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/common.test.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/common.test.ts @@ -8,7 +8,7 @@ import { EuiDataGridSorting } from '@elastic/eui'; import { multiColumnSortFactory } from './common'; -describe('Transform: Define Pivot Common', () => { +describe('Data Frame Analytics: Data Grid Common', () => { test('multiColumnSortFactory()', () => { const data = [ { s: 'a', n: 1 }, diff --git a/x-pack/plugins/ml/public/application/components/data_grid/common.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.ts index 642d0ae564b85..48a0a0c9ab126 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/common.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/common.ts @@ -24,7 +24,9 @@ import { KBN_FIELD_TYPES, } from '../../../../../../../src/plugins/data/public'; +import { DEFAULT_RESULTS_FIELD } from '../../../../common/constants/data_frame_analytics'; import { extractErrorMessage } from '../../../../common/util/errors'; +import { FeatureImportance, TopClasses } from '../../../../common/types/feature_importance'; import { BASIC_NUMERICAL_TYPES, @@ -158,6 +160,90 @@ export const getDataGridSchemaFromKibanaFieldType = ( return schema; }; +const getClassName = (className: string, isClassTypeBoolean: boolean) => { + if (isClassTypeBoolean) { + return className === 'true'; + } + + return className; +}; +/** + * Helper to transform feature importance flattened fields with arrays back to object structure + * + * @param row - EUI data grid data row + * @param mlResultsField - Data frame analytics results field + * @returns nested object structure of feature importance values + */ +export const getFeatureImportance = ( + row: Record, + mlResultsField: string, + isClassTypeBoolean = false +): FeatureImportance[] => { + const featureNames: string[] | undefined = + row[`${mlResultsField}.feature_importance.feature_name`]; + const classNames: string[] | undefined = + row[`${mlResultsField}.feature_importance.classes.class_name`]; + const classImportance: number[] | undefined = + row[`${mlResultsField}.feature_importance.classes.importance`]; + + if (featureNames === undefined) { + return []; + } + + // return object structure for classification job + if (classNames !== undefined && classImportance !== undefined) { + const overallClassNames = classNames?.slice(0, classNames.length / featureNames.length); + + return featureNames.map((fName, index) => { + const offset = overallClassNames.length * index; + const featureClassImportance = classImportance.slice( + offset, + offset + overallClassNames.length + ); + return { + feature_name: fName, + classes: overallClassNames.map((fClassName, fIndex) => { + return { + class_name: getClassName(fClassName, isClassTypeBoolean), + importance: featureClassImportance[fIndex], + }; + }), + }; + }); + } + + // return object structure for regression job + const importance: number[] = row[`${mlResultsField}.feature_importance.importance`]; + return featureNames.map((fName, index) => ({ + feature_name: fName, + importance: importance[index], + })); +}; + +/** + * Helper to transforms top classes flattened fields with arrays back to object structure + * + * @param row - EUI data grid data row + * @param mlResultsField - Data frame analytics results field + * @returns nested object structure of feature importance values + */ +export const getTopClasses = (row: Record, mlResultsField: string): TopClasses => { + const classNames: string[] | undefined = row[`${mlResultsField}.top_classes.class_name`]; + const classProbabilities: number[] | undefined = + row[`${mlResultsField}.top_classes.class_probability`]; + const classScores: number[] | undefined = row[`${mlResultsField}.top_classes.class_score`]; + + if (classNames === undefined || classProbabilities === undefined || classScores === undefined) { + return []; + } + + return classNames.map((className, index) => ({ + class_name: className, + class_probability: classProbabilities[index], + class_score: classScores[index], + })); +}; + export const useRenderCellValue = ( indexPattern: IndexPattern | undefined, pagination: IndexPagination, @@ -207,6 +293,15 @@ export const useRenderCellValue = ( return item[cId]; } + // For classification and regression results, we need to treat some fields with a custom transform. + if (cId === `${resultsField}.feature_importance`) { + return getFeatureImportance(fullItem, resultsField ?? DEFAULT_RESULTS_FIELD); + } + + if (cId === `${resultsField}.top_classes`) { + return getTopClasses(fullItem, resultsField ?? DEFAULT_RESULTS_FIELD); + } + // Try if the field name is available as a nested field. return getNestedProperty(tableItems[adjustedRowIndex], cId, null); } diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx index fad2439f5d5ee..50e9cabc99c35 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx @@ -27,10 +27,15 @@ import { DEFAULT_SAMPLER_SHARD_SIZE } from '../../../../common/constants/field_h import { ANALYSIS_CONFIG_TYPE, INDEX_STATUS } from '../../data_frame_analytics/common'; -import { euiDataGridStyle, euiDataGridToolbarSettings } from './common'; +import { + euiDataGridStyle, + euiDataGridToolbarSettings, + getFeatureImportance, + getTopClasses, +} from './common'; import { UseIndexDataReturnType } from './types'; import { DecisionPathPopover } from './feature_importance/decision_path_popover'; -import { TopClasses } from '../../../../common/types/feature_importance'; +import { FeatureImportance, TopClasses } from '../../../../common/types/feature_importance'; import { DEFAULT_RESULTS_FIELD } from '../../../../common/constants/data_frame_analytics'; import { DataFrameAnalysisConfigType } from '../../../../common/types/data_frame_analytics'; @@ -118,18 +123,28 @@ export const DataGrid: FC = memo( if (!row) return
; // if resultsField for some reason is not available then use ml const mlResultsField = resultsField ?? DEFAULT_RESULTS_FIELD; - const parsedFIArray = row[mlResultsField].feature_importance; let predictedValue: string | number | undefined; let topClasses: TopClasses = []; if ( predictionFieldName !== undefined && row && - row[mlResultsField][predictionFieldName] !== undefined + row[`${mlResultsField}.${predictionFieldName}`] !== undefined ) { - predictedValue = row[mlResultsField][predictionFieldName]; - topClasses = row[mlResultsField].top_classes; + predictedValue = row[`${mlResultsField}.${predictionFieldName}`]; + topClasses = getTopClasses(row, mlResultsField); } + const isClassTypeBoolean = topClasses.reduce( + (p, c) => typeof c.class_name === 'boolean' || p, + false + ); + + const parsedFIArray: FeatureImportance[] = getFeatureImportance( + row, + mlResultsField, + isClassTypeBoolean + ); + return (
{this.renderAppliesToPopover()} diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/scope_expression.js b/x-pack/plugins/ml/public/application/components/rule_editor/scope_expression.js index a8c7ac0f6f598..ced81d59ab51e 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/scope_expression.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/scope_expression.js @@ -157,7 +157,6 @@ export class ScopeExpression extends Component { closePopover={this.closeFilterList} panelPaddingSize="none" ownFocus - withTitle anchorPosition="downLeft" > {this.renderFilterListPopover()} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts index c606cbd1cc11a..785f3ac9cd4dc 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts @@ -213,6 +213,10 @@ export const getDefaultFieldsFromJobCaps = ( name: `${resultsField}.${FEATURE_IMPORTANCE}`, type: KBN_FIELD_TYPES.UNKNOWN, }); + // remove flattened feature importance fields + fields = fields.filter( + (field: any) => !field.name.includes(`${resultsField}.${FEATURE_IMPORTANCE}.`) + ); } if ((numTopClasses ?? 0) > 0) { @@ -221,6 +225,10 @@ export const getDefaultFieldsFromJobCaps = ( name: `${resultsField}.${TOP_CLASSES}`, type: KBN_FIELD_TYPES.UNKNOWN, }); + // remove flattened top classes fields + fields = fields.filter( + (field: any) => !field.name.includes(`${resultsField}.${TOP_CLASSES}.`) + ); } // Only need to add these fields if we didn't use dest index pattern to get the fields diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts index 8e50aab0914db..85f222109d408 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/get_index_data.ts @@ -53,7 +53,7 @@ export const getIndexData = async ( index: jobConfig.dest.index, body: { fields: ['*'], - _source: [], + _source: false, query: searchQuery, from: pageIndex * pageSize, size: pageSize, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx index c837fcbacdd55..4e84bd5ffeddb 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx @@ -7,7 +7,6 @@ import React, { Dispatch, FC, SetStateAction, useEffect, useState } from 'react'; import { EuiButtonGroup, EuiCode, EuiFlexGroup, EuiFlexItem, EuiInputPopover } from '@elastic/eui'; -import { EuiButtonGroupIdToSelectedMap } from '@elastic/eui/src/components/button/button_group/button_group'; import { i18n } from '@kbn/i18n'; @@ -54,7 +53,7 @@ export const ExplorationQueryBar: FC = ({ query: '', language: SEARCH_QUERY_LANGUAGE.KUERY, }); - const [idToSelectedMap, setIdToSelectedMap] = useState({}); + const [idToSelectedMap, setIdToSelectedMap] = useState<{ [id: string]: boolean }>({}); const [errorMessage, setErrorMessage] = useState(undefined); @@ -174,11 +173,10 @@ export const ExplorationQueryBar: FC = ({ defaultMessage: 'Analytics query bar filter buttons', } )} - name="analyticsQueryBarFilterButtons" options={filters.options} type="multi" idToSelectedMap={idToSelectedMap} - onChange={(optionId) => { + onChange={(optionId: string) => { const newIdToSelectedMap = { [optionId]: !idToSelectedMap[optionId] }; setIdToSelectedMap(newIdToSelectedMap); handleFilterUpdate(optionId, newIdToSelectedMap); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx index a6e95269b3633..10e2ad5b5eb53 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx @@ -29,7 +29,7 @@ interface Props { } export const ExplorationResultsTable: FC = React.memo( - ({ indexPattern, jobConfig, jobStatus, needsDestIndexPattern, searchQuery }) => { + ({ indexPattern, jobConfig, needsDestIndexPattern, searchQuery }) => { const { services: { mlServices: { mlApiServices }, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/number_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/number_content.tsx index e2fb8ae5547cc..bfc431166fab9 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/number_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/number_content.tsx @@ -146,7 +146,7 @@ export const NumberContent: FC = ({ config }) => { options={detailsOptions} idSelected={detailsMode} onChange={(optionId) => setDetailsMode(optionId as DETAILS_MODE)} - aria-label={i18n.translate( + legend={i18n.translate( 'xpack.ml.fieldDataCard.cardNumber.selectMetricDetailsDisplayAriaLabel', { defaultMessage: 'Select display option for metric details', diff --git a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap index 06ee16f264756..7d5c73b42f15b 100644 --- a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap +++ b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap @@ -21,9 +21,8 @@ exports[`CustomUrlEditor renders the editor for a dashboard type URL with a labe > = ({ className="url-label" error={invalidLabelError} isInvalid={isInvalidLabel} - compressed + display="rowCompressed" > = ({ label={ } - compressed + display="rowCompressed" > = ({ defaultMessage="Dashboard name" /> } - compressed + display="rowCompressed" > = ({ defaultMessage="Index pattern" /> } - compressed + display="rowCompressed" > = ({ /> } className="url-time-range" - compressed + display="rowCompressed" > = ({ className="url-time-range" error={invalidIntervalError} isInvalid={isInvalidTimeRange} - compressed + display="rowCompressed" > = ({ label={ } - compressed + display="rowCompressed" fullWidth={true} > { diff --git a/x-pack/plugins/ml/server/client/errors.js b/x-pack/plugins/ml/server/client/errors.js index 0f8a0f0d8c0a9..a86edb330dec1 100644 --- a/x-pack/plugins/ml/server/client/errors.js +++ b/x-pack/plugins/ml/server/client/errors.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { boomify } from 'boom'; +import { boomify } from '@hapi/boom'; export function wrapError(error) { return boomify(error, { statusCode: error.status }); diff --git a/x-pack/plugins/ml/server/models/annotation_service/annotation.ts b/x-pack/plugins/ml/server/models/annotation_service/annotation.ts index d45532e956f42..adcf93a6f11ec 100644 --- a/x-pack/plugins/ml/server/models/annotation_service/annotation.ts +++ b/x-pack/plugins/ml/server/models/annotation_service/annotation.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { each, get } from 'lodash'; import { IScopedClusterClient } from 'kibana/server'; diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts index 96be6db03c52b..2c08005cecc09 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts @@ -5,7 +5,7 @@ */ import fs from 'fs'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import numeral from '@elastic/numeral'; import { KibanaRequest, IScopedClusterClient, SavedObjectsClientContract } from 'kibana/server'; import moment from 'moment'; diff --git a/x-pack/plugins/ml/server/models/fields_service/fields_service.ts b/x-pack/plugins/ml/server/models/fields_service/fields_service.ts index 68427e98750eb..ed8d3f48e387c 100644 --- a/x-pack/plugins/ml/server/models/fields_service/fields_service.ts +++ b/x-pack/plugins/ml/server/models/fields_service/fields_service.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { IScopedClusterClient } from 'kibana/server'; import { duration } from 'moment'; import { parseInterval } from '../../../common/util/parse_interval'; diff --git a/x-pack/plugins/ml/server/models/filter/filter_manager.ts b/x-pack/plugins/ml/server/models/filter/filter_manager.ts index 19ba1b76f8a60..de174420d2535 100644 --- a/x-pack/plugins/ml/server/models/filter/filter_manager.ts +++ b/x-pack/plugins/ml/server/models/filter/filter_manager.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { IScopedClusterClient } from 'kibana/server'; import { DetectorRule, DetectorRuleScope } from '../../../common/types/detector_rules'; diff --git a/x-pack/plugins/ml/server/models/job_service/jobs.ts b/x-pack/plugins/ml/server/models/job_service/jobs.ts index 822aa4671302e..3209d5748cfd7 100644 --- a/x-pack/plugins/ml/server/models/job_service/jobs.ts +++ b/x-pack/plugins/ml/server/models/job_service/jobs.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { uniq } from 'lodash'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { IScopedClusterClient } from 'kibana/server'; import { parseTimeIntervalForJob } from '../../../common/util/job_utils'; import { JOB_STATE, DATAFEED_STATE } from '../../../common/constants/states'; diff --git a/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts b/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts index 34206a68ffeb9..5b7f4bd8516ed 100644 --- a/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts +++ b/x-pack/plugins/ml/server/models/job_service/model_snapshots.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { i18n } from '@kbn/i18n'; import { IScopedClusterClient } from 'kibana/server'; import { ModelSnapshot } from '../../../common/types/anomaly_detection_jobs'; diff --git a/x-pack/plugins/ml/server/models/job_validation/job_validation.ts b/x-pack/plugins/ml/server/models/job_validation/job_validation.ts index 9e272f1f770fc..243439115e712 100644 --- a/x-pack/plugins/ml/server/models/job_validation/job_validation.ts +++ b/x-pack/plugins/ml/server/models/job_validation/job_validation.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { IScopedClusterClient } from 'kibana/server'; import { TypeOf } from '@kbn/config-schema'; import { fieldsServiceProvider } from '../fields_service'; diff --git a/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts b/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts index 76dc68d2b59e3..eb2c6b461a9df 100644 --- a/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts +++ b/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { IScopedClusterClient } from 'kibana/server'; import { PARTITION_FIELDS } from '../../../common/constants/anomalies'; import { PartitionFieldsType } from '../../../common/types/anomalies'; diff --git a/x-pack/plugins/ml/server/models/results_service/results_service.ts b/x-pack/plugins/ml/server/models/results_service/results_service.ts index abb42a5a18689..4a4ab73b5ef3a 100644 --- a/x-pack/plugins/ml/server/models/results_service/results_service.ts +++ b/x-pack/plugins/ml/server/models/results_service/results_service.ts @@ -8,7 +8,7 @@ import { sortBy, slice, get } from 'lodash'; import moment from 'moment'; import { SearchResponse } from 'elasticsearch'; import { IScopedClusterClient } from 'kibana/server'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { buildAnomalyTableItems } from './build_anomaly_table_items'; import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns'; import { ANOMALIES_TABLE_DEFAULT_QUERY_SIZE } from '../../../common/constants/search'; diff --git a/x-pack/plugins/ml/server/routes/annotations.ts b/x-pack/plugins/ml/server/routes/annotations.ts index 5c4b36164fbb0..05d0624deba59 100644 --- a/x-pack/plugins/ml/server/routes/annotations.ts +++ b/x-pack/plugins/ml/server/routes/annotations.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { i18n } from '@kbn/i18n'; import { SecurityPluginSetup } from '../../../security/server'; diff --git a/x-pack/plugins/ml/server/routes/job_validation.ts b/x-pack/plugins/ml/server/routes/job_validation.ts index b52043595327b..2958b0bc41c24 100644 --- a/x-pack/plugins/ml/server/routes/job_validation.ts +++ b/x-pack/plugins/ml/server/routes/job_validation.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { IScopedClusterClient } from 'kibana/server'; import { TypeOf } from '@kbn/config-schema'; import { AnalysisConfig } from '../../common/types/anomaly_detection_jobs'; diff --git a/x-pack/plugins/ml/server/routes/system.ts b/x-pack/plugins/ml/server/routes/system.ts index 3a66f60943bb3..a4c61f4eb4576 100644 --- a/x-pack/plugins/ml/server/routes/system.ts +++ b/x-pack/plugins/ml/server/routes/system.ts @@ -6,7 +6,7 @@ import { schema } from '@kbn/config-schema'; -import { Request } from 'hapi'; +import { Request } from '@hapi/hapi'; import { IScopedClusterClient } from 'kibana/server'; import { wrapError } from '../client/error_wrapper'; import { mlLog } from '../client/log'; diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts index 76d9e7517b6ab..355390100ac2b 100644 --- a/x-pack/plugins/monitoring/common/constants.ts +++ b/x-pack/plugins/monitoring/common/constants.ts @@ -4,6 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; +import { CommonAlertParamDetail } from './types/alerts'; +import { AlertParamType } from './enums'; + /** * Helper string to add as a tag in every logging call */ @@ -216,13 +220,20 @@ export const REPORTING_SYSTEM_ID = 'reporting'; export const TELEMETRY_COLLECTION_INTERVAL = 86400000; /** - * We want to slowly rollout the migration from watcher-based cluster alerts to - * kibana alerts and we only want to enable the kibana alerts once all - * watcher-based cluster alerts have been migrated so this flag will serve - * as the only way to see the new UI and actually run Kibana alerts. It will - * be false until all alerts have been migrated, then it will be removed + * The amount of time, in milliseconds, to fetch the cluster uuids from es. + * + * Currently 3 hours. + * @type {Number} + */ +export const CLUSTER_DETAILS_FETCH_INTERVAL = 10800000; + +/** + * The amount of time, in milliseconds, to fetch the usage data from es. + * + * Currently 20 minutes. + * @type {Number} */ -export const KIBANA_CLUSTER_ALERTS_ENABLED = false; +export const USAGE_FETCH_INTERVAL = 1200000; /** * The prefix for all alert types used by monitoring @@ -238,6 +249,168 @@ export const ALERT_KIBANA_VERSION_MISMATCH = `${ALERT_PREFIX}alert_kibana_versio export const ALERT_LOGSTASH_VERSION_MISMATCH = `${ALERT_PREFIX}alert_logstash_version_mismatch`; export const ALERT_MEMORY_USAGE = `${ALERT_PREFIX}alert_jvm_memory_usage`; export const ALERT_MISSING_MONITORING_DATA = `${ALERT_PREFIX}alert_missing_monitoring_data`; +export const ALERT_THREAD_POOL_SEARCH_REJECTIONS = `${ALERT_PREFIX}alert_thread_pool_search_rejections`; +export const ALERT_THREAD_POOL_WRITE_REJECTIONS = `${ALERT_PREFIX}alert_thread_pool_write_rejections`; + +/** + * Legacy alerts details/label for server and public use + */ +export const LEGACY_ALERT_DETAILS = { + [ALERT_CLUSTER_HEALTH]: { + label: i18n.translate('xpack.monitoring.alerts.clusterHealth.label', { + defaultMessage: 'Cluster health', + }), + }, + [ALERT_ELASTICSEARCH_VERSION_MISMATCH]: { + label: i18n.translate('xpack.monitoring.alerts.elasticsearchVersionMismatch.label', { + defaultMessage: 'Elasticsearch version mismatch', + }), + }, + [ALERT_KIBANA_VERSION_MISMATCH]: { + label: i18n.translate('xpack.monitoring.alerts.kibanaVersionMismatch.label', { + defaultMessage: 'Kibana version mismatch', + }), + }, + [ALERT_LICENSE_EXPIRATION]: { + label: i18n.translate('xpack.monitoring.alerts.licenseExpiration.label', { + defaultMessage: 'License expiration', + }), + }, + [ALERT_LOGSTASH_VERSION_MISMATCH]: { + label: i18n.translate('xpack.monitoring.alerts.logstashVersionMismatch.label', { + defaultMessage: 'Logstash version mismatch', + }), + }, + [ALERT_NODES_CHANGED]: { + label: i18n.translate('xpack.monitoring.alerts.nodesChanged.label', { + defaultMessage: 'Nodes changed', + }), + }, +}; + +/** + * Alerts details/label for server and public use + */ +export const ALERT_DETAILS = { + [ALERT_CPU_USAGE]: { + label: i18n.translate('xpack.monitoring.alerts.cpuUsage.label', { + defaultMessage: 'CPU Usage', + }), + paramDetails: { + threshold: { + label: i18n.translate('xpack.monitoring.alerts.cpuUsage.paramDetails.threshold.label', { + defaultMessage: `Notify when CPU is over`, + }), + type: AlertParamType.Percentage, + } as CommonAlertParamDetail, + duration: { + label: i18n.translate('xpack.monitoring.alerts.cpuUsage.paramDetails.duration.label', { + defaultMessage: `Look at the average over`, + }), + type: AlertParamType.Duration, + } as CommonAlertParamDetail, + }, + }, + [ALERT_DISK_USAGE]: { + paramDetails: { + threshold: { + label: i18n.translate('xpack.monitoring.alerts.diskUsage.paramDetails.threshold.label', { + defaultMessage: `Notify when disk capacity is over`, + }), + type: AlertParamType.Percentage, + }, + duration: { + label: i18n.translate('xpack.monitoring.alerts.diskUsage.paramDetails.duration.label', { + defaultMessage: `Look at the average over`, + }), + type: AlertParamType.Duration, + }, + }, + label: i18n.translate('xpack.monitoring.alerts.diskUsage.label', { + defaultMessage: 'Disk Usage', + }), + }, + [ALERT_MEMORY_USAGE]: { + paramDetails: { + threshold: { + label: i18n.translate('xpack.monitoring.alerts.memoryUsage.paramDetails.threshold.label', { + defaultMessage: `Notify when memory usage is over`, + }), + type: AlertParamType.Percentage, + }, + duration: { + label: i18n.translate('xpack.monitoring.alerts.memoryUsage.paramDetails.duration.label', { + defaultMessage: `Look at the average over`, + }), + type: AlertParamType.Duration, + }, + }, + label: i18n.translate('xpack.monitoring.alerts.memoryUsage.label', { + defaultMessage: 'Memory Usage (JVM)', + }), + }, + [ALERT_MISSING_MONITORING_DATA]: { + paramDetails: { + duration: { + label: i18n.translate('xpack.monitoring.alerts.missingData.paramDetails.duration.label', { + defaultMessage: `Notify if monitoring data is missing for the last`, + }), + type: AlertParamType.Duration, + } as CommonAlertParamDetail, + limit: { + label: i18n.translate('xpack.monitoring.alerts.missingData.paramDetails.limit.label', { + defaultMessage: `looking back`, + }), + type: AlertParamType.Duration, + } as CommonAlertParamDetail, + }, + label: i18n.translate('xpack.monitoring.alerts.missingData.label', { + defaultMessage: 'Missing monitoring data', + }), + }, + [ALERT_THREAD_POOL_SEARCH_REJECTIONS]: { + paramDetails: { + threshold: { + label: i18n.translate('xpack.monitoring.alerts.rejection.paramDetails.threshold.label', { + defaultMessage: `Notify when {type} rejection count is over`, + values: { type: 'search' }, + }), + type: AlertParamType.Number, + }, + duration: { + label: i18n.translate('xpack.monitoring.alerts.rejection.paramDetails.duration.label', { + defaultMessage: `In the last`, + }), + type: AlertParamType.Duration, + }, + }, + label: i18n.translate('xpack.monitoring.alerts.threadPoolRejections.label', { + defaultMessage: 'Thread pool {type} rejections', + values: { type: 'search' }, + }), + }, + [ALERT_THREAD_POOL_WRITE_REJECTIONS]: { + paramDetails: { + threshold: { + label: i18n.translate('xpack.monitoring.alerts.rejection.paramDetails.threshold.label', { + defaultMessage: `Notify when {type} rejection count is over`, + values: { type: 'write' }, + }), + type: AlertParamType.Number, + }, + duration: { + label: i18n.translate('xpack.monitoring.alerts.rejection.paramDetails.duration.label', { + defaultMessage: `In the last`, + }), + type: AlertParamType.Duration, + }, + }, + label: i18n.translate('xpack.monitoring.alerts.threadPoolRejections.label', { + defaultMessage: 'Thread pool {type} rejections', + values: { type: 'write' }, + }), + }, +}; /** * A listing of all alert types @@ -253,6 +426,8 @@ export const ALERTS = [ ALERT_LOGSTASH_VERSION_MISMATCH, ALERT_MEMORY_USAGE, ALERT_MISSING_MONITORING_DATA, + ALERT_THREAD_POOL_SEARCH_REJECTIONS, + ALERT_THREAD_POOL_WRITE_REJECTIONS, ]; /** diff --git a/x-pack/plugins/monitoring/common/enums.ts b/x-pack/plugins/monitoring/common/enums.ts index d4058e9de801e..b373428bb279b 100644 --- a/x-pack/plugins/monitoring/common/enums.ts +++ b/x-pack/plugins/monitoring/common/enums.ts @@ -25,6 +25,7 @@ export enum AlertMessageTokenType { export enum AlertParamType { Duration = 'duration', Percentage = 'percentage', + Number = 'number', } export enum SetupModeFeature { diff --git a/x-pack/plugins/monitoring/common/types.ts b/x-pack/plugins/monitoring/common/types.ts deleted file mode 100644 index 825d2e454b3bb..0000000000000 --- a/x-pack/plugins/monitoring/common/types.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { Alert } from '../../alerts/common'; -import { AlertParamType } from './enums'; - -export interface CommonBaseAlert { - type: string; - label: string; - paramDetails: CommonAlertParamDetails; - rawAlert: Alert; - isLegacy: boolean; -} - -export interface CommonAlertStatus { - exists: boolean; - enabled: boolean; - states: CommonAlertState[]; - alert: CommonBaseAlert; -} - -export interface CommonAlertState { - firing: boolean; - state: any; - meta: any; -} - -export interface CommonAlertFilter { - nodeUuid?: string; -} - -export interface CommonAlertNodeUuidFilter extends CommonAlertFilter { - nodeUuid: string; -} - -export interface CommonAlertStackProductFilter extends CommonAlertFilter { - stackProduct: string; -} - -export interface CommonAlertParamDetail { - label: string; - type: AlertParamType; -} - -export interface CommonAlertParamDetails { - [name: string]: CommonAlertParamDetail; -} - -export interface CommonAlertParams { - [name: string]: string | number; -} diff --git a/x-pack/plugins/monitoring/server/alerts/types.d.ts b/x-pack/plugins/monitoring/common/types/alerts.ts similarity index 66% rename from x-pack/plugins/monitoring/server/alerts/types.d.ts rename to x-pack/plugins/monitoring/common/types/alerts.ts index 0b346e770a299..f7a27a1b1a2b0 100644 --- a/x-pack/plugins/monitoring/server/alerts/types.d.ts +++ b/x-pack/plugins/monitoring/common/types/alerts.ts @@ -3,8 +3,60 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; -import { AlertInstanceState as BaseAlertInstanceState } from '../../../alerts/server'; + +import { Alert } from '../../../alerts/common'; +import { AlertParamType, AlertMessageTokenType, AlertSeverity } from '../enums'; + +export interface CommonBaseAlert { + type: string; + label: string; + paramDetails: CommonAlertParamDetails; + rawAlert: Alert; + isLegacy: boolean; +} + +export interface CommonAlertStatus { + exists: boolean; + enabled: boolean; + states: CommonAlertState[]; + alert: CommonBaseAlert; +} + +export interface CommonAlertState { + firing: boolean; + state: any; + meta: any; +} + +export interface CommonAlertFilter { + nodeUuid?: string; +} + +export interface CommonAlertNodeUuidFilter extends CommonAlertFilter { + nodeUuid: string; +} + +export interface CommonAlertStackProductFilter extends CommonAlertFilter { + stackProduct: string; +} + +export interface CommonAlertParamDetail { + label: string; + type?: AlertParamType; +} + +export interface CommonAlertParamDetails { + [name: string]: CommonAlertParamDetail | undefined; +} + +export interface CommonAlertParams { + [name: string]: string | number; +} + +export interface ThreadPoolRejectionsAlertParams { + threshold: number; + duration: string; +} export interface AlertEnableAction { id: string; @@ -12,7 +64,9 @@ export interface AlertEnableAction { } export interface AlertInstanceState { - alertStates: Array; + alertStates: Array< + AlertState | AlertCpuUsageState | AlertDiskUsageState | AlertThreadPoolRejectionsState + >; [x: string]: unknown; } @@ -46,6 +100,13 @@ export interface AlertMemoryUsageState extends AlertNodeState { memoryUsage: number; } +export interface AlertThreadPoolRejectionsState extends AlertState { + rejectionCount: number; + type: string; + nodeId: string; + nodeName?: string; +} + export interface AlertUiState { isFiring: boolean; severity: AlertSeverity; @@ -100,6 +161,14 @@ export interface AlertCpuUsageNodeStats extends AlertNodeStats { containerQuota: number; } +export interface AlertThreadPoolRejectionsStats { + clusterUuid: string; + nodeId: string; + nodeName: string; + rejectionCount: number; + ccs?: string; +} + export interface AlertDiskUsageNodeStats extends AlertNodeStats { diskUsage: number; } @@ -121,7 +190,7 @@ export interface AlertData { instanceKey: string; clusterUuid: string; ccs?: string; - shouldFire: boolean; + shouldFire?: boolean; severity: AlertSeverity; meta: any; } diff --git a/x-pack/plugins/monitoring/public/alerts/badge.tsx b/x-pack/plugins/monitoring/public/alerts/badge.tsx index d4e823a194f8e..5087fe7b70c06 100644 --- a/x-pack/plugins/monitoring/public/alerts/badge.tsx +++ b/x-pack/plugins/monitoring/public/alerts/badge.tsx @@ -14,11 +14,11 @@ import { EuiFlexItem, EuiText, } from '@elastic/eui'; -import { CommonAlertStatus, CommonAlertState } from '../../common/types'; +import { CommonAlertStatus, CommonAlertState } from '../../common/types/alerts'; import { AlertSeverity } from '../../common/enums'; // @ts-ignore import { formatDateTimeLocal } from '../../common/formatting'; -import { AlertMessage, AlertState } from '../../server/alerts/types'; +import { AlertMessage, AlertState } from '../../common/types/alerts'; import { AlertPanel } from './panel'; import { Legacy } from '../legacy_shims'; import { isInSetupMode } from '../lib/setup_mode'; @@ -94,7 +94,6 @@ export const AlertsBadge: React.FC = (props: Props) => { isOpen={showPopover === true} closePopover={() => setShowPopover(null)} panelPaddingSize="none" - withTitle anchorPosition="downLeft" > @@ -178,7 +177,6 @@ export const AlertsBadge: React.FC = (props: Props) => { isOpen={showPopover === type} closePopover={() => setShowPopover(null)} panelPaddingSize="none" - withTitle anchorPosition="downLeft" > diff --git a/x-pack/plugins/monitoring/public/alerts/callout.tsx b/x-pack/plugins/monitoring/public/alerts/callout.tsx index 1ddd41c268456..769d4dc7b256d 100644 --- a/x-pack/plugins/monitoring/public/alerts/callout.tsx +++ b/x-pack/plugins/monitoring/public/alerts/callout.tsx @@ -7,10 +7,10 @@ import React, { Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiCallOut, EuiSpacer } from '@elastic/eui'; -import { CommonAlertStatus } from '../../common/types'; +import { CommonAlertStatus } from '../../common/types/alerts'; import { AlertSeverity } from '../../common/enums'; import { replaceTokens } from './lib/replace_tokens'; -import { AlertMessage, AlertState } from '../../server/alerts/types'; +import { AlertMessage, AlertState } from '../../common/types/alerts'; const TYPES = [ { diff --git a/x-pack/plugins/monitoring/public/alerts/components/duration/expression.tsx b/x-pack/plugins/monitoring/public/alerts/components/duration/expression.tsx index 2df7169efc675..26593fdd6e7b0 100644 --- a/x-pack/plugins/monitoring/public/alerts/components/duration/expression.tsx +++ b/x-pack/plugins/monitoring/public/alerts/components/duration/expression.tsx @@ -6,10 +6,11 @@ import React, { Fragment } from 'react'; import { EuiForm, EuiSpacer } from '@elastic/eui'; -import { CommonAlertParamDetails } from '../../../../common/types'; +import { CommonAlertParamDetails } from '../../../../common/types/alerts'; import { AlertParamDuration } from '../../flyout_expressions/alert_param_duration'; import { AlertParamType } from '../../../../common/enums'; import { AlertParamPercentage } from '../../flyout_expressions/alert_param_percentage'; +import { AlertParamNumber } from '../../flyout_expressions/alert_param_number'; export interface Props { alertParams: { [property: string]: any }; @@ -26,14 +27,14 @@ export const Expression: React.FC = (props) => { const details = paramDetails[alertParamName]; const value = alertParams[alertParamName]; - switch (details.type) { + switch (details?.type) { case AlertParamType.Duration: return ( @@ -43,12 +44,23 @@ export const Expression: React.FC = (props) => { ); + case AlertParamType.Number: + return ( + + ); } }); diff --git a/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx b/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx index fb4ecacf57fd6..d15fe6344ec0f 100644 --- a/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx @@ -6,20 +6,17 @@ import React from 'react'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; -import { ALERT_CPU_USAGE } from '../../../common/constants'; +import { ALERT_CPU_USAGE, ALERT_DETAILS } from '../../../common/constants'; import { validate } from '../components/duration/validation'; import { Expression, Props } from '../components/duration/expression'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { CpuUsageAlert } from '../../../server/alerts'; export function createCpuUsageAlertType(): AlertTypeModel { - const alert = new CpuUsageAlert(); return { id: ALERT_CPU_USAGE, - name: alert.label, + name: ALERT_DETAILS[ALERT_CPU_USAGE].label, iconClass: 'bell', alertParamsExpression: (props: Props) => ( - + ), validate, defaultActionMessage: '{{context.internalFullMessage}}', diff --git a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx index c2abb35612b38..589b374cae32c 100644 --- a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx @@ -10,17 +10,15 @@ import { Expression, Props } from '../components/duration/expression'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; - -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { DiskUsageAlert } from '../../../server/alerts'; +import { ALERT_DISK_USAGE, ALERT_DETAILS } from '../../../common/constants'; export function createDiskUsageAlertType(): AlertTypeModel { return { - id: DiskUsageAlert.TYPE, - name: DiskUsageAlert.LABEL, + id: ALERT_DISK_USAGE, + name: ALERT_DETAILS[ALERT_DISK_USAGE].label, iconClass: 'bell', alertParamsExpression: (props: Props) => ( - + ), validate, defaultActionMessage: '{{context.internalFullMessage}}', diff --git a/x-pack/plugins/monitoring/public/alerts/filter_alert_states.ts b/x-pack/plugins/monitoring/public/alerts/filter_alert_states.ts index 63714a6921e3f..e13ea7de0e226 100644 --- a/x-pack/plugins/monitoring/public/alerts/filter_alert_states.ts +++ b/x-pack/plugins/monitoring/public/alerts/filter_alert_states.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CommonAlertState, CommonAlertStatus } from '../../common/types'; +import { CommonAlertState, CommonAlertStatus } from '../../common/types/alerts'; export function filterAlertStates( alerts: { [type: string]: CommonAlertStatus }, diff --git a/x-pack/plugins/monitoring/public/alerts/flyout_expressions/alert_param_duration.tsx b/x-pack/plugins/monitoring/public/alerts/flyout_expressions/alert_param_duration.tsx index 862f32efd7361..4ece1b0c81827 100644 --- a/x-pack/plugins/monitoring/public/alerts/flyout_expressions/alert_param_duration.tsx +++ b/x-pack/plugins/monitoring/public/alerts/flyout_expressions/alert_param_duration.tsx @@ -69,7 +69,7 @@ export const AlertParamDuration: React.FC = (props: Props) => { }, [unit, value]); return ( - 0}> + 0}> void; +} +export const AlertParamNumber: React.FC = (props: Props) => { + const { name, label, setAlertParams, errors } = props; + const [value, setValue] = useState(props.value); + return ( + 0}> + { + let newValue = Number(e.target.value); + if (isNaN(newValue)) { + newValue = 0; + } + setValue(newValue); + setAlertParams(name, newValue); + }} + /> + + ); +}; diff --git a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx index f6223d41ab30e..83201b0512dbb 100644 --- a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx @@ -3,24 +3,21 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import React, { Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiTextColor, EuiSpacer } from '@elastic/eui'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; -import { LEGACY_ALERTS } from '../../../common/constants'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { BY_TYPE } from '../../../server/alerts'; +import { LEGACY_ALERTS, LEGACY_ALERT_DETAILS } from '../../../common/constants'; export function createLegacyAlertTypes(): AlertTypeModel[] { return LEGACY_ALERTS.map((legacyAlert) => { - const alertCls = BY_TYPE[legacyAlert]; - const alert = new alertCls(); return { id: legacyAlert, - name: alert.label, + name: LEGACY_ALERT_DETAILS[legacyAlert].label, iconClass: 'bell', - alertParamsExpression: (props: any) => ( + alertParamsExpression: () => ( diff --git a/x-pack/plugins/monitoring/public/alerts/lib/replace_tokens.tsx b/x-pack/plugins/monitoring/public/alerts/lib/replace_tokens.tsx index 02f5703f66382..b8ac69cbae68a 100644 --- a/x-pack/plugins/monitoring/public/alerts/lib/replace_tokens.tsx +++ b/x-pack/plugins/monitoring/public/alerts/lib/replace_tokens.tsx @@ -11,7 +11,7 @@ import { AlertMessageTimeToken, AlertMessageLinkToken, AlertMessageDocLinkToken, -} from '../../../server/alerts/types'; +} from '../../../common/types/alerts'; // @ts-ignore import { formatTimestampToDuration } from '../../../common'; import { CALCULATE_DURATION_UNTIL } from '../../../common/constants'; diff --git a/x-pack/plugins/monitoring/public/alerts/lib/should_show_alert_badge.ts b/x-pack/plugins/monitoring/public/alerts/lib/should_show_alert_badge.ts index 0b95592d92c84..2ec5d1ba8f94a 100644 --- a/x-pack/plugins/monitoring/public/alerts/lib/should_show_alert_badge.ts +++ b/x-pack/plugins/monitoring/public/alerts/lib/should_show_alert_badge.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { isInSetupMode } from '../../lib/setup_mode'; -import { CommonAlertStatus } from '../../../common/types'; +import { CommonAlertStatus } from '../../../common/types/alerts'; import { ISetupModeContext } from '../../components/setup_mode/setup_mode_context'; export function shouldShowAlertBadge( diff --git a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx index dd60967a3458b..d3d48d907d02e 100644 --- a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx @@ -10,17 +10,15 @@ import { Expression, Props } from '../components/duration/expression'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; - -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { MemoryUsageAlert } from '../../../server/alerts'; +import { ALERT_MEMORY_USAGE, ALERT_DETAILS } from '../../../common/constants'; export function createMemoryUsageAlertType(): AlertTypeModel { return { - id: MemoryUsageAlert.TYPE, - name: MemoryUsageAlert.LABEL, + id: ALERT_MEMORY_USAGE, + name: ALERT_DETAILS[ALERT_MEMORY_USAGE].label, iconClass: 'bell', alertParamsExpression: (props: Props) => ( - + ), validate, defaultActionMessage: '{{context.internalFullMessage}}', diff --git a/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/expression.tsx b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/expression.tsx index 7dc6155de529e..ac30a02173a5c 100644 --- a/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/expression.tsx +++ b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/expression.tsx @@ -6,7 +6,7 @@ import React, { Fragment } from 'react'; import { EuiForm, EuiSpacer } from '@elastic/eui'; -import { CommonAlertParamDetails } from '../../../common/types'; +import { CommonAlertParamDetails } from '../../../common/types/alerts'; import { AlertParamDuration } from '../flyout_expressions/alert_param_duration'; import { AlertParamType } from '../../../common/enums'; import { AlertParamPercentage } from '../flyout_expressions/alert_param_percentage'; @@ -26,7 +26,7 @@ export const Expression: React.FC = (props) => { const details = paramDetails[alertParamName]; const value = alertParams[alertParamName]; - switch (details.type) { + switch (details?.type) { case AlertParamType.Duration: return ( ( - + ), validate, defaultActionMessage: '{{context.internalFullMessage}}', diff --git a/x-pack/plugins/monitoring/public/alerts/panel.tsx b/x-pack/plugins/monitoring/public/alerts/panel.tsx index eb3b6ff9da1be..99db6c8b3c945 100644 --- a/x-pack/plugins/monitoring/public/alerts/panel.tsx +++ b/x-pack/plugins/monitoring/public/alerts/panel.tsx @@ -18,8 +18,7 @@ import { EuiListGroupItem, } from '@elastic/eui'; -import { CommonAlertStatus, CommonAlertState } from '../../common/types'; -import { AlertMessage } from '../../server/alerts/types'; +import { CommonAlertStatus, CommonAlertState, AlertMessage } from '../../common/types/alerts'; import { Legacy } from '../legacy_shims'; import { replaceTokens } from './lib/replace_tokens'; import { AlertsContextProvider } from '../../../triggers_actions_ui/public'; diff --git a/x-pack/plugins/monitoring/public/alerts/status.tsx b/x-pack/plugins/monitoring/public/alerts/status.tsx index c1ad41fc8d763..53918807a4272 100644 --- a/x-pack/plugins/monitoring/public/alerts/status.tsx +++ b/x-pack/plugins/monitoring/public/alerts/status.tsx @@ -7,9 +7,8 @@ import React from 'react'; import { EuiToolTip, EuiHealth } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { CommonAlertStatus } from '../../common/types'; +import { CommonAlertStatus, AlertMessage, AlertState } from '../../common/types/alerts'; import { AlertSeverity } from '../../common/enums'; -import { AlertMessage, AlertState } from '../../server/alerts/types'; import { AlertsBadge } from './badge'; import { isInSetupMode } from '../lib/setup_mode'; import { SetupModeContext } from '../components/setup_mode/setup_mode_context'; diff --git a/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx new file mode 100644 index 0000000000000..5e8e676448218 --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiSpacer } from '@elastic/eui'; +import { Expression, Props } from '../components/duration/expression'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; +import { CommonAlertParamDetails } from '../../../common/types/alerts'; + +interface ThreadPoolTypes { + [key: string]: unknown; +} + +interface ThreadPoolRejectionAlertDetails { + label: string; + paramDetails: CommonAlertParamDetails; +} + +export function createThreadPoolRejectionsAlertType( + alertType: string, + threadPoolAlertDetails: ThreadPoolRejectionAlertDetails +): AlertTypeModel { + return { + id: alertType, + name: threadPoolAlertDetails.label, + iconClass: 'bell', + alertParamsExpression: (props: Props) => ( + <> + + + + ), + validate: (inputValues: ThreadPoolTypes) => { + const errors: { [key: string]: string[] } = {}; + const value = inputValues.threshold as number; + if (value < 0) { + const errStr = i18n.translate('xpack.monitoring.alerts.validation.lessThanZero', { + defaultMessage: 'This value can not be less than zero', + }); + errors.threshold = [errStr]; + } + + if (!inputValues.duration) { + const errStr = i18n.translate('xpack.monitoring.alerts.validation.duration', { + defaultMessage: 'A valid duration is required.', + }); + errors.duration = [errStr]; + } + + return { errors }; + }, + defaultActionMessage: '{{context.internalFullMessage}}', + requiresAppContext: true, + }; +} diff --git a/x-pack/plugins/monitoring/public/angular/providers/private.js b/x-pack/plugins/monitoring/public/angular/providers/private.js index 3a667037b2919..7709865432fe6 100644 --- a/x-pack/plugins/monitoring/public/angular/providers/private.js +++ b/x-pack/plugins/monitoring/public/angular/providers/private.js @@ -81,9 +81,9 @@ * * @param {[type]} prov [description] */ -import _ from 'lodash'; +import { partial, uniqueId, isObject } from 'lodash'; -const nextId = _.partial(_.uniqueId, 'privateProvider#'); +const nextId = partial(uniqueId, 'privateProvider#'); function name(fn) { return fn.name || fn.toString().split('\n').shift(); @@ -141,7 +141,7 @@ export function PrivateProvider() { const context = {}; let instance = $injector.invoke(prov, context, locals); - if (!_.isObject(instance)) instance = context; + if (!isObject(instance)) instance = context; privPath.pop(); return instance; @@ -155,7 +155,7 @@ export function PrivateProvider() { if ($delegateId != null && $delegateProv != null) { instance = instantiate(prov, { - $decorate: _.partial(get, $delegateId, $delegateProv), + $decorate: partial(get, $delegateId, $delegateProv), }); } else { instance = instantiate(prov); diff --git a/x-pack/plugins/monitoring/public/components/chart/chart_target.js b/x-pack/plugins/monitoring/public/components/chart/chart_target.js index 9a590d803bb19..519964e4d5914 100644 --- a/x-pack/plugins/monitoring/public/components/chart/chart_target.js +++ b/x-pack/plugins/monitoring/public/components/chart/chart_target.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; +import { get, isEqual, filter } from 'lodash'; import $ from 'jquery'; import React from 'react'; import { eventBus } from './event_bus'; @@ -50,12 +50,12 @@ export class ChartTarget extends React.Component { } UNSAFE_componentWillReceiveProps(newProps) { - if (this.plot && !_.isEqual(newProps, this.props)) { + if (this.plot && !isEqual(newProps, this.props)) { const { series, timeRange } = newProps; const xaxisOptions = this.plot.getAxes().xaxis.options; - xaxisOptions.min = _.get(timeRange, 'min'); - xaxisOptions.max = _.get(timeRange, 'max'); + xaxisOptions.min = get(timeRange, 'min'); + xaxisOptions.max = get(timeRange, 'max'); this.plot.setData(this.filterData(series, newProps.seriesToShow)); this.plot.setupGrid(); @@ -73,7 +73,7 @@ export class ChartTarget extends React.Component { } filterData(data, seriesToShow) { - return _(data).filter(this.filterByShow(seriesToShow)).value(); + return filter(data, this.filterByShow(seriesToShow)); } async getOptions() { @@ -128,7 +128,7 @@ export class ChartTarget extends React.Component { this.handleThorPlotHover = (_event, pos, item, originalPlot) => { if (this.plot !== originalPlot) { // the crosshair is set for the original chart already - this.plot.setCrosshair({ x: _.get(pos, 'x') }); + this.plot.setCrosshair({ x: get(pos, 'x') }); } this.props.updateLegend(pos, item); }; diff --git a/x-pack/plugins/monitoring/public/components/chart/timeseries_visualization.js b/x-pack/plugins/monitoring/public/components/chart/timeseries_visualization.js index eb32ee108e7b3..829994791f769 100644 --- a/x-pack/plugins/monitoring/public/components/chart/timeseries_visualization.js +++ b/x-pack/plugins/monitoring/public/components/chart/timeseries_visualization.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; +import { debounce, keys, has, includes, isFunction, difference, assign } from 'lodash'; import React from 'react'; import { getLastValue } from './get_last_value'; import { TimeseriesContainer } from './timeseries_container'; @@ -17,7 +17,7 @@ export class TimeseriesVisualization extends React.Component { constructor(props) { super(props); - this.debouncedUpdateLegend = _.debounce(this.updateLegend, DEBOUNCE_SLOW_MS); + this.debouncedUpdateLegend = debounce(this.updateLegend, DEBOUNCE_SLOW_MS); this.debouncedUpdateLegend = this.debouncedUpdateLegend.bind(this); this.toggleFilter = this.toggleFilter.bind(this); @@ -26,18 +26,18 @@ export class TimeseriesVisualization extends React.Component { this.state = { values: {}, - seriesToShow: _.keys(values), + seriesToShow: keys(values), ignoreVisibilityUpdates: false, }; } filterLegend(id) { - if (!_.has(this.state.values, id)) { + if (!has(this.state.values, id)) { return []; } - const notAllShown = _.keys(this.state.values).length !== this.state.seriesToShow.length; - const isCurrentlyShown = _.includes(this.state.seriesToShow, id); + const notAllShown = keys(this.state.values).length !== this.state.seriesToShow.length; + const isCurrentlyShown = includes(this.state.seriesToShow, id); const seriesToShow = []; if (notAllShown && isCurrentlyShown) { @@ -59,7 +59,7 @@ export class TimeseriesVisualization extends React.Component { toggleFilter(_event, id) { const seriesToShow = this.filterLegend(id); - if (_.isFunction(this.props.onFilter)) { + if (isFunction(this.props.onFilter)) { this.props.onFilter(seriesToShow); } } @@ -94,7 +94,7 @@ export class TimeseriesVisualization extends React.Component { getValuesByX(this.props.series, pos.x, setValueCallback); } } else { - _.assign(values, this.getLastValues()); + assign(values, this.getLastValues()); } this.setState({ values }); @@ -102,13 +102,13 @@ export class TimeseriesVisualization extends React.Component { UNSAFE_componentWillReceiveProps(props) { const values = this.getLastValues(props); - const currentKeys = _.keys(this.state.values); - const keys = _.keys(values); - const diff = _.difference(keys, currentKeys); + const currentKeys = keys(this.state.values); + const valueKeys = keys(values); + const diff = difference(valueKeys, currentKeys); const nextState = { values: values }; if (diff.length && !this.state.ignoreVisibilityUpdates) { - nextState.seriesToShow = keys; + nextState.seriesToShow = valueKeys; } this.setState(nextState); diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js index 0fe434afa2c88..7e85d62c4bbd6 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js @@ -41,6 +41,8 @@ import { ALERT_CLUSTER_HEALTH, ALERT_CPU_USAGE, ALERT_DISK_USAGE, + ALERT_THREAD_POOL_SEARCH_REJECTIONS, + ALERT_THREAD_POOL_WRITE_REJECTIONS, ALERT_MEMORY_USAGE, ALERT_NODES_CHANGED, ALERT_ELASTICSEARCH_VERSION_MISMATCH, @@ -162,6 +164,8 @@ const OVERVIEW_PANEL_ALERTS = [ALERT_CLUSTER_HEALTH, ALERT_LICENSE_EXPIRATION]; const NODES_PANEL_ALERTS = [ ALERT_CPU_USAGE, ALERT_DISK_USAGE, + ALERT_THREAD_POOL_SEARCH_REJECTIONS, + ALERT_THREAD_POOL_WRITE_REJECTIONS, ALERT_MEMORY_USAGE, ALERT_NODES_CHANGED, ALERT_ELASTICSEARCH_VERSION_MISMATCH, diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js index 41d3a579db5a2..61188487e2f99 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js @@ -27,7 +27,7 @@ import { EuiHealth, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import _ from 'lodash'; +import { get } from 'lodash'; import { ELASTICSEARCH_SYSTEM_ID } from '../../../../common/constants'; import { FormattedMessage } from '@kbn/i18n/react'; import { ListingCallOut } from '../../setup_mode/listing_callout'; @@ -58,7 +58,7 @@ const getNodeTooltip = (node) => { return null; }; -const getSortHandler = (type) => (item) => _.get(item, [type, 'summary', 'lastVal']); +const getSortHandler = (type) => (item) => get(item, [type, 'summary', 'lastVal']); const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, alerts) => { const cols = []; @@ -87,7 +87,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler let setupModeStatus = null; if (isSetupModeFeatureEnabled(SetupModeFeature.MetricbeatMigration)) { - const list = _.get(setupMode, 'data.byUuid', {}); + const list = get(setupMode, 'data.byUuid', {}); const status = list[node.resolver] || {}; const instance = { uuid: node.resolver, @@ -396,7 +396,7 @@ export function ElasticsearchNodes({ clusterStatus, showCgroupMetricsElasticsear setupMode.data.totalUniqueInstanceCount ) { const finishMigrationAction = - _.get(setupMode.meta, 'liveClusterUuid') === clusterUuid + get(setupMode.meta, 'liveClusterUuid') === clusterUuid ? setupMode.shortcutToFinishMigration : setupMode.openFlyout; diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/unassigned.js b/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/unassigned.js index 2c66d14a40605..5c8dca54894b4 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/unassigned.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/unassigned.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; +import { sortBy } from 'lodash'; import React from 'react'; import { Shard } from './shard'; import { i18n } from '@kbn/i18n'; @@ -36,7 +36,7 @@ export class Unassigned extends React.Component { }; render() { - const shards = _.sortBy(this.props.shards, 'shard').map(this.createShard); + const shards = sortBy(this.props.shards, 'shard').map(this.createShard); return ( diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/lib/has_primary_children.js b/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/lib/has_primary_children.js index 47739b8fe31e8..a371f3e5ff40c 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/lib/has_primary_children.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/lib/has_primary_children.js @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; +import { some } from 'lodash'; export function hasPrimaryChildren(item) { - return _.some(item.children, { primary: true }); + return some(item.children, { primary: true }); } diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/lib/vents.js b/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/lib/vents.js index db9b7bacc3cdf..335c3d29a5b9e 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/lib/vents.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/lib/vents.js @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; +import { each, isArray } from 'lodash'; export const _vents = {}; export const vents = { vents: _vents, on: function (id, cb) { - if (!_.isArray(_vents[id])) { + if (!isArray(_vents[id])) { _vents[id] = []; } _vents[id].push(cb); @@ -22,7 +22,7 @@ export const vents = { const args = Array.prototype.slice.call(arguments); const id = args.shift(); if (_vents[id]) { - _.each(_vents[id], function (cb) { + each(_vents[id], function (cb) { cb.apply(null, args); }); } diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/transformers/indices_by_nodes.js b/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/transformers/indices_by_nodes.js index a9808ebc4c6ad..a04e2bcd1786e 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/transformers/indices_by_nodes.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/transformers/indices_by_nodes.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; +import { find, reduce, values, sortBy } from 'lodash'; import { decorateShards } from '../lib/decorate_shards'; export function indicesByNodes() { @@ -39,7 +39,7 @@ export function indicesByNodes() { return obj; } - let nodeObj = _.find(obj[index].children, { id: node }); + let nodeObj = find(obj[index].children, { id: node }); if (!nodeObj) { nodeObj = { id: node, @@ -55,7 +55,7 @@ export function indicesByNodes() { return obj; } - const data = _.reduce( + const data = reduce( decorateShards(shards, nodes), function (obj, shard) { obj = createIndex(obj, shard); @@ -64,10 +64,11 @@ export function indicesByNodes() { }, {} ); - - return _(data) - .values() - .sortBy((index) => [!index.unassignedPrimaries, /^\./.test(index.name), index.name]) - .value(); + const dataValues = values(data); + return sortBy(dataValues, (index) => [ + !index.unassignedPrimaries, + /^\./.test(index.name), + index.name, + ]); }; } diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/transformers/nodes_by_indices.js b/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/transformers/nodes_by_indices.js index 353e1c23d4bc1..f8dd5b6cb8e8d 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/transformers/nodes_by_indices.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/shard_allocation/transformers/nodes_by_indices.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; +import { find, some, reduce, values, sortBy } from 'lodash'; import { hasPrimaryChildren } from '../lib/has_primary_children'; import { decorateShards } from '../lib/decorate_shards'; @@ -32,7 +32,7 @@ export function nodesByIndices() { if (!obj[node]) { createNode(obj, nodes[node], node); } - let indexObj = _.find(obj[node].children, { id: index }); + let indexObj = find(obj[node].children, { id: index }); if (!indexObj) { indexObj = { id: index, @@ -51,7 +51,7 @@ export function nodesByIndices() { } let data = {}; - if (_.some(shards, isUnassigned)) { + if (some(shards, isUnassigned)) { data.unassigned = { name: 'Unassigned', master: false, @@ -60,19 +60,15 @@ export function nodesByIndices() { }; } - data = _.reduce(decorateShards(shards, nodes), createIndexAddShard, data); - - return _(data) - .values() - .sortBy(function (node) { - return [node.name !== 'Unassigned', !node.master, node.name]; - }) - .map(function (node) { - if (node.name === 'Unassigned') { - node.unassignedPrimaries = node.children.some(hasPrimaryChildren); - } - return node; - }) - .value(); + data = reduce(decorateShards(shards, nodes), createIndexAddShard, data); + const dataValues = values(data); + return sortBy(dataValues, function (node) { + return [node.name !== 'Unassigned', !node.master, node.name]; + }).map(function (node) { + if (node.name === 'Unassigned') { + node.unassignedPrimaries = node.children.some(hasPrimaryChildren); + } + return node; + }); }; } diff --git a/x-pack/plugins/monitoring/public/components/no_data/__tests__/checker_errors.test.js b/x-pack/plugins/monitoring/public/components/no_data/__tests__/checker_errors.test.js index 8902460507eb0..22700bf3fb5a8 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/__tests__/checker_errors.test.js +++ b/x-pack/plugins/monitoring/public/components/no_data/__tests__/checker_errors.test.js @@ -5,7 +5,7 @@ */ import React from 'react'; -import { boomify, forbidden } from 'boom'; +import { boomify, forbidden } from '@hapi/boom'; import { renderWithIntl } from 'test_utils/enzyme_helpers'; import { CheckerErrors } from '../checker_errors'; diff --git a/x-pack/plugins/monitoring/public/components/no_data/explanations/collection_enabled/__tests__/__snapshots__/collection_enabled.test.js.snap b/x-pack/plugins/monitoring/public/components/no_data/explanations/collection_enabled/__tests__/__snapshots__/collection_enabled.test.js.snap index deed4687e74f6..046f2bfa92247 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/explanations/collection_enabled/__tests__/__snapshots__/collection_enabled.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/no_data/explanations/collection_enabled/__tests__/__snapshots__/collection_enabled.test.js.snap @@ -248,6 +248,7 @@ exports[`ExplainCollectionEnabled should explain about xpack.monitoring.collecti type="button" > - + - + properties; const updateModelSpy = sinon.spy(updateModel); diff --git a/x-pack/plugins/monitoring/public/lib/get_cluster_from_clusters.js b/x-pack/plugins/monitoring/public/lib/get_cluster_from_clusters.js index 73422219add95..6e05c02ac7338 100644 --- a/x-pack/plugins/monitoring/public/lib/get_cluster_from_clusters.js +++ b/x-pack/plugins/monitoring/public/lib/get_cluster_from_clusters.js @@ -4,16 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; +import { find, first } from 'lodash'; export function getClusterFromClusters(clusters, globalState, unsetGlobalState = false) { const cluster = (() => { - const existingCurrent = _.find(clusters, { cluster_uuid: globalState.cluster_uuid }); + const existingCurrent = find(clusters, { cluster_uuid: globalState.cluster_uuid }); if (existingCurrent) { return existingCurrent; } - const firstCluster = _.first(clusters); + const firstCluster = first(clusters); if (firstCluster && firstCluster.cluster_uuid) { return firstCluster; } diff --git a/x-pack/plugins/monitoring/public/lib/route_init.js b/x-pack/plugins/monitoring/public/lib/route_init.js index eebdfa8692f1a..97ff621ee3164 100644 --- a/x-pack/plugins/monitoring/public/lib/route_init.js +++ b/x-pack/plugins/monitoring/public/lib/route_init.js @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; import { ajaxErrorHandlersProvider } from './ajax_error_handler'; import { isInSetupMode } from './setup_mode'; import { getClusterFromClusters } from './get_cluster_from_clusters'; @@ -13,7 +12,7 @@ export function routeInitProvider(Private, monitoringClusters, globalState, lice const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); function isOnPage(hash) { - return _.includes(window.location.hash, hash); + return window.location.hash.includes(hash); } /* diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts index 4c50abb40dd3d..a228c540761b8 100644 --- a/x-pack/plugins/monitoring/public/plugin.ts +++ b/x-pack/plugins/monitoring/public/plugin.ts @@ -22,11 +22,11 @@ import { UI_SETTINGS } from '../../../../src/plugins/data/public'; import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; import { MonitoringStartPluginDependencies, MonitoringConfig } from './types'; import { TriggersAndActionsUIPublicPluginSetup } from '../../triggers_actions_ui/public'; -import { createCpuUsageAlertType } from './alerts/cpu_usage_alert'; -import { createMissingMonitoringDataAlertType } from './alerts/missing_monitoring_data_alert'; -import { createLegacyAlertTypes } from './alerts/legacy_alert'; -import { createDiskUsageAlertType } from './alerts/disk_usage_alert'; -import { createMemoryUsageAlertType } from './alerts/memory_usage_alert'; +import { + ALERT_THREAD_POOL_SEARCH_REJECTIONS, + ALERT_THREAD_POOL_WRITE_REJECTIONS, + ALERT_DETAILS, +} from '../common/constants'; interface MonitoringSetupPluginDependencies { home?: HomePublicPluginSetup; @@ -40,7 +40,7 @@ export class MonitoringPlugin Plugin { constructor(private initializerContext: PluginInitializerContext) {} - public setup( + public async setup( core: CoreSetup, plugins: MonitoringSetupPluginDependencies ) { @@ -73,16 +73,7 @@ export class MonitoringPlugin }); } - const { alertTypeRegistry } = plugins.triggersActionsUi; - alertTypeRegistry.register(createCpuUsageAlertType()); - alertTypeRegistry.register(createDiskUsageAlertType()); - alertTypeRegistry.register(createMemoryUsageAlertType()); - alertTypeRegistry.register(createMissingMonitoringDataAlertType()); - - const legacyAlertTypes = createLegacyAlertTypes(); - for (const legacyAlertType of legacyAlertTypes) { - alertTypeRegistry.register(legacyAlertType); - } + await this.registerAlertsAsync(plugins); const app: App = { id, @@ -106,7 +97,6 @@ export class MonitoringPlugin usageCollection: plugins.usageCollection, }; - pluginsStart.kibanaLegacy.loadFontAwesome(); this.setInitialTimefilter(deps); const monitoringApp = new AngularApp(deps); @@ -154,4 +144,41 @@ export class MonitoringPlugin ['showCgroupMetricsLogstash', monitoring.ui.container.logstash.enabled], ]; } + + private registerAlertsAsync = async (plugins: MonitoringSetupPluginDependencies) => { + const { createCpuUsageAlertType } = await import('./alerts/cpu_usage_alert'); + const { createMissingMonitoringDataAlertType } = await import( + './alerts/missing_monitoring_data_alert' + ); + const { createLegacyAlertTypes } = await import('./alerts/legacy_alert'); + const { createDiskUsageAlertType } = await import('./alerts/disk_usage_alert'); + const { createThreadPoolRejectionsAlertType } = await import( + './alerts/thread_pool_rejections_alert' + ); + const { createMemoryUsageAlertType } = await import('./alerts/memory_usage_alert'); + + const { + triggersActionsUi: { alertTypeRegistry }, + } = plugins; + alertTypeRegistry.register(createCpuUsageAlertType()); + alertTypeRegistry.register(createDiskUsageAlertType()); + alertTypeRegistry.register(createMemoryUsageAlertType()); + alertTypeRegistry.register(createMissingMonitoringDataAlertType()); + alertTypeRegistry.register( + createThreadPoolRejectionsAlertType( + ALERT_THREAD_POOL_SEARCH_REJECTIONS, + ALERT_DETAILS[ALERT_THREAD_POOL_SEARCH_REJECTIONS] + ) + ); + alertTypeRegistry.register( + createThreadPoolRejectionsAlertType( + ALERT_THREAD_POOL_WRITE_REJECTIONS, + ALERT_DETAILS[ALERT_THREAD_POOL_WRITE_REJECTIONS] + ) + ); + const legacyAlertTypes = createLegacyAlertTypes(); + for (const legacyAlertType of legacyAlertTypes) { + alertTypeRegistry.register(legacyAlertType); + } + }; } diff --git a/x-pack/plugins/monitoring/public/services/features.js b/x-pack/plugins/monitoring/public/services/features.js index f98af10f8dfb4..5e29353e497d1 100644 --- a/x-pack/plugins/monitoring/public/services/features.js +++ b/x-pack/plugins/monitoring/public/services/features.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; +import { has, isUndefined } from 'lodash'; export function featuresProvider($window) { function getData() { @@ -28,11 +28,11 @@ export function featuresProvider($window) { function isEnabled(featureName, defaultSetting) { const monitoringDataObj = getData(); - if (_.has(monitoringDataObj, featureName)) { + if (has(monitoringDataObj, featureName)) { return monitoringDataObj[featureName]; } - if (_.isUndefined(defaultSetting)) { + if (isUndefined(defaultSetting)) { return false; } diff --git a/x-pack/plugins/monitoring/public/services/title.js b/x-pack/plugins/monitoring/public/services/title.js index 0715f4dc9e0b6..91ef4c32f3b98 100644 --- a/x-pack/plugins/monitoring/public/services/title.js +++ b/x-pack/plugins/monitoring/public/services/title.js @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; +import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import { Legacy } from '../legacy_shims'; export function titleProvider($rootScope) { return function changeTitle(cluster, suffix) { - let clusterName = _.get(cluster, 'cluster_name'); + let clusterName = get(cluster, 'cluster_name'); clusterName = clusterName ? `- ${clusterName}` : ''; suffix = suffix ? `- ${suffix}` : ''; $rootScope.$applyAsync(() => { diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js index 8021ae7e5f63c..7e78170d1117f 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/node/advanced/index.js @@ -20,6 +20,8 @@ import { MonitoringViewBaseController } from '../../../base_controller'; import { CODE_PATH_ELASTICSEARCH, ALERT_CPU_USAGE, + ALERT_THREAD_POOL_SEARCH_REJECTIONS, + ALERT_THREAD_POOL_WRITE_REJECTIONS, ALERT_MISSING_MONITORING_DATA, ALERT_DISK_USAGE, ALERT_MEMORY_USAGE, @@ -76,6 +78,8 @@ uiRoutes.when('/elasticsearch/nodes/:node/advanced', { alertTypeIds: [ ALERT_CPU_USAGE, ALERT_DISK_USAGE, + ALERT_THREAD_POOL_SEARCH_REJECTIONS, + ALERT_THREAD_POOL_WRITE_REJECTIONS, ALERT_MEMORY_USAGE, ALERT_MISSING_MONITORING_DATA, ], diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js index 5164e93c266ca..586261eecb250 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js @@ -21,6 +21,8 @@ import { MonitoringViewBaseController } from '../../base_controller'; import { CODE_PATH_ELASTICSEARCH, ALERT_CPU_USAGE, + ALERT_THREAD_POOL_SEARCH_REJECTIONS, + ALERT_THREAD_POOL_WRITE_REJECTIONS, ALERT_MISSING_MONITORING_DATA, ALERT_DISK_USAGE, ALERT_MEMORY_USAGE, @@ -60,6 +62,8 @@ uiRoutes.when('/elasticsearch/nodes/:node', { alertTypeIds: [ ALERT_CPU_USAGE, ALERT_DISK_USAGE, + ALERT_THREAD_POOL_SEARCH_REJECTIONS, + ALERT_THREAD_POOL_WRITE_REJECTIONS, ALERT_MEMORY_USAGE, ALERT_MISSING_MONITORING_DATA, ], diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js index e69d572f9560b..3ec9c6235867b 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/nodes/index.js @@ -19,6 +19,8 @@ import { ELASTICSEARCH_SYSTEM_ID, CODE_PATH_ELASTICSEARCH, ALERT_CPU_USAGE, + ALERT_THREAD_POOL_SEARCH_REJECTIONS, + ALERT_THREAD_POOL_WRITE_REJECTIONS, ALERT_MISSING_MONITORING_DATA, ALERT_DISK_USAGE, ALERT_MEMORY_USAGE, @@ -93,6 +95,8 @@ uiRoutes.when('/elasticsearch/nodes', { alertTypeIds: [ ALERT_CPU_USAGE, ALERT_DISK_USAGE, + ALERT_THREAD_POOL_SEARCH_REJECTIONS, + ALERT_THREAD_POOL_WRITE_REJECTIONS, ALERT_MEMORY_USAGE, ALERT_MISSING_MONITORING_DATA, ], diff --git a/x-pack/plugins/monitoring/server/alerts/alerts_common.ts b/x-pack/plugins/monitoring/server/alerts/alert_helpers.ts similarity index 97% rename from x-pack/plugins/monitoring/server/alerts/alerts_common.ts rename to x-pack/plugins/monitoring/server/alerts/alert_helpers.ts index 41c8bba17df0a..984746e59f06b 100644 --- a/x-pack/plugins/monitoring/server/alerts/alerts_common.ts +++ b/x-pack/plugins/monitoring/server/alerts/alert_helpers.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { AlertMessageDocLinkToken } from './types'; +import { AlertMessageDocLinkToken } from '../../common/types/alerts'; import { AlertMessageTokenType } from '../../common/enums'; export class AlertingDefaults { diff --git a/x-pack/plugins/monitoring/server/alerts/alerts_factory.test.ts b/x-pack/plugins/monitoring/server/alerts/alerts_factory.test.ts index f486061109b39..cc0423051f2aa 100644 --- a/x-pack/plugins/monitoring/server/alerts/alerts_factory.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/alerts_factory.test.ts @@ -60,9 +60,4 @@ describe('AlertsFactory', () => { expect(alert).not.toBeNull(); expect(alert?.type).toBe(ALERT_CPU_USAGE); }); - - it('should get all', () => { - const alerts = AlertsFactory.getAll(); - expect(alerts.length).toBe(10); - }); }); diff --git a/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts b/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts index 22c41c9c60038..efd3d7d5e3b30 100644 --- a/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts +++ b/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts @@ -8,6 +8,8 @@ import { CpuUsageAlert, MissingMonitoringDataAlert, DiskUsageAlert, + ThreadPoolSearchRejectionsAlert, + ThreadPoolWriteRejectionsAlert, MemoryUsageAlert, NodesChangedAlert, ClusterHealthAlert, @@ -23,6 +25,8 @@ import { ALERT_CPU_USAGE, ALERT_MISSING_MONITORING_DATA, ALERT_DISK_USAGE, + ALERT_THREAD_POOL_SEARCH_REJECTIONS, + ALERT_THREAD_POOL_WRITE_REJECTIONS, ALERT_MEMORY_USAGE, ALERT_NODES_CHANGED, ALERT_LOGSTASH_VERSION_MISMATCH, @@ -31,12 +35,14 @@ import { } from '../../common/constants'; import { AlertsClient } from '../../../alerts/server'; -export const BY_TYPE = { +const BY_TYPE = { [ALERT_CLUSTER_HEALTH]: ClusterHealthAlert, [ALERT_LICENSE_EXPIRATION]: LicenseExpirationAlert, [ALERT_CPU_USAGE]: CpuUsageAlert, [ALERT_MISSING_MONITORING_DATA]: MissingMonitoringDataAlert, [ALERT_DISK_USAGE]: DiskUsageAlert, + [ALERT_THREAD_POOL_SEARCH_REJECTIONS]: ThreadPoolSearchRejectionsAlert, + [ALERT_THREAD_POOL_WRITE_REJECTIONS]: ThreadPoolWriteRejectionsAlert, [ALERT_MEMORY_USAGE]: MemoryUsageAlert, [ALERT_NODES_CHANGED]: NodesChangedAlert, [ALERT_LOGSTASH_VERSION_MISMATCH]: LogstashVersionMismatchAlert, diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.ts index c92291cf72093..48b783a450807 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.ts @@ -28,14 +28,16 @@ import { AlertData, AlertInstanceState, AlertEnableAction, -} from './types'; + CommonAlertFilter, + CommonAlertParams, + CommonBaseAlert, +} from '../../common/types/alerts'; import { fetchAvailableCcs } from '../lib/alerts/fetch_available_ccs'; import { fetchClusters } from '../lib/alerts/fetch_clusters'; import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants'; import { MonitoringConfig } from '../config'; import { AlertSeverity } from '../../common/enums'; -import { CommonAlertFilter, CommonAlertParams, CommonBaseAlert } from '../../common/types'; import { MonitoringLicenseService } from '../types'; import { mbSafeQuery } from '../lib/mb_safe_query'; import { appendMetricbeatIndex } from '../lib/alerts/append_mb_index'; @@ -269,18 +271,18 @@ export class BaseAlert { } protected async fetchData( - params: CommonAlertParams, + params: CommonAlertParams | unknown, callCluster: any, clusters: AlertCluster[], uiSettings: IUiSettingsClient, availableCcs: string[] - ): Promise { + ): Promise> { // Child should implement throw new Error('Child classes must implement `fetchData`'); } protected async processData( - data: AlertData[], + data: Array, clusters: AlertCluster[], services: AlertServices, logger: Logger, @@ -365,15 +367,18 @@ export class BaseAlert { }; } - protected getUiMessage(alertState: AlertState, item: AlertData): AlertMessage { + protected getUiMessage( + alertState: AlertState | unknown, + item: AlertData | unknown + ): AlertMessage { throw new Error('Child classes must implement `getUiMessage`'); } protected executeActions( instance: AlertInstance, - instanceState: AlertInstanceState, - item: AlertData, - cluster: AlertCluster + instanceState: AlertInstanceState | unknown, + item: AlertData | unknown, + cluster?: AlertCluster | unknown ) { throw new Error('Child classes must implement `executeActions`'); } diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.ts b/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.ts index 427dd2f86de00..1d3d36413ebc2 100644 --- a/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.ts @@ -14,15 +14,15 @@ import { AlertMessageLinkToken, AlertInstanceState, LegacyAlert, -} from './types'; + CommonAlertParams, +} from '../../common/types/alerts'; import { AlertInstance } from '../../../alerts/server'; -import { INDEX_ALERTS, ALERT_CLUSTER_HEALTH } from '../../common/constants'; +import { INDEX_ALERTS, ALERT_CLUSTER_HEALTH, LEGACY_ALERT_DETAILS } from '../../common/constants'; import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; import { AlertMessageTokenType, AlertClusterHealthType } from '../../common/enums'; import { fetchLegacyAlerts } from '../lib/alerts/fetch_legacy_alerts'; import { mapLegacySeverity } from '../lib/alerts/map_legacy_severity'; -import { CommonAlertParams } from '../../common/types'; -import { AlertingDefaults } from './alerts_common'; +import { AlertingDefaults } from './alert_helpers'; const RED_STATUS_MESSAGE = i18n.translate('xpack.monitoring.alerts.clusterHealth.redMessage', { defaultMessage: 'Allocate missing primary and replica shards', @@ -39,9 +39,7 @@ const WATCH_NAME = 'elasticsearch_cluster_status'; export class ClusterHealthAlert extends BaseAlert { public type = ALERT_CLUSTER_HEALTH; - public label = i18n.translate('xpack.monitoring.alerts.clusterHealth.label', { - defaultMessage: 'Cluster health', - }); + public label = LEGACY_ALERT_DETAILS[ALERT_CLUSTER_HEALTH].label; public isLegacy = true; protected actionVariables = [ diff --git a/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts b/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts index 09133dadca162..55931e2996cbf 100644 --- a/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts @@ -16,55 +16,36 @@ import { AlertMessageTimeToken, AlertMessageLinkToken, AlertInstanceState, -} from './types'; + CommonAlertFilter, + CommonAlertNodeUuidFilter, + CommonAlertParams, +} from '../../common/types/alerts'; import { AlertInstance, AlertServices } from '../../../alerts/server'; -import { INDEX_PATTERN_ELASTICSEARCH, ALERT_CPU_USAGE } from '../../common/constants'; +import { + INDEX_PATTERN_ELASTICSEARCH, + ALERT_CPU_USAGE, + ALERT_DETAILS, +} from '../../common/constants'; import { fetchCpuUsageNodeStats } from '../lib/alerts/fetch_cpu_usage_node_stats'; import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; -import { AlertMessageTokenType, AlertSeverity, AlertParamType } from '../../common/enums'; +import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; import { RawAlertInstance } from '../../../alerts/common'; import { parseDuration } from '../../../alerts/common/parse_duration'; -import { - CommonAlertFilter, - CommonAlertNodeUuidFilter, - CommonAlertParams, - CommonAlertParamDetail, -} from '../../common/types'; -import { AlertingDefaults, createLink } from './alerts_common'; +import { AlertingDefaults, createLink } from './alert_helpers'; import { appendMetricbeatIndex } from '../lib/alerts/append_mb_index'; -const DEFAULT_THRESHOLD = 85; -const DEFAULT_DURATION = '5m'; - interface CpuUsageParams { threshold: number; duration: string; } export class CpuUsageAlert extends BaseAlert { - public static paramDetails = { - threshold: { - label: i18n.translate('xpack.monitoring.alerts.cpuUsage.paramDetails.threshold.label', { - defaultMessage: `Notify when CPU is over`, - }), - type: AlertParamType.Percentage, - } as CommonAlertParamDetail, - duration: { - label: i18n.translate('xpack.monitoring.alerts.cpuUsage.paramDetails.duration.label', { - defaultMessage: `Look at the average over`, - }), - type: AlertParamType.Duration, - } as CommonAlertParamDetail, - }; - public type = ALERT_CPU_USAGE; - public label = i18n.translate('xpack.monitoring.alerts.cpuUsage.label', { - defaultMessage: 'CPU Usage', - }); + public label = ALERT_DETAILS[ALERT_CPU_USAGE].label; protected defaultParams: CpuUsageParams = { - threshold: DEFAULT_THRESHOLD, - duration: DEFAULT_DURATION, + threshold: 85, + duration: '5m', }; protected actionVariables = [ diff --git a/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.ts b/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.ts index 34c640de79625..e54e736724357 100644 --- a/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.ts @@ -15,43 +15,25 @@ import { AlertMessageTimeToken, AlertMessageLinkToken, AlertInstanceState, -} from './types'; + CommonAlertFilter, + CommonAlertParams, +} from '../../common/types/alerts'; import { AlertInstance, AlertServices } from '../../../alerts/server'; -import { INDEX_PATTERN_ELASTICSEARCH, ALERT_DISK_USAGE } from '../../common/constants'; +import { + INDEX_PATTERN_ELASTICSEARCH, + ALERT_DISK_USAGE, + ALERT_DETAILS, +} from '../../common/constants'; import { fetchDiskUsageNodeStats } from '../lib/alerts/fetch_disk_usage_node_stats'; import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; -import { AlertMessageTokenType, AlertSeverity, AlertParamType } from '../../common/enums'; +import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; import { RawAlertInstance } from '../../../alerts/common'; -import { CommonAlertFilter, CommonAlertParams, CommonAlertParamDetail } from '../../common/types'; -import { AlertingDefaults, createLink } from './alerts_common'; +import { AlertingDefaults, createLink } from './alert_helpers'; import { appendMetricbeatIndex } from '../lib/alerts/append_mb_index'; -interface ParamDetails { - [key: string]: CommonAlertParamDetail; -} - export class DiskUsageAlert extends BaseAlert { - public static readonly PARAM_DETAILS: ParamDetails = { - threshold: { - label: i18n.translate('xpack.monitoring.alerts.diskUsage.paramDetails.threshold.label', { - defaultMessage: `Notify when disk capacity is over`, - }), - type: AlertParamType.Percentage, - }, - duration: { - label: i18n.translate('xpack.monitoring.alerts.diskUsage.paramDetails.duration.label', { - defaultMessage: `Look at the average over`, - }), - type: AlertParamType.Duration, - }, - }; - public static paramDetails = DiskUsageAlert.PARAM_DETAILS; - public static readonly TYPE = ALERT_DISK_USAGE; - public static readonly LABEL = i18n.translate('xpack.monitoring.alerts.diskUsage.label', { - defaultMessage: 'Disk Usage', - }); - public type = DiskUsageAlert.TYPE; - public label = DiskUsageAlert.LABEL; + public type = ALERT_DISK_USAGE; + public label = ALERT_DETAILS[ALERT_DISK_USAGE].label; protected defaultParams = { threshold: 80, diff --git a/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.ts b/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.ts index f26b21f0c64c5..6412dcfde54bd 100644 --- a/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.ts @@ -13,22 +13,24 @@ import { AlertMessage, AlertInstanceState, LegacyAlert, -} from './types'; + CommonAlertParams, +} from '../../common/types/alerts'; import { AlertInstance } from '../../../alerts/server'; -import { INDEX_ALERTS, ALERT_ELASTICSEARCH_VERSION_MISMATCH } from '../../common/constants'; +import { + INDEX_ALERTS, + ALERT_ELASTICSEARCH_VERSION_MISMATCH, + LEGACY_ALERT_DETAILS, +} from '../../common/constants'; import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; import { AlertSeverity } from '../../common/enums'; -import { CommonAlertParams } from '../../common/types'; import { fetchLegacyAlerts } from '../lib/alerts/fetch_legacy_alerts'; -import { AlertingDefaults } from './alerts_common'; +import { AlertingDefaults } from './alert_helpers'; const WATCH_NAME = 'elasticsearch_version_mismatch'; export class ElasticsearchVersionMismatchAlert extends BaseAlert { public type = ALERT_ELASTICSEARCH_VERSION_MISMATCH; - public label = i18n.translate('xpack.monitoring.alerts.elasticsearchVersionMismatch.label', { - defaultMessage: 'Elasticsearch version mismatch', - }); + public label = LEGACY_ALERT_DETAILS[ALERT_ELASTICSEARCH_VERSION_MISMATCH].label; public isLegacy = true; protected actionVariables = [ diff --git a/x-pack/plugins/monitoring/server/alerts/index.ts b/x-pack/plugins/monitoring/server/alerts/index.ts index 48254f2dec326..5fa718dfb34cd 100644 --- a/x-pack/plugins/monitoring/server/alerts/index.ts +++ b/x-pack/plugins/monitoring/server/alerts/index.ts @@ -8,6 +8,8 @@ export { BaseAlert } from './base_alert'; export { CpuUsageAlert } from './cpu_usage_alert'; export { MissingMonitoringDataAlert } from './missing_monitoring_data_alert'; export { DiskUsageAlert } from './disk_usage_alert'; +export { ThreadPoolSearchRejectionsAlert } from './thread_pool_search_rejections_alert'; +export { ThreadPoolWriteRejectionsAlert } from './thread_pool_write_rejections_alert'; export { MemoryUsageAlert } from './memory_usage_alert'; export { ClusterHealthAlert } from './cluster_health_alert'; export { LicenseExpirationAlert } from './license_expiration_alert'; @@ -15,4 +17,4 @@ export { NodesChangedAlert } from './nodes_changed_alert'; export { ElasticsearchVersionMismatchAlert } from './elasticsearch_version_mismatch_alert'; export { KibanaVersionMismatchAlert } from './kibana_version_mismatch_alert'; export { LogstashVersionMismatchAlert } from './logstash_version_mismatch_alert'; -export { AlertsFactory, BY_TYPE } from './alerts_factory'; +export { AlertsFactory } from './alerts_factory'; diff --git a/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.ts b/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.ts index 316f305603964..851a401635792 100644 --- a/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.ts @@ -13,22 +13,24 @@ import { AlertMessage, AlertInstanceState, LegacyAlert, -} from './types'; + CommonAlertParams, +} from '../../common/types/alerts'; import { AlertInstance } from '../../../alerts/server'; -import { INDEX_ALERTS, ALERT_KIBANA_VERSION_MISMATCH } from '../../common/constants'; +import { + INDEX_ALERTS, + ALERT_KIBANA_VERSION_MISMATCH, + LEGACY_ALERT_DETAILS, +} from '../../common/constants'; import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; import { AlertSeverity } from '../../common/enums'; -import { CommonAlertParams } from '../../common/types'; import { fetchLegacyAlerts } from '../lib/alerts/fetch_legacy_alerts'; -import { AlertingDefaults } from './alerts_common'; +import { AlertingDefaults } from './alert_helpers'; const WATCH_NAME = 'kibana_version_mismatch'; export class KibanaVersionMismatchAlert extends BaseAlert { public type = ALERT_KIBANA_VERSION_MISMATCH; - public label = i18n.translate('xpack.monitoring.alerts.kibanaVersionMismatch.label', { - defaultMessage: 'Kibana version mismatch', - }); + public label = LEGACY_ALERT_DETAILS[ALERT_KIBANA_VERSION_MISMATCH].label; public isLegacy = true; protected actionVariables = [ diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.ts index f1412ff0fc91a..e0396ee6673e8 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.ts @@ -16,27 +16,26 @@ import { AlertMessageLinkToken, AlertInstanceState, LegacyAlert, -} from './types'; + CommonAlertParams, +} from '../../common/types/alerts'; import { AlertInstance } from '../../../alerts/server'; import { INDEX_ALERTS, ALERT_LICENSE_EXPIRATION, FORMAT_DURATION_TEMPLATE_SHORT, + LEGACY_ALERT_DETAILS, } from '../../common/constants'; import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; import { AlertMessageTokenType } from '../../common/enums'; -import { CommonAlertParams } from '../../common/types'; import { fetchLegacyAlerts } from '../lib/alerts/fetch_legacy_alerts'; import { mapLegacySeverity } from '../lib/alerts/map_legacy_severity'; -import { AlertingDefaults } from './alerts_common'; +import { AlertingDefaults } from './alert_helpers'; const WATCH_NAME = 'xpack_license_expiration'; export class LicenseExpirationAlert extends BaseAlert { public type = ALERT_LICENSE_EXPIRATION; - public label = i18n.translate('xpack.monitoring.alerts.licenseExpiration.label', { - defaultMessage: 'License expiration', - }); + public label = LEGACY_ALERT_DETAILS[ALERT_LICENSE_EXPIRATION].label; public isLegacy = true; protected actionVariables = [ { diff --git a/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.ts b/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.ts index 37515e32e591a..7f5c0ea40e36a 100644 --- a/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.ts @@ -13,22 +13,24 @@ import { AlertMessage, AlertInstanceState, LegacyAlert, -} from './types'; + CommonAlertParams, +} from '../../common/types/alerts'; import { AlertInstance } from '../../../alerts/server'; -import { INDEX_ALERTS, ALERT_LOGSTASH_VERSION_MISMATCH } from '../../common/constants'; +import { + INDEX_ALERTS, + ALERT_LOGSTASH_VERSION_MISMATCH, + LEGACY_ALERT_DETAILS, +} from '../../common/constants'; import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; import { AlertSeverity } from '../../common/enums'; -import { CommonAlertParams } from '../../common/types'; import { fetchLegacyAlerts } from '../lib/alerts/fetch_legacy_alerts'; -import { AlertingDefaults } from './alerts_common'; +import { AlertingDefaults } from './alert_helpers'; const WATCH_NAME = 'logstash_version_mismatch'; export class LogstashVersionMismatchAlert extends BaseAlert { public type = ALERT_LOGSTASH_VERSION_MISMATCH; - public label = i18n.translate('xpack.monitoring.alerts.logstashVersionMismatch.label', { - defaultMessage: 'Logstash version mismatch', - }); + public label = LEGACY_ALERT_DETAILS[ALERT_LOGSTASH_VERSION_MISMATCH].label; public isLegacy = true; protected actionVariables = [ diff --git a/x-pack/plugins/monitoring/server/alerts/memory_usage_alert.ts b/x-pack/plugins/monitoring/server/alerts/memory_usage_alert.ts index 8dc707afab1e1..c37176764c020 100644 --- a/x-pack/plugins/monitoring/server/alerts/memory_usage_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/memory_usage_alert.ts @@ -15,44 +15,26 @@ import { AlertMessageTimeToken, AlertMessageLinkToken, AlertInstanceState, -} from './types'; + CommonAlertFilter, + CommonAlertParams, +} from '../../common/types/alerts'; import { AlertInstance, AlertServices } from '../../../alerts/server'; -import { INDEX_PATTERN_ELASTICSEARCH, ALERT_MEMORY_USAGE } from '../../common/constants'; +import { + INDEX_PATTERN_ELASTICSEARCH, + ALERT_MEMORY_USAGE, + ALERT_DETAILS, +} from '../../common/constants'; import { fetchMemoryUsageNodeStats } from '../lib/alerts/fetch_memory_usage_node_stats'; import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; -import { AlertMessageTokenType, AlertSeverity, AlertParamType } from '../../common/enums'; +import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; import { RawAlertInstance } from '../../../alerts/common'; -import { CommonAlertFilter, CommonAlertParams, CommonAlertParamDetail } from '../../common/types'; -import { AlertingDefaults, createLink } from './alerts_common'; +import { AlertingDefaults, createLink } from './alert_helpers'; import { appendMetricbeatIndex } from '../lib/alerts/append_mb_index'; import { parseDuration } from '../../../alerts/common/parse_duration'; -interface ParamDetails { - [key: string]: CommonAlertParamDetail; -} - export class MemoryUsageAlert extends BaseAlert { - public static readonly PARAM_DETAILS: ParamDetails = { - threshold: { - label: i18n.translate('xpack.monitoring.alerts.memoryUsage.paramDetails.threshold.label', { - defaultMessage: `Notify when memory usage is over`, - }), - type: AlertParamType.Percentage, - }, - duration: { - label: i18n.translate('xpack.monitoring.alerts.memoryUsage.paramDetails.duration.label', { - defaultMessage: `Look at the average over`, - }), - type: AlertParamType.Duration, - }, - }; - public static paramDetails = MemoryUsageAlert.PARAM_DETAILS; - public static readonly TYPE = ALERT_MEMORY_USAGE; - public static readonly LABEL = i18n.translate('xpack.monitoring.alerts.memoryUsage.label', { - defaultMessage: 'Memory Usage (JVM)', - }); - public type = MemoryUsageAlert.TYPE; - public label = MemoryUsageAlert.LABEL; + public type = ALERT_MEMORY_USAGE; + public label = ALERT_DETAILS[ALERT_MEMORY_USAGE].label; protected defaultParams = { threshold: 85, diff --git a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts index 5b4542a4439ca..456ad92855f65 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts @@ -16,24 +16,22 @@ import { AlertMissingData, AlertMessageTimeToken, AlertInstanceState, -} from './types'; + CommonAlertFilter, + CommonAlertParams, + CommonAlertStackProductFilter, + CommonAlertNodeUuidFilter, +} from '../../common/types/alerts'; import { AlertInstance, AlertServices } from '../../../alerts/server'; import { INDEX_PATTERN, ALERT_MISSING_MONITORING_DATA, INDEX_PATTERN_ELASTICSEARCH, + ALERT_DETAILS, } from '../../common/constants'; import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; -import { AlertMessageTokenType, AlertSeverity, AlertParamType } from '../../common/enums'; +import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; import { RawAlertInstance } from '../../../alerts/common'; import { parseDuration } from '../../../alerts/common/parse_duration'; -import { - CommonAlertFilter, - CommonAlertParams, - CommonAlertParamDetail, - CommonAlertStackProductFilter, - CommonAlertNodeUuidFilter, -} from '../../common/types'; import { appendMetricbeatIndex } from '../lib/alerts/append_mb_index'; import { fetchMissingMonitoringData } from '../lib/alerts/fetch_missing_monitoring_data'; import { getTypeLabelForStackProduct } from '../lib/alerts/get_type_label_for_stack_product'; @@ -41,7 +39,7 @@ import { getListingLinkForStackProduct } from '../lib/alerts/get_listing_link_fo import { getStackProductLabel } from '../lib/alerts/get_stack_product_label'; import { fetchClusters } from '../lib/alerts/fetch_clusters'; import { fetchAvailableCcs } from '../lib/alerts/fetch_available_ccs'; -import { AlertingDefaults, createLink } from './alerts_common'; +import { AlertingDefaults, createLink } from './alert_helpers'; const RESOLVED = i18n.translate('xpack.monitoring.alerts.missingData.resolved', { defaultMessage: 'resolved', @@ -62,27 +60,10 @@ interface MissingDataParams { } export class MissingMonitoringDataAlert extends BaseAlert { - public static paramDetails = { - duration: { - label: i18n.translate('xpack.monitoring.alerts.missingData.paramDetails.duration.label', { - defaultMessage: `Notify if monitoring data is missing for the last`, - }), - type: AlertParamType.Duration, - } as CommonAlertParamDetail, - limit: { - label: i18n.translate('xpack.monitoring.alerts.missingData.paramDetails.limit.label', { - defaultMessage: `looking back`, - }), - type: AlertParamType.Duration, - } as CommonAlertParamDetail, - }; - public defaultThrottle: string = '6h'; public type = ALERT_MISSING_MONITORING_DATA; - public label = i18n.translate('xpack.monitoring.alerts.missingData.label', { - defaultMessage: 'Missing monitoring data', - }); + public label = ALERT_DETAILS[ALERT_MISSING_MONITORING_DATA].label; protected defaultParams: MissingDataParams = { duration: DEFAULT_DURATION, diff --git a/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.ts b/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.ts index e03e6ea53ab4e..7b54ef629cba6 100644 --- a/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.ts @@ -14,22 +14,20 @@ import { AlertInstanceState, LegacyAlert, LegacyAlertNodesChangedList, -} from './types'; + CommonAlertParams, +} from '../../common/types/alerts'; import { AlertInstance } from '../../../alerts/server'; -import { INDEX_ALERTS, ALERT_NODES_CHANGED } from '../../common/constants'; +import { INDEX_ALERTS, ALERT_NODES_CHANGED, LEGACY_ALERT_DETAILS } from '../../common/constants'; import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; -import { CommonAlertParams } from '../../common/types'; import { fetchLegacyAlerts } from '../lib/alerts/fetch_legacy_alerts'; import { mapLegacySeverity } from '../lib/alerts/map_legacy_severity'; -import { AlertingDefaults } from './alerts_common'; +import { AlertingDefaults } from './alert_helpers'; const WATCH_NAME = 'elasticsearch_nodes'; export class NodesChangedAlert extends BaseAlert { public type = ALERT_NODES_CHANGED; - public label = i18n.translate('xpack.monitoring.alerts.nodesChanged.label', { - defaultMessage: 'Nodes changed', - }); + public label = LEGACY_ALERT_DETAILS[ALERT_NODES_CHANGED].label; public isLegacy = true; protected actionVariables = [ diff --git a/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_alert_base.ts b/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_alert_base.ts new file mode 100644 index 0000000000000..4905ae73b0545 --- /dev/null +++ b/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_alert_base.ts @@ -0,0 +1,312 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { IUiSettingsClient, Logger } from 'kibana/server'; +import { i18n } from '@kbn/i18n'; +import { BaseAlert } from './base_alert'; +import { + AlertData, + AlertCluster, + AlertMessage, + AlertThreadPoolRejectionsState, + AlertMessageTimeToken, + AlertMessageLinkToken, + CommonAlertFilter, + ThreadPoolRejectionsAlertParams, +} from '../../common/types/alerts'; +import { AlertInstance, AlertServices } from '../../../alerts/server'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants'; +import { fetchThreadPoolRejectionStats } from '../lib/alerts/fetch_thread_pool_rejections_stats'; +import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; +import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; +import { Alert, RawAlertInstance } from '../../../alerts/common'; +import { AlertingDefaults, createLink } from './alert_helpers'; +import { appendMetricbeatIndex } from '../lib/alerts/append_mb_index'; + +type ActionVariables = Array<{ name: string; description: string }>; + +export class ThreadPoolRejectionsAlertBase extends BaseAlert { + protected static createActionVariables(type: string) { + return [ + { + name: 'count', + description: i18n.translate( + 'xpack.monitoring.alerts.threadPoolRejections.actionVariables.count', + { + defaultMessage: 'The number of nodes reporting high thread pool {type} rejections.', + values: { type }, + } + ), + }, + ...Object.values(AlertingDefaults.ALERT_TYPE.context), + ]; + } + + protected defaultParams: ThreadPoolRejectionsAlertParams = { + threshold: 300, + duration: '5m', + }; + + constructor( + rawAlert: Alert | undefined = undefined, + public readonly type: string, + public readonly threadPoolType: string, + public readonly label: string, + public readonly actionVariables: ActionVariables + ) { + super(rawAlert); + } + + protected async fetchData( + params: ThreadPoolRejectionsAlertParams, + callCluster: any, + clusters: AlertCluster[], + uiSettings: IUiSettingsClient, + availableCcs: string[] + ): Promise { + let esIndexPattern = appendMetricbeatIndex(this.config, INDEX_PATTERN_ELASTICSEARCH); + if (availableCcs) { + esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs); + } + + const { threshold, duration } = params; + + const stats = await fetchThreadPoolRejectionStats( + callCluster, + clusters, + esIndexPattern, + this.config.ui.max_bucket_size, + this.threadPoolType, + duration + ); + + return stats.map((stat) => { + const { clusterUuid, nodeId, rejectionCount, ccs } = stat; + + return { + instanceKey: `${clusterUuid}:${nodeId}`, + shouldFire: rejectionCount > threshold, + rejectionCount, + severity: AlertSeverity.Danger, + meta: stat, + clusterUuid, + ccs, + }; + }); + } + + protected filterAlertInstance(alertInstance: RawAlertInstance, filters: CommonAlertFilter[]) { + const alertInstanceStates = alertInstance.state + ?.alertStates as AlertThreadPoolRejectionsState[]; + const nodeUuid = filters?.find((filter) => filter.nodeUuid)?.nodeUuid; + + if (!alertInstanceStates?.length || !nodeUuid) { + return true; + } + + const nodeAlerts = alertInstanceStates.filter(({ nodeId }) => nodeId === nodeUuid); + return Boolean(nodeAlerts.length); + } + + protected getUiMessage( + alertState: AlertThreadPoolRejectionsState, + rejectionCount: number + ): AlertMessage { + const { nodeName, nodeId } = alertState; + return { + text: i18n.translate('xpack.monitoring.alerts.threadPoolRejections.ui.firingMessage', { + defaultMessage: `Node #start_link{nodeName}#end_link is reporting {rejectionCount} {type} rejections at #absolute`, + values: { + nodeName, + type: this.threadPoolType, + rejectionCount, + }, + }), + nextSteps: [ + createLink( + i18n.translate( + 'xpack.monitoring.alerts.threadPoolRejections.ui.nextSteps.monitorThisNode', + { + defaultMessage: `#start_linkMonitor this node#end_link`, + } + ), + `elasticsearch/nodes/${nodeId}/advanced`, + AlertMessageTokenType.Link + ), + createLink( + i18n.translate( + 'xpack.monitoring.alerts.threadPoolRejections.ui.nextSteps.optimizeQueries', + { + defaultMessage: '#start_linkOptimize complex queries#end_link', + } + ), + `{elasticWebsiteUrl}blog/advanced-tuning-finding-and-fixing-slow-elasticsearch-queries` + ), + createLink( + i18n.translate('xpack.monitoring.alerts.threadPoolRejections.ui.nextSteps.addMoreNodes', { + defaultMessage: '#start_linkAdd more nodes#end_link', + }), + `{elasticWebsiteUrl}guide/en/elasticsearch/reference/{docLinkVersion}/add-elasticsearch-nodes.html` + ), + createLink( + i18n.translate( + 'xpack.monitoring.alerts.threadPoolRejections.ui.nextSteps.resizeYourDeployment', + { + defaultMessage: '#start_linkResize your deployment (ECE)#end_link', + } + ), + `{elasticWebsiteUrl}guide/en/cloud-enterprise/current/ece-resize-deployment.html` + ), + createLink( + i18n.translate( + 'xpack.monitoring.alerts.threadPoolRejections.ui.nextSteps.threadPoolSettings', + { + defaultMessage: '#start_linkThread pool settings#end_link', + } + ), + `{elasticWebsiteUrl}guide/en/elasticsearch/reference/{docLinkVersion}/modules-threadpool.html` + ), + ], + tokens: [ + { + startToken: '#absolute', + type: AlertMessageTokenType.Time, + isAbsolute: true, + isRelative: false, + timestamp: alertState.ui.triggeredMS, + } as AlertMessageTimeToken, + { + startToken: '#start_link', + endToken: '#end_link', + type: AlertMessageTokenType.Link, + url: `elasticsearch/nodes/${nodeId}`, + } as AlertMessageLinkToken, + ], + }; + } + + protected executeActions( + instance: AlertInstance, + alertStates: AlertThreadPoolRejectionsState[], + cluster: AlertCluster + ) { + const type = this.threadPoolType; + const count = alertStates.length; + const { clusterName: clusterKnownName, clusterUuid } = cluster; + const clusterName = clusterKnownName || clusterUuid; + const shortActionText = i18n.translate( + 'xpack.monitoring.alerts.threadPoolRejections.shortAction', + { + defaultMessage: 'Verify thread pool {type} rejections across affected nodes.', + values: { + type, + }, + } + ); + + const fullActionText = i18n.translate( + 'xpack.monitoring.alerts.threadPoolRejections.fullAction', + { + defaultMessage: 'View nodes', + } + ); + + const ccs = alertStates.find((state) => state.ccs)?.ccs; + const globalStateLink = this.createGlobalStateLink('elasticsearch/nodes', clusterUuid, ccs); + + const action = `[${fullActionText}](${globalStateLink})`; + const internalShortMessage = i18n.translate( + 'xpack.monitoring.alerts.threadPoolRejections.firing.internalShortMessage', + { + defaultMessage: `Thread pool {type} rejections alert is firing for {count} node(s) in cluster: {clusterName}. {shortActionText}`, + values: { + count, + clusterName, + shortActionText, + type, + }, + } + ); + const internalFullMessage = i18n.translate( + 'xpack.monitoring.alerts.threadPoolRejections.firing.internalFullMessage', + { + defaultMessage: `Thread pool {type} rejections alert is firing for {count} node(s) in cluster: {clusterName}. {action}`, + values: { + count, + clusterName, + action, + type, + }, + } + ); + + instance.scheduleActions('default', { + internalShortMessage, + internalFullMessage: this.isCloud ? internalShortMessage : internalFullMessage, + threadPoolType: type, + state: AlertingDefaults.ALERT_STATE.firing, + count, + clusterName, + action, + actionPlain: shortActionText, + }); + } + + protected async processData( + data: AlertData[], + clusters: AlertCluster[], + services: AlertServices, + logger: Logger, + state: { lastChecked?: number } + ) { + const currentUTC = +new Date(); + for (const cluster of clusters) { + const nodes = data.filter((node) => node.clusterUuid === cluster.clusterUuid); + if (!nodes.length) { + continue; + } + + const firingNodeUuids = nodes.filter((node) => node.shouldFire); + + if (!firingNodeUuids.length) { + continue; + } + + const instanceSuffix = firingNodeUuids.map((node) => node.meta.nodeId); + + const instancePrefix = `${this.type}:${cluster.clusterUuid}:`; + const alertInstanceId = `${instancePrefix}:${instanceSuffix}`; + const alertInstance = services.alertInstanceFactory(alertInstanceId); + const newAlertStates: AlertThreadPoolRejectionsState[] = []; + + for (const node of nodes) { + if (!node.shouldFire) { + continue; + } + const stat = node.meta as AlertThreadPoolRejectionsState; + const nodeState = this.getDefaultAlertState( + cluster, + node + ) as AlertThreadPoolRejectionsState; + const { nodeId, nodeName, rejectionCount } = stat; + nodeState.nodeId = nodeId; + nodeState.nodeName = nodeName; + nodeState.ui.triggeredMS = currentUTC; + nodeState.ui.isFiring = true; + nodeState.ui.severity = node.severity; + nodeState.ui.message = this.getUiMessage(nodeState, rejectionCount); + newAlertStates.push(nodeState); + } + + alertInstance.replaceState({ alertStates: newAlertStates }); + if (newAlertStates.length) { + this.executeActions(alertInstance, newAlertStates, cluster); + } + } + + state.lastChecked = currentUTC; + return state; + } +} diff --git a/x-pack/plugins/monitoring/server/alerts/thread_pool_search_rejections_alert.ts b/x-pack/plugins/monitoring/server/alerts/thread_pool_search_rejections_alert.ts new file mode 100644 index 0000000000000..10df95c05ba3f --- /dev/null +++ b/x-pack/plugins/monitoring/server/alerts/thread_pool_search_rejections_alert.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ThreadPoolRejectionsAlertBase } from './thread_pool_rejections_alert_base'; +import { ALERT_THREAD_POOL_SEARCH_REJECTIONS, ALERT_DETAILS } from '../../common/constants'; +import { Alert } from '../../../alerts/common'; + +export class ThreadPoolSearchRejectionsAlert extends ThreadPoolRejectionsAlertBase { + private static TYPE = ALERT_THREAD_POOL_SEARCH_REJECTIONS; + private static THREAD_POOL_TYPE = 'search'; + private static readonly LABEL = ALERT_DETAILS[ALERT_THREAD_POOL_SEARCH_REJECTIONS].label; + constructor(rawAlert?: Alert) { + super( + rawAlert, + ThreadPoolSearchRejectionsAlert.TYPE, + ThreadPoolSearchRejectionsAlert.THREAD_POOL_TYPE, + ThreadPoolSearchRejectionsAlert.LABEL, + ThreadPoolRejectionsAlertBase.createActionVariables( + ThreadPoolSearchRejectionsAlert.THREAD_POOL_TYPE + ) + ); + } +} diff --git a/x-pack/plugins/monitoring/server/alerts/thread_pool_write_rejections_alert.ts b/x-pack/plugins/monitoring/server/alerts/thread_pool_write_rejections_alert.ts new file mode 100644 index 0000000000000..d415515315b37 --- /dev/null +++ b/x-pack/plugins/monitoring/server/alerts/thread_pool_write_rejections_alert.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ThreadPoolRejectionsAlertBase } from './thread_pool_rejections_alert_base'; +import { ALERT_THREAD_POOL_WRITE_REJECTIONS, ALERT_DETAILS } from '../../common/constants'; +import { Alert } from '../../../alerts/common'; + +export class ThreadPoolWriteRejectionsAlert extends ThreadPoolRejectionsAlertBase { + private static TYPE = ALERT_THREAD_POOL_WRITE_REJECTIONS; + private static THREAD_POOL_TYPE = 'write'; + private static readonly LABEL = ALERT_DETAILS[ALERT_THREAD_POOL_WRITE_REJECTIONS].label; + constructor(rawAlert?: Alert) { + super( + rawAlert, + ThreadPoolWriteRejectionsAlert.TYPE, + ThreadPoolWriteRejectionsAlert.THREAD_POOL_TYPE, + ThreadPoolWriteRejectionsAlert.LABEL, + ThreadPoolRejectionsAlertBase.createActionVariables( + ThreadPoolWriteRejectionsAlert.THREAD_POOL_TYPE + ) + ); + } +} diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts index d474338bce922..368a909279b8c 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { get } from 'lodash'; -import { AlertCluster } from '../../alerts/types'; +import { AlertCluster } from '../../../common/types/alerts'; interface RangeFilter { [field: string]: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts index ecd324c083a8c..b38a32164223e 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cpu_usage_node_stats.ts @@ -6,7 +6,7 @@ import { get } from 'lodash'; import moment from 'moment'; import { NORMALIZED_DERIVATIVE_UNIT } from '../../../common/constants'; -import { AlertCluster, AlertCpuUsageNodeStats } from '../../alerts/types'; +import { AlertCluster, AlertCpuUsageNodeStats } from '../../../common/types/alerts'; interface NodeBucketESResponse { key: string; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts index 6201204ebebe0..f00c42d708b16 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_disk_usage_node_stats.ts @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { AlertCluster, AlertDiskUsageNodeStats } from '../../alerts/types'; +import { AlertCluster, AlertDiskUsageNodeStats } from '../../../common/types/alerts'; export async function fetchDiskUsageNodeStats( callCluster: any, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.ts index fe01a1b921c2e..fbf7608a737ba 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { get } from 'lodash'; -import { LegacyAlert, AlertCluster, LegacyAlertMetadata } from '../../alerts/types'; +import { LegacyAlert, AlertCluster, LegacyAlertMetadata } from '../../../common/types/alerts'; export async function fetchLegacyAlerts( callCluster: any, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts index c6843c3ed5f12..9a68b3afc7758 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_memory_usage_node_stats.ts @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { AlertCluster, AlertMemoryUsageNodeStats } from '../../alerts/types'; +import { AlertCluster, AlertMemoryUsageNodeStats } from '../../../common/types/alerts'; export async function fetchMemoryUsageNodeStats( callCluster: any, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts index 91fc05137a8c1..49307764e9f01 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_missing_monitoring_data.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { get } from 'lodash'; -import { AlertCluster, AlertMissingData } from '../../alerts/types'; +import { AlertCluster, AlertMissingData } from '../../../common/types/alerts'; import { KIBANA_SYSTEM_ID, BEATS_SYSTEM_ID, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts index 824eeab7245b4..c31ab91866b1d 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts @@ -5,7 +5,7 @@ */ import { fetchStatus } from './fetch_status'; -import { AlertUiState, AlertState } from '../../alerts/types'; +import { AlertUiState, AlertState } from '../../../common/types/alerts'; import { AlertSeverity } from '../../../common/enums'; import { ALERT_CPU_USAGE, diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts index ed49f42e4908c..ed860ee21344d 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts @@ -4,10 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ import moment from 'moment'; -import { AlertInstanceState } from '../../alerts/types'; +import { AlertInstanceState } from '../../../common/types/alerts'; import { AlertsClient } from '../../../../alerts/server'; import { AlertsFactory } from '../../alerts'; -import { CommonAlertStatus, CommonAlertState, CommonAlertFilter } from '../../../common/types'; +import { + CommonAlertStatus, + CommonAlertState, + CommonAlertFilter, +} from '../../../common/types/alerts'; import { ALERTS } from '../../../common/constants'; import { MonitoringLicenseService } from '../../types'; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts new file mode 100644 index 0000000000000..664ceb1d9411b --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_thread_pool_rejections_stats.ts @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get } from 'lodash'; +import { AlertCluster, AlertThreadPoolRejectionsStats } from '../../../common/types/alerts'; + +const invalidNumberValue = (value: number) => { + return isNaN(value) || value === undefined || value === null; +}; + +const getTopHits = (threadType: string, order: string) => ({ + top_hits: { + sort: [ + { + timestamp: { + order, + unmapped_type: 'long', + }, + }, + ], + _source: { + includes: [`node_stats.thread_pool.${threadType}.rejected`, 'source_node.name'], + }, + size: 1, + }, +}); + +export async function fetchThreadPoolRejectionStats( + callCluster: any, + clusters: AlertCluster[], + index: string, + size: number, + threadType: string, + duration: string +): Promise { + const clustersIds = clusters.map((cluster) => cluster.clusterUuid); + const params = { + index, + filterPath: ['aggregations'], + body: { + size: 0, + query: { + bool: { + filter: [ + { + terms: { + cluster_uuid: clustersIds, + }, + }, + { + term: { + type: 'node_stats', + }, + }, + { + range: { + timestamp: { + gte: `now-${duration}`, + }, + }, + }, + ], + }, + }, + aggs: { + clusters: { + terms: { + field: 'cluster_uuid', + size, + }, + aggs: { + nodes: { + terms: { + field: 'source_node.uuid', + size, + }, + aggs: { + most_recent: { + ...getTopHits(threadType, 'desc'), + }, + least_recent: { + ...getTopHits(threadType, 'asc'), + }, + }, + }, + }, + }, + }, + }, + }; + + const response = await callCluster('search', params); + const stats: AlertThreadPoolRejectionsStats[] = []; + const { buckets: clusterBuckets = [] } = response.aggregations.clusters; + + if (!clusterBuckets.length) { + return stats; + } + + for (const clusterBucket of clusterBuckets) { + for (const node of clusterBucket.nodes.buckets) { + const mostRecentDoc = get(node, 'most_recent.hits.hits[0]'); + mostRecentDoc.timestamp = mostRecentDoc.sort[0]; + + const leastRecentDoc = get(node, 'least_recent.hits.hits[0]'); + leastRecentDoc.timestamp = leastRecentDoc.sort[0]; + + if (!mostRecentDoc || mostRecentDoc.timestamp === leastRecentDoc.timestamp) { + continue; + } + + const rejectedPath = `_source.node_stats.thread_pool.${threadType}.rejected`; + const newRejectionCount = Number(get(mostRecentDoc, rejectedPath)); + const oldRejectionCount = Number(get(leastRecentDoc, rejectedPath)); + + if (invalidNumberValue(newRejectionCount) || invalidNumberValue(oldRejectionCount)) { + continue; + } + + const rejectionCount = + oldRejectionCount > newRejectionCount + ? newRejectionCount + : newRejectionCount - oldRejectionCount; + const indexName = mostRecentDoc._index; + const nodeName = get(mostRecentDoc, '_source.source_node.name') || node.key; + const nodeStat = { + rejectionCount, + type: threadType, + clusterUuid: clusterBucket.key, + nodeId: node.key, + nodeName, + ccs: indexName.includes(':') ? indexName.split(':')[0] : null, + }; + stats.push(nodeStat); + } + } + return stats; +} diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_stats.js b/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_stats.js index fa0dff9c09431..ea4eb36964b5c 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_stats.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_cluster_stats.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { badRequest, notFound } from 'boom'; +import { badRequest, notFound } from '@hapi/boom'; import { getClustersStats } from './get_clusters_stats'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js index e91679eff2817..ddc33a4b93730 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { notFound } from 'boom'; +import { notFound } from '@hapi/boom'; import { set } from '@elastic/safer-lodash-set'; import { findIndex } from 'lodash'; import { getClustersStats } from './get_clusters_stats'; diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js index 6d2a853ee24d2..8d774b752b5c0 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { INDEX_PATTERN } from '../../../common/constants'; /* diff --git a/x-pack/plugins/monitoring/server/lib/errors/__tests__/auth_errors.js b/x-pack/plugins/monitoring/server/lib/errors/__tests__/auth_errors.js index df10624999afd..677b395caedd4 100644 --- a/x-pack/plugins/monitoring/server/lib/errors/__tests__/auth_errors.js +++ b/x-pack/plugins/monitoring/server/lib/errors/__tests__/auth_errors.js @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { errors } from 'elasticsearch'; -import { forbidden, unauthorized } from 'boom'; +import { forbidden, unauthorized } from '@hapi/boom'; import { isAuthError, handleAuthError } from '../auth_errors'; describe('Error handling for 401/403 errors', () => { diff --git a/x-pack/plugins/monitoring/server/lib/errors/auth_errors.js b/x-pack/plugins/monitoring/server/lib/errors/auth_errors.js index b0c4d35a5b095..96535ac6d5311 100644 --- a/x-pack/plugins/monitoring/server/lib/errors/auth_errors.js +++ b/x-pack/plugins/monitoring/server/lib/errors/auth_errors.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { forbidden } from 'boom'; +import { forbidden } from '@hapi/boom'; import { i18n } from '@kbn/i18n'; const getStatusCode = (err) => { diff --git a/x-pack/plugins/monitoring/server/lib/errors/handle_error.js b/x-pack/plugins/monitoring/server/lib/errors/handle_error.js index 4726020210ce7..6e7dca15a392c 100644 --- a/x-pack/plugins/monitoring/server/lib/errors/handle_error.js +++ b/x-pack/plugins/monitoring/server/lib/errors/handle_error.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { boomify } from 'boom'; +import { boomify } from '@hapi/boom'; import { isKnownError, handleKnownError } from './known_errors'; import { isAuthError, handleAuthError } from './auth_errors'; diff --git a/x-pack/plugins/monitoring/server/lib/errors/handle_settings_error.js b/x-pack/plugins/monitoring/server/lib/errors/handle_settings_error.js index 54c0c39d0b396..dfe5f1c4a614f 100644 --- a/x-pack/plugins/monitoring/server/lib/errors/handle_settings_error.js +++ b/x-pack/plugins/monitoring/server/lib/errors/handle_settings_error.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { boomify } from 'boom'; +import { boomify } from '@hapi/boom'; export function handleSettingsError(err) { return boomify(err, { statusCode: err.statusCode }); diff --git a/x-pack/plugins/monitoring/server/lib/errors/known_errors.js b/x-pack/plugins/monitoring/server/lib/errors/known_errors.js index 17bcdd0414adf..fb62dc7261057 100644 --- a/x-pack/plugins/monitoring/server/lib/errors/known_errors.js +++ b/x-pack/plugins/monitoring/server/lib/errors/known_errors.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { boomify } from 'boom'; +import { boomify } from '@hapi/boom'; import { i18n } from '@kbn/i18n'; import { MonitoringLicenseError } from './custom_errors'; diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline.js b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline.js index a2665aeb041f8..2977159f4206d 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline.js +++ b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import boom from 'boom'; +import boom from '@hapi/boom'; import { get } from 'lodash'; import { checkParam } from '../error_missing_required'; import { getPipelineStateDocument } from './get_pipeline_state_document'; diff --git a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_vertex.js b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_vertex.js index 295d86c52b801..cdbe26d993f75 100644 --- a/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_vertex.js +++ b/x-pack/plugins/monitoring/server/lib/logstash/get_pipeline_vertex.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import boom from 'boom'; +import boom from '@hapi/boom'; import { get } from 'lodash'; import { checkParam } from '../error_missing_required'; import { getPipelineStateDocument } from './get_pipeline_state_document'; diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 6093eacffe9b7..56368a5ece10a 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { combineLatest } from 'rxjs'; import { first, map } from 'rxjs/operators'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/status.ts b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/status.ts index d97bc34c2adb0..29a27ac3d05e7 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/status.ts +++ b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/status.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { handleError } from '../../../../lib/errors'; import { RouteDependencies } from '../../../../types'; import { fetchStatus } from '../../../../lib/alerts/fetch_status'; -import { CommonAlertFilter } from '../../../../../common/types'; +import { CommonAlertFilter } from '../../../../../common/types/alerts'; export function alertStatusRoute(server: any, npRoute: RouteDependencies) { npRoute.router.post( diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts index 129b798740806..099f6915611cb 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts @@ -12,8 +12,7 @@ import { ClustersHighLevelStats } from './get_high_level_stats'; import { coreMock } from 'src/core/server/mocks'; describe('get_all_stats', () => { - const start = 0; - const end = 1; + const timestamp = Date.now(); const callCluster = sinon.stub(); const esClient = sinon.stub(); const soClient = sinon.stub(); @@ -181,8 +180,7 @@ describe('get_all_stats', () => { esClient: esClient as any, soClient: soClient as any, usageCollection: {} as any, - start, - end, + timestamp, }, { logger: coreMock.createPluginInitializerContext().logger.get('test'), @@ -208,8 +206,7 @@ describe('get_all_stats', () => { esClient: esClient as any, soClient: soClient as any, usageCollection: {} as any, - start, - end, + timestamp, }, { logger: coreMock.createPluginInitializerContext().logger.get('test'), diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts index 9ebd73ffbc833..b6b2023b2af1a 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts @@ -8,15 +8,17 @@ import { set } from '@elastic/safer-lodash-set'; import { get, merge } from 'lodash'; import { StatsGetter } from 'src/plugins/telemetry_collection_manager/server'; -import { LOGSTASH_SYSTEM_ID, KIBANA_SYSTEM_ID, BEATS_SYSTEM_ID } from '../../common/constants'; +import moment from 'moment'; +import { + LOGSTASH_SYSTEM_ID, + KIBANA_SYSTEM_ID, + BEATS_SYSTEM_ID, + USAGE_FETCH_INTERVAL, +} from '../../common/constants'; import { getElasticsearchStats, ESClusterStats } from './get_es_stats'; import { getKibanaStats, KibanaStats } from './get_kibana_stats'; -import { getBeatsStats } from './get_beats_stats'; -import { getHighLevelStats } from './get_high_level_stats'; - -type PromiseReturnType any> = ReturnType extends Promise - ? R - : T; +import { getBeatsStats, BeatsStatsByClusterUuid } from './get_beats_stats'; +import { getHighLevelStats, ClustersHighLevelStats } from './get_high_level_stats'; export interface CustomContext { maxBucketSize: number; @@ -28,9 +30,12 @@ export interface CustomContext { */ export const getAllStats: StatsGetter = async ( clustersDetails, - { callCluster, start, end, esClient, soClient }, + { callCluster, timestamp }, { maxBucketSize } ) => { + const start = moment(timestamp).subtract(USAGE_FETCH_INTERVAL, 'ms').toISOString(); + const end = moment(timestamp).toISOString(); + const clusterUuids = clustersDetails.map((clusterDetails) => clusterDetails.clusterUuid); const [esClusters, kibana, logstash, beats] = await Promise.all([ @@ -61,8 +66,8 @@ export function handleAllStats( beats, }: { kibana: KibanaStats; - logstash: PromiseReturnType; - beats: PromiseReturnType; + logstash: ClustersHighLevelStats; + beats: BeatsStatsByClusterUuid; } ) { return clusters.map((cluster) => { diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.test.ts index c619db90c8975..30b3bcdf63adf 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.test.ts @@ -20,8 +20,8 @@ const getBaseOptions = () => ({ describe('Get Beats Stats', () => { describe('fetchBeatsStats', () => { const clusterUuids = ['aCluster', 'bCluster', 'cCluster']; - const start = 100; - const end = 200; + const start = new Date().toISOString(); + const end = new Date().toISOString(); let callCluster = sinon.stub(); beforeEach(() => { diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.ts index d153c40bbe58b..a7f001f166d7d 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_beats_stats.ts @@ -320,8 +320,8 @@ export function processResults( async function fetchBeatsByType( callCluster: StatsCollectionConfig['callCluster'], clusterUuids: string[], - start: StatsCollectionConfig['start'], - end: StatsCollectionConfig['end'], + start: string, + end: string, { page = 0, ...options }: { page?: number } & BeatsProcessOptions, type: string ): Promise { @@ -384,8 +384,8 @@ async function fetchBeatsByType( export async function fetchBeatsStats( callCluster: StatsCollectionConfig['callCluster'], clusterUuids: string[], - start: StatsCollectionConfig['start'], - end: StatsCollectionConfig['end'], + start: string, + end: string, options: { page?: number } & BeatsProcessOptions ) { return fetchBeatsByType(callCluster, clusterUuids, start, end, options, 'beats_stats'); @@ -394,13 +394,17 @@ export async function fetchBeatsStats( export async function fetchBeatsStates( callCluster: StatsCollectionConfig['callCluster'], clusterUuids: string[], - start: StatsCollectionConfig['start'], - end: StatsCollectionConfig['end'], + start: string, + end: string, options: { page?: number } & BeatsProcessOptions ) { return fetchBeatsByType(callCluster, clusterUuids, start, end, options, 'beats_state'); } +export interface BeatsStatsByClusterUuid { + [clusterUuid: string]: BeatsBaseStats; +} + /* * Call the function for fetching and summarizing beats stats * @return {Object} - Beats stats in an object keyed by the cluster UUIDs @@ -408,9 +412,9 @@ export async function fetchBeatsStates( export async function getBeatsStats( callCluster: StatsCollectionConfig['callCluster'], clusterUuids: string[], - start: StatsCollectionConfig['start'], - end: StatsCollectionConfig['end'] -) { + start: string, + end: string +): Promise { const options: BeatsProcessOptions = { clusters: {}, // the result object to be built up clusterHostSets: {}, // passed to processResults for tracking state in the results generation diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts index c885bc9be4408..0acdb9968bc03 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts @@ -26,15 +26,14 @@ describe('get_cluster_uuids', () => { const expectedUuids = response.aggregations.cluster_uuids.buckets .map((bucket) => bucket.key) .map((expectedUuid) => ({ clusterUuid: expectedUuid })); - const start = new Date().toISOString(); - const end = new Date().toISOString(); + const timestamp = Date.now(); describe('getClusterUuids', () => { it('returns cluster UUIDs', async () => { callCluster.withArgs('search').returns(Promise.resolve(response)); expect( await getClusterUuids( - { callCluster, esClient, soClient, start, end, usageCollection: {} as any }, + { callCluster, esClient, soClient, timestamp, usageCollection: {} as any }, { maxBucketSize: 1, } as any @@ -48,7 +47,7 @@ describe('get_cluster_uuids', () => { callCluster.returns(Promise.resolve(response)); expect( await fetchClusterUuids( - { callCluster, esClient, soClient, start, end, usageCollection: {} as any }, + { callCluster, esClient, soClient, timestamp, usageCollection: {} as any }, { maxBucketSize: 1, } as any diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.ts index 9d4657b0ad799..5f471851b6621 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.ts @@ -5,15 +5,18 @@ */ import { get } from 'lodash'; +import moment from 'moment'; import { ClusterDetailsGetter, StatsCollectionConfig, ClusterDetails, } from 'src/plugins/telemetry_collection_manager/server'; import { createQuery } from './create_query'; -import { INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants'; +import { + INDEX_PATTERN_ELASTICSEARCH, + CLUSTER_DETAILS_FETCH_INTERVAL, +} from '../../common/constants'; import { CustomContext } from './get_all_stats'; - /** * Get a list of Cluster UUIDs that exist within the specified timespan. */ @@ -28,10 +31,14 @@ export const getClusterUuids: ClusterDetailsGetter = async ( /** * Fetch the aggregated Cluster UUIDs from the monitoring cluster. */ -export function fetchClusterUuids( - { callCluster, start, end }: StatsCollectionConfig, +export async function fetchClusterUuids( + { callCluster, timestamp }: StatsCollectionConfig, maxBucketSize: number ) { + const start = moment(timestamp).subtract(CLUSTER_DETAILS_FETCH_INTERVAL, 'ms').toISOString(); + + const end = moment(timestamp).toISOString(); + const params = { index: INDEX_PATTERN_ELASTICSEARCH, size: 0, @@ -50,7 +57,7 @@ export function fetchClusterUuids( }, }; - return callCluster('search', params); + return await callCluster('search', params); } /** diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.test.ts index d308ddc13e40c..d7a152d7b54cd 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.test.ts @@ -15,8 +15,8 @@ describe('get_high_level_stats', () => { const callWith = sinon.stub(); const product = 'xyz'; const cloudName = 'bare-metal'; - const start = 0; - const end = 1; + const start = new Date().toISOString(); + const end = new Date().toISOString(); const response = { hits: { hits: [ diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts index 481afc86fd115..ce1c6ccd4b106 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_high_level_stats.ts @@ -249,8 +249,8 @@ function getIndexPatternForStackProduct(product: string) { export async function getHighLevelStats( callCluster: StatsCollectionConfig['callCluster'], clusterUuids: string[], - start: StatsCollectionConfig['start'], - end: StatsCollectionConfig['end'], + start: string, + end: string, product: string, maxBucketSize: number ) { @@ -270,8 +270,8 @@ export async function fetchHighLevelStats< >( callCluster: StatsCollectionConfig['callCluster'], clusterUuids: string[], - start: StatsCollectionConfig['start'] | undefined, - end: StatsCollectionConfig['end'] | undefined, + start: string, + end: string, product: string, maxBucketSize: number ): Promise> { @@ -347,7 +347,7 @@ export async function fetchHighLevelStats< export function handleHighLevelStatsResponse( response: SearchResponse<{ cluster_uuid?: string }>, product: string -) { +): ClustersHighLevelStats { const instances = response.hits?.hits || []; const clusterMap = groupInstancesByCluster(instances, product); diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_kibana_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_kibana_stats.ts index e87c8398ad0b0..3bf1d087b973a 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_kibana_stats.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_kibana_stats.ts @@ -147,13 +147,21 @@ export function combineStats( * Ensure the start and end dates are, at least, TELEMETRY_COLLECTION_INTERVAL apart * because, otherwise, we are sending telemetry with empty Kibana usage data. * - * @param {date} [start] The start time from which to get the telemetry data - * @param {date} [end] The end time from which to get the telemetry data + * @param {string} [start] The start time (in ISO string format) from which to get the telemetry data + * @param {string} [end] The end time (in ISO string format) from which to get the telemetry data */ +export function ensureTimeSpan(start: string, end: string): { start: string; end: string }; +export function ensureTimeSpan(start: string, end: undefined): { start: string; end: undefined }; +export function ensureTimeSpan(start: undefined, end: string): { start: undefined; end: string }; export function ensureTimeSpan( - start?: StatsCollectionConfig['start'], - end?: StatsCollectionConfig['end'] -) { + start: undefined, + end: undefined +): { start: undefined; end: undefined }; + +export function ensureTimeSpan( + start?: string, + end?: string +): { start: string | undefined; end: string | undefined } { // We only care if we have a start date, because that's the limit that might make us lose the document if (start) { const duration = moment.duration(TELEMETRY_COLLECTION_INTERVAL, 'milliseconds'); @@ -177,8 +185,8 @@ export function ensureTimeSpan( export async function getKibanaStats( callCluster: StatsCollectionConfig['callCluster'], clusterUuids: string[], - start: StatsCollectionConfig['start'], - end: StatsCollectionConfig['end'], + start: string, + end: string, maxBucketSize: number ) { const { start: safeStart, end: safeEnd } = ensureTimeSpan(start, end); diff --git a/x-pack/plugins/observability/public/components/app/empty_section/index.test.tsx b/x-pack/plugins/observability/public/components/app/empty_section/index.test.tsx index e04e8f050006a..6a05749df6d7a 100644 --- a/x-pack/plugins/observability/public/components/app/empty_section/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/empty_section/index.test.tsx @@ -13,7 +13,7 @@ describe('EmptySection', () => { const section: ISection = { id: 'apm', title: 'APM', - icon: 'logoAPM', + icon: 'logoObservability', description: 'foo bar', }; const { getByText, queryAllByText } = render(); @@ -26,7 +26,7 @@ describe('EmptySection', () => { const section: ISection = { id: 'apm', title: 'APM', - icon: 'logoAPM', + icon: 'logoObservability', description: 'foo bar', linkTitle: 'install agent', href: 'https://www.elastic.co', diff --git a/x-pack/plugins/observability/public/pages/home/section.ts b/x-pack/plugins/observability/public/pages/home/section.ts index 8c87f17c16b3d..21bda1ac5bbba 100644 --- a/x-pack/plugins/observability/public/pages/home/section.ts +++ b/x-pack/plugins/observability/public/pages/home/section.ts @@ -24,7 +24,7 @@ export const appsSection: ISection[] = [ title: i18n.translate('xpack.observability.section.apps.apm.title', { defaultMessage: 'APM', }), - icon: 'logoAPM', + icon: 'logoObservability', description: i18n.translate('xpack.observability.section.apps.apm.description', { defaultMessage: 'Trace transactions through a distributed architecture and map your services’ interactions to easily spot performance bottlenecks.', diff --git a/x-pack/plugins/observability/public/pages/overview/empty_section.ts b/x-pack/plugins/observability/public/pages/overview/empty_section.ts index e13efbb8ffdd2..95b46cbb782d8 100644 --- a/x-pack/plugins/observability/public/pages/overview/empty_section.ts +++ b/x-pack/plugins/observability/public/pages/overview/empty_section.ts @@ -29,7 +29,7 @@ export const getEmptySections = ({ core }: { core: AppMountContext['core'] }): I title: i18n.translate('xpack.observability.emptySection.apps.apm.title', { defaultMessage: 'APM', }), - icon: 'logoAPM', + icon: 'logoObservability', description: i18n.translate('xpack.observability.emptySection.apps.apm.description', { defaultMessage: 'Trace transactions through a distributed architecture and map your services’ interactions to easily spot performance bottlenecks.', @@ -74,7 +74,7 @@ export const getEmptySections = ({ core }: { core: AppMountContext['core'] }): I title: i18n.translate('xpack.observability.emptySection.apps.ux.title', { defaultMessage: 'User Experience', }), - icon: 'logoAPM', + icon: 'logoObservability', description: i18n.translate('xpack.observability.emptySection.apps.ux.description', { defaultMessage: 'Performance is a distribution. Measure the experiences of all visitors to your web application and understand how to improve the experience for everyone.', diff --git a/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts b/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts index 1fb5211a71bcd..41f45683d244c 100644 --- a/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts +++ b/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts @@ -7,7 +7,7 @@ import { LegacyAPICaller, Logger } from 'kibana/server'; import * as t from 'io-ts'; import { Client } from 'elasticsearch'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { ILicense } from '../../../../licensing/server'; import { createAnnotationRt, diff --git a/x-pack/plugins/painless_lab/public/application/components/main_controls.tsx b/x-pack/plugins/painless_lab/public/application/components/main_controls.tsx index e6a61995ac78a..c6c0bee98a466 100644 --- a/x-pack/plugins/painless_lab/public/application/components/main_controls.tsx +++ b/x-pack/plugins/painless_lab/public/application/components/main_controls.tsx @@ -102,7 +102,6 @@ export function MainControls({ toggleRequestFlyout, isRequestFlyoutOpen, reset, isOpen={isHelpOpen} closePopover={() => setIsHelpOpen(false)} panelPaddingSize="none" - withTitle anchorPosition="upLeft" > diff --git a/x-pack/plugins/painless_lab/public/styles/_index.scss b/x-pack/plugins/painless_lab/public/styles/_index.scss index 0a6b84523424a..14a58fe4bdb8b 100644 --- a/x-pack/plugins/painless_lab/public/styles/_index.scss +++ b/x-pack/plugins/painless_lab/public/styles/_index.scss @@ -1,5 +1,4 @@ @import '@elastic/eui/src/global_styling/variables/header'; -@import '@elastic/eui/src/components/nav_drawer/variables'; /** * This is a very brittle way of preventing the editor and other content from disappearing @@ -51,16 +50,3 @@ $headerOffset: $euiHeaderHeightCompensation * 3; // The panels container should adopt the height of the main container height: 100%; } - -/** - * 1. Hack EUI so the bottom bar doesn't obscure the nav drawer flyout, but is also not obscured - * by the main content area. - */ -.painlessLab__bottomBar { - z-index: 5; /* 1 */ - left: $euiNavDrawerWidthCollapsed; -} - -.painlessLab__bottomBar-isNavDrawerLocked { - left: $euiNavDrawerWidthExpanded; -} diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap index 4f54c2f9a1675..ff0caecf93a96 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap +++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap @@ -1270,6 +1270,7 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u > diff --git a/x-pack/plugins/security/public/authentication/overwritten_session/__snapshots__/overwritten_session_page.test.tsx.snap b/x-pack/plugins/security/public/authentication/overwritten_session/__snapshots__/overwritten_session_page.test.tsx.snap index 1124924fcdda2..f33e322cbaae4 100644 --- a/x-pack/plugins/security/public/authentication/overwritten_session/__snapshots__/overwritten_session_page.test.tsx.snap +++ b/x-pack/plugins/security/public/authentication/overwritten_session/__snapshots__/overwritten_session_page.test.tsx.snap @@ -71,14 +71,21 @@ exports[`OverwrittenSessionPage renders as expected 1`] = ` href="/mock-base-path/" > { isOpen={isMenuOpen} closePopover={() => setIsMenuOpen(false)} panelPaddingSize="none" - withTitle anchorPosition="downLeft" > diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx index a07c2e1c14ac4..57828c50ca17f 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.tsx @@ -249,7 +249,7 @@ export class FeatureTable extends Component { ); const extraAction = ( - + {feature.reserved.description} ); @@ -320,6 +320,15 @@ export class FeatureTable extends Component { options={options} idSelected={`${feature.id}_${selectedPrivilegeId ?? NO_PRIVILEGE_VALUE}`} onChange={this.onChange(feature.id)} + legend={i18n.translate('xpack.security.management.editRole.featureTable.actionLegendText', { + defaultMessage: '{featureName} feature privilege', + values: { + featureName: feature.name, + }, + })} + style={{ + minWidth: 200, + }} /> ); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx index 5432a50c1f0df..41e15387f9c47 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiText, EuiCheckbox, EuiButtonGroup } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { NO_PRIVILEGE_VALUE } from '../constants'; import { PrivilegeFormCalculator } from '../privilege_form_calculator'; import { @@ -118,7 +119,7 @@ export const SubFeatureForm = (props: Props) => { options={options} idSelected={firstSelectedPrivilege?.id ?? NO_PRIVILEGE_VALUE} isDisabled={props.disabled} - onChange={(selectedPrivilegeId) => { + onChange={(selectedPrivilegeId: string) => { // Deselect all privileges which belong to this mutually-exclusive group const privilegesWithoutGroupEntries = props.selectedFeaturePrivileges.filter( (sp) => !privilegeGroup.privileges.some((privilege) => privilege.id === sp) @@ -130,6 +131,15 @@ export const SubFeatureForm = (props: Props) => { props.onChange([...privilegesWithoutGroupEntries, selectedPrivilegeId]); } }} + legend={i18n.translate( + 'xpack.security.management.editRole.subFeatureForm.controlLegendText', + { + defaultMessage: '{subFeatureName} sub-feature privilege', + values: { + subFeatureName: props.subFeature.name, + }, + } + )} /> ); } diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx index 6601c6ae1f8d5..8254e2632aa9d 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx @@ -193,7 +193,7 @@ describe('', () => { const featurePrivilegeToggles = wrapper.find(EuiButtonGroup); expect(featurePrivilegeToggles).toHaveLength(1); - expect(featurePrivilegeToggles.find('button')).toHaveLength(3); + expect(featurePrivilegeToggles.find('input')).toHaveLength(3); (featurePrivilegeToggles.props() as EuiButtonGroupProps).onChange('feature1_all', null); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx index 28bbd55c7d544..7ce878321185a 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx @@ -222,6 +222,12 @@ export class PrivilegeSpaceForm extends Component { idSelected={this.getDisplayedBasePrivilege()} isDisabled={!hasSelectedSpaces} onChange={this.onSpaceBasePrivilegeChange} + legend={i18n.translate( + 'xpack.security.management.editRole.spacePrivilegeForm.basePrivilegeControlLegend', + { + defaultMessage: 'Privileges for all features', + } + )} /> diff --git a/x-pack/plugins/security/server/authentication/authentication_result.test.ts b/x-pack/plugins/security/server/authentication/authentication_result.test.ts index fd875a7e029d2..8460df6f5de95 100644 --- a/x-pack/plugins/security/server/authentication/authentication_result.test.ts +++ b/x-pack/plugins/security/server/authentication/authentication_result.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; import { AuthenticationResult } from './authentication_result'; diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index 51c937f1edcb5..16cb51cbfccf5 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -9,7 +9,7 @@ jest.mock('./providers/token'); jest.mock('./providers/saml'); jest.mock('./providers/http'); -import Boom from 'boom'; +import Boom from '@hapi/boom'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { loggingSystemMock, diff --git a/x-pack/plugins/security/server/authentication/index.test.ts b/x-pack/plugins/security/server/authentication/index.test.ts index b5ce5283df8ce..3d2d26550215c 100644 --- a/x-pack/plugins/security/server/authentication/index.test.ts +++ b/x-pack/plugins/security/server/authentication/index.test.ts @@ -7,7 +7,7 @@ jest.mock('./api_keys'); jest.mock('./authenticator'); -import Boom from 'boom'; +import Boom from '@hapi/boom'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts index 839b5c991f09b..ee89af10cbeba 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { errors } from 'elasticsearch'; import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.ts index d7de71f4da9ed..34ed9ac920e93 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { LegacyElasticsearchError, LegacyElasticsearchErrorHelpers, diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts index 81e9ecb8a377b..fe46b159833bc 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.ts b/x-pack/plugins/security/server/authentication/providers/oidc.ts index 9570c59f8ea1d..eede9f334a5a1 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import type from 'type-detect'; import { KibanaRequest } from '../../../../../../src/core/server'; import { AuthenticatedUser } from '../../../common/model'; diff --git a/x-pack/plugins/security/server/authentication/providers/pki.test.ts b/x-pack/plugins/security/server/authentication/providers/pki.test.ts index 053d20e37b39e..7e76f4c81998d 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.test.ts @@ -9,7 +9,7 @@ jest.mock('tls'); import { Socket } from 'net'; import { PeerCertificate, TLSSocket } from 'tls'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { errors } from 'elasticsearch'; import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; diff --git a/x-pack/plugins/security/server/authentication/providers/pki.ts b/x-pack/plugins/security/server/authentication/providers/pki.ts index 6dcb448e08150..9214a025484fe 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { DetailedPeerCertificate } from 'tls'; import { KibanaRequest } from '../../../../../../src/core/server'; import { AuthenticationResult } from '../authentication_result'; diff --git a/x-pack/plugins/security/server/authentication/providers/saml.test.ts b/x-pack/plugins/security/server/authentication/providers/saml.test.ts index 75eb7ae93f360..d160b79bf659d 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; diff --git a/x-pack/plugins/security/server/authentication/providers/saml.ts b/x-pack/plugins/security/server/authentication/providers/saml.ts index 59a1782c1f1fd..54619c851470a 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { KibanaRequest } from '../../../../../../src/core/server'; import { AuthenticatedUser } from '../../../common/model'; import { isInternalURL } from '../../../common/is_internal_url'; diff --git a/x-pack/plugins/security/server/authentication/providers/token.test.ts b/x-pack/plugins/security/server/authentication/providers/token.test.ts index ffb1c89b24e47..8cd11bce98269 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { errors } from 'elasticsearch'; import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; diff --git a/x-pack/plugins/security/server/authentication/providers/token.ts b/x-pack/plugins/security/server/authentication/providers/token.ts index 7dace488bc95a..f919c20c15225 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { KibanaRequest } from '../../../../../../src/core/server'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; diff --git a/x-pack/plugins/security/server/errors.test.ts b/x-pack/plugins/security/server/errors.test.ts index 7c4668b245b0e..630ab5b9295db 100644 --- a/x-pack/plugins/security/server/errors.test.ts +++ b/x-pack/plugins/security/server/errors.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { errors as esErrors } from 'elasticsearch'; import * as errors from './errors'; diff --git a/x-pack/plugins/security/server/errors.ts b/x-pack/plugins/security/server/errors.ts index b5f3667558f55..9c177c3916faf 100644 --- a/x-pack/plugins/security/server/errors.ts +++ b/x-pack/plugins/security/server/errors.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { CustomHttpResponseOptions, ResponseError } from '../../../../src/core/server'; export function wrapError(error: any) { diff --git a/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts b/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts index 3c6dc3c0d7bda..7968402cd2176 100644 --- a/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts @@ -9,7 +9,7 @@ import { LicenseCheck } from '../../../../licensing/server'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../index.mock'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { defineEnabledApiKeysRoutes } from './enabled'; import { APIKeys } from '../../authentication/api_keys'; diff --git a/x-pack/plugins/security/server/routes/api_keys/get.test.ts b/x-pack/plugins/security/server/routes/api_keys/get.test.ts index 40065e757e999..cb991fb2f5aac 100644 --- a/x-pack/plugins/security/server/routes/api_keys/get.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/get.test.ts @@ -10,7 +10,7 @@ import { defineGetApiKeysRoutes } from './get'; import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../index.mock'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; interface TestOptions { isAdmin?: boolean; diff --git a/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts b/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts index 33c52688ce8e3..88e52f735395d 100644 --- a/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { Type } from '@kbn/config-schema'; import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; import { LicenseCheck } from '../../../../licensing/server'; diff --git a/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts b/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts index a506cc6306c53..ecc3d32e20aec 100644 --- a/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { LicenseCheck } from '../../../../licensing/server'; import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; diff --git a/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts index 399f79f44744d..9f5ec635f56cd 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../../src/core/server'; import { LicenseCheck } from '../../../../../licensing/server'; import { defineDeleteRolesRoutes } from './delete'; diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts index d9062bcfa2efe..b25b13b9fc04a 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../../src/core/server'; import { LicenseCheck } from '../../../../../licensing/server'; import { defineGetRolesRoutes } from './get'; diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts index 66e8086d49c66..30e0c52c4c443 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../../src/core/server'; import { LicenseCheck } from '../../../../../licensing/server'; import { defineGetAllRolesRoutes } from './get_all'; diff --git a/x-pack/plugins/security/server/routes/role_mapping/get.test.ts b/x-pack/plugins/security/server/routes/role_mapping/get.test.ts index 9af7268a57f9c..2519034b386bf 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/get.test.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/get.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { routeDefinitionParamsMock } from '../index.mock'; import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; import { defineRoleMappingGetRoutes } from './get'; diff --git a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts index 90483d7c0a4d5..cf3c787e6b0db 100644 --- a/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts +++ b/x-pack/plugins/security/server/usage_collector/security_usage_collector.ts @@ -59,7 +59,7 @@ export function registerSecurityUsageCollector({ usageCollection, config, licens type: 'boolean', }, authProviderCount: { - type: 'number', + type: 'long', }, enabledAuthProviders: { type: 'array', diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 2910f02a187f4..767a2616a4c7e 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -179,3 +179,5 @@ export const showAllOthersBucket: string[] = [ 'destination.ip', 'user.name', ]; + +export const ENABLE_NEW_TIMELINE = false; diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts index 491f4f8952fd9..783f8be840b7f 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts @@ -55,7 +55,6 @@ import { MITRE_ATTACK_DETAILS, REFERENCE_URLS_DETAILS, RISK_SCORE_DETAILS, - RULE_ABOUT_DETAILS_HEADER_TOGGLE, RULE_NAME_HEADER, RULE_TYPE_DETAILS, RUNS_EVERY_DETAILS, @@ -180,7 +179,7 @@ describe('Custom detection rules creation', () => { getDetails(MITRE_ATTACK_DETAILS).should('have.text', expectedMitre); getDetails(TAGS_DETAILS).should('have.text', expectedTags); }); - cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); + cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); cy.get(DEFINITION_DETAILS).within(() => { getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join('')); @@ -333,9 +332,7 @@ describe('Custom detection rules deletion and edition', () => { getDetails(RISK_SCORE_DETAILS).should('have.text', editedRule.riskScore); getDetails(TAGS_DETAILS).should('have.text', expectedEditedtags); }); - cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE) - .eq(INVESTIGATION_NOTES_TOGGLE) - .click({ force: true }); + cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', editedRule.note); cy.get(DEFINITION_DETAILS).within(() => { getDetails(INDEX_PATTERNS_DETAILS).should( diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts index bee4713ca7cda..3d4aaca8bb78f 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts @@ -38,7 +38,6 @@ import { MITRE_ATTACK_DETAILS, REFERENCE_URLS_DETAILS, RISK_SCORE_DETAILS, - RULE_ABOUT_DETAILS_HEADER_TOGGLE, RULE_NAME_HEADER, RULE_TYPE_DETAILS, RUNS_EVERY_DETAILS, @@ -142,7 +141,7 @@ describe('Detection rules, EQL', () => { getDetails(MITRE_ATTACK_DETAILS).should('have.text', expectedMitre); getDetails(TAGS_DETAILS).should('have.text', expectedTags); }); - cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); + cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); cy.get(DEFINITION_DETAILS).within(() => { getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join('')); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts index e31fe2e9a3911..e905365d1bbb3 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts @@ -41,7 +41,6 @@ import { REFERENCE_URLS_DETAILS, RISK_SCORE_DETAILS, RISK_SCORE_OVERRIDE_DETAILS, - RULE_ABOUT_DETAILS_HEADER_TOGGLE, RULE_NAME_HEADER, RULE_NAME_OVERRIDE_DETAILS, RULE_TYPE_DETAILS, @@ -160,7 +159,7 @@ describe('Detection rules, override', () => { }); }); }); - cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); + cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); cy.get(DEFINITION_DETAILS).within(() => { getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join('')); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts index a6f974256f3e4..a9b43d82bb7fd 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts @@ -38,7 +38,6 @@ import { MITRE_ATTACK_DETAILS, REFERENCE_URLS_DETAILS, RISK_SCORE_DETAILS, - RULE_ABOUT_DETAILS_HEADER_TOGGLE, RULE_NAME_HEADER, RULE_TYPE_DETAILS, RUNS_EVERY_DETAILS, @@ -141,7 +140,7 @@ describe('Detection rules, threshold', () => { getDetails(MITRE_ATTACK_DETAILS).should('have.text', expectedMitre); getDetails(TAGS_DETAILS).should('have.text', expectedTags); }); - cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); + cy.get(INVESTIGATION_NOTES_TOGGLE).click({ force: true }); cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); cy.get(DEFINITION_DETAILS).within(() => { getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns.join('')); diff --git a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts index e40b81ed0e856..d72210dd3e083 100644 --- a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts @@ -34,7 +34,7 @@ export const INDEX_PATTERNS_DETAILS = 'Index patterns'; export const INVESTIGATION_NOTES_MARKDOWN = 'test markdown'; -export const INVESTIGATION_NOTES_TOGGLE = 1; +export const INVESTIGATION_NOTES_TOGGLE = '[data-test-subj="stepAboutDetailsToggle-notes"]'; export const MACHINE_LEARNING_JOB_ID = '[data-test-subj="machineLearningJobId"]'; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_status/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_status/index.tsx index a37c9052c2ff3..2d3a7850eb0b6 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_status/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_status/index.tsx @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useCallback } from 'react'; import styled, { css } from 'styled-components'; import { EuiBadge, + EuiButton, EuiButtonEmpty, - EuiButtonToggle, EuiDescriptionList, EuiDescriptionListDescription, EuiDescriptionListTitle, @@ -44,7 +44,7 @@ interface CaseStatusProps { onRefresh: () => void; status: string; title: string; - toggleStatusCase: (evt: unknown) => void; + toggleStatusCase: (status: boolean) => void; value: string | null; } const CaseStatusComp: React.FC = ({ @@ -62,56 +62,62 @@ const CaseStatusComp: React.FC = ({ title, toggleStatusCase, value, -}) => ( - - - - - - {i18n.STATUS} - - - {status} - - +}) => { + const handleToggleStatusCase = useCallback(() => { + toggleStatusCase(!isSelected); + }, [toggleStatusCase, isSelected]); + return ( + + + + + + {i18n.STATUS} + + + {status} + + + + + {title} + + + + + + + + + + + + {i18n.CASE_REFRESH} + - {title} - - - + + {buttonLabel} + + + + - - - - - - - {i18n.CASE_REFRESH} - - - - - - - - - - - -); + + + ); +}; export const CaseStatus = React.memo(CaseStatusComp); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx index 58d7efb763ee2..5cb6ede0d9d21 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx @@ -183,9 +183,7 @@ describe('CaseView ', () => { ); await waitFor(() => { - wrapper - .find('input[data-test-subj="toggle-case-status"]') - .simulate('change', { target: { checked: true } }); + wrapper.find('[data-test-subj="toggle-case-status"]').first().simulate('click'); expect(updateCaseProperty).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index 52cea10cfb275..7ee2b856f8786 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -5,7 +5,7 @@ */ import { - EuiButtonToggle, + EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingContent, @@ -242,10 +242,10 @@ export const CaseComponent = React.memo( ); const toggleStatusCase = useCallback( - (e) => + (nextStatus) => onUpdateField({ key: 'status', - value: e.target.checked ? 'closed' : 'open', + value: nextStatus ? 'closed' : 'open', }), [onUpdateField] ); @@ -307,6 +307,11 @@ export const CaseComponent = React.memo( [allCasesLink] ); + const isSelected = useMemo(() => caseStatusData.isSelected, [caseStatusData]); + const handleToggleStatusCase = useCallback(() => { + toggleStatusCase(!isSelected); + }, [toggleStatusCase, isSelected]); + return ( <> @@ -330,7 +335,7 @@ export const CaseComponent = React.memo( disabled={!userCanCrud} isLoading={isLoading && updateKey === 'status'} onRefresh={handleRefresh} - toggleStatusCase={toggleStatusCase} + toggleStatusCase={handleToggleStatusCase} {...caseStatusData} /> @@ -358,15 +363,16 @@ export const CaseComponent = React.memo( - + fill={caseStatusData.isSelected} + onClick={handleToggleStatusCase} + > + {caseStatusData.buttonLabel} + {hasDataToPush && ( diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.test.tsx index fba352d3ee582..e7a4e7091180a 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.test.tsx @@ -67,9 +67,9 @@ describe('ConfigureCases', () => { expect(wrapper.find(ActionsConnectorsContextProvider).exists()).toBeTruthy(); }); - test('it renders the ConnectorAddFlyout', () => { + test('it does NOT render the ConnectorAddFlyout', () => { // Components from triggersActionsUi do not have a data-test-subj - expect(wrapper.find(ConnectorAddFlyout).exists()).toBeTruthy(); + expect(wrapper.find(ConnectorAddFlyout).exists()).toBeFalsy(); }); test('it does NOT render the ConnectorEditFlyout', () => { @@ -157,10 +157,6 @@ describe('ConfigureCases', () => { wrapper = mount(, { wrappingComponent: TestProviders }); }); - test('it renders the ConnectorEditFlyout', () => { - expect(wrapper.find(ConnectorEditFlyout).exists()).toBeTruthy(); - }); - test('it renders with correct props', () => { // Connector expect(wrapper.find(Connectors).prop('connectors')).toEqual(connectors); @@ -173,21 +169,8 @@ describe('ConfigureCases', () => { expect(wrapper.find(ClosureOptions).prop('closureTypeSelected')).toBe('close-by-user'); // Flyouts - expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(false); - expect(wrapper.find(ConnectorAddFlyout).prop('actionTypes')).toEqual([ - expect.objectContaining({ - id: '.servicenow', - }), - expect.objectContaining({ - id: '.jira', - }), - expect.objectContaining({ - id: '.resilient', - }), - ]); - - expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(false); - expect(wrapper.find(ConnectorEditFlyout).prop('initialConnector')).toEqual(connectors[0]); + expect(wrapper.find(ConnectorAddFlyout).exists()).toBe(false); + expect(wrapper.find(ConnectorEditFlyout).exists()).toBe(false); }); test('it disables correctly when the user cannot crud', () => { @@ -518,7 +501,18 @@ describe('user interactions', () => { wrapper.find('button[data-test-subj="dropdown-connector-add-connector"]').simulate('click'); wrapper.update(); - expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(true); + expect(wrapper.find(ConnectorAddFlyout).exists()).toBe(true); + expect(wrapper.find(ConnectorAddFlyout).prop('actionTypes')).toEqual([ + expect.objectContaining({ + id: '.servicenow', + }), + expect.objectContaining({ + id: '.jira', + }), + expect.objectContaining({ + id: '.resilient', + }), + ]); }); test('it show the edit flyout when pressing the update connector button', () => { @@ -528,7 +522,8 @@ describe('user interactions', () => { .simulate('click'); wrapper.update(); - expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(true); + expect(wrapper.find(ConnectorEditFlyout).exists()).toBe(true); + expect(wrapper.find(ConnectorEditFlyout).prop('initialConnector')).toEqual(connectors[1]); expect( wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() ).toBeFalsy(); diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.tsx index 9418eabaec352..a660ac0c097bf 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useEffect, useState, Dispatch, SetStateAction } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import styled, { css } from 'styled-components'; import { EuiCallOut } from '@elastic/eui'; @@ -204,19 +204,17 @@ const ConfigureCasesComponent: React.FC = ({ userC consumer: 'case', }} > - >} - actionTypes={actionTypes} - /> - {editedConnectorItem && ( + {addFlyoutVisible && ( + handleSetAddFlyoutVisibility(false)} + actionTypes={actionTypes} + /> + )} + {editedConnectorItem && editFlyoutVisible && ( > - } + onClose={() => handleSetEditFlyoutVisibility(false)} /> )} diff --git a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap index 3df8663324fdd..6331a2e02b219 100644 --- a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap @@ -77,15 +77,6 @@ exports[`Paginated Table Component rendering it renders the default load more ta "warning": "#ffce7a", }, "euiButtonMinWidth": "112px", - "euiButtonToggleBorderColor": "#343741", - "euiButtonToggleTypes": Object { - "danger": "#ff6666", - "ghost": "#ffffff", - "primary": "#1ba9f5", - "secondary": "#7de2d1", - "text": "#98a2b3", - "warning": "#ffce7a", - }, "euiButtonTypes": Object { "danger": "#ff6666", "ghost": "#ffffff", @@ -340,15 +331,6 @@ exports[`Paginated Table Component rendering it renders the default load more ta "small": "14px", "xSmall": "12px", }, - "euiNavDrawerBackgroundColor": "#1d1e24", - "euiNavDrawerContractingDelay": "150ms", - "euiNavDrawerExpandingDelay": "250ms", - "euiNavDrawerExtendedDelay": "1000ms", - "euiNavDrawerMenuAddedDelay": "90ms", - "euiNavDrawerSideShadow": "2px 0 2px -1px rgba(0, 0, 0, 0.3)", - "euiNavDrawerTopPosition": "49px", - "euiNavDrawerWidthCollapsed": "48px", - "euiNavDrawerWidthExpanded": "240px", "euiPageBackgroundColor": "#1a1b20", "euiPaletteColorBlind": Object { "euiColorVis0": Object { @@ -402,10 +384,22 @@ exports[`Paginated Table Component rendering it renders the default load more ta "euiPopoverTranslateDistance": "8px", "euiProgressColors": Object { "accent": "#f990c0", + "customColor": "currentColor", "danger": "#ff6666", "primary": "#1ba9f5", "secondary": "#7de2d1", "subdued": "#81858f", + "success": "#7de2d1", + "vis0": "#54b399", + "vis1": "#6092c0", + "vis2": "#d36086", + "vis3": "#9170b8", + "vis4": "#ca8eae", + "vis5": "#d6bf57", + "vis6": "#b9a888", + "vis7": "#da8b45", + "vis8": "#aa6556", + "vis9": "#e7664c", "warning": "#ffce7a", }, "euiProgressSizes": Object { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.test.tsx index 757319e7aa1ae..555e02d13d723 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.test.tsx @@ -104,8 +104,8 @@ describe('StepAboutRuleToggleDetails', () => { ); expect(wrapper.find(EuiButtonGroup).exists()).toBeTruthy(); - expect(wrapper.find('EuiButtonToggle[id="details"]').at(0).prop('isSelected')).toBeTruthy(); - expect(wrapper.find('EuiButtonToggle[id="notes"]').at(0).prop('isSelected')).toBeFalsy(); + expect(wrapper.find('#details').at(0).prop('isSelected')).toBeTruthy(); + expect(wrapper.find('#notes').at(0).prop('isSelected')).toBeFalsy(); }); test('it allows users to toggle between "details" and "note"', () => { @@ -122,16 +122,17 @@ describe('StepAboutRuleToggleDetails', () => { ); - expect(wrapper.find('EuiButtonGroup[idSelected="details"]').exists()).toBeTruthy(); - expect(wrapper.find('EuiButtonGroup[idSelected="notes"]').exists()).toBeFalsy(); + expect(wrapper.find('[idSelected="details"]').exists()).toBeTruthy(); + expect(wrapper.find('[idSelected="notes"]').exists()).toBeFalsy(); wrapper - .find('input[title="Investigation guide"]') + .find('[title="Investigation guide"]') .at(0) + .find('input') .simulate('change', { target: { value: 'notes' } }); - expect(wrapper.find('EuiButtonGroup[idSelected="details"]').exists()).toBeFalsy(); - expect(wrapper.find('EuiButtonGroup[idSelected="notes"]').exists()).toBeTruthy(); + expect(wrapper.find('[idSelected="details"]').exists()).toBeFalsy(); + expect(wrapper.find('[idSelected="notes"]').exists()).toBeTruthy(); }); test('it displays notes markdown when user toggles to "notes"', () => { @@ -149,8 +150,9 @@ describe('StepAboutRuleToggleDetails', () => { ); wrapper - .find('input[title="Investigation guide"]') + .find('[title="Investigation guide"]') .at(0) + .find('input') .simulate('change', { target: { value: 'notes' } }); expect(wrapper.find('EuiButtonGroup[idSelected="notes"]').exists()).toBeTruthy(); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.tsx index 52e9dc7e44ff7..fb98233bf8cc7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/index.tsx @@ -8,7 +8,7 @@ import { EuiPanel, EuiProgress, EuiButtonGroup, - EuiButtonGroupOption, + EuiButtonGroupOptionProps, EuiSpacer, EuiFlexItem, EuiText, @@ -46,14 +46,16 @@ const AboutContent = styled.div` height: 100%; `; -const toggleOptions: EuiButtonGroupOption[] = [ +const toggleOptions: EuiButtonGroupOptionProps[] = [ { id: 'details', label: i18n.ABOUT_PANEL_DETAILS_TAB, + 'data-test-subj': 'stepAboutDetailsToggle-details', }, { id: 'notes', label: i18n.ABOUT_PANEL_NOTES_TAB, + 'data-test-subj': 'stepAboutDetailsToggle-notes', }, ]; @@ -98,6 +100,7 @@ const StepAboutRuleToggleDetailsComponent: React.FC = ({ setToggleOption(val); }} data-test-subj="stepAboutDetailsToggle" + legend={i18n.ABOUT_CONTROL_LEGEND} /> )} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/translations.ts b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/translations.ts index e1b89b1ec8ce2..8a98697b523d7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule_details/translations.ts @@ -25,3 +25,10 @@ export const ABOUT_PANEL_NOTES_TAB = i18n.translate( defaultMessage: 'Investigation guide', } ); + +export const ABOUT_CONTROL_LEGEND = i18n.translate( + 'xpack.securitySolution.detectionEngine.details.stepAboutRule.controlLegend', + { + defaultMessage: 'Viewing', + } +); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap index 86cb203671ac2..c82b9cac8ab1f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap @@ -2653,7 +2653,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` class="euiFlexItem euiFlexItem--flexGrowZero" >
); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_cases/filters/index.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_cases/filters/index.tsx index af2c1523605d2..1bf608787fd7e 100644 --- a/x-pack/plugins/security_solution/public/overview/components/recent_cases/filters/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/recent_cases/filters/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButtonGroup, EuiButtonGroupOption } from '@elastic/eui'; +import { EuiButtonGroup, EuiButtonGroupOptionProps } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import { FilterMode } from '../types'; @@ -13,7 +13,7 @@ import * as i18n from '../translations'; const MY_RECENTLY_REPORTED_ID = 'myRecentlyReported'; -const toggleButtonIcons: EuiButtonGroupOption[] = [ +const toggleButtonIcons: EuiButtonGroupOptionProps[] = [ { id: 'recentlyCreated', label: i18n.RECENTLY_CREATED_CASES, @@ -45,7 +45,15 @@ export const Filters = React.memo<{ [setFilterBy] ); - return ; + return ( + + ); }); Filters.displayName = 'Filters'; diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_cases/translations.ts b/x-pack/plugins/security_solution/public/overview/components/recent_cases/translations.ts index f9b3e05ad9595..ff5585affb475 100644 --- a/x-pack/plugins/security_solution/public/overview/components/recent_cases/translations.ts +++ b/x-pack/plugins/security_solution/public/overview/components/recent_cases/translations.ts @@ -41,3 +41,10 @@ export const VIEW_ALL_CASES = i18n.translate( defaultMessage: 'View all cases', } ); + +export const CASES_FILTER_CONTROL = i18n.translate( + 'xpack.securitySolution.recentCases.controlLegend', + { + defaultMessage: 'Cases filter', + } +); diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_timelines/filters/index.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_timelines/filters/index.tsx index 815768482781b..bd6f1271f3073 100644 --- a/x-pack/plugins/security_solution/public/overview/components/recent_timelines/filters/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/recent_timelines/filters/index.tsx @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButtonGroup, EuiButtonGroupOption } from '@elastic/eui'; +import { EuiButtonGroup, EuiButtonGroupOptionProps } from '@elastic/eui'; import React from 'react'; import { FilterMode } from '../types'; import * as i18n from '../translations'; -const toggleButtonIcons: EuiButtonGroupOption[] = [ +const toggleButtonIcons: EuiButtonGroupOptionProps[] = [ { id: 'favorites', label: i18n.FAVORITES, @@ -35,6 +35,7 @@ export const Filters = React.memo<{ setFilterBy(f as FilterMode); }} isIconOnly + legend={i18n.TIMELINES_FILTER_CONTROL} /> )); diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_timelines/translations.ts b/x-pack/plugins/security_solution/public/overview/components/recent_timelines/translations.ts index 468773ae90790..998e333d727d2 100644 --- a/x-pack/plugins/security_solution/public/overview/components/recent_timelines/translations.ts +++ b/x-pack/plugins/security_solution/public/overview/components/recent_timelines/translations.ts @@ -81,3 +81,10 @@ export const VIEW_ALL_TIMELINES = i18n.translate( defaultMessage: 'View all timelines', } ); + +export const TIMELINES_FILTER_CONTROL = i18n.translate( + 'xpack.securitySolution.recentTimelines.filterControlLegend', + { + defaultMessage: 'Timelines filter', + } +); diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx index a711e7a1d0442..0737db7a00788 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx @@ -125,13 +125,27 @@ const makeMapStateToProps = () => { const mapDispatchToProps = (dispatch: Dispatch, { timelineId }: OwnProps) => ({ associateNote: (noteId: string) => dispatch(timelineActions.addNote({ id: timelineId, noteId })), - updateDescription: ({ id, description }: { id: string; description: string }) => - dispatch(timelineActions.updateDescription({ id, description })), + updateDescription: ({ + id, + description, + disableAutoSave, + }: { + id: string; + description: string; + disableAutoSave?: boolean; + }) => dispatch(timelineActions.updateDescription({ id, description, disableAutoSave })), updateIsFavorite: ({ id, isFavorite }: { id: string; isFavorite: boolean }) => dispatch(timelineActions.updateIsFavorite({ id, isFavorite })), updateNote: (note: Note) => dispatch(appActions.updateNote({ note })), - updateTitle: ({ id, title }: { id: string; title: string }) => - dispatch(timelineActions.updateTitle({ id, title })), + updateTitle: ({ + id, + title, + disableAutoSave, + }: { + id: string; + title: string; + disableAutoSave?: boolean; + }) => dispatch(timelineActions.updateTitle({ id, title, disableAutoSave })), toggleLock: ({ linkToId }: { linkToId: InputsModelId }) => dispatch(inputsActions.toggleTimelineLinkTo({ linkToId })), }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap index 10ad0123f7fc6..17c614bd2c83c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap @@ -77,15 +77,6 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` "warning": "#ffce7a", }, "euiButtonMinWidth": "112px", - "euiButtonToggleBorderColor": "#343741", - "euiButtonToggleTypes": Object { - "danger": "#ff6666", - "ghost": "#ffffff", - "primary": "#1ba9f5", - "secondary": "#7de2d1", - "text": "#98a2b3", - "warning": "#ffce7a", - }, "euiButtonTypes": Object { "danger": "#ff6666", "ghost": "#ffffff", @@ -340,15 +331,6 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` "small": "14px", "xSmall": "12px", }, - "euiNavDrawerBackgroundColor": "#1d1e24", - "euiNavDrawerContractingDelay": "150ms", - "euiNavDrawerExpandingDelay": "250ms", - "euiNavDrawerExtendedDelay": "1000ms", - "euiNavDrawerMenuAddedDelay": "90ms", - "euiNavDrawerSideShadow": "2px 0 2px -1px rgba(0, 0, 0, 0.3)", - "euiNavDrawerTopPosition": "49px", - "euiNavDrawerWidthCollapsed": "48px", - "euiNavDrawerWidthExpanded": "240px", "euiPageBackgroundColor": "#1a1b20", "euiPaletteColorBlind": Object { "euiColorVis0": Object { @@ -402,10 +384,22 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` "euiPopoverTranslateDistance": "8px", "euiProgressColors": Object { "accent": "#f990c0", + "customColor": "currentColor", "danger": "#ff6666", "primary": "#1ba9f5", "secondary": "#7de2d1", "subdued": "#81858f", + "success": "#7de2d1", + "vis0": "#54b399", + "vis1": "#6092c0", + "vis2": "#d36086", + "vis3": "#9170b8", + "vis4": "#ca8eae", + "vis5": "#d6bf57", + "vis6": "#b9a888", + "vis7": "#da8b45", + "vis8": "#aa6556", + "vis9": "#e7664c", "warning": "#ffce7a", }, "euiProgressSizes": Object { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/add_data_provider_popover.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/add_data_provider_popover.tsx index 0cd7032596f15..ff3df357f7337 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/add_data_provider_popover.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/add_data_provider_popover.tsx @@ -196,7 +196,6 @@ const AddDataProviderPopoverComponent: React.FC = ( isOpen={isAddFilterPopoverOpen} closePopover={handleClosePopover} anchorPosition="downLeft" - withTitle panelPaddingSize="none" ownFocus={true} repositionOnScroll diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.test.tsx new file mode 100644 index 0000000000000..e9dc312ee8d19 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.test.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow, mount } from 'enzyme'; + +import { SaveTimelineButton } from './save_timeline_button'; +import { act } from '@testing-library/react-hooks'; + +jest.mock('react-redux', () => { + const actual = jest.requireActual('react-redux'); + return { + ...actual, + useDispatch: jest.fn(), + }; +}); + +jest.mock('./title_and_description'); + +describe('SaveTimelineButton', () => { + const props = { + timelineId: 'timeline-1', + showOverlay: false, + toolTip: 'tooltip message', + toggleSaveTimeline: jest.fn(), + onSaveTimeline: jest.fn(), + updateTitle: jest.fn(), + updateDescription: jest.fn(), + }; + test('Show tooltip', () => { + const component = shallow(); + expect(component.find('[data-test-subj="save-timeline-btn-tooltip"]').exists()).toEqual(true); + }); + + test('Hide tooltip', () => { + const testProps = { + ...props, + showOverlay: true, + }; + const component = mount(); + component.find('[data-test-subj="save-timeline-button-icon"]').first().simulate('click'); + + act(() => { + expect(component.find('[data-test-subj="save-timeline-btn-tooltip"]').exists()).toEqual( + false + ); + }); + }); + + test('should show a button with pencil icon', () => { + const component = shallow(); + expect(component.find('[data-test-subj="save-timeline-button-icon"]').prop('iconType')).toEqual( + 'pencil' + ); + }); + + test('should not show a modal when showOverlay equals false', () => { + const component = shallow(); + expect(component.find('[data-test-subj="save-timeline-modal"]').exists()).toEqual(false); + }); + + test('should show a modal when showOverlay equals true', () => { + const testProps = { + ...props, + showOverlay: true, + }; + const component = mount(); + component.find('[data-test-subj="save-timeline-button-icon"]').first().simulate('click'); + act(() => { + expect(component.find('[data-test-subj="save-timeline-modal"]').exists()).toEqual(true); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.tsx new file mode 100644 index 0000000000000..476ef8d1dd5a1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.tsx @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiButtonIcon, EuiOverlayMask, EuiModal, EuiToolTip } from '@elastic/eui'; + +import React, { useCallback, useMemo, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { timelineActions } from '../../../store/timeline'; +import { NOTES_PANEL_WIDTH } from '../properties/notes_size'; + +import { TimelineTitleAndDescription } from './title_and_description'; +import { EDIT } from './translations'; + +export interface SaveTimelineComponentProps { + timelineId: string; + toolTip?: string; +} + +export const SaveTimelineButton = React.memo( + ({ timelineId, toolTip }) => { + const [showSaveTimelineOverlay, setShowSaveTimelineOverlay] = useState(false); + const onToggleSaveTimeline = useCallback(() => { + setShowSaveTimelineOverlay((prevShowSaveTimelineOverlay) => !prevShowSaveTimelineOverlay); + }, [setShowSaveTimelineOverlay]); + + const dispatch = useDispatch(); + const updateTitle = useCallback( + ({ id, title, disableAutoSave }: { id: string; title: string; disableAutoSave?: boolean }) => + dispatch(timelineActions.updateTitle({ id, title, disableAutoSave })), + [dispatch] + ); + + const updateDescription = useCallback( + ({ + id, + description, + disableAutoSave, + }: { + id: string; + description: string; + disableAutoSave?: boolean; + }) => dispatch(timelineActions.updateDescription({ id, description, disableAutoSave })), + [dispatch] + ); + + const saveTimelineButtonIcon = useMemo( + () => ( + + ), + [onToggleSaveTimeline] + ); + + return showSaveTimelineOverlay ? ( + <> + {saveTimelineButtonIcon} + + + + + + + ) : ( + + {saveTimelineButtonIcon} + + ); + } +); + +SaveTimelineButton.displayName = 'SaveTimelineButton'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.test.tsx new file mode 100644 index 0000000000000..bcc90a25d5789 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.test.tsx @@ -0,0 +1,261 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import { TimelineTitleAndDescription } from './title_and_description'; +import { useShallowEqualSelector } from '../../../../common/hooks/use_selector'; +import { useCreateTimelineButton } from '../properties/use_create_timeline'; +import { TimelineType } from '../../../../../common/types/timeline'; +import * as i18n from './translations'; + +jest.mock('../../../../common/hooks/use_selector', () => ({ + useShallowEqualSelector: jest.fn(), +})); + +jest.mock('../../../../timelines/store/timeline', () => ({ + timelineSelectors: { + selectTimeline: jest.fn(), + }, +})); + +jest.mock('../properties/use_create_timeline', () => ({ + useCreateTimelineButton: jest.fn(), +})); + +jest.mock('react-redux', () => { + const actual = jest.requireActual('react-redux'); + return { + ...actual, + useDispatch: jest.fn(), + }; +}); + +describe('TimelineTitleAndDescription', () => { + describe('save timeline', () => { + const props = { + timelineId: 'timeline-1', + toggleSaveTimeline: jest.fn(), + onSaveTimeline: jest.fn(), + updateTitle: jest.fn(), + updateDescription: jest.fn(), + }; + + const mockGetButton = jest.fn().mockReturnValue(
); + + beforeEach(() => { + (useShallowEqualSelector as jest.Mock).mockReturnValue({ + description: '', + isSaving: true, + savedObjectId: null, + title: 'my timeline', + timelineType: TimelineType.default, + }); + (useCreateTimelineButton as jest.Mock).mockReturnValue({ + getButton: mockGetButton, + }); + }); + + afterEach(() => { + (useShallowEqualSelector as jest.Mock).mockReset(); + (useCreateTimelineButton as jest.Mock).mockReset(); + mockGetButton.mockClear(); + }); + + test('show proress bar while saving', () => { + const component = shallow(); + expect(component.find('[data-test-subj="progress-bar"]').exists()).toEqual(true); + }); + + test('Show correct header for save timeline modal', () => { + const component = shallow(); + expect(component.find('[data-test-subj="modal-header"]').prop('children')).toEqual( + i18n.SAVE_TIMELINE + ); + }); + + test('Show correct header for save timeline template modal', () => { + (useShallowEqualSelector as jest.Mock).mockReturnValue({ + description: '', + isSaving: true, + savedObjectId: null, + title: 'my timeline', + timelineType: TimelineType.template, + }); + const component = shallow(); + expect(component.find('[data-test-subj="modal-header"]').prop('children')).toEqual( + i18n.SAVE_TIMELINE_TEMPLATE + ); + }); + + test('Show name field', () => { + const component = shallow(); + expect(component.find('[data-test-subj="save-timeline-name"]').exists()).toEqual(true); + }); + + test('Show description field', () => { + const component = shallow(); + expect(component.find('[data-test-subj="save-timeline-description"]').exists()).toEqual(true); + }); + + test('Show close button', () => { + const component = shallow(); + expect(component.find('[data-test-subj="close-button"]').exists()).toEqual(true); + }); + + test('Show saveButton', () => { + const component = shallow(); + expect(component.find('[data-test-subj="save-button"]').exists()).toEqual(true); + }); + }); + + describe('update timeline', () => { + const props = { + timelineId: 'timeline-1', + toggleSaveTimeline: jest.fn(), + onSaveTimeline: jest.fn(), + updateTitle: jest.fn(), + updateDescription: jest.fn(), + }; + + const mockGetButton = jest.fn().mockReturnValue(
); + + beforeEach(() => { + (useShallowEqualSelector as jest.Mock).mockReturnValue({ + description: 'xxxx', + isSaving: true, + savedObjectId: '1234', + title: 'my timeline', + timelineType: TimelineType.default, + }); + (useCreateTimelineButton as jest.Mock).mockReturnValue({ + getButton: mockGetButton, + }); + }); + + afterEach(() => { + (useShallowEqualSelector as jest.Mock).mockReset(); + (useCreateTimelineButton as jest.Mock).mockReset(); + mockGetButton.mockClear(); + }); + + test('show proress bar while saving', () => { + const component = shallow(); + expect(component.find('[data-test-subj="progress-bar"]').exists()).toEqual(true); + }); + + test('Show correct header for save timeline modal', () => { + const component = shallow(); + expect(component.find('[data-test-subj="modal-header"]').prop('children')).toEqual( + i18n.NAME_TIMELINE + ); + }); + + test('Show correct header for save timeline template modal', () => { + (useShallowEqualSelector as jest.Mock).mockReturnValue({ + description: 'xxxx', + isSaving: true, + savedObjectId: '1234', + title: 'my timeline', + timelineType: TimelineType.template, + }); + const component = shallow(); + expect(component.find('[data-test-subj="modal-header"]').prop('children')).toEqual( + i18n.NAME_TIMELINE_TEMPLATE + ); + }); + + test('Show name field', () => { + const component = shallow(); + expect(component.find('[data-test-subj="save-timeline-name"]').exists()).toEqual(true); + }); + + test('Show description field', () => { + const component = shallow(); + expect(component.find('[data-test-subj="save-timeline-description"]').exists()).toEqual(true); + }); + + test('Show saveButton', () => { + const component = shallow(); + expect(component.find('[data-test-subj="save-button"]').exists()).toEqual(true); + }); + }); + + describe('showWarning', () => { + const props = { + timelineId: 'timeline-1', + toggleSaveTimeline: jest.fn(), + onSaveTimeline: jest.fn(), + updateTitle: jest.fn(), + updateDescription: jest.fn(), + showWarning: true, + }; + + const mockGetButton = jest.fn().mockReturnValue(
); + + beforeEach(() => { + (useShallowEqualSelector as jest.Mock).mockReturnValue({ + description: '', + isSaving: true, + savedObjectId: null, + title: 'my timeline', + timelineType: TimelineType.default, + showWarnging: true, + }); + (useCreateTimelineButton as jest.Mock).mockReturnValue({ + getButton: mockGetButton, + }); + }); + + afterEach(() => { + (useShallowEqualSelector as jest.Mock).mockReset(); + (useCreateTimelineButton as jest.Mock).mockReset(); + mockGetButton.mockClear(); + }); + + test('Show EuiCallOut', () => { + const component = shallow(); + expect(component.find('[data-test-subj="save-timeline-callout"]').exists()).toEqual(true); + }); + + test('Show discardTimelineButton', () => { + const component = shallow(); + expect(component.find('[data-test-subj="mock-discard-button"]').exists()).toEqual(true); + }); + + test('get discardTimelineButton with correct props', () => { + shallow(); + expect(mockGetButton).toBeCalledWith({ + title: i18n.DISCARD_TIMELINE, + outline: true, + iconType: '', + fill: false, + }); + }); + + test('get discardTimelineTemplateButton with correct props', () => { + (useShallowEqualSelector as jest.Mock).mockReturnValue({ + description: 'xxxx', + isSaving: true, + savedObjectId: null, + title: 'my timeline', + timelineType: TimelineType.template, + }); + shallow(); + expect(mockGetButton).toBeCalledWith({ + title: i18n.DISCARD_TIMELINE_TEMPLATE, + outline: true, + iconType: '', + fill: false, + }); + }); + + test('Show saveButton', () => { + const component = shallow(); + expect(component.find('[data-test-subj="save-button"]').exists()).toEqual(true); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx new file mode 100644 index 0000000000000..3597b26e2663a --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx @@ -0,0 +1,218 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiButton, + EuiFlexGroup, + EuiFormRow, + EuiFlexItem, + EuiModalBody, + EuiModalHeader, + EuiSpacer, + EuiProgress, + EuiCallOut, +} from '@elastic/eui'; +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; +import { useDispatch } from 'react-redux'; +import styled from 'styled-components'; +import { TimelineType } from '../../../../../common/types/timeline'; +import { useShallowEqualSelector } from '../../../../common/hooks/use_selector'; +import { timelineActions, timelineSelectors } from '../../../../timelines/store/timeline'; +import { TimelineInput } from '../../../store/timeline/actions'; +import { Description, Name, UpdateTitle, UpdateDescription } from '../properties/helpers'; +import { TIMELINE_TITLE, DESCRIPTION, OPTIONAL } from '../properties/translations'; +import { useCreateTimelineButton } from '../properties/use_create_timeline'; +import * as i18n from './translations'; + +interface TimelineTitleAndDescriptionProps { + showWarning?: boolean; + timelineId: string; + toggleSaveTimeline: () => void; + updateTitle: UpdateTitle; + updateDescription: UpdateDescription; +} + +const Wrapper = styled(EuiModalBody)` + .euiFormRow { + max-width: none; + } + + .euiFormControlLayout { + max-width: none; + } + + .euiFieldText { + max-width: none; + } +`; + +Wrapper.displayName = 'Wrapper'; + +const usePrevious = (value: unknown) => { + const ref = useRef(); + useEffect(() => { + ref.current = value; + }); + return ref.current; +}; + +// when showWarning equals to true, +// the modal is used as a reminder for users to save / discard +// the unsaved timeline / template +export const TimelineTitleAndDescription = React.memo( + ({ timelineId, toggleSaveTimeline, updateTitle, updateDescription, showWarning }) => { + const timeline = useShallowEqualSelector((state) => + timelineSelectors.selectTimeline(state, timelineId) + ); + + const { description, isSaving, savedObjectId, title, timelineType } = timeline; + + const prevIsSaving = usePrevious(isSaving); + const dispatch = useDispatch(); + const onSaveTimeline = useCallback( + (args: TimelineInput) => dispatch(timelineActions.saveTimeline(args)), + [dispatch] + ); + + const handleClick = useCallback(() => { + onSaveTimeline({ + ...timeline, + id: timelineId, + }); + }, [onSaveTimeline, timeline, timelineId]); + + const { getButton } = useCreateTimelineButton({ timelineId, timelineType }); + + const discardTimelineButton = useMemo( + () => + getButton({ + title: + timelineType === TimelineType.template + ? i18n.DISCARD_TIMELINE_TEMPLATE + : i18n.DISCARD_TIMELINE, + outline: true, + iconType: '', + fill: false, + }), + [getButton, timelineType] + ); + + useEffect(() => { + if (!isSaving && prevIsSaving) { + toggleSaveTimeline(); + } + }, [isSaving, prevIsSaving, toggleSaveTimeline]); + + const modalHeader = + savedObjectId == null + ? timelineType === TimelineType.template + ? i18n.SAVE_TIMELINE_TEMPLATE + : i18n.SAVE_TIMELINE + : timelineType === TimelineType.template + ? i18n.NAME_TIMELINE_TEMPLATE + : i18n.NAME_TIMELINE; + + const saveButtonTitle = + savedObjectId == null && showWarning + ? timelineType === TimelineType.template + ? i18n.SAVE_TIMELINE_TEMPLATE + : i18n.SAVE_TIMELINE + : i18n.SAVE; + + const calloutMessage = useMemo(() => i18n.UNSAVED_TIMELINE_WARNING(timelineType), [ + timelineType, + ]); + + const descriptionLabel = savedObjectId == null ? `${DESCRIPTION} (${OPTIONAL})` : DESCRIPTION; + + return ( + <> + {isSaving && ( + + )} + {modalHeader} + + + {showWarning && ( + + + + + )} + + + + + + + + + + + + + + + + {savedObjectId == null && showWarning ? ( + discardTimelineButton + ) : ( + + {i18n.CLOSE_MODAL} + + )} + + + + {saveButtonTitle} + + + + + + + ); + } +); + +TimelineTitleAndDescription.displayName = 'TimelineTitleAndDescription'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/translations.ts index 89ad11d75cae1..80aa719a3469d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/translations.ts @@ -5,6 +5,7 @@ */ import { i18n } from '@kbn/i18n'; +import { TimelineType, TimelineTypeLiteral } from '../../../../../common/types/timeline'; export const CALL_OUT_UNAUTHORIZED_MSG = i18n.translate( 'xpack.securitySolution.timeline.callOut.unauthorized.message.description', @@ -21,3 +22,66 @@ export const CALL_OUT_IMMUTABLE = i18n.translate( 'This prebuilt timeline template cannot be modified. To make changes, please duplicate this template and make modifications to the duplicate template.', } ); + +export const EDIT = i18n.translate('xpack.securitySolution.timeline.saveTimeline.modal.button', { + defaultMessage: 'edit', +}); + +export const SAVE_TIMELINE = i18n.translate( + 'xpack.securitySolution.timeline.saveTimeline.modal.header', + { + defaultMessage: 'Save Timeline', + } +); + +export const SAVE_TIMELINE_TEMPLATE = i18n.translate( + 'xpack.securitySolution.timeline.saveTimelineTemplate.modal.header', + { + defaultMessage: 'Save Timeline Template', + } +); + +export const SAVE = i18n.translate('xpack.securitySolution.timeline.nameTimeline.save.title', { + defaultMessage: 'Save', +}); + +export const NAME_TIMELINE = i18n.translate( + 'xpack.securitySolution.timeline.nameTimeline.modal.header', + { + defaultMessage: 'Name Timeline', + } +); + +export const NAME_TIMELINE_TEMPLATE = i18n.translate( + 'xpack.securitySolution.timeline.nameTimelineTemplate.modal.header', + { + defaultMessage: 'Name Timeline Template', + } +); + +export const DISCARD_TIMELINE = i18n.translate( + 'xpack.securitySolution.timeline.saveTimeline.modal.discard.title', + { + defaultMessage: 'Discard Timeline', + } +); + +export const DISCARD_TIMELINE_TEMPLATE = i18n.translate( + 'xpack.securitySolution.timeline.saveTimelineTemplate.modal.discard.title', + { + defaultMessage: 'Discard Timeline Template', + } +); + +export const CLOSE_MODAL = i18n.translate( + 'xpack.securitySolution.timeline.saveTimeline.modal.close.title', + { + defaultMessage: 'Close', + } +); + +export const UNSAVED_TIMELINE_WARNING = (timelineType: TimelineTypeLiteral) => + i18n.translate('xpack.securitySolution.timeline.saveTimeline.modal.warning.title', { + values: { timeline: timelineType === TimelineType.template ? 'timeline template' : 'timeline' }, + defaultMessage: 'You have an unsaved {timeline}. Do you wish to save it?', + }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx index 887c2e1e825f8..dd0695e795397 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx @@ -5,8 +5,10 @@ */ import React from 'react'; import { mount, shallow } from 'enzyme'; -import { NewTimeline, NewTimelineProps } from './helpers'; +import { Description, Name, NewTimeline, NewTimelineProps } from './helpers'; import { useCreateTimelineButton } from './use_create_timeline'; +import * as i18n from './translations'; +import { TimelineType } from '../../../../../common/types/timeline'; jest.mock('./use_create_timeline', () => ({ useCreateTimelineButton: jest.fn(), @@ -83,3 +85,72 @@ describe('NewTimeline', () => { }); }); }); + +describe('Description', () => { + const props = { + description: 'xxx', + timelineId: 'timeline-1', + updateDescription: jest.fn(), + }; + + test('should render tooltip', () => { + const component = shallow(); + expect( + component.find('[data-test-subj="timeline-description-tool-tip"]').prop('content') + ).toEqual(i18n.DESCRIPTION_TOOL_TIP); + }); + + test('should not render textarea if isTextArea is false', () => { + const component = shallow(); + expect(component.find('[data-test-subj="timeline-description-textarea"]').exists()).toEqual( + false + ); + + expect(component.find('[data-test-subj="timeline-description"]').exists()).toEqual(true); + }); + + test('should render textarea if isTextArea is true', () => { + const testProps = { + ...props, + isTextArea: true, + }; + const component = shallow(); + expect(component.find('[data-test-subj="timeline-description-textarea"]').exists()).toEqual( + true + ); + }); +}); + +describe('Name', () => { + const props = { + timelineId: 'timeline-1', + timelineType: TimelineType.default, + title: 'xxx', + updateTitle: jest.fn(), + }; + + test('should render tooltip', () => { + const component = shallow(); + expect(component.find('[data-test-subj="timeline-title-tool-tip"]').prop('content')).toEqual( + i18n.TITLE + ); + }); + + test('should render placeholder by timelineType - timeline', () => { + const component = shallow(); + expect(component.find('[data-test-subj="timeline-title"]').prop('placeholder')).toEqual( + i18n.UNTITLED_TIMELINE + ); + }); + + test('should render placeholder by timelineType - timeline template', () => { + const testProps = { + ...props, + timelineType: TimelineType.template, + }; + const component = shallow(); + expect(component.find('[data-test-subj="timeline-title"]').prop('placeholder')).toEqual( + i18n.UNTITLED_TEMPLATE + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx index a28f4240d3a2f..25039dbc9529a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx @@ -16,8 +16,9 @@ import { EuiModal, EuiOverlayMask, EuiToolTip, + EuiTextArea, } from '@elastic/eui'; -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import uuid from 'uuid'; import styled from 'styled-components'; import { useDispatch } from 'react-redux'; @@ -41,14 +42,22 @@ import { Notes } from '../../notes'; import { AssociateNote, UpdateNote } from '../../notes/helpers'; import { NOTES_PANEL_WIDTH } from './notes_size'; -import { ButtonContainer, DescriptionContainer, LabelText, NameField, StyledStar } from './styles'; +import { + ButtonContainer, + DescriptionContainer, + LabelText, + NameField, + NameWrapper, + StyledStar, +} from './styles'; import * as i18n from './translations'; -import { setInsertTimeline, showTimeline } from '../../../store/timeline/actions'; +import { setInsertTimeline, showTimeline, TimelineInput } from '../../../store/timeline/actions'; import { useCreateTimelineButton } from './use_create_timeline'; export const historyToolTip = 'The chronological history of actions related to this timeline'; export const streamLiveToolTip = 'Update the Timeline as new data arrives'; export const newTimelineToolTip = 'Create a new timeline'; +export const TIMELINE_TITLE_CLASSNAME = 'timeline-title'; const NotesCountBadge = (styled(EuiBadge)` margin-left: 5px; @@ -66,8 +75,25 @@ type CreateTimeline = ({ timelineType?: TimelineTypeLiteral; }) => void; type UpdateIsFavorite = ({ id, isFavorite }: { id: string; isFavorite: boolean }) => void; -type UpdateTitle = ({ id, title }: { id: string; title: string }) => void; -type UpdateDescription = ({ id, description }: { id: string; description: string }) => void; +export type UpdateTitle = ({ + id, + title, + disableAutoSave, +}: { + id: string; + title: string; + disableAutoSave?: boolean; +}) => void; +export type UpdateDescription = ({ + id, + description, + disableAutoSave, +}: { + id: string; + description: string; + disableAutoSave?: boolean; +}) => void; +export type SaveTimeline = (args: TimelineInput) => void; export const StarIcon = React.memo<{ isFavorite: boolean; @@ -104,55 +130,146 @@ interface DescriptionProps { description: string; timelineId: string; updateDescription: UpdateDescription; + isTextArea?: boolean; + disableAutoSave?: boolean; + disableTooltip?: boolean; + disabled?: boolean; + marginRight?: number; } export const Description = React.memo( - ({ description, timelineId, updateDescription }) => ( - - - updateDescription({ id: timelineId, description: e.target.value })} - placeholder={i18n.DESCRIPTION} - spellCheck={true} - value={description} - /> + ({ + description, + timelineId, + updateDescription, + isTextArea = false, + disableAutoSave = false, + disableTooltip = false, + disabled = false, + marginRight, + }) => { + const onDescriptionChanged = useCallback( + (e) => { + updateDescription({ id: timelineId, description: e.target.value, disableAutoSave }); + }, + [updateDescription, disableAutoSave, timelineId] + ); + + const inputField = useMemo( + () => + isTextArea ? ( + + ) : ( + + ), + [description, isTextArea, onDescriptionChanged, disabled] + ); + return ( + + {disableTooltip ? ( + inputField + ) : ( + + {inputField} + + )} - - ) + ); + } ); Description.displayName = 'Description'; interface NameProps { + autoFocus?: boolean; + disableAutoSave?: boolean; + disableTooltip?: boolean; + disabled?: boolean; timelineId: string; timelineType: TimelineType; title: string; updateTitle: UpdateTitle; + width?: string; + marginRight?: number; } -export const Name = React.memo(({ timelineId, timelineType, title, updateTitle }) => { - const handleChange = useCallback((e) => updateTitle({ id: timelineId, title: e.target.value }), [ +export const Name = React.memo( + ({ + autoFocus = false, + disableAutoSave = false, + disableTooltip = false, + disabled = false, timelineId, + timelineType, + title, updateTitle, - ]); + width, + marginRight, + }) => { + const timelineNameRef = useRef(null); + + const handleChange = useCallback( + (e) => updateTitle({ id: timelineId, title: e.target.value, disableAutoSave }), + [timelineId, updateTitle, disableAutoSave] + ); - return ( - - - - ); -}); + useEffect(() => { + if (autoFocus && timelineNameRef && timelineNameRef.current) { + timelineNameRef.current.focus(); + } + }, [autoFocus]); + + const nameField = useMemo( + () => ( + + ), + [handleChange, marginRight, timelineType, title, width, disabled] + ); + + return ( + + {disableTooltip ? ( + nameField + ) : ( + + {nameField} + + )} + + ); + } +); Name.displayName = 'Name'; interface NewCaseProps { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.test.tsx index 19344a7fd7c9b..cdedca23e85af 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.test.tsx @@ -92,6 +92,7 @@ const defaultProps = { description: '', getNotesByIds: jest.fn(), noteIds: [], + saveTimeline: jest.fn(), status: TimelineStatus.active, timelineId: 'abc', toggleLock: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.tsx index 9eea95a0a9b1a..9df2b585449a0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/index.tsx @@ -92,6 +92,7 @@ export const Properties = React.memo( setShowTimelineModal(true); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const { Modal: AllCasesModal, onOpenModal: onOpenCaseModal } = useAllCasesModal({ timelineId }); const datePickerWidth = useMemo( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/properties_left.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/properties_left.tsx index a3cd8802c36bc..6b181a5af7bf3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/properties_left.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/properties_left.tsx @@ -16,6 +16,8 @@ import { SuperDatePicker } from '../../../../common/components/super_date_picker import { TimelineTypeLiteral, TimelineStatusLiteral } from '../../../../../common/types/timeline'; import * as i18n from './translations'; +import { SaveTimelineButton } from '../header/save_timeline_button'; +import { ENABLE_NEW_TIMELINE } from '../../../../../common/constants'; type UpdateIsFavorite = ({ id, isFavorite }: { id: string; isFavorite: boolean }) => void; type UpdateTitle = ({ id, title }: { id: string; title: string }) => void; @@ -122,6 +124,8 @@ export const PropertiesLeft = React.memo( ) : null} + {ENABLE_NEW_TIMELINE && } + {showNotesFromWidth ? ( (({ width }) => ({ `; DatePicker.displayName = 'DatePicker'; -export const NameField = styled(EuiFieldText)` - width: 150px; - margin-right: 5px; +export const NameField = styled(({ width, marginRight, ...rest }) => )` + width: ${({ width = '150px' }) => width}; + margin-right: ${({ marginRight = 10 }) => marginRight} px; + + .euiToolTipAnchor { + display: block; + } `; NameField.displayName = 'NameField'; -export const DescriptionContainer = styled.div` +export const NameWrapper = styled.div` + .euiToolTipAnchor { + display: block; + } +`; +NameWrapper.displayName = 'NameWrapper'; + +export const DescriptionContainer = styled.div<{ marginRight?: number }>` animation: ${fadeInEffect} 0.3s; - margin-right: 5px; + margin-right: ${({ marginRight = 5 }) => marginRight}px; min-width: 150px; + + .euiToolTipAnchor { + display: block; + } `; DescriptionContainer.displayName = 'DescriptionContainer'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/translations.ts index 1fc3b7b00f847..78d01b2d98ab3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/translations.ts @@ -34,7 +34,7 @@ export const NOT_A_FAVORITE = i18n.translate( export const TIMELINE_TITLE = i18n.translate( 'xpack.securitySolution.timeline.properties.timelineTitleAriaLabel', { - defaultMessage: 'Timeline title', + defaultMessage: 'Title', } ); @@ -194,3 +194,10 @@ export const UNLOCK_SYNC_MAIN_DATE_PICKER_ARIA = i18n.translate( defaultMessage: 'Unlock date picker to global date picker', } ); + +export const OPTIONAL = i18n.translate( + 'xpack.securitySolution.timeline.properties.timelineDescriptionOptional', + { + defaultMessage: 'Optional', + } +); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx index c21592bed12e0..10b505da5c76f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.test.tsx @@ -63,6 +63,46 @@ describe('useCreateTimelineButton', () => { }); }); + test('getButton renders correct iconType - EuiButton', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook( + () => useCreateTimelineButton({ timelineId: mockId, timelineType }), + { wrapper: wrapperContainer } + ); + await waitForNextUpdate(); + + const button = result.current.getButton({ + outline: true, + title: 'mock title', + iconType: 'pencil', + }); + const wrapper = shallow(button); + expect(wrapper.find('[data-test-subj="timeline-new-with-border"]').prop('iconType')).toEqual( + 'pencil' + ); + }); + }); + + test('getButton renders correct filling - EuiButton', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook( + () => useCreateTimelineButton({ timelineId: mockId, timelineType }), + { wrapper: wrapperContainer } + ); + await waitForNextUpdate(); + + const button = result.current.getButton({ + outline: true, + title: 'mock title', + fill: false, + }); + const wrapper = shallow(button); + expect(wrapper.find('[data-test-subj="timeline-new-with-border"]').prop('fill')).toEqual( + false + ); + }); + }); + test('getButton renders correct outline - EuiButtonEmpty', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx index 28dd865c763ae..b4d168cc980b6 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx @@ -93,15 +93,28 @@ export const useCreateTimelineButton = ({ }, [createTimeline, timelineId, timelineType, closeGearMenu]); const getButton = useCallback( - ({ outline, title }: { outline?: boolean; title?: string }) => { + ({ + outline, + title, + iconType = 'plusInCircle', + fill = true, + isDisabled = false, + }: { + outline?: boolean; + title?: string; + iconType?: string; + fill?: boolean; + isDisabled?: boolean; + }) => { const buttonProps = { - iconType: 'plusInCircle', + iconType, onClick: handleButtonClick, + fill, }; const dataTestSubjPrefix = timelineType === TimelineType.template ? `template-timeline-new` : `timeline-new`; return outline ? ( - + {title} ) : ( diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts index 472e82426468e..c066de8af9f20 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts @@ -56,7 +56,7 @@ export const applyDeltaToColumnWidth = actionCreator<{ delta: number; }>('APPLY_DELTA_TO_COLUMN_WIDTH'); -export const createTimeline = actionCreator<{ +export interface TimelineInput { id: string; dataProviders?: DataProvider[]; dateRange?: { @@ -76,9 +76,13 @@ export const createTimeline = actionCreator<{ sort?: Sort; showCheckboxes?: boolean; timelineType?: TimelineTypeLiteral; - templateTimelineId?: string; - templateTimelineVersion?: number; -}>('CREATE_TIMELINE'); + templateTimelineId?: string | null; + templateTimelineVersion?: number | null; +} + +export const saveTimeline = actionCreator('SAVE_TIMELINE'); + +export const createTimeline = actionCreator('CREATE_TIMELINE'); export const pinEvent = actionCreator<{ id: string; eventId: string }>('PIN_EVENT'); @@ -174,9 +178,11 @@ export const updateHighlightedDropAndProviderId = actionCreator<{ providerId: string; }>('UPDATE_DROP_AND_PROVIDER'); -export const updateDescription = actionCreator<{ id: string; description: string }>( - 'UPDATE_DESCRIPTION' -); +export const updateDescription = actionCreator<{ + id: string; + description: string; + disableAutoSave?: boolean; +}>('UPDATE_DESCRIPTION'); export const updateKqlMode = actionCreator<{ id: string; kqlMode: KqlMode }>('UPDATE_KQL_MODE'); @@ -205,7 +211,9 @@ export const updateItemsPerPageOptions = actionCreator<{ itemsPerPageOptions: number[]; }>('UPDATE_ITEMS_PER_PAGE_OPTIONS'); -export const updateTitle = actionCreator<{ id: string; title: string }>('UPDATE_TITLE'); +export const updateTitle = actionCreator<{ id: string; title: string; disableAutoSave?: boolean }>( + 'UPDATE_TITLE' +); export const updatePageIndex = actionCreator<{ id: string; activePage: number }>( 'UPDATE_PAGE_INDEX' diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts index cc8e856de1b16..d50de33412175 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts @@ -78,6 +78,7 @@ import { createTimeline, addTimeline, showCallOutUnauthorizedMsg, + saveTimeline, } from './actions'; import { ColumnHeaderOptions, TimelineModel } from './model'; import { epicPersistNote, timelineNoteActionsType } from './epic_note'; @@ -95,6 +96,7 @@ const timelineActionsType = [ dataProviderEdited.type, removeColumn.type, removeProvider.type, + saveTimeline.type, setExcludedRowRendererIds.type, setFilters.type, setSavedQueryId.type, @@ -179,11 +181,11 @@ export const createTimelineEpic = (): Epic< } else if ( timelineActionsType.includes(action.type) && !timelineObj.isLoading && - isItAtimelineAction(timelineId) + isItAtimelineAction(timelineId) && + !get('payload.disableAutoSave', action) ) { return true; } - return false; }), debounceTime(500), mergeMap(([action]) => { diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts index 1d956e02e7083..7c227f1c80610 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts @@ -389,7 +389,7 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState) ...state, timelineById: updateTimelineKqlMode({ id, kqlMode, timelineById: state.timelineById }), })) - .case(updateTitle, (state, { id, title }) => ({ + .case(updateTitle, (state, { id, title, disableAutoSave }) => ({ ...state, timelineById: updateTimelineTitle({ id, title, timelineById: state.timelineById }), })) diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts index c8e0292e8d93a..194f0a1c2e7c5 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { RequestHandlerContext, Logger, RequestHandler } from 'kibana/server'; import { TypeOf } from '@kbn/config-schema'; import { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts index 977dad680f8a4..25e47b38e8a56 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { SavedObjectsFindResponse } from 'kibana/server'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts index 72be7a3c0fa08..6a304a207d000 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import Joi from 'joi'; import { has, snakeCase } from 'lodash/fp'; import { SanitizedAlert } from '../../../../../alerts/common'; diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx index b4612c9df42ff..aecb3c02ef43e 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_details/policy_details.tsx @@ -214,7 +214,6 @@ export const PolicyDetails: React.FunctionComponent = ({ isOpen={isPopoverOpen} closePopover={() => setIsPopoverOpen(false)} panelPaddingSize="none" - withTitle anchorPosition="rightUp" repositionOnScroll > diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_retention_schedule/policy_retention_schedule.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_retention_schedule/policy_retention_schedule.tsx index dc5de0b4295e8..583c8e4ef1dc9 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_retention_schedule/policy_retention_schedule.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/policy_list/policy_retention_schedule/policy_retention_schedule.tsx @@ -176,7 +176,6 @@ export const PolicyRetentionSchedule: React.FunctionComponent = ({ isOpen={isPopoverOpen} closePopover={() => setIsPopoverOpen(false)} panelPaddingSize="none" - withTitle anchorPosition="rightUp" repositionOnScroll > diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx index e214db22aa9cc..f484b738bd222 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { CopySavedObjectsToSpaceFlyout } from './copy_to_space_flyout'; import { CopyToSpaceForm } from './copy_to_space_form'; diff --git a/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap b/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap index 22d65f4600e05..e02e81e497806 100644 --- a/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap +++ b/x-pack/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap @@ -26,7 +26,6 @@ exports[`NavControlPopover renders without crashing 1`] = ` ownFocus={true} panelPaddingSize="none" repositionOnScroll={true} - withTitle={true} > { anchorPosition={this.props.anchorPosition} panelPaddingSize="none" repositionOnScroll={true} - withTitle={this.props.anchorPosition.includes('down')} ownFocus > {element} diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout.test.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout.test.tsx index ad49161ddd705..0c9f4847a4952 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout.test.tsx +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { ShareSavedObjectsToSpaceFlyout } from './share_to_space_flyout'; import { ShareToSpaceForm } from './share_to_space_form'; diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/create_empty_failure_response.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/create_empty_failure_response.ts index 4913a0ae988e9..764ae5a87ec0e 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/create_empty_failure_response.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/create_empty_failure_response.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import Boom, { Payload } from 'boom'; +import Boom, { Payload } from '@hapi/boom'; import { SavedObjectsImportError } from 'src/core/server'; export const createEmptyFailureResponse = (errors?: Array) => { diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/types.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/types.ts index 8d4169f972795..d774231d3eb48 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/types.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Payload } from 'boom'; +import { Payload } from '@hapi/boom'; import { SavedObjectsImportSuccess, SavedObjectsImportError, diff --git a/x-pack/plugins/spaces/server/lib/errors.ts b/x-pack/plugins/spaces/server/lib/errors.ts index d800020038a38..13a5c2440877a 100644 --- a/x-pack/plugins/spaces/server/lib/errors.ts +++ b/x-pack/plugins/spaces/server/lib/errors.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { boomify, isBoom } from 'boom'; +import { boomify, isBoom } from '@hapi/boom'; import { ResponseError, CustomHttpResponseOptions } from 'src/core/server'; export function wrapError(error: any): CustomHttpResponseOptions { diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts index fe1acd93570f6..89371259ae04c 100644 --- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts +++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import * as Rx from 'rxjs'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { Legacy } from 'kibana'; // @ts-ignore import { kibanaTestUser } from '@kbn/test'; diff --git a/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.ts b/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.ts index 50e7182b76686..a054ff773be4d 100644 --- a/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.ts +++ b/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { omit } from 'lodash'; import { KibanaRequest } from 'src/core/server'; import { SecurityPluginSetup } from '../../../../security/server'; diff --git a/x-pack/plugins/spaces/server/routes/api/external/delete.ts b/x-pack/plugins/spaces/server/routes/api/external/delete.ts index 150f1d198cdf6..81e643bf5ede8 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/delete.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/delete.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { schema } from '@kbn/config-schema'; import { SavedObjectsErrorHelpers } from '../../../../../../../src/core/server'; import { wrapError } from '../../../lib/errors'; diff --git a/x-pack/plugins/spaces/server/routes/api/external/post.ts b/x-pack/plugins/spaces/server/routes/api/external/post.ts index 61f90adb300ab..0c77bcc74bb50 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/post.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/post.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { SavedObjectsErrorHelpers } from '../../../../../../../src/core/server'; import { wrapError } from '../../../lib/errors'; import { spaceSchema } from '../../../lib/space_schema'; diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts index f7621f11a1c05..254cf3817ca48 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts @@ -11,7 +11,7 @@ import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; import { SavedObjectTypeRegistry } from 'src/core/server'; import { SpacesClient } from '../lib/spaces_client'; import { spacesClientMock } from '../lib/spaces_client/spaces_client.mock'; -import Boom from 'boom'; +import Boom from '@hapi/boom'; const typeRegistry = new SavedObjectTypeRegistry(); typeRegistry.registerType({ diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts index 4c8e93acd68ac..e96ae49b41679 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { SavedObjectsBaseOptions, SavedObjectsBulkCreateObject, diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index 2b3ff6c8a0aef..c965623ebfc17 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -3250,7 +3250,7 @@ "type": "boolean" }, "authProviderCount": { - "type": "number" + "type": "long" }, "enabledAuthProviders": { "type": "array", diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_range_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_range_form.tsx index 7f6c23dddb9fc..8a41ea81407e4 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_range_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_range_form.tsx @@ -11,7 +11,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer, - EuiButtonToggle, + EuiButton, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { FilterAggConfigRange } from '../types'; @@ -60,18 +60,17 @@ export const FilterRangeForm: FilterAggConfigRange['aggTypeConfig']['FilterAggFo onChange={(e) => { updateConfig({ from: e.target.value === '' ? undefined : Number(e.target.value) }); }} - // @ts-ignore step="any" prepend={ - '} onChange={(e: any) => { updateConfig({ includeFrom: e.target.checked }); }} - isSelected={includeFrom} - isEmpty={!includeFrom} - /> + fill={includeFrom} + > + {includeFrom ? '≥' : '>'} + } /> @@ -91,18 +90,17 @@ export const FilterRangeForm: FilterAggConfigRange['aggTypeConfig']['FilterAggFo onChange={(e) => { updateConfig({ to: e.target.value === '' ? undefined : Number(e.target.value) }); }} - // @ts-ignore step="any" append={ - { - updateConfig({ includeTo: e.target.checked }); + onClick={() => { + updateConfig({ includeTo: !includeTo }); }} - isSelected={includeTo} - isEmpty={!includeTo} - /> + fill={includeTo} + > + {includeTo ? '≤' : '<'}s + } /> diff --git a/x-pack/plugins/transform/server/routes/api/error_utils.ts b/x-pack/plugins/transform/server/routes/api/error_utils.ts index ef5927651df88..cf388f3c8ca08 100644 --- a/x-pack/plugins/transform/server/routes/api/error_utils.ts +++ b/x-pack/plugins/transform/server/routes/api/error_utils.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; +import Boom from '@hapi/boom'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 1492c8a03906a..7386903c311d2 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -529,8 +529,6 @@ "core.ui.chrome.headerGlobalNav.helpMenuOpenGitHubIssueTitle": "GitHubで問題を開く", "core.ui.chrome.headerGlobalNav.helpMenuTitle": "ヘルプ", "core.ui.chrome.headerGlobalNav.helpMenuVersion": "v {version}", - "core.ui.chrome.sideGlobalNav.viewRecentItemsFlyoutTitle": "最近のアイテム", - "core.ui.chrome.sideGlobalNav.viewRecentItemsLabel": "最近閲覧", "core.ui.EmptyRecentlyViewed": "最近閲覧したアイテムはありません", "core.ui.enterpriseSearchNavList.label": "エンタープライズサーチ", "core.ui.errorUrlOverflow.bigUrlWarningNotificationMessage": "{advancedSettingsLink}で{storeInSessionStorageParam}オプションを有効にするか、オンスクリーンビジュアルを簡素化してください。", @@ -559,7 +557,6 @@ "core.ui.primaryNavSection.undockAriaLabel": "プライマリナビゲーションリンクの固定を解除する", "core.ui.primaryNavSection.undockLabel": "ナビゲーションの固定を解除する", "core.ui.recentLinks.linkItem.screenReaderLabel": "{recentlyAccessedItemLinklabel}、タイプ: {pageType}", - "core.ui.recentLinks.screenReaderLabel": "最近閲覧したリンク、ナビゲーション", "core.ui.recentlyViewed": "最近閲覧", "core.ui.recentlyViewedAriaLabel": "最近閲覧したリンク", "core.ui.securityNavList.label": "セキュリティ", @@ -3671,7 +3668,6 @@ "visDefaultEditor.editorConfig.dateHistogram.customInterval.helpText": "構成間隔の倍数でなければなりません: {interval}", "visDefaultEditor.editorConfig.histogram.interval.helpText": "構成間隔の倍数でなければなりません: {interval}", "visDefaultEditor.metrics.wrongLastBucketTypeErrorMessage": "「{type}」メトリック集約を使用する場合、最後のバケット集約は「Date Histogram」または「Histogram」でなければなりません。", - "visDefaultEditor.sidebar.autoApplyChangesAriaLabel": "エディターの変更を自動適用します", "visDefaultEditor.sidebar.autoApplyChangesOffLabel": "自動適用がオフです", "visDefaultEditor.sidebar.autoApplyChangesOnLabel": "自動適用がオンです", "visDefaultEditor.sidebar.autoApplyChangesTooltip": "変更されるごとにビジュアライゼーションを自動的に更新します。", @@ -3801,7 +3797,6 @@ "visTypeTimeseries.aggLookup.percentileLabel": "パーセンタイル", "visTypeTimeseries.aggLookup.percentileRankLabel": "パーセンタイルランク", "visTypeTimeseries.aggLookup.positiveOnlyLabel": "プラスのみ", - "visTypeTimeseries.aggLookup.positiveRateLabel": "正の割合", "visTypeTimeseries.aggLookup.serialDifferenceLabel": "連続差", "visTypeTimeseries.aggLookup.seriesAggLabel": "数列集約", "visTypeTimeseries.aggLookup.staticValueLabel": "不動値", @@ -3824,7 +3819,6 @@ "visTypeTimeseries.aggSelect.metricsAggs.minLabel": "最低", "visTypeTimeseries.aggSelect.metricsAggs.percentileLabel": "パーセンタイル", "visTypeTimeseries.aggSelect.metricsAggs.percentileRankLabel": "パーセンタイルランク", - "visTypeTimeseries.aggSelect.metricsAggs.positiveRateLabel": "正の割合", "visTypeTimeseries.aggSelect.metricsAggs.staticValueLabel": "不動値", "visTypeTimeseries.aggSelect.metricsAggs.stdDeviationLabel": "標準偏差", "visTypeTimeseries.aggSelect.metricsAggs.sumLabel": "合計", @@ -3868,7 +3862,6 @@ "visTypeTimeseries.calculateLabel.lookupMetricTypeOfTargetLabel": "{targetLabel} 中 {lookupMetricType}", "visTypeTimeseries.calculateLabel.lookupMetricTypeOfTargetWithAdditionalLabel": "{targetLabel} ({additionalLabel}) 中 {lookupMetricType}", "visTypeTimeseries.calculateLabel.mathLabel": "数学処理", - "visTypeTimeseries.calculateLabel.positiveRateLabel": "{field}の正の割合", "visTypeTimeseries.calculateLabel.seriesAggLabel": "数列集約 ({metricFunction})", "visTypeTimeseries.calculateLabel.staticValueLabel": "{metricValue} の不動値", "visTypeTimeseries.calculateLabel.unknownLabel": "不明", @@ -5108,10 +5101,10 @@ "xpack.apm.serviceOverview.mlNudgeMessage.content": "ML異常検知との統合により、サービスの正常性ステータスを確認できます", "xpack.apm.serviceOverview.mlNudgeMessage.dismissButton": "メッセージを消去", "xpack.apm.serviceOverview.mlNudgeMessage.learnMoreButton": "詳細", - "xpack.apm.serviceOverview.mlNudgeMessage.title": "異常検知を有効にしてサービスのヘルスを確認", - "xpack.apm.serviceOverview.toastText": "現在 Elastic Stack 7.0+ を実行中で、以前のバージョン 6.x からの互換性のないデータを検知しました。このデータを APM で表示するには、移行が必要です。詳細: ", - "xpack.apm.serviceOverview.toastTitle": "選択された時間範囲内にレガシーデータが検知されました。", - "xpack.apm.serviceOverview.upgradeAssistantLink": "アップグレードアシスタント", + "xpack.apm.serviceInventory.mlNudgeMessageTitle": "異常検知を有効にしてサービスのヘルスを確認", + "xpack.apm.serviceInventory.toastText": "現在 Elastic Stack 7.0+ を実行中で、以前のバージョン 6.x からの互換性のないデータを検知しました。このデータを APM で表示するには、移行が必要です。詳細: ", + "xpack.apm.serviceInventory.toastTitle": "選択された時間範囲内にレガシーデータが検知されました。", + "xpack.apm.serviceInventory.upgradeAssistantLinkText": "アップグレードアシスタント", "xpack.apm.servicesTable.7xOldDataMessage": "また、移行が必要な古いデータがある可能性もあります。", "xpack.apm.servicesTable.7xUpgradeServerMessage": "バージョン7.xより前からのアップグレードですか?また、\n APMサーバーインスタンスを7.0以降にアップグレードしていることも確認してください。", "xpack.apm.servicesTable.avgResponseTimeColumnLabel": "平均応答時間", @@ -5211,7 +5204,7 @@ "xpack.apm.settings.customizeUI.customLink.table.url": "URL", "xpack.apm.settings.indices": "インデックス", "xpack.apm.settings.pageTitle": "設定", - "xpack.apm.settings.returnToOverviewLinkLabel": "概要に戻る", + "xpack.apm.settings.returnLinkLabel": "概要に戻る", "xpack.apm.settingsLinkLabel": "設定", "xpack.apm.setupInstructionsButtonLabel": "セットアップの手順", "xpack.apm.stacktraceTab.causedByFramesToogleButtonLabel": "作成元", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index be5bcd3cf0543..3dfa1cbbca434 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -529,8 +529,6 @@ "core.ui.chrome.headerGlobalNav.helpMenuOpenGitHubIssueTitle": "在 GitHub 中提出问题", "core.ui.chrome.headerGlobalNav.helpMenuTitle": "帮助", "core.ui.chrome.headerGlobalNav.helpMenuVersion": "v {version}", - "core.ui.chrome.sideGlobalNav.viewRecentItemsFlyoutTitle": "最近项", - "core.ui.chrome.sideGlobalNav.viewRecentItemsLabel": "最近查看", "core.ui.EmptyRecentlyViewed": "没有最近查看的项目", "core.ui.enterpriseSearchNavList.label": "企业搜索", "core.ui.errorUrlOverflow.bigUrlWarningNotificationMessage": "在{advancedSettingsLink}中启用“{storeInSessionStorageParam}”选项或简化屏幕视觉效果。", @@ -559,7 +557,6 @@ "core.ui.primaryNavSection.undockAriaLabel": "取消停靠主导航", "core.ui.primaryNavSection.undockLabel": "取消停靠导航", "core.ui.recentLinks.linkItem.screenReaderLabel": "{recentlyAccessedItemLinklabel},类型:{pageType}", - "core.ui.recentLinks.screenReaderLabel": "最近查看的链接, 导航", "core.ui.recentlyViewed": "最近查看", "core.ui.recentlyViewedAriaLabel": "最近查看的链接", "core.ui.securityNavList.label": "安全", @@ -3672,7 +3669,6 @@ "visDefaultEditor.editorConfig.dateHistogram.customInterval.helpText": "必须是配置时间间隔的倍数:{interval}", "visDefaultEditor.editorConfig.histogram.interval.helpText": "必须是配置时间间隔的倍数:{interval}", "visDefaultEditor.metrics.wrongLastBucketTypeErrorMessage": "使用“{type}”指标聚合时,上一存储桶聚合必须是“Date Histogram”或“Histogram”。", - "visDefaultEditor.sidebar.autoApplyChangesAriaLabel": "自动应用编辑器更改", "visDefaultEditor.sidebar.autoApplyChangesOffLabel": "自动应用关闭", "visDefaultEditor.sidebar.autoApplyChangesOnLabel": "自动应用开启", "visDefaultEditor.sidebar.autoApplyChangesTooltip": "每次更改时自动更新可视化。", @@ -3802,7 +3798,6 @@ "visTypeTimeseries.aggLookup.percentileLabel": "百分位数", "visTypeTimeseries.aggLookup.percentileRankLabel": "百分位数排名", "visTypeTimeseries.aggLookup.positiveOnlyLabel": "仅正数", - "visTypeTimeseries.aggLookup.positiveRateLabel": "正比率", "visTypeTimeseries.aggLookup.serialDifferenceLabel": "串行差分", "visTypeTimeseries.aggLookup.seriesAggLabel": "序列聚合", "visTypeTimeseries.aggLookup.staticValueLabel": "静态值", @@ -3825,7 +3820,6 @@ "visTypeTimeseries.aggSelect.metricsAggs.minLabel": "最小值", "visTypeTimeseries.aggSelect.metricsAggs.percentileLabel": "百分位数", "visTypeTimeseries.aggSelect.metricsAggs.percentileRankLabel": "百分位数排名", - "visTypeTimeseries.aggSelect.metricsAggs.positiveRateLabel": "正比率", "visTypeTimeseries.aggSelect.metricsAggs.staticValueLabel": "静态值", "visTypeTimeseries.aggSelect.metricsAggs.stdDeviationLabel": "标准偏差", "visTypeTimeseries.aggSelect.metricsAggs.sumLabel": "和", @@ -3869,7 +3863,6 @@ "visTypeTimeseries.calculateLabel.lookupMetricTypeOfTargetLabel": "{targetLabel} 的 {lookupMetricType}", "visTypeTimeseries.calculateLabel.lookupMetricTypeOfTargetWithAdditionalLabel": "{targetLabel} ({additionalLabel}) 的 {lookupMetricType}", "visTypeTimeseries.calculateLabel.mathLabel": "数学", - "visTypeTimeseries.calculateLabel.positiveRateLabel": "{field} 的正比率", "visTypeTimeseries.calculateLabel.seriesAggLabel": "序列聚合 ({metricFunction})", "visTypeTimeseries.calculateLabel.staticValueLabel": "{metricValue} 的静态值", "visTypeTimeseries.calculateLabel.unknownLabel": "未知", @@ -5112,10 +5105,10 @@ "xpack.apm.serviceOverview.mlNudgeMessage.content": "我们集成了 ML 异常检测,让您可以查看服务的运行状况", "xpack.apm.serviceOverview.mlNudgeMessage.dismissButton": "关闭消息", "xpack.apm.serviceOverview.mlNudgeMessage.learnMoreButton": "了解详情", - "xpack.apm.serviceOverview.mlNudgeMessage.title": "启用异常检测可查看服务的运行状况", - "xpack.apm.serviceOverview.toastText": "您正在运行 Elastic Stack 7.0+,我们检测到来自以前 6.x 版本的不兼容数据。如果想在 APM 中查看,您应迁移这些数据。在以下位置查看更多: ", - "xpack.apm.serviceOverview.toastTitle": "在选定时间范围中检测到旧数据", - "xpack.apm.serviceOverview.upgradeAssistantLink": "升级助手", + "xpack.apm.serviceInventory.mlNudgeMessageTitle": "启用异常检测可查看服务的运行状况", + "xpack.apm.serviceInventory.toastText": "您正在运行 Elastic Stack 7.0+,我们检测到来自以前 6.x 版本的不兼容数据。如果想在 APM 中查看,您应迁移这些数据。在以下位置查看更多: ", + "xpack.apm.serviceInventory.toastTitle": "在选定时间范围中检测到旧数据", + "xpack.apm.serviceInventory.upgradeAssistantLinkText": "升级助手", "xpack.apm.servicesTable.7xOldDataMessage": "可能还有需要迁移的旧数据。", "xpack.apm.servicesTable.7xUpgradeServerMessage": "从 7.x 之前的版本升级?另外,确保您已将\n APM Server 实例升级到至少 7.0。", "xpack.apm.servicesTable.avgResponseTimeColumnLabel": "平均响应时间", @@ -5215,7 +5208,7 @@ "xpack.apm.settings.customizeUI.customLink.table.url": "URL", "xpack.apm.settings.indices": "索引", "xpack.apm.settings.pageTitle": "设置", - "xpack.apm.settings.returnToOverviewLinkLabel": "返回到概览", + "xpack.apm.settings.returnLinkLabel": "返回到概览", "xpack.apm.settingsLinkLabel": "璁剧疆", "xpack.apm.setupInstructionsButtonLabel": "设置说明", "xpack.apm.stacktraceTab.causedByFramesToogleButtonLabel": "原因", diff --git a/x-pack/plugins/triggers_actions_ui/kibana.json b/x-pack/plugins/triggers_actions_ui/kibana.json index a4446e0a75120..72e1f0be5f7f4 100644 --- a/x-pack/plugins/triggers_actions_ui/kibana.json +++ b/x-pack/plugins/triggers_actions_ui/kibana.json @@ -3,7 +3,7 @@ "version": "kibana", "server": true, "ui": true, - "optionalPlugins": ["home", "alerts", "stackAlerts"], + "optionalPlugins": ["alerts", "stackAlerts", "features", "home"], "requiredPlugins": ["management", "charts", "data", "kibanaReact"], "configPath": ["xpack", "trigger_actions_ui"], "extraPublicDirs": ["public/common", "public/common/constants"], diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx index c53dc0c105084..fc48a8e977c7d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx @@ -17,9 +17,9 @@ import { ScopedHistory, } from 'kibana/public'; import { Section, routeToAlertDetails } from './constants'; +import { KibanaFeature } from '../../../features/common'; import { AppContextProvider } from './app_context'; -import { ActionTypeModel, AlertTypeModel } from '../types'; -import { TypeRegistry } from './type_registry'; +import { ActionTypeRegistryContract, AlertTypeRegistryContract } from '../types'; import { ChartsPluginStart } from '../../../../../src/plugins/charts/public'; import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; import { PluginStartContract as AlertingStart } from '../../../alerts/public'; @@ -42,9 +42,10 @@ export interface AppDeps { uiSettings: IUiSettingsClient; setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; capabilities: ApplicationStart['capabilities']; - actionTypeRegistry: TypeRegistry; - alertTypeRegistry: TypeRegistry; + actionTypeRegistry: ActionTypeRegistryContract; + alertTypeRegistry: AlertTypeRegistryContract; history: ScopedHistory; + kibanaFeatures: KibanaFeature[]; } export const App = (appDeps: AppDeps) => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/index.tsx index b33d5e16c6eb9..ccc2ddd9c01ca 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/index.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/index.tsx @@ -188,7 +188,7 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent - + setPopoverOpen(false)} ownFocus - withTitle anchorPosition="downLeft" zIndex={8000} display="block" diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx index cc00c244ecd02..7c42c43dc79a2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx @@ -314,7 +314,6 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent; + actionTypeRegistry: ActionTypeRegistryContract; toastNotifications: ToastsSetup; capabilities: ApplicationStart['capabilities']; reloadConnectors?: () => Promise; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx b/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx index b4cf13538d64d..a4293f94268ba 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx @@ -18,14 +18,13 @@ import { DataPublicPluginStartUi, IndexPatternsContract, } from 'src/plugins/data/public'; -import { TypeRegistry } from '../type_registry'; -import { AlertTypeModel, ActionTypeModel } from '../../types'; +import { AlertTypeRegistryContract, ActionTypeRegistryContract } from '../../types'; export interface AlertsContextValue> { reloadAlerts?: () => Promise; http: HttpSetup; - alertTypeRegistry: TypeRegistry; - actionTypeRegistry: TypeRegistry; + alertTypeRegistry: AlertTypeRegistryContract; + actionTypeRegistry: ActionTypeRegistryContract; toastNotifications: ToastsStart; uiSettings?: IUiSettingsClient; charts?: ChartsPluginSetup; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.test.tsx new file mode 100644 index 0000000000000..d19dc3d303479 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/home.test.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import { RouteComponentProps, Router } from 'react-router-dom'; +import { createMemoryHistory, createLocation } from 'history'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; + +import TriggersActionsUIHome, { MatchParams } from './home'; +import { AppContextProvider } from './app_context'; +import { getMockedAppDependencies } from './test_utils'; + +describe('home', () => { + it('renders the documentation link', async () => { + const deps = await getMockedAppDependencies(); + + const props: RouteComponentProps = { + history: createMemoryHistory(), + location: createLocation('/'), + match: { + isExact: true, + path: `/alerts`, + url: '', + params: { + section: 'alerts', + }, + }, + }; + const wrapper = mountWithIntl( + + + + + + ); + const documentationLink = wrapper.find('[data-test-subj="documentationLink"]'); + expect(documentationLink.exists()).toBeTruthy(); + expect(documentationLink.first().prop('href')).toEqual( + 'https://www.elastic.co/guide/en/kibana/mocked-test-branch/managing-alerts-and-actions.html' + ); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx index 482b38ffc0d68..450f33d4f7e89 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx @@ -10,13 +10,14 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPageBody, EuiPageContent, - EuiPageContentHeader, - EuiPageContentHeaderSection, EuiSpacer, EuiTab, EuiTabs, EuiTitle, EuiText, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, } from '@elastic/eui'; import { Section, routeToConnectors, routeToAlerts } from './constants'; @@ -30,7 +31,7 @@ import { AlertsList } from './sections/alerts_list/components/alerts_list'; import { HealthCheck } from './components/health_check'; import { HealthContextProvider } from './context/health_context'; -interface MatchParams { +export interface MatchParams { section: Section; } @@ -80,27 +81,40 @@ export const TriggersActionsUIHome: React.FunctionComponent - - - + + +

-
- - -

+ + + -

-
-
-
+ +
+
+ + + +

+ +

+
{tabs.map((tab) => ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.test.tsx index 60ec8004983a3..cf83062b5781e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.test.tsx @@ -22,7 +22,7 @@ describe('action_connector_form', () => { ] = await mocks.getStartServices(); deps = { http: mocks.http, - actionTypeRegistry: actionTypeRegistry as any, + actionTypeRegistry, docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' }, capabilities, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx index f91bd7382b61c..3a1f9872a96a8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx @@ -24,10 +24,9 @@ import { ReducerAction } from './connector_reducer'; import { ActionConnector, IErrorObject, - ActionTypeModel, + ActionTypeRegistryContract, UserConfiguredActionConnector, } from '../../../types'; -import { TypeRegistry } from '../../type_registry'; import { hasSaveActionsCapability } from '../../lib/capabilities'; export function validateBaseProperties(actionObject: ActionConnector) { @@ -61,7 +60,7 @@ interface ActionConnectorProps< }; errors: IErrorObject; http: HttpSetup; - actionTypeRegistry: TypeRegistry; + actionTypeRegistry: ActionTypeRegistryContract; docLinks: DocLinksStart; capabilities: ApplicationStart['capabilities']; consumer?: string; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index 3e229c6a2333d..7c718e8248e41 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -178,7 +178,7 @@ describe('action_form', () => { }, }, setHasActionsWithBrokenConnector: jest.fn(), - actionTypeRegistry: actionTypeRegistry as any, + actionTypeRegistry, docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' }, }; actionTypeRegistry.list.mockReturnValue([ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 61cf3f2d37925..51d3b0074ca54 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -34,6 +34,7 @@ import { loadActionTypes, loadAllActions as loadConnectors } from '../../lib/act import { IErrorObject, ActionTypeModel, + ActionTypeRegistryContract, AlertAction, ActionTypeIndex, ActionConnector, @@ -42,7 +43,6 @@ import { } from '../../../types'; import { SectionLoading } from '../../components/section_loading'; import { ConnectorAddModal } from './connector_add_modal'; -import { TypeRegistry } from '../../type_registry'; import { actionTypeCompare } from '../../lib/action_type_compare'; import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_enabled'; import { VIEW_LICENSE_OPTIONS_LINK } from '../../../common/constants'; @@ -55,7 +55,7 @@ interface ActionAccordionFormProps { setAlertProperty: (actions: AlertAction[]) => void; setActionParamsProperty: (key: string, value: any, index: number) => void; http: HttpSetup; - actionTypeRegistry: TypeRegistry; + actionTypeRegistry: ActionTypeRegistryContract; toastNotifications: ToastsSetup; docLinks: DocLinksStart; actionTypes?: ActionType[]; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.test.tsx index a5e9cdc65cfa6..2fe068536f4a1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.test.tsx @@ -33,7 +33,7 @@ describe('connector_add_flyout', () => { show: true, }, }, - actionTypeRegistry: actionTypeRegistry as any, + actionTypeRegistry, docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' }, }; }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx index 0863465833c0b..551bcf658e87f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx @@ -34,7 +34,7 @@ describe('connector_add_flyout', () => { show: true, }, }, - actionTypeRegistry: actionTypeRegistry as any, + actionTypeRegistry, docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' }, }; }); @@ -58,8 +58,7 @@ describe('connector_add_flyout', () => { }} > {}} + onClose={() => {}} actionTypes={[ { id: actionType.id, @@ -100,8 +99,7 @@ describe('connector_add_flyout', () => { }} > {}} + onClose={() => {}} actionTypes={[ { id: actionType.id, @@ -160,8 +158,7 @@ describe('connector_add_flyout', () => { }} > {}} + onClose={() => {}} actionTypes={[ { id: actionType.id, @@ -208,8 +205,7 @@ describe('connector_add_flyout', () => { }} > {}} + onClose={() => {}} actionTypes={[ { id: actionType.id, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx index 2e222884dab50..00ff6fc132cdc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx @@ -32,15 +32,13 @@ import { useActionsConnectorsContext } from '../../context/actions_connectors_co import { VIEW_LICENSE_OPTIONS_LINK } from '../../../common/constants'; export interface ConnectorAddFlyoutProps { - addFlyoutVisible: boolean; - setAddFlyoutVisibility: React.Dispatch>; + onClose: () => void; actionTypes?: ActionType[]; onTestConnector?: (connector: ActionConnector) => void; } export const ConnectorAddFlyout = ({ - addFlyoutVisible, - setAddFlyoutVisibility, + onClose, actionTypes, onTestConnector, }: ConnectorAddFlyoutProps) => { @@ -74,17 +72,13 @@ export const ConnectorAddFlyout = ({ const [isSaving, setIsSaving] = useState(false); const closeFlyout = useCallback(() => { - setAddFlyoutVisibility(false); setActionType(undefined); setConnector(initialConnector); - }, [setAddFlyoutVisibility, initialConnector]); + onClose(); + }, [onClose, initialConnector]); const canSave = hasSaveActionsCapability(capabilities); - if (!addFlyoutVisible) { - return null; - } - function onActionTypeChange(newActionType: ActionType) { setActionType(newActionType); setActionProperty('actionTypeId', newActionType.id); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx index 3d621367fc40a..cba9eea3cf3f7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx @@ -32,7 +32,7 @@ describe('connector_add_modal', () => { delete: true, }, }, - actionTypeRegistry: actionTypeRegistry as any, + actionTypeRegistry, docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' }, }; }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx index d7ca91218d4dd..13ec8395aa557 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx @@ -19,12 +19,16 @@ import { EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { HttpSetup, ToastsApi, ApplicationStart, DocLinksStart } from 'kibana/public'; import { ActionConnectorForm, validateBaseProperties } from './action_connector_form'; -import { ActionType, ActionConnector, IErrorObject, ActionTypeModel } from '../../../types'; import { connectorReducer } from './connector_reducer'; import { createActionConnector } from '../../lib/action_connector_api'; -import { TypeRegistry } from '../../type_registry'; import './connector_add_modal.scss'; import { hasSaveActionsCapability } from '../../lib/capabilities'; +import { + ActionType, + ActionConnector, + IErrorObject, + ActionTypeRegistryContract, +} from '../../../types'; interface ConnectorAddModalProps { actionType: ActionType; @@ -32,7 +36,7 @@ interface ConnectorAddModalProps { setAddModalVisibility: React.Dispatch>; postSaveEventHandler?: (savedAction: ActionConnector) => void; http: HttpSetup; - actionTypeRegistry: TypeRegistry; + actionTypeRegistry: ActionTypeRegistryContract; toastNotifications: Pick< ToastsApi, 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx index 0c2f4df0ca52b..ab58b2430fa1a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx @@ -35,7 +35,7 @@ describe('connector_edit_flyout', () => { show: true, }, }, - actionTypeRegistry: actionTypeRegistry as any, + actionTypeRegistry, alertTypeRegistry: {} as any, docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' }, }; @@ -84,11 +84,7 @@ describe('connector_edit_flyout', () => { docLinks: deps.docLinks, }} > - {}} - /> + {}} /> ); @@ -141,11 +137,7 @@ describe('connector_edit_flyout', () => { docLinks: deps.docLinks, }} > - {}} - /> + {}} /> ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx index e89eb8c95fbab..d81f30e4f3647 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx @@ -39,8 +39,7 @@ import './connector_edit_flyout.scss'; export interface ConnectorEditProps { initialConnector: ActionConnector; - editFlyoutVisible: boolean; - setEditFlyoutVisibility: React.Dispatch>; + onClose: () => void; tab?: EditConectorTabs; } @@ -51,8 +50,7 @@ export enum EditConectorTabs { export const ConnectorEditFlyout = ({ initialConnector, - editFlyoutVisible, - setEditFlyoutVisibility, + onClose, tab = EditConectorTabs.Configuration, }: ConnectorEditProps) => { const { @@ -86,16 +84,12 @@ export const ConnectorEditFlyout = ({ const [isExecutingAction, setIsExecutinAction] = useState(false); const closeFlyout = useCallback(() => { - setEditFlyoutVisibility(false); setConnector('connector', { ...initialConnector, secrets: {} }); setHasChanges(false); setTestExecutionResult(none); + onClose(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [setEditFlyoutVisibility]); - - if (!editFlyoutVisible) { - return null; - } + }, [onClose]); const actionTypeModel = actionTypeRegistry.get(connector.actionTypeId); const errorsInConnectorConfig = (!connector.isPreconfigured diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx index c96e62df71ce4..e946e881def10 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx @@ -15,6 +15,7 @@ import { AppContextProvider } from '../../../app_context'; import { chartPluginMock } from '../../../../../../../../src/plugins/charts/public/mocks'; import { dataPluginMock } from '../../../../../../../../src/plugins/data/public/mocks'; import { alertingPluginMock } from '../../../../../../alerts/public/mocks'; +import { featuresPluginMock } from '../../../../../../features/public/mocks'; jest.mock('../../../lib/action_connector_api', () => ({ loadAllActions: jest.fn(), @@ -49,6 +50,8 @@ describe('actions_connectors_list component empty', () => { application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); + const deps = { chrome, docLinks, @@ -69,8 +72,9 @@ describe('actions_connectors_list component empty', () => { }, history: scopedHistoryMock.create(), setBreadcrumbs: jest.fn(), - actionTypeRegistry: actionTypeRegistry as any, + actionTypeRegistry, alertTypeRegistry: {} as any, + kibanaFeatures, }; actionTypeRegistry.has.mockReturnValue(true); @@ -156,6 +160,8 @@ describe('actions_connectors_list component with items', () => { application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); + const deps = { chrome, docLinks, @@ -182,6 +188,7 @@ describe('actions_connectors_list component with items', () => { }, } as any, alertTypeRegistry: {} as any, + kibanaFeatures, }; wrapper = mountWithIntl( @@ -244,6 +251,8 @@ describe('actions_connectors_list component empty with show only capability', () application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); + const deps = { chrome, docLinks, @@ -270,6 +279,7 @@ describe('actions_connectors_list component empty with show only capability', () }, } as any, alertTypeRegistry: {} as any, + kibanaFeatures, }; wrapper = mountWithIntl( @@ -333,6 +343,8 @@ describe('actions_connectors_list with show only capability', () => { application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); + const deps = { chrome, docLinks, @@ -359,6 +371,7 @@ describe('actions_connectors_list with show only capability', () => { }, } as any, alertTypeRegistry: {} as any, + kibanaFeatures, }; wrapper = mountWithIntl( @@ -434,6 +447,8 @@ describe('actions_connectors_list component with disabled items', () => { application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); + const deps = { chrome, docLinks, @@ -460,6 +475,7 @@ describe('actions_connectors_list component with disabled items', () => { }, } as any, alertTypeRegistry: {} as any, + kibanaFeatures, }; wrapper = mountWithIntl( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index da833c3495b4a..ff5585cf04dbe 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -20,6 +20,7 @@ import { EuiEmptyPrompt, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { omit } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; import { useAppDependencies } from '../../../app_context'; import { loadAllActions, loadActionTypes, deleteActions } from '../../../lib/action_connector_api'; @@ -56,7 +57,6 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { const [selectedItems, setSelectedItems] = useState([]); const [isLoadingActionTypes, setIsLoadingActionTypes] = useState(false); const [isLoadingActions, setIsLoadingActions] = useState(false); - const [editFlyoutVisible, setEditFlyoutVisibility] = useState(false); const [addFlyoutVisible, setAddFlyoutVisibility] = useState(false); const [editConnectorProps, setEditConnectorProps] = useState<{ initialConnector?: ActionConnector; @@ -134,7 +134,6 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { async function editItem(actionConnector: ActionConnector, tab: EditConectorTabs) { setEditConnectorProps({ initialConnector: actionConnector, tab }); - setEditFlyoutVisibility(true); } const actionsTableColumns = [ @@ -367,11 +366,14 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { docLinks, }} > - editItem(connector, EditConectorTabs.Test)} - /> + {addFlyoutVisible ? ( + { + setAddFlyoutVisibility(false); + }} + onTestConnector={(connector) => editItem(connector, EditConectorTabs.Test)} + /> + ) : null} {editConnectorProps.initialConnector ? ( { }`} initialConnector={editConnectorProps.initialConnector} tab={editConnectorProps.tab} - editFlyoutVisible={editFlyoutVisible} - setEditFlyoutVisibility={setEditFlyoutVisibility} + onClose={() => { + setEditConnectorProps(omit(editConnectorProps, 'initialConnector')); + }} /> ) : null} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx index 8ac80c4ad2880..0ac20626e1044 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx @@ -84,8 +84,8 @@ describe('alert_add', () => { uiSettings: mocks.uiSettings, dataPlugin: dataPluginMock.createStartContract(), charts: chartPluginMock.createStartContract(), - actionTypeRegistry: actionTypeRegistry as any, - alertTypeRegistry: alertTypeRegistry as any, + actionTypeRegistry, + alertTypeRegistry, docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' }, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx index 24eb7aabb9549..fe86e5da98765 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx @@ -36,8 +36,8 @@ describe('alert_edit', () => { toastNotifications: mockedCoreSetup.notifications.toasts, http: mockedCoreSetup.http, uiSettings: mockedCoreSetup.uiSettings, - actionTypeRegistry: actionTypeRegistry as any, - alertTypeRegistry: alertTypeRegistry as any, + actionTypeRegistry, + alertTypeRegistry, docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' }, capabilities, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx index 6091519f5851e..cda791489d7f7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx @@ -98,8 +98,8 @@ describe('alert_form', () => { toastNotifications: mocks.notifications.toasts, http: mocks.http, uiSettings: mocks.uiSettings, - actionTypeRegistry: actionTypeRegistry as any, - alertTypeRegistry: alertTypeRegistry as any, + actionTypeRegistry, + alertTypeRegistry, docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' }, capabilities, }; @@ -231,8 +231,8 @@ describe('alert_form', () => { toastNotifications: mocks.notifications.toasts, http: mocks.http, uiSettings: mocks.uiSettings, - actionTypeRegistry: actionTypeRegistry as any, - alertTypeRegistry: alertTypeRegistry as any, + actionTypeRegistry, + alertTypeRegistry, docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' }, capabilities, }; @@ -332,8 +332,8 @@ describe('alert_form', () => { toastNotifications: mockes.notifications.toasts, http: mockes.http, uiSettings: mockes.uiSettings, - actionTypeRegistry: actionTypeRegistry as any, - alertTypeRegistry: alertTypeRegistry as any, + actionTypeRegistry, + alertTypeRegistry, }; alertTypeRegistry.list.mockReturnValue([alertType]); alertTypeRegistry.get.mockReturnValue(alertType); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index d2ca0abe566ad..bdc11fd543ee1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -401,7 +401,7 @@ export const AlertForm = ({ 0} error={errors.interval} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx index 86b9afd9565f8..21dd17b538c63 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx @@ -18,6 +18,7 @@ import { chartPluginMock } from '../../../../../../../../src/plugins/charts/publ import { dataPluginMock } from '../../../../../../../../src/plugins/data/public/mocks'; import { alertingPluginMock } from '../../../../../../alerts/public/mocks'; import { ALERTS_FEATURE_ID } from '../../../../../../alerts/common'; +import { featuresPluginMock } from '../../../../../../features/public/mocks'; jest.mock('../../../lib/action_connector_api', () => ({ loadActionTypes: jest.fn(), @@ -96,6 +97,9 @@ describe('alerts_list component empty', () => { application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); + const deps = { chrome, docLinks, @@ -109,8 +113,9 @@ describe('alerts_list component empty', () => { capabilities, history: scopedHistoryMock.create(), setBreadcrumbs: jest.fn(), - actionTypeRegistry: actionTypeRegistry as any, - alertTypeRegistry: alertTypeRegistry as any, + actionTypeRegistry, + alertTypeRegistry, + kibanaFeatures, }; wrapper = mountWithIntl( @@ -265,6 +270,7 @@ describe('alerts_list component with items', () => { application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); const deps = { chrome, docLinks, @@ -278,8 +284,9 @@ describe('alerts_list component with items', () => { capabilities, history: scopedHistoryMock.create(), setBreadcrumbs: jest.fn(), - actionTypeRegistry: actionTypeRegistry as any, - alertTypeRegistry: alertTypeRegistry as any, + actionTypeRegistry, + alertTypeRegistry, + kibanaFeatures, }; alertTypeRegistry.has.mockReturnValue(true); @@ -346,6 +353,7 @@ describe('alerts_list component empty with show only capability', () => { application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); const deps = { chrome, docLinks, @@ -365,6 +373,7 @@ describe('alerts_list component empty with show only capability', () => { }, } as any, alertTypeRegistry: {} as any, + kibanaFeatures, }; wrapper = mountWithIntl( @@ -465,6 +474,7 @@ describe('alerts_list with show only capability', () => { application: { capabilities, navigateToApp }, }, ] = await mockes.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); const deps = { chrome, docLinks, @@ -478,8 +488,9 @@ describe('alerts_list with show only capability', () => { capabilities, history: scopedHistoryMock.create(), setBreadcrumbs: jest.fn(), - actionTypeRegistry: actionTypeRegistry as any, - alertTypeRegistry: alertTypeRegistry as any, + actionTypeRegistry, + alertTypeRegistry, + kibanaFeatures, }; alertTypeRegistry.has.mockReturnValue(false); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index 9eb1149cf3905..3f39c698597ce 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -7,6 +7,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { i18n } from '@kbn/i18n'; +import { capitalize, sortBy } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useEffect, useState, Fragment } from 'react'; import { @@ -78,6 +79,7 @@ export const AlertsList: React.FunctionComponent = () => { docLinks, charts, dataPlugin, + kibanaFeatures, } = useAppDependencies(); const canExecuteActions = hasExecuteActionsCapability(capabilities); @@ -334,16 +336,43 @@ export const AlertsList: React.FunctionComponent = () => { (alertType) => alertType.authorizedConsumers[ALERTS_FEATURE_ID]?.all ); + const getProducerFeatureName = (producer: string) => { + return kibanaFeatures?.find((featureItem) => featureItem.id === producer)?.name; + }; + + const groupAlertTypesByProducer = () => { + return authorizedAlertTypes.reduce( + ( + result: Record< + string, + Array<{ + value: string; + name: string; + }> + >, + alertType + ) => { + const producer = alertType.producer; + (result[producer] = result[producer] || []).push({ + value: alertType.id, + name: alertType.name, + }); + return result; + }, + {} + ); + }; + const toolsRight = [ setTypesFilter(types)} - options={authorizedAlertTypes - .map((alertType) => ({ - value: alertType.id, - name: alertType.name, - })) - .sort((a, b) => a.name.localeCompare(b.name))} + options={sortBy(Object.entries(groupAlertTypesByProducer())).map( + ([groupName, alertTypesOptions]) => ({ + groupName: getProducerFeatureName(groupName) ?? capitalize(groupName), + subOptions: alertTypesOptions.sort((a, b) => a.name.localeCompare(b.name)), + }) + )} />, ; }>; onChange?: (selectedTags: string[]) => void; } @@ -52,22 +61,29 @@ export const TypeFilter: React.FunctionComponent = ({ } >
- {options.map((item, index) => ( - { - const isPreviouslyChecked = selectedValues.includes(item.value); - if (isPreviouslyChecked) { - setSelectedValues(selectedValues.filter((val) => val !== item.value)); - } else { - setSelectedValues(selectedValues.concat(item.value)); - } - }} - checked={selectedValues.includes(item.value) ? 'on' : undefined} - data-test-subj={`alertType${item.value}FilterOption`} - > - {item.name} - + {options.map((groupItem, groupIndex) => ( + + +

{groupItem.groupName}

+
+ {groupItem.subOptions.map((item, index) => ( + { + const isPreviouslyChecked = selectedValues.includes(item.value); + if (isPreviouslyChecked) { + setSelectedValues(selectedValues.filter((val) => val !== item.value)); + } else { + setSelectedValues(selectedValues.concat(item.value)); + } + }} + checked={selectedValues.includes(item.value) ? 'on' : undefined} + data-test-subj={`alertType${item.value}FilterOption`} + > + {item.name} + + ))} +
))}
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/test_utils/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/test_utils/index.ts new file mode 100644 index 0000000000000..b5ab53d868cf1 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/test_utils/index.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { featuresPluginMock } from '../../../../features/public/mocks'; +import { chartPluginMock } from '../../../../../../src/plugins/charts/public/mocks'; +import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; +import { alertingPluginMock } from '../../../../alerts/public/mocks'; +import { actionTypeRegistryMock } from '../action_type_registry.mock'; +import { alertTypeRegistryMock } from '../alert_type_registry.mock'; +import { coreMock, scopedHistoryMock } from '../../../../../../src/core/public/mocks'; + +export async function getMockedAppDependencies() { + const coreSetupMock = coreMock.createSetup(); + const actionTypeRegistry = actionTypeRegistryMock.create(); + const alertTypeRegistry = alertTypeRegistryMock.create(); + const [ + { + chrome, + docLinks, + application: { capabilities, navigateToApp }, + }, + ] = await coreSetupMock.getStartServices(); + const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); + + return { + chrome, + docLinks, + dataPlugin: dataPluginMock.createStartContract(), + charts: chartPluginMock.createStartContract(), + alerting: alertingPluginMock.createStartContract(), + toastNotifications: coreSetupMock.notifications.toasts, + http: coreSetupMock.http, + uiSettings: coreSetupMock.uiSettings, + navigateToApp, + capabilities, + history: scopedHistoryMock.create(), + setBreadcrumbs: jest.fn(), + actionTypeRegistry, + alertTypeRegistry, + kibanaFeatures, + }; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/for_the_last.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/for_the_last.tsx index 08339b509d5fd..388f87cbf752b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/for_the_last.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/for_the_last.tsx @@ -83,7 +83,6 @@ export const ForLastExpression = ({ }} ownFocus display={display === 'fullWidth' ? 'block' : 'inlineBlock'} - withTitle anchorPosition={popupPosition ?? 'downLeft'} >
diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx index 6af103be96e13..785df0981ebe6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/group_by_over.tsx @@ -113,7 +113,6 @@ export const GroupByExpression = ({ setGroupByPopoverOpen(false); }} ownFocus - withTitle display={display === 'fullWidth' ? 'block' : 'inlineBlock'} anchorPosition={popupPosition ?? 'downRight'} > diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.tsx index 9cea1d3812274..e15b9a21570c9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.tsx @@ -101,7 +101,6 @@ export const OfExpression = ({ closePopover={() => { setAggFieldPopoverOpen(false); }} - withTitle display={display === 'fullWidth' ? 'block' : 'inlineBlock'} anchorPosition={popupPosition ?? 'downRight'} zIndex={8000} diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx index 2b5cec98b16a1..bdf30414b68bb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx @@ -97,7 +97,6 @@ export const ThresholdExpression = ({ setAlertThresholdPopoverOpen(false); }} ownFocus - withTitle display={display === 'fullWidth' ? 'block' : 'inlineBlock'} anchorPosition={popupPosition ?? 'downLeft'} > diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/when.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/when.tsx index 18197b6f64e43..5696417f241fd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/when.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/when.tsx @@ -66,7 +66,6 @@ export const WhenExpression = ({ }} ownFocus display={display === 'fullWidth' ? 'block' : 'inlineBlock'} - withTitle anchorPosition={popupPosition ?? 'downLeft'} >
diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 393ac5bc1b74d..b22be6ef9b2f6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -12,6 +12,7 @@ import { } from 'src/core/public'; import { i18n } from '@kbn/i18n'; +import { FeaturesPluginStart } from '../../features/public'; import { registerBuiltInActionTypes } from './application/components/builtin_action_types'; import { registerBuiltInAlertTypes } from './application/components/builtin_alert_types'; import { ActionTypeModel, AlertTypeModel } from './types'; @@ -52,6 +53,7 @@ interface PluginsStart { charts: ChartsPluginStart; alerts?: AlertingStart; navigateToApp: CoreStart['application']['navigateToApp']; + features: FeaturesPluginStart; } export class Plugin @@ -112,6 +114,7 @@ export class Plugin ]; const { boot } = await import('./application/boot'); + const kibanaFeatures = await pluginsStart.features.getFeatures(); return boot({ dataPlugin: pluginsStart.data, @@ -131,6 +134,7 @@ export class Plugin history: params.history, actionTypeRegistry, alertTypeRegistry, + kibanaFeatures, }); }, }); diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx index bd0191443d785..3251e85841d86 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx @@ -189,7 +189,6 @@ function AddVariableButton({ closePopover={closePopover} panelPaddingSize="none" anchorPosition="downLeft" - withTitle > = [ + { + id: 'dynamicActions', + getDisplayName: () => + i18n.translate('xpack.uiActionsEnhanced.CustomActions', { + defaultMessage: 'Custom actions', + }), + getIconType: () => 'symlink', + order: 26, + }, +]; diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts index cdd357f3560b8..cbc381c911c3d 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.test.ts @@ -13,6 +13,7 @@ import { UiActionsServiceEnhancements } from '../services'; import { ActionFactoryDefinition } from './action_factory_definition'; import { SerializedAction, SerializedEvent } from './types'; import { licensingMock } from '../../../licensing/public/mocks'; +import { dynamicActionGrouping } from './dynamic_action_grouping'; const actionFactoryDefinition1: ActionFactoryDefinition = { id: 'ACTION_FACTORY_1', @@ -294,6 +295,27 @@ describe('DynamicActionManager', () => { expect(manager.state.get().events.length).toBe(1); }); + test('adds revived actiosn to "dynamic action" grouping', async () => { + const { manager, uiActions, actions } = setup([]); + const action: SerializedAction = { + factoryId: actionFactoryDefinition1.id, + name: 'foo', + config: {}, + }; + + uiActions.registerActionFactory(actionFactoryDefinition1); + + await manager.start(); + + expect(manager.state.get().events.length).toBe(0); + + await manager.createEvent(action, ['VALUE_CLICK_TRIGGER']); + + const createdAction = actions.values().next().value; + + expect(createdAction.grouping).toBe(dynamicActionGrouping); + }); + test('optimistically adds event to UI state', async () => { const { manager, uiActions } = setup([]); const action: SerializedAction = { diff --git a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.ts b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.ts index b414296690c9e..f096b17f8a78d 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/dynamic_actions/dynamic_action_manager.ts @@ -18,6 +18,7 @@ import { } from '../../../../../src/plugins/kibana_utils/common'; import { StartContract } from '../plugin'; import { SerializedAction, SerializedEvent } from './types'; +import { dynamicActionGrouping } from './dynamic_action_grouping'; const compareEvents = ( a: ReadonlyArray<{ eventId: string }>, @@ -93,6 +94,7 @@ export class DynamicActionManager { uiActions.registerAction({ ...actionDefinition, id: actionId, + grouping: dynamicActionGrouping, isCompatible: async (context) => { if (!(await isCompatible(context))) return false; if (!actionDefinition.isCompatible) return true; diff --git a/x-pack/plugins/uptime/public/components/common/__tests__/__snapshots__/uptime_date_picker.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/__tests__/__snapshots__/uptime_date_picker.test.tsx.snap index c540334222418..a3a6d44a0320b 100644 --- a/x-pack/plugins/uptime/public/components/common/__tests__/__snapshots__/uptime_date_picker.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/common/__tests__/__snapshots__/uptime_date_picker.test.tsx.snap @@ -69,7 +69,7 @@ exports[`UptimeDatePicker component renders properly with mock data 1`] = ` class="euiToolTipAnchor" >
,
In order to access duration anomaly detection, you have to be subscribed to an Elastic Platinum license.

- - - Start free 14-day trial - - + Start free 14-day trial + diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap index ac1afccd1feae..fd59b14520ce1 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap @@ -146,24 +146,21 @@ exports[`ML Flyout component shows license info if no ml available 1`] = `

In order to access duration anomaly detection, you have to be subscribed to an Elastic Platinum license.

- -
+ - - Start free 14-day trial - + Start free 14-day trial - - + +
{ const { basePath } = useContext(UptimeSettingsContext); - const [loading, setLoading] = useState(false); - const hasMlFeature = useSelector(hasMLFeatureSelector); const dispatch = useDispatch(); @@ -22,29 +19,6 @@ export const ShowLicenseInfo = () => { dispatch(getMLCapabilitiesAction.get()); }, [dispatch]); - useEffect(() => { - let retryInterval: any; - if (loading) { - retryInterval = setInterval(() => { - dispatch(getMLCapabilitiesAction.get()); - }, 5000); - } else { - clearInterval(retryInterval); - } - - return () => { - clearInterval(retryInterval); - }; - }, [dispatch, loading]); - - useEffect(() => { - setLoading(false); - }, [hasMlFeature]); - - const startLicenseTrial = () => { - setLoading(true); - }; - return ( <> { iconType="help" >

{labels.START_TRAIL_DESC}

- {}}> - - {labels.START_TRAIL} - - + + {labels.START_TRAIL} +
diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/toggle_view_btn.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/toggle_view_btn.tsx index dfce583945cce..2abe6f3f90c3c 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/toggle_view_btn.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/location_availability/toggle_view_btn.tsx @@ -7,6 +7,7 @@ import * as React from 'react'; import styled from 'styled-components'; import { EuiButtonGroup } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { useSelectedView } from './use_selected_view'; import { ChangeToListView, ChangeToMapView } from '../translations'; @@ -55,6 +56,9 @@ export const ToggleViewBtn = ({ onChange }: Props) => { type="multi" isIconOnly style={{ marginLeft: 'auto' }} + legend={i18n.translate('xpack.uptime.locationAvailabilityViewToggleLegend', { + defaultMessage: 'View toggle', + })} /> ); diff --git a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap index 024bd61eba33e..ab38ee9adc6c2 100644 --- a/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/overview/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap @@ -374,6 +374,7 @@ exports[`EmptyState component does not render empty state with appropriate base href="/app/home#/tutorial/uptimeMonitors" > @@ -58,7 +57,6 @@ exports[`FilterPopover component renders without errors for valid props 1`] = ` isOpen={false} ownFocus={true} panelPaddingSize="m" - withTitle={true} zIndex={10000} > @@ -86,7 +84,7 @@ exports[`FilterPopover component returns selected items on popover close 1`] = ` Some text
diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx index 064907a633df0..902f497babda8 100644 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx +++ b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx @@ -93,7 +93,6 @@ export const FilterPopover = ({ id={id} isOpen={isOpen || forceOpen} ownFocus={true} - withTitle zIndex={10000} > diff --git a/x-pack/plugins/uptime/public/components/settings/add_connector_flyout.tsx b/x-pack/plugins/uptime/public/components/settings/add_connector_flyout.tsx index a87748e62b513..9baacaf21acd0 100644 --- a/x-pack/plugins/uptime/public/components/settings/add_connector_flyout.tsx +++ b/x-pack/plugins/uptime/public/components/settings/add_connector_flyout.tsx @@ -69,10 +69,9 @@ export const AddConnectorFlyout = ({ focusInput }: Props) => { capabilities: application?.capabilities, }} > - + {addFlyoutVisible ? ( + setAddFlyoutVisibility(false)} /> + ) : null} ); diff --git a/x-pack/plugins/uptime/public/pages/__tests__/__snapshots__/page_header.test.tsx.snap b/x-pack/plugins/uptime/public/pages/__tests__/__snapshots__/page_header.test.tsx.snap index 15389460517de..7bb578494ab44 100644 --- a/x-pack/plugins/uptime/public/pages/__tests__/__snapshots__/page_header.test.tsx.snap +++ b/x-pack/plugins/uptime/public/pages/__tests__/__snapshots__/page_header.test.tsx.snap @@ -276,7 +276,7 @@ Array [ class="euiToolTipAnchor" >