diff --git a/docs/developer/contributing/development-functional-tests.asciidoc b/docs/developer/contributing/development-functional-tests.asciidoc index 110704a8e569a9..f0041b85c14ebf 100644 --- a/docs/developer/contributing/development-functional-tests.asciidoc +++ b/docs/developer/contributing/development-functional-tests.asciidoc @@ -139,11 +139,14 @@ export default function (/* { providerAPI } */) { } ----------- -**Services**::: -Services are named singleton values produced by a Service Provider. Tests and other services can retrieve service instances by asking for them by name. All functionality except the mocha API is exposed via services. +**Service**::: +A Service is a named singleton created using a subclass of `FtrService`. Tests and other services can retrieve service instances by asking for them by name. All functionality except the mocha API is exposed via services. When you write your own functional tests check for existing services that help with the interactions you're looking to execute, and add new services for interactions which aren't already encoded in a service. + +**Service Providers**::: +For legacy purposes, and for when creating a subclass of `FtrService` is inconvenient, you can also create services using a "Service Provider". These are functions which which create service instances and return them. These instances are cached and provided to tests. Currently these providers may also return a Promise for the service instance, allowing the service to do some setup work before tests run. We expect to fully deprecate and remove support for async service providers in the near future and instead require that services use the `lifecycle` service to run setup before tests. Providers which return instances of classes other than `FtrService` will likely remain supported for as long as possible. **Page objects**::: -Page objects are a special type of service that encapsulate behaviors common to a particular page or plugin. When you write your own plugin, you’ll likely want to add a page object (or several) that describes the common interactions your tests need to execute. +Page objects are functionally equivalent to services, except they are loaded with a slightly different mechanism and generally defined separate from services. When you write your own functional tests you might want to write some of your services as Page objects, but it is not required. **Test Files**::: The `FunctionalTestRunner`'s primary purpose is to execute test files. These files export a Test Provider that is called with a Provider API but is not expected to return a value. Instead Test Providers define a suite using https://mochajs.org/#bdd[mocha's BDD interface]. diff --git a/docs/development/core/public/kibana-plugin-core-public.app.deeplinks.md b/docs/development/core/public/kibana-plugin-core-public.app.deeplinks.md new file mode 100644 index 00000000000000..0392cb7eaefb02 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.app.deeplinks.md @@ -0,0 +1,39 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [deepLinks](./kibana-plugin-core-public.app.deeplinks.md) + +## App.deepLinks property + +Input type for registering secondary in-app locations for an application. + +Deep links must include at least one of `path` or `deepLinks`. A deep link that does not have a `path` represents a topological level in the application's hierarchy, but does not have a destination URL that is user-accessible. + +Signature: + +```typescript +deepLinks?: AppDeepLink[]; +``` + +## Example + + +```ts +core.application.register({ + id: 'my_app', + title: 'Translated title', + keywords: ['translated keyword1', 'translated keyword2'], + deepLinks: [ + { id: 'sub1', title: 'Sub1', path: '/sub1', keywords: ['subpath1'] }, + { + id: 'sub2', + title: 'Sub2', + deepLinks: [ + { id: 'subsub', title: 'SubSub', path: '/sub2/sub', keywords: ['subpath2'] } + ] + } + ], + mount: () => { ... } +}) + +``` + diff --git a/docs/development/core/public/kibana-plugin-core-public.app.keywords.md b/docs/development/core/public/kibana-plugin-core-public.app.keywords.md new file mode 100644 index 00000000000000..585df1b48c16e7 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.app.keywords.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [keywords](./kibana-plugin-core-public.app.keywords.md) + +## App.keywords property + +Optional keywords to match with in deep links search. Omit if this part of the hierarchy does not have a page URL. + +Signature: + +```typescript +keywords?: string[]; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.app.md b/docs/development/core/public/kibana-plugin-core-public.app.md index 9a508f293d8e8c..721d9a2f121c73 100644 --- a/docs/development/core/public/kibana-plugin-core-public.app.md +++ b/docs/development/core/public/kibana-plugin-core-public.app.md @@ -19,12 +19,13 @@ export interface App | [capabilities](./kibana-plugin-core-public.app.capabilities.md) | Partial<Capabilities> | Custom capabilities defined by the app. | | [category](./kibana-plugin-core-public.app.category.md) | AppCategory | The category definition of the product See [AppCategory](./kibana-plugin-core-public.appcategory.md) See DEFAULT\_APP\_CATEGORIES for more reference | | [chromeless](./kibana-plugin-core-public.app.chromeless.md) | boolean | Hide the UI chrome when the application is mounted. Defaults to false. Takes precedence over chrome service visibility settings. | +| [deepLinks](./kibana-plugin-core-public.app.deeplinks.md) | AppDeepLink[] | Input type for registering secondary in-app locations for an application.Deep links must include at least one of path or deepLinks. A deep link that does not have a path represents a topological level in the application's hierarchy, but does not have a destination URL that is user-accessible. | | [defaultPath](./kibana-plugin-core-public.app.defaultpath.md) | string | Allow to define the default path a user should be directed to when navigating to the app. When defined, this value will be used as a default for the path option when calling [navigateToApp](./kibana-plugin-core-public.applicationstart.navigatetoapp.md)\`, and will also be appended to the [application navLink](./kibana-plugin-core-public.chromenavlink.md) in the navigation bar. | | [euiIconType](./kibana-plugin-core-public.app.euiicontype.md) | string | A EUI iconType that will be used for the app's icon. This icon takes precendence over the icon property. | | [exactRoute](./kibana-plugin-core-public.app.exactroute.md) | boolean | If set to true, the application's route will only be checked against an exact match. Defaults to false. | | [icon](./kibana-plugin-core-public.app.icon.md) | string | A URL to an image file used as an icon. Used as a fallback if euiIconType is not provided. | | [id](./kibana-plugin-core-public.app.id.md) | string | The unique identifier of the application | -| [meta](./kibana-plugin-core-public.app.meta.md) | AppMeta | Meta data for an application that represent additional information for the app. See [AppMeta](./kibana-plugin-core-public.appmeta.md) | +| [keywords](./kibana-plugin-core-public.app.keywords.md) | string[] | Optional keywords to match with in deep links search. Omit if this part of the hierarchy does not have a page URL. | | [mount](./kibana-plugin-core-public.app.mount.md) | AppMount<HistoryLocationState> | A mount function called when the user navigates to this app's route. | | [navLinkStatus](./kibana-plugin-core-public.app.navlinkstatus.md) | AppNavLinkStatus | The initial status of the application's navLink. Defaulting to visible if status is accessible and hidden if status is inaccessible See [AppNavLinkStatus](./kibana-plugin-core-public.appnavlinkstatus.md) | | [order](./kibana-plugin-core-public.app.order.md) | number | An ordinal used to sort nav links relative to one another for display. | diff --git a/docs/development/core/public/kibana-plugin-core-public.app.meta.md b/docs/development/core/public/kibana-plugin-core-public.app.meta.md deleted file mode 100644 index 574fa11605aec9..00000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.app.meta.md +++ /dev/null @@ -1,43 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [meta](./kibana-plugin-core-public.app.meta.md) - -## App.meta property - -Meta data for an application that represent additional information for the app. See [AppMeta](./kibana-plugin-core-public.appmeta.md) - -Signature: - -```typescript -meta?: AppMeta; -``` - -## Remarks - -Used to populate navigational search results (where available). Can be updated using the [App.updater$](./kibana-plugin-core-public.app.updater_.md) observable. See [PublicAppSearchDeepLinkInfo](./kibana-plugin-core-public.publicappsearchdeeplinkinfo.md) for more details. - -## Example - - -```ts -core.application.register({ - id: 'my_app', - title: 'Translated title', - meta: { - keywords: ['translated keyword1', 'translated keyword2'], - searchDeepLinks: [ - { id: 'sub1', title: 'Sub1', path: '/sub1', keywords: ['subpath1'] }, - { - id: 'sub2', - title: 'Sub2', - searchDeepLinks: [ - { id: 'subsub', title: 'SubSub', path: '/sub2/sub', keywords: ['subpath2'] } - ] - } - ], - }, - mount: () => { ... } -}) - -``` - diff --git a/docs/development/core/public/kibana-plugin-core-public.appdeeplink.md b/docs/development/core/public/kibana-plugin-core-public.appdeeplink.md new file mode 100644 index 00000000000000..5aa951cffdcb54 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.appdeeplink.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppDeepLink](./kibana-plugin-core-public.appdeeplink.md) + +## AppDeepLink type + +Input type for registering secondary in-app locations for an application. + +Deep links must include at least one of `path` or `deepLinks`. A deep link that does not have a `path` represents a topological level in the application's hierarchy, but does not have a destination URL that is user-accessible. + +Signature: + +```typescript +export declare type AppDeepLink = { + id: string; + title: string; + keywords?: string[]; + navLinkStatus?: AppNavLinkStatus; +} & ({ + path: string; + deepLinks?: AppDeepLink[]; +} | { + path?: string; + deepLinks: AppDeepLink[]; +}); +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appmeta.keywords.md b/docs/development/core/public/kibana-plugin-core-public.appmeta.keywords.md deleted file mode 100644 index 13709df68e76a1..00000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appmeta.keywords.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppMeta](./kibana-plugin-core-public.appmeta.md) > [keywords](./kibana-plugin-core-public.appmeta.keywords.md) - -## AppMeta.keywords property - -Keywords to represent this application - -Signature: - -```typescript -keywords?: string[]; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appmeta.md b/docs/development/core/public/kibana-plugin-core-public.appmeta.md deleted file mode 100644 index a2b72f7ec799d2..00000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appmeta.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppMeta](./kibana-plugin-core-public.appmeta.md) - -## AppMeta interface - -Input type for meta data for an application. - -Meta fields include `keywords` and `searchDeepLinks` Keywords is an array of string with which to associate the app, must include at least one unique string as an array. `searchDeepLinks` is an array of links that represent secondary in-app locations for the app. - -Signature: - -```typescript -export interface AppMeta -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [keywords](./kibana-plugin-core-public.appmeta.keywords.md) | string[] | Keywords to represent this application | -| [searchDeepLinks](./kibana-plugin-core-public.appmeta.searchdeeplinks.md) | AppSearchDeepLink[] | Array of links that represent secondary in-app locations for the app. | - diff --git a/docs/development/core/public/kibana-plugin-core-public.appmeta.searchdeeplinks.md b/docs/development/core/public/kibana-plugin-core-public.appmeta.searchdeeplinks.md deleted file mode 100644 index 7ec0bbaa4b418b..00000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appmeta.searchdeeplinks.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppMeta](./kibana-plugin-core-public.appmeta.md) > [searchDeepLinks](./kibana-plugin-core-public.appmeta.searchdeeplinks.md) - -## AppMeta.searchDeepLinks property - -Array of links that represent secondary in-app locations for the app. - -Signature: - -```typescript -searchDeepLinks?: AppSearchDeepLink[]; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appsearchdeeplink.md b/docs/development/core/public/kibana-plugin-core-public.appsearchdeeplink.md deleted file mode 100644 index 29aad675fb105d..00000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.appsearchdeeplink.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppSearchDeepLink](./kibana-plugin-core-public.appsearchdeeplink.md) - -## AppSearchDeepLink type - -Input type for registering secondary in-app locations for an application. - -Deep links must include at least one of `path` or `searchDeepLinks`. A deep link that does not have a `path` represents a topological level in the application's hierarchy, but does not have a destination URL that is user-accessible. - -Signature: - -```typescript -export declare type AppSearchDeepLink = { - id: string; - title: string; -} & ({ - path: string; - searchDeepLinks?: AppSearchDeepLink[]; - keywords?: string[]; -} | { - path?: string; - searchDeepLinks: AppSearchDeepLink[]; - keywords?: string[]; -}); -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md b/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md index 55672d9339f615..d7b12d4b707010 100644 --- a/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md +++ b/docs/development/core/public/kibana-plugin-core-public.appupdatablefields.md @@ -9,5 +9,5 @@ Defines the list of fields that can be updated via an [AppUpdater](./kibana-plug Signature: ```typescript -export declare type AppUpdatableFields = Pick; +export declare type AppUpdatableFields = Pick; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index 3972f737f66183..6239279f275d1c 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -37,7 +37,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [AppLeaveDefaultAction](./kibana-plugin-core-public.appleavedefaultaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-core-public.appleavehandler.md) to execute the default behaviour when leaving the application.See | | [ApplicationSetup](./kibana-plugin-core-public.applicationsetup.md) | | | [ApplicationStart](./kibana-plugin-core-public.applicationstart.md) | | -| [AppMeta](./kibana-plugin-core-public.appmeta.md) | Input type for meta data for an application.Meta fields include keywords and searchDeepLinks Keywords is an array of string with which to associate the app, must include at least one unique string as an array. searchDeepLinks is an array of links that represent secondary in-app locations for the app. | | [AppMountParameters](./kibana-plugin-core-public.appmountparameters.md) | | | [AsyncPlugin](./kibana-plugin-core-public.asyncplugin.md) | A plugin with asynchronous lifecycle methods. | | [Capabilities](./kibana-plugin-core-public.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. | @@ -144,10 +143,10 @@ The plugin integrates with the core system via lifecycle events: `setup` | Type Alias | Description | | --- | --- | +| [AppDeepLink](./kibana-plugin-core-public.appdeeplink.md) | Input type for registering secondary in-app locations for an application.Deep links must include at least one of path or deepLinks. A deep link that does not have a path represents a topological level in the application's hierarchy, but does not have a destination URL that is user-accessible. | | [AppLeaveAction](./kibana-plugin-core-public.appleaveaction.md) | Possible actions to return from a [AppLeaveHandler](./kibana-plugin-core-public.appleavehandler.md)See [AppLeaveConfirmAction](./kibana-plugin-core-public.appleaveconfirmaction.md) and [AppLeaveDefaultAction](./kibana-plugin-core-public.appleavedefaultaction.md) | | [AppLeaveHandler](./kibana-plugin-core-public.appleavehandler.md) | A handler that will be executed before leaving the application, either when going to another application or when closing the browser tab or manually changing the url. Should return confirm to prompt a message to the user before leaving the page, or default to keep the default behavior (doing nothing).See [AppMountParameters](./kibana-plugin-core-public.appmountparameters.md) for detailed usage examples. | | [AppMount](./kibana-plugin-core-public.appmount.md) | A mount function called when the user navigates to this app's route. | -| [AppSearchDeepLink](./kibana-plugin-core-public.appsearchdeeplink.md) | Input type for registering secondary in-app locations for an application.Deep links must include at least one of path or searchDeepLinks. A deep link that does not have a path represents a topological level in the application's hierarchy, but does not have a destination URL that is user-accessible. | | [AppUnmount](./kibana-plugin-core-public.appunmount.md) | A function called when an application should be unmounted from the page. This function should be synchronous. | | [AppUpdatableFields](./kibana-plugin-core-public.appupdatablefields.md) | Defines the list of fields that can be updated via an [AppUpdater](./kibana-plugin-core-public.appupdater.md). | | [AppUpdater](./kibana-plugin-core-public.appupdater.md) | Updater for applications. see [ApplicationSetup](./kibana-plugin-core-public.applicationsetup.md) | @@ -161,9 +160,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [NavType](./kibana-plugin-core-public.navtype.md) | | | [PluginInitializer](./kibana-plugin-core-public.plugininitializer.md) | The plugin export at the root of a plugin's public directory should conform to this interface. | | [PluginOpaqueId](./kibana-plugin-core-public.pluginopaqueid.md) | | +| [PublicAppDeepLinkInfo](./kibana-plugin-core-public.publicappdeeplinkinfo.md) | Public information about a registered app's [deepLinks](./kibana-plugin-core-public.appdeeplink.md) | | [PublicAppInfo](./kibana-plugin-core-public.publicappinfo.md) | Public information about a registered [application](./kibana-plugin-core-public.app.md) | -| [PublicAppMetaInfo](./kibana-plugin-core-public.publicappmetainfo.md) | Public information about a registered app's [keywords](./kibana-plugin-core-public.appmeta.md) | -| [PublicAppSearchDeepLinkInfo](./kibana-plugin-core-public.publicappsearchdeeplinkinfo.md) | Public information about a registered app's [searchDeepLinks](./kibana-plugin-core-public.appsearchdeeplink.md) | | [PublicUiSettingsParams](./kibana-plugin-core-public.publicuisettingsparams.md) | A sub-set of [UiSettingsParams](./kibana-plugin-core-public.uisettingsparams.md) exposed to the client-side. | | [ResolveDeprecationResponse](./kibana-plugin-core-public.resolvedeprecationresponse.md) | | | [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value | diff --git a/docs/development/core/public/kibana-plugin-core-public.publicappdeeplinkinfo.md b/docs/development/core/public/kibana-plugin-core-public.publicappdeeplinkinfo.md new file mode 100644 index 00000000000000..d3a6a4de905fdf --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.publicappdeeplinkinfo.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PublicAppDeepLinkInfo](./kibana-plugin-core-public.publicappdeeplinkinfo.md) + +## PublicAppDeepLinkInfo type + +Public information about a registered app's [deepLinks](./kibana-plugin-core-public.appdeeplink.md) + +Signature: + +```typescript +export declare type PublicAppDeepLinkInfo = Omit & { + deepLinks: PublicAppDeepLinkInfo[]; + keywords: string[]; + navLinkStatus: AppNavLinkStatus; +}; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md b/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md index 9f45a06935fe4c..a5563eae83563f 100644 --- a/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md +++ b/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md @@ -9,10 +9,11 @@ Public information about a registered [application](./kibana-plugin-core-public. Signature: ```typescript -export declare type PublicAppInfo = Omit & { +export declare type PublicAppInfo = Omit & { status: AppStatus; navLinkStatus: AppNavLinkStatus; appRoute: string; - meta: PublicAppMetaInfo; + keywords: string[]; + deepLinks: PublicAppDeepLinkInfo[]; }; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.publicappmetainfo.md b/docs/development/core/public/kibana-plugin-core-public.publicappmetainfo.md deleted file mode 100644 index 3ef0460aec4670..00000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.publicappmetainfo.md +++ /dev/null @@ -1,16 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PublicAppMetaInfo](./kibana-plugin-core-public.publicappmetainfo.md) - -## PublicAppMetaInfo type - -Public information about a registered app's [keywords](./kibana-plugin-core-public.appmeta.md) - -Signature: - -```typescript -export declare type PublicAppMetaInfo = Omit & { - keywords: string[]; - searchDeepLinks: PublicAppSearchDeepLinkInfo[]; -}; -``` diff --git a/docs/development/core/public/kibana-plugin-core-public.publicappsearchdeeplinkinfo.md b/docs/development/core/public/kibana-plugin-core-public.publicappsearchdeeplinkinfo.md deleted file mode 100644 index e88cdb7d55edd6..00000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.publicappsearchdeeplinkinfo.md +++ /dev/null @@ -1,16 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PublicAppSearchDeepLinkInfo](./kibana-plugin-core-public.publicappsearchdeeplinkinfo.md) - -## PublicAppSearchDeepLinkInfo type - -Public information about a registered app's [searchDeepLinks](./kibana-plugin-core-public.appsearchdeeplink.md) - -Signature: - -```typescript -export declare type PublicAppSearchDeepLinkInfo = Omit & { - searchDeepLinks: PublicAppSearchDeepLinkInfo[]; - keywords: string[]; -}; -``` diff --git a/docs/maps/index.asciidoc b/docs/maps/index.asciidoc index e4150fc280096c..45d24bfb5a7e4f 100644 --- a/docs/maps/index.asciidoc +++ b/docs/maps/index.asciidoc @@ -1,6 +1,5 @@ :ems-docker-repo: docker.elastic.co/elastic-maps-service/elastic-maps-server-ubi8 :ems-docker-image: {ems-docker-repo}:{version} -:hosted-ems: Elastic Maps Server [role="xpack"] [[maps]] diff --git a/package.json b/package.json index 5737bce303e09c..73f3e5585faf74 100644 --- a/package.json +++ b/package.json @@ -130,6 +130,7 @@ "@kbn/config": "link:bazel-bin/packages/kbn-config/npm_module", "@kbn/config-schema": "link:bazel-bin/packages/kbn-config-schema/npm_module", "@kbn/crypto": "link:bazel-bin/packages/kbn-crypto/npm_module", + "@kbn/mapbox-gl": "link:bazel-bin/packages/kbn-mapbox-gl/npm_module", "@kbn/i18n": "link:bazel-bin/packages/kbn-i18n/npm_module", "@kbn/interpreter": "link:packages/kbn-interpreter", "@kbn/io-ts-utils": "link:packages/kbn-io-ts-utils", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index ec8252cb6144da..43528e0ae41629 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -24,6 +24,7 @@ filegroup( "//packages/kbn-i18n:build", "//packages/kbn-legacy-logging:build", "//packages/kbn-logging:build", + "//packages/kbn-mapbox-gl:build", "//packages/kbn-plugin-generator:build", "//packages/kbn-securitysolution-list-constants:build", "//packages/kbn-securitysolution-io-ts-types:build", diff --git a/packages/kbn-mapbox-gl/BUILD.bazel b/packages/kbn-mapbox-gl/BUILD.bazel new file mode 100644 index 00000000000000..7d7186068832ec --- /dev/null +++ b/packages/kbn-mapbox-gl/BUILD.bazel @@ -0,0 +1,84 @@ + +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-mapbox-gl" +PKG_REQUIRE_NAME = "@kbn/mapbox-gl" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + ], + exclude = [ + "**/*.test.*", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md" +] + +SRC_DEPS = [ + "@npm//@mapbox/mapbox-gl-rtl-text", + "@npm//file-loader", + "@npm//mapbox-gl", +] + +TYPES_DEPS = [ + "@npm//@types/mapbox-gl", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_project( + name = "tsc", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + declaration = True, + declaration_map = True, + incremental = True, + out_dir = "target", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = DEPS + [":tsc"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-mapbox-gl/README.md b/packages/kbn-mapbox-gl/README.md new file mode 100644 index 00000000000000..c1fdea491feaae --- /dev/null +++ b/packages/kbn-mapbox-gl/README.md @@ -0,0 +1,3 @@ +# @kbn/mapbox-gl + +Default instantiation for mapbox-gl. \ No newline at end of file diff --git a/packages/kbn-mapbox-gl/package.json b/packages/kbn-mapbox-gl/package.json new file mode 100644 index 00000000000000..9de88dac54a5ab --- /dev/null +++ b/packages/kbn-mapbox-gl/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/mapbox-gl", + "version": "1.0.0", + "private": true, + "license": "SSPL-1.0 OR Elastic License 2.0", + "main": "./target/index.js", + "types": "./target/index.d.ts" +} diff --git a/packages/kbn-mapbox-gl/src/index.ts b/packages/kbn-mapbox-gl/src/index.ts new file mode 100644 index 00000000000000..117b874a28ffbd --- /dev/null +++ b/packages/kbn-mapbox-gl/src/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import './typings'; +import mapboxgl from 'mapbox-gl/dist/mapbox-gl-csp'; +// @ts-expect-error +import mbRtlPlugin from '!!file-loader!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.min.js'; +// @ts-expect-error +import mbWorkerUrl from '!!file-loader!mapbox-gl/dist/mapbox-gl-csp-worker'; +import 'mapbox-gl/dist/mapbox-gl.css'; + +mapboxgl.workerUrl = mbWorkerUrl; +mapboxgl.setRTLTextPlugin(mbRtlPlugin); + +export { mapboxgl }; diff --git a/packages/kbn-mapbox-gl/src/typings.ts b/packages/kbn-mapbox-gl/src/typings.ts new file mode 100644 index 00000000000000..0cc6908aca4284 --- /dev/null +++ b/packages/kbn-mapbox-gl/src/typings.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// Mapbox-gl doesn't declare this type. +declare module 'mapbox-gl/dist/mapbox-gl-csp'; diff --git a/packages/kbn-mapbox-gl/tsconfig.json b/packages/kbn-mapbox-gl/tsconfig.json new file mode 100644 index 00000000000000..cf1cca0f5a0fd1 --- /dev/null +++ b/packages/kbn-mapbox-gl/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "incremental": true, + "outDir": "./target", + "declaration": true, + "declarationMap": true, + "rootDir": "src", + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-mapbox-gl/src", + "types": [] + }, + "include": [ + "src/**/*", + ] +} diff --git a/packages/kbn-test/src/functional_test_runner/lib/providers/provider_collection.ts b/packages/kbn-test/src/functional_test_runner/lib/providers/provider_collection.ts index 1aa5df1105f460..2d05d5bba5ff6f 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/providers/provider_collection.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/providers/provider_collection.ts @@ -12,6 +12,7 @@ import { loadTracer } from '../load_tracer'; import { createAsyncInstance, isAsyncInstance } from './async_instance'; import { Providers } from './read_provider_spec'; import { createVerboseInstance } from './verbose_instance'; +import { GenericFtrService } from '../../public_types'; export class ProviderCollection { private readonly instances = new Map(); @@ -58,12 +59,19 @@ export class ProviderCollection { } public invokeProviderFn(provider: (args: any) => any) { - return provider({ + const ctx = { getService: this.getService, hasService: this.hasService, getPageObject: this.getPageObject, getPageObjects: this.getPageObjects, - }); + }; + + if (provider.prototype instanceof GenericFtrService) { + const Constructor = (provider as any) as new (ctx: any) => any; + return new Constructor(ctx); + } + + return provider(ctx); } private findProvider(type: string, name: string) { diff --git a/packages/kbn-test/src/functional_test_runner/public_types.ts b/packages/kbn-test/src/functional_test_runner/public_types.ts index 915cb34f6ffe50..4a30744c09b516 100644 --- a/packages/kbn-test/src/functional_test_runner/public_types.ts +++ b/packages/kbn-test/src/functional_test_runner/public_types.ts @@ -13,7 +13,7 @@ import { Test, Suite } from './fake_mocha_types'; export { Lifecycle, Config, FailureMetadata }; -interface AsyncInstance { +export interface AsyncInstance { /** * Services that are initialized async are not ready before the tests execute, so you might need * to call `init()` and await the promise it returns before interacting with the service @@ -39,7 +39,11 @@ export type ProvidedType any> = MaybeAsyncInstance * promise types into the async instances that other providers will receive. */ type ProvidedTypeMap = { - [K in keyof T]: T[K] extends (...args: any[]) => any ? ProvidedType : unknown; + [K in keyof T]: T[K] extends new (...args: any[]) => infer X + ? X + : T[K] extends (...args: any[]) => any + ? ProvidedType + : unknown; }; export interface GenericFtrProviderContext< @@ -84,6 +88,10 @@ export interface GenericFtrProviderContext< loadTestFile(path: string): void; } +export class GenericFtrService> { + constructor(protected readonly ctx: ProviderContext) {} +} + export interface FtrConfigProviderContext { log: ToolingLog; readConfigFile(path: string): Promise; diff --git a/src/core/public/application/application_service.test.ts b/src/core/public/application/application_service.test.ts index 76b9c7a73d3bd1..2e2f1cad49f199 100644 --- a/src/core/public/application/application_service.test.ts +++ b/src/core/public/application/application_service.test.ts @@ -75,7 +75,10 @@ describe('#setup()', () => { const pluginId = Symbol('plugin'); const updater$ = new BehaviorSubject((app) => ({})); setup.register(pluginId, createApp({ id: 'app1', updater$ })); - setup.register(pluginId, createApp({ id: 'app2' })); + setup.register( + pluginId, + createApp({ id: 'app2', deepLinks: [{ id: 'subapp1', title: 'Subapp', path: '/subapp' }] }) + ); const { applications$ } = await service.start(startDeps); let applications = await applications$.pipe(take(1)).toPromise(); @@ -92,6 +95,11 @@ describe('#setup()', () => { id: 'app2', navLinkStatus: AppNavLinkStatus.visible, status: AppStatus.accessible, + deepLinks: [ + expect.objectContaining({ + navLinkStatus: AppNavLinkStatus.hidden, + }), + ], }) ); diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index 4a93c98205b842..bbfea61220b513 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -19,6 +19,7 @@ import { AppRouter } from './ui'; import { Capabilities, CapabilitiesService } from './capabilities'; import { App, + AppDeepLink, AppLeaveHandler, AppMount, AppNavLinkStatus, @@ -166,6 +167,7 @@ export class ApplicationService { ...appProps, status: app.status ?? AppStatus.accessible, navLinkStatus: app.navLinkStatus ?? AppNavLinkStatus.default, + deepLinks: populateDeepLinkDefaults(appProps.deepLinks), }); if (updater$) { registerStatusUpdater(app.id, updater$); @@ -392,3 +394,12 @@ const updateStatus = (app: App, statusUpdaters: AppUpdaterWrapper[]): App => { ...changes, }; }; + +const populateDeepLinkDefaults = (deepLinks?: AppDeepLink[]): AppDeepLink[] => { + if (!deepLinks) return []; + return deepLinks.map((deepLink) => ({ + ...deepLink, + navLinkStatus: deepLink.navLinkStatus ?? AppNavLinkStatus.default, + deepLinks: populateDeepLinkDefaults(deepLink.deepLinks), + })); +}; diff --git a/src/core/public/application/index.ts b/src/core/public/application/index.ts index 1e9a91717e81ae..68e1991646afbd 100644 --- a/src/core/public/application/index.ts +++ b/src/core/public/application/index.ts @@ -18,8 +18,7 @@ export type { AppMountParameters, AppUpdatableFields, AppUpdater, - AppMeta, - AppSearchDeepLink, + AppDeepLink, ApplicationSetup, ApplicationStart, AppLeaveHandler, @@ -29,8 +28,7 @@ export type { AppLeaveConfirmAction, NavigateToAppOptions, PublicAppInfo, - PublicAppMetaInfo, - PublicAppSearchDeepLinkInfo, + PublicAppDeepLinkInfo, // Internal types InternalApplicationSetup, InternalApplicationStart, diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 24f46752f28e58..ffc41955360bdf 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -63,7 +63,7 @@ export enum AppNavLinkStatus { */ export type AppUpdatableFields = Pick< App, - 'status' | 'navLinkStatus' | 'tooltip' | 'defaultPath' | 'meta' + 'status' | 'navLinkStatus' | 'tooltip' | 'defaultPath' | 'deepLinks' >; /** @@ -211,106 +211,92 @@ export interface App { */ exactRoute?: boolean; + /** Optional keywords to match with in deep links search. Omit if this part of the hierarchy does not have a page URL. */ + keywords?: string[]; + /** - * Meta data for an application that represent additional information for the app. - * See {@link AppMeta} + * Input type for registering secondary in-app locations for an application. * - * @remarks - * Used to populate navigational search results (where available). - * Can be updated using the {@link App.updater$} observable. See {@link PublicAppSearchDeepLinkInfo} for more details. + * Deep links must include at least one of `path` or `deepLinks`. A deep link that does not have a `path` + * represents a topological level in the application's hierarchy, but does not have a destination URL that is + * user-accessible. * * @example * ```ts * core.application.register({ * id: 'my_app', * title: 'Translated title', - * meta: { - * keywords: ['translated keyword1', 'translated keyword2'], - * searchDeepLinks: [ - * { id: 'sub1', title: 'Sub1', path: '/sub1', keywords: ['subpath1'] }, + * keywords: ['translated keyword1', 'translated keyword2'], + * deepLinks: [ + * { + * id: 'sub1', + * title: 'Sub1', + * path: '/sub1', + * keywords: ['subpath1'], + * }, * { * id: 'sub2', * title: 'Sub2', - * searchDeepLinks: [ - * { id: 'subsub', title: 'SubSub', path: '/sub2/sub', keywords: ['subpath2'] } - * ] - * } + * deepLinks: [ + * { + * id: 'subsub', + * title: 'SubSub', + * path: '/sub2/sub', + * keywords: ['subpath2'], + * }, + * ], + * }, * ], - * }, * mount: () => { ... } * }) * ``` */ - meta?: AppMeta; -} - -/** - * Input type for meta data for an application. - * - * Meta fields include `keywords` and `searchDeepLinks` - * Keywords is an array of string with which to associate the app, must include at least one unique string as an array. - * `searchDeepLinks` is an array of links that represent secondary in-app locations for the app. - * @public - */ -export interface AppMeta { - /** Keywords to represent this application */ - keywords?: string[]; - /** Array of links that represent secondary in-app locations for the app. */ - searchDeepLinks?: AppSearchDeepLink[]; + deepLinks?: AppDeepLink[]; } /** - * Public information about a registered app's {@link AppMeta | keywords } + * Public information about a registered app's {@link AppDeepLink | deepLinks} * * @public */ -export type PublicAppMetaInfo = Omit & { - keywords: string[]; - searchDeepLinks: PublicAppSearchDeepLinkInfo[]; -}; - -/** - * Public information about a registered app's {@link AppSearchDeepLink | searchDeepLinks} - * - * @public - */ -export type PublicAppSearchDeepLinkInfo = Omit< - AppSearchDeepLink, - 'searchDeepLinks' | 'keywords' +export type PublicAppDeepLinkInfo = Omit< + AppDeepLink, + 'deepLinks' | 'keywords' | 'navLinkStatus' > & { - searchDeepLinks: PublicAppSearchDeepLinkInfo[]; + deepLinks: PublicAppDeepLinkInfo[]; keywords: string[]; + navLinkStatus: AppNavLinkStatus; }; /** * Input type for registering secondary in-app locations for an application. * - * Deep links must include at least one of `path` or `searchDeepLinks`. A deep link that does not have a `path` + * Deep links must include at least one of `path` or `deepLinks`. A deep link that does not have a `path` * represents a topological level in the application's hierarchy, but does not have a destination URL that is * user-accessible. * @public */ -export type AppSearchDeepLink = { +export type AppDeepLink = { /** Identifier to represent this sublink, should be unique for this application */ id: string; /** Title to label represent this deep link */ title: string; + /** Optional keywords to match with in deep links search. Omit if this part of the hierarchy does not have a page URL. */ + keywords?: string[]; + /** Optional status of the chrome navigation, defaults to `hidden` */ + navLinkStatus?: AppNavLinkStatus; } & ( | { /** URL path to access this link, relative to the application's appRoute. */ path: string; /** Optional array of links that are 'underneath' this section in the hierarchy */ - searchDeepLinks?: AppSearchDeepLink[]; - /** Optional keywords to match with in deep links search for the page at the path */ - keywords?: string[]; + deepLinks?: AppDeepLink[]; } | { /** Optional path to access this section. Omit if this part of the hierarchy does not have a page URL. */ path?: string; /** Array links that are 'underneath' this section in this hierarchy. */ - searchDeepLinks: AppSearchDeepLink[]; - /** Optional keywords to match with in deep links search. Omit if this part of the hierarchy does not have a page URL. */ - keywords?: string[]; + deepLinks: AppDeepLink[]; } ); @@ -319,12 +305,13 @@ export type AppSearchDeepLink = { * * @public */ -export type PublicAppInfo = Omit & { +export type PublicAppInfo = Omit & { // remove optional on fields populated with default values status: AppStatus; navLinkStatus: AppNavLinkStatus; appRoute: string; - meta: PublicAppMetaInfo; + keywords: string[]; + deepLinks: PublicAppDeepLinkInfo[]; }; /** diff --git a/src/core/public/application/utils/get_app_info.test.ts b/src/core/public/application/utils/get_app_info.test.ts index 28824867234ff9..ef4a06707d6664 100644 --- a/src/core/public/application/utils/get_app_info.test.ts +++ b/src/core/public/application/utils/get_app_info.test.ts @@ -32,24 +32,20 @@ describe('getAppInfo', () => { status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.visible, appRoute: `/app/some-id`, - meta: { - keywords: [], - searchDeepLinks: [], - }, + keywords: [], + deepLinks: [], }); }); - it('populates default values for nested searchDeepLinks', () => { + it('populates default values for nested deepLinks', () => { const app = createApp({ - meta: { - searchDeepLinks: [ - { - id: 'sub-id', - title: 'sub-title', - searchDeepLinks: [{ id: 'sub-sub-id', title: 'sub-sub-title', path: '/sub-sub' }], - }, - ], - }, + deepLinks: [ + { + id: 'sub-id', + title: 'sub-title', + deepLinks: [{ id: 'sub-sub-id', title: 'sub-sub-title', path: '/sub-sub' }], + }, + ], }); const info = getAppInfo(app); @@ -59,25 +55,23 @@ describe('getAppInfo', () => { status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.visible, appRoute: `/app/some-id`, - meta: { - keywords: [], - searchDeepLinks: [ - { - id: 'sub-id', - title: 'sub-title', - keywords: [], - searchDeepLinks: [ - { - id: 'sub-sub-id', - title: 'sub-sub-title', - path: '/sub-sub', - keywords: [], - searchDeepLinks: [], // default empty array added - }, - ], - }, - ], - }, + keywords: [], + deepLinks: [ + { + id: 'sub-id', + title: 'sub-title', + keywords: [], + deepLinks: [ + { + id: 'sub-sub-id', + title: 'sub-sub-title', + path: '/sub-sub', + keywords: [], + deepLinks: [], // default empty array added + }, + ], + }, + ], }); }); @@ -110,22 +104,20 @@ describe('getAppInfo', () => { it('adds default meta fields to sublinks when needed', () => { const app = createApp({ - meta: { - searchDeepLinks: [ - { - id: 'sub-id', - title: 'sub-title', - searchDeepLinks: [ - { - id: 'sub-sub-id', - title: 'sub-sub-title', - path: '/sub-sub', - keywords: ['sub sub'], - }, - ], - }, - ], - }, + deepLinks: [ + { + id: 'sub-id', + title: 'sub-title', + deepLinks: [ + { + id: 'sub-sub-id', + title: 'sub-sub-title', + path: '/sub-sub', + keywords: ['sub sub'], + }, + ], + }, + ], }); const info = getAppInfo(app); @@ -135,25 +127,23 @@ describe('getAppInfo', () => { status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.visible, appRoute: `/app/some-id`, - meta: { - keywords: [], - searchDeepLinks: [ - { - id: 'sub-id', - title: 'sub-title', - keywords: [], // default empty array - searchDeepLinks: [ - { - id: 'sub-sub-id', - title: 'sub-sub-title', - path: '/sub-sub', - keywords: ['sub sub'], - searchDeepLinks: [], - }, - ], - }, - ], - }, + keywords: [], + deepLinks: [ + { + id: 'sub-id', + title: 'sub-title', + keywords: [], // default empty array + deepLinks: [ + { + id: 'sub-sub-id', + title: 'sub-sub-title', + path: '/sub-sub', + keywords: ['sub sub'], + deepLinks: [], + }, + ], + }, + ], }); }); }); diff --git a/src/core/public/application/utils/get_app_info.ts b/src/core/public/application/utils/get_app_info.ts index ca1e8ac8076463..4c94e24f501bc4 100644 --- a/src/core/public/application/utils/get_app_info.ts +++ b/src/core/public/application/utils/get_app_info.ts @@ -10,9 +10,9 @@ import { App, AppNavLinkStatus, AppStatus, - AppSearchDeepLink, + AppDeepLink, PublicAppInfo, - PublicAppSearchDeepLinkInfo, + PublicAppDeepLinkInfo, } from '../types'; export function getAppInfo(app: App): PublicAppInfo { @@ -28,29 +28,27 @@ export function getAppInfo(app: App): PublicAppInfo { status: app.status!, navLinkStatus, appRoute: app.appRoute!, - meta: { - keywords: app.meta?.keywords ?? [], - searchDeepLinks: getSearchDeepLinkInfos(app, app.meta?.searchDeepLinks), - }, + keywords: app.keywords ?? [], + deepLinks: getDeepLinkInfos(app.deepLinks), }; } -function getSearchDeepLinkInfos( - app: App, - searchDeepLinks?: AppSearchDeepLink[] -): PublicAppSearchDeepLinkInfo[] { - if (!searchDeepLinks) { - return []; - } +function getDeepLinkInfos(deepLinks?: AppDeepLink[]): PublicAppDeepLinkInfo[] { + if (!deepLinks) return []; - return searchDeepLinks.map( - (rawDeepLink): PublicAppSearchDeepLinkInfo => { + return deepLinks.map( + (rawDeepLink): PublicAppDeepLinkInfo => { + const navLinkStatus = + rawDeepLink.navLinkStatus === AppNavLinkStatus.default + ? AppNavLinkStatus.hidden + : rawDeepLink.navLinkStatus!; return { id: rawDeepLink.id, title: rawDeepLink.title, path: rawDeepLink.path, keywords: rawDeepLink.keywords ?? [], - searchDeepLinks: getSearchDeepLinkInfos(app, rawDeepLink.searchDeepLinks), + navLinkStatus, + deepLinks: getDeepLinkInfos(rawDeepLink.deepLinks), }; } ); diff --git a/src/core/public/chrome/nav_links/to_nav_link.test.ts b/src/core/public/chrome/nav_links/to_nav_link.test.ts index 41c4ff178d7378..db783d0028f075 100644 --- a/src/core/public/chrome/nav_links/to_nav_link.test.ts +++ b/src/core/public/chrome/nav_links/to_nav_link.test.ts @@ -17,10 +17,8 @@ const app = (props: Partial = {}): PublicAppInfo => ({ status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.default, appRoute: `/app/some-id`, - meta: { - keywords: [], - searchDeepLinks: [], - }, + keywords: [], + deepLinks: [], ...props, }); diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 129ae1c16f99b1..24b48683cdd937 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -89,13 +89,11 @@ export type { AppLeaveAction, AppLeaveDefaultAction, AppLeaveConfirmAction, - AppMeta, AppUpdatableFields, AppUpdater, - AppSearchDeepLink, + AppDeepLink, PublicAppInfo, - PublicAppMetaInfo, - PublicAppSearchDeepLinkInfo, + PublicAppDeepLinkInfo, NavigateToAppOptions, } from './application'; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 31e7a1e2321dfc..9f0c5135e702fa 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -58,12 +58,13 @@ export interface App { capabilities?: Partial; category?: AppCategory; chromeless?: boolean; + deepLinks?: AppDeepLink[]; defaultPath?: string; euiIconType?: string; exactRoute?: boolean; icon?: string; id: string; - meta?: AppMeta; + keywords?: string[]; mount: AppMount; navLinkStatus?: AppNavLinkStatus; order?: number; @@ -85,6 +86,20 @@ export interface AppCategory { order?: number; } +// @public +export type AppDeepLink = { + id: string; + title: string; + keywords?: string[]; + navLinkStatus?: AppNavLinkStatus; +} & ({ + path: string; + deepLinks?: AppDeepLink[]; +} | { + path?: string; + deepLinks: AppDeepLink[]; +}); + // @public export type AppLeaveAction = AppLeaveDefaultAction | AppLeaveConfirmAction; @@ -142,12 +157,6 @@ export interface ApplicationStart { navigateToUrl(url: string): Promise; } -// @public -export interface AppMeta { - keywords?: string[]; - searchDeepLinks?: AppSearchDeepLink[]; -} - // @public export type AppMount = (params: AppMountParameters) => AppUnmount | Promise; @@ -170,20 +179,6 @@ export enum AppNavLinkStatus { visible = 1 } -// @public -export type AppSearchDeepLink = { - id: string; - title: string; -} & ({ - path: string; - searchDeepLinks?: AppSearchDeepLink[]; - keywords?: string[]; -} | { - path?: string; - searchDeepLinks: AppSearchDeepLink[]; - keywords?: string[]; -}); - // @public export enum AppStatus { accessible = 0, @@ -194,7 +189,7 @@ export enum AppStatus { export type AppUnmount = () => void; // @public -export type AppUpdatableFields = Pick; +export type AppUpdatableFields = Pick; // @public export type AppUpdater = (app: App) => Partial | undefined; @@ -1071,23 +1066,19 @@ export interface PluginInitializerContext export type PluginOpaqueId = symbol; // @public -export type PublicAppInfo = Omit & { - status: AppStatus; - navLinkStatus: AppNavLinkStatus; - appRoute: string; - meta: PublicAppMetaInfo; -}; - -// @public -export type PublicAppMetaInfo = Omit & { +export type PublicAppDeepLinkInfo = Omit & { + deepLinks: PublicAppDeepLinkInfo[]; keywords: string[]; - searchDeepLinks: PublicAppSearchDeepLinkInfo[]; + navLinkStatus: AppNavLinkStatus; }; // @public -export type PublicAppSearchDeepLinkInfo = Omit & { - searchDeepLinks: PublicAppSearchDeepLinkInfo[]; +export type PublicAppInfo = Omit & { + status: AppStatus; + navLinkStatus: AppNavLinkStatus; + appRoute: string; keywords: string[]; + deepLinks: PublicAppDeepLinkInfo[]; }; // @public diff --git a/src/plugins/dev_tools/public/plugin.ts b/src/plugins/dev_tools/public/plugin.ts index e9f5d206de9180..5ccf614533164a 100644 --- a/src/plugins/dev_tools/public/plugin.ts +++ b/src/plugins/dev_tools/public/plugin.ts @@ -7,7 +7,7 @@ */ import { BehaviorSubject } from 'rxjs'; -import { Plugin, CoreSetup, AppMountParameters, AppSearchDeepLink } from 'src/core/public'; +import { Plugin, CoreSetup, AppMountParameters, AppDeepLink } from 'src/core/public'; import { AppUpdater } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { sortBy } from 'lodash'; @@ -86,7 +86,7 @@ export class DevToolsPlugin implements Plugin { this.appStateUpdater.next(() => ({ navLinkStatus: AppNavLinkStatus.hidden })); } else { this.appStateUpdater.next(() => { - const deepLinks: AppSearchDeepLink[] = [...this.devTools.values()] + const deepLinks: AppDeepLink[] = [...this.devTools.values()] .filter( // Some tools do not use a string title, so we filter those out (tool) => !tool.enableRouting && !tool.isDisabled() && typeof tool.title === 'string' @@ -96,7 +96,7 @@ export class DevToolsPlugin implements Plugin { title: tool.title as string, path: `#/${tool.id}`, })); - return { meta: { searchDeepLinks: deepLinks } }; + return { deepLinks }; }); } } diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index 0429ea8c9bd7bd..1f96ec87171c5c 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -20,7 +20,7 @@ import { AppUpdater, AppStatus, AppNavLinkStatus, - AppSearchDeepLink, + AppDeepLink, } from '../../../core/public'; import { MANAGEMENT_APP_ID } from '../common/contants'; @@ -38,22 +38,20 @@ export class ManagementPlugin implements Plugin(() => { - const deepLinks: AppSearchDeepLink[] = Object.values( - this.managementSections.definedSections - ).map((section: ManagementSection) => ({ - id: section.id, - title: section.title, - searchDeepLinks: section.getAppsEnabled().map((mgmtApp) => ({ - id: mgmtApp.id, - title: mgmtApp.title, - path: mgmtApp.basePath, - meta: { ...mgmtApp.meta }, - })), - })); - - return { - meta: { searchDeepLinks: deepLinks }, - }; + const deepLinks: AppDeepLink[] = Object.values(this.managementSections.definedSections).map( + (section: ManagementSection) => ({ + id: section.id, + title: section.title, + deepLinks: section.getAppsEnabled().map((mgmtApp) => ({ + id: mgmtApp.id, + title: mgmtApp.title, + path: mgmtApp.basePath, + keywords: mgmtApp.keywords, + })), + }) + ); + + return { deepLinks }; }); private hasAnyEnabledApps = true; diff --git a/src/plugins/management/public/utils/management_app.ts b/src/plugins/management/public/utils/management_app.ts index c9385463def5b4..3578b2ab0b7f2d 100644 --- a/src/plugins/management/public/utils/management_app.ts +++ b/src/plugins/management/public/utils/management_app.ts @@ -6,28 +6,25 @@ * Side Public License, v 1. */ -import { AppMeta } from 'kibana/public'; import { CreateManagementItemArgs, Mount } from '../types'; import { ManagementItem } from './management_item'; export interface RegisterManagementAppArgs extends CreateManagementItemArgs { mount: Mount; basePath: string; - meta?: AppMeta; + keywords?: string[]; } export class ManagementApp extends ManagementItem { public readonly mount: Mount; public readonly basePath: string; - public readonly meta: AppMeta; + public readonly keywords: string[]; constructor(args: RegisterManagementAppArgs) { super(args); this.mount = args.mount; this.basePath = args.basePath; - this.meta = { - keywords: args.meta?.keywords || [], - }; + this.keywords = args.keywords || []; } } diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_map_view/vega_map_view.scss b/src/plugins/vis_type_vega/public/vega_view/vega_map_view/vega_map_view.scss index 33e63e7ef317c1..3e3ef71faf0d7a 100644 --- a/src/plugins/vis_type_vega/public/vega_view/vega_map_view/vega_map_view.scss +++ b/src/plugins/vis_type_vega/public/vega_view/vega_map_view/vega_map_view.scss @@ -1,5 +1,3 @@ -@import '~mapbox-gl/dist/mapbox-gl.css'; - .vgaVis { .mapboxgl-canvas-container { cursor: auto; diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_map_view/view.test.ts b/src/plugins/vis_type_vega/public/vega_view/vega_map_view/view.test.ts index 4fd19aa45e69e7..ee3bf305e9427e 100644 --- a/src/plugins/vis_type_vega/public/vega_view/vega_map_view/view.test.ts +++ b/src/plugins/vis_type_vega/public/vega_view/vega_map_view/view.test.ts @@ -29,30 +29,31 @@ import { } from '../../services'; import { initVegaLayer, initTmsRasterLayer } from './layers'; -// @ts-expect-error -import mapboxgl from 'mapbox-gl/dist/mapbox-gl-csp'; - -jest.mock('mapbox-gl/dist/mapbox-gl-csp', () => ({ - setRTLTextPlugin: jest.fn(), - Map: jest.fn().mockImplementation(() => ({ - getLayer: () => '', - removeLayer: jest.fn(), - once: (eventName: string, handler: Function) => handler(), - remove: () => jest.fn(), - getCanvas: () => ({ clientWidth: 512, clientHeight: 512 }), - getCenter: () => ({ lat: 20, lng: 20 }), - getZoom: () => 3, - addControl: jest.fn(), - addLayer: jest.fn(), - dragRotate: { - disable: jest.fn(), - }, - touchZoomRotate: { - disableRotation: jest.fn(), - }, - })), - MapboxOptions: jest.fn(), - NavigationControl: jest.fn(), +import { mapboxgl } from '@kbn/mapbox-gl'; + +jest.mock('@kbn/mapbox-gl', () => ({ + mapboxgl: { + setRTLTextPlugin: jest.fn(), + Map: jest.fn().mockImplementation(() => ({ + getLayer: () => '', + removeLayer: jest.fn(), + once: (eventName: string, handler: Function) => handler(), + remove: () => jest.fn(), + getCanvas: () => ({ clientWidth: 512, clientHeight: 512 }), + getCenter: () => ({ lat: 20, lng: 20 }), + getZoom: () => 3, + addControl: jest.fn(), + addLayer: jest.fn(), + dragRotate: { + disable: jest.fn(), + }, + touchZoomRotate: { + disableRotation: jest.fn(), + }, + })), + MapboxOptions: jest.fn(), + NavigationControl: jest.fn(), + }, })); jest.mock('./layers', () => ({ diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_map_view/view.ts b/src/plugins/vis_type_vega/public/vega_view/vega_map_view/view.ts index e899057819a192..835ac36ceee471 100644 --- a/src/plugins/vis_type_vega/public/vega_view/vega_map_view/view.ts +++ b/src/plugins/vis_type_vega/public/vega_view/vega_map_view/view.ts @@ -10,8 +10,9 @@ import { i18n } from '@kbn/i18n'; import type { Map, Style, MapboxOptions } from 'mapbox-gl'; import { View, parse } from 'vega'; -// @ts-expect-error -import mapboxgl from 'mapbox-gl/dist/mapbox-gl-csp'; + +import { mapboxgl } from '@kbn/mapbox-gl'; + import { initTmsRasterLayer, initVegaLayer } from './layers'; import { VegaBaseView } from '../vega_base_view'; import { getMapServiceSettings } from '../../services'; @@ -27,14 +28,6 @@ import { import { validateZoomSettings, injectMapPropsIntoSpec } from './utils'; import './vega_map_view.scss'; -// @ts-expect-error -import mbRtlPlugin from '!!file-loader!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.min.js'; -// @ts-expect-error -import mbWorkerUrl from '!!file-loader!mapbox-gl/dist/mapbox-gl-csp-worker'; - -mapboxgl.workerUrl = mbWorkerUrl; -mapboxgl.setRTLTextPlugin(mbRtlPlugin); - async function updateVegaView(mapBoxInstance: Map, vegaView: View) { const mapCanvas = mapBoxInstance.getCanvas(); const { lat, lng } = mapBoxInstance.getCenter(); diff --git a/src/plugins/vis_type_vislib/public/__snapshots__/to_ast.test.ts.snap b/src/plugins/vis_type_vislib/public/__snapshots__/to_ast.test.ts.snap index 3ca2834a54fca2..8b720568c4d2c4 100644 --- a/src/plugins/vis_type_vislib/public/__snapshots__/to_ast.test.ts.snap +++ b/src/plugins/vis_type_vislib/public/__snapshots__/to_ast.test.ts.snap @@ -8,7 +8,7 @@ Object { "area", ], "visConfig": Array [ - "{\\"type\\":\\"area\\",\\"grid\\":{\\"categoryLines\\":false,\\"style\\":{\\"color\\":\\"#eee\\"}},\\"categoryAxes\\":[{\\"id\\":\\"CategoryAxis-1\\",\\"type\\":\\"category\\",\\"position\\":\\"bottom\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\"},\\"labels\\":{\\"show\\":true,\\"truncate\\":100},\\"title\\":{}}],\\"valueAxes\\":[{\\"id\\":\\"ValueAxis-1\\",\\"name\\":\\"LeftAxis-1\\",\\"type\\":\\"value\\",\\"position\\":\\"left\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\",\\"mode\\":\\"normal\\"},\\"labels\\":{\\"show\\":true,\\"rotate\\":0,\\"filter\\":false,\\"truncate\\":100},\\"title\\":{\\"text\\":\\"Sum of total_quantity\\"}}],\\"seriesParams\\":[{\\"show\\":\\"true\\",\\"type\\":\\"area\\",\\"mode\\":\\"stacked\\",\\"data\\":{\\"label\\":\\"Sum of total_quantity\\",\\"id\\":\\"1\\"},\\"drawLinesBetweenPoints\\":true,\\"showCircles\\":true,\\"interpolate\\":\\"linear\\",\\"valueAxis\\":\\"ValueAxis-1\\"}],\\"addTooltip\\":true,\\"addLegend\\":true,\\"legendPosition\\":\\"top\\",\\"times\\":[],\\"addTimeMarker\\":false,\\"thresholdLine\\":{\\"show\\":false,\\"value\\":10,\\"width\\":1,\\"style\\":\\"full\\",\\"color\\":\\"#E7664C\\"},\\"palette\\":{\\"name\\":\\"default\\"},\\"labels\\":{},\\"dimensions\\":{\\"x\\":{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"date\\",\\"params\\":{\\"pattern\\":\\"HH:mm:ss.SSS\\"}},\\"params\\":{}},\\"y\\":[{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\",\\"params\\":{\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}],\\"series\\":[{\\"accessor\\":2,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}]}}", + "{\\"type\\":\\"area\\",\\"grid\\":{\\"categoryLines\\":false,\\"style\\":{\\"color\\":\\"#eee\\"}},\\"categoryAxes\\":[{\\"id\\":\\"CategoryAxis-1\\",\\"type\\":\\"category\\",\\"position\\":\\"bottom\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\"},\\"labels\\":{\\"show\\":true,\\"truncate\\":100},\\"title\\":{}}],\\"valueAxes\\":[{\\"id\\":\\"ValueAxis-1\\",\\"name\\":\\"LeftAxis-1\\",\\"type\\":\\"value\\",\\"position\\":\\"left\\",\\"show\\":true,\\"style\\":{},\\"scale\\":{\\"type\\":\\"linear\\",\\"mode\\":\\"normal\\"},\\"labels\\":{\\"show\\":true,\\"rotate\\":0,\\"filter\\":false,\\"truncate\\":100},\\"title\\":{\\"text\\":\\"Sum of total_quantity\\"}}],\\"seriesParams\\":[{\\"show\\":\\"true\\",\\"type\\":\\"area\\",\\"mode\\":\\"stacked\\",\\"data\\":{\\"label\\":\\"Sum of total_quantity\\",\\"id\\":\\"1\\"},\\"drawLinesBetweenPoints\\":true,\\"showCircles\\":true,\\"circlesRadius\\":5,\\"interpolate\\":\\"linear\\",\\"valueAxis\\":\\"ValueAxis-1\\"}],\\"addTooltip\\":true,\\"addLegend\\":true,\\"legendPosition\\":\\"top\\",\\"times\\":[],\\"addTimeMarker\\":false,\\"thresholdLine\\":{\\"show\\":false,\\"value\\":10,\\"width\\":1,\\"style\\":\\"full\\",\\"color\\":\\"#E7664C\\"},\\"palette\\":{\\"name\\":\\"default\\"},\\"labels\\":{},\\"dimensions\\":{\\"x\\":{\\"accessor\\":1,\\"format\\":{\\"id\\":\\"date\\",\\"params\\":{\\"pattern\\":\\"HH:mm:ss.SSS\\"}},\\"params\\":{}},\\"y\\":[{\\"accessor\\":0,\\"format\\":{\\"id\\":\\"number\\",\\"params\\":{\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}],\\"series\\":[{\\"accessor\\":2,\\"format\\":{\\"id\\":\\"terms\\",\\"params\\":{\\"id\\":\\"string\\",\\"otherBucketLabel\\":\\"Other\\",\\"missingBucketLabel\\":\\"Missing\\",\\"parsedUrl\\":{\\"origin\\":\\"http://localhost:5801\\",\\"pathname\\":\\"/app/visualize\\",\\"basePath\\":\\"\\"}}},\\"params\\":{}}]}}", ], }, "getArgument": [Function], diff --git a/src/plugins/vis_type_xy/public/config/get_config.ts b/src/plugins/vis_type_xy/public/config/get_config.ts index 8ebac1b71940a1..ce01572060a408 100644 --- a/src/plugins/vis_type_xy/public/config/get_config.ts +++ b/src/plugins/vis_type_xy/public/config/get_config.ts @@ -39,6 +39,7 @@ export function getConfig(table: Datatable, params: VisParams): VisConfig { fittingFunction, detailedTooltip, isVislibVis, + fillOpacity, } = params; const aspects = getAspects(table.columns, params.dimensions); const xAxis = getAxis( @@ -63,6 +64,7 @@ export function getConfig(table: Datatable, params: VisParams): VisConfig { // NOTE: downscale ratio to match current vislib implementation markSizeRatio: radiusRatio * 0.6, fittingFunction, + fillOpacity, detailedTooltip, orderBucketsBySum, isTimeChart, diff --git a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/index.test.tsx.snap b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/index.test.tsx.snap index 40e53d88f99cfc..05e2532073eaf4 100644 --- a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/index.test.tsx.snap +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/index.test.tsx.snap @@ -7,6 +7,7 @@ exports[`MetricsAxisOptions component should init with the default set of props seriesParams={ Array [ Object { + "circlesRadius": 3, "data": Object { "id": "1", "label": "Count", @@ -79,6 +80,7 @@ exports[`MetricsAxisOptions component should init with the default set of props seriesParams={ Array [ Object { + "circlesRadius": 3, "data": Object { "id": "1", "label": "Count", diff --git a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/line_options.test.tsx.snap b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/line_options.test.tsx.snap index 7b45423f5f861e..8764db1dea06ad 100644 --- a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/line_options.test.tsx.snap +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/line_options.test.tsx.snap @@ -54,14 +54,5 @@ exports[`LineOptions component should init with the default set of props 1`] = ` /> - - `; diff --git a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/point_options.test.tsx.snap b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/point_options.test.tsx.snap new file mode 100644 index 00000000000000..fcd6f8d00a1385 --- /dev/null +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/point_options.test.tsx.snap @@ -0,0 +1,46 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PointOptions component should init with the default set of props 1`] = ` + + + + + + + + +`; diff --git a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.test.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.test.tsx index def24d51f49f39..1d5c8be2b92464 100644 --- a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.test.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.test.tsx @@ -12,6 +12,7 @@ import { shallow, mount } from 'enzyme'; import { ChartOptions, ChartOptionsParams } from './chart_options'; import { SeriesParam, ChartMode, AxisMode } from '../../../../types'; import { LineOptions } from './line_options'; +import { PointOptions } from './point_options'; import { valueAxis, seriesParam } from './mocks'; import { ChartType } from '../../../../../common'; @@ -41,6 +42,12 @@ describe('ChartOptions component', () => { expect(comp).toMatchSnapshot(); }); + it('should hide the PointOptions when type is bar', () => { + const comp = shallow(); + + expect(comp.find(PointOptions).exists()).toBeFalsy(); + }); + it('should show LineOptions when type is line', () => { chart.type = ChartType.Line; const comp = shallow(); @@ -48,6 +55,13 @@ describe('ChartOptions component', () => { expect(comp.find(LineOptions).exists()).toBeTruthy(); }); + it('should show PointOptions when type is area', () => { + chart.type = ChartType.Area; + const comp = shallow(); + + expect(comp.find(PointOptions).exists()).toBeTruthy(); + }); + it('should show line mode when type is area', () => { chart.type = ChartType.Area; const comp = shallow(); diff --git a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.tsx index 23452a87aae605..34ee33781f269d 100644 --- a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.tsx @@ -15,6 +15,7 @@ import { SelectOption } from '../../../../../../vis_default_editor/public'; import { SeriesParam, ValueAxis, ChartMode, AxisMode } from '../../../../types'; import { LineOptions } from './line_options'; +import { PointOptions } from './point_options'; import { SetParamByIndex, ChangeValueAxis } from '.'; import { ChartType } from '../../../../../common'; import { getConfigCollections } from '../../../collections'; @@ -143,6 +144,9 @@ function ChartOptions({ )} {chart.type === ChartType.Line && } + {(chart.type === ChartType.Area || chart.type === ChartType.Line) && ( + + )} ); } diff --git a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/line_options.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/line_options.tsx index 140f190c77181e..75dfe8627d73ea 100644 --- a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/line_options.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/line_options.tsx @@ -78,17 +78,6 @@ function LineOptions({ chart, setChart }: LineOptionsParams) { /> - - - - ); } diff --git a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/mocks.ts b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/mocks.ts index 7451f6dea9039b..eed224cf2a514a 100644 --- a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/mocks.ts +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/mocks.ts @@ -75,6 +75,7 @@ const seriesParam: SeriesParam = { drawLinesBetweenPoints: true, lineWidth: 2, showCircles: true, + circlesRadius: 3, interpolate: InterpolationMode.Linear, valueAxis: defaultValueAxisId, }; diff --git a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/point_options.test.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/point_options.test.tsx new file mode 100644 index 00000000000000..68ac1832d28a89 --- /dev/null +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/point_options.test.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { shallow, mount } from 'enzyme'; +import { findTestSubject } from '@elastic/eui/lib/test'; + +import { SeriesParam } from '../../../../types'; +import { PointOptions, PointOptionsParams } from './point_options'; +import { seriesParam } from './mocks'; + +describe('PointOptions component', () => { + let setChart: jest.Mock; + let defaultProps: PointOptionsParams; + let chart: SeriesParam; + + beforeEach(() => { + setChart = jest.fn(); + chart = { ...seriesParam }; + + defaultProps = { + chart, + setChart, + }; + }); + + it('should init with the default set of props', () => { + const comp = shallow(); + + expect(comp).toMatchSnapshot(); + }); + + it('should disable the dots size range if the show dots switch is off', () => { + chart.showCircles = false; + const comp = mount(); + const range = findTestSubject(comp, 'circlesRadius'); + expect(range.at(1).props().disabled).toBeTruthy(); + }); +}); diff --git a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/point_options.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/point_options.tsx new file mode 100644 index 00000000000000..d35a5a2374ca34 --- /dev/null +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/point_options.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; + +import { i18n } from '@kbn/i18n'; +import { EuiRange, EuiFormRow, EuiSpacer } from '@elastic/eui'; + +import { SwitchOption } from '../../../../../../vis_default_editor/public'; + +import { SeriesParam } from '../../../../types'; +import { SetChart } from './chart_options'; + +export interface PointOptionsParams { + chart: SeriesParam; + setChart: SetChart; +} + +function PointOptions({ chart, setChart }: PointOptionsParams) { + return ( + <> + + + + + { + setChart('circlesRadius', Number(e.currentTarget.value)); + }} + /> + + + ); +} + +export { PointOptions }; diff --git a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/utils.ts b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/utils.ts index d0d0c08060acf5..a8d53e45bc988b 100644 --- a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/utils.ts +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/utils.ts @@ -26,6 +26,7 @@ export const makeSerie = ( type: ChartType.Line, drawLinesBetweenPoints: true, showCircles: true, + circlesRadius: 3, interpolate: InterpolationMode.Linear, lineWidth: 2, valueAxis: defaultValueAxis, diff --git a/src/plugins/vis_type_xy/public/editor/components/options/point_series/elastic_charts_options.tsx b/src/plugins/vis_type_xy/public/editor/components/options/point_series/elastic_charts_options.tsx index 5398980e268d48..271c5445a95807 100644 --- a/src/plugins/vis_type_xy/public/editor/components/options/point_series/elastic_charts_options.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/point_series/elastic_charts_options.tsx @@ -10,7 +10,7 @@ import React, { useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { METRIC_TYPE } from '@kbn/analytics'; - +import { EuiFormRow, EuiRange } from '@elastic/eui'; import { SelectOption, SwitchOption, @@ -31,10 +31,14 @@ export function ElasticChartsOptions(props: ValidationVisOptionsProps const [palettesRegistry, setPalettesRegistry] = useState(null); const { stateParams, setValue, aggs } = props; - const hasLineChart = stateParams.seriesParams.some( + const isLineChart = stateParams.seriesParams.some( + ({ type, data: { id: paramId } }) => + type === ChartType.Line && aggs.aggs.find(({ id }) => id === paramId)?.enabled + ); + + const isAreaChart = stateParams.seriesParams.some( ({ type, data: { id: paramId } }) => - (type === ChartType.Line || type === ChartType.Area) && - aggs.aggs.find(({ id }) => id === paramId)?.enabled + type === ChartType.Area && aggs.aggs.find(({ id }) => id === paramId)?.enabled ); useEffect(() => { @@ -66,7 +70,7 @@ export function ElasticChartsOptions(props: ValidationVisOptionsProps }} /> - {hasLineChart && ( + {(isLineChart || isAreaChart) && ( }} /> )} + {isAreaChart && ( + + { + setValue('fillOpacity', Number(e.currentTarget.value)); + }} + /> + + )} ); } diff --git a/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.mocks.ts b/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.mocks.ts index eb8d4d1c440d70..f23d9e4ada3361 100644 --- a/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.mocks.ts +++ b/src/plugins/vis_type_xy/public/editor/components/options/point_series/point_series.mocks.ts @@ -410,6 +410,7 @@ export const getVis = (bucketType: string) => { drawLinesBetweenPoints: true, lineWidth: 2, showCircles: true, + circlesRadius: 3, interpolate: 'linear', valueAxis: 'ValueAxis-1', }, @@ -838,6 +839,7 @@ export const getStateParams = (type: string, thresholdPanelOn: boolean) => { }, drawLinesBetweenPoints: true, showCircles: true, + circlesRadius: 3, interpolate: 'cardinal', valueAxis: 'ValueAxis-1', }, diff --git a/src/plugins/vis_type_xy/public/expression_functions/series_param.ts b/src/plugins/vis_type_xy/public/expression_functions/series_param.ts index 402187cea65866..3fd62e33e257fe 100644 --- a/src/plugins/vis_type_xy/public/expression_functions/series_param.ts +++ b/src/plugins/vis_type_xy/public/expression_functions/series_param.ts @@ -29,6 +29,7 @@ export type ExpressionValueSeriesParam = ExpressionValueBoxed< mode: SeriesParam['mode']; show: boolean; showCircles: boolean; + circlesRadius: number; seriesParamType: SeriesParam['type']; valueAxis: string; } @@ -98,6 +99,12 @@ export const seriesParam = (): ExpressionFunctionDefinition< defaultMessage: 'Show circles', }), }, + circlesRadius: { + types: ['number'], + help: i18n.translate('visTypeXy.function.seriesParam.circlesRadius.help', { + defaultMessage: 'Defines the circles size (radius)', + }), + }, type: { types: ['string'], help: i18n.translate('visTypeXy.function.seriesParam.type.help', { @@ -121,6 +128,7 @@ export const seriesParam = (): ExpressionFunctionDefinition< mode: args.mode, show: args.show, showCircles: args.showCircles, + circlesRadius: args.circlesRadius, seriesParamType: args.type, valueAxis: args.valueAxis, }; diff --git a/src/plugins/vis_type_xy/public/expression_functions/xy_vis_fn.ts b/src/plugins/vis_type_xy/public/expression_functions/xy_vis_fn.ts index b8b8c0e8b8cca8..29403a12fdce63 100644 --- a/src/plugins/vis_type_xy/public/expression_functions/xy_vis_fn.ts +++ b/src/plugins/vis_type_xy/public/expression_functions/xy_vis_fn.ts @@ -161,6 +161,12 @@ export const visTypeXyVisFn = (): VisTypeXyExpressionFunctionDefinition => ({ defaultMessage: 'Defines the chart palette name', }), }, + fillOpacity: { + types: ['number'], + help: i18n.translate('visTypeXy.function.args.fillOpacity.help', { + defaultMessage: 'Defines the area chart fill opacity', + }), + }, xDimension: { types: ['xy_dimension', 'null'], help: i18n.translate('visTypeXy.function.args.xDimension.help', { @@ -242,6 +248,7 @@ export const visTypeXyVisFn = (): VisTypeXyExpressionFunctionDefinition => ({ type: 'palette', name: args.palette, }, + fillOpacity: args.fillOpacity, fittingFunction: args.fittingFunction, dimensions: { x: args.xDimension, diff --git a/src/plugins/vis_type_xy/public/sample_vis.test.mocks.ts b/src/plugins/vis_type_xy/public/sample_vis.test.mocks.ts index e15f9c42077020..39370d941b52ac 100644 --- a/src/plugins/vis_type_xy/public/sample_vis.test.mocks.ts +++ b/src/plugins/vis_type_xy/public/sample_vis.test.mocks.ts @@ -1397,6 +1397,7 @@ export const sampleAreaVis = { drawLinesBetweenPoints: true, lineWidth: 2, showCircles: true, + circlesRadius: 3, interpolate: 'linear', valueAxis: 'ValueAxis-1', }, @@ -1417,6 +1418,7 @@ export const sampleAreaVis = { palette: { name: 'default', }, + fillOpacity: 0.5, }, }, editorConfig: { @@ -1562,6 +1564,7 @@ export const sampleAreaVis = { }, drawLinesBetweenPoints: true, showCircles: true, + circlesRadius: 5, interpolate: 'linear', valueAxis: 'ValueAxis-1', }, diff --git a/src/plugins/vis_type_xy/public/to_ast.ts b/src/plugins/vis_type_xy/public/to_ast.ts index c0a0ee566a4453..f473cd77c2d2b7 100644 --- a/src/plugins/vis_type_xy/public/to_ast.ts +++ b/src/plugins/vis_type_xy/public/to_ast.ts @@ -98,6 +98,7 @@ const prepareSeriesParam = (data: SeriesParam) => { mode: data.mode, show: data.show, showCircles: data.showCircles, + circlesRadius: data.circlesRadius, type: data.type, valueAxis: data.valueAxis, }); @@ -207,6 +208,7 @@ export const toExpressionAst: VisToExpressionAst = async (vis, params fittingFunction: vis.params.fittingFunction, times: vis.params.times.map(prepareTimeMarker), palette: vis.params.palette.name, + fillOpacity: vis.params.fillOpacity, xDimension: dimensions.x ? prepareXYDimension(dimensions.x) : null, yDimension: dimensions.y.map(prepareXYDimension), zDimension: dimensions.z?.map(prepareXYDimension), diff --git a/src/plugins/vis_type_xy/public/types/config.ts b/src/plugins/vis_type_xy/public/types/config.ts index f025a36a82410a..e52b47366bc857 100644 --- a/src/plugins/vis_type_xy/public/types/config.ts +++ b/src/plugins/vis_type_xy/public/types/config.ts @@ -116,6 +116,7 @@ export interface VisConfig { showValueLabel: boolean; enableHistogramMode: boolean; fittingFunction?: Exclude; + fillOpacity?: number; detailedTooltip?: boolean; isVislibVis?: boolean; } diff --git a/src/plugins/vis_type_xy/public/types/param.ts b/src/plugins/vis_type_xy/public/types/param.ts index f90899620126aa..7a2ff7e2402640 100644 --- a/src/plugins/vis_type_xy/public/types/param.ts +++ b/src/plugins/vis_type_xy/public/types/param.ts @@ -78,6 +78,7 @@ export interface SeriesParam { mode: ChartMode; show: boolean; showCircles: boolean; + circlesRadius: number; type: ChartType; valueAxis: string; } @@ -155,6 +156,7 @@ export interface VisParams { */ detailedTooltip?: boolean; palette: PaletteOutput; + fillOpacity?: number; fittingFunction?: Exclude; } @@ -186,6 +188,7 @@ export interface XYVisConfig { */ detailedTooltip?: boolean; fittingFunction?: Exclude; + fillOpacity?: number; xDimension: ExpressionValueXYDimension | null; yDimension: ExpressionValueXYDimension[]; zDimension?: ExpressionValueXYDimension[]; diff --git a/src/plugins/vis_type_xy/public/utils/render_all_series.test.tsx b/src/plugins/vis_type_xy/public/utils/render_all_series.test.tsx index 628d3620090ca1..23dabef662d559 100644 --- a/src/plugins/vis_type_xy/public/utils/render_all_series.test.tsx +++ b/src/plugins/vis_type_xy/public/utils/render_all_series.test.tsx @@ -31,6 +31,7 @@ const defaultSeriesParams = [ mode: 'stacked', show: true, showCircles: true, + circlesRadius: 3, type: 'area', valueAxis: 'ValueAxis-1', }, diff --git a/src/plugins/vis_type_xy/public/utils/render_all_series.tsx b/src/plugins/vis_type_xy/public/utils/render_all_series.tsx index 3bce5ddc2e85e6..e915e6d4966c50 100644 --- a/src/plugins/vis_type_xy/public/utils/render_all_series.tsx +++ b/src/plugins/vis_type_xy/public/utils/render_all_series.tsx @@ -51,7 +51,15 @@ const getCurveType = (type?: 'linear' | 'cardinal' | 'step-after'): CurveType => * @param getSeriesColor */ export const renderAllSeries = ( - { aspects, yAxes, xAxis, showValueLabel, enableHistogramMode, fittingFunction }: VisConfig, + { + aspects, + yAxes, + xAxis, + showValueLabel, + enableHistogramMode, + fittingFunction, + fillOpacity, + }: VisConfig, seriesParams: SeriesParam[], data: DatatableRow[], getSeriesName: (series: XYChartSeriesIdentifier) => SeriesName, @@ -67,6 +75,7 @@ export const renderAllSeries = ( data: { id: paramId }, lineWidth: strokeWidth, showCircles, + circlesRadius, drawLinesBetweenPoints, mode, interpolate, @@ -158,7 +167,7 @@ export const renderAllSeries = ( stackMode={stackMode} areaSeriesStyle={{ area: { - ...(type === ChartType.Line && { opacity: 0 }), + ...(type === ChartType.Line ? { opacity: 0 } : { opacity: fillOpacity }), }, line: { strokeWidth, @@ -167,6 +176,7 @@ export const renderAllSeries = ( point: { visible: showCircles, fill: markSizeAccessor ? ColorVariant.Series : undefined, + radius: circlesRadius, }, }} /> diff --git a/src/plugins/vis_type_xy/public/vis_types/area.ts b/src/plugins/vis_type_xy/public/vis_types/area.ts index f22f8df1752d66..912b3d8d48e952 100644 --- a/src/plugins/vis_type_xy/public/vis_types/area.ts +++ b/src/plugins/vis_type_xy/public/vis_types/area.ts @@ -98,6 +98,7 @@ export const getAreaVisTypeDefinition = ( drawLinesBetweenPoints: true, lineWidth: 2, showCircles: true, + circlesRadius: 3, interpolate: InterpolationMode.Linear, valueAxis: 'ValueAxis-1', }, diff --git a/src/plugins/vis_type_xy/public/vis_types/histogram.ts b/src/plugins/vis_type_xy/public/vis_types/histogram.ts index 732833ffecc802..9af4cfd7b43a3e 100644 --- a/src/plugins/vis_type_xy/public/vis_types/histogram.ts +++ b/src/plugins/vis_type_xy/public/vis_types/histogram.ts @@ -102,6 +102,7 @@ export const getHistogramVisTypeDefinition = ( drawLinesBetweenPoints: true, lineWidth: 2, showCircles: true, + circlesRadius: 3, }, ], radiusRatio: 0, diff --git a/src/plugins/vis_type_xy/public/vis_types/horizontal_bar.ts b/src/plugins/vis_type_xy/public/vis_types/horizontal_bar.ts index 791d93bb646b23..874e69b246a4d9 100644 --- a/src/plugins/vis_type_xy/public/vis_types/horizontal_bar.ts +++ b/src/plugins/vis_type_xy/public/vis_types/horizontal_bar.ts @@ -103,6 +103,7 @@ export const getHorizontalBarVisTypeDefinition = ( drawLinesBetweenPoints: true, lineWidth: 2, showCircles: true, + circlesRadius: 3, }, ], addTooltip: true, diff --git a/src/plugins/vis_type_xy/public/vis_types/line.ts b/src/plugins/vis_type_xy/public/vis_types/line.ts index 6316fe44582290..2e8944f44daab8 100644 --- a/src/plugins/vis_type_xy/public/vis_types/line.ts +++ b/src/plugins/vis_type_xy/public/vis_types/line.ts @@ -100,6 +100,7 @@ export const getLineVisTypeDefinition = ( lineWidth: 2, interpolate: InterpolationMode.Linear, showCircles: true, + circlesRadius: 3, }, ], addTooltip: true, diff --git a/test/common/ftr_provider_context.d.ts b/test/common/ftr_provider_context.ts similarity index 76% rename from test/common/ftr_provider_context.d.ts rename to test/common/ftr_provider_context.ts index 91d35a2dbc32a6..6d21aedfe1d5ec 100644 --- a/test/common/ftr_provider_context.d.ts +++ b/test/common/ftr_provider_context.ts @@ -6,8 +6,9 @@ * Side Public License, v 1. */ -import { GenericFtrProviderContext } from '@kbn/test'; +import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test'; import { services } from './services'; export type FtrProviderContext = GenericFtrProviderContext; +export class FtrService extends GenericFtrService {} diff --git a/test/common/services/deployment.ts b/test/common/services/deployment.ts index 65466ca966ad25..b250d39ce65d65 100644 --- a/test/common/services/deployment.ts +++ b/test/common/services/deployment.ts @@ -10,39 +10,37 @@ import { get } from 'lodash'; import fetch from 'node-fetch'; import { getUrl } from '@kbn/test'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function DeploymentProvider({ getService }: FtrProviderContext) { - const config = getService('config'); +export class DeploymentService extends FtrService { + private readonly config = this.ctx.getService('config'); - return { - /** - * Returns Kibana host URL - */ - getHostPort() { - return getUrl.baseUrl(config.get('servers.kibana')); - }, + /** + * Returns Kibana host URL + */ + getHostPort() { + return getUrl.baseUrl(this.config.get('servers.kibana')); + } - /** - * Returns ES host URL - */ - getEsHostPort() { - return getUrl.baseUrl(config.get('servers.elasticsearch')); - }, + /** + * Returns ES host URL + */ + getEsHostPort() { + return getUrl.baseUrl(this.config.get('servers.elasticsearch')); + } - async isCloud(): Promise { - const baseUrl = this.getHostPort(); - const username = config.get('servers.kibana.username'); - const password = config.get('servers.kibana.password'); - const response = await fetch(baseUrl + '/api/stats?extended', { - method: 'get', - headers: { - 'Content-Type': 'application/json', - Authorization: 'Basic ' + Buffer.from(username + ':' + password).toString('base64'), - }, - }); - const data = await response.json(); - return get(data, 'usage.cloud.is_cloud_enabled', false); - }, - }; + async isCloud(): Promise { + const baseUrl = this.getHostPort(); + const username = this.config.get('servers.kibana.username'); + const password = this.config.get('servers.kibana.password'); + const response = await fetch(baseUrl + '/api/stats?extended', { + method: 'get', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Basic ' + Buffer.from(username + ':' + password).toString('base64'), + }, + }); + const data = await response.json(); + return get(data, 'usage.cloud.is_cloud_enabled', false); + } } diff --git a/test/common/services/index.ts b/test/common/services/index.ts index cc4859b7016bf3..02aafc59fa80f4 100644 --- a/test/common/services/index.ts +++ b/test/common/services/index.ts @@ -6,26 +6,26 @@ * Side Public License, v 1. */ -import { DeploymentProvider } from './deployment'; +import { DeploymentService } from './deployment'; import { LegacyEsProvider } from './legacy_es'; import { ElasticsearchProvider } from './elasticsearch'; import { EsArchiverProvider } from './es_archiver'; import { KibanaServerProvider } from './kibana_server'; -import { RetryProvider } from './retry'; -import { RandomnessProvider } from './randomness'; +import { RetryService } from './retry'; +import { RandomnessService } from './randomness'; import { SecurityServiceProvider } from './security'; import { EsDeleteAllIndicesProvider } from './es_delete_all_indices'; -import { SavedObjectInfoProvider } from './saved_object_info'; +import { SavedObjectInfoService } from './saved_object_info'; export const services = { - deployment: DeploymentProvider, + deployment: DeploymentService, legacyEs: LegacyEsProvider, es: ElasticsearchProvider, esArchiver: EsArchiverProvider, kibanaServer: KibanaServerProvider, - retry: RetryProvider, - randomness: RandomnessProvider, + retry: RetryService, + randomness: RandomnessService, security: SecurityServiceProvider, esDeleteAllIndices: EsDeleteAllIndicesProvider, - savedObjectInfo: SavedObjectInfoProvider, + savedObjectInfo: SavedObjectInfoService, }; diff --git a/test/common/services/kibana_server/kibana_server.ts b/test/common/services/kibana_server/kibana_server.ts index f366a864db980d..63803bd511bd14 100644 --- a/test/common/services/kibana_server/kibana_server.ts +++ b/test/common/services/kibana_server/kibana_server.ts @@ -11,7 +11,7 @@ import { KbnClient } from '@kbn/test'; import { FtrProviderContext } from '../../ftr_provider_context'; -export function KibanaServerProvider({ getService }: FtrProviderContext) { +export function KibanaServerProvider({ getService }: FtrProviderContext): KbnClient { const log = getService('log'); const config = getService('config'); const lifecycle = getService('lifecycle'); diff --git a/test/common/services/randomness.ts b/test/common/services/randomness.ts index 88b0411f98033e..82f06fb681066a 100644 --- a/test/common/services/randomness.ts +++ b/test/common/services/randomness.ts @@ -7,8 +7,20 @@ */ import Chance from 'chance'; +import { ToolingLog } from '@kbn/dev-utils'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; + +let __CACHED_SEED__: number | undefined; +function getSeed(log: ToolingLog) { + if (__CACHED_SEED__ !== undefined) { + return __CACHED_SEED__; + } + + __CACHED_SEED__ = Date.now(); + log.debug('randomness seed: %j', __CACHED_SEED__); + return __CACHED_SEED__; +} interface CharOptions { pool?: string; @@ -27,52 +39,45 @@ interface NumberOptions { max?: number; } -export function RandomnessProvider({ getService }: FtrProviderContext) { - const log = getService('log'); - - const seed = Date.now(); - log.debug('randomness seed: %j', seed); - - const chance = new Chance(seed); +export class RandomnessService extends FtrService { + private readonly chance = new Chance(getSeed(this.ctx.getService('log'))); - return new (class RandomnessService { - /** - * Generate a random natural number - * - * range: 0 to 9007199254740991 - * - */ - naturalNumber(options?: NumberOptions) { - return chance.natural(options); - } + /** + * Generate a random natural number + * + * range: 0 to 9007199254740991 + * + */ + naturalNumber(options?: NumberOptions) { + return this.chance.natural(options); + } - /** - * Generate a random integer - */ - integer(options?: NumberOptions) { - return chance.integer(options); - } + /** + * Generate a random integer + */ + integer(options?: NumberOptions) { + return this.chance.integer(options); + } - /** - * Generate a random number, defaults to at least 4 and no more than 8 syllables - */ - word(options: { syllables?: number } = {}) { - const { syllables = this.naturalNumber({ min: 4, max: 8 }) } = options; + /** + * Generate a random number, defaults to at least 4 and no more than 8 syllables + */ + word(options: { syllables?: number } = {}) { + const { syllables = this.naturalNumber({ min: 4, max: 8 }) } = options; - return chance.word({ - syllables, - }); - } + return this.chance.word({ + syllables, + }); + } - /** - * Generate a random string, defaults to at least 8 and no more than 15 alpha-numeric characters - */ - string(options: StringOptions = {}) { - return chance.string({ - length: this.naturalNumber({ min: 8, max: 15 }), - ...(options.pool === 'undefined' ? { alpha: true, numeric: true, symbols: false } : {}), - ...options, - }); - } - })(); + /** + * Generate a random string, defaults to at least 8 and no more than 15 alpha-numeric characters + */ + string(options: StringOptions = {}) { + return this.chance.string({ + length: this.naturalNumber({ min: 8, max: 15 }), + ...(options.pool === 'undefined' ? { alpha: true, numeric: true, symbols: false } : {}), + ...options, + }); + } } diff --git a/test/common/services/retry/index.ts b/test/common/services/retry/index.ts index 4914b3cff2261e..08ce3f9bd46611 100644 --- a/test/common/services/retry/index.ts +++ b/test/common/services/retry/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { RetryProvider } from './retry'; +export { RetryService } from './retry'; diff --git a/test/common/services/retry/retry.ts b/test/common/services/retry/retry.ts index 8ea2a52b6adf69..5c823e256ddc8d 100644 --- a/test/common/services/retry/retry.ts +++ b/test/common/services/retry/retry.ts @@ -6,64 +6,62 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrService } from '../../ftr_provider_context'; import { retryForSuccess } from './retry_for_success'; import { retryForTruthy } from './retry_for_truthy'; -export function RetryProvider({ getService }: FtrProviderContext) { - const config = getService('config'); - const log = getService('log'); +export class RetryService extends FtrService { + private readonly config = this.ctx.getService('config'); + private readonly log = this.ctx.getService('log'); - return new (class Retry { - public async tryForTime( - timeout: number, - block: () => Promise, - onFailureBlock?: () => Promise - ) { - return await retryForSuccess(log, { - timeout, - methodName: 'retry.tryForTime', - block, - onFailureBlock, - }); - } + public async tryForTime( + timeout: number, + block: () => Promise, + onFailureBlock?: () => Promise + ) { + return await retryForSuccess(this.log, { + timeout, + methodName: 'retry.tryForTime', + block, + onFailureBlock, + }); + } - public async try(block: () => Promise, onFailureBlock?: () => Promise) { - return await retryForSuccess(log, { - timeout: config.get('timeouts.try'), - methodName: 'retry.try', - block, - onFailureBlock, - }); - } + public async try(block: () => Promise, onFailureBlock?: () => Promise) { + return await retryForSuccess(this.log, { + timeout: this.config.get('timeouts.try'), + methodName: 'retry.try', + block, + onFailureBlock, + }); + } - public async waitForWithTimeout( - description: string, - timeout: number, - block: () => Promise, - onFailureBlock?: () => Promise - ) { - await retryForTruthy(log, { - timeout, - methodName: 'retry.waitForWithTimeout', - description, - block, - onFailureBlock, - }); - } + public async waitForWithTimeout( + description: string, + timeout: number, + block: () => Promise, + onFailureBlock?: () => Promise + ) { + await retryForTruthy(this.log, { + timeout, + methodName: 'retry.waitForWithTimeout', + description, + block, + onFailureBlock, + }); + } - public async waitFor( - description: string, - block: () => Promise, - onFailureBlock?: () => Promise - ) { - await retryForTruthy(log, { - timeout: config.get('timeouts.waitFor'), - methodName: 'retry.waitFor', - description, - block, - onFailureBlock, - }); - } - })(); + public async waitFor( + description: string, + block: () => Promise, + onFailureBlock?: () => Promise + ) { + await retryForTruthy(this.log, { + timeout: this.config.get('timeouts.waitFor'), + methodName: 'retry.waitFor', + description, + block, + onFailureBlock, + }); + } } diff --git a/test/common/services/saved_object_info.ts b/test/common/services/saved_object_info.ts index 02ab38d4ecb1db..1558b364f53916 100644 --- a/test/common/services/saved_object_info.ts +++ b/test/common/services/saved_object_info.ts @@ -6,48 +6,44 @@ * Side Public License, v 1. */ -import { Client } from '@elastic/elasticsearch'; -import url from 'url'; -import { Either, fromNullable, chain, getOrElse } from 'fp-ts/Either'; -import { flow } from 'fp-ts/function'; -import { FtrProviderContext } from '../ftr_provider_context'; - -const pluck = (key: string) => (obj: any): Either => - fromNullable(new Error(`Missing ${key}`))(obj[key]); - -const types = (node: string) => async (index: string = '.kibana') => { - let res: unknown; - try { - const { body } = await new Client({ node }).search({ - index, - body: { - aggs: { - savedobjs: { - terms: { - field: 'type', +import { inspect } from 'util'; + +import { TermsAggregate } from '@elastic/elasticsearch/api/types'; + +import { FtrService } from '../ftr_provider_context'; + +export class SavedObjectInfoService extends FtrService { + private readonly es = this.ctx.getService('es'); + + public async getTypes(index = '.kibana') { + try { + const { body } = await this.es.search({ + index, + size: 0, + body: { + aggs: { + savedobjs: { + terms: { + field: 'type', + }, }, }, }, - }, - }); - - res = flow( - pluck('aggregations'), - chain(pluck('savedobjs')), - chain(pluck('buckets')), - getOrElse((err) => `${err.message}`) - )(body); - } catch (err) { - throw new Error(`Error while searching for saved object types: ${err}`); - } + }); - return res; -}; + const agg = body.aggregations?.savedobjs as + | TermsAggregate<{ key: string; doc_count: number }> + | undefined; -export const SavedObjectInfoProvider: any = ({ getService }: FtrProviderContext) => { - const config = getService('config'); + if (!agg?.buckets) { + throw new Error( + `expected es to return buckets of saved object types: ${inspect(body, { depth: 100 })}` + ); + } - return { - types: types(url.format(config.get('servers.elasticsearch'))), - }; -}; + return agg.buckets; + } catch (error) { + throw new Error(`Error while searching for saved object types: ${error}`); + } + } +} diff --git a/test/common/services/security/security.ts b/test/common/services/security/security.ts index 52fb6bdd70330e..b8fea0a0c59b26 100644 --- a/test/common/services/security/security.ts +++ b/test/common/services/security/security.ts @@ -10,23 +10,27 @@ import { Role } from './role'; import { User } from './user'; import { RoleMappings } from './role_mappings'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { createTestUserService, TestUserSupertestProvider } from './test_user'; +import { createTestUserService, TestUserSupertestProvider, TestUser } from './test_user'; -export async function SecurityServiceProvider(context: FtrProviderContext) { - const { getService } = context; - const log = getService('log'); - const kibanaServer = getService('kibanaServer'); +export class SecurityService { + constructor( + public readonly roleMappings: RoleMappings, + public readonly testUser: TestUser, + public readonly role: Role, + public readonly user: User, + public readonly testUserSupertest: ReturnType + ) {} +} + +export async function SecurityServiceProvider(ctx: FtrProviderContext) { + const log = ctx.getService('log'); + const kibanaServer = ctx.getService('kibanaServer'); const role = new Role(log, kibanaServer); const user = new User(log, kibanaServer); - const testUser = await createTestUserService(role, user, context); - const testUserSupertest = TestUserSupertestProvider(context); + const testUser = await createTestUserService(ctx, role, user); + const testUserSupertest = TestUserSupertestProvider(ctx); + const roleMappings = new RoleMappings(log, kibanaServer); - return new (class SecurityService { - roleMappings = new RoleMappings(log, kibanaServer); - testUser = testUser; - role = role; - user = user; - testUserSupertest = testUserSupertest; - })(); + return new SecurityService(roleMappings, testUser, role, user, testUserSupertest); } diff --git a/test/common/services/security/test_user.ts b/test/common/services/security/test_user.ts index d5e1f02e1bc8ca..8b0a1c34e790cb 100644 --- a/test/common/services/security/test_user.ts +++ b/test/common/services/security/test_user.ts @@ -11,41 +11,84 @@ import supertestAsPromised from 'supertest-as-promised'; import { Role } from './role'; import { User } from './user'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrService, FtrProviderContext } from '../../ftr_provider_context'; import { Browser } from '../../../functional/services/common'; import { TestSubjects } from '../../../functional/services/common'; const TEST_USER_NAME = 'test_user'; const TEST_USER_PASSWORD = 'changeme'; -export async function createTestUserService( - role: Role, - user: User, - { getService, hasService }: FtrProviderContext -) { - const log = getService('log'); - const config = getService('config'); - // @ts-ignore browser service is not normally available in common. - const browser: Browser | void = hasService('browser') && getService('browser'); - const testSubjects: TestSubjects | undefined = +export class TestUser extends FtrService { + private readonly config = this.ctx.getService('config'); + private readonly log = this.ctx.getService('log'); + + private readonly browser: Browser | void = + // browser service is not normally available in common. + this.ctx.hasService('browser') ? (this.ctx.getService('browser' as any) as Browser) : undefined; + + private readonly testSubjects: TestSubjects | undefined = // testSubject service is not normally available in common. - hasService('testSubjects') ? (getService('testSubjects' as any) as TestSubjects) : undefined; - const kibanaServer = getService('kibanaServer'); + this.ctx.hasService('testSubjects') + ? (this.ctx.getService('testSubjects' as any) as TestSubjects) + : undefined; + + constructor( + ctx: FtrProviderContext, + private readonly enabled: boolean, + private readonly user: User + ) { + super(ctx); + } + + async restoreDefaults(shouldRefreshBrowser: boolean = true) { + if (this.enabled) { + await this.setRoles(this.config.get('security.defaultRoles'), shouldRefreshBrowser); + } + } + + async setRoles(roles: string[], shouldRefreshBrowser: boolean = true) { + if (this.enabled) { + this.log.debug(`set roles = ${roles}`); + await this.user.create(TEST_USER_NAME, { + password: TEST_USER_PASSWORD, + roles, + full_name: 'test user', + }); + + if (this.browser && this.testSubjects && shouldRefreshBrowser) { + if (await this.testSubjects.exists('kibanaChrome', { allowHidden: true })) { + await this.browser.refresh(); + // accept alert if it pops up + const alert = await this.browser.getAlert(); + await alert?.accept(); + await this.testSubjects.find('kibanaChrome', this.config.get('timeouts.find') * 10); + } + } + } + } +} + +export async function createTestUserService(ctx: FtrProviderContext, role: Role, user: User) { + const log = ctx.getService('log'); + const config = ctx.getService('config'); + const kibanaServer = ctx.getService('kibanaServer'); const enabledPlugins = config.get('security.disableTestUser') ? [] : await kibanaServer.plugins.getEnabledIds(); - const isEnabled = () => { - return enabledPlugins.includes('security') && !config.get('security.disableTestUser'); - }; - if (isEnabled()) { + + const enabled = enabledPlugins.includes('security') && !config.get('security.disableTestUser'); + + if (enabled) { log.debug('===============creating roles and users==============='); + + // create the defined roles (need to map array to create roles) for (const [name, definition] of Object.entries(config.get('security.roles'))) { - // create the defined roles (need to map array to create roles) await role.create(name, definition); } + + // delete the test_user if present (will it error if the user doesn't exist?) try { - // delete the test_user if present (will it error if the user doesn't exist?) await user.delete(TEST_USER_NAME); } catch (exception) { log.debug('no test user to delete'); @@ -60,34 +103,7 @@ export async function createTestUserService( }); } - return new (class TestUser { - async restoreDefaults(shouldRefreshBrowser: boolean = true) { - if (isEnabled()) { - await this.setRoles(config.get('security.defaultRoles'), shouldRefreshBrowser); - } - } - - async setRoles(roles: string[], shouldRefreshBrowser: boolean = true) { - if (isEnabled()) { - log.debug(`set roles = ${roles}`); - await user.create(TEST_USER_NAME, { - password: TEST_USER_PASSWORD, - roles, - full_name: 'test user', - }); - - if (browser && testSubjects && shouldRefreshBrowser) { - if (await testSubjects.exists('kibanaChrome', { allowHidden: true })) { - await browser.refresh(); - // accept alert if it pops up - const alert = await browser.getAlert(); - await alert?.accept(); - await testSubjects.find('kibanaChrome', config.get('timeouts.find') * 10); - } - } - } - } - })(); + return new TestUser(ctx, enabled, user); } export function TestUserSupertestProvider({ getService }: FtrProviderContext) { diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index eb224b3c9b8798..b87184bab3c0d7 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../../ftr_provider_context.d'; +import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, loadTestFile }: FtrProviderContext) { const browser = getService('browser'); diff --git a/test/functional/apps/visualize/legacy/index.ts b/test/functional/apps/visualize/legacy/index.ts index 187e8f3f3a663c..914559e5cea925 100644 --- a/test/functional/apps/visualize/legacy/index.ts +++ b/test/functional/apps/visualize/legacy/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../../../ftr_provider_context.d'; +import { FtrProviderContext } from '../../../ftr_provider_context'; import { UI_SETTINGS } from '../../../../../src/plugins/data/common'; export default function ({ getPageObjects, getService, loadTestFile }: FtrProviderContext) { diff --git a/test/functional/ftr_provider_context.d.ts b/test/functional/ftr_provider_context.ts similarity index 78% rename from test/functional/ftr_provider_context.d.ts rename to test/functional/ftr_provider_context.ts index 4c827393e1ef3b..a1a29f50b77611 100644 --- a/test/functional/ftr_provider_context.d.ts +++ b/test/functional/ftr_provider_context.ts @@ -6,9 +6,10 @@ * Side Public License, v 1. */ -import { GenericFtrProviderContext } from '@kbn/test'; +import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test'; import { pageObjects } from './page_objects'; import { services } from './services'; export type FtrProviderContext = GenericFtrProviderContext; +export class FtrService extends GenericFtrService {} diff --git a/test/functional/page_objects/time_picker.ts b/test/functional/page_objects/time_picker.ts index cfe250831e06cc..d3b6edaffdbd32 100644 --- a/test/functional/page_objects/time_picker.ts +++ b/test/functional/page_objects/time_picker.ts @@ -7,7 +7,7 @@ */ import moment from 'moment'; -import { FtrProviderContext } from '../ftr_provider_context.d'; +import { FtrProviderContext } from '../ftr_provider_context'; import { WebElementWrapper } from '../services/lib/web_element_wrapper'; export type CommonlyUsed = diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 3ed5d74808fce5..997a1127005ee5 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../ftr_provider_context.d'; +import { FtrProviderContext } from '../ftr_provider_context'; import { WebElementWrapper } from '../services/lib/web_element_wrapper'; export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrProviderContext) { diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx index 9410fd00411e38..89b8db5f386dcd 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx @@ -173,7 +173,7 @@ export const routes: APMRouteDefinition[] = [ render: renderAsRedirectTo('/services'), breadcrumb: 'APM', }, - // !! Need to be kept in sync with the searchDeepLinks in x-pack/plugins/apm/public/plugin.ts + // !! Need to be kept in sync with the deepLinks in x-pack/plugins/apm/public/plugin.ts { exact: true, path: '/services', @@ -182,7 +182,7 @@ export const routes: APMRouteDefinition[] = [ defaultMessage: 'Services', }), }, - // !! Need to be kept in sync with the searchDeepLinks in x-pack/plugins/apm/public/plugin.ts + // !! Need to be kept in sync with the deepLinks in x-pack/plugins/apm/public/plugin.ts { exact: true, path: '/traces', @@ -328,7 +328,7 @@ export const routes: APMRouteDefinition[] = [ component: TraceLink, breadcrumb: null, }, - // !! Need to be kept in sync with the searchDeepLinks in x-pack/plugins/apm/public/plugin.ts + // !! Need to be kept in sync with the deepLinks in x-pack/plugins/apm/public/plugin.ts { exact: true, path: '/service-map', diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index b76849ccf30115..10af1837dab42f 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -140,32 +140,30 @@ export class ApmPlugin implements Plugin { appRoute: '/app/apm', icon: 'plugins/apm/public/icon.svg', category: DEFAULT_APP_CATEGORIES.observability, - meta: { - // !! Need to be kept in sync with the routes in x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx - searchDeepLinks: [ - { - id: 'services', - title: i18n.translate('xpack.apm.breadcrumb.servicesTitle', { - defaultMessage: 'Services', - }), - path: '/services', - }, - { - id: 'traces', - title: i18n.translate('xpack.apm.breadcrumb.tracesTitle', { - defaultMessage: 'Traces', - }), - path: '/traces', - }, - { - id: 'service-map', - title: i18n.translate('xpack.apm.breadcrumb.serviceMapTitle', { - defaultMessage: 'Service Map', - }), - path: '/service-map', - }, - ], - }, + // !! Need to be kept in sync with the routes in x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx + deepLinks: [ + { + id: 'services', + title: i18n.translate('xpack.apm.breadcrumb.servicesTitle', { + defaultMessage: 'Services', + }), + path: '/services', + }, + { + id: 'traces', + title: i18n.translate('xpack.apm.breadcrumb.tracesTitle', { + defaultMessage: 'Traces', + }), + path: '/traces', + }, + { + id: 'service-map', + title: i18n.translate('xpack.apm.breadcrumb.serviceMapTitle', { + defaultMessage: 'Service Map', + }), + path: '/service-map', + }, + ], async mount(appMountParameters: AppMountParameters) { // Load application bundle and Get start services @@ -196,24 +194,22 @@ export class ApmPlugin implements Plugin { navLinkStatus: config.ui.enabled ? AppNavLinkStatus.default : AppNavLinkStatus.hidden, - meta: { - keywords: [ - 'RUM', - 'Real User Monitoring', - 'DEM', - 'Digital Experience Monitoring', - 'EUM', - 'End User Monitoring', - 'UX', - 'Javascript', - 'APM', - 'Mobile', - 'digital', - 'performance', - 'web performance', - 'web perf', - ], - }, + keywords: [ + 'RUM', + 'Real User Monitoring', + 'DEM', + 'Digital Experience Monitoring', + 'EUM', + 'End User Monitoring', + 'UX', + 'Javascript', + 'APM', + 'Mobile', + 'digital', + 'performance', + 'web performance', + 'web perf', + ], async mount(appMountParameters: AppMountParameters) { // Load application bundle and Get start service const [{ renderApp }, [coreStart, corePlugins]] = await Promise.all([ diff --git a/x-pack/plugins/global_search_providers/public/providers/application.test.ts b/x-pack/plugins/global_search_providers/public/providers/application.test.ts index 9b084d7bb9a6ad..2d555f38d16d1e 100644 --- a/x-pack/plugins/global_search_providers/public/providers/application.test.ts +++ b/x-pack/plugins/global_search_providers/public/providers/application.test.ts @@ -29,10 +29,8 @@ const createApp = (props: Partial = {}): PublicAppInfo => ({ status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.visible, chromeless: false, - meta: { - keywords: props.meta?.keywords || [], - searchDeepLinks: [], - }, + keywords: props.keywords || [], + deepLinks: [], ...props, }); diff --git a/x-pack/plugins/global_search_providers/public/providers/get_app_results.test.ts b/x-pack/plugins/global_search_providers/public/providers/get_app_results.test.ts index 8b875dbb7ed9bc..251dd84395aa00 100644 --- a/x-pack/plugins/global_search_providers/public/providers/get_app_results.test.ts +++ b/x-pack/plugins/global_search_providers/public/providers/get_app_results.test.ts @@ -26,7 +26,8 @@ const createApp = (props: Partial = {}): PublicAppInfo => ({ status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.visible, chromeless: false, - meta: { keywords: [], searchDeepLinks: [] }, + keywords: [], + deepLinks: [], ...props, }); @@ -34,7 +35,7 @@ const createAppLink = (props: Partial = {}): AppLink => ({ id: props.id ?? 'app1', path: props.appRoute ?? '/app/app1', subLinkTitles: [], - keywords: props.meta?.keywords ?? [], // start off with the top level app keywords + keywords: props.keywords ?? [], // start off with the top level app keywords app: createApp(props), }); @@ -51,30 +52,37 @@ describe('getAppResults', () => { expect(results[0]).toEqual(expect.objectContaining({ id: 'dashboard', score: 100 })); }); - it('creates multiple links for apps with searchDeepLinks', () => { + it('creates multiple links for apps with deepLinks', () => { const apps = [ createApp({ - meta: { - searchDeepLinks: [ - { id: 'sub1', title: 'Sub1', path: '/sub1', searchDeepLinks: [], keywords: [] }, - { - id: 'sub2', - title: 'Sub2', - path: '/sub2', - searchDeepLinks: [ - { - id: 'sub2sub1', - title: 'Sub2Sub1', - path: '/sub2/sub1', - searchDeepLinks: [], - keywords: [], - }, - ], - keywords: [], - }, - ], - keywords: [], - }, + deepLinks: [ + { + id: 'sub1', + title: 'Sub1', + path: '/sub1', + deepLinks: [], + keywords: [], + navLinkStatus: AppNavLinkStatus.hidden, + }, + { + id: 'sub2', + title: 'Sub2', + path: '/sub2', + deepLinks: [ + { + id: 'sub2sub1', + title: 'Sub2Sub1', + path: '/sub2/sub1', + deepLinks: [], + keywords: [], + navLinkStatus: AppNavLinkStatus.hidden, + }, + ], + keywords: [], + navLinkStatus: AppNavLinkStatus.hidden, + }, + ], + keywords: [], }), ]; @@ -89,21 +97,20 @@ describe('getAppResults', () => { ]); }); - it('only includes searchDeepLinks when search term is non-empty', () => { + it('only includes deepLinks when search term is non-empty', () => { const apps = [ createApp({ - meta: { - searchDeepLinks: [ - { - id: 'sub1', - title: 'Sub1', - path: '/sub1', - searchDeepLinks: [], - keywords: [], - }, - ], - keywords: [], - }, + deepLinks: [ + { + id: 'sub1', + title: 'Sub1', + path: '/sub1', + deepLinks: [], + keywords: [], + navLinkStatus: AppNavLinkStatus.hidden, + }, + ], + keywords: [], }), ]; @@ -112,11 +119,7 @@ describe('getAppResults', () => { }); it('retrieves the matching results from keywords', () => { - const apps = [ - createApp({ - meta: { searchDeepLinks: [], keywords: ['One'] }, - }), - ]; + const apps = [createApp({ deepLinks: [], keywords: ['One'] })]; const results = getAppResults('One', apps); expect(results.map(({ title }) => title)).toEqual(['App 1']); }); @@ -124,27 +127,34 @@ describe('getAppResults', () => { it('retrieves the matching results from deeplink keywords', () => { const apps = [ createApp({ - meta: { - searchDeepLinks: [ - { id: 'sub1', title: 'Sub1', path: '/sub1', searchDeepLinks: [], keywords: [] }, - { - id: 'sub2', - title: 'Sub2', - path: '/sub2', - searchDeepLinks: [ - { - id: 'sub2sub1', - title: 'Sub2Sub1', - path: '/sub2/sub1', - searchDeepLinks: [], - keywords: ['TwoOne'], - }, - ], - keywords: ['two'], - }, - ], - keywords: [], - }, + deepLinks: [ + { + id: 'sub1', + title: 'Sub1', + path: '/sub1', + deepLinks: [], + keywords: [], + navLinkStatus: AppNavLinkStatus.hidden, + }, + { + id: 'sub2', + title: 'Sub2', + path: '/sub2', + deepLinks: [ + { + id: 'sub2sub1', + title: 'Sub2Sub1', + path: '/sub2/sub1', + deepLinks: [], + keywords: ['TwoOne'], + navLinkStatus: AppNavLinkStatus.hidden, + }, + ], + keywords: ['two'], + navLinkStatus: AppNavLinkStatus.hidden, + }, + ], + keywords: [], }), ]; @@ -187,26 +197,17 @@ describe('scoreApp', () => { describe('when the term is included in the keywords but not in the title', () => { it(`returns 100 * ${keywordScoreWeighting} if one of the app meta keywords is an exact match`, () => { expect( - scoreApp( - 'bar', - createAppLink({ title: 'foo', meta: { keywords: ['bar'], searchDeepLinks: [] } }) - ) + scoreApp('bar', createAppLink({ title: 'foo', keywords: ['bar'], deepLinks: [] })) ).toBe(100 * keywordScoreWeighting); expect( - scoreApp( - 'bar', - createAppLink({ title: 'foo', meta: { keywords: ['BAR'], searchDeepLinks: [] } }) - ) + scoreApp('bar', createAppLink({ title: 'foo', keywords: ['BAR'], deepLinks: [] })) ).toBe(100 * keywordScoreWeighting); }); it(`returns 90 * ${keywordScoreWeighting} if any of the keywords start with the term`, () => { expect( scoreApp( 'viz', - createAppLink({ - title: 'Foo', - meta: { keywords: ['Vizualize', 'Viz view'], searchDeepLinks: [] }, - }) + createAppLink({ title: 'Foo', keywords: ['Vizualize', 'Viz view'], deepLinks: [] }) ) ).toBe(90 * keywordScoreWeighting); }); @@ -214,19 +215,13 @@ describe('scoreApp', () => { expect( scoreApp( 'board', - createAppLink({ - title: 'Foo', - meta: { keywords: ['dashboard app'], searchDeepLinks: [] }, - }) + createAppLink({ title: 'Foo', keywords: ['dashboard app'], deepLinks: [] }) ) ).toBe(75 * keywordScoreWeighting); expect( scoreApp( 'shboa', - createAppLink({ - title: 'Foo', - meta: { keywords: ['dashboard app'], searchDeepLinks: [] }, - }) + createAppLink({ title: 'Foo', keywords: ['dashboard app'], deepLinks: [] }) ) ).toBe(75 * keywordScoreWeighting); }); @@ -235,26 +230,17 @@ describe('scoreApp', () => { describe('when the term is included in the keywords and the title', () => { it('returns 100 if one of the app meta keywords and the title is an exact match', () => { expect( - scoreApp( - 'home', - createAppLink({ title: 'Home', meta: { keywords: ['home'], searchDeepLinks: [] } }) - ) + scoreApp('home', createAppLink({ title: 'Home', keywords: ['home'], deepLinks: [] })) ).toBe(100); expect( - scoreApp( - 'Home', - createAppLink({ title: 'Home', meta: { keywords: ['HOME'], searchDeepLinks: [] } }) - ) + scoreApp('Home', createAppLink({ title: 'Home', keywords: ['HOME'], deepLinks: [] })) ).toBe(100); }); it('returns 90 if either one of the keywords or the title start with the term', () => { expect( scoreApp( 'vis', - createAppLink({ - title: 'Visualize', - meta: { keywords: ['Visualise'], searchDeepLinks: [] }, - }) + createAppLink({ title: 'Visualize', keywords: ['Visualise'], deepLinks: [] }) ) ).toBe(90); }); @@ -262,19 +248,13 @@ describe('scoreApp', () => { expect( scoreApp( 'board', - createAppLink({ - title: 'Dashboard', - meta: { keywords: ['dashboard app'], searchDeepLinks: [] }, - }) + createAppLink({ title: 'Dashboard', keywords: ['dashboard app'], deepLinks: [] }) ) ).toBe(75); expect( scoreApp( 'shboa', - createAppLink({ - title: 'dashboard', - meta: { keywords: ['dashboard app'], searchDeepLinks: [] }, - }) + createAppLink({ title: 'dashboard', keywords: ['dashboard app'], deepLinks: [] }) ) ).toBe(75); }); @@ -285,19 +265,13 @@ describe('scoreApp', () => { expect( scoreApp( '0123456789', - createAppLink({ - title: '012345', - meta: { keywords: ['0345', '9987'], searchDeepLinks: [] }, - }) + createAppLink({ title: '012345', keywords: ['0345', '9987'], deepLinks: [] }) ) ).toBe(60); expect( scoreApp( '--1234567-', - createAppLink({ - title: '123456789', - meta: { keywords: ['--345--'], searchDeepLinks: [] }, - }) + createAppLink({ title: '123456789', keywords: ['--345--'], deepLinks: [] }) ) ).toBe(60); }); @@ -305,13 +279,13 @@ describe('scoreApp', () => { expect( scoreApp( '0123456789', - createAppLink({ title: '12345', meta: { keywords: ['12', '34'], searchDeepLinks: [] } }) + createAppLink({ title: '12345', keywords: ['12', '34'], deepLinks: [] }) ) ).toBe(0); expect( scoreApp( '1-2-3-4-5', - createAppLink({ title: '123456789', meta: { keywords: ['12-789'], searchDeepLinks: [] } }) + createAppLink({ title: '123456789', keywords: ['12-789'], deepLinks: [] }) ) ).toBe(0); }); diff --git a/x-pack/plugins/global_search_providers/public/providers/get_app_results.ts b/x-pack/plugins/global_search_providers/public/providers/get_app_results.ts index f5f0a2d34e91c1..3ae1a082cdebfa 100644 --- a/x-pack/plugins/global_search_providers/public/providers/get_app_results.ts +++ b/x-pack/plugins/global_search_providers/public/providers/get_app_results.ts @@ -6,10 +6,10 @@ */ import levenshtein from 'js-levenshtein'; -import { PublicAppInfo, PublicAppSearchDeepLinkInfo } from 'src/core/public'; +import { PublicAppInfo, PublicAppDeepLinkInfo } from 'src/core/public'; import { GlobalSearchProviderResult } from '../../../global_search/public'; -/** Type used internally to represent an application unrolled into its separate searchDeepLinks */ +/** Type used internally to represent an application unrolled into its separate deepLinks */ export interface AppLink { id: string; app: PublicAppInfo; @@ -27,7 +27,7 @@ export const getAppResults = ( ): GlobalSearchProviderResult[] => { return ( apps - // Unroll all searchDeepLinks, only if there is a search term + // Unroll all deepLinks, only if there is a search term .flatMap((app) => term.length > 0 ? flattenDeepLinks(app) @@ -37,7 +37,7 @@ export const getAppResults = ( app, path: app.appRoute, subLinkTitles: [], - keywords: app.meta?.keywords ?? [], + keywords: app.keywords ?? [], }, ] ) @@ -56,7 +56,7 @@ export const scoreApp = (term: string, appLink: AppLink): number => { const appScoreByTerms = scoreAppByTerms(term, title); const keywords = [ - ...appLink.app.meta.keywords.map((keyword) => keyword.toLowerCase()), + ...appLink.app.keywords.map((keyword) => keyword.toLowerCase()), ...appLink.keywords.map((keyword) => keyword.toLowerCase()), ]; const appScoreByKeywords = scoreAppByKeywords(term, keywords); @@ -115,10 +115,7 @@ export const appToResult = (appLink: AppLink, score: number): GlobalSearchProvid }; }; -const flattenDeepLinks = ( - app: PublicAppInfo, - deepLink?: PublicAppSearchDeepLinkInfo -): AppLink[] => { +const flattenDeepLinks = (app: PublicAppInfo, deepLink?: PublicAppDeepLinkInfo): AppLink[] => { if (!deepLink) { return [ { @@ -126,9 +123,9 @@ const flattenDeepLinks = ( app, path: app.appRoute, subLinkTitles: [], - keywords: app.meta?.keywords ?? [], + keywords: app?.keywords ?? [], }, - ...app.meta.searchDeepLinks.flatMap((appDeepLink) => flattenDeepLinks(app, appDeepLink)), + ...app.deepLinks.flatMap((appDeepLink) => flattenDeepLinks(app, appDeepLink)), ]; } return [ @@ -143,7 +140,7 @@ const flattenDeepLinks = ( }, ] : []), - ...deepLink.searchDeepLinks + ...deepLink.deepLinks .flatMap((deepDeepLink) => flattenDeepLinks(app, deepDeepLink)) .map((deepAppLink) => ({ ...deepAppLink, diff --git a/x-pack/plugins/infra/public/pages/logs/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/page_content.tsx index d43fe198c50770..9ae127a8eca664 100644 --- a/x-pack/plugins/infra/public/pages/logs/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/page_content.tsx @@ -40,7 +40,7 @@ export const LogsPageContent: React.FunctionComponent = () => { initialize(); }); - // !! Need to be kept in sync with the searchDeepLinks in x-pack/plugins/infra/public/plugin.ts + // !! Need to be kept in sync with the deepLinks in x-pack/plugins/infra/public/plugin.ts const streamTab = { app: 'logs', title: streamTabTitle, diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index b43d7640f63907..819c764bfb7ba5 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -120,7 +120,7 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { > - {/** !! Need to be kept in sync with the searchDeepLinks in x-pack/plugins/infra/public/plugin.ts */} + {/** !! Need to be kept in sync with the deepLinks in x-pack/plugins/infra/public/plugin.ts */} - - CPU usage - - - 10% - + +
+ CPU usage +
+
+ +
+ 10% +
+
+
- - Memory usage - - - 80% - + +
+ Memory usage +
+
+ +
+ 80% +
+
+
- - Outbound traffic - - - 8Mbit/s - + +
+ Outbound traffic +
+
+ +
+ 8Mbit/s +
+
+
- - Inbound traffic - - - 8Mbit/s - + +
+ Inbound traffic +
+
+ +
+ 8Mbit/s +
+
+
- - My Custom Label - - - 34.1% - + +
+ My Custom Label +
+
+ +
+ 34.1% +
+
+
- - Avg of host.network.out.packets - - - 4,392.2 - + +
+ Avg of host.network.out.packets +
+
+ +
+ 4,392.2 +
+
+
`; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx index 6dde53efae761b..ac4fac394dacc5 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.test.tsx @@ -7,15 +7,10 @@ import React from 'react'; import { mount } from 'enzyme'; -// import { act } from 'react-dom/test-utils'; +import toJson from 'enzyme-to-json'; import { EuiThemeProvider } from '../../../../../../../../../src/plugins/kibana_react/common'; -import { EuiToolTip } from '@elastic/eui'; import { ConditionalToolTip } from './conditional_tooltip'; -import { - InfraWaffleMapNode, - InfraWaffleMapOptions, - InfraFormatterType, -} from '../../../../../lib/lib'; +import { InfraWaffleMapNode } from '../../../../../lib/lib'; jest.mock('../../../../../containers/metrics_source', () => ({ useSourceContext: () => ({ sourceId: 'default' }), @@ -38,61 +33,12 @@ const NODE: InfraWaffleMapNode = { metrics: [{ name: 'cpu' }], }; -const OPTIONS: InfraWaffleMapOptions = { - formatter: InfraFormatterType.percent, - formatTemplate: '{value}', - metric: { type: 'cpu' }, - groupBy: [], - legend: { - type: 'steppedGradient', - rules: [], - }, - sort: { by: 'value', direction: 'desc' }, -}; - export const nextTick = () => new Promise((res) => process.nextTick(res)); -const ChildComponent = () =>
child
; describe('ConditionalToolTip', () => { - afterEach(() => { - mockedUseSnapshot.mockReset(); - mockedUseWaffleOptionsContext.mockReset(); - }); - - function createWrapper(currentTime: number = Date.now(), hidden: boolean = false) { - return mount( - - - - ); - } - - it('should return children when hidden', () => { - mockedUseSnapshot.mockReturnValue({ - nodes: [], - error: null, - loading: true, - interval: '', - reload: jest.fn(() => Promise.resolve()), - }); - mockedUseWaffleOptionsContext.mockReturnValue(mockedUseWaffleOptionsContexReturnValue); - const currentTime = Date.now(); - const wrapper = createWrapper(currentTime, true); - expect(wrapper.find(ChildComponent).exists()).toBeTruthy(); - }); + const currentTime = Date.now(); - it('should just work', () => { - jest.useFakeTimers(); - const reloadMock = jest.fn(() => Promise.resolve()); + it('renders correctly', () => { mockedUseSnapshot.mockReturnValue({ nodes: [ { @@ -121,13 +67,9 @@ describe('ConditionalToolTip', () => { error: null, loading: false, interval: '60s', - reload: reloadMock, + reload: jest.fn(() => Promise.resolve()), }); mockedUseWaffleOptionsContext.mockReturnValue(mockedUseWaffleOptionsContexReturnValue); - const currentTime = Date.now(); - const wrapper = createWrapper(currentTime, false); - expect(wrapper.find(ChildComponent).exists()).toBeTruthy(); - expect(wrapper.find(EuiToolTip).exists()).toBeTruthy(); const expectedQuery = JSON.stringify({ bool: { filter: { @@ -154,6 +96,14 @@ describe('ConditionalToolTip', () => { type: 'custom', }, ]; + const wrapper = mount( + + + + ); + const tooltip = wrapper.find('[data-test-subj~="conditionalTooltipContent-host-01"]'); + expect(toJson(tooltip)).toMatchSnapshot(); + expect(mockedUseSnapshot).toBeCalledWith( expectedQuery, expectedMetrics, @@ -162,36 +112,8 @@ describe('ConditionalToolTip', () => { 'default', currentTime, '', - '', - false + '' ); - wrapper.find('[data-test-subj~="conditionalTooltipMouseHandler"]').simulate('mouseOver'); - wrapper.find(EuiToolTip).simulate('mouseOver'); - jest.advanceTimersByTime(500); - expect(reloadMock).toHaveBeenCalled(); - expect(wrapper.find(EuiToolTip).props().content).toMatchSnapshot(); - }); - - it('should not load data if mouse out before 200 ms', () => { - jest.useFakeTimers(); - const reloadMock = jest.fn(() => Promise.resolve()); - mockedUseSnapshot.mockReturnValue({ - nodes: [], - error: null, - loading: true, - interval: '', - reload: reloadMock, - }); - mockedUseWaffleOptionsContext.mockReturnValue(mockedUseWaffleOptionsContexReturnValue); - const currentTime = Date.now(); - const wrapper = createWrapper(currentTime, false); - expect(wrapper.find(ChildComponent).exists()).toBeTruthy(); - expect(wrapper.find(EuiToolTip).exists()).toBeTruthy(); - wrapper.find('[data-test-subj~="conditionalTooltipMouseHandler"]').simulate('mouseOver'); - jest.advanceTimersByTime(100); - wrapper.find('[data-test-subj~="conditionalTooltipMouseHandler"]').simulate('mouseOut'); - jest.advanceTimersByTime(200); - expect(reloadMock).not.toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.tsx index 6e334f4fbca752..a47512906abd13 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/conditional_tooltip.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import React, { useCallback, useState, useEffect } from 'react'; -import { EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { first } from 'lodash'; import { getCustomMetricLabel } from '../../../../../../common/formatters/get_custom_metric_label'; import { SnapshotCustomMetricInput } from '../../../../../../common/http_api'; @@ -18,7 +18,7 @@ import { SnapshotMetricType, SnapshotMetricTypeRT, } from '../../../../../../common/inventory_models/types'; -import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../../../lib/lib'; +import { InfraWaffleMapNode } from '../../../../../lib/lib'; import { useSnapshot } from '../../hooks/use_snaphot'; import { createInventoryMetricFormatter } from '../../lib/create_inventory_metric_formatter'; import { SNAPSHOT_METRIC_TRANSLATIONS } from '../../../../../../common/inventory_models/intl_strings'; @@ -27,113 +27,69 @@ import { createFormatterForMetric } from '../../../metrics_explorer/components/h export interface Props { currentTime: number; - hidden: boolean; node: InfraWaffleMapNode; - options: InfraWaffleMapOptions; - formatter: (val: number) => string; - children: React.ReactElement; nodeType: InventoryItemType; theme: EuiTheme | undefined; } - -export const ConditionalToolTip = withTheme( - ({ theme, hidden, node, children, nodeType, currentTime }: Props) => { - const { sourceId } = useSourceContext(); - const [timer, setTimer] = useState | null>(null); - const model = findInventoryModel(nodeType); - const { customMetrics } = useWaffleOptionsContext(); - const requestMetrics = model.tooltipMetrics - .map((type) => ({ type })) - .concat(customMetrics) as Array< - | { - type: SnapshotMetricType; - } - | SnapshotCustomMetricInput - >; - const query = JSON.stringify({ - bool: { - filter: { - match_phrase: { [model.fields.id]: node.id }, - }, +export const ConditionalToolTip = withTheme(({ theme, node, nodeType, currentTime }: Props) => { + const { sourceId } = useSourceContext(); + const model = findInventoryModel(nodeType); + const { customMetrics } = useWaffleOptionsContext(); + const requestMetrics = model.tooltipMetrics + .map((type) => ({ type })) + .concat(customMetrics) as Array< + | { + type: SnapshotMetricType; + } + | SnapshotCustomMetricInput + >; + const query = JSON.stringify({ + bool: { + filter: { + match_phrase: { [model.fields.id]: node.id }, }, - }); - const { nodes, reload } = useSnapshot( - query, - requestMetrics, - [], - nodeType, - sourceId, - currentTime, - '', - '', - false // Doesn't send request until reload() is called - ); - - const handleDataLoad = useCallback(() => { - const id = setTimeout(reload, 200); - setTimer(id); - }, [reload]); - - const cancelDataLoad = useCallback(() => { - return (timer && clearTimeout(timer)) || void 0; - }, [timer]); + }, + }); + const { nodes } = useSnapshot(query, requestMetrics, [], nodeType, sourceId, currentTime, '', ''); - useEffect(() => { - return cancelDataLoad; - }, [timer, cancelDataLoad]); - - if (hidden) { - return children; - } - const dataNode = first(nodes); - const metrics = (dataNode && dataNode.metrics) || []; - const content = ( -
-
- {node.name} -
- {metrics.map((metric) => { - const metricName = SnapshotMetricTypeRT.is(metric.name) ? metric.name : 'custom'; - const name = SNAPSHOT_METRIC_TRANSLATIONS[metricName] || metricName; - // if custom metric, find field and label from waffleOptionsContext result - // because useSnapshot does not return it - const customMetric = - name === 'custom' ? customMetrics.find((item) => item.id === metric.name) : null; - const formatter = customMetric - ? createFormatterForMetric(customMetric) - : createInventoryMetricFormatter({ type: metricName }); - return ( - - - {customMetric ? getCustomMetricLabel(customMetric) : name} - - - {(metric.value && formatter(metric.value)) || '-'} - - - ); - })} + const dataNode = first(nodes); + const metrics = (dataNode && dataNode.metrics) || []; + return ( +
+
+ {node.name}
- ); - - return ( - -
- {children} -
-
- ); - } -); + {metrics.map((metric) => { + const metricName = SnapshotMetricTypeRT.is(metric.name) ? metric.name : 'custom'; + const name = SNAPSHOT_METRIC_TRANSLATIONS[metricName] || metricName; + // if custom metric, find field and label from waffleOptionsContext result + // because useSnapshot does not return it + const customMetric = + name === 'custom' ? customMetrics.find((item) => item.id === metric.name) : null; + const formatter = customMetric + ? createFormatterForMetric(customMetric) + : createInventoryMetricFormatter({ type: metricName }); + return ( + + + {customMetric ? getCustomMetricLabel(customMetric) : name} + + + {(metric.value && formatter(metric.value)) || '-'} + + + ); + })} +
+ ); +}); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node.tsx index e972f9ca4f345f..031b826265e16f 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/waffle/node.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { first } from 'lodash'; -import { EuiPopover } from '@elastic/eui'; +import { EuiPopover, EuiToolTip } from '@elastic/eui'; import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; import { InfraWaffleMapBounds, @@ -64,13 +64,10 @@ export class Node extends React.PureComponent { const nodeBorder = this.state.isOverlayOpen ? { border: 'solid 4px #000' } : undefined; const button = ( - + ); return ( diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index 12fe53fb426c0e..9948976b01ea13 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -63,39 +63,37 @@ export class Plugin implements InfraClientPluginClass { euiIconType: 'logoObservability', order: 8100, appRoute: '/app/logs', - meta: { - // !! Need to be kept in sync with the routes in x-pack/plugins/infra/public/pages/logs/page_content.tsx - searchDeepLinks: [ - { - id: 'stream', - title: i18n.translate('xpack.infra.logs.index.streamTabTitle', { - defaultMessage: 'Stream', - }), - path: '/stream', - }, - { - id: 'anomalies', - title: i18n.translate('xpack.infra.logs.index.anomaliesTabTitle', { - defaultMessage: 'Anomalies', - }), - path: '/anomalies', - }, - { - id: 'log-categories', - title: i18n.translate('xpack.infra.logs.index.logCategoriesBetaBadgeTitle', { - defaultMessage: 'Categories', - }), - path: '/log-categories', - }, - { - id: 'settings', - title: i18n.translate('xpack.infra.logs.index.settingsTabTitle', { - defaultMessage: 'Settings', - }), - path: '/settings', - }, - ], - }, + // !! Need to be kept in sync with the routes in x-pack/plugins/infra/public/pages/logs/page_content.tsx + deepLinks: [ + { + id: 'stream', + title: i18n.translate('xpack.infra.logs.index.streamTabTitle', { + defaultMessage: 'Stream', + }), + path: '/stream', + }, + { + id: 'anomalies', + title: i18n.translate('xpack.infra.logs.index.anomaliesTabTitle', { + defaultMessage: 'Anomalies', + }), + path: '/anomalies', + }, + { + id: 'log-categories', + title: i18n.translate('xpack.infra.logs.index.logCategoriesBetaBadgeTitle', { + defaultMessage: 'Categories', + }), + path: '/log-categories', + }, + { + id: 'settings', + title: i18n.translate('xpack.infra.logs.index.settingsTabTitle', { + defaultMessage: 'Settings', + }), + path: '/settings', + }, + ], category: DEFAULT_APP_CATEGORIES.observability, mount: async (params: AppMountParameters) => { // mount callback should not use setup dependencies, get start dependencies instead @@ -115,32 +113,30 @@ export class Plugin implements InfraClientPluginClass { order: 8200, appRoute: '/app/metrics', category: DEFAULT_APP_CATEGORIES.observability, - meta: { - // !! Need to be kept in sync with the routes in x-pack/plugins/infra/public/pages/metrics/index.tsx - searchDeepLinks: [ - { - id: 'inventory', - title: i18n.translate('xpack.infra.homePage.inventoryTabTitle', { - defaultMessage: 'Inventory', - }), - path: '/inventory', - }, - { - id: 'metrics-explorer', - title: i18n.translate('xpack.infra.homePage.metricsExplorerTabTitle', { - defaultMessage: 'Metrics Explorer', - }), - path: '/explorer', - }, - { - id: 'settings', - title: i18n.translate('xpack.infra.homePage.settingsTabTitle', { - defaultMessage: 'Settings', - }), - path: '/settings', - }, - ], - }, + // !! Need to be kept in sync with the routes in x-pack/plugins/infra/public/pages/metrics/index.tsx + deepLinks: [ + { + id: 'inventory', + title: i18n.translate('xpack.infra.homePage.inventoryTabTitle', { + defaultMessage: 'Inventory', + }), + path: '/inventory', + }, + { + id: 'metrics-explorer', + title: i18n.translate('xpack.infra.homePage.metricsExplorerTabTitle', { + defaultMessage: 'Metrics Explorer', + }), + path: '/explorer', + }, + { + id: 'settings', + title: i18n.translate('xpack.infra.homePage.settingsTabTitle', { + defaultMessage: 'Settings', + }), + path: '/settings', + }, + ], mount: async (params: AppMountParameters) => { // mount callback should not use setup dependencies, get start dependencies instead const [coreStart, pluginsStart] = await core.getStartServices(); diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap index 339fb5a7ab68f3..08b3393fafe482 100644 --- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap +++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/to_expression.test.ts.snap @@ -33,6 +33,9 @@ Object { "description": Array [ "", ], + "fillOpacity": Array [ + 0.3, + ], "fittingFunction": Array [ "Carry", ], diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 006727b05b9056..e3b4565913ad87 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -196,6 +196,12 @@ export const xyChart: ExpressionFunctionDefinition< defaultMessage: 'Define how curve type is rendered for a line chart', }), }, + fillOpacity: { + types: ['number'], + help: i18n.translate('xpack.lens.xyChart.fillOpacity.help', { + defaultMessage: 'Define the area chart fill opacity', + }), + }, hideEndzones: { types: ['boolean'], default: false, @@ -812,6 +818,7 @@ export function XYChart({ visible: !xAccessor, radius: 5, }, + ...(args.fillOpacity && { area: { opacity: args.fillOpacity } }), }, lineSeriesStyle: { point: { diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts index dea6b1a7be0c57..269f10159892f4 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts @@ -149,6 +149,7 @@ export const buildExpression = ( ], fittingFunction: [state.fittingFunction || 'None'], curveType: [state.curveType || 'LINEAR'], + fillOpacity: [state.fillOpacity || 0.3], yLeftExtent: [ { type: 'expression', diff --git a/x-pack/plugins/lens/public/xy_visualization/types.ts b/x-pack/plugins/lens/public/xy_visualization/types.ts index ea28b492477c18..531b034b532425 100644 --- a/x-pack/plugins/lens/public/xy_visualization/types.ts +++ b/x-pack/plugins/lens/public/xy_visualization/types.ts @@ -464,6 +464,7 @@ export interface XYArgs { tickLabelsVisibilitySettings?: AxesSettingsConfig & { type: 'lens_xy_tickLabelsConfig' }; gridlinesVisibilitySettings?: AxesSettingsConfig & { type: 'lens_xy_gridlinesConfig' }; curveType?: XYCurveType; + fillOpacity?: number; hideEndzones?: boolean; } @@ -485,6 +486,7 @@ export interface XYState { tickLabelsVisibilitySettings?: AxesSettingsConfig; gridlinesVisibilitySettings?: AxesSettingsConfig; curveType?: XYCurveType; + fillOpacity?: number; hideEndzones?: boolean; } diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/fill_opacity_option.test.tsx b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/fill_opacity_option.test.tsx new file mode 100644 index 00000000000000..3ba29e4f72c837 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/fill_opacity_option.test.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { mountWithIntl as mount, shallowWithIntl as shallow } from '@kbn/test/jest'; +import { EuiRange } from '@elastic/eui'; +import { FillOpacityOption } from './fill_opacity_option'; + +describe('Line curve option', () => { + it('should show currently selected opacity value', () => { + const component = shallow(); + + expect(component.find(EuiRange).prop('value')).toEqual(0.3); + }); + + it('should show fill opacity option when enabled', () => { + const component = mount( + + ); + + expect(component.exists('[data-test-subj="lnsFillOpacity"]')).toEqual(true); + }); + + it('should hide curve option when disabled', () => { + const component = mount( + + ); + + expect(component.exists('[data-test-subj="lnsFillOpacity"]')).toEqual(false); + }); +}); diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/fill_opacity_option.tsx b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/fill_opacity_option.tsx new file mode 100644 index 00000000000000..eb8d35c54a99b9 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/fill_opacity_option.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFormRow, EuiRange } from '@elastic/eui'; +import { useDebouncedValue } from '../../shared_components'; + +export interface FillOpacityOptionProps { + /** + * Currently selected value + */ + value: number; + /** + * Callback on display option change + */ + onChange: (value: number) => void; + /** + * Flag for rendering or not the component + */ + isFillOpacityEnabled?: boolean; +} + +export const FillOpacityOption: React.FC = ({ + onChange, + value, + isFillOpacityEnabled = true, +}) => { + const { inputValue, handleInputChange } = useDebouncedValue({ value, onChange }); + return isFillOpacityEnabled ? ( + <> + + { + handleInputChange(Number(e.currentTarget.value)); + }} + /> + + + ) : null; +}; diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/missing_values_option.tsx b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/missing_values_option.tsx index fb6ecec4d28013..a683d4fbf514c3 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/missing_values_option.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/missing_values_option.tsx @@ -7,7 +7,14 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiButtonGroup, EuiFormRow, EuiIconTip, EuiSuperSelect, EuiText } from '@elastic/eui'; +import { + EuiButtonGroup, + EuiFormRow, + EuiIconTip, + EuiSuperSelect, + EuiText, + EuiSpacer, +} from '@elastic/eui'; import { FittingFunction, fittingFunctionDefinitions } from '../fitting_functions'; import { ValueLabelConfig } from '../types'; @@ -133,6 +140,7 @@ export const MissingValuesOptions: React.FC = ({ /> )} + ); }; diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.test.tsx b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.test.tsx index e7ec395312bff4..b46ad1940491e6 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.test.tsx @@ -14,6 +14,7 @@ import { State } from '../types'; import { VisualOptionsPopover } from './visual_options_popover'; import { ToolbarPopover } from '../../shared_components'; import { MissingValuesOptions } from './missing_values_option'; +import { FillOpacityOption } from './fill_opacity_option'; describe('Visual options popover', () => { let frame: FramePublicAPI; @@ -74,6 +75,22 @@ describe('Visual options popover', () => { expect(component.find(MissingValuesOptions).prop('isFittingEnabled')).toEqual(false); }); + it('should not disable the fill opacity for percentage area charts', () => { + const state = testState(); + const component = shallow( + + ); + + expect(component.find(FillOpacityOption).prop('isFillOpacityEnabled')).toEqual(true); + }); + it('should not disable the visual options for percentage area charts', () => { const state = testState(); const component = shallow( @@ -128,6 +145,40 @@ describe('Visual options popover', () => { expect(component.find(MissingValuesOptions).prop('isFittingEnabled')).toEqual(false); }); + it('should hide the fill opacity option for bar series', () => { + const state = testState(); + const component = shallow( + + ); + + expect(component.find(FillOpacityOption).prop('isFillOpacityEnabled')).toEqual(false); + }); + + it('should hide the fill opacity option for line series', () => { + const state = testState(); + const component = shallow( + + ); + + expect(component.find(FillOpacityOption).prop('isFillOpacityEnabled')).toEqual(false); + }); + it('should show the popover and display field enabled for bar and horizontal_bar series', () => { const state = testState(); diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.tsx b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.tsx index b8b89f146bdc07..b07feb85892e53 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.tsx @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import { ToolbarPopover } from '../../shared_components'; import { MissingValuesOptions } from './missing_values_option'; import { LineCurveOption } from './line_curve_option'; +import { FillOpacityOption } from './fill_opacity_option'; import { XYState } from '../types'; import { hasHistogramSeries } from '../state_helpers'; import { ValidLayer } from '../types'; @@ -61,6 +62,10 @@ export const VisualOptionsPopover: React.FC = ({ ['bar', 'bar_horizontal'].includes(seriesType) ); + const hasAreaSeries = state?.layers.some(({ seriesType }) => + ['area_stacked', 'area', 'area_percentage_stacked'].includes(seriesType) + ); + const isHistogramSeries = Boolean( hasHistogramSeries(state?.layers as ValidLayer[], datasourceLayers) ); @@ -110,6 +115,17 @@ export const VisualOptionsPopover: React.FC = ({ setState({ ...state, fittingFunction: newVal }); }} /> + + { + setState({ + ...state, + fillOpacity: newValue, + }); + }} + /> ); diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts index 4554c34b97c555..aff33778258fed 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts +++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts @@ -524,6 +524,7 @@ function buildSuggestion({ valueLabels: currentState?.valueLabels || 'hide', fittingFunction: currentState?.fittingFunction || 'None', curveType: currentState?.curveType, + fillOpacity: currentState?.fillOpacity, xTitle: currentState?.xTitle, yTitle: currentState?.yTitle, yRightTitle: currentState?.yRightTitle, diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts index 973f5822cae2b2..1566241e7351e0 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts @@ -32,9 +32,11 @@ export class ExceptionListClientMock extends ExceptionListClient { public createEndpointList = jest.fn().mockResolvedValue(getExceptionListSchemaMock()); } -export const getExceptionListClientMock = (): ExceptionListClient => { +export const getExceptionListClientMock = ( + savedObject?: ReturnType +): ExceptionListClient => { const mock = new ExceptionListClientMock({ - savedObjectsClient: savedObjectsClientMock.create(), + savedObjectsClient: savedObject ? savedObject : savedObjectsClientMock.create(), user: 'elastic', }); return mock; diff --git a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx index e0cfe978bf45cf..851fd583b4251f 100644 --- a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx @@ -30,7 +30,6 @@ import { registerLayerWizards } from '../../classes/layers/load_layer_wizards'; import { RenderToolTipContent } from '../../classes/tooltips/tooltip_property'; import { GeoFieldWithIndex } from '../../components/geo_field_with_index'; import { MapRefreshConfig } from '../../../common/descriptor_types'; -import 'mapbox-gl/dist/mapbox-gl.css'; const RENDER_COMPLETE_EVENT = 'renderComplete'; diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx index ac3e72545033fb..355e49564620dd 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx @@ -7,9 +7,8 @@ import _ from 'lodash'; import React, { Component } from 'react'; -import { Map as MapboxMap, MapboxOptions, MapMouseEvent } from 'mapbox-gl'; -// @ts-expect-error -import mapboxgl from 'mapbox-gl/dist/mapbox-gl-csp'; +import type { Map as MapboxMap, MapboxOptions, MapMouseEvent } from 'mapbox-gl'; + // @ts-expect-error import { spritesheet } from '@elastic/maki'; import sprites1 from '@elastic/maki/dist/sprite@1.png'; @@ -17,6 +16,9 @@ import sprites2 from '@elastic/maki/dist/sprite@2.png'; import { Adapters } from 'src/plugins/inspector/public'; import { Filter } from 'src/plugins/data/public'; import { ActionExecutionContext, Action } from 'src/plugins/ui_actions/public'; + +import { mapboxgl } from '@kbn/mapbox-gl'; + import { DrawFilterControl } from './draw_control'; import { ScaleControl } from './scale_control'; import { TooltipControl } from './tooltip_control'; @@ -45,13 +47,6 @@ import { GeoFieldWithIndex } from '../../components/geo_field_with_index'; import { RenderToolTipContent } from '../../classes/tooltips/tooltip_property'; import { MapExtentState } from '../../actions'; import { TileStatusTracker } from './tile_status_tracker'; -// @ts-expect-error -import mbRtlPlugin from '!!file-loader!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.min.js'; -// @ts-expect-error -import mbWorkerUrl from '!!file-loader!mapbox-gl/dist/mapbox-gl-csp-worker'; - -mapboxgl.workerUrl = mbWorkerUrl; -mapboxgl.setRTLTextPlugin(mbRtlPlugin); export interface Props { isMapReady: boolean; diff --git a/x-pack/plugins/ml/public/register_helper/register_search_links/register_search_links.ts b/x-pack/plugins/ml/public/register_helper/register_search_links/register_search_links.ts index 6c219340da817d..dd3ca0bb8fa309 100644 --- a/x-pack/plugins/ml/public/register_helper/register_search_links/register_search_links.ts +++ b/x-pack/plugins/ml/public/register_helper/register_search_links/register_search_links.ts @@ -9,20 +9,18 @@ import { i18n } from '@kbn/i18n'; import { BehaviorSubject } from 'rxjs'; import { AppUpdater } from 'src/core/public'; -import { getSearchDeepLinks } from './search_deep_links'; +import { getDeepLinks } from './search_deep_links'; export function registerSearchLinks( appUpdater: BehaviorSubject, isFullLicense: boolean ) { appUpdater.next(() => ({ - meta: { - keywords: [ - i18n.translate('xpack.ml.keyword.ml', { - defaultMessage: 'ML', - }), - ], - searchDeepLinks: getSearchDeepLinks(isFullLicense), - }, + keywords: [ + i18n.translate('xpack.ml.keyword.ml', { + defaultMessage: 'ML', + }), + ], + deepLinks: getDeepLinks(isFullLicense), })); } diff --git a/x-pack/plugins/ml/public/register_helper/register_search_links/search_deep_links.ts b/x-pack/plugins/ml/public/register_helper/register_search_links/search_deep_links.ts index d248df90889897..d682a93fa274c4 100644 --- a/x-pack/plugins/ml/public/register_helper/register_search_links/search_deep_links.ts +++ b/x-pack/plugins/ml/public/register_helper/register_search_links/search_deep_links.ts @@ -7,35 +7,35 @@ import { i18n } from '@kbn/i18n'; -import type { AppSearchDeepLink } from 'src/core/public'; +import type { AppDeepLink } from 'src/core/public'; import { ML_PAGES } from '../../../common/constants/ml_url_generator'; -const OVERVIEW_LINK_SEARCH_DEEP_LINK: AppSearchDeepLink = { - id: 'mlOverviewSearchDeepLink', - title: i18n.translate('xpack.ml.searchDeepLink.overview', { +const OVERVIEW_LINK_DEEP_LINK: AppDeepLink = { + id: 'mlOverviewDeepLink', + title: i18n.translate('xpack.ml.deepLink.overview', { defaultMessage: 'Overview', }), path: `/${ML_PAGES.OVERVIEW}`, }; -const ANOMALY_DETECTION_SEARCH_DEEP_LINK: AppSearchDeepLink = { - id: 'mlAnomalyDetectionSearchDeepLink', - title: i18n.translate('xpack.ml.searchDeepLink.anomalyDetection', { +const ANOMALY_DETECTION_DEEP_LINK: AppDeepLink = { + id: 'mlAnomalyDetectionDeepLink', + title: i18n.translate('xpack.ml.deepLink.anomalyDetection', { defaultMessage: 'Anomaly Detection', }), path: `/${ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE}`, }; -const DATA_FRAME_ANALYTICS_SEARCH_DEEP_LINK: AppSearchDeepLink = { - id: 'mlDataFrameAnalyticsSearchDeepLink', - title: i18n.translate('xpack.ml.searchDeepLink.dataFrameAnalytics', { +const DATA_FRAME_ANALYTICS_DEEP_LINK: AppDeepLink = { + id: 'mlDataFrameAnalyticsDeepLink', + title: i18n.translate('xpack.ml.deepLink.dataFrameAnalytics', { defaultMessage: 'Data Frame Analytics', }), path: `/${ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE}`, - searchDeepLinks: [ + deepLinks: [ { - id: 'mlTrainedModelsSearchDeepLink', - title: i18n.translate('xpack.ml.searchDeepLink.trainedModels', { + id: 'mlTrainedModelsDeepLink', + title: i18n.translate('xpack.ml.deepLink.trainedModels', { defaultMessage: 'Trained Models', }), path: `/${ML_PAGES.DATA_FRAME_ANALYTICS_MODELS_MANAGE}`, @@ -43,47 +43,47 @@ const DATA_FRAME_ANALYTICS_SEARCH_DEEP_LINK: AppSearchDeepLink = { ], }; -const DATA_VISUALIZER_SEARCH_DEEP_LINK: AppSearchDeepLink = { - id: 'mlDataVisualizerSearchDeepLink', - title: i18n.translate('xpack.ml.searchDeepLink.dataVisualizer', { +const DATA_VISUALIZER_DEEP_LINK: AppDeepLink = { + id: 'mlDataVisualizerDeepLink', + title: i18n.translate('xpack.ml.deepLink.dataVisualizer', { defaultMessage: 'Data Visualizer', }), path: `/${ML_PAGES.DATA_VISUALIZER}`, }; -const FILE_UPLOAD_SEARCH_DEEP_LINK: AppSearchDeepLink = { - id: 'mlFileUploadSearchDeepLink', - title: i18n.translate('xpack.ml.searchDeepLink.fileUpload', { +const FILE_UPLOAD_DEEP_LINK: AppDeepLink = { + id: 'mlFileUploadDeepLink', + title: i18n.translate('xpack.ml.deepLink.fileUpload', { defaultMessage: 'File Upload', }), path: `/${ML_PAGES.DATA_VISUALIZER_FILE}`, }; -const INDEX_DATA_VISUALIZER_SEARCH_DEEP_LINK: AppSearchDeepLink = { - id: 'mlIndexDataVisualizerSearchDeepLink', - title: i18n.translate('xpack.ml.searchDeepLink.indexDataVisualizer', { +const INDEX_DATA_VISUALIZER_DEEP_LINK: AppDeepLink = { + id: 'mlIndexDataVisualizerDeepLink', + title: i18n.translate('xpack.ml.deepLink.indexDataVisualizer', { defaultMessage: 'Index Data Visualizer', }), path: `/${ML_PAGES.DATA_VISUALIZER_INDEX_SELECT}`, }; -const SETTINGS_SEARCH_DEEP_LINK: AppSearchDeepLink = { - id: 'mlSettingsSearchDeepLink', - title: i18n.translate('xpack.ml.searchDeepLink.settings', { +const SETTINGS_DEEP_LINK: AppDeepLink = { + id: 'mlSettingsDeepLink', + title: i18n.translate('xpack.ml.deepLink.settings', { defaultMessage: 'Settings', }), path: `/${ML_PAGES.SETTINGS}`, - searchDeepLinks: [ + deepLinks: [ { - id: 'mlCalendarSettingsSearchDeepLink', - title: i18n.translate('xpack.ml.searchDeepLink.calendarSettings', { + id: 'mlCalendarSettingsDeepLink', + title: i18n.translate('xpack.ml.deepLink.calendarSettings', { defaultMessage: 'Calendars', }), path: `/${ML_PAGES.CALENDARS_MANAGE}`, }, { - id: 'mlFilterListsSettingsSearchDeepLink', - title: i18n.translate('xpack.ml.searchDeepLink.filterListsSettings', { + id: 'mlFilterListsSettingsDeepLink', + title: i18n.translate('xpack.ml.deepLink.filterListsSettings', { defaultMessage: 'Filter Lists', }), path: `/${ML_PAGES.SETTINGS}`, // Link to settings page as read only users cannot view filter lists. @@ -91,19 +91,19 @@ const SETTINGS_SEARCH_DEEP_LINK: AppSearchDeepLink = { ], }; -export function getSearchDeepLinks(isFullLicense: boolean) { - const deepLinks: AppSearchDeepLink[] = [ - DATA_VISUALIZER_SEARCH_DEEP_LINK, - FILE_UPLOAD_SEARCH_DEEP_LINK, - INDEX_DATA_VISUALIZER_SEARCH_DEEP_LINK, +export function getDeepLinks(isFullLicense: boolean) { + const deepLinks: AppDeepLink[] = [ + DATA_VISUALIZER_DEEP_LINK, + FILE_UPLOAD_DEEP_LINK, + INDEX_DATA_VISUALIZER_DEEP_LINK, ]; if (isFullLicense === true) { deepLinks.push( - OVERVIEW_LINK_SEARCH_DEEP_LINK, - ANOMALY_DETECTION_SEARCH_DEEP_LINK, - DATA_FRAME_ANALYTICS_SEARCH_DEEP_LINK, - SETTINGS_SEARCH_DEEP_LINK + OVERVIEW_LINK_DEEP_LINK, + ANOMALY_DETECTION_DEEP_LINK, + DATA_FRAME_ANALYTICS_DEEP_LINK, + SETTINGS_DEEP_LINK ); } diff --git a/x-pack/plugins/reporting/server/lib/store/report_ilm_policy.ts b/x-pack/plugins/reporting/server/lib/store/report_ilm_policy.ts new file mode 100644 index 00000000000000..f4cd69a0331d7d --- /dev/null +++ b/x-pack/plugins/reporting/server/lib/store/report_ilm_policy.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PutLifecycleRequest } from '@elastic/elasticsearch/api/types'; + +export const reportingIlmPolicy: PutLifecycleRequest['body'] = { + policy: { + phases: { + hot: { + actions: {}, + }, + }, + }, +}; diff --git a/x-pack/plugins/reporting/server/lib/store/store.test.ts b/x-pack/plugins/reporting/server/lib/store/store.test.ts index 7f96433fcc6ceb..fa35240dfc8fbd 100644 --- a/x-pack/plugins/reporting/server/lib/store/store.test.ts +++ b/x-pack/plugins/reporting/server/lib/store/store.test.ts @@ -7,6 +7,7 @@ import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; import { ElasticsearchClient } from 'src/core/server'; +import { elasticsearchServiceMock } from 'src/core/server/mocks'; import { ReportingCore } from '../../'; import { createMockConfigSchema, @@ -16,6 +17,8 @@ import { import { Report, ReportDocument } from './report'; import { ReportingStore } from './store'; +const { createApiResponse } = elasticsearchServiceMock; + describe('ReportingStore', () => { const mockLogger = createMockLevelLogger(); let mockCore: ReportingCore; @@ -403,4 +406,40 @@ describe('ReportingStore', () => { ] `); }); + + describe('start', () => { + it('creates an ILM policy for managing reporting indices if there is not already one', async () => { + mockEsClient.ilm.getLifecycle.mockRejectedValueOnce(createApiResponse({ statusCode: 404 })); + mockEsClient.ilm.putLifecycle.mockResolvedValueOnce(createApiResponse()); + + const store = new ReportingStore(mockCore, mockLogger); + await store.start(); + + expect(mockEsClient.ilm.getLifecycle).toHaveBeenCalledWith({ policy: 'kibana-reporting' }); + expect(mockEsClient.ilm.putLifecycle.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "body": Object { + "policy": Object { + "phases": Object { + "hot": Object { + "actions": Object {}, + }, + }, + }, + }, + "policy": "kibana-reporting", + } + `); + }); + + it('does not create an ILM policy for managing reporting indices if one already exists', async () => { + mockEsClient.ilm.getLifecycle.mockResolvedValueOnce(createApiResponse()); + + const store = new ReportingStore(mockCore, mockLogger); + await store.start(); + + expect(mockEsClient.ilm.getLifecycle).toHaveBeenCalledWith({ policy: 'kibana-reporting' }); + expect(mockEsClient.ilm.putLifecycle).not.toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/plugins/reporting/server/lib/store/store.ts b/x-pack/plugins/reporting/server/lib/store/store.ts index fc7bd9c23d7693..9fb203fd5627ab 100644 --- a/x-pack/plugins/reporting/server/lib/store/store.ts +++ b/x-pack/plugins/reporting/server/lib/store/store.ts @@ -14,6 +14,7 @@ import { ReportTaskParams } from '../tasks'; import { indexTimestamp } from './index_timestamp'; import { mapping } from './mapping'; import { Report, ReportDocument, ReportSource } from './report'; +import { reportingIlmPolicy } from './report_ilm_policy'; /* * When searching for long-pending reports, we get a subset of fields @@ -71,19 +72,22 @@ export class ReportingStore { return exists; } - const indexSettings = { - number_of_shards: 1, - auto_expand_replicas: '0-1', - }; - const body = { - settings: indexSettings, - mappings: { - properties: mapping, - }, - }; - try { - await client.indices.create({ index: indexName, body }); + await client.indices.create({ + index: indexName, + body: { + settings: { + number_of_shards: 1, + auto_expand_replicas: '0-1', + lifecycle: { + name: this.ilmPolicyName, + }, + }, + mappings: { + properties: mapping, + }, + }, + }); return true; } catch (error) { @@ -130,6 +134,44 @@ export class ReportingStore { return client.indices.refresh({ index }); } + private readonly ilmPolicyName = 'kibana-reporting'; + + private async doesIlmPolicyExist(): Promise { + const client = await this.getClient(); + try { + await client.ilm.getLifecycle({ policy: this.ilmPolicyName }); + return true; + } catch (e) { + if (e.statusCode === 404) { + return false; + } + throw e; + } + } + + /** + * Function to be called during plugin start phase. This ensures the environment is correctly + * configured for storage of reports. + */ + public async start() { + const client = await this.getClient(); + try { + if (await this.doesIlmPolicyExist()) { + this.logger.debug(`Found ILM policy ${this.ilmPolicyName}; skipping creation.`); + return; + } + this.logger.info(`Creating ILM policy for managing reporting indices: ${this.ilmPolicyName}`); + await client.ilm.putLifecycle({ + policy: this.ilmPolicyName, + body: reportingIlmPolicy, + }); + } catch (e) { + this.logger.error('Error in start phase'); + this.logger.error(e.body.error); + throw e; + } + } + public async addReport(report: Report): Promise { let index = report._index; if (!index) { diff --git a/x-pack/plugins/reporting/server/plugin.ts b/x-pack/plugins/reporting/server/plugin.ts index fc52e10dd0cf94..efe1d9450bef31 100644 --- a/x-pack/plugins/reporting/server/plugin.ts +++ b/x-pack/plugins/reporting/server/plugin.ts @@ -107,6 +107,9 @@ export class ReportingPlugin logger: this.logger, }); + // Note: this must be called after ReportingCore.pluginStart + await store.start(); + this.logger.debug('Start complete'); })().catch((e) => { this.logger.error(`Error in Reporting start, reporting may not function properly`); diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 8d1cc4ca2c1f07..6195dd61a79841 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -14,7 +14,6 @@ export type ExperimentalFeatures = typeof allowedExperimentalValues; const allowedExperimentalValues = Object.freeze({ trustedAppsByPolicyEnabled: false, metricsEntitiesEnabled: false, - eventFilteringEnabled: false, hostIsolationEnabled: false, }); diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts index 5af5a8adf95b7f..aeee7077ec9c04 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts @@ -23,7 +23,7 @@ import { unmappedRule } from '../../objects/rule'; import { DETECTIONS_URL } from '../../urls/navigation'; describe('Alert details with unmapped fields', () => { - before(() => { + beforeEach(() => { cleanKibana(); esArchiverLoad('unmapped_fields'); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); diff --git a/x-pack/plugins/security_solution/public/app/search/index.test.ts b/x-pack/plugins/security_solution/public/app/search/index.test.ts index d6c36e89558d02..328395f9b85c9e 100644 --- a/x-pack/plugins/security_solution/public/app/search/index.test.ts +++ b/x-pack/plugins/security_solution/public/app/search/index.test.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { getSearchDeepLinksAndKeywords } from '.'; +import { getDeepLinksAndKeywords } from '.'; import { SecurityPageName } from '../../../common/constants'; describe('public search functions', () => { @@ -13,10 +13,9 @@ describe('public search functions', () => { const platinumLicense = 'platinum'; for (const pageName of Object.values(SecurityPageName)) { expect.assertions(Object.values(SecurityPageName).length * 2); - const basicLinkCount = - getSearchDeepLinksAndKeywords(pageName, basicLicense).searchDeepLinks?.length || 0; - const platinumLinks = getSearchDeepLinksAndKeywords(pageName, platinumLicense); - expect(platinumLinks.searchDeepLinks?.length).toBeGreaterThanOrEqual(basicLinkCount); + const basicLinkCount = getDeepLinksAndKeywords(pageName, basicLicense).deepLinks?.length || 0; + const platinumLinks = getDeepLinksAndKeywords(pageName, platinumLicense); + expect(platinumLinks.deepLinks?.length).toBeGreaterThanOrEqual(basicLinkCount); expect(platinumLinks.keywords?.length).not.toBe(null); } }); diff --git a/x-pack/plugins/security_solution/public/app/search/index.ts b/x-pack/plugins/security_solution/public/app/search/index.ts index 110356269e8917..93d931fc4d1370 100644 --- a/x-pack/plugins/security_solution/public/app/search/index.ts +++ b/x-pack/plugins/security_solution/public/app/search/index.ts @@ -11,7 +11,7 @@ import { Subject } from 'rxjs'; import { AppUpdater } from 'src/core/public'; import { LicenseType } from '../../../../licensing/common/types'; import { SecuritySubPluginNames, SecurityDeepLinks } from '../types'; -import { AppMeta } from '../../../../../../src/core/public'; +import { App } from '../../../../../../src/core/public'; const securityDeepLinks: SecurityDeepLinks = { detections: { @@ -198,10 +198,10 @@ const subpluginKeywords: { [key in SecuritySubPluginNames]: string[] } = { * @param subPluginName SubPluginName of the app to retrieve meta information for. * @param licenseType optional string for license level, if not provided basic is assumed. */ -export function getSearchDeepLinksAndKeywords( +export function getDeepLinksAndKeywords( subPluginName: SecuritySubPluginNames, licenseType?: LicenseType -): AppMeta { +): Pick { const baseRoutes = [...securityDeepLinks[subPluginName].base]; if ( licenseType === 'gold' || @@ -214,29 +214,27 @@ export function getSearchDeepLinksAndKeywords( if (premiumRoutes !== undefined) { return { keywords: subpluginKeywords[subPluginName], - searchDeepLinks: [...baseRoutes, ...premiumRoutes], + deepLinks: [...baseRoutes, ...premiumRoutes], }; } } return { keywords: subpluginKeywords[subPluginName], - searchDeepLinks: baseRoutes, + deepLinks: baseRoutes, }; } /** * A function that updates a subplugin's meta property as appropriate when license level changes. - * @param subPluginName SubPluginName of the app to register searchDeepLinks for + * @param subPluginName SubPluginName of the app to register deepLinks for * @param appUpdater an instance of appUpdater$ observable to update search links when needed. * @param licenseType A string representing the current license level. */ -export function registerSearchLinks( +export function registerDeepLinks( subPluginName: SecuritySubPluginNames, appUpdater?: Subject, licenseType?: LicenseType ) { if (appUpdater !== undefined) { - appUpdater.next(() => ({ - meta: getSearchDeepLinksAndKeywords(subPluginName, licenseType), - })); + appUpdater.next(() => ({ ...getDeepLinksAndKeywords(subPluginName, licenseType) })); } } diff --git a/x-pack/plugins/security_solution/public/app/types.ts b/x-pack/plugins/security_solution/public/app/types.ts index a617c6f14b9c48..77d5b99e1c3a3b 100644 --- a/x-pack/plugins/security_solution/public/app/types.ts +++ b/x-pack/plugins/security_solution/public/app/types.ts @@ -17,7 +17,7 @@ import { CombinedState, } from 'redux'; -import { AppMountParameters, AppSearchDeepLink } from '../../../../../src/core/public'; +import { AppMountParameters, AppDeepLink } from '../../../../../src/core/public'; import { StartServices } from '../types'; /** @@ -58,8 +58,8 @@ export type SecuritySubPluginKeyStore = export type SecuritySubPluginNames = keyof typeof SecurityPageName; interface SecurityDeepLink { - base: AppSearchDeepLink[]; - premium?: AppSearchDeepLink[]; + base: AppDeepLink[]; + premium?: AppDeepLink[]; } export type SecurityDeepLinks = { [key in SecuritySubPluginNames]: SecurityDeepLink }; diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index b1b3147f4f4941..af278b09e719c4 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -40,7 +40,6 @@ export const mockGlobalState: State = { { id: 'error-id-2', title: 'title-2', message: ['error-message-2'] }, ], enableExperimental: { - eventFilteringEnabled: false, trustedAppsByPolicyEnabled: false, metricsEntitiesEnabled: false, hostIsolationEnabled: false, diff --git a/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx b/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx index 02fbb4f4b02962..72a6de2a2de8d1 100644 --- a/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx +++ b/x-pack/plugins/security_solution/public/management/components/administration_list_page.tsx @@ -25,7 +25,6 @@ import { getEventFiltersListPath, getTrustedAppsListPath, } from '../common/routing'; -import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; /** Ensure that all flyouts z-index in Administation area show the flyout header */ const EuiPanelStyled = styled(EuiPanel)` @@ -44,7 +43,6 @@ interface AdministrationListPageProps { export const AdministrationListPage: FC = memo( ({ beta, title, subtitle, actions, children, headerBackComponent, ...otherProps }) => { - const isEventFilteringEnabled = useIsExperimentalFeatureEnabled('eventFilteringEnabled'); const badgeOptions = !beta ? undefined : { beta: true, text: BETA_BADGE_LABEL }; return ( @@ -77,18 +75,14 @@ export const AdministrationListPage: FC diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.test.tsx index cec3e34d9c98fd..c594aaa5c7e19d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/event_filter_delete_modal.test.tsx @@ -64,7 +64,6 @@ describe('When event filters delete modal is shown', () => { }; waitForAction = mockedContext.middlewareSpy.waitForAction; - mockedContext.setExperimentalFlag({ eventFilteringEnabled: true }); }); it('should display name of event filter in body message', async () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.test.tsx index 2fbabad746cad7..465f92dfda767f 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.test.tsx @@ -41,7 +41,6 @@ describe('When on the Event Filters List Page', () => { waitForAction = mockedContext.middlewareSpy.waitForAction; act(() => { - mockedContext.setExperimentalFlag({ eventFilteringEnabled: true }); history.push('/event_filters'); }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/index.tsx b/x-pack/plugins/security_solution/public/management/pages/index.tsx index 4be75117daedad..8273f1a6e55c20 100644 --- a/x-pack/plugins/security_solution/public/management/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/index.tsx @@ -25,7 +25,6 @@ import { SecurityPageName } from '../../../common/constants'; import { SpyRoute } from '../../common/utils/route/spy_routes'; import { useIngestEnabledCheck } from '../../common/hooks/endpoint/ingest_enabled'; import { EventFiltersContainer } from './event_filters'; -import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; const NoPermissions = memo(() => { return ( @@ -58,7 +57,6 @@ NoPermissions.displayName = 'NoPermissions'; export const ManagementContainer = memo(() => { const history = useHistory(); - const isEventFilteringEnabled = useIsExperimentalFeatureEnabled('eventFilteringEnabled'); const { allEnabled: isIngestEnabled } = useIngestEnabledCheck(); if (!isIngestEnabled) { @@ -70,10 +68,7 @@ export const ManagementContainer = memo(() => { - - {isEventFilteringEnabled && ( - - )} + { const [coreStart, startPlugins] = await core.getStartServices(); const { timelines: subPlugin } = await this.subPlugins(); @@ -300,7 +300,7 @@ export class Plugin implements IPlugin { const [coreStart, startPlugins] = await core.getStartServices(); const { management: managementSubPlugin } = await this.subPlugins(); @@ -366,19 +366,19 @@ export class Plugin implements IPlugin { if (currentLicense.type !== undefined) { - registerSearchLinks(SecurityPageName.network, this.networkUpdater$, currentLicense.type); - registerSearchLinks( + registerDeepLinks(SecurityPageName.network, this.networkUpdater$, currentLicense.type); + registerDeepLinks( SecurityPageName.detections, this.detectionsUpdater$, currentLicense.type ); - registerSearchLinks(SecurityPageName.hosts, this.hostsUpdater$, currentLicense.type); - registerSearchLinks(SecurityPageName.case, this.caseUpdater$, currentLicense.type); + registerDeepLinks(SecurityPageName.hosts, this.hostsUpdater$, currentLicense.type); + registerDeepLinks(SecurityPageName.case, this.caseUpdater$, currentLicense.type); } }); } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx index 0824dea0803ba9..2053b9a0da942d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx @@ -21,7 +21,6 @@ import { EventsTdContent } from '../../styles'; import * as i18n from '../translations'; import { DEFAULT_ICON_BUTTON_WIDTH } from '../../helpers'; import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector'; -import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { AddToCaseAction } from '../../../../../cases/components/timeline_actions/add_to_case_action'; import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline'; import { timelineSelectors } from '../../../../store/timeline'; @@ -89,8 +88,6 @@ const ActionsComponent: React.FC = ({ const emptyNotes: string[] = []; const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); - const isEventFilteringEnabled = useIsExperimentalFeatureEnabled('eventFilteringEnabled'); - const handleSelectEvent = useCallback( (event: React.ChangeEvent) => onRowSelected({ @@ -116,8 +113,8 @@ const ActionsComponent: React.FC = ({ const eventType = getEventType(ecsData); const isEventContextMenuEnabled = useMemo( - () => isEventFilteringEnabled && !!ecsData.event?.kind && ecsData.event?.kind[0] === 'event', - [ecsData.event?.kind, isEventFilteringEnabled] + () => !!ecsData.event?.kind && ecsData.event?.kind[0] === 'event', + [ecsData.event?.kind] ); return ( diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/mocks.ts index 85857301d5f399..cda42bdf3f585e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/mocks.ts @@ -92,6 +92,36 @@ export const createPackagePolicyWithInitialManifestMock = (): PackagePolicy => { artifact_manifest: { value: { artifacts: { + 'endpoint-eventfilterlist-linux-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-eventfilterlist-macos-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-eventfilterlist-windows-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, 'endpoint-exceptionlist-macos-v1': { compression_algorithm: 'zlib', encryption_algorithm: 'none', diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts index f471ace617a6dc..e0bbfc351a20f1 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts @@ -69,13 +69,16 @@ export interface ManifestManagerMockOptions { export const buildManifestManagerMockOptions = ( opts: Partial -): ManifestManagerMockOptions => ({ - cache: new LRU({ max: 10, maxAge: 1000 * 60 * 60 }), - exceptionListClient: listMock.getExceptionListClient(), - packagePolicyService: createPackagePolicyServiceMock(), - savedObjectsClient: savedObjectsClientMock.create(), - ...opts, -}); +): ManifestManagerMockOptions => { + const savedObjectMock = savedObjectsClientMock.create(); + return { + cache: new LRU({ max: 10, maxAge: 1000 * 60 * 60 }), + exceptionListClient: listMock.getExceptionListClient(savedObjectMock), + packagePolicyService: createPackagePolicyServiceMock(), + savedObjectsClient: savedObjectMock, + ...opts, + }; +}; export const buildManifestManagerContextMock = ( opts: Partial diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts index e1de39482428d7..7719dbf30c72bf 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts @@ -65,6 +65,9 @@ describe('ManifestManager', () => { const ARTIFACT_NAME_TRUSTED_APPS_MACOS = 'endpoint-trustlist-macos-v1'; const ARTIFACT_NAME_TRUSTED_APPS_WINDOWS = 'endpoint-trustlist-windows-v1'; const ARTIFACT_NAME_TRUSTED_APPS_LINUX = 'endpoint-trustlist-linux-v1'; + const ARTIFACT_NAME_EVENT_FILTERS_MACOS = 'endpoint-eventfilterlist-macos-v1'; + const ARTIFACT_NAME_EVENT_FILTERS_WINDOWS = 'endpoint-eventfilterlist-windows-v1'; + const ARTIFACT_NAME_EVENT_FILTERS_LINUX = 'endpoint-eventfilterlist-linux-v1'; let ARTIFACTS: InternalArtifactCompleteSchema[] = []; let ARTIFACTS_BY_ID: { [K: string]: InternalArtifactCompleteSchema } = {}; @@ -219,6 +222,9 @@ describe('ManifestManager', () => { ARTIFACT_NAME_TRUSTED_APPS_MACOS, ARTIFACT_NAME_TRUSTED_APPS_WINDOWS, ARTIFACT_NAME_TRUSTED_APPS_LINUX, + ARTIFACT_NAME_EVENT_FILTERS_MACOS, + ARTIFACT_NAME_EVENT_FILTERS_WINDOWS, + ARTIFACT_NAME_EVENT_FILTERS_LINUX, ]; const getArtifactIds = (artifacts: InternalArtifactSchema[]) => [ @@ -249,6 +255,11 @@ describe('ManifestManager', () => { context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({}); context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]); + context.savedObjectsClient.create = jest + .fn() + .mockImplementation((type: string, object: InternalManifestSchema) => ({ + attributes: object, + })); const manifest = await manifestManager.buildNewManifest(); expect(manifest?.getSchemaVersion()).toStrictEqual('v1'); @@ -257,7 +268,7 @@ describe('ManifestManager', () => { const artifacts = manifest.getAllArtifacts(); - expect(artifacts.length).toBe(6); + expect(artifacts.length).toBe(9); expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); expect(artifacts.every(isCompressed)).toBe(true); @@ -280,6 +291,11 @@ describe('ManifestManager', () => { [ENDPOINT_LIST_ID]: { macos: [exceptionListItem] }, [ENDPOINT_TRUSTED_APPS_LIST_ID]: { linux: [trustedAppListItem] }, }); + context.savedObjectsClient.create = jest + .fn() + .mockImplementation((type: string, object: InternalManifestSchema) => ({ + attributes: object, + })); context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]); const manifest = await manifestManager.buildNewManifest(); @@ -290,7 +306,7 @@ describe('ManifestManager', () => { const artifacts = manifest.getAllArtifacts(); - expect(artifacts.length).toBe(6); + expect(artifacts.length).toBe(9); expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); expect(artifacts.every(isCompressed)).toBe(true); @@ -304,6 +320,9 @@ describe('ManifestManager', () => { expect(await uncompressArtifact(artifacts[5])).toStrictEqual({ entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), }); + expect(await uncompressArtifact(artifacts[6])).toStrictEqual({ entries: [] }); + expect(await uncompressArtifact(artifacts[7])).toStrictEqual({ entries: [] }); + expect(await uncompressArtifact(artifacts[8])).toStrictEqual({ entries: [] }); for (const artifact of artifacts) { expect(manifest.isDefaultArtifact(artifact)).toBe(true); @@ -323,7 +342,11 @@ describe('ManifestManager', () => { [ENDPOINT_LIST_ID]: { macos: [exceptionListItem] }, }); context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]); - + context.savedObjectsClient.create = jest + .fn() + .mockImplementation((type: string, object: InternalManifestSchema) => ({ + attributes: object, + })); const oldManifest = await manifestManager.buildNewManifest(); context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({ @@ -339,7 +362,7 @@ describe('ManifestManager', () => { const artifacts = manifest.getAllArtifacts(); - expect(artifacts.length).toBe(6); + expect(artifacts.length).toBe(9); expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); expect(artifacts.every(isCompressed)).toBe(true); @@ -351,6 +374,9 @@ describe('ManifestManager', () => { expect(await uncompressArtifact(artifacts[5])).toStrictEqual({ entries: translateToEndpointExceptions([trustedAppListItem], 'v1'), }); + expect(await uncompressArtifact(artifacts[6])).toStrictEqual({ entries: [] }); + expect(await uncompressArtifact(artifacts[7])).toStrictEqual({ entries: [] }); + expect(await uncompressArtifact(artifacts[8])).toStrictEqual({ entries: [] }); for (const artifact of artifacts) { expect(manifest.isDefaultArtifact(artifact)).toBe(true); @@ -384,6 +410,12 @@ describe('ManifestManager', () => { TEST_POLICY_ID_2, ]); + context.savedObjectsClient.create = jest + .fn() + .mockImplementation((type: string, object: InternalManifestSchema) => ({ + attributes: object, + })); + const manifest = await manifestManager.buildNewManifest(); expect(manifest?.getSchemaVersion()).toStrictEqual('v1'); @@ -392,7 +424,7 @@ describe('ManifestManager', () => { const artifacts = manifest.getAllArtifacts(); - expect(artifacts.length).toBe(7); + expect(artifacts.length).toBe(10); expect(getArtifactIds(artifacts)).toStrictEqual(SUPPORTED_ARTIFACT_NAMES); expect(artifacts.every(isCompressed)).toBe(true); @@ -412,6 +444,9 @@ describe('ManifestManager', () => { 'v1' ), }); + expect(await uncompressArtifact(artifacts[7])).toStrictEqual({ entries: [] }); + expect(await uncompressArtifact(artifacts[8])).toStrictEqual({ entries: [] }); + expect(await uncompressArtifact(artifacts[9])).toStrictEqual({ entries: [] }); for (const artifact of artifacts.slice(0, 5)) { expect(manifest.isDefaultArtifact(artifact)).toBe(true); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts index fe4aba165d2bda..6c25b6152938f7 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts @@ -361,10 +361,7 @@ export class ManifestManager { const results = await Promise.all([ this.buildExceptionListArtifacts(), this.buildTrustedAppsArtifacts(), - // If Endpoint Event Filtering feature is ON, then add in the exceptions for them - ...(this.experimentalFeatures.eventFilteringEnabled - ? [this.buildEventFiltersArtifacts()] - : []), + this.buildEventFiltersArtifacts(), ]); const manifest = new Manifest({ diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.test.tsx b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.test.tsx index 4474b9f288570e..e43db6b86f8b90 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.test.tsx +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/index.test.tsx @@ -32,7 +32,6 @@ const mockDeps = { experimentalFeatures: { trustedAppsByPolicyEnabled: false, metricsEntitiesEnabled: false, - eventFilteringEnabled: false, hostIsolationEnabled: false, }, service: {} as EndpointAppContextService, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 134f58236cfee3..af6cdd1d672a17 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -15978,16 +15978,16 @@ "xpack.ml.ruleEditor.selectRuleAction.orText": "OR ", "xpack.ml.ruleEditor.typicalAppliesTypeText": "通常", "xpack.ml.sampleDataLinkLabel": "ML ジョブ", - "xpack.ml.searchDeepLink.anomalyDetection": "異常検知", - "xpack.ml.searchDeepLink.calendarSettings": "カレンダー", - "xpack.ml.searchDeepLink.dataFrameAnalytics": "データフレーム分析", - "xpack.ml.searchDeepLink.dataVisualizer": "データビジュアライザー", - "xpack.ml.searchDeepLink.fileUpload": "ファイルアップロード", - "xpack.ml.searchDeepLink.filterListsSettings": "フィルターリスト", - "xpack.ml.searchDeepLink.indexDataVisualizer": "インデックスデータビジュアライザー", - "xpack.ml.searchDeepLink.overview": "概要", - "xpack.ml.searchDeepLink.settings": "設定", - "xpack.ml.searchDeepLink.trainedModels": "学習済みモデル", + "xpack.ml.deepLink.anomalyDetection": "異常検知", + "xpack.ml.deepLink.calendarSettings": "カレンダー", + "xpack.ml.deepLink.dataFrameAnalytics": "データフレーム分析", + "xpack.ml.deepLink.dataVisualizer": "データビジュアライザー", + "xpack.ml.deepLink.fileUpload": "ファイルアップロード", + "xpack.ml.deepLink.filterListsSettings": "フィルターリスト", + "xpack.ml.deepLink.indexDataVisualizer": "インデックスデータビジュアライザー", + "xpack.ml.deepLink.overview": "概要", + "xpack.ml.deepLink.settings": "設定", + "xpack.ml.deepLink.trainedModels": "学習済みモデル", "xpack.ml.settings.anomalyDetection.anomalyDetectionTitle": "異常検知", "xpack.ml.settings.anomalyDetection.calendarsText": "システム停止日や祝日など、異常値を生成したくないイベントについては、カレンダーに予定されているイベントのリストを登録できます。", "xpack.ml.settings.anomalyDetection.calendarsTitle": "カレンダー", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 67677f86ddbf78..c8376b72daef17 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -16203,16 +16203,16 @@ "xpack.ml.ruleEditor.selectRuleAction.orText": "或 ", "xpack.ml.ruleEditor.typicalAppliesTypeText": "典型", "xpack.ml.sampleDataLinkLabel": "ML 作业", - "xpack.ml.searchDeepLink.anomalyDetection": "异常检测", - "xpack.ml.searchDeepLink.calendarSettings": "日历", - "xpack.ml.searchDeepLink.dataFrameAnalytics": "数据帧分析", - "xpack.ml.searchDeepLink.dataVisualizer": "数据可视化工具", - "xpack.ml.searchDeepLink.fileUpload": "文件上传", - "xpack.ml.searchDeepLink.filterListsSettings": "筛选列表", - "xpack.ml.searchDeepLink.indexDataVisualizer": "索引数据可视化工具", - "xpack.ml.searchDeepLink.overview": "概览", - "xpack.ml.searchDeepLink.settings": "设置", - "xpack.ml.searchDeepLink.trainedModels": "已训练模型", + "xpack.ml.deepLink.anomalyDetection": "异常检测", + "xpack.ml.deepLink.calendarSettings": "日历", + "xpack.ml.deepLink.dataFrameAnalytics": "数据帧分析", + "xpack.ml.deepLink.dataVisualizer": "数据可视化工具", + "xpack.ml.deepLink.fileUpload": "文件上传", + "xpack.ml.deepLink.filterListsSettings": "筛选列表", + "xpack.ml.deepLink.indexDataVisualizer": "索引数据可视化工具", + "xpack.ml.deepLink.overview": "概览", + "xpack.ml.deepLink.settings": "设置", + "xpack.ml.deepLink.trainedModels": "已训练模型", "xpack.ml.settings.anomalyDetection.anomalyDetectionTitle": "异常检测", "xpack.ml.settings.anomalyDetection.calendarsSummaryCount": "您有 {calendarsCountBadge} 个{calendarsCount, plural, other {日历}}", "xpack.ml.settings.anomalyDetection.calendarsText": "日志包含不应生成异常的已计划事件列表,例如已计划系统中断或公共假期。", diff --git a/x-pack/plugins/uptime/public/apps/plugin.ts b/x-pack/plugins/uptime/public/apps/plugin.ts index 0832274f0785a1..80a131676951e4 100644 --- a/x-pack/plugins/uptime/public/apps/plugin.ts +++ b/x-pack/plugins/uptime/public/apps/plugin.ts @@ -104,28 +104,26 @@ export class UptimePlugin order: 8400, title: PLUGIN.TITLE, category: DEFAULT_APP_CATEGORIES.observability, - meta: { - keywords: [ - 'Synthetics', - 'pings', - 'checks', - 'availability', - 'response duration', - 'response time', - 'outside in', - 'reachability', - 'reachable', - 'digital', - 'performance', - 'web performance', - 'web perf', - ], - searchDeepLinks: [ - { id: 'Down monitors', title: 'Down monitors', path: '/?statusFilter=down' }, - { id: 'Certificates', title: 'TLS Certificates', path: '/certificates' }, - { id: 'Settings', title: 'Settings', path: '/settings' }, - ], - }, + keywords: [ + 'Synthetics', + 'pings', + 'checks', + 'availability', + 'response duration', + 'response time', + 'outside in', + 'reachability', + 'reachable', + 'digital', + 'performance', + 'web performance', + 'web perf', + ], + deepLinks: [ + { id: 'Down monitors', title: 'Down monitors', path: '/?statusFilter=down' }, + { id: 'Certificates', title: 'TLS Certificates', path: '/certificates' }, + { id: 'Settings', title: 'Settings', path: '/settings' }, + ], mount: async (params: AppMountParameters) => { const [coreStart, corePlugins] = await core.getStartServices(); diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index 1cc7c87f3a1a84..7578abbad33e75 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -38,9 +38,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); after(async () => await esArchiver.unload('infra/metrics_and_logs')); - it('renders the waffle map for dates with data', async () => { + it('renders the waffle map and tooltips for dates with data', async () => { await pageObjects.infraHome.goToTime(DATE_WITH_DATA); await pageObjects.infraHome.getWaffleMap(); + await pageObjects.infraHome.getWaffleMapTooltips(); }); it('renders an empty data prompt for dates with no data', async () => { diff --git a/x-pack/test/functional/apps/lens/index.ts b/x-pack/test/functional/apps/lens/index.ts index ab7cee13ffebda..d0466b8814fec1 100644 --- a/x-pack/test/functional/apps/lens/index.ts +++ b/x-pack/test/functional/apps/lens/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context.d'; +import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, loadTestFile }: FtrProviderContext) { const browser = getService('browser'); diff --git a/x-pack/test/functional/ftr_provider_context.d.ts b/x-pack/test/functional/ftr_provider_context.ts similarity index 74% rename from x-pack/test/functional/ftr_provider_context.d.ts rename to x-pack/test/functional/ftr_provider_context.ts index 24f5087ef7fe2f..e757164fa1de92 100644 --- a/x-pack/test/functional/ftr_provider_context.d.ts +++ b/x-pack/test/functional/ftr_provider_context.ts @@ -5,9 +5,10 @@ * 2.0. */ -import { GenericFtrProviderContext } from '@kbn/test'; +import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test'; import { pageObjects } from './page_objects'; import { services } from './services'; export type FtrProviderContext = GenericFtrProviderContext; +export class FtrService extends GenericFtrService {} diff --git a/x-pack/test/functional/page_objects/infra_home_page.ts b/x-pack/test/functional/page_objects/infra_home_page.ts index 04dfbe5da00024..2f4575d45cc20f 100644 --- a/x-pack/test/functional/page_objects/infra_home_page.ts +++ b/x-pack/test/functional/page_objects/infra_home_page.ts @@ -5,6 +5,7 @@ * 2.0. */ +import expect from '@kbn/expect/expect.js'; import testSubjSelector from '@kbn/test-subj-selector'; import { FtrProviderContext } from '../ftr_provider_context'; @@ -34,6 +35,34 @@ export function InfraHomePageProvider({ getService }: FtrProviderContext) { return await testSubjects.find('waffleMap'); }, + async getWaffleMapTooltips() { + const node = await testSubjects.findAll('nodeContainer'); + await node[0].moveMouseTo(); + const tooltip = await testSubjects.find('conditionalTooltipContent-demo-stack-redis-01'); + const metrics = await tooltip.findAllByTestSubject('conditionalTooltipContent-metric'); + const values = await tooltip.findAllByTestSubject('conditionalTooltipContent-value'); + expect(await metrics[0].getVisibleText()).to.be('CPU usage'); + expect(await values[0].getVisibleText()).to.be('1%'); + expect(await metrics[1].getVisibleText()).to.be('Memory usage'); + expect(await values[1].getVisibleText()).to.be('15.9%'); + expect(await metrics[2].getVisibleText()).to.be('Outbound traffic'); + expect(await values[2].getVisibleText()).to.be('71.9kbit/s'); + expect(await metrics[3].getVisibleText()).to.be('Inbound traffic'); + expect(await values[3].getVisibleText()).to.be('25.6kbit/s'); + await node[1].moveMouseTo(); + const tooltip2 = await testSubjects.find('conditionalTooltipContent-demo-stack-nginx-01'); + const metrics2 = await tooltip2.findAllByTestSubject('conditionalTooltipContent-metric'); + const values2 = await tooltip2.findAllByTestSubject('conditionalTooltipContent-value'); + expect(await metrics2[0].getVisibleText()).to.be('CPU usage'); + expect(await values2[0].getVisibleText()).to.be('1.1%'); + expect(await metrics2[1].getVisibleText()).to.be('Memory usage'); + expect(await values2[1].getVisibleText()).to.be('18%'); + expect(await metrics2[2].getVisibleText()).to.be('Outbound traffic'); + expect(await values2[2].getVisibleText()).to.be('256.3kbit/s'); + expect(await metrics2[3].getVisibleText()).to.be('Inbound traffic'); + expect(await values2[3].getVisibleText()).to.be('255.1kbit/s'); + }, + async openInvenotrySwitcher() { await testSubjects.click('openInventorySwitcher'); return await testSubjects.find('goToHost'); diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index d8bc9f6444f646..44348d1ad0d9c4 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -248,6 +248,42 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { relative_url: '/api/fleet/artifacts/endpoint-trustlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', }, + 'endpoint-eventfilterlist-linux-v1': { + compression_algorithm: 'zlib', + decoded_sha256: + 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: + 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-eventfilterlist-macos-v1': { + compression_algorithm: 'zlib', + decoded_sha256: + 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: + 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-eventfilterlist-windows-v1': { + compression_algorithm: 'zlib', + decoded_sha256: + 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: + 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, }, // The manifest version could have changed when the Policy was updated because the // policy details page ensures that a save action applies the udpated policy on top @@ -416,6 +452,42 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { relative_url: '/api/fleet/artifacts/endpoint-trustlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', }, + 'endpoint-eventfilterlist-linux-v1': { + compression_algorithm: 'zlib', + decoded_sha256: + 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: + 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-eventfilterlist-macos-v1': { + compression_algorithm: 'zlib', + decoded_sha256: + 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: + 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-eventfilterlist-windows-v1': { + compression_algorithm: 'zlib', + decoded_sha256: + 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: + 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, }, // The manifest version could have changed when the Policy was updated because the // policy details page ensures that a save action applies the udpated policy on top @@ -582,6 +654,42 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { relative_url: '/api/fleet/artifacts/endpoint-trustlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', }, + 'endpoint-eventfilterlist-linux-v1': { + compression_algorithm: 'zlib', + decoded_sha256: + 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: + 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-eventfilterlist-macos-v1': { + compression_algorithm: 'zlib', + decoded_sha256: + 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: + 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-eventfilterlist-windows-v1': { + compression_algorithm: 'zlib', + decoded_sha256: + 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: + 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, }, // The manifest version could have changed when the Policy was updated because the // policy details page ensures that a save action applies the udpated policy on top diff --git a/yarn.lock b/yarn.lock index f1f421e2a766f5..9967cedea9fde1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2678,6 +2678,10 @@ version "0.0.0" uid "" +"@kbn/mapbox-gl@link:bazel-bin/packages/kbn-mapbox-gl/npm_module": + version "0.0.0" + uid "" + "@kbn/monaco@link:packages/kbn-monaco": version "0.0.0" uid ""