diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 516de74378cbca..0993876f98a6a7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -262,8 +262,31 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib # Enterprise Search # Shared -/x-pack/plugins/enterprise_search/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/* @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/common/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/public/* @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/public/applications/* @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/public/applications/enterprise_search/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/public/applications/shared/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/public/applications/__mocks__/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/* @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/lib/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/__mocks__/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/enterprise_search/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/lib/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/routes/enterprise_search/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/saved_objects/enterprise_search/ @elastic/enterprise-search-frontend /x-pack/test/functional_enterprise_search/ @elastic/enterprise-search-frontend +# App Search +/x-pack/plugins/enterprise_search/public/applications/app_search/ @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/routes/app_search/ @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/app_search/ @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/saved_objects/app_search/ @elastic/app-search-frontend +# Workplace Search +/x-pack/plugins/enterprise_search/public/applications/workplace_search/ @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/routes/workplace_search/ @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/workplace_search/ @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/saved_objects/workplace_search/ @elastic/workplace-search-frontend # Elasticsearch UI /src/plugins/dev_tools/ @elastic/es-ui diff --git a/STYLEGUIDE.md b/STYLEGUIDE.md index 94bb40ab3ff2e1..cb75452a28cd29 100644 --- a/STYLEGUIDE.md +++ b/STYLEGUIDE.md @@ -589,6 +589,24 @@ Do not use setters, they cause more problems than they can solve. [sideeffect]: http://en.wikipedia.org/wiki/Side_effect_(computer_science) +### Avoid circular dependencies + +As part of a future effort to use correct and idempotent build tools we need our code to be +able to be represented as a directed acyclic graph. We must avoid having circular dependencies +both on code and type imports to achieve that. One of the most critical parts is the plugins +code. We've developed a tool to identify plugins with circular dependencies which +has allowed us to build a list of plugins who have circular dependencies +between each other. + +When building plugins we should avoid importing from plugins +who are known to have circular dependencies at the moment as well as introducing +new circular dependencies. You can run the same tool we use on our CI locally by +typing `node scripts/find_plugins_with_circular_deps --debug`. It will error out in +case new circular dependencies has been added with your changes +(which will also happen in the CI) as well as print out the current list of +the known circular dependencies which, as mentioned before, should not be imported +by your code until the circular dependencies on these have been solved. + ## SASS files When writing a new component, create a sibling SASS file of the same name and import directly into the **top** of the JS/TS component file. Doing so ensures the styles are never separated or lost on import and allows for better modularization (smaller individual plugin asset footprint). diff --git a/docs/api/logstash-configuration-management/create-logstash.asciidoc b/docs/api/logstash-configuration-management/create-logstash.asciidoc index b608f4ee698f74..9bd5a9028ee9af 100644 --- a/docs/api/logstash-configuration-management/create-logstash.asciidoc +++ b/docs/api/logstash-configuration-management/create-logstash.asciidoc @@ -20,9 +20,6 @@ experimental[] Create a centrally-managed Logstash pipeline, or update an existi [[logstash-configuration-management-api-create-request-body]] ==== Request body -`id`:: - (Required, string) The pipeline ID. - `description`:: (Optional, string) The pipeline description. diff --git a/docs/api/saved-objects/create.asciidoc b/docs/api/saved-objects/create.asciidoc index 50809a1bd5d4e2..d7a368034ef07f 100644 --- a/docs/api/saved-objects/create.asciidoc +++ b/docs/api/saved-objects/create.asciidoc @@ -9,11 +9,13 @@ experimental[] Create {kib} saved objects. [[saved-objects-api-create-request]] ==== Request -`POST :/api/saved_objects/` + +`POST :/api/saved_objects/` `POST :/api/saved_objects//` -`POST :/s//saved_objects/` +`POST :/s//api/saved_objects/` + +`POST :/s//api/saved_objects//` [[saved-objects-api-create-path-params]] ==== Path parameters diff --git a/docs/developer/best-practices/typescript.asciidoc b/docs/developer/best-practices/typescript.asciidoc index 6d298f92b841eb..f6db3fdffcb6ad 100644 --- a/docs/developer/best-practices/typescript.asciidoc +++ b/docs/developer/best-practices/typescript.asciidoc @@ -19,7 +19,7 @@ More details are available in the https://www.typescriptlang.org/docs/handbook/p ==== Caveats This architecture imposes several limitations to which we must comply: -- Projects cannot have circular dependencies. Even though the Kibana platform doesn't support circular dependencies between Kibana plugins, TypeScript (and ES6 modules) does allow circular imports between files. So in theory, you may face a problem when migrating to the TS project references and you will have to resolve this circular dependency. https://github.com/elastic/kibana/issues/78162 is going to provide a tool to find such problem places. +- Projects cannot have circular dependencies. Even though the Kibana platform doesn't support circular dependencies between Kibana plugins, TypeScript (and ES6 modules) does allow circular imports between files. So in theory, you may face a problem when migrating to the TS project references and you will have to resolve this circular dependency. We've built a tool that can be used to find such problems. Please read the prerequisites section below to know how to use it. - A project must emit its type declaration. It's not always possible to generate a type declaration if the compiler cannot infer a type. There are two basic cases: 1. Your plugin exports a type inferring an internal type declared in Kibana codebase. In this case, you'll have to either export an internal type or to declare an exported type explicitly. @@ -30,6 +30,8 @@ This architecture imposes several limitations to which we must comply: Since project refs rely on generated `d.ts` files, the migration order does matter. You can migrate your plugin only when all the plugin dependencies already have migrated. It creates a situation where commonly used plugins (such as `data` or `kibana_react`) have to migrate first. Run `node scripts/find_plugins_without_ts_refs.js --id your_plugin_id` to get a list of plugins that should be switched to TS project refs to unblock your plugin migration. +Additionally, in order to migrate into project refs, you also need to make sure your plugin doesn't have circular dependencies with other plugins both on code and type imports. We run a job in the CI for each PR trying to find if new circular dependencies are being added which runs our tool with `node scripts/find_plugins_with_circular_deps`. However there are also a couple of circular dependencies already identified and that are in an allowed list to be solved. You also need to make sure your plugin don't rely in any other plugin into that allowed list. For a complete overview of the circular dependencies both found and in the allowed list as well as the complete circular dependencies path please run the following script locally with the debug flag `node scripts/find_plugins_with_circular_deps --debug` . + [discrete] ==== Implementation - Make sure all the plugins listed as dependencies in *requiredPlugins*, *optionalPlugins* & *requiredBundles* properties of `kibana.json` manifest file have migrated to TS project references. diff --git a/docs/developer/contributing/development-unit-tests.asciidoc b/docs/developer/contributing/development-unit-tests.asciidoc index 5322106b17ac14..d5f5bc76b3302f 100644 --- a/docs/developer/contributing/development-unit-tests.asciidoc +++ b/docs/developer/contributing/development-unit-tests.asciidoc @@ -20,11 +20,13 @@ yarn test:mocha == Jest Jest tests are stored in the same directory as source code files with the `.test.{js,mjs,ts,tsx}` suffix. -*Running Jest Unit Tests* +Each plugin and package contains it's own `jest.config.js` file to define its root, and any overrides +to the jest-preset provided by `@kbn/test`. When working on a single plugin or package, you will find +it's more efficient to supply the Jest configuration file when running. ["source","shell"] ----------- -yarn test:jest +yarn jest --config src/plugins/discover/jest.config.js ----------- [discrete] diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 5ee71316105840..e515abee6014c5 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -507,8 +507,8 @@ Kibana. |or -|{kib-repo}blob/{branch}/x-pack/plugins/spaces[spaces] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/x-pack/plugins/spaces/README.md[spaces] +|See Configuring Kibana Spaces. |{kib-repo}blob/{branch}/x-pack/plugins/stack_alerts/README.md[stackAlerts] diff --git a/docs/development/core/public/kibana-plugin-core-public.app.exactroute.md b/docs/development/core/public/kibana-plugin-core-public.app.exactroute.md index d1e0be17a92b24..eb050b62c7d43b 100644 --- a/docs/development/core/public/kibana-plugin-core-public.app.exactroute.md +++ b/docs/development/core/public/kibana-plugin-core-public.app.exactroute.md @@ -18,7 +18,7 @@ exactRoute?: boolean; ```ts core.application.register({ id: 'my_app', - title: 'My App' + title: 'My App', exactRoute: true, mount: () => { ... }, }) 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 7bdee9dc4c53eb..8e8bae5ad9c586 100644 --- a/docs/development/core/public/kibana-plugin-core-public.app.md +++ b/docs/development/core/public/kibana-plugin-core-public.app.md @@ -27,6 +27,7 @@ export interface App | [mount](./kibana-plugin-core-public.app.mount.md) | AppMount<HistoryLocationState> | AppMountDeprecated<HistoryLocationState> | A mount function called when the user navigates to this app's route. May have signature of [AppMount](./kibana-plugin-core-public.appmount.md) or [AppMountDeprecated](./kibana-plugin-core-public.appmountdeprecated.md). | | [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. | +| [searchDeepLinks](./kibana-plugin-core-public.app.searchdeeplinks.md) | AppSearchDeepLink[] | Array of links that represent secondary in-app locations for the app. | | [status](./kibana-plugin-core-public.app.status.md) | AppStatus | The initial status of the application. Defaulting to accessible | | [title](./kibana-plugin-core-public.app.title.md) | string | The title of the application. | | [tooltip](./kibana-plugin-core-public.app.tooltip.md) | string | A tooltip shown when hovering over app link. | diff --git a/docs/development/core/public/kibana-plugin-core-public.app.searchdeeplinks.md b/docs/development/core/public/kibana-plugin-core-public.app.searchdeeplinks.md new file mode 100644 index 00000000000000..667fddbc212a55 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.app.searchdeeplinks.md @@ -0,0 +1,42 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [App](./kibana-plugin-core-public.app.md) > [searchDeepLinks](./kibana-plugin-core-public.app.searchdeeplinks.md) + +## App.searchDeepLinks property + +Array of links that represent secondary in-app locations for the app. + +Signature: + +```typescript +searchDeepLinks?: AppSearchDeepLink[]; +``` + +## 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 for more details. + +## Example + +The `path` property on deep links should not include the application's `appRoute`: + +```ts +core.application.register({ + id: 'my_app', + title: 'My App', + searchDeepLinks: [ + { id: 'sub1', title: 'Sub1', path: '/sub1' }, + { + id: 'sub2', + title: 'Sub2', + searchDeepLinks: [ + { id: 'subsub', title: 'SubSub', path: '/sub2/sub' } + ] + } + ], + mount: () => { ... }, +}) + +``` +Will produce deep links on these paths: - `/app/my_app/sub1` - `/app/my_app/sub2/sub` + diff --git a/docs/development/core/public/kibana-plugin-core-public.appsearchdeeplink.md b/docs/development/core/public/kibana-plugin-core-public.appsearchdeeplink.md new file mode 100644 index 00000000000000..7e5ccf7d06ed19 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.appsearchdeeplink.md @@ -0,0 +1,24 @@ + + +[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[]; +} | { + path?: string; + searchDeepLinks: AppSearchDeepLink[]; +}); +``` 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 1232b7f9402552..b6f404c3d11aa3 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 6a90fd49f1d667..5f656b9ca510d6 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -138,6 +138,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [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 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. | | [AppMountDeprecated](./kibana-plugin-core-public.appmountdeprecated.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) | @@ -160,6 +161,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [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) | | | [PublicAppInfo](./kibana-plugin-core-public.publicappinfo.md) | Public information about a registered [application](./kibana-plugin-core-public.app.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. | | [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value | | [SavedObjectAttributeSingle](./kibana-plugin-core-public.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | 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 3717dc847db25b..d56b0ac58cd9b1 100644 --- a/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md +++ b/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md @@ -9,9 +9,10 @@ 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; + 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 new file mode 100644 index 00000000000000..9814f0408d0474 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.publicappsearchdeeplinkinfo.md @@ -0,0 +1,15 @@ + + +[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[]; +}; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.callasinternaluser.md b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.callasinternaluser.md index 2e235485711c6e..7c8cc18d24e299 100644 --- a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.callasinternaluser.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.callasinternaluser.md @@ -4,6 +4,11 @@ ## LegacyClusterClient.callAsInternalUser property +> Warning: This API is now obsolete. +> +> Use [IClusterClient.asInternalUser](./kibana-plugin-core-server.iclusterclient.asinternaluser.md). +> + Calls specified endpoint with provided clientParams on behalf of the Kibana internal user. See [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md). Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md index 9130f9adde76e0..7517acc59ac801 100644 --- a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md @@ -4,6 +4,11 @@ ## LegacyScopedClusterClient.callAsCurrentUser() method +> Warning: This API is now obsolete. +> +> Use [IScopedClusterClient.asCurrentUser](./kibana-plugin-core-server.iscopedclusterclient.ascurrentuser.md). +> + Calls specified `endpoint` with provided `clientParams` on behalf of the user initiated request to the Kibana server (via HTTP request headers). See [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md). Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md index bf95782299e720..b683d3945f9ff0 100644 --- a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md @@ -4,6 +4,11 @@ ## LegacyScopedClusterClient.callAsInternalUser() method +> Warning: This API is now obsolete. +> +> Use [IScopedClusterClient.asInternalUser](./kibana-plugin-core-server.iscopedclusterclient.asinternaluser.md). +> + Calls specified `endpoint` with provided `clientParams` on behalf of the Kibana internal user. See [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md). Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsincrementcounteroptions.initialize.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsincrementcounteroptions.initialize.md new file mode 100644 index 00000000000000..61091306d0dbc8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsincrementcounteroptions.initialize.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsIncrementCounterOptions](./kibana-plugin-core-server.savedobjectsincrementcounteroptions.md) > [initialize](./kibana-plugin-core-server.savedobjectsincrementcounteroptions.initialize.md) + +## SavedObjectsIncrementCounterOptions.initialize property + +(default=false) If true, sets all the counter fields to 0 if they don't already exist. Existing fields will be left as-is and won't be incremented. + +Signature: + +```typescript +initialize?: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsincrementcounteroptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsincrementcounteroptions.md index 6077945ddd376c..68e9bb09456cd5 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsincrementcounteroptions.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsincrementcounteroptions.md @@ -15,6 +15,7 @@ export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOpt | Property | Type | Description | | --- | --- | --- | -| [migrationVersion](./kibana-plugin-core-server.savedobjectsincrementcounteroptions.migrationversion.md) | SavedObjectsMigrationVersion | | -| [refresh](./kibana-plugin-core-server.savedobjectsincrementcounteroptions.refresh.md) | MutatingOperationRefreshSetting | The Elasticsearch Refresh setting for this operation | +| [initialize](./kibana-plugin-core-server.savedobjectsincrementcounteroptions.initialize.md) | boolean | (default=false) If true, sets all the counter fields to 0 if they don't already exist. Existing fields will be left as-is and won't be incremented. | +| [migrationVersion](./kibana-plugin-core-server.savedobjectsincrementcounteroptions.migrationversion.md) | SavedObjectsMigrationVersion | [SavedObjectsMigrationVersion](./kibana-plugin-core-server.savedobjectsmigrationversion.md) | +| [refresh](./kibana-plugin-core-server.savedobjectsincrementcounteroptions.refresh.md) | MutatingOperationRefreshSetting | (default='wait\_for') The Elasticsearch refresh setting for this operation. See [MutatingOperationRefreshSetting](./kibana-plugin-core-server.mutatingoperationrefreshsetting.md) | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsincrementcounteroptions.migrationversion.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsincrementcounteroptions.migrationversion.md index 417db99fd5a27a..aff80138d61cf3 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsincrementcounteroptions.migrationversion.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsincrementcounteroptions.migrationversion.md @@ -4,6 +4,8 @@ ## SavedObjectsIncrementCounterOptions.migrationVersion property +[SavedObjectsMigrationVersion](./kibana-plugin-core-server.savedobjectsmigrationversion.md) + Signature: ```typescript diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsincrementcounteroptions.refresh.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsincrementcounteroptions.refresh.md index 31d957ca30a3e4..4f217cc223d46e 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsincrementcounteroptions.refresh.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsincrementcounteroptions.refresh.md @@ -4,7 +4,7 @@ ## SavedObjectsIncrementCounterOptions.refresh property -The Elasticsearch Refresh setting for this operation +(default='wait\_for') The Elasticsearch refresh setting for this operation. See [MutatingOperationRefreshSetting](./kibana-plugin-core-server.mutatingoperationrefreshsetting.md) Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md index f3a2ee38cbdbd7..dc62cacf6741bc 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md @@ -4,26 +4,53 @@ ## SavedObjectsRepository.incrementCounter() method -Increases a counter field by one. Creates the document if one doesn't exist for the given id. +Increments all the specified counter fields by one. Creates the document if one doesn't exist for the given id. Signature: ```typescript -incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise; +incrementCounter(type: string, id: string, counterFieldNames: string[], options?: SavedObjectsIncrementCounterOptions): Promise; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| type | string | | -| id | string | | -| counterFieldName | string | | -| options | SavedObjectsIncrementCounterOptions | | +| type | string | The type of saved object whose fields should be incremented | +| id | string | The id of the document whose fields should be incremented | +| counterFieldNames | string[] | An array of field names to increment | +| options | SavedObjectsIncrementCounterOptions | [SavedObjectsIncrementCounterOptions](./kibana-plugin-core-server.savedobjectsincrementcounteroptions.md) | Returns: `Promise` -{promise} +The saved object after the specified fields were incremented + +## Remarks + +When supplying a field name like `stats.api.counter` the field name will be used as-is to create a document like: `{attributes: {'stats.api.counter': 1}}` It will not create a nested structure like: `{attributes: {stats: {api: {counter: 1}}}}` + +When using incrementCounter for collecting usage data, you need to ensure that usage collection happens on a best-effort basis and doesn't negatively affect your plugin or users. See https://github.com/elastic/kibana/blob/master/src/plugins/usage\_collection/README.md\#tracking-interactions-with-incrementcounter) + +## Example + + +```ts +const repository = coreStart.savedObjects.createInternalRepository(); + +// Initialize all fields to 0 +repository + .incrementCounter('dashboard_counter_type', 'counter_id', [ + 'stats.apiCalls', + 'stats.sampleDataInstalled', + ], {initialize: true}); + +// Increment the apiCalls field counter +repository + .incrementCounter('dashboard_counter_type', 'counter_id', [ + 'stats.apiCalls', + ]) + +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md index 6a56f0bee718b6..e0a6b8af5658a0 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepository.md @@ -26,7 +26,7 @@ export declare class SavedObjectsRepository | [deleteFromNamespaces(type, id, namespaces, options)](./kibana-plugin-core-server.savedobjectsrepository.deletefromnamespaces.md) | | Removes one or more namespaces from a given multi-namespace saved object. If no namespaces remain, the saved object is deleted entirely. This method and \[addToNamespaces\][SavedObjectsRepository.addToNamespaces()](./kibana-plugin-core-server.savedobjectsrepository.addtonamespaces.md) are the only ways to change which Spaces a multi-namespace saved object is shared to. | | [find(options)](./kibana-plugin-core-server.savedobjectsrepository.find.md) | | | | [get(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.get.md) | | Gets a single object | -| [incrementCounter(type, id, counterFieldName, options)](./kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md) | | Increases a counter field by one. Creates the document if one doesn't exist for the given id. | +| [incrementCounter(type, id, counterFieldNames, options)](./kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md) | | Increments all the specified counter fields by one. Creates the document if one doesn't exist for the given id. | | [removeReferencesTo(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.removereferencesto.md) | | Updates all objects containing a reference to the given {type, id} tuple to remove the said reference. | | [update(type, id, attributes, options)](./kibana-plugin-core-server.savedobjectsrepository.update.md) | | Updates an object | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md index b2f8e83d8e6545..a370c67f460f4e 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md @@ -17,6 +17,7 @@ export interface ISearchSetup | Property | Type | Description | | --- | --- | --- | | [aggs](./kibana-plugin-plugins-data-public.isearchsetup.aggs.md) | AggsSetup | | -| [session](./kibana-plugin-plugins-data-public.isearchsetup.session.md) | ISessionService | session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) | +| [session](./kibana-plugin-plugins-data-public.isearchsetup.session.md) | ISessionService | Current session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) | +| [sessionsClient](./kibana-plugin-plugins-data-public.isearchsetup.sessionsclient.md) | ISessionsClient | Background search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) | | [usageCollector](./kibana-plugin-plugins-data-public.isearchsetup.usagecollector.md) | SearchUsageCollector | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.session.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.session.md index 739fdfdeb5fc36..451dbc86b86b66 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.session.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.session.md @@ -4,7 +4,7 @@ ## ISearchSetup.session property -session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) +Current session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) Signature: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.sessionsclient.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.sessionsclient.md new file mode 100644 index 00000000000000..d9af202cf1018f --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.sessionsclient.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchSetup](./kibana-plugin-plugins-data-public.isearchsetup.md) > [sessionsClient](./kibana-plugin-plugins-data-public.isearchsetup.sessionsclient.md) + +## ISearchSetup.sessionsClient property + +Background search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) + +Signature: + +```typescript +sessionsClient: ISessionsClient; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md index dba60c7bdf147d..a27e155dda1117 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md @@ -19,6 +19,7 @@ export interface ISearchStart | [aggs](./kibana-plugin-plugins-data-public.isearchstart.aggs.md) | AggsStart | agg config sub service [AggsStart](./kibana-plugin-plugins-data-public.aggsstart.md) | | [search](./kibana-plugin-plugins-data-public.isearchstart.search.md) | ISearchGeneric | low level search [ISearchGeneric](./kibana-plugin-plugins-data-public.isearchgeneric.md) | | [searchSource](./kibana-plugin-plugins-data-public.isearchstart.searchsource.md) | ISearchStartSearchSource | high level search [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) | -| [session](./kibana-plugin-plugins-data-public.isearchstart.session.md) | ISessionService | session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) | +| [session](./kibana-plugin-plugins-data-public.isearchstart.session.md) | ISessionService | Current session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) | +| [sessionsClient](./kibana-plugin-plugins-data-public.isearchstart.sessionsclient.md) | ISessionsClient | Background search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) | | [showError](./kibana-plugin-plugins-data-public.isearchstart.showerror.md) | (e: Error) => void | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.session.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.session.md index 1ad194a9bec868..892b0fa6acb607 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.session.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.session.md @@ -4,7 +4,7 @@ ## ISearchStart.session property -session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) +Current session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) Signature: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.sessionsclient.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.sessionsclient.md new file mode 100644 index 00000000000000..9c3210d2ec4175 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.sessionsclient.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchStart](./kibana-plugin-plugins-data-public.isearchstart.md) > [sessionsClient](./kibana-plugin-plugins-data-public.isearchstart.sessionsclient.md) + +## ISearchStart.sessionsClient property + +Background search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) + +Signature: + +```typescript +sessionsClient: ISessionsClient; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionsclient.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionsclient.md new file mode 100644 index 00000000000000..d6efabb1b9518e --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionsclient.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) + +## ISessionsClient type + +Signature: + +```typescript +export declare type ISessionsClient = PublicContract; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.clear.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.clear.md deleted file mode 100644 index fc3d214eb4cad1..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.clear.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [clear](./kibana-plugin-plugins-data-public.isessionservice.clear.md) - -## ISessionService.clear property - -Clears the active session. - -Signature: - -```typescript -clear: () => void; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md deleted file mode 100644 index eabb966160c4d8..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [delete](./kibana-plugin-plugins-data-public.isessionservice.delete.md) - -## ISessionService.delete property - -Deletes a session - -Signature: - -```typescript -delete: (sessionId: string) => Promise; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md deleted file mode 100644 index 58e2fea0e6fe93..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [find](./kibana-plugin-plugins-data-public.isessionservice.find.md) - -## ISessionService.find property - -Gets a list of saved sessions - -Signature: - -```typescript -find: (options: SearchSessionFindOptions) => Promise>; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md deleted file mode 100644 index a2dff2f18253bc..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [get](./kibana-plugin-plugins-data-public.isessionservice.get.md) - -## ISessionService.get property - -Gets a saved session - -Signature: - -```typescript -get: (sessionId: string) => Promise>; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsession_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsession_.md deleted file mode 100644 index e30c89fb1a9fdf..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsession_.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [getSession$](./kibana-plugin-plugins-data-public.isessionservice.getsession_.md) - -## ISessionService.getSession$ property - -Returns the observable that emits an update every time the session ID changes - -Signature: - -```typescript -getSession$: () => Observable; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsessionid.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsessionid.md deleted file mode 100644 index 838023ff1d8b90..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.getsessionid.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [getSessionId](./kibana-plugin-plugins-data-public.isessionservice.getsessionid.md) - -## ISessionService.getSessionId property - -Returns the active session ID - -Signature: - -```typescript -getSessionId: () => string | undefined; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isrestore.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isrestore.md deleted file mode 100644 index 8d8cd1f31bb957..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isrestore.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [isRestore](./kibana-plugin-plugins-data-public.isessionservice.isrestore.md) - -## ISessionService.isRestore property - -Whether the active session is restored (i.e. reusing previous search IDs) - -Signature: - -```typescript -isRestore: () => boolean; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isstored.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isstored.md deleted file mode 100644 index db737880bb84e9..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isstored.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [isStored](./kibana-plugin-plugins-data-public.isessionservice.isstored.md) - -## ISessionService.isStored property - -Whether the active session is already saved (i.e. sent to background) - -Signature: - -```typescript -isStored: () => boolean; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md index 02c0a821e552dc..8938c880a04718 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md @@ -2,28 +2,10 @@ [Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) -## ISessionService interface +## ISessionService type Signature: ```typescript -export interface ISessionService +export declare type ISessionService = PublicContract; ``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [clear](./kibana-plugin-plugins-data-public.isessionservice.clear.md) | () => void | Clears the active session. | -| [delete](./kibana-plugin-plugins-data-public.isessionservice.delete.md) | (sessionId: string) => Promise<void> | Deletes a session | -| [find](./kibana-plugin-plugins-data-public.isessionservice.find.md) | (options: SearchSessionFindOptions) => Promise<SavedObjectsFindResponse<BackgroundSessionSavedObjectAttributes>> | Gets a list of saved sessions | -| [get](./kibana-plugin-plugins-data-public.isessionservice.get.md) | (sessionId: string) => Promise<SavedObject<BackgroundSessionSavedObjectAttributes>> | Gets a saved session | -| [getSession$](./kibana-plugin-plugins-data-public.isessionservice.getsession_.md) | () => Observable<string | undefined> | Returns the observable that emits an update every time the session ID changes | -| [getSessionId](./kibana-plugin-plugins-data-public.isessionservice.getsessionid.md) | () => string | undefined | Returns the active session ID | -| [isRestore](./kibana-plugin-plugins-data-public.isessionservice.isrestore.md) | () => boolean | Whether the active session is restored (i.e. reusing previous search IDs) | -| [isStored](./kibana-plugin-plugins-data-public.isessionservice.isstored.md) | () => boolean | Whether the active session is already saved (i.e. sent to background) | -| [restore](./kibana-plugin-plugins-data-public.isessionservice.restore.md) | (sessionId: string) => Promise<SavedObject<BackgroundSessionSavedObjectAttributes>> | Restores existing session | -| [save](./kibana-plugin-plugins-data-public.isessionservice.save.md) | (name: string, url: string) => Promise<SavedObject<BackgroundSessionSavedObjectAttributes>> | Saves a session | -| [start](./kibana-plugin-plugins-data-public.isessionservice.start.md) | () => string | Starts a new session | -| [update](./kibana-plugin-plugins-data-public.isessionservice.update.md) | (sessionId: string, attributes: Partial<BackgroundSessionSavedObjectAttributes>) => Promise<any> | Updates a session | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md deleted file mode 100644 index 96106a6ef7e2d1..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [restore](./kibana-plugin-plugins-data-public.isessionservice.restore.md) - -## ISessionService.restore property - -Restores existing session - -Signature: - -```typescript -restore: (sessionId: string) => Promise>; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md deleted file mode 100644 index 4ac4a966144676..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [save](./kibana-plugin-plugins-data-public.isessionservice.save.md) - -## ISessionService.save property - -Saves a session - -Signature: - -```typescript -save: (name: string, url: string) => Promise>; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.start.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.start.md deleted file mode 100644 index 9e14c5ed267658..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.start.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [start](./kibana-plugin-plugins-data-public.isessionservice.start.md) - -## ISessionService.start property - -Starts a new session - -Signature: - -```typescript -start: () => string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.update.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.update.md deleted file mode 100644 index 5e2ff53d58ab77..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.update.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [update](./kibana-plugin-plugins-data-public.isessionservice.update.md) - -## ISessionService.update property - -Updates a session - -Signature: - -```typescript -update: (sessionId: string, attributes: Partial) => Promise; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index b8e45cde3c18b1..9121b0aade4706 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -34,6 +34,7 @@ | [KBN\_FIELD\_TYPES](./kibana-plugin-plugins-data-public.kbn_field_types.md) | \* | | [METRIC\_TYPES](./kibana-plugin-plugins-data-public.metric_types.md) | | | [QuerySuggestionTypes](./kibana-plugin-plugins-data-public.querysuggestiontypes.md) | | +| [SessionState](./kibana-plugin-plugins-data-public.sessionstate.md) | Possible state that current session can be in | | [SortDirection](./kibana-plugin-plugins-data-public.sortdirection.md) | | | [TimeoutErrorMode](./kibana-plugin-plugins-data-public.timeouterrormode.md) | | @@ -74,7 +75,6 @@ | [ISearchSetup](./kibana-plugin-plugins-data-public.isearchsetup.md) | The setup contract exposed by the Search plugin exposes the search strategy extension point. | | [ISearchStart](./kibana-plugin-plugins-data-public.isearchstart.md) | search service | | [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) | high level search service | -| [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) | | | [KueryNode](./kibana-plugin-plugins-data-public.kuerynode.md) | | | [OptionedValueProp](./kibana-plugin-plugins-data-public.optionedvalueprop.md) | | | [QueryState](./kibana-plugin-plugins-data-public.querystate.md) | All query state service state | @@ -89,6 +89,7 @@ | [SavedQueryService](./kibana-plugin-plugins-data-public.savedqueryservice.md) | | | [SearchError](./kibana-plugin-plugins-data-public.searcherror.md) | | | [SearchInterceptorDeps](./kibana-plugin-plugins-data-public.searchinterceptordeps.md) | | +| [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchSessionInfoprovider.md) | Provide info about current search session to be stored in backgroundSearch saved object | | [SearchSourceFields](./kibana-plugin-plugins-data-public.searchsourcefields.md) | search source fields | | [TabbedAggColumn](./kibana-plugin-plugins-data-public.tabbedaggcolumn.md) | \* | | [TabbedTable](./kibana-plugin-plugins-data-public.tabbedtable.md) | \* | @@ -166,6 +167,8 @@ | [InputTimeRange](./kibana-plugin-plugins-data-public.inputtimerange.md) | | | [ISearchGeneric](./kibana-plugin-plugins-data-public.isearchgeneric.md) | | | [ISearchSource](./kibana-plugin-plugins-data-public.isearchsource.md) | search source interface | +| [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) | | +| [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) | | | [KibanaContext](./kibana-plugin-plugins-data-public.kibanacontext.md) | | | [MatchAllFilter](./kibana-plugin-plugins-data-public.matchallfilter.md) | | | [ParsedInterval](./kibana-plugin-plugins-data-public.parsedinterval.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.getname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.getname.md new file mode 100644 index 00000000000000..0f0b616066dd66 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.getname.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchSessionInfoprovider.md) > [getName](./kibana-plugin-plugins-data-public.searchSessionInfoprovider.getname.md) + +## SearchSessionInfoProvider.getName property + +User-facing name of the session. e.g. will be displayed in background sessions management list + +Signature: + +```typescript +getName: () => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.geturlgeneratordata.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.geturlgeneratordata.md new file mode 100644 index 00000000000000..207adaf2bd50b6 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.geturlgeneratordata.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchSessionInfoprovider.md) > [getUrlGeneratorData](./kibana-plugin-plugins-data-public.searchSessionInfoprovider.geturlgeneratordata.md) + +## SearchSessionInfoProvider.getUrlGeneratorData property + +Signature: + +```typescript +getUrlGeneratorData: () => Promise<{ + urlGeneratorId: ID; + initialState: UrlGeneratorStateMapping[ID]['State']; + restoreState: UrlGeneratorStateMapping[ID]['State']; + }>; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.md new file mode 100644 index 00000000000000..a3d294f5e33038 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchSessionInfoprovider.md) + +## SearchSessionInfoProvider interface + +Provide info about current search session to be stored in backgroundSearch saved object + +Signature: + +```typescript +export interface SearchSessionInfoProvider +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [getName](./kibana-plugin-plugins-data-public.searchSessionInfoprovider.getname.md) | () => Promise<string> | User-facing name of the session. e.g. will be displayed in background sessions management list | +| [getUrlGeneratorData](./kibana-plugin-plugins-data-public.searchSessionInfoprovider.geturlgeneratordata.md) | () => Promise<{
urlGeneratorId: ID;
initialState: UrlGeneratorStateMapping[ID]['State'];
restoreState: UrlGeneratorStateMapping[ID]['State'];
}> | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.sessionstate.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.sessionstate.md new file mode 100644 index 00000000000000..9a60a5b2a9f9be --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.sessionstate.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SessionState](./kibana-plugin-plugins-data-public.sessionstate.md) + +## SessionState enum + +Possible state that current session can be in + +Signature: + +```typescript +export declare enum SessionState +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| BackgroundCompleted | "backgroundCompleted" | Page load completed with background session created. | +| BackgroundLoading | "backgroundLoading" | Search request was sent to the background. The page is loading in background. | +| Canceled | "canceled" | Current session requests where explicitly canceled by user Displaying none or partial results | +| Completed | "completed" | No action was taken and the page completed loading without background session creation. | +| Loading | "loading" | Pending search request has not been sent to the background yet | +| None | "none" | Session is not active, e.g. didn't start | +| Restored | "restored" | Revisiting the page after background completion | + diff --git a/docs/settings/alert-action-settings.asciidoc b/docs/settings/alert-action-settings.asciidoc index 3c0e63fae0daa9..fa44d3a6ae41b1 100644 --- a/docs/settings/alert-action-settings.asciidoc +++ b/docs/settings/alert-action-settings.asciidoc @@ -27,6 +27,7 @@ You can configure the following settings in the `kibana.yml` file. If not set, {kib} will generate a random key on startup, but all alert and action functions will be blocked. Generated keys are not allowed for alerts and actions because when a new key is generated on restart, existing encrypted data becomes inaccessible. For the same reason, alerts and actions in high-availability deployments of {kib} will behave unexpectedly if the key isn't the same on all instances of {kib}. + + Although the key can be specified in clear text in `kibana.yml`, it's recommended to store this key securely in the <>. + Be sure to back up the encryption key value somewhere safe, as your alerts and actions will cease to function due to decryption failures should you lose it. If you want to rotate the encryption key, be sure to follow the instructions on <>. |=== diff --git a/docs/settings/spaces-settings.asciidoc b/docs/settings/spaces-settings.asciidoc index bda5f00f762cdc..3b643f76f0c09d 100644 --- a/docs/settings/spaces-settings.asciidoc +++ b/docs/settings/spaces-settings.asciidoc @@ -16,6 +16,7 @@ roles when Security is enabled. |=== | `xpack.spaces.enabled` | Set to `true` (default) to enable Spaces in {kib}. + This setting is deprecated. Starting in 8.0, it will not be possible to disable this plugin. | `xpack.spaces.maxSpaces` | The maximum amount of Spaces that can be used with this instance of {kib}. Some operations diff --git a/docs/user/alerting/defining-alerts.asciidoc b/docs/user/alerting/defining-alerts.asciidoc index 05d022d039b23a..667038739d45ff 100644 --- a/docs/user/alerting/defining-alerts.asciidoc +++ b/docs/user/alerting/defining-alerts.asciidoc @@ -89,6 +89,8 @@ Here's a list of the available global configuration options and an explanation o * `xpack.actions.proxyRejectUnauthorizedCertificates`: Set to `false` to bypass certificate validation for proxy, if using a proxy for actions. * `xpack.actions.rejectUnauthorized`: Set to `false` to bypass certificate validation for actions. +*NOTE:* As an alternative to both `xpack.actions.proxyRejectUnauthorizedCertificates` and `xpack.actions.rejectUnauthorized`, the OS level environment variable `NODE_EXTRA_CA_CERTS` can be set to point to a file that contains the root CA(s) needed for certificates to be trusted. + [float] === Managing alerts diff --git a/src/dev/jest/config.integration.js b/jest.config.integration.js similarity index 82% rename from src/dev/jest/config.integration.js rename to jest.config.integration.js index 9e7bbc34ac711b..3dacb107f94c04 100644 --- a/src/dev/jest/config.integration.js +++ b/jest.config.integration.js @@ -17,16 +17,14 @@ * under the License. */ -import preset from '@kbn/test/jest-preset'; -import config from './config'; +const preset = require('@kbn/test/jest-preset'); -export default { - ...config, - testMatch: [ - '**/integration_tests/**/*.test.js', - '**/integration_tests/**/*.test.ts', - '**/integration_tests/**/*.test.tsx', - ], +module.exports = { + preset: '@kbn/test', + rootDir: '.', + roots: ['/src', '/packages'], + testMatch: ['**/integration_tests**/*.test.{js,mjs,ts,tsx}'], + testRunner: 'jasmine2', testPathIgnorePatterns: preset.testPathIgnorePatterns.filter( (pattern) => !pattern.includes('integration_tests') ), diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000000000..c190556700b812 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + rootDir: '.', + projects: [...require('./jest.config.oss').projects, ...require('./x-pack/jest.config').projects], +}; diff --git a/jest.config.oss.js b/jest.config.oss.js new file mode 100644 index 00000000000000..e9235069687e0a --- /dev/null +++ b/jest.config.oss.js @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + rootDir: '.', + projects: [ + '/packages/*/jest.config.js', + '/src/*/jest.config.js', + '/src/legacy/*/jest.config.js', + '/src/plugins/*/jest.config.js', + '/test/*/jest.config.js', + ], + reporters: ['default', '/packages/kbn-test/target/jest/junit_reporter'], +}; diff --git a/package.json b/package.json index 1febfc2380b7ad..d5c1f247d87d7d 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "@babel/core": "^7.11.6", "@babel/runtime": "^7.11.2", "@elastic/datemath": "link:packages/elastic-datemath", - "@elastic/elasticsearch": "7.10.0-rc.1", + "@elastic/elasticsearch": "7.10.0", "@elastic/ems-client": "7.11.0", "@elastic/eui": "30.2.0", "@elastic/filesaver": "1.1.2", @@ -347,7 +347,7 @@ "@cypress/webpack-preprocessor": "^5.4.10", "@elastic/apm-rum": "^5.6.1", "@elastic/apm-rum-react": "^1.2.5", - "@elastic/charts": "24.0.0", + "@elastic/charts": "24.2.0", "@elastic/eslint-config-kibana": "link:packages/elastic-eslint-config-kibana", "@elastic/eslint-plugin-eui": "0.0.2", "@elastic/github-checks-reporter": "0.0.20b3", @@ -619,6 +619,7 @@ "delete-empty": "^2.0.0", "dependency-check": "^4.1.0", "diff": "^4.0.1", + "dpdm": "3.5.0", "ejs": "^3.1.5", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.2", diff --git a/packages/README.md b/packages/README.md index 8ff05f4e8ff899..9d9cd4ed7b6e55 100644 --- a/packages/README.md +++ b/packages/README.md @@ -60,7 +60,7 @@ A package can also follow the pattern of having `.test.js` files as siblings of A package using the `.test.js` naming convention will have those tests automatically picked up by Jest and run by the unit test runner, currently mapped to the Kibana `test` script in the root `package.json`. * `yarn test` or `yarn grunt test` runs all unit tests. -* `node scripts/jest` runs all Jest tests in Kibana. +* `yarn jest` runs all Jest tests in Kibana. ---- Each package can also specify its own `test` script in the package's `package.json`, for cases where you'd prefer to run the tests from the local package directory. diff --git a/packages/kbn-apm-config-loader/jest.config.js b/packages/kbn-apm-config-loader/jest.config.js new file mode 100644 index 00000000000000..2b88679a57e72c --- /dev/null +++ b/packages/kbn-apm-config-loader/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-apm-config-loader'], +}; diff --git a/packages/kbn-babel-code-parser/jest.config.js b/packages/kbn-babel-code-parser/jest.config.js new file mode 100644 index 00000000000000..60fce8897723e5 --- /dev/null +++ b/packages/kbn-babel-code-parser/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-babel-code-parser'], +}; diff --git a/packages/kbn-babel-preset/common_preset.js b/packages/kbn-babel-preset/common_preset.js index 8e2f1d207f3f41..b14dcd8971c31c 100644 --- a/packages/kbn-babel-preset/common_preset.js +++ b/packages/kbn-babel-preset/common_preset.js @@ -28,18 +28,19 @@ const plugins = [ // See https://github.com/babel/proposals/issues/12 for progress require.resolve('@babel/plugin-proposal-class-properties'), - // Optional Chaining proposal is stage 3 (https://github.com/tc39/proposal-optional-chaining) + // Optional Chaining proposal is stage 4 (https://github.com/tc39/proposal-optional-chaining) // Need this since we are using TypeScript 3.7+ require.resolve('@babel/plugin-proposal-optional-chaining'), - // Nullish coalescing proposal is stage 3 (https://github.com/tc39/proposal-nullish-coalescing) + + // Nullish coalescing proposal is stage 4 (https://github.com/tc39/proposal-nullish-coalescing) // Need this since we are using TypeScript 3.7+ require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'), - // Proposal is on stage 4 (https://github.com/tc39/proposal-export-ns-from) + // Proposal is on stage 4, and included in ECMA-262 (https://github.com/tc39/proposal-export-ns-from) // Need this since we are using TypeScript 3.8+ require.resolve('@babel/plugin-proposal-export-namespace-from'), - // Proposal is on stage 4 (https://github.com/tc39/proposal-export-ns-from) + // Proposal is on stage 4, and included in ECMA-262 (https://github.com/tc39/proposal-export-ns-from) // Need this since we are using TypeScript 3.9+ require.resolve('@babel/plugin-proposal-private-methods'), ]; diff --git a/packages/kbn-config-schema/jest.config.js b/packages/kbn-config-schema/jest.config.js new file mode 100644 index 00000000000000..35de02838aa1c6 --- /dev/null +++ b/packages/kbn-config-schema/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-config-schema'], +}; diff --git a/packages/kbn-config/jest.config.js b/packages/kbn-config/jest.config.js new file mode 100644 index 00000000000000..b4c84eef4675ce --- /dev/null +++ b/packages/kbn-config/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-config'], +}; diff --git a/packages/kbn-dev-utils/jest.config.js b/packages/kbn-dev-utils/jest.config.js new file mode 100644 index 00000000000000..2b0cefe5e741f7 --- /dev/null +++ b/packages/kbn-dev-utils/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-dev-utils'], +}; diff --git a/packages/kbn-es-archiver/jest.config.js b/packages/kbn-es-archiver/jest.config.js new file mode 100644 index 00000000000000..e5df757f6637e0 --- /dev/null +++ b/packages/kbn-es-archiver/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-es-archiver'], +}; diff --git a/packages/kbn-es/jest.config.js b/packages/kbn-es/jest.config.js new file mode 100644 index 00000000000000..2c09b5400369dd --- /dev/null +++ b/packages/kbn-es/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-es'], +}; diff --git a/packages/kbn-i18n/jest.config.js b/packages/kbn-i18n/jest.config.js new file mode 100644 index 00000000000000..dff8b872bdfe08 --- /dev/null +++ b/packages/kbn-i18n/jest.config.js @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-i18n'], + testRunner: 'jasmine2', +}; diff --git a/packages/kbn-interpreter/jest.config.js b/packages/kbn-interpreter/jest.config.js new file mode 100644 index 00000000000000..d2f6127ccff79c --- /dev/null +++ b/packages/kbn-interpreter/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-interpreter'], +}; diff --git a/packages/kbn-legacy-logging/jest.config.js b/packages/kbn-legacy-logging/jest.config.js new file mode 100644 index 00000000000000..f33205439e134c --- /dev/null +++ b/packages/kbn-legacy-logging/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-legacy-logging'], +}; diff --git a/packages/kbn-logging/jest.config.js b/packages/kbn-logging/jest.config.js new file mode 100644 index 00000000000000..74ff8fd14f56a6 --- /dev/null +++ b/packages/kbn-logging/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-logging'], +}; diff --git a/packages/kbn-monaco/jest.config.js b/packages/kbn-monaco/jest.config.js new file mode 100644 index 00000000000000..03f879f6d0beff --- /dev/null +++ b/packages/kbn-monaco/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-monaco'], +}; diff --git a/packages/kbn-monaco/package.json b/packages/kbn-monaco/package.json index e2406a73f5342d..eef68d3a35e0c7 100644 --- a/packages/kbn-monaco/package.json +++ b/packages/kbn-monaco/package.json @@ -11,5 +11,8 @@ "devDependencies": { "@kbn/babel-preset": "link:../kbn-babel-preset", "@kbn/dev-utils": "link:../kbn-dev-utils" + }, + "dependencies": { + "@kbn/i18n": "link:../kbn-i18n" } } \ No newline at end of file diff --git a/packages/kbn-optimizer/jest.config.js b/packages/kbn-optimizer/jest.config.js new file mode 100644 index 00000000000000..6e313aaad3c82f --- /dev/null +++ b/packages/kbn-optimizer/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-optimizer'], +}; diff --git a/packages/kbn-plugin-generator/jest.config.js b/packages/kbn-plugin-generator/jest.config.js new file mode 100644 index 00000000000000..1d81a72128afdb --- /dev/null +++ b/packages/kbn-plugin-generator/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-plugin-generator'], +}; diff --git a/packages/kbn-pm/jest.config.js b/packages/kbn-pm/jest.config.js new file mode 100644 index 00000000000000..ba0624f5f6ccda --- /dev/null +++ b/packages/kbn-pm/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-pm'], +}; diff --git a/packages/kbn-release-notes/jest.config.js b/packages/kbn-release-notes/jest.config.js new file mode 100644 index 00000000000000..44390a8c981620 --- /dev/null +++ b/packages/kbn-release-notes/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-release-notes'], +}; diff --git a/packages/kbn-spec-to-console/jest.config.js b/packages/kbn-spec-to-console/jest.config.js new file mode 100644 index 00000000000000..cef82f4d76f734 --- /dev/null +++ b/packages/kbn-spec-to-console/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-spec-to-console'], +}; diff --git a/packages/kbn-std/jest.config.js b/packages/kbn-std/jest.config.js new file mode 100644 index 00000000000000..0615e33e41af8d --- /dev/null +++ b/packages/kbn-std/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-std'], +}; diff --git a/packages/kbn-telemetry-tools/jest.config.js b/packages/kbn-telemetry-tools/jest.config.js new file mode 100644 index 00000000000000..b7b101beccf32a --- /dev/null +++ b/packages/kbn-telemetry-tools/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-telemetry-tools'], +}; diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js index f899a5b44ab6c1..63457aa3cc37d4 100644 --- a/packages/kbn-test/jest-preset.js +++ b/packages/kbn-test/jest-preset.js @@ -48,7 +48,6 @@ module.exports = { '^(!!)?file-loader!': '/packages/kbn-test/target/jest/mocks/file_mock.js', '^fixtures/(.*)': '/src/fixtures/$1', '^src/core/(.*)': '/src/core/$1', - '^src/legacy/(.*)': '/src/legacy/$1', '^src/plugins/(.*)': '/src/plugins/$1', }, diff --git a/packages/kbn-test/jest.config.js b/packages/kbn-test/jest.config.js new file mode 100644 index 00000000000000..9400d402a1a33e --- /dev/null +++ b/packages/kbn-test/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-test'], +}; diff --git a/packages/kbn-test/src/index.ts b/packages/kbn-test/src/index.ts index 3e4a62387d1bf4..54b064f5cd49e3 100644 --- a/packages/kbn-test/src/index.ts +++ b/packages/kbn-test/src/index.ts @@ -58,3 +58,7 @@ export { runFailedTestsReporterCli } from './failed_tests_reporter'; export { CI_PARALLEL_PROCESS_PREFIX } from './ci_parallel_process_prefix'; export * from './functional_test_runner'; + +export { getUrl } from './jest/utils/get_url'; + +export { runCheckJestConfigsCli } from './jest/run_check_jest_configs_cli'; diff --git a/packages/kbn-test/src/jest/run_check_jest_configs_cli.ts b/packages/kbn-test/src/jest/run_check_jest_configs_cli.ts new file mode 100644 index 00000000000000..385fb453697ef4 --- /dev/null +++ b/packages/kbn-test/src/jest/run_check_jest_configs_cli.ts @@ -0,0 +1,113 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { relative, resolve, sep } from 'path'; +import { writeFileSync } from 'fs'; + +import execa from 'execa'; +import globby from 'globby'; +import Mustache from 'mustache'; + +import { run } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; + +// @ts-ignore +import { testMatch } from '../../jest-preset'; + +const template: string = `module.exports = { + preset: '@kbn/test', + rootDir: '{{{relToRoot}}}', + roots: ['/{{{modulePath}}}'], +}; +`; + +const roots: string[] = ['x-pack/plugins', 'packages', 'src/legacy', 'src/plugins', 'test', 'src']; + +export async function runCheckJestConfigsCli() { + run( + async ({ flags: { fix = false }, log }) => { + const { stdout: coveredFiles } = await execa( + 'yarn', + ['--silent', 'jest', '--listTests', '--json'], + { + cwd: REPO_ROOT, + } + ); + + const allFiles = new Set( + await globby(testMatch.concat(['!**/integration_tests/**']), { + gitignore: true, + }) + ); + + JSON.parse(coveredFiles).forEach((file: string) => { + const pathFromRoot = relative(REPO_ROOT, file); + allFiles.delete(pathFromRoot); + }); + + if (allFiles.size) { + log.error( + `The following files do not belong to a jest.config.js file, or that config is not included from the root jest.config.js\n${[ + ...allFiles, + ] + .map((file) => ` - ${file}`) + .join('\n')}` + ); + } else { + log.success('All test files are included by a Jest configuration'); + return; + } + + if (fix) { + allFiles.forEach((file) => { + const root = roots.find((r) => file.startsWith(r)); + + if (root) { + const name = relative(root, file).split(sep)[0]; + const modulePath = [root, name].join('/'); + + const content = Mustache.render(template, { + relToRoot: relative(modulePath, '.'), + modulePath, + }); + + writeFileSync(resolve(root, name, 'jest.config.js'), content); + } else { + log.warning(`Unable to determind where to place jest.config.js for ${file}`); + } + }); + } else { + log.info( + `Run 'node scripts/check_jest_configs --fix' to attempt to create the missing config files` + ); + } + + process.exit(1); + }, + { + description: 'Check that all test files are covered by a jest.config.js', + flags: { + boolean: ['fix'], + help: ` + --fix Attempt to create missing config files + `, + }, + } + ); +} diff --git a/packages/kbn-test/src/jest/utils/index.ts b/packages/kbn-test/src/jest/utils/index.ts index f85160652828b6..808f049cda6d8b 100644 --- a/packages/kbn-test/src/jest/utils/index.ts +++ b/packages/kbn-test/src/jest/utils/index.ts @@ -19,6 +19,8 @@ export * from './enzyme_helpers'; +export * from './get_url'; + export * from './find_test_subject'; export * from './jsdom_svg_mocks'; diff --git a/packages/kbn-ui-framework/jest.config.js b/packages/kbn-ui-framework/jest.config.js new file mode 100644 index 00000000000000..d9cb93d7c069d4 --- /dev/null +++ b/packages/kbn-ui-framework/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-ui-framework'], +}; diff --git a/packages/kbn-utils/jest.config.js b/packages/kbn-utils/jest.config.js new file mode 100644 index 00000000000000..39fb0a8ff1a8c3 --- /dev/null +++ b/packages/kbn-utils/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-utils'], +}; diff --git a/scripts/find_plugin_circular_deps.js b/scripts/check_jest_configs.js similarity index 94% rename from scripts/find_plugin_circular_deps.js rename to scripts/check_jest_configs.js index 6b0661cb841b47..a7a520f433bf9b 100644 --- a/scripts/find_plugin_circular_deps.js +++ b/scripts/check_jest_configs.js @@ -18,4 +18,4 @@ */ require('../src/setup_node_env'); -require('../src/dev/run_find_plugin_circular_deps'); +require('@kbn/test').runCheckJestConfigsCli(); diff --git a/src/dev/jest/cli.js b/scripts/find_plugins_with_circular_deps.js similarity index 89% rename from src/dev/jest/cli.js rename to scripts/find_plugins_with_circular_deps.js index 40627c4bece74f..138fec33fd6b40 100644 --- a/src/dev/jest/cli.js +++ b/scripts/find_plugins_with_circular_deps.js @@ -17,6 +17,5 @@ * under the License. */ -import { run } from 'jest'; - -run(process.argv.slice(2)); +require('../src/setup_node_env'); +require('../src/dev/run_find_plugins_with_circular_deps'); diff --git a/scripts/jest.js b/scripts/jest.js index c252056de766ba..90f8da10f4c902 100755 --- a/scripts/jest.js +++ b/scripts/jest.js @@ -29,8 +29,15 @@ // // See all cli options in https://facebook.github.io/jest/docs/cli.html -var resolve = require('path').resolve; -process.argv.push('--config', resolve(__dirname, '../src/dev/jest/config.js')); +if (process.argv.indexOf('--config') === -1) { + // append correct jest.config if none is provided + var configPath = require('path').resolve(__dirname, '../jest.config.oss.js'); + process.argv.push('--config', configPath); + console.log('Running Jest with --config', configPath); +} -require('../src/setup_node_env'); -require('../src/dev/jest/cli'); +if (process.env.NODE_ENV == null) { + process.env.NODE_ENV = 'test'; +} + +require('jest').run(); diff --git a/scripts/jest_integration.js b/scripts/jest_integration.js index 7da1436f5583c3..f07d28939ef0c4 100755 --- a/scripts/jest_integration.js +++ b/scripts/jest_integration.js @@ -29,9 +29,17 @@ // // See all cli options in https://facebook.github.io/jest/docs/cli.html -var resolve = require('path').resolve; -process.argv.push('--config', resolve(__dirname, '../src/dev/jest/config.integration.js')); process.argv.push('--runInBand'); -require('../src/setup_node_env'); -require('../src/dev/jest/cli'); +if (process.argv.indexOf('--config') === -1) { + // append correct jest.config if none is provided + var configPath = require('path').resolve(__dirname, '../jest.config.integration.js'); + process.argv.push('--config', configPath); + console.log('Running Jest with --config', configPath); +} + +if (process.env.NODE_ENV == null) { + process.env.NODE_ENV = 'test'; +} + +require('jest').run(); diff --git a/src/plugins/data/common/mocks.ts b/src/cli/jest.config.js similarity index 89% rename from src/plugins/data/common/mocks.ts rename to src/cli/jest.config.js index dde70b1d07443b..6a1055ca864c8c 100644 --- a/src/plugins/data/common/mocks.ts +++ b/src/cli/jest.config.js @@ -17,4 +17,8 @@ * under the License. */ -export { getSessionServiceMock } from './search/session/mocks'; +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/src/cli'], +}; diff --git a/src/cli_encryption_keys/jest.config.js b/src/cli_encryption_keys/jest.config.js new file mode 100644 index 00000000000000..f3be28f7898f57 --- /dev/null +++ b/src/cli_encryption_keys/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/src/cli_encryption_keys'], +}; diff --git a/src/cli_keystore/jest.config.js b/src/cli_keystore/jest.config.js new file mode 100644 index 00000000000000..787cd7ccd84be6 --- /dev/null +++ b/src/cli_keystore/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/src/cli_keystore'], +}; diff --git a/src/cli_plugin/jest.config.js b/src/cli_plugin/jest.config.js new file mode 100644 index 00000000000000..cbd226f5df887d --- /dev/null +++ b/src/cli_plugin/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/src/cli_plugin'], +}; diff --git a/src/core/jest.config.js b/src/core/jest.config.js new file mode 100644 index 00000000000000..bdb65b3817507e --- /dev/null +++ b/src/core/jest.config.js @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/src/core'], + testRunner: 'jasmine2', +}; diff --git a/src/core/public/application/index.ts b/src/core/public/application/index.ts index 4f3b113a29c9b1..b39aa70c888fec 100644 --- a/src/core/public/application/index.ts +++ b/src/core/public/application/index.ts @@ -31,6 +31,7 @@ export { AppNavLinkStatus, AppUpdatableFields, AppUpdater, + AppSearchDeepLink, ApplicationSetup, ApplicationStart, AppLeaveHandler, @@ -40,6 +41,7 @@ export { AppLeaveConfirmAction, NavigateToAppOptions, PublicAppInfo, + PublicAppSearchDeepLinkInfo, // Internal types InternalApplicationSetup, InternalApplicationStart, diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 02d2d3a52a01a5..d9f326c7a59ab6 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -81,7 +81,10 @@ export enum AppNavLinkStatus { * Defines the list of fields that can be updated via an {@link AppUpdater}. * @public */ -export type AppUpdatableFields = Pick; +export type AppUpdatableFields = Pick< + App, + 'status' | 'navLinkStatus' | 'tooltip' | 'defaultPath' | 'searchDeepLinks' +>; /** * Updater for applications. @@ -222,7 +225,7 @@ export interface App { * ```ts * core.application.register({ * id: 'my_app', - * title: 'My App' + * title: 'My App', * exactRoute: true, * mount: () => { ... }, * }) @@ -232,18 +235,89 @@ export interface App { * ``` */ exactRoute?: boolean; + + /** + * Array of links that represent secondary in-app locations for the app. + * + * @remarks + * Used to populate navigational search results (where available). + * Can be updated using the {@link App.updater$} observable. See {@link AppSubLink} for more details. + * + * @example + * The `path` property on deep links should not include the application's `appRoute`: + * ```ts + * core.application.register({ + * id: 'my_app', + * title: 'My App', + * searchDeepLinks: [ + * { id: 'sub1', title: 'Sub1', path: '/sub1' }, + * { + * id: 'sub2', + * title: 'Sub2', + * searchDeepLinks: [ + * { id: 'subsub', title: 'SubSub', path: '/sub2/sub' } + * ] + * } + * ], + * mount: () => { ... }, + * }) + * ``` + * + * Will produce deep links on these paths: + * - `/app/my_app/sub1` + * - `/app/my_app/sub2/sub` + */ + searchDeepLinks?: AppSearchDeepLink[]; } +/** + * 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. + * @public + */ +export type AppSearchDeepLink = { + /** Identifier to represent this sublink, should be unique for this application */ + id: string; + /** Title to label represent this deep link */ + title: string; +} & ( + | { + /** 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 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[]; + } +); + +/** + * Public information about a registered app's {@link AppSearchDeepLink | searchDeepLinks} + * + * @public + */ +export type PublicAppSearchDeepLinkInfo = Omit & { + searchDeepLinks: PublicAppSearchDeepLinkInfo[]; +}; + /** * Public information about a registered {@link App | application} * * @public */ -export type PublicAppInfo = Omit & { +export type PublicAppInfo = Omit & { // remove optional on fields populated with default values status: AppStatus; navLinkStatus: AppNavLinkStatus; appRoute: string; + searchDeepLinks: PublicAppSearchDeepLinkInfo[]; }; /** 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 055f7d1a5ada9d..ee0bd4f1eadfaf 100644 --- a/src/core/public/application/utils/get_app_info.test.ts +++ b/src/core/public/application/utils/get_app_info.test.ts @@ -43,6 +43,42 @@ describe('getAppInfo', () => { status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.visible, appRoute: `/app/some-id`, + searchDeepLinks: [], + }); + }); + + it('populates default values for nested searchDeepLinks', () => { + const app = createApp({ + searchDeepLinks: [ + { + id: 'sub-id', + title: 'sub-title', + searchDeepLinks: [{ id: 'sub-sub-id', title: 'sub-sub-title', path: '/sub-sub' }], + }, + ], + }); + const info = getAppInfo(app); + + expect(info).toEqual({ + id: 'some-id', + title: 'some-title', + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.visible, + appRoute: `/app/some-id`, + searchDeepLinks: [ + { + id: 'sub-id', + title: 'sub-title', + searchDeepLinks: [ + { + id: 'sub-sub-id', + title: 'sub-sub-title', + path: '/sub-sub', + searchDeepLinks: [], // default empty array added + }, + ], + }, + ], }); }); diff --git a/src/core/public/application/utils/get_app_info.ts b/src/core/public/application/utils/get_app_info.ts index 71cd8a3e149290..7316080816da7c 100644 --- a/src/core/public/application/utils/get_app_info.ts +++ b/src/core/public/application/utils/get_app_info.ts @@ -17,9 +17,16 @@ * under the License. */ -import { App, AppNavLinkStatus, AppStatus, PublicAppInfo } from '../types'; +import { + App, + AppNavLinkStatus, + AppStatus, + AppSearchDeepLink, + PublicAppInfo, + PublicAppSearchDeepLinkInfo, +} from '../types'; -export function getAppInfo(app: App): PublicAppInfo { +export function getAppInfo(app: App): PublicAppInfo { const navLinkStatus = app.navLinkStatus === AppNavLinkStatus.default ? app.status === AppStatus.inaccessible @@ -32,5 +39,26 @@ export function getAppInfo(app: App): PublicAppInfo { status: app.status!, navLinkStatus, appRoute: app.appRoute!, + searchDeepLinks: getSearchDeepLinkInfos(app, app.searchDeepLinks), }; } + +function getSearchDeepLinkInfos( + app: App, + searchDeepLinks?: AppSearchDeepLink[] +): PublicAppSearchDeepLinkInfo[] { + if (!searchDeepLinks) { + return []; + } + + return searchDeepLinks.map( + (rawDeepLink): PublicAppSearchDeepLinkInfo => { + return { + id: rawDeepLink.id, + title: rawDeepLink.title, + path: rawDeepLink.path, + searchDeepLinks: getSearchDeepLinkInfos(app, rawDeepLink.searchDeepLinks), + }; + } + ); +} 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 7e2c1fc1f89f8e..606370c5afd0a9 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 @@ -28,6 +28,7 @@ const app = (props: Partial = {}): PublicAppInfo => ({ status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.default, appRoute: `/app/some-id`, + searchDeepLinks: [], ...props, }); diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 6af14734444d1d..15df6b34e22ffc 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -39,9 +39,9 @@ export class DocLinksService { dashboard: { guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/dashboard.html`, drilldowns: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html`, - drilldownsTriggerPicker: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html#url-drilldown`, + drilldownsTriggerPicker: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html#url-drilldowns`, urlDrilldownTemplateSyntax: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url_templating-language.html`, - urlDrilldownVariables: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url_templating-language.html#variables`, + urlDrilldownVariables: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url_templating-language.html#url-template-variables`, }, filebeat: { base: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}`, diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 564bbd712c535d..557529fc94dc4a 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -95,7 +95,6 @@ export { ApplicationSetup, ApplicationStart, App, - PublicAppInfo, AppMount, AppMountDeprecated, AppUnmount, @@ -110,6 +109,9 @@ export { AppNavLinkStatus, AppUpdatableFields, AppUpdater, + AppSearchDeepLink, + PublicAppInfo, + PublicAppSearchDeepLinkInfo, ScopedHistory, NavigateToAppOptions, } from './application'; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 37e57a9ee606e3..aaea8f2f7c3fd8 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -59,6 +59,8 @@ export interface App { mount: AppMount | AppMountDeprecated; navLinkStatus?: AppNavLinkStatus; order?: number; + // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "AppSubLink" + searchDeepLinks?: AppSearchDeepLink[]; status?: AppStatus; title: string; tooltip?: string; @@ -175,6 +177,18 @@ export enum AppNavLinkStatus { visible = 1 } +// @public +export type AppSearchDeepLink = { + id: string; + title: string; +} & ({ + path: string; + searchDeepLinks?: AppSearchDeepLink[]; +} | { + path?: string; + searchDeepLinks: AppSearchDeepLink[]; +}); + // @public export enum AppStatus { accessible = 0, @@ -185,7 +199,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; @@ -967,10 +981,16 @@ export interface PluginInitializerContext export type PluginOpaqueId = symbol; // @public -export type PublicAppInfo = Omit & { +export type PublicAppInfo = Omit & { status: AppStatus; navLinkStatus: AppNavLinkStatus; appRoute: string; + searchDeepLinks: PublicAppSearchDeepLinkInfo[]; +}; + +// @public +export type PublicAppSearchDeepLinkInfo = Omit & { + searchDeepLinks: PublicAppSearchDeepLinkInfo[]; }; // @public diff --git a/src/core/server/elasticsearch/legacy/cluster_client.ts b/src/core/server/elasticsearch/legacy/cluster_client.ts index 00417e3bef4f45..e851b3a4db7116 100644 --- a/src/core/server/elasticsearch/legacy/cluster_client.ts +++ b/src/core/server/elasticsearch/legacy/cluster_client.ts @@ -140,6 +140,7 @@ export class LegacyClusterClient implements ILegacyClusterClient { * Calls specified endpoint with provided clientParams on behalf of the * Kibana internal user. * See {@link LegacyAPICaller}. + * @deprecated Use {@link IClusterClient.asInternalUser}. * * @param endpoint - String descriptor of the endpoint e.g. `cluster.getSettings` or `ping`. * @param clientParams - A dictionary of parameters that will be passed directly to the Elasticsearch JS client. diff --git a/src/core/server/elasticsearch/legacy/scoped_cluster_client.ts b/src/core/server/elasticsearch/legacy/scoped_cluster_client.ts index 65484f0927c9ed..e3f3d33d03766c 100644 --- a/src/core/server/elasticsearch/legacy/scoped_cluster_client.ts +++ b/src/core/server/elasticsearch/legacy/scoped_cluster_client.ts @@ -56,6 +56,7 @@ export class LegacyScopedClusterClient implements ILegacyScopedClusterClient { * Calls specified `endpoint` with provided `clientParams` on behalf of the * Kibana internal user. * See {@link LegacyAPICaller}. + * @deprecated Use {@link IScopedClusterClient.asInternalUser}. * * @param endpoint - String descriptor of the endpoint e.g. `cluster.getSettings` or `ping`. * @param clientParams - A dictionary of parameters that will be passed directly to the Elasticsearch JS client. @@ -73,6 +74,7 @@ export class LegacyScopedClusterClient implements ILegacyScopedClusterClient { * Calls specified `endpoint` with provided `clientParams` on behalf of the * user initiated request to the Kibana server (via HTTP request headers). * See {@link LegacyAPICaller}. + * @deprecated Use {@link IScopedClusterClient.asCurrentUser}. * * @param endpoint - String descriptor of the endpoint e.g. `cluster.getSettings` or `ping`. * @param clientParams - A dictionary of parameters that will be passed directly to the Elasticsearch JS client. diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 7b19c3a6867579..9e654ea1e23035 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -394,6 +394,9 @@ export interface RequestHandlerContext { elasticsearch: { client: IScopedClusterClient; legacy: { + /* + * @deprecated Use {@link IScopedClusterClient}. + */ client: ILegacyScopedClusterClient; }; }; diff --git a/src/core/server/saved_objects/service/lib/integration_tests/repository.test.ts b/src/core/server/saved_objects/service/lib/integration_tests/repository.test.ts new file mode 100644 index 00000000000000..2f64776501df06 --- /dev/null +++ b/src/core/server/saved_objects/service/lib/integration_tests/repository.test.ts @@ -0,0 +1,152 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { InternalCoreStart } from 'src/core/server/internal_types'; +import * as kbnTestServer from '../../../../../test_helpers/kbn_server'; +import { Root } from '../../../../root'; + +const { startES } = kbnTestServer.createTestServers({ + adjustTimeout: (t: number) => jest.setTimeout(t), +}); +let esServer: kbnTestServer.TestElasticsearchUtils; + +describe('SavedObjectsRepository', () => { + let root: Root; + let start: InternalCoreStart; + + beforeAll(async () => { + esServer = await startES(); + root = kbnTestServer.createRootWithCorePlugins({ + server: { + basePath: '/hello', + }, + }); + + const setup = await root.setup(); + setup.savedObjects.registerType({ + hidden: false, + mappings: { + dynamic: false, + properties: {}, + }, + name: 'test_counter_type', + namespaceType: 'single', + }); + start = await root.start(); + }); + + afterAll(async () => { + await esServer.stop(); + await root.shutdown(); + }); + + describe('#incrementCounter', () => { + describe('initialize=false', () => { + it('creates a new document if none exists and sets all counter fields set to 1', async () => { + const now = new Date().getTime(); + const repository = start.savedObjects.createInternalRepository(); + await repository.incrementCounter('test_counter_type', 'counter_1', [ + 'stats.api.count', + 'stats.api.count2', + 'stats.total', + ]); + const result = await repository.get('test_counter_type', 'counter_1'); + expect(result.attributes).toMatchInlineSnapshot(` + Object { + "stats.api.count": 1, + "stats.api.count2": 1, + "stats.total": 1, + } + `); + expect(Date.parse(result.updated_at!)).toBeGreaterThanOrEqual(now); + }); + it('increments the specified counters of an existing document', async () => { + const repository = start.savedObjects.createInternalRepository(); + // Create document + await repository.incrementCounter('test_counter_type', 'counter_2', [ + 'stats.api.count', + 'stats.api.count2', + 'stats.total', + ]); + + const now = new Date().getTime(); + // Increment counters + await repository.incrementCounter('test_counter_type', 'counter_2', [ + 'stats.api.count', + 'stats.api.count2', + 'stats.total', + ]); + const result = await repository.get('test_counter_type', 'counter_2'); + expect(result.attributes).toMatchInlineSnapshot(` + Object { + "stats.api.count": 2, + "stats.api.count2": 2, + "stats.total": 2, + } + `); + expect(Date.parse(result.updated_at!)).toBeGreaterThanOrEqual(now); + }); + }); + describe('initialize=true', () => { + it('creates a new document if none exists and sets all counter fields to 0', async () => { + const now = new Date().getTime(); + const repository = start.savedObjects.createInternalRepository(); + await repository.incrementCounter( + 'test_counter_type', + 'counter_3', + ['stats.api.count', 'stats.api.count2', 'stats.total'], + { initialize: true } + ); + const result = await repository.get('test_counter_type', 'counter_3'); + expect(result.attributes).toMatchInlineSnapshot(` + Object { + "stats.api.count": 0, + "stats.api.count2": 0, + "stats.total": 0, + } + `); + expect(Date.parse(result.updated_at!)).toBeGreaterThanOrEqual(now); + }); + it('sets any undefined counter fields to 0 but does not alter existing fields of an existing document', async () => { + const repository = start.savedObjects.createInternalRepository(); + // Create document + await repository.incrementCounter('test_counter_type', 'counter_4', [ + 'stats.existing_field', + ]); + + const now = new Date().getTime(); + // Initialize counters + await repository.incrementCounter( + 'test_counter_type', + 'counter_4', + ['stats.existing_field', 'stats.new_field'], + { initialize: true } + ); + const result = await repository.get('test_counter_type', 'counter_4'); + expect(result.attributes).toMatchInlineSnapshot(` + Object { + "stats.existing_field": 1, + "stats.new_field": 0, + } + `); + expect(Date.parse(result.updated_at!)).toBeGreaterThanOrEqual(now); + }); + }); + }); +}); diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index 6f885f17fd82b2..8443d1dd071847 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -3272,11 +3272,11 @@ describe('SavedObjectsRepository', () => { describe('#incrementCounter', () => { const type = 'config'; const id = 'one'; - const field = 'buildNum'; + const counterFields = ['buildNum', 'apiCallsCount']; const namespace = 'foo-namespace'; const originId = 'some-origin-id'; - const incrementCounterSuccess = async (type, id, field, options) => { + const incrementCounterSuccess = async (type, id, fields, options) => { const isMultiNamespace = registry.isMultiNamespace(type); if (isMultiNamespace) { const response = getMockGetResponse({ type, id }, options?.namespace); @@ -3295,7 +3295,10 @@ describe('SavedObjectsRepository', () => { type, ...mockTimestampFields, [type]: { - [field]: 8468, + ...fields.reduce((acc, field) => { + acc[field] = 8468; + return acc; + }, {}), defaultIndex: 'logstash-*', }, }, @@ -3303,25 +3306,25 @@ describe('SavedObjectsRepository', () => { }) ); - const result = await savedObjectsRepository.incrementCounter(type, id, field, options); + const result = await savedObjectsRepository.incrementCounter(type, id, fields, options); expect(client.get).toHaveBeenCalledTimes(isMultiNamespace ? 1 : 0); return result; }; describe('client calls', () => { it(`should use the ES update action if type is not multi-namespace`, async () => { - await incrementCounterSuccess(type, id, field, { namespace }); + await incrementCounterSuccess(type, id, counterFields, { namespace }); expect(client.update).toHaveBeenCalledTimes(1); }); it(`should use the ES get action then update action if type is multi-namespace, ID is defined, and overwrite=true`, async () => { - await incrementCounterSuccess(MULTI_NAMESPACE_TYPE, id, field, { namespace }); + await incrementCounterSuccess(MULTI_NAMESPACE_TYPE, id, counterFields, { namespace }); expect(client.get).toHaveBeenCalledTimes(1); expect(client.update).toHaveBeenCalledTimes(1); }); it(`defaults to a refresh setting of wait_for`, async () => { - await incrementCounterSuccess(type, id, field, { namespace }); + await incrementCounterSuccess(type, id, counterFields, { namespace }); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ refresh: 'wait_for', @@ -3331,7 +3334,7 @@ describe('SavedObjectsRepository', () => { }); it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => { - await incrementCounterSuccess(type, id, field, { namespace }); + await incrementCounterSuccess(type, id, counterFields, { namespace }); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ id: `${namespace}:${type}:${id}`, @@ -3341,7 +3344,7 @@ describe('SavedObjectsRepository', () => { }); it(`doesn't prepend namespace to the id when providing no namespace for single-namespace type`, async () => { - await incrementCounterSuccess(type, id, field); + await incrementCounterSuccess(type, id, counterFields); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ id: `${type}:${id}`, @@ -3351,7 +3354,7 @@ describe('SavedObjectsRepository', () => { }); it(`normalizes options.namespace from 'default' to undefined`, async () => { - await incrementCounterSuccess(type, id, field, { namespace: 'default' }); + await incrementCounterSuccess(type, id, counterFields, { namespace: 'default' }); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ id: `${type}:${id}`, @@ -3361,7 +3364,7 @@ describe('SavedObjectsRepository', () => { }); it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { - await incrementCounterSuccess(NAMESPACE_AGNOSTIC_TYPE, id, field, { namespace }); + await incrementCounterSuccess(NAMESPACE_AGNOSTIC_TYPE, id, counterFields, { namespace }); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ id: `${NAMESPACE_AGNOSTIC_TYPE}:${id}`, @@ -3370,7 +3373,7 @@ describe('SavedObjectsRepository', () => { ); client.update.mockClear(); - await incrementCounterSuccess(MULTI_NAMESPACE_TYPE, id, field, { namespace }); + await incrementCounterSuccess(MULTI_NAMESPACE_TYPE, id, counterFields, { namespace }); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ id: `${MULTI_NAMESPACE_TYPE}:${id}`, @@ -3389,7 +3392,7 @@ describe('SavedObjectsRepository', () => { it(`throws when options.namespace is '*'`, async () => { await expect( - savedObjectsRepository.incrementCounter(type, id, field, { + savedObjectsRepository.incrementCounter(type, id, counterFields, { namespace: ALL_NAMESPACES_STRING, }) ).rejects.toThrowError(createBadRequestError('"options.namespace" cannot be "*"')); @@ -3398,7 +3401,7 @@ describe('SavedObjectsRepository', () => { it(`throws when type is not a string`, async () => { const test = async (type) => { await expect( - savedObjectsRepository.incrementCounter(type, id, field) + savedObjectsRepository.incrementCounter(type, id, counterFields) ).rejects.toThrowError(`"type" argument must be a string`); expect(client.update).not.toHaveBeenCalled(); }; @@ -3413,23 +3416,24 @@ describe('SavedObjectsRepository', () => { const test = async (field) => { await expect( savedObjectsRepository.incrementCounter(type, id, field) - ).rejects.toThrowError(`"counterFieldName" argument must be a string`); + ).rejects.toThrowError(`"counterFieldNames" argument must be an array of strings`); expect(client.update).not.toHaveBeenCalled(); }; - await test(null); - await test(42); - await test(false); - await test({}); + await test([null]); + await test([42]); + await test([false]); + await test([{}]); + await test([{}, false, 42, null, 'string']); }); it(`throws when type is invalid`, async () => { - await expectUnsupportedTypeError('unknownType', id, field); + await expectUnsupportedTypeError('unknownType', id, counterFields); expect(client.update).not.toHaveBeenCalled(); }); it(`throws when type is hidden`, async () => { - await expectUnsupportedTypeError(HIDDEN_TYPE, id, field); + await expectUnsupportedTypeError(HIDDEN_TYPE, id, counterFields); expect(client.update).not.toHaveBeenCalled(); }); @@ -3439,7 +3443,9 @@ describe('SavedObjectsRepository', () => { elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); await expect( - savedObjectsRepository.incrementCounter(MULTI_NAMESPACE_TYPE, id, field, { namespace }) + savedObjectsRepository.incrementCounter(MULTI_NAMESPACE_TYPE, id, counterFields, { + namespace, + }) ).rejects.toThrowError(createConflictError(MULTI_NAMESPACE_TYPE, id)); expect(client.get).toHaveBeenCalledTimes(1); }); @@ -3452,8 +3458,8 @@ describe('SavedObjectsRepository', () => { it(`migrates a document and serializes the migrated doc`, async () => { const migrationVersion = mockMigrationVersion; - await incrementCounterSuccess(type, id, field, { migrationVersion }); - const attributes = { buildNum: 1 }; // this is added by the incrementCounter function + await incrementCounterSuccess(type, id, counterFields, { migrationVersion }); + const attributes = { buildNum: 1, apiCallsCount: 1 }; // this is added by the incrementCounter function const doc = { type, id, attributes, migrationVersion, ...mockTimestampFields }; expectMigrationArgs(doc); @@ -3476,6 +3482,7 @@ describe('SavedObjectsRepository', () => { ...mockTimestampFields, config: { buildNum: 8468, + apiCallsCount: 100, defaultIndex: 'logstash-*', }, originId, @@ -3487,7 +3494,7 @@ describe('SavedObjectsRepository', () => { const response = await savedObjectsRepository.incrementCounter( 'config', '6.0.0-alpha1', - 'buildNum', + ['buildNum', 'apiCallsCount'], { namespace: 'foo-namespace', } @@ -3500,6 +3507,7 @@ describe('SavedObjectsRepository', () => { version: mockVersion, attributes: { buildNum: 8468, + apiCallsCount: 100, defaultIndex: 'logstash-*', }, originId, diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index d362c02de49153..2f09ad71de5587 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -101,8 +101,17 @@ export interface SavedObjectsRepositoryOptions { * @public */ export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions { + /** + * (default=false) If true, sets all the counter fields to 0 if they don't + * already exist. Existing fields will be left as-is and won't be incremented. + */ + initialize?: boolean; + /** {@link SavedObjectsMigrationVersion} */ migrationVersion?: SavedObjectsMigrationVersion; - /** The Elasticsearch Refresh setting for this operation */ + /** + * (default='wait_for') The Elasticsearch refresh setting for this + * operation. See {@link MutatingOperationRefreshSetting} + */ refresh?: MutatingOperationRefreshSetting; } @@ -1515,32 +1524,64 @@ export class SavedObjectsRepository { } /** - * Increases a counter field by one. Creates the document if one doesn't exist for the given id. + * Increments all the specified counter fields by one. Creates the document + * if one doesn't exist for the given id. * - * @param {string} type - * @param {string} id - * @param {string} counterFieldName - * @param {object} [options={}] - * @property {object} [options.migrationVersion=undefined] - * @returns {promise} + * @remarks + * When supplying a field name like `stats.api.counter` the field name will + * be used as-is to create a document like: + * `{attributes: {'stats.api.counter': 1}}` + * It will not create a nested structure like: + * `{attributes: {stats: {api: {counter: 1}}}}` + * + * When using incrementCounter for collecting usage data, you need to ensure + * that usage collection happens on a best-effort basis and doesn't + * negatively affect your plugin or users. See https://github.com/elastic/kibana/blob/master/src/plugins/usage_collection/README.md#tracking-interactions-with-incrementcounter) + * + * @example + * ```ts + * const repository = coreStart.savedObjects.createInternalRepository(); + * + * // Initialize all fields to 0 + * repository + * .incrementCounter('dashboard_counter_type', 'counter_id', [ + * 'stats.apiCalls', + * 'stats.sampleDataInstalled', + * ], {initialize: true}); + * + * // Increment the apiCalls field counter + * repository + * .incrementCounter('dashboard_counter_type', 'counter_id', [ + * 'stats.apiCalls', + * ]) + * ``` + * + * @param type - The type of saved object whose fields should be incremented + * @param id - The id of the document whose fields should be incremented + * @param counterFieldNames - An array of field names to increment + * @param options - {@link SavedObjectsIncrementCounterOptions} + * @returns The saved object after the specified fields were incremented */ async incrementCounter( type: string, id: string, - counterFieldName: string, + counterFieldNames: string[], options: SavedObjectsIncrementCounterOptions = {} ): Promise { if (typeof type !== 'string') { throw new Error('"type" argument must be a string'); } - if (typeof counterFieldName !== 'string') { - throw new Error('"counterFieldName" argument must be a string'); + const isArrayOfStrings = + Array.isArray(counterFieldNames) && + !counterFieldNames.some((field) => typeof field !== 'string'); + if (!isArrayOfStrings) { + throw new Error('"counterFieldNames" argument must be an array of strings'); } if (!this._allowedTypes.includes(type)) { throw SavedObjectsErrorHelpers.createUnsupportedTypeError(type); } - const { migrationVersion, refresh = DEFAULT_REFRESH_SETTING } = options; + const { migrationVersion, refresh = DEFAULT_REFRESH_SETTING, initialize = false } = options; const namespace = normalizeNamespace(options.namespace); const time = this._getCurrentTime(); @@ -1558,7 +1599,10 @@ export class SavedObjectsRepository { type, ...(savedObjectNamespace && { namespace: savedObjectNamespace }), ...(savedObjectNamespaces && { namespaces: savedObjectNamespaces }), - attributes: { [counterFieldName]: 1 }, + attributes: counterFieldNames.reduce((acc, counterFieldName) => { + acc[counterFieldName] = initialize ? 0 : 1; + return acc; + }, {} as Record), migrationVersion, updated_at: time, }); @@ -1573,20 +1617,22 @@ export class SavedObjectsRepository { body: { script: { source: ` - if (ctx._source[params.type][params.counterFieldName] == null) { - ctx._source[params.type][params.counterFieldName] = params.count; - } - else { - ctx._source[params.type][params.counterFieldName] += params.count; + for (counterFieldName in params.counterFieldNames) { + if (ctx._source[params.type][counterFieldName] == null) { + ctx._source[params.type][counterFieldName] = params.count; + } + else { + ctx._source[params.type][counterFieldName] += params.count; + } } ctx._source.updated_at = params.time; `, lang: 'painless', params: { - count: 1, + count: initialize ? 0 : 1, time, type, - counterFieldName, + counterFieldNames, }, }, upsert: raw._source, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index a03e5ec9acd275..36a8d9a52fd52d 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1324,6 +1324,7 @@ export interface LegacyCallAPIOptions { export class LegacyClusterClient implements ILegacyClusterClient { constructor(config: LegacyElasticsearchClientConfig, log: Logger, getAuthHeaders?: GetAuthHeaders); asScoped(request?: ScopeableRequest): ILegacyScopedClusterClient; + // @deprecated callAsInternalUser: LegacyAPICaller; close(): void; } @@ -1371,7 +1372,9 @@ export interface LegacyRequest extends Request { // @public @deprecated export class LegacyScopedClusterClient implements ILegacyScopedClusterClient { constructor(internalAPICaller: LegacyAPICaller, scopedAPICaller: LegacyAPICaller, headers?: Headers | undefined); + // @deprecated callAsCurrentUser(endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions): Promise; + // @deprecated callAsInternalUser(endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions): Promise; } @@ -2366,6 +2369,7 @@ export interface SavedObjectsImportUnsupportedTypeError { // @public (undocumented) export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions { + initialize?: boolean; // (undocumented) migrationVersion?: SavedObjectsMigrationVersion; refresh?: MutatingOperationRefreshSetting; @@ -2444,7 +2448,7 @@ export class SavedObjectsRepository { // (undocumented) find(options: SavedObjectsFindOptions): Promise>; get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; - incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise; + incrementCounter(type: string, id: string, counterFieldNames: string[], options?: SavedObjectsIncrementCounterOptions): Promise; removeReferencesTo(type: string, id: string, options?: SavedObjectsRemoveReferencesToOptions): Promise; update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; } diff --git a/src/core/server/ui_settings/cache.test.ts b/src/core/server/ui_settings/cache.test.ts new file mode 100644 index 00000000000000..ea375751fe4372 --- /dev/null +++ b/src/core/server/ui_settings/cache.test.ts @@ -0,0 +1,50 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { Cache } from './cache'; + +describe('Cache', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.clearAllTimers(); + }); + it('stores value for maxAge ms', async () => { + const cache = new Cache(500); + cache.set(42); + expect(cache.get()).toBe(42); + jest.advanceTimersByTime(100); + expect(cache.get()).toBe(42); + }); + it('invalidates cache after maxAge ms', async () => { + const cache = new Cache(500); + cache.set(42); + expect(cache.get()).toBe(42); + jest.advanceTimersByTime(1000); + expect(cache.get()).toBe(null); + }); + it('del invalidates cache immediately', async () => { + const cache = new Cache(10); + cache.set(42); + expect(cache.get()).toBe(42); + cache.del(); + expect(cache.get()).toBe(null); + }); +}); diff --git a/src/core/server/ui_settings/cache.ts b/src/core/server/ui_settings/cache.ts new file mode 100644 index 00000000000000..697cf2b284c785 --- /dev/null +++ b/src/core/server/ui_settings/cache.ts @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +const oneSec = 1000; +const defMaxAge = 5 * oneSec; +/** + * @internal + */ +export class Cache> { + private value: T | null; + private timer?: NodeJS.Timeout; + + /** + * Delete cached value after maxAge ms. + */ + constructor(private readonly maxAge: number = defMaxAge) { + this.value = null; + } + get() { + return this.value; + } + set(value: T) { + this.del(); + this.value = value; + this.timer = setTimeout(() => this.del(), this.maxAge); + } + del() { + if (this.timer) { + clearTimeout(this.timer); + } + this.value = null; + } +} diff --git a/src/core/server/ui_settings/ui_settings_client.test.ts b/src/core/server/ui_settings/ui_settings_client.test.ts index a38fb2ab7e06c6..8238511e27ed9e 100644 --- a/src/core/server/ui_settings/ui_settings_client.test.ts +++ b/src/core/server/ui_settings/ui_settings_client.test.ts @@ -676,4 +676,111 @@ describe('ui settings', () => { expect(uiSettings.isOverridden('bar')).toBe(true); }); }); + + describe('caching', () => { + describe('read operations cache user config', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.clearAllTimers(); + }); + + it('get', async () => { + const esDocSource = {}; + const { uiSettings, savedObjectsClient } = setup({ esDocSource }); + + await uiSettings.get('any'); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(1); + + await uiSettings.get('foo'); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(10000); + await uiSettings.get('foo'); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(2); + }); + + it('getAll', async () => { + const esDocSource = {}; + const { uiSettings, savedObjectsClient } = setup({ esDocSource }); + + await uiSettings.getAll(); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(1); + + await uiSettings.getAll(); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(10000); + await uiSettings.getAll(); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(2); + }); + + it('getUserProvided', async () => { + const esDocSource = {}; + const { uiSettings, savedObjectsClient } = setup({ esDocSource }); + + await uiSettings.getUserProvided(); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(1); + + await uiSettings.getUserProvided(); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(10000); + await uiSettings.getUserProvided(); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(2); + }); + }); + + describe('write operations invalidate user config cache', () => { + it('set', async () => { + const esDocSource = {}; + const { uiSettings, savedObjectsClient } = setup({ esDocSource }); + + await uiSettings.get('any'); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(1); + + await uiSettings.set('foo', 'bar'); + await uiSettings.get('foo'); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(2); + }); + + it('setMany', async () => { + const esDocSource = {}; + const { uiSettings, savedObjectsClient } = setup({ esDocSource }); + + await uiSettings.get('any'); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(1); + + await uiSettings.setMany({ foo: 'bar' }); + await uiSettings.get('foo'); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(2); + }); + + it('remove', async () => { + const esDocSource = {}; + const { uiSettings, savedObjectsClient } = setup({ esDocSource }); + + await uiSettings.get('any'); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(1); + + await uiSettings.remove('foo'); + await uiSettings.get('foo'); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(2); + }); + + it('removeMany', async () => { + const esDocSource = {}; + const { uiSettings, savedObjectsClient } = setup({ esDocSource }); + + await uiSettings.get('any'); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(1); + + await uiSettings.removeMany(['foo', 'bar']); + await uiSettings.get('foo'); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(2); + }); + }); + }); }); diff --git a/src/core/server/ui_settings/ui_settings_client.ts b/src/core/server/ui_settings/ui_settings_client.ts index f168784a933305..ab5fca9f810310 100644 --- a/src/core/server/ui_settings/ui_settings_client.ts +++ b/src/core/server/ui_settings/ui_settings_client.ts @@ -24,6 +24,7 @@ import { Logger } from '../logging'; import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; import { IUiSettingsClient, UiSettingsParams, PublicUiSettingsParams } from './types'; import { CannotOverrideError } from './ui_settings_errors'; +import { Cache } from './cache'; export interface UiSettingsServiceOptions { type: string; @@ -36,7 +37,6 @@ export interface UiSettingsServiceOptions { } interface ReadOptions { - ignore401Errors?: boolean; autoCreateOrUpgradeIfMissing?: boolean; } @@ -58,6 +58,7 @@ export class UiSettingsClient implements IUiSettingsClient { private readonly overrides: NonNullable; private readonly defaults: NonNullable; private readonly log: Logger; + private readonly cache: Cache; constructor(options: UiSettingsServiceOptions) { const { type, id, buildNum, savedObjectsClient, log, defaults = {}, overrides = {} } = options; @@ -69,6 +70,7 @@ export class UiSettingsClient implements IUiSettingsClient { this.defaults = defaults; this.overrides = overrides; this.log = log; + this.cache = new Cache(); } getRegistered() { @@ -95,7 +97,12 @@ export class UiSettingsClient implements IUiSettingsClient { } async getUserProvided(): Promise> { - const userProvided: UserProvided = this.onReadHook(await this.read()); + const cachedValue = this.cache.get(); + if (cachedValue) { + return cachedValue; + } + + const userProvided: UserProvided = this.onReadHook(await this.read()); // write all overridden keys, dropping the userValue is override is null and // adding keys for overrides that are not in saved object @@ -104,10 +111,13 @@ export class UiSettingsClient implements IUiSettingsClient { value === null ? { isOverridden: true } : { isOverridden: true, userValue: value }; } + this.cache.set(userProvided); + return userProvided; } async setMany(changes: Record) { + this.cache.del(); this.onWriteHook(changes); await this.write({ changes }); } @@ -140,7 +150,7 @@ export class UiSettingsClient implements IUiSettingsClient { private async getRaw(): Promise { const userProvided = await this.getUserProvided(); - return defaultsDeep(userProvided, this.defaults); + return defaultsDeep({}, userProvided, this.defaults); } private validateKey(key: string, value: unknown) { @@ -209,10 +219,9 @@ export class UiSettingsClient implements IUiSettingsClient { } } - private async read({ - ignore401Errors = false, - autoCreateOrUpgradeIfMissing = true, - }: ReadOptions = {}): Promise> { + private async read({ autoCreateOrUpgradeIfMissing = true }: ReadOptions = {}): Promise< + Record + > { try { const resp = await this.savedObjectsClient.get>(this.type, this.id); return resp.attributes; @@ -227,16 +236,13 @@ export class UiSettingsClient implements IUiSettingsClient { }); if (!failedUpgradeAttributes) { - return await this.read({ - ignore401Errors, - autoCreateOrUpgradeIfMissing: false, - }); + return await this.read({ autoCreateOrUpgradeIfMissing: false }); } return failedUpgradeAttributes; } - if (this.isIgnorableError(error, ignore401Errors)) { + if (this.isIgnorableError(error)) { return {}; } @@ -244,17 +250,9 @@ export class UiSettingsClient implements IUiSettingsClient { } } - private isIgnorableError(error: Error, ignore401Errors: boolean) { - const { - isForbiddenError, - isEsUnavailableError, - isNotAuthorizedError, - } = this.savedObjectsClient.errors; - - return ( - isForbiddenError(error) || - isEsUnavailableError(error) || - (ignore401Errors && isNotAuthorizedError(error)) - ); + private isIgnorableError(error: Error) { + const { isForbiddenError, isEsUnavailableError } = this.savedObjectsClient.errors; + + return isForbiddenError(error) || isEsUnavailableError(error); } } diff --git a/src/core/tsconfig.json b/src/core/tsconfig.json index 4281559c9aa140..e8a82ee533f11f 100644 --- a/src/core/tsconfig.json +++ b/src/core/tsconfig.json @@ -14,8 +14,5 @@ "test_helpers/**/*", "utils/**/*", "index.ts" - ], - "references": [ - { "path": "../test_utils/" } ] } diff --git a/src/dev/build/tasks/nodejs/download_node_builds_task.ts b/src/dev/build/tasks/nodejs/download_node_builds_task.ts index ad42ea11436f55..93ad599e41e404 100644 --- a/src/dev/build/tasks/nodejs/download_node_builds_task.ts +++ b/src/dev/build/tasks/nodejs/download_node_builds_task.ts @@ -25,7 +25,7 @@ export const DownloadNodeBuilds: GlobalTask = { global: true, description: 'Downloading node.js builds for all platforms', async run(config, log) { - const shasums = await getNodeShasums(config.getNodeVersion()); + const shasums = await getNodeShasums(log, config.getNodeVersion()); await Promise.all( config.getNodePlatforms().map(async (platform) => { const { url, downloadPath, downloadName } = getNodeDownloadInfo(config, platform); diff --git a/src/dev/build/tasks/nodejs/node_shasums.test.ts b/src/dev/build/tasks/nodejs/node_shasums.test.ts index 08ac823c7ebf03..53d073afd64991 100644 --- a/src/dev/build/tasks/nodejs/node_shasums.test.ts +++ b/src/dev/build/tasks/nodejs/node_shasums.test.ts @@ -70,11 +70,12 @@ jest.mock('axios', () => ({ }, })); +import { ToolingLog } from '@kbn/dev-utils'; import { getNodeShasums } from './node_shasums'; describe('src/dev/build/tasks/nodejs/node_shasums', () => { it('resolves to an object with shasums for node downloads for version', async () => { - const shasums = await getNodeShasums('8.9.4'); + const shasums = await getNodeShasums(new ToolingLog(), '8.9.4'); expect(shasums).toEqual( expect.objectContaining({ 'node-v8.9.4.tar.gz': '729b44b32b2f82ecd5befac4f7518de0c4e3add34e8fe878f745740a66cbbc01', diff --git a/src/dev/build/tasks/nodejs/node_shasums.ts b/src/dev/build/tasks/nodejs/node_shasums.ts index e0926aa3e49e4b..0f506dff4fd884 100644 --- a/src/dev/build/tasks/nodejs/node_shasums.ts +++ b/src/dev/build/tasks/nodejs/node_shasums.ts @@ -18,10 +18,13 @@ */ import axios from 'axios'; +import { ToolingLog } from '@kbn/dev-utils'; -export async function getNodeShasums(nodeVersion: string) { +export async function getNodeShasums(log: ToolingLog, nodeVersion: string) { const url = `https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v${nodeVersion}/SHASUMS256.txt`; + log.debug('Downloading shasum values for node version', nodeVersion, 'from', url); + const { status, data } = await axios.get(url); if (status !== 200) { diff --git a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts index 9b03dcd828cf9c..5b3c1bad749300 100644 --- a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts +++ b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts @@ -45,6 +45,7 @@ const testWriter = new ToolingLogCollectingWriter(); log.setWriters([testWriter]); expect.addSnapshotSerializer(createAnyInstanceSerializer(Config)); +expect.addSnapshotSerializer(createAnyInstanceSerializer(ToolingLog)); const nodeVersion = Fs.readFileSync(Path.resolve(REPO_ROOT, '.node-version'), 'utf8').trim(); expect.addSnapshotSerializer( @@ -100,6 +101,7 @@ it('checks shasums for each downloaded node build', async () => { [MockFunction] { "calls": Array [ Array [ + , "", ], ], diff --git a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.ts b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.ts index 9ce0778d2d1f08..50684d866cbf51 100644 --- a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.ts +++ b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.ts @@ -25,7 +25,7 @@ export const VerifyExistingNodeBuilds: GlobalTask = { global: true, description: 'Verifying previously downloaded node.js build for all platforms', async run(config, log) { - const shasums = await getNodeShasums(config.getNodeVersion()); + const shasums = await getNodeShasums(log, config.getNodeVersion()); await Promise.all( config.getNodePlatforms().map(async (platform) => { diff --git a/src/dev/code_coverage/ingest_coverage/integration_tests/team_assignment.test.js b/src/dev/code_coverage/ingest_coverage/integration_tests/team_assignment.test.js index c666581ddb08ce..177439c56a1157 100644 --- a/src/dev/code_coverage/ingest_coverage/integration_tests/team_assignment.test.js +++ b/src/dev/code_coverage/ingest_coverage/integration_tests/team_assignment.test.js @@ -49,7 +49,8 @@ describe('Team Assignment', () => { describe(`when the codeowners file contains #CC#`, () => { it(`should strip the prefix and still drill down through the fs`, async () => { const { stdout } = await execa('grep', ['tre', teamAssignmentsPath], { cwd: ROOT_DIR }); - expect(stdout).to.be(`x-pack/plugins/code/server/config.ts kibana-tre + expect(stdout).to.be(`x-pack/plugins/code/jest.config.js kibana-tre +x-pack/plugins/code/server/config.ts kibana-tre x-pack/plugins/code/server/index.ts kibana-tre x-pack/plugins/code/server/plugin.test.ts kibana-tre x-pack/plugins/code/server/plugin.ts kibana-tre`); diff --git a/src/dev/jest.config.js b/src/dev/jest.config.js new file mode 100644 index 00000000000000..bdb51372e2c267 --- /dev/null +++ b/src/dev/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/src/dev'], +}; diff --git a/src/dev/plugin_discovery/find_plugins.ts b/src/dev/plugin_discovery/find_plugins.ts index 4e7c34698c9648..e07c503e213307 100644 --- a/src/dev/plugin_discovery/find_plugins.ts +++ b/src/dev/plugin_discovery/find_plugins.ts @@ -17,9 +17,12 @@ * under the License. */ import Path from 'path'; -import { REPO_ROOT } from '@kbn/dev-utils'; import { getPluginSearchPaths } from '@kbn/config'; -import { KibanaPlatformPlugin, simpleKibanaPlatformPluginDiscovery } from '@kbn/dev-utils'; +import { + KibanaPlatformPlugin, + REPO_ROOT, + simpleKibanaPlatformPluginDiscovery, +} from '@kbn/dev-utils'; export interface SearchOptions { oss: boolean; diff --git a/src/dev/run_find_plugin_circular_deps.ts b/src/dev/run_find_plugin_circular_deps.ts deleted file mode 100644 index 501e2c4fed0480..00000000000000 --- a/src/dev/run_find_plugin_circular_deps.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { run } from '@kbn/dev-utils'; -import { findPlugins, getPluginDeps, SearchErrors } from './plugin_discovery'; - -interface AllOptions { - examples?: boolean; - extraPluginScanDirs?: string[]; -} - -run( - async ({ flags, log }) => { - const { examples = false, extraPluginScanDirs = [] } = flags as AllOptions; - - const pluginMap = findPlugins({ - oss: false, - examples, - extraPluginScanDirs, - }); - - const allErrors = new Map(); - for (const pluginId of pluginMap.keys()) { - const { errors } = getPluginDeps({ - pluginMap, - id: pluginId, - }); - - for (const [errorId, error] of errors) { - if (!allErrors.has(errorId)) { - allErrors.set(errorId, error); - } - } - } - - if (allErrors.size > 0) { - allErrors.forEach((error) => { - log.warning( - `Circular refs detected: ${[...error.stack, error.to].map((p) => `[${p}]`).join(' --> ')}` - ); - }); - } - }, - { - flags: { - boolean: ['examples'], - default: { - examples: false, - }, - allowUnexpected: false, - help: ` - --examples Include examples folder - --extraPluginScanDirs Include extra scan folder - `, - }, - } -); diff --git a/src/dev/run_find_plugins_with_circular_deps.ts b/src/dev/run_find_plugins_with_circular_deps.ts new file mode 100644 index 00000000000000..65a03a87525d71 --- /dev/null +++ b/src/dev/run_find_plugins_with_circular_deps.ts @@ -0,0 +1,214 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import dedent from 'dedent'; +import { parseDependencyTree, parseCircular, prettyCircular } from 'dpdm'; +import { relative } from 'path'; +import { getPluginSearchPaths } from '@kbn/config'; +import { REPO_ROOT, run } from '@kbn/dev-utils'; + +interface Options { + debug?: boolean; + filter?: string; +} + +type CircularDepList = Set; + +const allowedList: CircularDepList = new Set([ + 'src/plugins/charts -> src/plugins/expressions', + 'src/plugins/charts -> src/plugins/vis_default_editor', + 'src/plugins/data -> src/plugins/embeddable', + 'src/plugins/data -> src/plugins/expressions', + 'src/plugins/data -> src/plugins/ui_actions', + 'src/plugins/embeddable -> src/plugins/ui_actions', + 'src/plugins/expressions -> src/plugins/visualizations', + 'src/plugins/vis_default_editor -> src/plugins/visualizations', + 'src/plugins/vis_default_editor -> src/plugins/visualize', + 'src/plugins/visualizations -> src/plugins/visualize', + 'x-pack/plugins/actions -> x-pack/plugins/case', + 'x-pack/plugins/apm -> x-pack/plugins/infra', + 'x-pack/plugins/lists -> x-pack/plugins/security_solution', + 'x-pack/plugins/security -> x-pack/plugins/spaces', +]); + +run( + async ({ flags, log }) => { + const { debug, filter } = flags as Options; + const foundList: CircularDepList = new Set(); + + const pluginSearchPathGlobs = getPluginSearchPaths({ + rootDir: REPO_ROOT, + oss: false, + examples: true, + }).map((pluginFolderPath) => `${relative(REPO_ROOT, pluginFolderPath)}/**/*`); + + const depTree = await parseDependencyTree(pluginSearchPathGlobs, { + context: REPO_ROOT, + }); + + // Build list of circular dependencies as well as the circular dependencies full paths + const circularDependenciesFullPaths = parseCircular(depTree).filter((circularDeps) => { + const first = circularDeps[0]; + const last = circularDeps[circularDeps.length - 1]; + const matchRegex = /(?(src|x-pack)\/plugins|examples|x-pack\/examples)\/(?[^\/]*)\/.*/; + const firstMatch = first.match(matchRegex); + const lastMatch = last.match(matchRegex); + + if ( + firstMatch?.groups?.pluginFolder && + firstMatch?.groups?.pluginName && + lastMatch?.groups?.pluginFolder && + lastMatch?.groups?.pluginName + ) { + const firstPlugin = `${firstMatch.groups.pluginFolder}/${firstMatch.groups.pluginName}`; + const lastPlugin = `${lastMatch.groups.pluginFolder}/${lastMatch.groups.pluginName}`; + const sortedPlugins = [firstPlugin, lastPlugin].sort(); + + // Exclude if both plugin paths involved in the circular dependency + // doesn't includes the provided filter + if (filter && !firstPlugin.includes(filter) && !lastPlugin.includes(filter)) { + return false; + } + + if (firstPlugin !== lastPlugin) { + foundList.add(`${sortedPlugins[0]} -> ${sortedPlugins[1]}`); + return true; + } + } + + return false; + }); + + if (!debug && filter) { + log.warning( + dedent(` + !!!!!!!!!!!!!! WARNING: FILTER WITHOUT DEBUG !!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + ! Using the --filter flag without using --debug flag ! + ! will not allow you to see the filtered list of ! + ! the correct results. ! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + `) + ); + } + + if (debug && filter) { + log.warning( + dedent(` + !!!!!!!!!!!!!!! WARNING: FILTER FLAG IS ON !!!!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + ! Be aware the following results are not complete as ! + ! --filter flag has been passed. Ignore suggestions ! + ! to update the allowedList or any reports of failures ! + ! or successes. ! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + The following filter has peen passed: ${filter} + `) + ); + } + + // Log the full circular dependencies path if we are under debug flag + if (debug && circularDependenciesFullPaths.length > 0) { + log.debug( + dedent(` + !!!!!!!!!!!!!! CIRCULAR DEPENDENCIES FOUND !!!!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + ! Circular dependencies were found, you can find below ! + ! all the paths involved. ! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + `) + ); + log.debug(`${prettyCircular(circularDependenciesFullPaths)}\n`); + } + + // Always log the result of comparing the found list with the allowed list + const diffSet = (first: CircularDepList, second: CircularDepList) => + new Set([...first].filter((circularDep) => !second.has(circularDep))); + + const printList = (list: CircularDepList) => { + return Array.from(list) + .sort() + .reduce((listStr, entry) => { + return listStr ? `${listStr}\n'${entry}',` : `'${entry}',`; + }, ''); + }; + + const foundDifferences = diffSet(foundList, allowedList); + + if (debug && !foundDifferences.size) { + log.debug( + dedent(` + !!!!!!!!!!!!!!!!! UP TO DATE ALLOWED LIST !!!!!!!!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + ! The declared circular dependencies allowed list is up ! + ! to date and includes every plugin listed in above paths. ! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + The allowed circular dependencies list is (#${allowedList.size}): + ${printList(allowedList)} + `) + ); + } + + if (foundDifferences.size > 0) { + log.error( + dedent(` + !!!!!!!!!!!!!!!!! OUT OF DATE ALLOWED LIST !!!!!!!!!!!!!!!!! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + ! The declared circular dependencies allowed list is out ! + ! of date. Please run the following locally to know more: ! + ! ! + ! 'node scripts/find_plugins_with_circular_deps --debug' ! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + The allowed circular dependencies list is (#${allowedList.size}): + ${printList(allowedList)} + + The found circular dependencies list is (#${foundList.size}): + ${printList(foundList)} + + The differences between both are (#${foundDifferences.size}): + ${printList(foundDifferences)} + + FAILED: circular dependencies in the allowed list declared on the file '${__filename}' did not match the found ones. + `) + ); + + process.exit(1); + } + + log.success('None non allowed circular dependencies were found'); + }, + { + description: + 'Searches circular dependencies between plugins located under src/plugins, x-pack/plugins, examples and x-pack/examples', + flags: { + boolean: ['debug'], + string: ['filter'], + default: { + debug: false, + }, + help: ` + --debug Run the script in debug mode which enables detailed path logs for circular dependencies + --filter It will only include in the results circular deps where the plugin paths contains parts of the passed string in the filter + `, + }, + } +); diff --git a/src/dev/typescript/projects.ts b/src/dev/typescript/projects.ts index 9891e9fa02c82d..6a13a86ba55a54 100644 --- a/src/dev/typescript/projects.ts +++ b/src/dev/typescript/projects.ts @@ -27,7 +27,6 @@ export const PROJECTS = [ new Project(resolve(REPO_ROOT, 'test/tsconfig.json'), { name: 'kibana/test' }), new Project(resolve(REPO_ROOT, 'x-pack/tsconfig.json')), new Project(resolve(REPO_ROOT, 'x-pack/test/tsconfig.json'), { name: 'x-pack/test' }), - new Project(resolve(REPO_ROOT, 'src/test_utils/tsconfig.json')), new Project(resolve(REPO_ROOT, 'src/core/tsconfig.json')), new Project(resolve(REPO_ROOT, 'x-pack/plugins/security_solution/cypress/tsconfig.json'), { name: 'security_solution/cypress', diff --git a/src/legacy/server/jest.config.js b/src/legacy/server/jest.config.js new file mode 100644 index 00000000000000..f971e823765ac1 --- /dev/null +++ b/src/legacy/server/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/legacy/server'], +}; diff --git a/src/legacy/ui/jest.config.js b/src/legacy/ui/jest.config.js new file mode 100644 index 00000000000000..45809f87971294 --- /dev/null +++ b/src/legacy/ui/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/legacy/ui'], +}; diff --git a/src/legacy/utils/jest.config.js b/src/legacy/utils/jest.config.js new file mode 100644 index 00000000000000..7ce73fa3676139 --- /dev/null +++ b/src/legacy/utils/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/legacy/utils'], +}; diff --git a/src/optimize/jest.config.js b/src/optimize/jest.config.js new file mode 100644 index 00000000000000..419f4f97098b30 --- /dev/null +++ b/src/optimize/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/src/optimize'], +}; diff --git a/src/plugins/advanced_settings/jest.config.js b/src/plugins/advanced_settings/jest.config.js new file mode 100644 index 00000000000000..94fd65aae44645 --- /dev/null +++ b/src/plugins/advanced_settings/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/advanced_settings'], +}; diff --git a/src/plugins/bfetch/jest.config.js b/src/plugins/bfetch/jest.config.js new file mode 100644 index 00000000000000..5976a994be7e57 --- /dev/null +++ b/src/plugins/bfetch/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/bfetch'], +}; diff --git a/src/plugins/charts/README.md b/src/plugins/charts/README.md index 31727b7acb7a1a..dae7b9695ed60e 100644 --- a/src/plugins/charts/README.md +++ b/src/plugins/charts/README.md @@ -27,3 +27,7 @@ Truncated color mappings in `value`/`text` form ## Theme See Theme service [docs](public/services/theme/README.md) + +## Palettes + +See palette service [docs](public/services/palettes/README.md) diff --git a/src/plugins/charts/jest.config.js b/src/plugins/charts/jest.config.js new file mode 100644 index 00000000000000..168ccde71a667d --- /dev/null +++ b/src/plugins/charts/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/charts'], +}; diff --git a/src/plugins/charts/public/services/palettes/README.md b/src/plugins/charts/public/services/palettes/README.md new file mode 100644 index 00000000000000..3403d422682bdb --- /dev/null +++ b/src/plugins/charts/public/services/palettes/README.md @@ -0,0 +1,33 @@ +# Palette Service + +The `palette` service offers a collection of palettes which implement a uniform interface for assigning colors to charts. The service provides methods for switching palettes +easily. It's used by the x-pack plugins `canvas` and `lens`. + +Each palette is allowed to store some state as well which has to be handled by the consumer. + +Palettes are integrated with the expression as well using the `system_palette` and `palette` functions. + +## Using the palette service + +To consume the palette service, use `charts.palettes.getPalettes` to lazily load the async bundle implementing existing palettes. This is recommended to be called in the renderer, not as part of the `setup` or `start` phases of a plugin. + +All palette definitions can be loaded using `paletteService.getAll()`. If the id of the palette is known, it can be fetched using `paleteService.get(id)`. + +One a palette is loaded, there are two ways to request colors - either by fetching a list of colors (`getColors`) or by specifying the chart object to be colored (`getColor`). If possible, using `getColor` is recommended because it allows the palette implementation to apply custom logic to coloring (e.g. lightening up colors or syncing colors) which has to be implemented by the consumer if `getColors` is used). + +### SeriesLayer + +If `getColor` is used, an array of `SeriesLayer` objects has to be passed in. These correspond with the current series in the chart a color has to be determined for. An array is necessary as some charts are constructed hierarchically (e.g. pie charts or treemaps). The array of objects represents the current series with all ancestors up to the corresponding root series. For each layer in the series hierarchy, the number of "sibling" series and the position of the current series has to be specified along with the name of the series. + +## Custom palette + +All palettes are stateless and define their own colors except for the `custom` palette which takes a state of the form +```ts +{ colors: string[]; gradient: boolean } +``` + +This state has to be passed into the `getColors` and `getColor` function to retrieve specific colors. + +## Registering new palettes + +Currently palettes can't be extended dynamically. diff --git a/src/plugins/console/jest.config.js b/src/plugins/console/jest.config.js new file mode 100644 index 00000000000000..f08613f91e1f14 --- /dev/null +++ b/src/plugins/console/jest.config.js @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/console'], + testRunner: 'jasmine2', +}; diff --git a/src/plugins/dashboard/jest.config.js b/src/plugins/dashboard/jest.config.js new file mode 100644 index 00000000000000..b9f6f66159b306 --- /dev/null +++ b/src/plugins/dashboard/jest.config.js @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/dashboard'], + testRunner: 'jasmine2', +}; diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index c99e4e4e069878..0d9e7e51b4a972 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -74,7 +74,7 @@ import { NavAction, SavedDashboardPanel } from '../types'; import { showOptionsPopover } from './top_nav/show_options_popover'; import { DashboardSaveModal, SaveOptions } from './top_nav/save_modal'; import { showCloneModal } from './top_nav/show_clone_modal'; -import { saveDashboard } from './lib'; +import { createSessionRestorationDataProvider, saveDashboard } from './lib'; import { DashboardStateManager } from './dashboard_state_manager'; import { createDashboardEditUrl, DashboardConstants } from '../dashboard_constants'; import { getTopNavConfig } from './top_nav/get_top_nav_config'; @@ -150,7 +150,7 @@ export class DashboardAppController { dashboardCapabilities, scopedHistory, embeddableCapabilities: { visualizeCapabilities, mapsCapabilities }, - data: { query: queryService, search: searchService }, + data, core: { notifications, overlays, @@ -168,6 +168,8 @@ export class DashboardAppController { navigation, savedObjectsTagging, }: DashboardAppControllerDependencies) { + const queryService = data.query; + const searchService = data.search; const filterManager = queryService.filterManager; const timefilter = queryService.timefilter.timefilter; const queryStringManager = queryService.queryString; @@ -262,6 +264,16 @@ export class DashboardAppController { $scope.showSaveQuery = dashboardCapabilities.saveQuery as boolean; + const landingPageUrl = () => `#${DashboardConstants.LANDING_PAGE_PATH}`; + + const getDashTitle = () => + getDashboardTitle( + dashboardStateManager.getTitle(), + dashboardStateManager.getViewMode(), + dashboardStateManager.getIsDirty(timefilter), + dashboardStateManager.isNew() + ); + const getShouldShowEditHelp = () => !dashboardStateManager.getPanels().length && dashboardStateManager.getIsEditMode() && @@ -429,6 +441,15 @@ export class DashboardAppController { DashboardContainer >(DASHBOARD_CONTAINER_TYPE); + searchService.session.setSearchSessionInfoProvider( + createSessionRestorationDataProvider({ + data, + getDashboardTitle: () => getDashTitle(), + getDashboardId: () => dash.id, + getAppState: () => dashboardStateManager.getAppState(), + }) + ); + if (dashboardFactory) { const searchSessionIdFromURL = getSearchSessionIdFromURL(history); if (searchSessionIdFromURL) { @@ -552,16 +573,6 @@ export class DashboardAppController { filterManager.getFilters() ); - const landingPageUrl = () => `#${DashboardConstants.LANDING_PAGE_PATH}`; - - const getDashTitle = () => - getDashboardTitle( - dashboardStateManager.getTitle(), - dashboardStateManager.getViewMode(), - dashboardStateManager.getIsDirty(timefilter), - dashboardStateManager.isNew() - ); - // Push breadcrumbs to new header navigation const updateBreadcrumbs = () => { chrome.setBreadcrumbs([ @@ -638,6 +649,13 @@ export class DashboardAppController { } }; + const searchServiceSessionRefreshSubscribtion = searchService.session.onRefresh$.subscribe( + () => { + lastReloadRequestTime = new Date().getTime(); + refreshDashboardContainer(); + } + ); + const updateStateFromSavedQuery = (savedQuery: SavedQuery) => { const allFilters = filterManager.getFilters(); dashboardStateManager.applyFilters(savedQuery.attributes.query, allFilters); @@ -1199,6 +1217,7 @@ export class DashboardAppController { if (dashboardContainer) { dashboardContainer.destroy(); } + searchServiceSessionRefreshSubscribtion.unsubscribe(); searchService.session.clear(); }); } diff --git a/src/plugins/dashboard/public/application/lib/index.ts b/src/plugins/dashboard/public/application/lib/index.ts index e9ebe73c3b34dc..6741bbbc5d4b13 100644 --- a/src/plugins/dashboard/public/application/lib/index.ts +++ b/src/plugins/dashboard/public/application/lib/index.ts @@ -21,3 +21,4 @@ export { saveDashboard } from './save_dashboard'; export { getAppStateDefaults } from './get_app_state_defaults'; export { migrateAppState } from './migrate_app_state'; export { getDashboardIdFromUrl } from './url'; +export { createSessionRestorationDataProvider } from './session_restoration'; diff --git a/src/plugins/dashboard/public/application/lib/session_restoration.ts b/src/plugins/dashboard/public/application/lib/session_restoration.ts new file mode 100644 index 00000000000000..f8ea8f8dcd76dc --- /dev/null +++ b/src/plugins/dashboard/public/application/lib/session_restoration.ts @@ -0,0 +1,66 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { DASHBOARD_APP_URL_GENERATOR, DashboardUrlGeneratorState } from '../../url_generator'; +import { DataPublicPluginStart } from '../../../../data/public'; +import { DashboardAppState } from '../../types'; + +export function createSessionRestorationDataProvider(deps: { + data: DataPublicPluginStart; + getAppState: () => DashboardAppState; + getDashboardTitle: () => string; + getDashboardId: () => string; +}) { + return { + getName: async () => deps.getDashboardTitle(), + getUrlGeneratorData: async () => { + return { + urlGeneratorId: DASHBOARD_APP_URL_GENERATOR, + initialState: getUrlGeneratorState({ ...deps, forceAbsoluteTime: false }), + restoreState: getUrlGeneratorState({ ...deps, forceAbsoluteTime: true }), + }; + }, + }; +} + +function getUrlGeneratorState({ + data, + getAppState, + getDashboardId, + forceAbsoluteTime, // TODO: not implemented +}: { + data: DataPublicPluginStart; + getAppState: () => DashboardAppState; + getDashboardId: () => string; + forceAbsoluteTime: boolean; +}): DashboardUrlGeneratorState { + const appState = getAppState(); + return { + dashboardId: getDashboardId(), + timeRange: data.query.timefilter.timefilter.getTime(), + filters: data.query.filterManager.getFilters(), + query: data.query.queryString.formatQuery(appState.query), + savedQuery: appState.savedQuery, + useHash: false, + preserveSavedFilters: false, + viewMode: appState.viewMode, + panels: getDashboardId() ? undefined : appState.panels, + searchSessionId: data.search.session.getSessionId(), + }; +} diff --git a/src/plugins/dashboard/public/url_generator.test.ts b/src/plugins/dashboard/public/url_generator.test.ts index 461caedc5cba76..0272e9d3ebdf70 100644 --- a/src/plugins/dashboard/public/url_generator.test.ts +++ b/src/plugins/dashboard/public/url_generator.test.ts @@ -142,6 +142,39 @@ describe('dashboard url generator', () => { ); }); + test('savedQuery', async () => { + const generator = createDashboardUrlGenerator(() => + Promise.resolve({ + appBasePath: APP_BASE_PATH, + useHashedUrl: false, + savedDashboardLoader: createMockDashboardLoader(), + }) + ); + const url = await generator.createUrl!({ + savedQuery: '__savedQueryId__', + }); + expect(url).toMatchInlineSnapshot( + `"xyz/app/dashboards#/create?_a=(savedQuery:__savedQueryId__)&_g=()"` + ); + expect(url).toContain('__savedQueryId__'); + }); + + test('panels', async () => { + const generator = createDashboardUrlGenerator(() => + Promise.resolve({ + appBasePath: APP_BASE_PATH, + useHashedUrl: false, + savedDashboardLoader: createMockDashboardLoader(), + }) + ); + const url = await generator.createUrl!({ + panels: [{ fakePanelContent: 'fakePanelContent' } as any], + }); + expect(url).toMatchInlineSnapshot( + `"xyz/app/dashboards#/create?_a=(panels:!((fakePanelContent:fakePanelContent)))&_g=()"` + ); + }); + test('if no useHash setting is given, uses the one was start services', async () => { const generator = createDashboardUrlGenerator(() => Promise.resolve({ diff --git a/src/plugins/dashboard/public/url_generator.ts b/src/plugins/dashboard/public/url_generator.ts index b23b26e4022dd0..182020d032e4e2 100644 --- a/src/plugins/dashboard/public/url_generator.ts +++ b/src/plugins/dashboard/public/url_generator.ts @@ -30,6 +30,7 @@ import { UrlGeneratorsDefinition } from '../../share/public'; import { SavedObjectLoader } from '../../saved_objects/public'; import { ViewMode } from '../../embeddable/public'; import { DashboardConstants } from './dashboard_constants'; +import { SavedDashboardPanel } from '../common/types'; export const STATE_STORAGE_KEY = '_a'; export const GLOBAL_STATE_STORAGE_KEY = '_g'; @@ -86,6 +87,16 @@ export interface DashboardUrlGeneratorState { * (Background search) */ searchSessionId?: string; + + /** + * List of dashboard panels + */ + panels?: SavedDashboardPanel[]; + + /** + * Saved query ID + */ + savedQuery?: string; } export const createDashboardUrlGenerator = ( @@ -137,6 +148,8 @@ export const createDashboardUrlGenerator = ( query: state.query, filters: filters?.filter((f) => !esFilters.isFilterPinned(f)), viewMode: state.viewMode, + panels: state.panels, + savedQuery: state.savedQuery, }), { useHash }, `${appBasePath}#/${hash}` diff --git a/src/plugins/data/common/index_patterns/utils.ts b/src/plugins/data/common/index_patterns/utils.ts index d9e1cfa0d952af..b7e1f28d5d60f3 100644 --- a/src/plugins/data/common/index_patterns/utils.ts +++ b/src/plugins/data/common/index_patterns/utils.ts @@ -17,8 +17,8 @@ * under the License. */ -import { find } from 'lodash'; -import { SavedObjectsClientCommon, SavedObject } from '..'; +import type { IndexPatternSavedObjectAttrs } from './index_patterns'; +import type { SavedObjectsClientCommon } from '../types'; /** * Returns an object matching a given title @@ -27,24 +27,16 @@ import { SavedObjectsClientCommon, SavedObject } from '..'; * @param title {string} * @returns {Promise} */ -export async function findByTitle( - client: SavedObjectsClientCommon, - title: string -): Promise | void> { - if (!title) { - return Promise.resolve(); - } - - const savedObjects = await client.find({ - type: 'index-pattern', - perPage: 10, - search: `"${title}"`, - searchFields: ['title'], - fields: ['title'], - }); +export async function findByTitle(client: SavedObjectsClientCommon, title: string) { + if (title) { + const savedObjects = await client.find({ + type: 'index-pattern', + perPage: 10, + search: `"${title}"`, + searchFields: ['title'], + fields: ['title'], + }); - return find( - savedObjects, - (obj: SavedObject) => obj.attributes.title.toLowerCase() === title.toLowerCase() - ); + return savedObjects.find((obj) => obj.attributes.title.toLowerCase() === title.toLowerCase()); + } } diff --git a/src/plugins/data/common/search/aggs/agg_type.ts b/src/plugins/data/common/search/aggs/agg_type.ts index 4f4a593764b1e8..bf6fe11f746f96 100644 --- a/src/plugins/data/common/search/aggs/agg_type.ts +++ b/src/plugins/data/common/search/aggs/agg_type.ts @@ -55,7 +55,8 @@ export interface AggTypeConfig< aggConfig: TAggConfig, searchSource: ISearchSource, inspectorRequestAdapter?: RequestAdapter, - abortSignal?: AbortSignal + abortSignal?: AbortSignal, + searchSessionId?: string ) => Promise; getSerializedFormat?: (agg: TAggConfig) => SerializedFieldFormat; getValue?: (agg: TAggConfig, bucket: any) => any; @@ -182,6 +183,8 @@ export class AggType< * @param searchSourceAggs - SearchSource aggregation configuration * @param resp - Response to the main request * @param nestedSearchSource - the new SearchSource that will be used to make post flight request + * @param abortSignal - `AbortSignal` to abort the request + * @param searchSessionId - searchSessionId to be used for grouping requests into a single search session * @return {Promise} */ postFlightRequest: ( @@ -190,7 +193,8 @@ export class AggType< aggConfig: TAggConfig, searchSource: ISearchSource, inspectorRequestAdapter?: RequestAdapter, - abortSignal?: AbortSignal + abortSignal?: AbortSignal, + searchSessionId?: string ) => Promise; /** * Get the serialized format for the values produced by this agg type, diff --git a/src/plugins/data/common/search/aggs/buckets/terms.ts b/src/plugins/data/common/search/aggs/buckets/terms.ts index ac65e7fa813b3d..7071d9c1dc9c44 100644 --- a/src/plugins/data/common/search/aggs/buckets/terms.ts +++ b/src/plugins/data/common/search/aggs/buckets/terms.ts @@ -102,7 +102,8 @@ export const getTermsBucketAgg = () => aggConfig, searchSource, inspectorRequestAdapter, - abortSignal + abortSignal, + searchSessionId ) => { if (!resp.aggregations) return resp; const nestedSearchSource = searchSource.createChild(); @@ -124,6 +125,7 @@ export const getTermsBucketAgg = () => 'This request counts the number of documents that fall ' + 'outside the criterion of the data buckets.', }), + searchSessionId, } ); nestedSearchSource.getSearchRequestBody().then((body) => { @@ -132,7 +134,10 @@ export const getTermsBucketAgg = () => request.stats(getRequestInspectorStats(nestedSearchSource)); } - const response = await nestedSearchSource.fetch({ abortSignal }); + const response = await nestedSearchSource.fetch({ + abortSignal, + sessionId: searchSessionId, + }); if (request) { request .stats(getResponseInspectorStats(response, nestedSearchSource)) diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/common/search/session/types.ts index d1ab22057695a7..50ca3ca390ece0 100644 --- a/src/plugins/data/common/search/session/types.ts +++ b/src/plugins/data/common/search/session/types.ts @@ -17,82 +17,19 @@ * under the License. */ -import { Observable } from 'rxjs'; -import type { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; - -export interface ISessionService { - /** - * Returns the active session ID - * @returns The active session ID - */ - getSessionId: () => string | undefined; - /** - * Returns the observable that emits an update every time the session ID changes - * @returns `Observable` - */ - getSession$: () => Observable; - - /** - * Whether the active session is already saved (i.e. sent to background) - */ - isStored: () => boolean; - - /** - * Whether the active session is restored (i.e. reusing previous search IDs) - */ - isRestore: () => boolean; - - /** - * Starts a new session - */ - start: () => string; - - /** - * Restores existing session - */ - restore: (sessionId: string) => Promise>; - - /** - * Clears the active session. - */ - clear: () => void; - - /** - * Saves a session - */ - save: (name: string, url: string) => Promise>; - - /** - * Gets a saved session - */ - get: (sessionId: string) => Promise>; - - /** - * Gets a list of saved sessions - */ - find: ( - options: SearchSessionFindOptions - ) => Promise>; - +export interface BackgroundSessionSavedObjectAttributes { /** - * Updates a session + * User-facing session name to be displayed in session management */ - update: ( - sessionId: string, - attributes: Partial - ) => Promise; - + name: string; /** - * Deletes a session + * App that created the session. e.g 'discover' */ - delete: (sessionId: string) => Promise; -} - -export interface BackgroundSessionSavedObjectAttributes { - name: string; + appId: string; created: string; expires: string; status: string; + urlGeneratorId: string; initialState: Record; restoreState: Record; idMapping: Record; diff --git a/src/plugins/data/jest.config.js b/src/plugins/data/jest.config.js new file mode 100644 index 00000000000000..3c6e854a53d7bc --- /dev/null +++ b/src/plugins/data/jest.config.js @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/data'], + testRunner: 'jasmine2', +}; diff --git a/src/plugins/data/kibana.json b/src/plugins/data/kibana.json index 3e4d08c8faa1b6..06b083e0ff3aaa 100644 --- a/src/plugins/data/kibana.json +++ b/src/plugins/data/kibana.json @@ -6,7 +6,8 @@ "requiredPlugins": [ "bfetch", "expressions", - "uiActions" + "uiActions", + "share" ], "optionalPlugins": ["usageCollection"], "extraPublicDirs": ["common"], diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index e0b0c5a0ea980f..1c07b4b99e4c09 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -385,6 +385,7 @@ export { SearchRequest, SearchSourceFields, SortDirection, + SessionState, // expression functions and types EsdslExpressionFunctionDefinition, EsRawResponseExpressionTypeDefinition, @@ -395,7 +396,12 @@ export { PainlessError, } from './search'; -export type { SearchSource, ISessionService } from './search'; +export type { + SearchSource, + ISessionService, + SearchSessionInfoProvider, + ISessionsClient, +} from './search'; export { ISearchOptions, isErrorResponse, isCompleteResponse, isPartialResponse } from '../common'; diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 1b83eb569b1a1c..67c1ff7e09dd74 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -64,6 +64,7 @@ const createStartContract = (): Start => { SearchBar: jest.fn().mockReturnValue(null), }, indexPatterns: ({ + find: jest.fn((search) => [{ id: search, title: search }]), createField: jest.fn(() => {}), createFieldList: jest.fn(() => []), ensureDefaultIndexPattern: jest.fn(), diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 8ceb91269adbea..ad1861cecea0be 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -40,6 +40,7 @@ import { ExpressionValueBoxed } from 'src/plugins/expressions/common'; import { FormatFactory as FormatFactory_2 } from 'src/plugins/data/common/field_formats/utils'; import { History } from 'history'; import { Href } from 'history'; +import { HttpSetup } from 'kibana/public'; import { IconType } from '@elastic/eui'; import { InjectedIntl } from '@kbn/i18n/react'; import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public'; @@ -62,7 +63,9 @@ import { PackageInfo } from '@kbn/config'; import { Path } from 'history'; import { Plugin as Plugin_2 } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/public'; +import { PluginInitializerContext as PluginInitializerContext_3 } from 'kibana/public'; import { PopoverAnchorPosition } from '@elastic/eui'; +import { PublicContract } from '@kbn/utility-types'; import { PublicMethodsOf } from '@kbn/utility-types'; import { PublicUiSettingsParams } from 'src/core/server/types'; import React from 'react'; @@ -82,6 +85,7 @@ import { SavedObjectsFindResponse } from 'kibana/server'; import { Search } from '@elastic/elasticsearch/api/requestParams'; import { SearchResponse } from 'elasticsearch'; import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common'; +import { StartServicesAccessor } from 'kibana/public'; import { ToastInputFields } from 'src/core/public/notifications'; import { ToastsSetup } from 'kibana/public'; import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; @@ -1478,6 +1482,7 @@ export interface ISearchSetup { // (undocumented) aggs: AggsSetup; session: ISessionService; + sessionsClient: ISessionsClient; // Warning: (ae-forgotten-export) The symbol "SearchUsageCollector" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -1493,6 +1498,7 @@ export interface ISearchStart { search: ISearchGeneric; searchSource: ISearchStartSearchSource; session: ISessionService; + sessionsClient: ISessionsClient; // (undocumented) showError: (e: Error) => void; } @@ -1508,25 +1514,17 @@ export interface ISearchStartSearchSource { // @public (undocumented) export const isErrorResponse: (response?: IKibanaSearchResponse | undefined) => boolean | undefined; +// Warning: (ae-forgotten-export) The symbol "SessionsClient" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "ISessionsClient" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ISessionsClient = PublicContract; + +// Warning: (ae-forgotten-export) The symbol "SessionService" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ISessionService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface ISessionService { - clear: () => void; - delete: (sessionId: string) => Promise; - // Warning: (ae-forgotten-export) The symbol "SearchSessionFindOptions" needs to be exported by the entry point index.d.ts - find: (options: SearchSessionFindOptions) => Promise>; - get: (sessionId: string) => Promise>; - getSession$: () => Observable; - getSessionId: () => string | undefined; - isRestore: () => boolean; - isStored: () => boolean; - // Warning: (ae-forgotten-export) The symbol "BackgroundSessionSavedObjectAttributes" needs to be exported by the entry point index.d.ts - restore: (sessionId: string) => Promise>; - save: (name: string, url: string) => Promise>; - start: () => string; - update: (sessionId: string, attributes: Partial) => Promise; -} +export type ISessionService = PublicContract; // Warning: (ae-missing-release-tag) "isFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -2107,6 +2105,7 @@ export class SearchInterceptor { timeoutSignal: AbortSignal; combinedSignal: AbortSignal; cleanup: () => void; + abort: () => void; }; // (undocumented) showError(e: Error): void; @@ -2135,6 +2134,20 @@ export interface SearchInterceptorDeps { // @internal export type SearchRequest = Record; +// Warning: (ae-forgotten-export) The symbol "UrlGeneratorId" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "SearchSessionInfoProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface SearchSessionInfoProvider { + getName: () => Promise; + // (undocumented) + getUrlGeneratorData: () => Promise<{ + urlGeneratorId: ID; + initialState: UrlGeneratorStateMapping[ID]['State']; + restoreState: UrlGeneratorStateMapping[ID]['State']; + }>; +} + // @public (undocumented) export class SearchSource { // Warning: (ae-forgotten-export) The symbol "SearchSourceDependencies" needs to be exported by the entry point index.d.ts @@ -2240,6 +2253,17 @@ export class SearchTimeoutError extends KbnError { mode: TimeoutErrorMode; } +// @public +export enum SessionState { + BackgroundCompleted = "backgroundCompleted", + BackgroundLoading = "backgroundLoading", + Canceled = "canceled", + Completed = "completed", + Loading = "loading", + None = "none", + Restored = "restored" +} + // Warning: (ae-missing-release-tag) "SortDirection" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -2415,22 +2439,23 @@ export const UI_SETTINGS: { // src/plugins/data/public/index.ts:246:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:246:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:246:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:403:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:403:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:403:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:403:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:415:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:416:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:417:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:418:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:423:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:426:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:427:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:430:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:409:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:409:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:409:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:409:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:421:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:423:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:424:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:428:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:429:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:432:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:433:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:436:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:45:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/search/session/session_service.ts:46:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/data/public/search/expressions/esaggs/request_handler.ts b/src/plugins/data/public/search/expressions/esaggs/request_handler.ts index 93b5705b821c02..7a27d652671499 100644 --- a/src/plugins/data/public/search/expressions/esaggs/request_handler.ts +++ b/src/plugins/data/public/search/expressions/esaggs/request_handler.ts @@ -182,7 +182,8 @@ export const handleRequest = async ({ agg, requestSearchSource, inspectorAdapters.requests, - abortSignal + abortSignal, + searchSessionId ); } } diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index f6bd46c17192c9..2a767d1bf7c0d1 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -40,9 +40,15 @@ export { SearchSourceDependencies, SearchSourceFields, SortDirection, - ISessionService, } from '../../common/search'; - +export { + SessionService, + ISessionService, + SearchSessionInfoProvider, + SessionState, + SessionsClient, + ISessionsClient, +} from './session'; export { getEsPreference } from './es_search'; export { SearchInterceptor, SearchInterceptorDeps } from './search_interceptor'; diff --git a/src/plugins/data/public/search/mocks.ts b/src/plugins/data/public/search/mocks.ts index 836ddb618e7468..b799c661051fa5 100644 --- a/src/plugins/data/public/search/mocks.ts +++ b/src/plugins/data/public/search/mocks.ts @@ -20,13 +20,14 @@ import { searchAggsSetupMock, searchAggsStartMock } from './aggs/mocks'; import { searchSourceMock } from './search_source/mocks'; import { ISearchSetup, ISearchStart } from './types'; -import { getSessionServiceMock } from '../../common/mocks'; +import { getSessionsClientMock, getSessionServiceMock } from './session/mocks'; function createSetupContract(): jest.Mocked { return { aggs: searchAggsSetupMock(), __enhance: jest.fn(), session: getSessionServiceMock(), + sessionsClient: getSessionsClientMock(), }; } @@ -36,6 +37,7 @@ function createStartContract(): jest.Mocked { search: jest.fn(), showError: jest.fn(), session: getSessionServiceMock(), + sessionsClient: getSessionsClientMock(), searchSource: searchSourceMock.createStartContract(), }; } diff --git a/src/plugins/data/public/search/search_interceptor.test.ts b/src/plugins/data/public/search/search_interceptor.test.ts index 6dc52d7016797e..947dac1b326400 100644 --- a/src/plugins/data/public/search/search_interceptor.test.ts +++ b/src/plugins/data/public/search/search_interceptor.test.ts @@ -24,7 +24,7 @@ import { SearchInterceptor } from './search_interceptor'; import { AbortError } from '../../../kibana_utils/public'; import { SearchTimeoutError, PainlessError, TimeoutErrorMode } from './errors'; import { searchServiceMock } from './mocks'; -import { ISearchStart } from '.'; +import { ISearchStart, ISessionService } from '.'; import { bfetchPluginMock } from '../../../bfetch/public/mocks'; import { BfetchPublicSetup } from 'src/plugins/bfetch/public'; @@ -104,7 +104,99 @@ describe('SearchInterceptor', () => { params: {}, }; const response = searchInterceptor.search(mockRequest); - expect(response.toPromise()).resolves.toBe(mockResponse); + await expect(response.toPromise()).resolves.toBe(mockResponse); + }); + + describe('Search session', () => { + const setup = ({ + isRestore = false, + isStored = false, + sessionId, + }: { + isRestore?: boolean; + isStored?: boolean; + sessionId?: string; + }) => { + const sessionServiceMock = searchMock.session as jest.Mocked; + sessionServiceMock.getSessionId.mockImplementation(() => sessionId); + sessionServiceMock.isRestore.mockImplementation(() => isRestore); + sessionServiceMock.isStored.mockImplementation(() => isStored); + fetchMock.mockResolvedValue({ result: 200 }); + }; + + const mockRequest: IEsSearchRequest = { + params: {}, + }; + + afterEach(() => { + const sessionServiceMock = searchMock.session as jest.Mocked; + sessionServiceMock.getSessionId.mockReset(); + sessionServiceMock.isRestore.mockReset(); + sessionServiceMock.isStored.mockReset(); + fetchMock.mockReset(); + }); + + test('infers isRestore from session service state', async () => { + const sessionId = 'sid'; + setup({ + isRestore: true, + sessionId, + }); + + await searchInterceptor.search(mockRequest, { sessionId }).toPromise(); + expect(fetchMock.mock.calls[0][0]).toEqual( + expect.objectContaining({ + options: { sessionId: 'sid', isStored: false, isRestore: true }, + }) + ); + }); + + test('infers isStored from session service state', async () => { + const sessionId = 'sid'; + setup({ + isStored: true, + sessionId, + }); + + await searchInterceptor.search(mockRequest, { sessionId }).toPromise(); + expect(fetchMock.mock.calls[0][0]).toEqual( + expect.objectContaining({ + options: { sessionId: 'sid', isStored: true, isRestore: false }, + }) + ); + }); + + test('skips isRestore & isStore in case not a current session Id', async () => { + setup({ + isStored: true, + isRestore: true, + sessionId: 'session id', + }); + + await searchInterceptor + .search(mockRequest, { sessionId: 'different session id' }) + .toPromise(); + expect(fetchMock.mock.calls[0][0]).toEqual( + expect.objectContaining({ + options: { sessionId: 'different session id', isStored: false, isRestore: false }, + }) + ); + }); + + test('skips isRestore & isStore in case no session Id', async () => { + setup({ + isStored: true, + isRestore: true, + sessionId: undefined, + }); + + await searchInterceptor.search(mockRequest, { sessionId: 'sessionId' }).toPromise(); + expect(fetchMock.mock.calls[0][0]).toEqual( + expect.objectContaining({ + options: { sessionId: 'sessionId', isStored: false, isRestore: false }, + }) + ); + }); }); describe('Should throw typed errors', () => { diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts index 055b3a71705bf2..8548a2a9f2b2a0 100644 --- a/src/plugins/data/public/search/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor.ts @@ -24,12 +24,7 @@ import { PublicMethodsOf } from '@kbn/utility-types'; import { CoreStart, CoreSetup, ToastsSetup } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { BatchedFunc, BfetchPublicSetup } from 'src/plugins/bfetch/public'; -import { - IKibanaSearchRequest, - IKibanaSearchResponse, - ISearchOptions, - ISessionService, -} from '../../common'; +import { IKibanaSearchRequest, IKibanaSearchResponse, ISearchOptions } from '../../common'; import { SearchUsageCollector } from './collectors'; import { SearchTimeoutError, @@ -42,6 +37,7 @@ import { } from './errors'; import { toMountPoint } from '../../../kibana_react/public'; import { AbortError, getCombinedAbortSignal } from '../../../kibana_utils/public'; +import { ISessionService } from './session'; export interface SearchInterceptorDeps { bfetch: BfetchPublicSetup; @@ -133,10 +129,18 @@ export class SearchInterceptor { options?: ISearchOptions ): Promise { const { abortSignal, ...requestOptions } = options || {}; + + const isCurrentSession = + options?.sessionId && this.deps.session.getSessionId() === options.sessionId; + return this.batchedFetch( { request, - options: requestOptions, + options: { + ...requestOptions, + isStored: isCurrentSession ? this.deps.session.isStored() : false, + isRestore: isCurrentSession ? this.deps.session.isRestore() : false, + }, }, abortSignal ); @@ -160,13 +164,18 @@ export class SearchInterceptor { timeoutController.abort(); }); + const selfAbortController = new AbortController(); + // Get a combined `AbortSignal` that will be aborted whenever the first of the following occurs: // 1. The user manually aborts (via `cancelPending`) // 2. The request times out - // 3. The passed-in signal aborts (e.g. when re-fetching, or whenever the app determines) + // 3. abort() is called on `selfAbortController`. This is used by session service to abort all pending searches that it tracks + // in the current session + // 4. The passed-in signal aborts (e.g. when re-fetching, or whenever the app determines) const signals = [ this.abortController.signal, timeoutSignal, + selfAbortController.signal, ...(abortSignal ? [abortSignal] : []), ]; @@ -184,6 +193,9 @@ export class SearchInterceptor { timeoutSignal, combinedSignal, cleanup, + abort: () => { + selfAbortController.abort(); + }, }; } diff --git a/src/plugins/data/public/search/search_service.test.ts b/src/plugins/data/public/search/search_service.test.ts index 3179da4d03a1a4..d617010d13011e 100644 --- a/src/plugins/data/public/search/search_service.test.ts +++ b/src/plugins/data/public/search/search_service.test.ts @@ -49,6 +49,8 @@ describe('Search service', () => { expect(setup).toHaveProperty('aggs'); expect(setup).toHaveProperty('usageCollector'); expect(setup).toHaveProperty('__enhance'); + expect(setup).toHaveProperty('sessionsClient'); + expect(setup).toHaveProperty('session'); }); }); @@ -61,6 +63,8 @@ describe('Search service', () => { expect(start).toHaveProperty('aggs'); expect(start).toHaveProperty('search'); expect(start).toHaveProperty('searchSource'); + expect(start).toHaveProperty('sessionsClient'); + expect(start).toHaveProperty('session'); }); }); }); diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index b76b5846d3d938..60d2dfdf866cfb 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -28,7 +28,6 @@ import { kibanaContext, kibanaContextFunction, ISearchGeneric, - ISessionService, SearchSourceDependencies, SearchSourceService, } from '../../common/search'; @@ -40,7 +39,7 @@ import { SearchUsageCollector, createUsageCollector } from './collectors'; import { UsageCollectionSetup } from '../../../usage_collection/public'; import { esdsl, esRawResponse } from './expressions'; import { ExpressionsSetup } from '../../../expressions/public'; -import { SessionService } from './session_service'; +import { ISessionsClient, ISessionService, SessionsClient, SessionService } from './session'; import { ConfigSchema } from '../../config'; import { SHARD_DELAY_AGG_NAME, @@ -67,6 +66,7 @@ export class SearchService implements Plugin { private searchInterceptor!: ISearchInterceptor; private usageCollector?: SearchUsageCollector; private sessionService!: ISessionService; + private sessionsClient!: ISessionsClient; constructor(private initializerContext: PluginInitializerContext) {} @@ -76,7 +76,12 @@ export class SearchService implements Plugin { ): ISearchSetup { this.usageCollector = createUsageCollector(getStartServices, usageCollection); - this.sessionService = new SessionService(this.initializerContext, getStartServices); + this.sessionsClient = new SessionsClient({ http }); + this.sessionService = new SessionService( + this.initializerContext, + getStartServices, + this.sessionsClient + ); /** * A global object that intercepts all searches and provides convenience methods for cancelling * all pending search requests, as well as getting the number of pending search requests. @@ -115,6 +120,7 @@ export class SearchService implements Plugin { this.searchInterceptor = enhancements.searchInterceptor; }, session: this.sessionService, + sessionsClient: this.sessionsClient, }; } @@ -146,6 +152,7 @@ export class SearchService implements Plugin { this.searchInterceptor.showError(e); }, session: this.sessionService, + sessionsClient: this.sessionsClient, searchSource: this.searchSourceService.start(indexPatterns, searchSourceDependencies), }; } diff --git a/src/plugins/data/public/search/session/index.ts b/src/plugins/data/public/search/session/index.ts new file mode 100644 index 00000000000000..ee0121aacad5e8 --- /dev/null +++ b/src/plugins/data/public/search/session/index.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { SessionService, ISessionService, SearchSessionInfoProvider } from './session_service'; +export { SessionState } from './session_state'; +export { SessionsClient, ISessionsClient } from './sessions_client'; diff --git a/src/plugins/data/common/search/session/mocks.ts b/src/plugins/data/public/search/session/mocks.ts similarity index 67% rename from src/plugins/data/common/search/session/mocks.ts rename to src/plugins/data/public/search/session/mocks.ts index 4604e15e4e93b8..0ff99b3080365a 100644 --- a/src/plugins/data/common/search/session/mocks.ts +++ b/src/plugins/data/public/search/session/mocks.ts @@ -17,8 +17,20 @@ * under the License. */ -import { BehaviorSubject } from 'rxjs'; -import { ISessionService } from './types'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { ISessionsClient } from './sessions_client'; +import { ISessionService } from './session_service'; +import { SessionState } from './session_state'; + +export function getSessionsClientMock(): jest.Mocked { + return { + get: jest.fn(), + create: jest.fn(), + find: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + }; +} export function getSessionServiceMock(): jest.Mocked { return { @@ -27,12 +39,15 @@ export function getSessionServiceMock(): jest.Mocked { restore: jest.fn(), getSessionId: jest.fn(), getSession$: jest.fn(() => new BehaviorSubject(undefined).asObservable()), + state$: new BehaviorSubject(SessionState.None).asObservable(), + setSearchSessionInfoProvider: jest.fn(), + trackSearch: jest.fn((searchDescriptor) => () => {}), + destroy: jest.fn(), + onRefresh$: new Subject(), + refresh: jest.fn(), + cancel: jest.fn(), isStored: jest.fn(), isRestore: jest.fn(), save: jest.fn(), - get: jest.fn(), - find: jest.fn(), - update: jest.fn(), - delete: jest.fn(), }; } diff --git a/src/plugins/data/public/search/session_service.test.ts b/src/plugins/data/public/search/session/session_service.test.ts similarity index 53% rename from src/plugins/data/public/search/session_service.test.ts rename to src/plugins/data/public/search/session/session_service.test.ts index bcfd06944d9831..83c3185ead63eb 100644 --- a/src/plugins/data/public/search/session_service.test.ts +++ b/src/plugins/data/public/search/session/session_service.test.ts @@ -17,20 +17,27 @@ * under the License. */ -import { SessionService } from './session_service'; -import { ISessionService } from '../../common'; -import { coreMock } from '../../../../core/public/mocks'; +import { SessionService, ISessionService } from './session_service'; +import { coreMock } from '../../../../../core/public/mocks'; import { take, toArray } from 'rxjs/operators'; +import { getSessionsClientMock } from './mocks'; +import { BehaviorSubject } from 'rxjs'; +import { SessionState } from './session_state'; describe('Session service', () => { let sessionService: ISessionService; + let state$: BehaviorSubject; beforeEach(() => { const initializerContext = coreMock.createPluginInitializerContext(); sessionService = new SessionService( initializerContext, - coreMock.createSetup().getStartServices + coreMock.createSetup().getStartServices, + getSessionsClientMock(), + { freezeState: false } // needed to use mocks inside state container ); + state$ = new BehaviorSubject(SessionState.None); + sessionService.state$.subscribe(state$); }); describe('Session management', () => { @@ -55,5 +62,35 @@ describe('Session service', () => { expect(await emittedValues).toEqual(['1', '2', undefined]); }); + + it('Tracks searches for current session', () => { + expect(() => sessionService.trackSearch({ abort: () => {} })).toThrowError(); + expect(state$.getValue()).toBe(SessionState.None); + + sessionService.start(); + const untrack1 = sessionService.trackSearch({ abort: () => {} }); + expect(state$.getValue()).toBe(SessionState.Loading); + const untrack2 = sessionService.trackSearch({ abort: () => {} }); + expect(state$.getValue()).toBe(SessionState.Loading); + untrack1(); + expect(state$.getValue()).toBe(SessionState.Loading); + untrack2(); + expect(state$.getValue()).toBe(SessionState.Completed); + }); + + it('Cancels all tracked searches within current session', async () => { + const abort = jest.fn(); + + sessionService.start(); + sessionService.trackSearch({ abort }); + sessionService.trackSearch({ abort }); + sessionService.trackSearch({ abort }); + const untrack = sessionService.trackSearch({ abort }); + + untrack(); + await sessionService.cancel(); + + expect(abort).toBeCalledTimes(3); + }); }); }); diff --git a/src/plugins/data/public/search/session/session_service.ts b/src/plugins/data/public/search/session/session_service.ts new file mode 100644 index 00000000000000..ef0b36a33be52e --- /dev/null +++ b/src/plugins/data/public/search/session/session_service.ts @@ -0,0 +1,242 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PublicContract } from '@kbn/utility-types'; +import { distinctUntilChanged, map, startWith } from 'rxjs/operators'; +import { Observable, Subject, Subscription } from 'rxjs'; +import { PluginInitializerContext, StartServicesAccessor } from 'kibana/public'; +import { UrlGeneratorId, UrlGeneratorStateMapping } from '../../../../share/public/'; +import { ConfigSchema } from '../../../config'; +import { createSessionStateContainer, SessionState, SessionStateContainer } from './session_state'; +import { ISessionsClient } from './sessions_client'; + +export type ISessionService = PublicContract; + +export interface TrackSearchDescriptor { + abort: () => void; +} + +/** + * Provide info about current search session to be stored in backgroundSearch saved object + */ +export interface SearchSessionInfoProvider { + /** + * User-facing name of the session. + * e.g. will be displayed in background sessions management list + */ + getName: () => Promise; + getUrlGeneratorData: () => Promise<{ + urlGeneratorId: ID; + initialState: UrlGeneratorStateMapping[ID]['State']; + restoreState: UrlGeneratorStateMapping[ID]['State']; + }>; +} + +/** + * Responsible for tracking a current search session. Supports only a single session at a time. + */ +export class SessionService { + public readonly state$: Observable; + private readonly state: SessionStateContainer; + + private searchSessionInfoProvider?: SearchSessionInfoProvider; + private appChangeSubscription$?: Subscription; + private curApp?: string; + + constructor( + initializerContext: PluginInitializerContext, + getStartServices: StartServicesAccessor, + private readonly sessionsClient: ISessionsClient, + { freezeState = true }: { freezeState: boolean } = { freezeState: true } + ) { + const { stateContainer, sessionState$ } = createSessionStateContainer({ + freeze: freezeState, + }); + this.state$ = sessionState$; + this.state = stateContainer; + + getStartServices().then(([coreStart]) => { + // Apps required to clean up their sessions before unmounting + // Make sure that apps don't leave sessions open. + this.appChangeSubscription$ = coreStart.application.currentAppId$.subscribe((appName) => { + if (this.state.get().sessionId) { + const message = `Application '${this.curApp}' had an open session while navigating`; + if (initializerContext.env.mode.dev) { + // TODO: This setTimeout is necessary due to a race condition while navigating. + setTimeout(() => { + coreStart.fatalErrors.add(message); + }, 100); + } else { + // eslint-disable-next-line no-console + console.warn(message); + this.clear(); + } + } + this.curApp = appName; + }); + }); + } + + /** + * Set a provider of info about current session + * This will be used for creating a background session saved object + * @param searchSessionInfoProvider + */ + public setSearchSessionInfoProvider( + searchSessionInfoProvider: SearchSessionInfoProvider | undefined + ) { + this.searchSessionInfoProvider = searchSessionInfoProvider; + } + + /** + * Used to track pending searches within current session + * + * @param searchDescriptor - uniq object that will be used to untrack the search + * @returns untrack function + */ + public trackSearch(searchDescriptor: TrackSearchDescriptor): () => void { + this.state.transitions.trackSearch(searchDescriptor); + return () => { + this.state.transitions.unTrackSearch(searchDescriptor); + }; + } + + public destroy() { + if (this.appChangeSubscription$) { + this.appChangeSubscription$.unsubscribe(); + } + this.clear(); + } + + /** + * Get current session id + */ + public getSessionId() { + return this.state.get().sessionId; + } + + /** + * Get observable for current session id + */ + public getSession$() { + return this.state.state$.pipe( + startWith(this.state.get()), + map((s) => s.sessionId), + distinctUntilChanged() + ); + } + + /** + * Is current session already saved as SO (send to background) + */ + public isStored() { + return this.state.get().isStored; + } + + /** + * Is restoring the older saved searches + */ + public isRestore() { + return this.state.get().isRestore; + } + + /** + * Start a new search session + * @returns sessionId + */ + public start() { + this.state.transitions.start(); + return this.getSessionId()!; + } + + /** + * Restore previously saved search session + * @param sessionId + */ + public restore(sessionId: string) { + this.state.transitions.restore(sessionId); + } + + /** + * Cleans up current state + */ + public clear() { + this.state.transitions.clear(); + this.setSearchSessionInfoProvider(undefined); + } + + private refresh$ = new Subject(); + /** + * Observable emits when search result refresh was requested + * For example, search to background UI could have it's own "refresh" button + * Application would use this observable to handle user interaction on that button + */ + public onRefresh$ = this.refresh$.asObservable(); + + /** + * Request a search results refresh + */ + public refresh() { + this.refresh$.next(); + } + + /** + * Request a cancellation of on-going search requests within current session + */ + public async cancel(): Promise { + const isStoredSession = this.state.get().isStored; + this.state.get().pendingSearches.forEach((s) => { + s.abort(); + }); + this.state.transitions.cancel(); + if (isStoredSession) { + await this.sessionsClient.delete(this.state.get().sessionId!); + } + } + + /** + * Save current session as SO to get back to results later + * (Send to background) + */ + public async save(): Promise { + const sessionId = this.getSessionId(); + if (!sessionId) throw new Error('No current session'); + if (!this.curApp) throw new Error('No current app id'); + const currentSessionInfoProvider = this.searchSessionInfoProvider; + if (!currentSessionInfoProvider) throw new Error('No info provider for current session'); + const [name, { initialState, restoreState, urlGeneratorId }] = await Promise.all([ + currentSessionInfoProvider.getName(), + currentSessionInfoProvider.getUrlGeneratorData(), + ]); + + await this.sessionsClient.create({ + name, + appId: this.curApp, + restoreState: (restoreState as unknown) as Record, + initialState: (initialState as unknown) as Record, + urlGeneratorId, + sessionId, + }); + + // if we are still interested in this result + if (this.getSessionId() === sessionId) { + this.state.transitions.store(); + } + } +} diff --git a/src/plugins/data/public/search/session/session_state.test.ts b/src/plugins/data/public/search/session/session_state.test.ts new file mode 100644 index 00000000000000..5f709c75bb5d21 --- /dev/null +++ b/src/plugins/data/public/search/session/session_state.test.ts @@ -0,0 +1,124 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { createSessionStateContainer, SessionState } from './session_state'; + +describe('Session state container', () => { + const { stateContainer: state } = createSessionStateContainer(); + + afterEach(() => { + state.transitions.clear(); + }); + + describe('transitions', () => { + test('start', () => { + state.transitions.start(); + expect(state.selectors.getState()).toBe(SessionState.None); + expect(state.get().sessionId).not.toBeUndefined(); + }); + + test('track', () => { + expect(() => state.transitions.trackSearch({})).toThrowError(); + + state.transitions.start(); + state.transitions.trackSearch({}); + + expect(state.selectors.getState()).toBe(SessionState.Loading); + }); + + test('untrack', () => { + state.transitions.start(); + const search = {}; + state.transitions.trackSearch(search); + expect(state.selectors.getState()).toBe(SessionState.Loading); + state.transitions.unTrackSearch(search); + expect(state.selectors.getState()).toBe(SessionState.Completed); + }); + + test('clear', () => { + state.transitions.start(); + state.transitions.clear(); + expect(state.selectors.getState()).toBe(SessionState.None); + expect(state.get().sessionId).toBeUndefined(); + }); + + test('cancel', () => { + expect(() => state.transitions.cancel()).toThrowError(); + + state.transitions.start(); + const search = {}; + state.transitions.trackSearch(search); + expect(state.selectors.getState()).toBe(SessionState.Loading); + state.transitions.cancel(); + expect(state.selectors.getState()).toBe(SessionState.Canceled); + state.transitions.clear(); + expect(state.selectors.getState()).toBe(SessionState.None); + }); + + test('store -> completed', () => { + expect(() => state.transitions.store()).toThrowError(); + + state.transitions.start(); + const search = {}; + state.transitions.trackSearch(search); + expect(state.selectors.getState()).toBe(SessionState.Loading); + state.transitions.store(); + expect(state.selectors.getState()).toBe(SessionState.BackgroundLoading); + state.transitions.unTrackSearch(search); + expect(state.selectors.getState()).toBe(SessionState.BackgroundCompleted); + state.transitions.clear(); + expect(state.selectors.getState()).toBe(SessionState.None); + }); + test('store -> cancel', () => { + state.transitions.start(); + const search = {}; + state.transitions.trackSearch(search); + expect(state.selectors.getState()).toBe(SessionState.Loading); + state.transitions.store(); + expect(state.selectors.getState()).toBe(SessionState.BackgroundLoading); + state.transitions.cancel(); + expect(state.selectors.getState()).toBe(SessionState.Canceled); + + state.transitions.trackSearch(search); + expect(state.selectors.getState()).toBe(SessionState.Canceled); + + state.transitions.start(); + expect(state.selectors.getState()).toBe(SessionState.None); + }); + + test('restore', () => { + const id = 'id'; + state.transitions.restore(id); + expect(state.selectors.getState()).toBe(SessionState.None); + const search = {}; + state.transitions.trackSearch(search); + expect(state.selectors.getState()).toBe(SessionState.BackgroundLoading); + state.transitions.unTrackSearch(search); + + expect(state.selectors.getState()).toBe(SessionState.Restored); + expect(() => state.transitions.store()).toThrowError(); + expect(state.selectors.getState()).toBe(SessionState.Restored); + expect(() => state.transitions.cancel()).toThrowError(); + expect(state.selectors.getState()).toBe(SessionState.Restored); + + state.transitions.start(); + expect(state.selectors.getState()).toBe(SessionState.None); + }); + }); +}); diff --git a/src/plugins/data/public/search/session/session_state.ts b/src/plugins/data/public/search/session/session_state.ts new file mode 100644 index 00000000000000..087417263e5bf0 --- /dev/null +++ b/src/plugins/data/public/search/session/session_state.ts @@ -0,0 +1,234 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import uuid from 'uuid'; +import { Observable } from 'rxjs'; +import { distinctUntilChanged, map, shareReplay } from 'rxjs/operators'; +import { createStateContainer, StateContainer } from '../../../../kibana_utils/public'; + +/** + * Possible state that current session can be in + * + * @public + */ +export enum SessionState { + /** + * Session is not active, e.g. didn't start + */ + None = 'none', + + /** + * Pending search request has not been sent to the background yet + */ + Loading = 'loading', + + /** + * No action was taken and the page completed loading without background session creation. + */ + Completed = 'completed', + + /** + * Search request was sent to the background. + * The page is loading in background. + */ + BackgroundLoading = 'backgroundLoading', + + /** + * Page load completed with background session created. + */ + BackgroundCompleted = 'backgroundCompleted', + + /** + * Revisiting the page after background completion + */ + Restored = 'restored', + + /** + * Current session requests where explicitly canceled by user + * Displaying none or partial results + */ + Canceled = 'canceled', +} + +/** + * Internal state of SessionService + * {@link SessionState} is inferred from this state + * + * @private + */ +export interface SessionStateInternal { + /** + * Current session Id + * Empty means there is no current active session. + */ + sessionId?: string; + + /** + * Has the session already been stored (i.e. "sent to background")? + */ + isStored: boolean; + + /** + * Is this session a restored session (have these requests already been made, and we're just + * looking to re-use the previous search IDs)? + */ + isRestore: boolean; + + /** + * Set of currently running searches + * within a session and any info associated with them + */ + pendingSearches: SearchDescriptor[]; + + /** + * There was at least a single search in this session + */ + isStarted: boolean; + + /** + * If user has explicitly canceled search requests + */ + isCanceled: boolean; +} + +const createSessionDefaultState: < + SearchDescriptor = unknown +>() => SessionStateInternal = () => ({ + sessionId: undefined, + isStored: false, + isRestore: false, + isCanceled: false, + isStarted: false, + pendingSearches: [], +}); + +export interface SessionPureTransitions< + SearchDescriptor = unknown, + S = SessionStateInternal +> { + start: (state: S) => () => S; + restore: (state: S) => (sessionId: string) => S; + clear: (state: S) => () => S; + store: (state: S) => () => S; + trackSearch: (state: S) => (search: SearchDescriptor) => S; + unTrackSearch: (state: S) => (search: SearchDescriptor) => S; + cancel: (state: S) => () => S; +} + +export const sessionPureTransitions: SessionPureTransitions = { + start: (state) => () => ({ ...createSessionDefaultState(), sessionId: uuid.v4() }), + restore: (state) => (sessionId: string) => ({ + ...createSessionDefaultState(), + sessionId, + isRestore: true, + isStored: true, + }), + clear: (state) => () => createSessionDefaultState(), + store: (state) => () => { + if (!state.sessionId) throw new Error("Can't store session. Missing sessionId"); + if (state.isStored || state.isRestore) + throw new Error('Can\'t store because current session is already stored"'); + return { + ...state, + isStored: true, + }; + }, + trackSearch: (state) => (search) => { + if (!state.sessionId) throw new Error("Can't track search. Missing sessionId"); + return { + ...state, + isStarted: true, + pendingSearches: state.pendingSearches.concat(search), + }; + }, + unTrackSearch: (state) => (search) => { + return { + ...state, + pendingSearches: state.pendingSearches.filter((s) => s !== search), + }; + }, + cancel: (state) => () => { + if (!state.sessionId) throw new Error("Can't cancel searches. Missing sessionId"); + if (state.isRestore) throw new Error("Can't cancel searches when restoring older searches"); + return { + ...state, + pendingSearches: [], + isCanceled: true, + isStored: false, + }; + }, +}; + +export interface SessionPureSelectors< + SearchDescriptor = unknown, + S = SessionStateInternal +> { + getState: (state: S) => () => SessionState; +} + +export const sessionPureSelectors: SessionPureSelectors = { + getState: (state) => () => { + if (!state.sessionId) return SessionState.None; + if (!state.isStarted) return SessionState.None; + if (state.isCanceled) return SessionState.Canceled; + switch (true) { + case state.isRestore: + return state.pendingSearches.length > 0 + ? SessionState.BackgroundLoading + : SessionState.Restored; + case state.isStored: + return state.pendingSearches.length > 0 + ? SessionState.BackgroundLoading + : SessionState.BackgroundCompleted; + default: + return state.pendingSearches.length > 0 ? SessionState.Loading : SessionState.Completed; + } + return SessionState.None; + }, +}; + +export type SessionStateContainer = StateContainer< + SessionStateInternal, + SessionPureTransitions, + SessionPureSelectors +>; + +export const createSessionStateContainer = ( + { freeze = true }: { freeze: boolean } = { freeze: true } +): { + stateContainer: SessionStateContainer; + sessionState$: Observable; +} => { + const stateContainer = createStateContainer( + createSessionDefaultState(), + sessionPureTransitions, + sessionPureSelectors, + freeze ? undefined : { freeze: (s) => s } + ) as SessionStateContainer; + + const sessionState$: Observable = stateContainer.state$.pipe( + map(() => stateContainer.selectors.getState()), + distinctUntilChanged(), + shareReplay(1) + ); + return { + stateContainer, + sessionState$, + }; +}; diff --git a/src/plugins/data/public/search/session/sessions_client.ts b/src/plugins/data/public/search/session/sessions_client.ts new file mode 100644 index 00000000000000..c19c5db0640946 --- /dev/null +++ b/src/plugins/data/public/search/session/sessions_client.ts @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PublicContract } from '@kbn/utility-types'; +import { HttpSetup } from 'kibana/public'; +import type { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; +import { BackgroundSessionSavedObjectAttributes, SearchSessionFindOptions } from '../../../common'; + +export type ISessionsClient = PublicContract; +export interface SessionsClientDeps { + http: HttpSetup; +} + +/** + * CRUD backgroundSession SO + */ +export class SessionsClient { + private readonly http: HttpSetup; + + constructor(deps: SessionsClientDeps) { + this.http = deps.http; + } + + public get(sessionId: string): Promise> { + return this.http.get(`/internal/session/${encodeURIComponent(sessionId)}`); + } + + public create({ + name, + appId, + urlGeneratorId, + initialState, + restoreState, + sessionId, + }: { + name: string; + appId: string; + initialState: Record; + restoreState: Record; + urlGeneratorId: string; + sessionId: string; + }): Promise> { + return this.http.post(`/internal/session`, { + body: JSON.stringify({ + name, + initialState, + restoreState, + sessionId, + appId, + urlGeneratorId, + }), + }); + } + + public find( + options: SearchSessionFindOptions + ): Promise> { + return this.http!.post(`/internal/session`, { + body: JSON.stringify(options), + }); + } + + public update( + sessionId: string, + attributes: Partial + ): Promise> { + return this.http!.put(`/internal/session/${encodeURIComponent(sessionId)}`, { + body: JSON.stringify(attributes), + }); + } + + public delete(sessionId: string): Promise { + return this.http!.delete(`/internal/session/${encodeURIComponent(sessionId)}`); + } +} diff --git a/src/plugins/data/public/search/session_service.ts b/src/plugins/data/public/search/session_service.ts deleted file mode 100644 index 0141cff258a9f2..00000000000000 --- a/src/plugins/data/public/search/session_service.ts +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import uuid from 'uuid'; -import { BehaviorSubject, Subscription } from 'rxjs'; -import { HttpStart, PluginInitializerContext, StartServicesAccessor } from 'kibana/public'; -import { ConfigSchema } from '../../config'; -import { - ISessionService, - BackgroundSessionSavedObjectAttributes, - SearchSessionFindOptions, -} from '../../common'; - -export class SessionService implements ISessionService { - private session$ = new BehaviorSubject(undefined); - private get sessionId() { - return this.session$.getValue(); - } - private appChangeSubscription$?: Subscription; - private curApp?: string; - private http!: HttpStart; - - /** - * Has the session already been stored (i.e. "sent to background")? - */ - private _isStored: boolean = false; - - /** - * Is this session a restored session (have these requests already been made, and we're just - * looking to re-use the previous search IDs)? - */ - private _isRestore: boolean = false; - - constructor( - initializerContext: PluginInitializerContext, - getStartServices: StartServicesAccessor - ) { - /* - Make sure that apps don't leave sessions open. - */ - getStartServices().then(([coreStart]) => { - this.http = coreStart.http; - - this.appChangeSubscription$ = coreStart.application.currentAppId$.subscribe((appName) => { - if (this.sessionId) { - const message = `Application '${this.curApp}' had an open session while navigating`; - if (initializerContext.env.mode.dev) { - // TODO: This setTimeout is necessary due to a race condition while navigating. - setTimeout(() => { - coreStart.fatalErrors.add(message); - }, 100); - } else { - // eslint-disable-next-line no-console - console.warn(message); - } - } - this.curApp = appName; - }); - }); - } - - public destroy() { - this.appChangeSubscription$?.unsubscribe(); - } - - public getSessionId() { - return this.sessionId; - } - - public getSession$() { - return this.session$.asObservable(); - } - - public isStored() { - return this._isStored; - } - - public isRestore() { - return this._isRestore; - } - - public start() { - this._isStored = false; - this._isRestore = false; - this.session$.next(uuid.v4()); - return this.sessionId!; - } - - public restore(sessionId: string) { - this._isStored = true; - this._isRestore = true; - this.session$.next(sessionId); - return this.http.get(`/internal/session/${encodeURIComponent(sessionId)}`); - } - - public clear() { - this._isStored = false; - this._isRestore = false; - this.session$.next(undefined); - } - - public async save(name: string, url: string) { - const response = await this.http.post(`/internal/session`, { - body: JSON.stringify({ - name, - url, - sessionId: this.sessionId, - }), - }); - this._isStored = true; - return response; - } - - public get(sessionId: string) { - return this.http.get(`/internal/session/${encodeURIComponent(sessionId)}`); - } - - public find(options: SearchSessionFindOptions) { - return this.http.post(`/internal/session`, { - body: JSON.stringify(options), - }); - } - - public update(sessionId: string, attributes: Partial) { - return this.http.put(`/internal/session/${encodeURIComponent(sessionId)}`, { - body: JSON.stringify(attributes), - }); - } - - public delete(sessionId: string) { - return this.http.delete(`/internal/session/${encodeURIComponent(sessionId)}`); - } -} diff --git a/src/plugins/data/public/search/types.ts b/src/plugins/data/public/search/types.ts index c08d9f4c7be3f6..057b242c22f20a 100644 --- a/src/plugins/data/public/search/types.ts +++ b/src/plugins/data/public/search/types.ts @@ -21,9 +21,10 @@ import { PackageInfo } from 'kibana/server'; import { ISearchInterceptor } from './search_interceptor'; import { SearchUsageCollector } from './collectors'; import { AggsSetup, AggsSetupDependencies, AggsStartDependencies, AggsStart } from './aggs'; -import { ISearchGeneric, ISessionService, ISearchStartSearchSource } from '../../common/search'; +import { ISearchGeneric, ISearchStartSearchSource } from '../../common/search'; import { IndexPatternsContract } from '../../common/index_patterns/index_patterns'; import { UsageCollectionSetup } from '../../../usage_collection/public'; +import { ISessionsClient, ISessionService } from './session'; export { ISearchStartSearchSource }; @@ -39,10 +40,15 @@ export interface ISearchSetup { aggs: AggsSetup; usageCollector?: SearchUsageCollector; /** - * session management + * Current session management * {@link ISessionService} */ session: ISessionService; + /** + * Background search sessions SO CRUD + * {@link ISessionsClient} + */ + sessionsClient: ISessionsClient; /** * @internal */ @@ -73,10 +79,15 @@ export interface ISearchStart { */ searchSource: ISearchStartSearchSource; /** - * session management + * Current session management * {@link ISessionService} */ session: ISessionService; + /** + * Background search sessions SO CRUD + * {@link ISessionsClient} + */ + sessionsClient: ISessionsClient; } export { SEARCH_EVENT_TYPE } from './collectors'; diff --git a/src/plugins/data/server/kql_telemetry/route.ts b/src/plugins/data/server/kql_telemetry/route.ts index efcb3d038bcc65..c93500f360ad07 100644 --- a/src/plugins/data/server/kql_telemetry/route.ts +++ b/src/plugins/data/server/kql_telemetry/route.ts @@ -45,7 +45,7 @@ export function registerKqlTelemetryRoute( const counterName = optIn ? 'optInCount' : 'optOutCount'; try { - await internalRepository.incrementCounter('kql-telemetry', 'kql-telemetry', counterName); + await internalRepository.incrementCounter('kql-telemetry', 'kql-telemetry', [counterName]); } catch (error) { logger.warn(`Unable to increment counter: ${error}`); return response.customError({ diff --git a/src/plugins/data/server/saved_objects/background_session.ts b/src/plugins/data/server/saved_objects/background_session.ts index 74b03c4d867e40..e81272628c091a 100644 --- a/src/plugins/data/server/saved_objects/background_session.ts +++ b/src/plugins/data/server/saved_objects/background_session.ts @@ -39,6 +39,12 @@ export const backgroundSessionMapping: SavedObjectsType = { status: { type: 'keyword', }, + appId: { + type: 'keyword', + }, + urlGeneratorId: { + type: 'keyword', + }, initialState: { type: 'object', enabled: false, diff --git a/src/plugins/data/server/search/routes/session.ts b/src/plugins/data/server/search/routes/session.ts index 93f07ecfb92ff6..f7dfc776565e08 100644 --- a/src/plugins/data/server/search/routes/session.ts +++ b/src/plugins/data/server/search/routes/session.ts @@ -28,19 +28,31 @@ export function registerSessionRoutes(router: IRouter): void { body: schema.object({ sessionId: schema.string(), name: schema.string(), + appId: schema.string(), expires: schema.maybe(schema.string()), + urlGeneratorId: schema.string(), initialState: schema.maybe(schema.object({}, { unknowns: 'allow' })), restoreState: schema.maybe(schema.object({}, { unknowns: 'allow' })), }), }, }, async (context, request, res) => { - const { sessionId, name, expires, initialState, restoreState } = request.body; + const { + sessionId, + name, + expires, + initialState, + restoreState, + appId, + urlGeneratorId, + } = request.body; try { const response = await context.search!.session.save(sessionId, { name, + appId, expires, + urlGeneratorId, initialState, restoreState, }); diff --git a/src/plugins/data/server/search/session/session_service.test.ts b/src/plugins/data/server/search/session/session_service.test.ts index 1ceebae967d4c6..5ff6d4b932487c 100644 --- a/src/plugins/data/server/search/session/session_service.test.ts +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -33,6 +33,8 @@ describe('BackgroundSessionService', () => { type: BACKGROUND_SESSION_TYPE, attributes: { name: 'my_name', + appId: 'my_app_id', + urlGeneratorId: 'my_url_generator_id', idMapping: {}, }, references: [], @@ -121,6 +123,8 @@ describe('BackgroundSessionService', () => { const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const isStored = false; const name = 'my saved background search session'; + const appId = 'my_app_id'; + const urlGeneratorId = 'my_url_generator_id'; const created = new Date().toISOString(); const expires = new Date().toISOString(); @@ -133,7 +137,11 @@ describe('BackgroundSessionService', () => { expect(savedObjectsClient.update).not.toHaveBeenCalled(); - await service.save(sessionId, { name, created, expires }, { savedObjectsClient }); + await service.save( + sessionId, + { name, created, expires, appId, urlGeneratorId }, + { savedObjectsClient } + ); expect(savedObjectsClient.create).toHaveBeenCalledWith( BACKGROUND_SESSION_TYPE, @@ -145,6 +153,8 @@ describe('BackgroundSessionService', () => { restoreState: {}, status: BackgroundSessionStatus.IN_PROGRESS, idMapping: { [requestHash]: searchId }, + appId, + urlGeneratorId, }, { id: sessionId } ); @@ -215,6 +225,8 @@ describe('BackgroundSessionService', () => { type: BACKGROUND_SESSION_TYPE, attributes: { name: 'my_name', + appId: 'my_app_id', + urlGeneratorId: 'my_url_generator_id', idMapping: { [requestHash]: searchId }, }, references: [], diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index eca5f428b8555f..b9a738413ede4f 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -64,20 +64,34 @@ export class BackgroundSessionService { sessionId: string, { name, + appId, created = new Date().toISOString(), expires = new Date(Date.now() + DEFAULT_EXPIRATION).toISOString(), status = BackgroundSessionStatus.IN_PROGRESS, + urlGeneratorId, initialState = {}, restoreState = {}, }: Partial, { savedObjectsClient }: BackgroundSessionDependencies ) => { if (!name) throw new Error('Name is required'); + if (!appId) throw new Error('AppId is required'); + if (!urlGeneratorId) throw new Error('UrlGeneratorId is required'); // Get the mapping of request hash/search ID for this session const searchMap = this.sessionSearchMap.get(sessionId) ?? new Map(); const idMapping = Object.fromEntries(searchMap.entries()); - const attributes = { name, created, expires, status, initialState, restoreState, idMapping }; + const attributes = { + name, + created, + expires, + status, + initialState, + restoreState, + idMapping, + urlGeneratorId, + appId, + }; const session = await savedObjectsClient.create( BACKGROUND_SESSION_TYPE, attributes, diff --git a/src/plugins/discover/jest.config.js b/src/plugins/discover/jest.config.js new file mode 100644 index 00000000000000..0723569db357d3 --- /dev/null +++ b/src/plugins/discover/jest.config.js @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/discover'], + testRunner: 'jasmine2', +}; diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 7059593c0c4e73..d0340c2cf4edd1 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -23,7 +23,7 @@ import { debounceTime } from 'rxjs/operators'; import moment from 'moment'; import dateMath from '@elastic/datemath'; import { i18n } from '@kbn/i18n'; -import { getState, splitState } from './discover_state'; +import { createSearchSessionRestorationDataProvider, getState, splitState } from './discover_state'; import { RequestAdapter } from '../../../../inspector/public'; import { @@ -60,14 +60,14 @@ import { getSwitchIndexPatternAppState } from '../helpers/get_switch_index_patte import { addFatalError } from '../../../../kibana_legacy/public'; import { METRIC_TYPE } from '@kbn/analytics'; import { SEARCH_SESSION_ID_QUERY_PARAM } from '../../url_generator'; -import { removeQueryParam, getQueryParams } from '../../../../kibana_utils/public'; +import { getQueryParams, removeQueryParam } from '../../../../kibana_utils/public'; import { DEFAULT_COLUMNS_SETTING, MODIFY_COLUMNS_ON_SWITCH, SAMPLE_SIZE_SETTING, SEARCH_ON_PAGE_LOAD_SETTING, } from '../../../common'; -import { resolveIndexPattern, loadIndexPattern } from '../helpers/resolve_index_pattern'; +import { loadIndexPattern, resolveIndexPattern } from '../helpers/resolve_index_pattern'; import { getTopNavLinks } from '../components/top_nav/get_top_nav_links'; import { updateSearchSource } from '../helpers/update_search_source'; import { calcFieldCounts } from '../helpers/calc_field_counts'; @@ -85,7 +85,7 @@ const { toastNotifications, uiSettings: config, trackUiMetric, -} = services; +} = getServices(); const fetchStatuses = { UNINITIALIZED: 'uninitialized', @@ -204,12 +204,20 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise // used for restoring background session let isInitialSearch = true; + // search session requested a data refresh + subscriptions.add( + data.search.session.onRefresh$.subscribe(() => { + refetch$.next(); + }) + ); + const state = getState({ getStateDefaults, storeInSessionStorage: config.get('state:storeInSessionStorage'), history, toasts: core.notifications.toasts, }); + const { appStateContainer, startSync: startStateSync, @@ -280,6 +288,14 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise } }); + data.search.session.setSearchSessionInfoProvider( + createSearchSessionRestorationDataProvider({ + appStateContainer, + data, + getSavedSearchId: () => savedSearch.id, + }) + ); + $scope.setIndexPattern = async (id) => { const nextIndexPattern = await indexPatterns.get(id); if (nextIndexPattern) { diff --git a/src/plugins/discover/public/application/angular/discover_state.ts b/src/plugins/discover/public/application/angular/discover_state.ts index 3c6ef1d3e4334e..7de4ac27dd81fe 100644 --- a/src/plugins/discover/public/application/angular/discover_state.ts +++ b/src/plugins/discover/public/application/angular/discover_state.ts @@ -20,15 +20,23 @@ import { isEqual } from 'lodash'; import { History } from 'history'; import { NotificationsStart } from 'kibana/public'; import { - createStateContainer, createKbnUrlStateStorage, - syncState, - ReduxLikeStateContainer, + createStateContainer, IKbnUrlStateStorage, + ReduxLikeStateContainer, + StateContainer, + syncState, withNotifyOnErrors, } from '../../../../kibana_utils/public'; -import { esFilters, Filter, Query } from '../../../../data/public'; +import { + DataPublicPluginStart, + esFilters, + Filter, + Query, + SearchSessionInfoProvider, +} from '../../../../data/public'; import { migrateLegacyQuery } from '../helpers/migrate_legacy_query'; +import { DISCOVER_APP_URL_GENERATOR, DiscoverUrlGeneratorState } from '../../url_generator'; export interface AppState { /** @@ -247,3 +255,47 @@ export function isEqualState(stateA: AppState, stateB: AppState) { const { filters: stateBFilters = [], ...stateBPartial } = stateB; return isEqual(stateAPartial, stateBPartial) && isEqualFilters(stateAFilters, stateBFilters); } + +export function createSearchSessionRestorationDataProvider(deps: { + appStateContainer: StateContainer; + data: DataPublicPluginStart; + getSavedSearchId: () => string | undefined; +}): SearchSessionInfoProvider { + return { + getName: async () => 'Discover', + getUrlGeneratorData: async () => { + return { + urlGeneratorId: DISCOVER_APP_URL_GENERATOR, + initialState: createUrlGeneratorState({ ...deps, forceAbsoluteTime: false }), + restoreState: createUrlGeneratorState({ ...deps, forceAbsoluteTime: true }), + }; + }, + }; +} + +function createUrlGeneratorState({ + appStateContainer, + data, + getSavedSearchId, + forceAbsoluteTime, // TODO: not implemented +}: { + appStateContainer: StateContainer; + data: DataPublicPluginStart; + getSavedSearchId: () => string | undefined; + forceAbsoluteTime: boolean; +}): DiscoverUrlGeneratorState { + const appState = appStateContainer.get(); + return { + filters: data.query.filterManager.getFilters(), + indexPatternId: appState.index, + query: appState.query, + savedSearchId: getSavedSearchId(), + timeRange: data.query.timefilter.timefilter.getTime(), // TODO: handle relative time range + searchSessionId: data.search.session.getSessionId(), + columns: appState.columns, + sort: appState.sort, + savedQuery: appState.savedQuery, + interval: appState.interval, + useHash: false, + }; +} diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable.ts b/src/plugins/discover/public/application/embeddable/search_embeddable.ts index 980e90d0acf200..e592d0b0ec8fd0 100644 --- a/src/plugins/discover/public/application/embeddable/search_embeddable.ts +++ b/src/plugins/discover/public/application/embeddable/search_embeddable.ts @@ -342,12 +342,11 @@ export class SearchEmbeddable if (isFetchRequired) { this.filtersSearchSource!.setField('filter', this.input.filters); this.filtersSearchSource!.setField('query', this.input.query); - - this.fetch(); - this.prevFilters = this.input.filters; this.prevQuery = this.input.query; this.prevTimeRange = this.input.timeRange; + + this.fetch(); } else if (this.searchScope) { // trigger a digest cycle to make sure non-fetch relevant changes are propagated this.searchScope.$applyAsync(); diff --git a/src/plugins/discover/public/url_generator.test.ts b/src/plugins/discover/public/url_generator.test.ts index 98b7625e63c72f..95bff6b1fdc9c1 100644 --- a/src/plugins/discover/public/url_generator.test.ts +++ b/src/plugins/discover/public/url_generator.test.ts @@ -221,6 +221,19 @@ describe('Discover url generator', () => { expect(url).toContain('__test__'); }); + test('can specify columns, interval, sort and savedQuery', async () => { + const { generator } = await setup(); + const url = await generator.createUrl({ + columns: ['_source'], + interval: 'auto', + sort: [['timestamp, asc']], + savedQuery: '__savedQueryId__', + }); + expect(url).toMatchInlineSnapshot( + `"xyz/app/discover#/?_g=()&_a=(columns:!(_source),interval:auto,savedQuery:__savedQueryId__,sort:!(!('timestamp,%20asc')))"` + ); + }); + describe('useHash property', () => { describe('when default useHash is set to false', () => { test('when using default, sets index pattern ID in the generated URL', async () => { diff --git a/src/plugins/discover/public/url_generator.ts b/src/plugins/discover/public/url_generator.ts index df9b16a4627ec7..6d86818910b11b 100644 --- a/src/plugins/discover/public/url_generator.ts +++ b/src/plugins/discover/public/url_generator.ts @@ -52,7 +52,7 @@ export interface DiscoverUrlGeneratorState { refreshInterval?: RefreshInterval; /** - * Optionally apply filers. + * Optionally apply filters. */ filters?: Filter[]; @@ -72,6 +72,24 @@ export interface DiscoverUrlGeneratorState { * Background search session id */ searchSessionId?: string; + + /** + * Columns displayed in the table + */ + columns?: string[]; + + /** + * Used interval of the histogram + */ + interval?: string; + /** + * Array of the used sorting [[field,direction],...] + */ + sort?: string[][]; + /** + * id of the used saved query + */ + savedQuery?: string; } interface Params { @@ -88,20 +106,28 @@ export class DiscoverUrlGenerator public readonly id = DISCOVER_APP_URL_GENERATOR; public readonly createUrl = async ({ + useHash = this.params.useHash, filters, indexPatternId, query, refreshInterval, savedSearchId, timeRange, - useHash = this.params.useHash, searchSessionId, + columns, + savedQuery, + sort, + interval, }: DiscoverUrlGeneratorState): Promise => { const savedSearchPath = savedSearchId ? encodeURIComponent(savedSearchId) : ''; const appState: { query?: Query; filters?: Filter[]; index?: string; + columns?: string[]; + interval?: string; + sort?: string[][]; + savedQuery?: string; } = {}; const queryState: QueryState = {}; @@ -109,6 +135,10 @@ export class DiscoverUrlGenerator if (filters && filters.length) appState.filters = filters?.filter((f) => !esFilters.isFilterPinned(f)); if (indexPatternId) appState.index = indexPatternId; + if (columns) appState.columns = columns; + if (savedQuery) appState.savedQuery = savedQuery; + if (sort) appState.sort = sort; + if (interval) appState.interval = interval; if (timeRange) queryState.time = timeRange; if (filters && filters.length) diff --git a/src/plugins/embeddable/jest.config.js b/src/plugins/embeddable/jest.config.js new file mode 100644 index 00000000000000..a0797910925498 --- /dev/null +++ b/src/plugins/embeddable/jest.config.js @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/embeddable'], + testRunner: 'jasmine2', +}; diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index 023cb3d19b632d..534ab0f331e87d 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -34,6 +34,7 @@ import { ExclusiveUnion } from '@elastic/eui'; import { ExpressionAstFunction } from 'src/plugins/expressions/common'; import { History } from 'history'; import { Href } from 'history'; +import { HttpSetup as HttpSetup_2 } from 'kibana/public'; import { I18nStart as I18nStart_2 } from 'src/core/public'; import { IconType } from '@elastic/eui'; import { ISearchOptions } from 'src/plugins/data/public'; @@ -56,7 +57,9 @@ import { OverlayStart as OverlayStart_2 } from 'src/core/public'; import { PackageInfo } from '@kbn/config'; import { Path } from 'history'; import { PluginInitializerContext } from 'src/core/public'; +import { PluginInitializerContext as PluginInitializerContext_3 } from 'kibana/public'; import * as PropTypes from 'prop-types'; +import { PublicContract } from '@kbn/utility-types'; import { PublicMethodsOf } from '@kbn/utility-types'; import { PublicUiSettingsParams } from 'src/core/server/types'; import React from 'react'; @@ -77,6 +80,7 @@ import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/ex import { ShallowPromise } from '@kbn/utility-types'; import { SimpleSavedObject as SimpleSavedObject_2 } from 'src/core/public'; import { Start as Start_2 } from 'src/plugins/inspector/public'; +import { StartServicesAccessor as StartServicesAccessor_2 } from 'kibana/public'; import { ToastInputFields as ToastInputFields_2 } from 'src/core/public/notifications'; import { ToastsSetup as ToastsSetup_2 } from 'kibana/public'; import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; diff --git a/src/plugins/es_ui_shared/jest.config.js b/src/plugins/es_ui_shared/jest.config.js new file mode 100644 index 00000000000000..5b8b34692800d6 --- /dev/null +++ b/src/plugins/es_ui_shared/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/es_ui_shared'], +}; diff --git a/src/plugins/expressions/jest.config.js b/src/plugins/expressions/jest.config.js new file mode 100644 index 00000000000000..b4e3e10b3fc700 --- /dev/null +++ b/src/plugins/expressions/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/expressions'], +}; diff --git a/src/plugins/home/jest.config.js b/src/plugins/home/jest.config.js new file mode 100644 index 00000000000000..c56c7b3eed1d6d --- /dev/null +++ b/src/plugins/home/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/home'], +}; diff --git a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts index 37657912deb95b..60d05890028d10 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts @@ -294,8 +294,7 @@ export const getSavedObjects = (): SavedObject[] => [ attributes: { title: 'kibana_sample_data_ecommerce', timeFieldName: 'order_date', - fields: - '[{"name":"_id","type":"string","esTypes":["_id"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_index","type":"string","esTypes":["_index"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_score","type":"number","count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_source","type":"_source","esTypes":["_source"],"count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_type","type":"string","esTypes":["_type"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"category","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"category.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"category"}}},{"name":"currency","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"customer_birth_date","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"customer_first_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_first_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"customer_first_name"}}},{"name":"customer_full_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_full_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"customer_full_name"}}},{"name":"customer_gender","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"customer_id","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"customer_last_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_last_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"customer_last_name"}}},{"name":"customer_phone","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"day_of_week","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"day_of_week_i","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"email","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"event.dataset","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.city_name","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.continent_name","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.country_iso_code","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.location","type":"geo_point","esTypes":["geo_point"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.region_name","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"manufacturer","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"manufacturer.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"manufacturer"}}},{"name":"order_date","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"order_id","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products._id","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"products._id.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"products._id"}}},{"name":"products.base_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.base_unit_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.category","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"products.category.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"products.category"}}},{"name":"products.created_on","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.discount_amount","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.discount_percentage","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.manufacturer","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"products.manufacturer.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"products.manufacturer"}}},{"name":"products.min_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.product_id","type":"number","esTypes":["long"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.product_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"products.product_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"products.product_name"}}},{"name":"products.quantity","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.sku","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.tax_amount","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.taxful_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.taxless_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.unit_discount_amount","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"sku","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"taxful_total_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"taxless_total_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"total_quantity","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"total_unique_products","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"type","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"user","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true}]', + fields: '[]', fieldFormatMap: '{"taxful_total_price":{"id":"number","params":{"pattern":"$0,0.[00]"}}}', }, references: [], diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts index 6f701d75e7d52d..e65b6ad40651bc 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts @@ -439,7 +439,7 @@ export const getSavedObjects = (): SavedObject[] => [ title: 'kibana_sample_data_flights', timeFieldName: 'timestamp', fields: - '[{"name":"AvgTicketPrice","type":"number","esTypes":["float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"Cancelled","type":"boolean","esTypes":["boolean"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"Carrier","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"Dest","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestAirportID","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestCityName","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestCountry","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestLocation","type":"geo_point","esTypes":["geo_point"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestRegion","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestWeather","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DistanceKilometers","type":"number","esTypes":["float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DistanceMiles","type":"number","esTypes":["float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightDelay","type":"boolean","esTypes":["boolean"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightDelayMin","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightDelayType","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightNum","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightTimeHour","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightTimeMin","type":"number","esTypes":["float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"Origin","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginAirportID","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginCityName","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginCountry","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginLocation","type":"geo_point","esTypes":["geo_point"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginRegion","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginWeather","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"_id","type":"string","esTypes":["_id"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_index","type":"string","esTypes":["_index"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_score","type":"number","count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_source","type":"_source","esTypes":["_source"],"count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_type","type":"string","esTypes":["_type"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"dayOfWeek","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"timestamp","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"hour_of_day","type":"number","count":0,"scripted":true,"script":"doc[\'timestamp\'].value.hourOfDay","lang":"painless","searchable":true,"aggregatable":true,"readFromDocValues":false}]', + '[{"name":"hour_of_day","type":"number","count":0,"scripted":true,"script":"doc[\'timestamp\'].value.hourOfDay","lang":"painless","searchable":true,"aggregatable":true,"readFromDocValues":false}]', fieldFormatMap: '{"hour_of_day":{"id":"number","params":{"pattern":"00"}},"AvgTicketPrice":{"id":"number","params":{"pattern":"$0,0.[00]"}}}', }, diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts index f8d39e6689fa8b..068ba66c4b0ded 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts @@ -270,7 +270,7 @@ export const getSavedObjects = (): SavedObject[] => [ title: 'kibana_sample_data_logs', timeFieldName: 'timestamp', fields: - '[{"name":"@timestamp","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"_id","type":"string","esTypes":["_id"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_index","type":"string","esTypes":["_index"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_score","type":"number","count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_source","type":"_source","esTypes":["_source"],"count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_type","type":"string","esTypes":["_type"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"agent","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"agent.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "agent"}}},{"name":"bytes","type":"number","esTypes":["long"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"clientip","type":"ip","esTypes":["ip"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"event.dataset","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"extension","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"extension.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "extension"}}},{"name":"geo.coordinates","type":"geo_point","esTypes":["geo_point"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geo.dest","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geo.src","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geo.srcdest","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"host","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"host.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "host"}}},{"name":"index","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"index.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "index"}}},{"name":"ip","type":"ip","esTypes":["ip"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"machine.os","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"machine.os.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "machine.os"}}},{"name":"machine.ram","type":"number","esTypes":["long"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"memory","type":"number","esTypes":["double"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"message","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"message.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "message"}}},{"name":"phpmemory","type":"number","esTypes":["long"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"referer","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"request","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"request.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "request"}}},{"name":"response","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"response.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "response"}}},{"name":"tags","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"tags.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "tags"}}},{"name":"timestamp","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"url","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"url.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "url"}}},{"name":"utc_time","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"hour_of_day","type":"number","count":0,"scripted":true,"script":"doc[\'timestamp\'].value.getHour()","lang":"painless","searchable":true,"aggregatable":true,"readFromDocValues":false}]', + '[{"name":"hour_of_day","type":"number","count":0,"scripted":true,"script":"doc[\'timestamp\'].value.getHour()","lang":"painless","searchable":true,"aggregatable":true,"readFromDocValues":false}]', fieldFormatMap: '{"hour_of_day":{}}', }, references: [], diff --git a/src/plugins/home/server/services/sample_data/usage/usage.ts b/src/plugins/home/server/services/sample_data/usage/usage.ts index ba67906febf1a3..6a243b47dee554 100644 --- a/src/plugins/home/server/services/sample_data/usage/usage.ts +++ b/src/plugins/home/server/services/sample_data/usage/usage.ts @@ -43,7 +43,7 @@ export function usage( addInstall: async (dataSet: string) => { try { const internalRepository = await internalRepositoryPromise; - await internalRepository.incrementCounter(SAVED_OBJECT_ID, dataSet, `installCount`); + await internalRepository.incrementCounter(SAVED_OBJECT_ID, dataSet, [`installCount`]); } catch (err) { handleIncrementError(err); } @@ -51,7 +51,7 @@ export function usage( addUninstall: async (dataSet: string) => { try { const internalRepository = await internalRepositoryPromise; - await internalRepository.incrementCounter(SAVED_OBJECT_ID, dataSet, `unInstallCount`); + await internalRepository.incrementCounter(SAVED_OBJECT_ID, dataSet, [`unInstallCount`]); } catch (err) { handleIncrementError(err); } diff --git a/src/plugins/index_pattern_management/jest.config.js b/src/plugins/index_pattern_management/jest.config.js new file mode 100644 index 00000000000000..8a499406080fd7 --- /dev/null +++ b/src/plugins/index_pattern_management/jest.config.js @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/index_pattern_management'], + testRunner: 'jasmine2', +}; diff --git a/src/plugins/input_control_vis/jest.config.js b/src/plugins/input_control_vis/jest.config.js new file mode 100644 index 00000000000000..17fb6f3359bf38 --- /dev/null +++ b/src/plugins/input_control_vis/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/input_control_vis'], +}; diff --git a/src/plugins/input_control_vis/public/__snapshots__/input_control_fn.test.ts.snap b/src/plugins/input_control_vis/public/__snapshots__/input_control_fn.test.ts.snap index 35349b47196761..696b74d040e0c5 100644 --- a/src/plugins/input_control_vis/public/__snapshots__/input_control_fn.test.ts.snap +++ b/src/plugins/input_control_vis/public/__snapshots__/input_control_fn.test.ts.snap @@ -2,7 +2,7 @@ exports[`interpreter/functions#input_control_vis returns an object with the correct structure 1`] = ` Object { - "as": "visualization", + "as": "input_control_vis", "type": "render", "value": Object { "visConfig": Object { diff --git a/src/plugins/input_control_vis/public/__snapshots__/to_ast.test.ts.snap b/src/plugins/input_control_vis/public/__snapshots__/to_ast.test.ts.snap new file mode 100644 index 00000000000000..edd44d8dd03374 --- /dev/null +++ b/src/plugins/input_control_vis/public/__snapshots__/to_ast.test.ts.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`input_control_vis toExpressionAst should build an expression based on vis.params 1`] = ` +Object { + "chain": Array [ + Object { + "arguments": Object { + "visConfig": Array [ + "{\\"controls\\":[{\\"id\\":\\"1536977437774\\",\\"fieldName\\":\\"manufacturer.keyword\\",\\"parent\\":\\"\\",\\"label\\":\\"Manufacturer\\",\\"type\\":\\"list\\",\\"options\\":{\\"type\\":\\"terms\\",\\"multiselect\\":true,\\"dynamicOptions\\":true,\\"size\\":5,\\"order\\":\\"desc\\"},\\"indexPattern\\":\\"ff959d40-b880-11e8-a6d9-e546fe2bba5f\\"}],\\"updateFiltersOnChange\\":false,\\"useTimeFilter\\":true,\\"pinFilters\\":false}", + ], + }, + "function": "input_control_vis", + "type": "function", + }, + ], + "type": "expression", +} +`; diff --git a/src/plugins/input_control_vis/public/components/editor/_index.scss b/src/plugins/input_control_vis/public/components/editor/_index.scss deleted file mode 100644 index 9af8f8d6e82224..00000000000000 --- a/src/plugins/input_control_vis/public/components/editor/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './control_editor'; diff --git a/src/plugins/input_control_vis/public/components/editor/_control_editor.scss b/src/plugins/input_control_vis/public/components/editor/control_editor.scss similarity index 100% rename from src/plugins/input_control_vis/public/components/editor/_control_editor.scss rename to src/plugins/input_control_vis/public/components/editor/control_editor.scss diff --git a/src/plugins/input_control_vis/public/components/editor/control_editor.tsx b/src/plugins/input_control_vis/public/components/editor/control_editor.tsx index aa473095aaf3f1..109237f8db4ec6 100644 --- a/src/plugins/input_control_vis/public/components/editor/control_editor.tsx +++ b/src/plugins/input_control_vis/public/components/editor/control_editor.tsx @@ -36,6 +36,8 @@ import { getTitle, ControlParams, CONTROL_TYPES, ControlParamsOptions } from '.. import { IIndexPattern } from '../../../../data/public'; import { InputControlVisDependencies } from '../../plugin'; +import './control_editor.scss'; + interface ControlEditorUiProps { controlIndex: number; controlParams: ControlParams; diff --git a/src/plugins/input_control_vis/public/components/editor/controls_tab.test.tsx b/src/plugins/input_control_vis/public/components/editor/controls_tab.test.tsx index a85f98c7b89ba8..c05dec8fccbe1b 100644 --- a/src/plugins/input_control_vis/public/components/editor/controls_tab.test.tsx +++ b/src/plugins/input_control_vis/public/components/editor/controls_tab.test.tsx @@ -21,16 +21,16 @@ import React from 'react'; import { shallowWithIntl, mountWithIntl } from '@kbn/test/jest'; import { findTestSubject } from '@elastic/eui/lib/test'; import { getDepsMock, getIndexPatternMock } from '../../test_utils'; -import { ControlsTab, ControlsTabUiProps } from './controls_tab'; +import ControlsTab, { ControlsTabProps } from './controls_tab'; import { Vis } from '../../../../visualizations/public'; const indexPatternsMock = { get: getIndexPatternMock, }; -let props: ControlsTabUiProps; +let props: ControlsTabProps; beforeEach(() => { - props = { + props = ({ deps: getDepsMock(), vis: ({ API: { @@ -78,18 +78,18 @@ beforeEach(() => { }, setValue: jest.fn(), intl: null as any, - }; + } as unknown) as ControlsTabProps; }); test('renders ControlsTab', () => { - const component = shallowWithIntl(); + const component = shallowWithIntl(); expect(component).toMatchSnapshot(); }); describe('behavior', () => { test('add control button', () => { - const component = mountWithIntl(); + const component = mountWithIntl(); findTestSubject(component, 'inputControlEditorAddBtn').simulate('click'); @@ -102,7 +102,7 @@ describe('behavior', () => { }); test('remove control button', () => { - const component = mountWithIntl(); + const component = mountWithIntl(); findTestSubject(component, 'inputControlEditorRemoveControl0').simulate('click'); const expectedParams = [ 'controls', @@ -125,7 +125,7 @@ describe('behavior', () => { }); test('move down control button', () => { - const component = mountWithIntl(); + const component = mountWithIntl(); findTestSubject(component, 'inputControlEditorMoveDownControl0').simulate('click'); const expectedParams = [ 'controls', @@ -162,7 +162,7 @@ describe('behavior', () => { }); test('move up control button', () => { - const component = mountWithIntl(); + const component = mountWithIntl(); findTestSubject(component, 'inputControlEditorMoveUpControl1').simulate('click'); const expectedParams = [ 'controls', diff --git a/src/plugins/input_control_vis/public/components/editor/controls_tab.tsx b/src/plugins/input_control_vis/public/components/editor/controls_tab.tsx index a9f04a86f8d03c..0e622e08c529fc 100644 --- a/src/plugins/input_control_vis/public/components/editor/controls_tab.tsx +++ b/src/plugins/input_control_vis/public/components/editor/controls_tab.tsx @@ -18,7 +18,8 @@ */ import React, { PureComponent } from 'react'; -import { injectI18n, FormattedMessage, InjectedIntlProps } from '@kbn/i18n/react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { EuiButton, @@ -44,22 +45,17 @@ import { } from '../../editor_utils'; import { getLineageMap, getParentCandidates } from '../../lineage'; import { InputControlVisDependencies } from '../../plugin'; +import { InputControlVisParams } from '../../types'; interface ControlsTabUiState { type: CONTROL_TYPES; } -interface ControlsTabUiParams { - controls: ControlParams[]; -} -type ControlsTabUiInjectedProps = InjectedIntlProps & - Pick, 'vis' | 'stateParams' | 'setValue'> & { - deps: InputControlVisDependencies; - }; +export type ControlsTabProps = VisOptionsProps & { + deps: InputControlVisDependencies; +}; -export type ControlsTabUiProps = ControlsTabUiInjectedProps; - -class ControlsTabUi extends PureComponent { +class ControlsTab extends PureComponent { state = { type: CONTROL_TYPES.LIST, }; @@ -161,8 +157,6 @@ class ControlsTabUi extends PureComponent {this.renderControls()} @@ -176,25 +170,31 @@ class ControlsTabUi extends PureComponent this.setState({ type: event.target.value as CONTROL_TYPES })} - aria-label={intl.formatMessage({ - id: 'inputControl.editor.controlsTab.select.controlTypeAriaLabel', - defaultMessage: 'Select control type', - })} + aria-label={i18n.translate( + 'inputControl.editor.controlsTab.select.controlTypeAriaLabel', + { + defaultMessage: 'Select control type', + } + )} /> @@ -205,10 +205,12 @@ class ControlsTabUi extends PureComponent ( - props: Omit -) => ; +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { ControlsTab as default }; diff --git a/src/plugins/input_control_vis/public/components/editor/index.tsx b/src/plugins/input_control_vis/public/components/editor/index.tsx new file mode 100644 index 00000000000000..11b3c2ea4ee8a8 --- /dev/null +++ b/src/plugins/input_control_vis/public/components/editor/index.tsx @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { lazy } from 'react'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; +import { InputControlVisDependencies } from '../../plugin'; +import { InputControlVisParams } from '../../types'; + +const ControlsTab = lazy(() => import('./controls_tab')); +const OptionsTab = lazy(() => import('./options_tab')); + +export const getControlsTab = (deps: InputControlVisDependencies) => ( + props: VisOptionsProps +) => ; + +export const OptionsTabLazy = (props: VisOptionsProps) => ( + +); diff --git a/src/plugins/input_control_vis/public/components/editor/options_tab.test.tsx b/src/plugins/input_control_vis/public/components/editor/options_tab.test.tsx index 0f126e915a68c5..0970d1cd3c2984 100644 --- a/src/plugins/input_control_vis/public/components/editor/options_tab.test.tsx +++ b/src/plugins/input_control_vis/public/components/editor/options_tab.test.tsx @@ -22,13 +22,13 @@ import { shallow } from 'enzyme'; import { mountWithIntl } from '@kbn/test/jest'; import { Vis } from '../../../../visualizations/public'; -import { OptionsTab, OptionsTabProps } from './options_tab'; +import OptionsTab, { OptionsTabProps } from './options_tab'; describe('OptionsTab', () => { let props: OptionsTabProps; beforeEach(() => { - props = { + props = ({ vis: {} as Vis, stateParams: { updateFiltersOnChange: false, @@ -36,7 +36,7 @@ describe('OptionsTab', () => { pinFilters: false, }, setValue: jest.fn(), - }; + } as unknown) as OptionsTabProps; }); it('should renders OptionsTab', () => { diff --git a/src/plugins/input_control_vis/public/components/editor/options_tab.tsx b/src/plugins/input_control_vis/public/components/editor/options_tab.tsx index cdff6cabad8ba8..306d1141e75bd8 100644 --- a/src/plugins/input_control_vis/public/components/editor/options_tab.tsx +++ b/src/plugins/input_control_vis/public/components/editor/options_tab.tsx @@ -24,20 +24,11 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiSwitchEvent } from '@elastic/eui'; import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; +import { InputControlVisParams } from '../../types'; -interface OptionsTabParams { - updateFiltersOnChange: boolean; - useTimeFilter: boolean; - pinFilters: boolean; -} -type OptionsTabInjectedProps = Pick< - VisOptionsProps, - 'vis' | 'setValue' | 'stateParams' ->; - -export type OptionsTabProps = OptionsTabInjectedProps; +export type OptionsTabProps = VisOptionsProps; -export class OptionsTab extends PureComponent { +class OptionsTab extends PureComponent { handleUpdateFiltersChange = (event: EuiSwitchEvent) => { this.props.setValue('updateFiltersOnChange', event.target.checked); }; @@ -98,3 +89,6 @@ export class OptionsTab extends PureComponent { ); } } +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { OptionsTab as default }; diff --git a/src/plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap b/src/plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap index 5a76967c71fbb3..5e1f25993616b0 100644 --- a/src/plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap +++ b/src/plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap @@ -2,355 +2,371 @@ exports[`Apply and Cancel change btns enabled when there are changes 1`] = `
- - - - - - - - - - - - + + - - - - - - + + + + - - - - + + + + + + + + + + +
`; exports[`Clear btns enabled when there are values 1`] = `
- - - - - - - - - - - - + + - - - - - - + + + + - - - - + + + + + + + + + + +
`; exports[`Renders list control 1`] = `
- - - - - - - - - - - - + + - - - - - - + + + + - - - - + + + + + + + + + + +
`; exports[`Renders range control 1`] = `
- - - - - - - - - - - - + + - - - - - - + + + + - - - - + + + + + + + + + + +
`; diff --git a/src/plugins/input_control_vis/public/components/vis/_index.scss b/src/plugins/input_control_vis/public/components/vis/_index.scss deleted file mode 100644 index a428a7c1782e30..00000000000000 --- a/src/plugins/input_control_vis/public/components/vis/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './vis'; diff --git a/src/plugins/input_control_vis/public/components/vis/_vis.scss b/src/plugins/input_control_vis/public/components/vis/_vis.scss deleted file mode 100644 index d42c2c5f263c75..00000000000000 --- a/src/plugins/input_control_vis/public/components/vis/_vis.scss +++ /dev/null @@ -1,5 +0,0 @@ -.icvContainer { - width: 100%; - margin: 0 $euiSizeXS; - padding: $euiSizeS; -} diff --git a/src/plugins/input_control_vis/public/components/vis/input_control_vis.scss b/src/plugins/input_control_vis/public/components/vis/input_control_vis.scss new file mode 100644 index 00000000000000..322573446f7629 --- /dev/null +++ b/src/plugins/input_control_vis/public/components/vis/input_control_vis.scss @@ -0,0 +1,13 @@ +.icvContainer__wrapper { + @include euiScrollBar; + min-height: 0; + flex: 1 1 0; + display: flex; + overflow: auto; +} + +.icvContainer { + width: 100%; + margin: 0 $euiSizeXS; + padding: $euiSizeS; +} diff --git a/src/plugins/input_control_vis/public/components/vis/input_control_vis.tsx b/src/plugins/input_control_vis/public/components/vis/input_control_vis.tsx index 95edb4a35bc22d..058f39cb8a6d43 100644 --- a/src/plugins/input_control_vis/public/components/vis/input_control_vis.tsx +++ b/src/plugins/input_control_vis/public/components/vis/input_control_vis.tsx @@ -26,6 +26,8 @@ import { RangeControl } from '../../control/range_control_factory'; import { ListControl as ListControlComponent } from './list_control'; import { RangeControl as RangeControlComponent } from './range_control'; +import './input_control_vis.scss'; + function isListControl(control: RangeControl | ListControl): control is ListControl { return control.type === CONTROL_TYPES.LIST; } @@ -165,9 +167,11 @@ export class InputControlVis extends Component { } return ( -
- {this.renderControls()} - {stagingButtons} +
+
+ {this.renderControls()} + {stagingButtons} +
); } diff --git a/src/plugins/input_control_vis/public/index.scss b/src/plugins/input_control_vis/public/index.scss deleted file mode 100644 index 42fded23d77615..00000000000000 --- a/src/plugins/input_control_vis/public/index.scss +++ /dev/null @@ -1,9 +0,0 @@ -// Prefix all styles with "icv" to avoid conflicts. -// Examples -// icvChart -// icvChart__legend -// icvChart__legend--small -// icvChart__legend-isLoading - -@import './components/editor/index'; -@import './components/vis/index'; diff --git a/src/plugins/input_control_vis/public/index.ts b/src/plugins/input_control_vis/public/index.ts index 8edd3fd9996c3f..b6fee12f6d9cbd 100644 --- a/src/plugins/input_control_vis/public/index.ts +++ b/src/plugins/input_control_vis/public/index.ts @@ -17,8 +17,6 @@ * under the License. */ -import './index.scss'; - import { PluginInitializerContext } from '../../../core/public'; import { InputControlVisPlugin as Plugin } from './plugin'; diff --git a/src/plugins/input_control_vis/public/input_control_fn.ts b/src/plugins/input_control_vis/public/input_control_fn.ts index 1664555b916b66..46fba66264bcb2 100644 --- a/src/plugins/input_control_vis/public/input_control_fn.ts +++ b/src/plugins/input_control_vis/public/input_control_fn.ts @@ -20,24 +20,25 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition, Datatable, Render } from '../../expressions/public'; +import { InputControlVisParams } from './types'; interface Arguments { visConfig: string; } -type VisParams = Required; - -interface RenderValue { +export interface InputControlRenderValue { visType: 'input_control_vis'; - visConfig: VisParams; + visConfig: InputControlVisParams; } -export const createInputControlVisFn = (): ExpressionFunctionDefinition< +export type InputControlExpressionFunctionDefinition = ExpressionFunctionDefinition< 'input_control_vis', Datatable, Arguments, - Render -> => ({ + Render +>; + +export const createInputControlVisFn = (): InputControlExpressionFunctionDefinition => ({ name: 'input_control_vis', type: 'render', inputTypes: [], @@ -52,10 +53,10 @@ export const createInputControlVisFn = (): ExpressionFunctionDefinition< }, }, fn(input, args) { - const params = JSON.parse(args.visConfig); + const params: InputControlVisParams = JSON.parse(args.visConfig); return { type: 'render', - as: 'visualization', + as: 'input_control_vis', value: { visType: 'input_control_vis', visConfig: params, diff --git a/src/plugins/input_control_vis/public/input_control_vis_renderer.tsx b/src/plugins/input_control_vis/public/input_control_vis_renderer.tsx new file mode 100644 index 00000000000000..6431ed6ebed1ee --- /dev/null +++ b/src/plugins/input_control_vis/public/input_control_vis_renderer.tsx @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExpressionRenderDefinition } from 'src/plugins/expressions'; +import { InputControlVisDependencies } from './plugin'; +import { InputControlRenderValue } from './input_control_fn'; +import type { InputControlVisControllerType } from './vis_controller'; + +const inputControlVisRegistry = new Map(); + +export const getInputControlVisRenderer: ( + deps: InputControlVisDependencies +) => ExpressionRenderDefinition = (deps) => ({ + name: 'input_control_vis', + reuseDomNode: true, + render: async (domNode, { visConfig }, handlers) => { + let registeredController = inputControlVisRegistry.get(domNode); + + if (!registeredController) { + const { createInputControlVisController } = await import('./vis_controller'); + + const Controller = createInputControlVisController(deps, handlers); + registeredController = new Controller(domNode); + inputControlVisRegistry.set(domNode, registeredController); + + handlers.onDestroy(() => { + registeredController?.destroy(); + inputControlVisRegistry.delete(domNode); + }); + } + + await registeredController.render(visConfig); + handlers.done(); + }, +}); diff --git a/src/plugins/input_control_vis/public/input_control_vis_type.ts b/src/plugins/input_control_vis/public/input_control_vis_type.ts index 6e33e18c1603b0..686327a1ba774a 100644 --- a/src/plugins/input_control_vis/public/input_control_vis_type.ts +++ b/src/plugins/input_control_vis/public/input_control_vis_type.ts @@ -19,15 +19,14 @@ import { i18n } from '@kbn/i18n'; import { VisGroups, BaseVisTypeOptions } from '../../visualizations/public'; -import { createInputControlVisController } from './vis_controller'; -import { getControlsTab } from './components/editor/controls_tab'; -import { OptionsTab } from './components/editor/options_tab'; +import { getControlsTab, OptionsTabLazy } from './components/editor'; import { InputControlVisDependencies } from './plugin'; +import { toExpressionAst } from './to_ast'; +import { InputControlVisParams } from './types'; export function createInputControlVisTypeDefinition( deps: InputControlVisDependencies -): BaseVisTypeOptions { - const InputControlVisController = createInputControlVisController(deps); +): BaseVisTypeOptions { const ControlsTab = getControlsTab(deps); return { @@ -41,7 +40,6 @@ export function createInputControlVisTypeDefinition( defaultMessage: 'Add dropdown menus and range sliders to your dashboard.', }), stage: 'experimental', - visualization: InputControlVisController, visConfig: { defaults: { controls: [], @@ -64,12 +62,12 @@ export function createInputControlVisTypeDefinition( title: i18n.translate('inputControl.register.tabs.optionsTitle', { defaultMessage: 'Options', }), - editor: OptionsTab, + editor: OptionsTabLazy, }, ], }, inspectorAdapters: {}, requestHandler: 'none', - responseHandler: 'none', + toExpressionAst, }; } diff --git a/src/plugins/input_control_vis/public/plugin.ts b/src/plugins/input_control_vis/public/plugin.ts index 2c93a529c25b19..afaaa27d74c82e 100644 --- a/src/plugins/input_control_vis/public/plugin.ts +++ b/src/plugins/input_control_vis/public/plugin.ts @@ -22,6 +22,7 @@ import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/p import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; import { VisualizationsSetup, VisualizationsStart } from '../../visualizations/public'; import { createInputControlVisFn } from './input_control_fn'; +import { getInputControlVisRenderer } from './input_control_vis_renderer'; import { createInputControlVisTypeDefinition } from './input_control_vis_type'; type InputControlVisCoreSetup = CoreSetup; @@ -76,6 +77,7 @@ export class InputControlVisPlugin implements Plugin { }; expressions.registerFunction(createInputControlVisFn); + expressions.registerRenderer(getInputControlVisRenderer(visualizationDependencies)); visualizations.createBaseVisualization( createInputControlVisTypeDefinition(visualizationDependencies) ); diff --git a/src/plugins/input_control_vis/public/to_ast.test.ts b/src/plugins/input_control_vis/public/to_ast.test.ts new file mode 100644 index 00000000000000..fbeb78ee93a1ed --- /dev/null +++ b/src/plugins/input_control_vis/public/to_ast.test.ts @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Vis } from '../../visualizations/public'; +import { InputControlVisParams } from './types'; +import { toExpressionAst } from './to_ast'; + +describe('input_control_vis toExpressionAst', () => { + const vis = { + params: { + controls: [ + { + id: '1536977437774', + fieldName: 'manufacturer.keyword', + parent: '', + label: 'Manufacturer', + type: 'list', + options: { + type: 'terms', + multiselect: true, + dynamicOptions: true, + size: 5, + order: 'desc', + }, + indexPattern: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + }, + ], + updateFiltersOnChange: false, + useTimeFilter: true, + pinFilters: false, + }, + } as Vis; + + it('should build an expression based on vis.params', () => { + const expression = toExpressionAst(vis); + expect(expression).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/input_control_vis/public/to_ast.ts b/src/plugins/input_control_vis/public/to_ast.ts new file mode 100644 index 00000000000000..93c0b4a87cfe6c --- /dev/null +++ b/src/plugins/input_control_vis/public/to_ast.ts @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { buildExpression, buildExpressionFunction } from '../../expressions/public'; +import { Vis } from '../../visualizations/public'; +import { InputControlExpressionFunctionDefinition } from './input_control_fn'; +import { InputControlVisParams } from './types'; + +export const toExpressionAst = (vis: Vis) => { + const inputControl = buildExpressionFunction( + 'input_control_vis', + { + visConfig: JSON.stringify(vis.params), + } + ); + + const ast = buildExpression([inputControl]); + + return ast.toAst(); +}; diff --git a/src/plugins/input_control_vis/public/types.ts b/src/plugins/input_control_vis/public/types.ts new file mode 100644 index 00000000000000..2898ab49590ed4 --- /dev/null +++ b/src/plugins/input_control_vis/public/types.ts @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ControlParams } from './editor_utils'; + +export interface InputControlVisParams { + controls: ControlParams[]; + pinFilters: boolean; + updateFiltersOnChange: boolean; + useTimeFilter: boolean; +} diff --git a/src/plugins/input_control_vis/public/vis_controller.tsx b/src/plugins/input_control_vis/public/vis_controller.tsx index 6f35e17866120b..8e762a38671e9c 100644 --- a/src/plugins/input_control_vis/public/vis_controller.tsx +++ b/src/plugins/input_control_vis/public/vis_controller.tsx @@ -20,20 +20,29 @@ import React from 'react'; import { isEqual } from 'lodash'; import { render, unmountComponentAtNode } from 'react-dom'; - import { Subscription } from 'rxjs'; + import { I18nStart } from 'kibana/public'; +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { VisualizationContainer } from '../../visualizations/public'; +import { FilterManager, Filter } from '../../data/public'; + import { InputControlVis } from './components/vis/input_control_vis'; import { getControlFactory } from './control/control_factory'; import { getLineageMap } from './lineage'; -import { ControlParams } from './editor_utils'; import { RangeControl } from './control/range_control_factory'; import { ListControl } from './control/list_control_factory'; import { InputControlVisDependencies } from './plugin'; -import { FilterManager, Filter } from '../../data/public'; -import { VisParams, ExprVis } from '../../visualizations/public'; +import { InputControlVisParams } from './types'; -export const createInputControlVisController = (deps: InputControlVisDependencies) => { +export type InputControlVisControllerType = InstanceType< + ReturnType +>; + +export const createInputControlVisController = ( + deps: InputControlVisDependencies, + handlers: IInterpreterRenderHandlers +) => { return class InputControlVisController { private I18nContext?: I18nStart['Context']; private _isLoaded = false; @@ -43,9 +52,9 @@ export const createInputControlVisController = (deps: InputControlVisDependencie filterManager: FilterManager; updateSubsciption: any; timeFilterSubscription: Subscription; - visParams?: VisParams; + visParams?: InputControlVisParams; - constructor(public el: Element, public vis: ExprVis) { + constructor(public el: Element) { this.controls = []; this.queryBarUpdateHandler = this.updateControlsFromKbn.bind(this); @@ -63,7 +72,7 @@ export const createInputControlVisController = (deps: InputControlVisDependencie }); } - async render(visData: any, visParams: VisParams) { + async render(visParams: InputControlVisParams) { if (!this.I18nContext) { const [{ i18n }] = await deps.core.getStartServices(); this.I18nContext = i18n.Context; @@ -71,7 +80,7 @@ export const createInputControlVisController = (deps: InputControlVisDependencie if (!this._isLoaded || !isEqual(visParams, this.visParams)) { this.visParams = visParams; this.controls = []; - this.controls = await this.initControls(); + this.controls = await this.initControls(visParams); this._isLoaded = true; } this.drawVis(); @@ -91,34 +100,34 @@ export const createInputControlVisController = (deps: InputControlVisDependencie render( - + + + , this.el ); }; - async initControls() { - const controlParamsList = (this.visParams?.controls as ControlParams[])?.filter( - (controlParams) => { - // ignore controls that do not have indexPattern or field - return controlParams.indexPattern && controlParams.fieldName; - } - ); + async initControls(visParams: InputControlVisParams) { + const controlParamsList = visParams.controls.filter((controlParams) => { + // ignore controls that do not have indexPattern or field + return controlParams.indexPattern && controlParams.fieldName; + }); const controlFactoryPromises = controlParamsList.map((controlParams) => { const factory = getControlFactory(controlParams); - return factory(controlParams, this.visParams?.useTimeFilter, deps); + return factory(controlParams, visParams.useTimeFilter, deps); }); const controls = await Promise.all(controlFactoryPromises); diff --git a/src/plugins/inspector/jest.config.js b/src/plugins/inspector/jest.config.js new file mode 100644 index 00000000000000..6fc4a063970b93 --- /dev/null +++ b/src/plugins/inspector/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/inspector'], +}; diff --git a/src/plugins/kibana_legacy/jest.config.js b/src/plugins/kibana_legacy/jest.config.js new file mode 100644 index 00000000000000..69df43bc5b15f7 --- /dev/null +++ b/src/plugins/kibana_legacy/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/kibana_legacy'], +}; diff --git a/src/plugins/kibana_overview/jest.config.js b/src/plugins/kibana_overview/jest.config.js new file mode 100644 index 00000000000000..4a719b38e47aee --- /dev/null +++ b/src/plugins/kibana_overview/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/kibana_overview'], +}; diff --git a/src/plugins/kibana_react/jest.config.js b/src/plugins/kibana_react/jest.config.js new file mode 100644 index 00000000000000..2810331c9b667f --- /dev/null +++ b/src/plugins/kibana_react/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/kibana_react'], +}; diff --git a/src/plugins/kibana_usage_collection/jest.config.js b/src/plugins/kibana_usage_collection/jest.config.js new file mode 100644 index 00000000000000..9510fc98732b32 --- /dev/null +++ b/src/plugins/kibana_usage_collection/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/kibana_usage_collection'], +}; diff --git a/src/plugins/kibana_utils/jest.config.js b/src/plugins/kibana_utils/jest.config.js new file mode 100644 index 00000000000000..2ddfb7047bf2e4 --- /dev/null +++ b/src/plugins/kibana_utils/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/kibana_utils'], +}; diff --git a/src/plugins/kibana_utils/tsconfig.json b/src/plugins/kibana_utils/tsconfig.json index bd65e06c786087..ae5e9b90af8077 100644 --- a/src/plugins/kibana_utils/tsconfig.json +++ b/src/plugins/kibana_utils/tsconfig.json @@ -16,7 +16,6 @@ "../../../typings/**/*" ], "references": [ - { "path": "../../test_utils/tsconfig.json" }, { "path": "../../core/tsconfig.json" } ] } diff --git a/src/plugins/legacy_export/jest.config.js b/src/plugins/legacy_export/jest.config.js new file mode 100644 index 00000000000000..1480049fd8f85a --- /dev/null +++ b/src/plugins/legacy_export/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/legacy_export'], +}; diff --git a/src/plugins/management/jest.config.js b/src/plugins/management/jest.config.js new file mode 100644 index 00000000000000..287bafc4b1c11e --- /dev/null +++ b/src/plugins/management/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/management'], +}; diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index 122e73796753cd..bf03c649fa6b46 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -31,6 +31,7 @@ import { AppUpdater, AppStatus, AppNavLinkStatus, + AppSearchDeepLink, } from '../../../core/public'; import { MANAGEMENT_APP_ID } from '../common/contants'; @@ -38,6 +39,7 @@ import { ManagementSectionsService, getSectionsServiceStartPrivate, } from './management_sections_service'; +import { ManagementSection } from './utils'; interface ManagementSetupDependencies { home?: HomePublicPluginSetup; @@ -46,7 +48,23 @@ interface ManagementSetupDependencies { export class ManagementPlugin implements Plugin { private readonly managementSections = new ManagementSectionsService(); - private readonly appUpdater = new BehaviorSubject(() => ({})); + private readonly appUpdater = new BehaviorSubject(() => { + 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, + })), + })); + + return { + searchDeepLinks: deepLinks, + }; + }); private hasAnyEnabledApps = true; diff --git a/src/plugins/maps_legacy/jest.config.js b/src/plugins/maps_legacy/jest.config.js new file mode 100644 index 00000000000000..849bd2957ba628 --- /dev/null +++ b/src/plugins/maps_legacy/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/maps_legacy'], +}; diff --git a/src/plugins/navigation/jest.config.js b/src/plugins/navigation/jest.config.js new file mode 100644 index 00000000000000..bc999a25854de8 --- /dev/null +++ b/src/plugins/navigation/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/navigation'], +}; diff --git a/src/plugins/newsfeed/jest.config.js b/src/plugins/newsfeed/jest.config.js new file mode 100644 index 00000000000000..bf530497bcbad3 --- /dev/null +++ b/src/plugins/newsfeed/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/newsfeed'], +}; diff --git a/src/plugins/region_map/jest.config.js b/src/plugins/region_map/jest.config.js new file mode 100644 index 00000000000000..c0d4e4d40bb3af --- /dev/null +++ b/src/plugins/region_map/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/region_map'], +}; diff --git a/src/plugins/saved_objects/jest.config.js b/src/plugins/saved_objects/jest.config.js new file mode 100644 index 00000000000000..00ab010bc61ba4 --- /dev/null +++ b/src/plugins/saved_objects/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/saved_objects'], +}; diff --git a/src/plugins/saved_objects_management/jest.config.js b/src/plugins/saved_objects_management/jest.config.js new file mode 100644 index 00000000000000..3cedb8c937f5ea --- /dev/null +++ b/src/plugins/saved_objects_management/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/saved_objects_management'], +}; diff --git a/src/plugins/saved_objects_tagging_oss/jest.config.js b/src/plugins/saved_objects_tagging_oss/jest.config.js new file mode 100644 index 00000000000000..7e75b5c5593e7b --- /dev/null +++ b/src/plugins/saved_objects_tagging_oss/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/saved_objects_tagging_oss'], +}; diff --git a/src/plugins/security_oss/jest.config.js b/src/plugins/security_oss/jest.config.js new file mode 100644 index 00000000000000..3bf6ee33d3e485 --- /dev/null +++ b/src/plugins/security_oss/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/security_oss'], +}; diff --git a/src/plugins/share/jest.config.js b/src/plugins/share/jest.config.js new file mode 100644 index 00000000000000..39b048279e73be --- /dev/null +++ b/src/plugins/share/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/share'], +}; diff --git a/src/plugins/telemetry/jest.config.js b/src/plugins/telemetry/jest.config.js new file mode 100644 index 00000000000000..914cea68cd01bc --- /dev/null +++ b/src/plugins/telemetry/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/telemetry'], +}; diff --git a/src/plugins/telemetry/server/fetcher.ts b/src/plugins/telemetry/server/fetcher.ts index a3649f51577ac3..820f2c7c4c4af0 100644 --- a/src/plugins/telemetry/server/fetcher.ts +++ b/src/plugins/telemetry/server/fetcher.ts @@ -17,7 +17,6 @@ * under the License. */ -import moment from 'moment'; import { Observable, Subscription, timer } from 'rxjs'; import { take } from 'rxjs/operators'; // @ts-ignore @@ -213,7 +212,6 @@ export class FetcherTask { private async fetchTelemetry() { return await this.telemetryCollectionManager!.getStats({ unencrypted: false, - timestamp: moment().valueOf(), }); } diff --git a/src/plugins/telemetry/server/index.ts b/src/plugins/telemetry/server/index.ts index e9887456e2f365..326c87a75b0ea5 100644 --- a/src/plugins/telemetry/server/index.ts +++ b/src/plugins/telemetry/server/index.ts @@ -44,7 +44,6 @@ export const plugin = (initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); @@ -109,12 +105,7 @@ export class TelemetryPlugin implements Plugin this.elasticsearchClient, - () => this.savedObjectsService - ); + registerCollection(telemetryCollectionManager); const router = http.createRouter(); registerRoutes({ @@ -138,11 +129,9 @@ export class TelemetryPlugin implements Plugin; * @param {Object} config contains the usageCollection, callCluster (deprecated), the esClient and Saved Objects client scoped to the request or the internal repository, and the kibana request * @param {Object} StatsCollectionContext contains logger and version (string) */ -export const getLocalStats: StatsGetter<{}, TelemetryLocalStats> = async ( +export const getLocalStats: StatsGetter = async ( clustersDetails, config, context diff --git a/src/plugins/telemetry/server/telemetry_collection/index.ts b/src/plugins/telemetry/server/telemetry_collection/index.ts index 40cbf0e4caa1d9..77894091f61330 100644 --- a/src/plugins/telemetry/server/telemetry_collection/index.ts +++ b/src/plugins/telemetry/server/telemetry_collection/index.ts @@ -24,6 +24,5 @@ export { buildDataTelemetryPayload, } from './get_data_telemetry'; export { getLocalStats, TelemetryLocalStats } from './get_local_stats'; -export { getLocalLicense } from './get_local_license'; export { getClusterUuids } from './get_cluster_stats'; export { registerCollection } from './register_collection'; diff --git a/src/plugins/telemetry/server/telemetry_collection/register_collection.ts b/src/plugins/telemetry/server/telemetry_collection/register_collection.ts index 27ca5ae7465123..fac315b01493eb 100644 --- a/src/plugins/telemetry/server/telemetry_collection/register_collection.ts +++ b/src/plugins/telemetry/server/telemetry_collection/register_collection.ts @@ -36,27 +36,17 @@ * under the License. */ -import { ILegacyClusterClient, SavedObjectsServiceStart } from 'kibana/server'; import { TelemetryCollectionManagerPluginSetup } from 'src/plugins/telemetry_collection_manager/server'; -import { IClusterClient } from '../../../../../src/core/server'; import { getLocalStats } from './get_local_stats'; import { getClusterUuids } from './get_cluster_stats'; -import { getLocalLicense } from './get_local_license'; export function registerCollection( - telemetryCollectionManager: TelemetryCollectionManagerPluginSetup, - esCluster: ILegacyClusterClient, - esClientGetter: () => IClusterClient | undefined, - soServiceGetter: () => SavedObjectsServiceStart | undefined + telemetryCollectionManager: TelemetryCollectionManagerPluginSetup ) { - telemetryCollectionManager.setCollection({ - esCluster, - esClientGetter, - soServiceGetter, + telemetryCollectionManager.setCollectionStrategy({ title: 'local', priority: 0, statsGetter: getLocalStats, clusterDetailsGetter: getClusterUuids, - licenseGetter: getLocalLicense, }); } diff --git a/src/plugins/telemetry_collection_manager/jest.config.js b/src/plugins/telemetry_collection_manager/jest.config.js new file mode 100644 index 00000000000000..9278ca21d7bc23 --- /dev/null +++ b/src/plugins/telemetry_collection_manager/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/telemetry_collection_manager'], +}; diff --git a/src/plugins/telemetry_collection_manager/server/index.ts b/src/plugins/telemetry_collection_manager/server/index.ts index 36ab64731fe582..de2080059c80b7 100644 --- a/src/plugins/telemetry_collection_manager/server/index.ts +++ b/src/plugins/telemetry_collection_manager/server/index.ts @@ -30,13 +30,11 @@ export function plugin(initializerContext: PluginInitializerContext) { export { TelemetryCollectionManagerPluginSetup, TelemetryCollectionManagerPluginStart, - ESLicense, StatsCollectionConfig, StatsGetter, StatsGetterConfig, StatsCollectionContext, ClusterDetails, ClusterDetailsGetter, - LicenseGetter, UsageStatsPayload, } from './types'; diff --git a/src/plugins/telemetry_collection_manager/server/plugin.ts b/src/plugins/telemetry_collection_manager/server/plugin.ts index bc33e9fbc82c5b..a135f4b115b215 100644 --- a/src/plugins/telemetry_collection_manager/server/plugin.ts +++ b/src/plugins/telemetry_collection_manager/server/plugin.ts @@ -26,14 +26,15 @@ import { Logger, IClusterClient, SavedObjectsServiceStart, -} from '../../../core/server'; + ILegacyClusterClient, +} from 'src/core/server'; import { TelemetryCollectionManagerPluginSetup, TelemetryCollectionManagerPluginStart, BasicStatsPayload, - CollectionConfig, - Collection, + CollectionStrategyConfig, + CollectionStrategy, StatsGetterConfig, StatsCollectionConfig, UsageStatsPayload, @@ -49,9 +50,12 @@ interface TelemetryCollectionPluginsDepsSetup { export class TelemetryCollectionManagerPlugin implements Plugin { private readonly logger: Logger; - private readonly collections: Array> = []; + private collectionStrategy: CollectionStrategy | undefined; private usageGetterMethodPriority = -1; private usageCollection?: UsageCollectionSetup; + private legacyElasticsearchClient?: ILegacyClusterClient; + private elasticsearchClient?: IClusterClient; + private savedObjectsService?: SavedObjectsServiceStart; private readonly isDistributable: boolean; private readonly version: string; @@ -65,7 +69,7 @@ export class TelemetryCollectionManagerPlugin this.usageCollection = usageCollection; return { - setCollection: this.setCollection.bind(this), + setCollectionStrategy: this.setCollectionStrategy.bind(this), getOptInStats: this.getOptInStats.bind(this), getStats: this.getStats.bind(this), areAllCollectorsReady: this.areAllCollectorsReady.bind(this), @@ -73,8 +77,11 @@ export class TelemetryCollectionManagerPlugin } public start(core: CoreStart) { + this.legacyElasticsearchClient = core.elasticsearch.legacy.client; // TODO: Remove when all the collectors have migrated + this.elasticsearchClient = core.elasticsearch.client; + this.savedObjectsService = core.savedObjects; + return { - setCollection: this.setCollection.bind(this), getOptInStats: this.getOptInStats.bind(this), getStats: this.getStats.bind(this), areAllCollectorsReady: this.areAllCollectorsReady.bind(this), @@ -83,19 +90,10 @@ export class TelemetryCollectionManagerPlugin public stop() {} - private setCollection, T extends BasicStatsPayload>( - collectionConfig: CollectionConfig + private setCollectionStrategy( + collectionConfig: CollectionStrategyConfig ) { - const { - title, - priority, - esCluster, - esClientGetter, - soServiceGetter, - statsGetter, - clusterDetailsGetter, - licenseGetter, - } = collectionConfig; + const { title, priority, statsGetter, clusterDetailsGetter } = collectionConfig; if (typeof priority !== 'number') { throw new Error('priority must be set.'); @@ -108,78 +106,58 @@ export class TelemetryCollectionManagerPlugin if (!statsGetter) { throw Error('Stats getter method not set.'); } - if (!esCluster) { - throw Error('esCluster name must be set for the getCluster method.'); - } - if (!esClientGetter) { - throw Error('esClientGetter method not set.'); - } - if (!soServiceGetter) { - throw Error('soServiceGetter method not set.'); - } if (!clusterDetailsGetter) { throw Error('Cluster UUIds method is not set.'); } - if (!licenseGetter) { - throw Error('License getter method not set.'); - } - this.collections.unshift({ - licenseGetter, - statsGetter, - clusterDetailsGetter, - esCluster, - title, - esClientGetter, - soServiceGetter, - }); + this.logger.debug(`Setting ${title} as the telemetry collection strategy`); + + // Overwrite the collection strategy + this.collectionStrategy = collectionConfig; this.usageGetterMethodPriority = priority; } } + /** + * Returns the context to provide to the Collection Strategies. + * It may return undefined if the ES and SO clients are not initialised yet. + * @param config {@link StatsGetterConfig} + * @param usageCollection {@link UsageCollectionSetup} + * @private + */ private getStatsCollectionConfig( config: StatsGetterConfig, - collection: Collection, - collectionEsClient: IClusterClient, - collectionSoService: SavedObjectsServiceStart, usageCollection: UsageCollectionSetup - ): StatsCollectionConfig { - const { request } = config; - + ): StatsCollectionConfig | undefined { const callCluster = config.unencrypted - ? collection.esCluster.asScoped(request).callAsCurrentUser - : collection.esCluster.callAsInternalUser; + ? this.legacyElasticsearchClient?.asScoped(config.request).callAsCurrentUser + : this.legacyElasticsearchClient?.callAsInternalUser; // Scope the new elasticsearch Client appropriately and pass to the stats collection config const esClient = config.unencrypted - ? collectionEsClient.asScoped(config.request).asCurrentUser - : collectionEsClient.asInternalUser; + ? this.elasticsearchClient?.asScoped(config.request).asCurrentUser + : this.elasticsearchClient?.asInternalUser; // Scope the saved objects client appropriately and pass to the stats collection config const soClient = config.unencrypted - ? collectionSoService.getScopedClient(config.request) - : collectionSoService.createInternalRepository(); + ? this.savedObjectsService?.getScopedClient(config.request) + : this.savedObjectsService?.createInternalRepository(); // Provide the kibanaRequest so opted-in plugins can scope their custom clients only if the request is not encrypted - const kibanaRequest = config.unencrypted ? request : void 0; + const kibanaRequest = config.unencrypted ? config.request : void 0; - return { callCluster, usageCollection, esClient, soClient, kibanaRequest }; + if (callCluster && esClient && soClient) { + return { callCluster, usageCollection, esClient, soClient, kibanaRequest }; + } } private async getOptInStats(optInStatus: boolean, config: StatsGetterConfig) { if (!this.usageCollection) { return []; } - for (const collection of this.collections) { - // first fetch the client and make sure it's not undefined. - const collectionEsClient = collection.esClientGetter(); - const collectionSoService = collection.soServiceGetter(); - if (collectionEsClient !== undefined && collectionSoService !== undefined) { - const statsCollectionConfig = this.getStatsCollectionConfig( - config, - collection, - collectionEsClient, - collectionSoService, - this.usageCollection - ); + const collection = this.collectionStrategy; + if (collection) { + // Build the context (clients and others) to send to the CollectionStrategies + const statsCollectionConfig = this.getStatsCollectionConfig(config, this.usageCollection); + if (statsCollectionConfig) { try { const optInStats = await this.getOptInStatsForCollection( collection, @@ -194,8 +172,9 @@ export class TelemetryCollectionManagerPlugin return encryptTelemetry(optInStats, { useProdKey: this.isDistributable }); } } catch (err) { - this.logger.debug(`Failed to collect any opt in stats with registered collections.`); - // swallow error to try next collection; + this.logger.debug( + `Failed to collect any opt in stats with collection ${collection.title}.` + ); } } } @@ -203,19 +182,18 @@ export class TelemetryCollectionManagerPlugin return []; } - private areAllCollectorsReady = async () => { + private async areAllCollectorsReady() { return await this.usageCollection?.areAllCollectorsReady(); - }; + } private getOptInStatsForCollection = async ( - collection: Collection, + collection: CollectionStrategy, optInStatus: boolean, statsCollectionConfig: StatsCollectionConfig ) => { const context: StatsCollectionContext = { logger: this.logger.get(collection.title), version: this.version, - ...collection.customContext, }; const clustersDetails = await collection.clusterDetailsGetter(statsCollectionConfig, context); @@ -229,17 +207,11 @@ export class TelemetryCollectionManagerPlugin if (!this.usageCollection) { return []; } - for (const collection of this.collections) { - const collectionEsClient = collection.esClientGetter(); - const collectionSavedObjectsService = collection.soServiceGetter(); - if (collectionEsClient !== undefined && collectionSavedObjectsService !== undefined) { - const statsCollectionConfig = this.getStatsCollectionConfig( - config, - collection, - collectionEsClient, - collectionSavedObjectsService, - this.usageCollection - ); + const collection = this.collectionStrategy; + if (collection) { + // Build the context (clients and others) to send to the CollectionStrategies + const statsCollectionConfig = this.getStatsCollectionConfig(config, this.usageCollection); + if (statsCollectionConfig) { try { const usageData = await this.getUsageForCollection(collection, statsCollectionConfig); if (usageData.length) { @@ -256,7 +228,6 @@ export class TelemetryCollectionManagerPlugin this.logger.debug( `Failed to collect any usage with registered collection ${collection.title}.` ); - // swallow error to try next collection; } } } @@ -265,34 +236,24 @@ export class TelemetryCollectionManagerPlugin } private async getUsageForCollection( - collection: Collection, + collection: CollectionStrategy, statsCollectionConfig: StatsCollectionConfig ): Promise { const context: StatsCollectionContext = { logger: this.logger.get(collection.title), version: this.version, - ...collection.customContext, }; const clustersDetails = await collection.clusterDetailsGetter(statsCollectionConfig, context); if (clustersDetails.length === 0) { - // don't bother doing a further lookup, try next collection. + // don't bother doing a further lookup. return []; } - const [stats, licenses] = await Promise.all([ - collection.statsGetter(clustersDetails, statsCollectionConfig, context), - collection.licenseGetter(clustersDetails, statsCollectionConfig, context), - ]); + const stats = await collection.statsGetter(clustersDetails, statsCollectionConfig, context); - return stats.map((stat) => { - const license = licenses[stat.cluster_uuid]; - return { - collectionSource: collection.title, - ...(license ? { license } : {}), - ...stat, - }; - }); + // Add the `collectionSource` to the resulting payload + return stats.map((stat) => ({ collectionSource: collection.title, ...stat })); } } diff --git a/src/plugins/telemetry_collection_manager/server/types.ts b/src/plugins/telemetry_collection_manager/server/types.ts index a6cf1a9e5aaf95..05641d50645931 100644 --- a/src/plugins/telemetry_collection_manager/server/types.ts +++ b/src/plugins/telemetry_collection_manager/server/types.ts @@ -19,21 +19,18 @@ import { LegacyAPICaller, + ElasticsearchClient, Logger, KibanaRequest, - ILegacyClusterClient, - IClusterClient, - SavedObjectsServiceStart, SavedObjectsClientContract, ISavedObjectsRepository, -} from 'kibana/server'; +} from 'src/core/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { ElasticsearchClient } from '../../../../src/core/server'; import { TelemetryCollectionManagerPlugin } from './plugin'; export interface TelemetryCollectionManagerPluginSetup { - setCollection: , T extends BasicStatsPayload>( - collectionConfig: CollectionConfig + setCollectionStrategy: ( + collectionConfig: CollectionStrategyConfig ) => void; getOptInStats: TelemetryCollectionManagerPlugin['getOptInStats']; getStats: TelemetryCollectionManagerPlugin['getStats']; @@ -41,9 +38,6 @@ export interface TelemetryCollectionManagerPluginSetup { } export interface TelemetryCollectionManagerPluginStart { - setCollection: , T extends BasicStatsPayload>( - collectionConfig: CollectionConfig - ) => void; getOptInStats: TelemetryCollectionManagerPlugin['getOptInStats']; getStats: TelemetryCollectionManagerPlugin['getStats']; areAllCollectorsReady: TelemetryCollectionManagerPlugin['areAllCollectorsReady']; @@ -91,74 +85,34 @@ export interface BasicStatsPayload { } export interface UsageStatsPayload extends BasicStatsPayload { - license?: ESLicense; collectionSource: string; } -// From https://www.elastic.co/guide/en/elasticsearch/reference/current/get-license.html -export interface ESLicense { - status: string; - uid: string; - type: string; - issue_date: string; - issue_date_in_millis: number; - expiry_date: string; - expirty_date_in_millis: number; - max_nodes: number; - issued_to: string; - issuer: string; - start_date_in_millis: number; -} - export interface StatsCollectionContext { logger: Logger | Console; version: string; } export type StatsGetterConfig = UnencryptedStatsGetterConfig | EncryptedStatsGetterConfig; -export type ClusterDetailsGetter = {}> = ( +export type ClusterDetailsGetter = ( config: StatsCollectionConfig, - context: StatsCollectionContext & CustomContext + context: StatsCollectionContext ) => Promise; -export type StatsGetter< - CustomContext extends Record = {}, - T extends BasicStatsPayload = BasicStatsPayload -> = ( +export type StatsGetter = ( clustersDetails: ClusterDetails[], config: StatsCollectionConfig, - context: StatsCollectionContext & CustomContext + context: StatsCollectionContext ) => Promise; -export type LicenseGetter = {}> = ( - clustersDetails: ClusterDetails[], - config: StatsCollectionConfig, - context: StatsCollectionContext & CustomContext -) => Promise<{ [clusterUuid: string]: ESLicense | undefined }>; -export interface CollectionConfig< - CustomContext extends Record = {}, - T extends BasicStatsPayload = BasicStatsPayload -> { +export interface CollectionStrategyConfig { title: string; priority: number; - esCluster: ILegacyClusterClient; - esClientGetter: () => IClusterClient | undefined; // --> by now we know that the client getter will return the IClusterClient but we assure that through a code check - soServiceGetter: () => SavedObjectsServiceStart | undefined; // --> by now we know that the service getter will return the SavedObjectsServiceStart but we assure that through a code check - statsGetter: StatsGetter; - clusterDetailsGetter: ClusterDetailsGetter; - licenseGetter: LicenseGetter; - customContext?: CustomContext; + statsGetter: StatsGetter; + clusterDetailsGetter: ClusterDetailsGetter; } -export interface Collection< - CustomContext extends Record = {}, - T extends BasicStatsPayload = BasicStatsPayload -> { - customContext?: CustomContext; - statsGetter: StatsGetter; - licenseGetter: LicenseGetter; - clusterDetailsGetter: ClusterDetailsGetter; - esCluster: ILegacyClusterClient; - esClientGetter: () => IClusterClient | undefined; // the collection could still return undefined for the es client getter. - soServiceGetter: () => SavedObjectsServiceStart | undefined; // the collection could still return undefined for the Saved Objects Service getter. +export interface CollectionStrategy { + statsGetter: StatsGetter; + clusterDetailsGetter: ClusterDetailsGetter; title: string; } diff --git a/src/plugins/telemetry_management_section/jest.config.js b/src/plugins/telemetry_management_section/jest.config.js new file mode 100644 index 00000000000000..a38fa84b08afcb --- /dev/null +++ b/src/plugins/telemetry_management_section/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/telemetry_management_section'], +}; diff --git a/src/plugins/tile_map/jest.config.js b/src/plugins/tile_map/jest.config.js new file mode 100644 index 00000000000000..9a89247b4f782e --- /dev/null +++ b/src/plugins/tile_map/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/tile_map'], +}; diff --git a/src/plugins/ui_actions/jest.config.js b/src/plugins/ui_actions/jest.config.js new file mode 100644 index 00000000000000..3a7de575ea248d --- /dev/null +++ b/src/plugins/ui_actions/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/ui_actions'], +}; diff --git a/src/plugins/url_forwarding/jest.config.js b/src/plugins/url_forwarding/jest.config.js new file mode 100644 index 00000000000000..9dcbfccfcf90ad --- /dev/null +++ b/src/plugins/url_forwarding/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/url_forwarding'], +}; diff --git a/src/plugins/usage_collection/README.md b/src/plugins/usage_collection/README.md index 5e6ed901c76474..33f7993f142338 100644 --- a/src/plugins/usage_collection/README.md +++ b/src/plugins/usage_collection/README.md @@ -140,6 +140,98 @@ export function registerMyPluginUsageCollector( } ``` +## Tracking interactions with incrementCounter +There are several ways to collect data that can provide insight into how users +use your plugin or specific features. For tracking user interactions the +`SavedObjectsRepository` provided by Core provides a useful `incrementCounter` +method which can be used to increment one or more counter fields in a +document. Examples of interactions include tracking: + - the number of API calls + - the number of times users installed and uninstalled the sample datasets + +When using `incrementCounter` for collecting usage data, you need to ensure +that usage collection happens on a best-effort basis and doesn't +negatively affect your plugin or users (see the example): + - Swallow any exceptions thrown from the incrementCounter method and log + a message in development. + - Don't block your application on the incrementCounter method (e.g. + don't use `await`) + - Set the `refresh` option to false to prevent unecessary index refreshes + which slows down Elasticsearch performance + + +Note: for brevity the following example does not follow Kibana's conventions +for structuring your plugin code. +```ts +// src/plugins/dashboard/server/plugin.ts + +import { PluginInitializerContext, Plugin, CoreStart, CoreSetup } from '../../src/core/server'; + +export class DashboardPlugin implements Plugin { + private readonly logger: Logger; + private readonly isDevEnvironment: boolean; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + this.isDevEnvironment = initializerContext.env.cliArgs.dev; + } + public setup(core) { + // Register a saved object type to store our usage counters + core.savedObjects.registerType({ + // Don't expose this saved object type via the saved objects HTTP API + hidden: true, + mappings: { + // Since we're not querying or aggregating over our counter documents + // we don't define any fields. + dynamic: false, + properties: {}, + }, + name: 'dashboard_usage_counters', + namespaceType: 'single', + }); + } + public start(core) { + const repository = core.savedObjects.createInternalRepository(['dashboard_usage_counters']); + // Initialize all the counter fields to 0 when our plugin starts + // NOTE: Usage collection happens on a best-effort basis, so we don't + // `await` the promise returned by `incrementCounter` and we swallow any + // exceptions in production. + repository + .incrementCounter('dashboard_usage_counters', 'dashboard_usage_counters', [ + 'apiCalls', + 'settingToggled', + ], {refresh: false, initialize: true}) + .catch((e) => (this.isDevEnvironment ? this.logger.error(e) : e)); + + const router = core.http.createRouter(); + + router.post( + { + path: `api/v1/dashboard/counters/{counter}`, + validate: { + params: schema.object({ + counter: schema.oneOf([schema.literal('apiCalls'), schema.literal('settingToggled')]), + }), + }, + }, + async (context, request, response) => { + request.params.id + + // NOTE: Usage collection happens on a best-effort basis, so we don't + // `await` the promise returned by `incrementCounter` and we swallow any + // exceptions in production. + repository + .incrementCounter('dashboard_usage_counters', 'dashboard_usage_counters', [ + counter + ], {refresh: false}) + .catch((e) => (this.isDevEnvironement ? this.logger.error(e) : e)); + + return response.ok(); + } + ); + } +} + ## Schema Field The `schema` field is a proscribed data model assists with detecting changes in usage collector payloads. To define the collector schema add a schema field that specifies every possible field reported when registering the collector. Whenever the `schema` field is set or changed please run `node scripts/telemetry_check.js --fix` to update the stored schema json files. @@ -200,7 +292,6 @@ export const myCollector = makeUsageCollector({ }, }); ``` - ## Update the telemetry payload and telemetry cluster field mappings There is a module in the telemetry service that creates the payload of data that gets sent up to the telemetry cluster. diff --git a/src/plugins/usage_collection/jest.config.js b/src/plugins/usage_collection/jest.config.js new file mode 100644 index 00000000000000..89b7fc70fd620e --- /dev/null +++ b/src/plugins/usage_collection/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/usage_collection'], +}; diff --git a/src/plugins/usage_collection/server/report/store_report.test.ts b/src/plugins/usage_collection/server/report/store_report.test.ts index d8327eb834e12c..939c37764ab0e9 100644 --- a/src/plugins/usage_collection/server/report/store_report.test.ts +++ b/src/plugins/usage_collection/server/report/store_report.test.ts @@ -69,7 +69,7 @@ describe('store_report', () => { expect(savedObjectClient.incrementCounter).toHaveBeenCalledWith( 'ui-metric', 'test-app-name:test-event-name', - 'count' + ['count'] ); expect(savedObjectClient.bulkCreate).toHaveBeenCalledWith([ { diff --git a/src/plugins/usage_collection/server/report/store_report.ts b/src/plugins/usage_collection/server/report/store_report.ts index d9aac23fd1ff09..a54d3d226d7367 100644 --- a/src/plugins/usage_collection/server/report/store_report.ts +++ b/src/plugins/usage_collection/server/report/store_report.ts @@ -50,7 +50,7 @@ export async function storeReport( const savedObjectId = `${appName}:${eventName}`; return { saved_objects: [ - await internalRepository.incrementCounter('ui-metric', savedObjectId, 'count'), + await internalRepository.incrementCounter('ui-metric', savedObjectId, ['count']), ], }; }), diff --git a/src/plugins/vis_default_editor/jest.config.js b/src/plugins/vis_default_editor/jest.config.js new file mode 100644 index 00000000000000..618f9734fb54c5 --- /dev/null +++ b/src/plugins/vis_default_editor/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/vis_default_editor'], +}; diff --git a/src/plugins/vis_type_markdown/jest.config.js b/src/plugins/vis_type_markdown/jest.config.js new file mode 100644 index 00000000000000..bff1b12641c92e --- /dev/null +++ b/src/plugins/vis_type_markdown/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/vis_type_markdown'], +}; diff --git a/src/plugins/vis_type_metric/jest.config.js b/src/plugins/vis_type_metric/jest.config.js new file mode 100644 index 00000000000000..5c50fc5f4368ec --- /dev/null +++ b/src/plugins/vis_type_metric/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/vis_type_metric'], +}; diff --git a/src/plugins/vis_type_table/jest.config.js b/src/plugins/vis_type_table/jest.config.js new file mode 100644 index 00000000000000..3aa02089df0122 --- /dev/null +++ b/src/plugins/vis_type_table/jest.config.js @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/vis_type_table'], + testRunner: 'jasmine2', +}; diff --git a/src/plugins/vis_type_tagcloud/jest.config.js b/src/plugins/vis_type_tagcloud/jest.config.js new file mode 100644 index 00000000000000..5419ca05cca846 --- /dev/null +++ b/src/plugins/vis_type_tagcloud/jest.config.js @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/vis_type_tagcloud'], + testRunner: 'jasmine2', +}; diff --git a/src/plugins/vis_type_timelion/jest.config.js b/src/plugins/vis_type_timelion/jest.config.js new file mode 100644 index 00000000000000..eae12936427f4a --- /dev/null +++ b/src/plugins/vis_type_timelion/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/vis_type_timelion'], +}; diff --git a/src/plugins/vis_type_timeseries/jest.config.js b/src/plugins/vis_type_timeseries/jest.config.js new file mode 100644 index 00000000000000..16c001e5981889 --- /dev/null +++ b/src/plugins/vis_type_timeseries/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/vis_type_timeseries'], +}; diff --git a/src/plugins/vis_type_timeseries/public/metrics_type.ts b/src/plugins/vis_type_timeseries/public/metrics_type.ts index 2b75f696206293..41dc26c8c130d6 100644 --- a/src/plugins/vis_type_timeseries/public/metrics_type.ts +++ b/src/plugins/vis_type_timeseries/public/metrics_type.ts @@ -24,7 +24,6 @@ import { PANEL_TYPES } from '../common/panel_types'; import { toExpressionAst } from './to_ast'; import { VIS_EVENT_TO_TRIGGER, VisGroups, VisParams } from '../../visualizations/public'; import { getDataStart } from './services'; -import { INDEXES_SEPARATOR } from '../common/constants'; export const metricsVisDefinition = { name: 'metrics', @@ -84,18 +83,7 @@ export const metricsVisDefinition = { inspectorAdapters: {}, getUsedIndexPattern: async (params: VisParams) => { const { indexPatterns } = getDataStart(); - const indexes: string = params.index_pattern; - if (indexes) { - const cachedIndexes = await indexPatterns.getIdsWithTitle(); - const ids = indexes - .split(INDEXES_SEPARATOR) - .map((title) => cachedIndexes.find((i) => i.title === title)?.id) - .filter((id) => id); - - return Promise.all(ids.map((id) => indexPatterns.get(id!))); - } - - return []; + return params.index_pattern ? await indexPatterns.find(params.index_pattern) : []; }, }; diff --git a/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts b/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts index 0969174c7143c0..46f46eaa3026f3 100644 --- a/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts +++ b/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts @@ -83,7 +83,7 @@ export class ValidationTelemetryService implements Plugin/src/plugins/vis_type_vega'], +}; diff --git a/src/plugins/vis_type_vega/public/data_model/es_query_parser.ts b/src/plugins/vis_type_vega/public/data_model/es_query_parser.ts index 1aac8e25d5c738..79eb049fb6dcce 100644 --- a/src/plugins/vis_type_vega/public/data_model/es_query_parser.ts +++ b/src/plugins/vis_type_vega/public/data_model/es_query_parser.ts @@ -226,6 +226,7 @@ export class EsQueryParser { const requestObject = requests.find((item) => getRequestName(item, index) === data.name); if (requestObject) { + requestObject.dataObject.url = requestObject.url; requestObject.dataObject.values = data.rawResponse; } }); diff --git a/src/plugins/vis_type_vega/public/data_model/types.ts b/src/plugins/vis_type_vega/public/data_model/types.ts index acd35e17476242..3bfe218099577d 100644 --- a/src/plugins/vis_type_vega/public/data_model/types.ts +++ b/src/plugins/vis_type_vega/public/data_model/types.ts @@ -82,8 +82,9 @@ interface Projection { name: string; } -interface RequestDataObject { +interface RequestDataObject { name?: string; + url?: TUrlData; values: SearchResponse; } @@ -186,7 +187,7 @@ export interface CacheBounds { max: number; } -interface Requests { +interface Requests> { url: TUrlData; name: string; dataObject: TRequestDataObject; diff --git a/src/plugins/vis_type_vega/public/data_model/vega_parser.test.js b/src/plugins/vis_type_vega/public/data_model/vega_parser.test.js index 9fb80c6a1b19dc..eb666d65b86706 100644 --- a/src/plugins/vis_type_vega/public/data_model/vega_parser.test.js +++ b/src/plugins/vis_type_vega/public/data_model/vega_parser.test.js @@ -185,21 +185,21 @@ describe('VegaParser._resolveEsQueries', () => { 'es', check( { data: { name: 'requestId', url: { index: 'a' }, x: 1 } }, - { data: { name: 'requestId', values: [42], x: 1 } } + { data: { name: 'requestId', url: { index: 'a', body: {} }, values: [42], x: 1 } } ) ); test( 'es 2', check( { data: { name: 'requestId', url: { '%type%': 'elasticsearch', index: 'a' } } }, - { data: { name: 'requestId', values: [42] } } + { data: { name: 'requestId', url: { index: 'a', body: {} }, values: [42] } } ) ); test( 'es arr', check( { arr: [{ data: { name: 'requestId', url: { index: 'a' }, x: 1 } }] }, - { arr: [{ data: { name: 'requestId', values: [42], x: 1 } }] } + { arr: [{ data: { name: 'requestId', url: { index: 'a', body: {} }, values: [42], x: 1 } }] } ) ); test( diff --git a/src/plugins/vis_type_vega/public/lib/extract_index_pattern.test.ts b/src/plugins/vis_type_vega/public/lib/extract_index_pattern.test.ts new file mode 100644 index 00000000000000..a13428d539ad98 --- /dev/null +++ b/src/plugins/vis_type_vega/public/lib/extract_index_pattern.test.ts @@ -0,0 +1,125 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { dataPluginMock } from '../../../data/public/mocks'; +import { extractIndexPatternsFromSpec } from './extract_index_pattern'; +import { setData } from '../services'; + +import type { VegaSpec } from '../data_model/types'; + +const getMockedSpec = (mockedObj: any) => (mockedObj as unknown) as VegaSpec; + +describe('extractIndexPatternsFromSpec', () => { + const dataStart = dataPluginMock.createStartContract(); + + beforeAll(() => { + setData(dataStart); + }); + + test('should not throw errors if no index is specified', async () => { + const spec = getMockedSpec({ + data: {}, + }); + + const indexes = await extractIndexPatternsFromSpec(spec); + + expect(indexes).toMatchInlineSnapshot(`Array []`); + }); + + test('should extract single index pattern', async () => { + const spec = getMockedSpec({ + data: { + url: { + index: 'test', + }, + }, + }); + + const indexes = await extractIndexPatternsFromSpec(spec); + + expect(indexes).toMatchInlineSnapshot(` + Array [ + Object { + "id": "test", + "title": "test", + }, + ] + `); + }); + + test('should extract multiple index patterns', async () => { + const spec = getMockedSpec({ + data: [ + { + url: { + index: 'test1', + }, + }, + { + url: { + index: 'test2', + }, + }, + ], + }); + + const indexes = await extractIndexPatternsFromSpec(spec); + + expect(indexes).toMatchInlineSnapshot(` + Array [ + Object { + "id": "test1", + "title": "test1", + }, + Object { + "id": "test2", + "title": "test2", + }, + ] + `); + }); + + test('should filter empty values', async () => { + const spec = getMockedSpec({ + data: [ + { + url: { + wrong: 'wrong', + }, + }, + { + url: { + index: 'ok', + }, + }, + ], + }); + + const indexes = await extractIndexPatternsFromSpec(spec); + + expect(indexes).toMatchInlineSnapshot(` + Array [ + Object { + "id": "ok", + "title": "ok", + }, + ] + `); + }); +}); diff --git a/src/dev/jest/config.js b/src/plugins/vis_type_vega/public/lib/extract_index_pattern.ts similarity index 51% rename from src/dev/jest/config.js rename to src/plugins/vis_type_vega/public/lib/extract_index_pattern.ts index 93d7218b11c281..12cbd6f7ebbfaa 100644 --- a/src/dev/jest/config.js +++ b/src/plugins/vis_type_vega/public/lib/extract_index_pattern.ts @@ -17,27 +17,31 @@ * under the License. */ -export default { - preset: '@kbn/test', - rootDir: '../../..', - roots: [ - '/src/plugins', - '/src/legacy/ui', - '/src/core', - '/src/legacy/server', - '/src/cli', - '/src/cli_keystore', - '/src/cli_encryption_keys', - '/src/cli_plugin', - '/packages/kbn-test/target/functional_test_runner', - '/src/dev', - '/src/optimize', - '/src/legacy/utils', - '/src/setup_node_env', - '/packages', - '/src/test_utils', - '/test/functional/services/remote', - '/src/dev/code_coverage/ingest_coverage', - ], - testRunner: 'jasmine2', +import { flatten } from 'lodash'; +import { getData } from '../services'; + +import type { Data, VegaSpec } from '../data_model/types'; +import type { IndexPattern } from '../../../data/public'; + +export const extractIndexPatternsFromSpec = async (spec: VegaSpec) => { + const { indexPatterns } = getData(); + let data: Data[] = []; + + if (Array.isArray(spec.data)) { + data = spec.data; + } else if (spec.data) { + data = [spec.data]; + } + + return flatten( + await Promise.all( + data.reduce>>((accumulator, currentValue) => { + if (currentValue.url?.index) { + accumulator.push(indexPatterns.find(currentValue.url.index)); + } + + return accumulator; + }, []) + ) + ); }; diff --git a/src/plugins/vis_type_vega/public/plugin.ts b/src/plugins/vis_type_vega/public/plugin.ts index 04481685c841ba..55a69ab11966c6 100644 --- a/src/plugins/vis_type_vega/public/plugin.ts +++ b/src/plugins/vis_type_vega/public/plugin.ts @@ -25,7 +25,6 @@ import { Setup as InspectorSetup } from '../../inspector/public'; import { setNotifications, setData, - setSavedObjects, setInjectedVars, setUISettings, setMapsLegacyConfig, @@ -100,7 +99,6 @@ export class VegaPlugin implements Plugin, void> { public start(core: CoreStart, { data }: VegaPluginStartDependencies) { setNotifications(core.notifications); - setSavedObjects(core.savedObjects); setData(data); setInjectedMetadata(core.injectedMetadata); } diff --git a/src/plugins/vis_type_vega/public/services.ts b/src/plugins/vis_type_vega/public/services.ts index 455fe67dbc4233..43856c83248477 100644 --- a/src/plugins/vis_type_vega/public/services.ts +++ b/src/plugins/vis_type_vega/public/services.ts @@ -17,12 +17,7 @@ * under the License. */ -import { - CoreStart, - SavedObjectsStart, - NotificationsStart, - IUiSettingsClient, -} from 'src/core/public'; +import { CoreStart, NotificationsStart, IUiSettingsClient } from 'src/core/public'; import { DataPublicPluginStart } from '../../data/public'; import { createGetterSetter } from '../../kibana_utils/public'; @@ -40,10 +35,6 @@ export const [getInjectedMetadata, setInjectedMetadata] = createGetterSetter< CoreStart['injectedMetadata'] >('InjectedMetadata'); -export const [getSavedObjects, setSavedObjects] = createGetterSetter( - 'SavedObjects' -); - export const [getInjectedVars, setInjectedVars] = createGetterSetter<{ enableExternalUrls: boolean; emsTileLayerId: unknown; diff --git a/src/plugins/vis_type_vega/public/vega_type.ts b/src/plugins/vis_type_vega/public/vega_type.ts index 2211abb54aa93f..d81bfe02389e2f 100644 --- a/src/plugins/vis_type_vega/public/vega_type.ts +++ b/src/plugins/vis_type_vega/public/vega_type.ts @@ -18,19 +18,24 @@ */ import { i18n } from '@kbn/i18n'; -import { BaseVisTypeOptions } from 'src/plugins/visualizations/public'; +import { parse } from 'hjson'; +import type { BaseVisTypeOptions } from 'src/plugins/visualizations/public'; + import { DefaultEditorSize } from '../../vis_default_editor/public'; -import { VegaVisualizationDependencies } from './plugin'; +import type { VegaVisualizationDependencies } from './plugin'; import { createVegaRequestHandler } from './vega_request_handler'; import { getDefaultSpec } from './default_spec'; +import { extractIndexPatternsFromSpec } from './lib/extract_index_pattern'; import { createInspectorAdapters } from './vega_inspector'; import { VIS_EVENT_TO_TRIGGER, VisGroups } from '../../visualizations/public'; import { toExpressionAst } from './to_ast'; -import { VisParams } from './vega_fn'; import { getInfoMessage } from './components/experimental_map_vis_info'; import { VegaVisEditorComponent } from './components/vega_vis_editor_lazy'; +import type { VegaSpec } from './data_model/types'; +import type { VisParams } from './vega_fn'; + export const createVegaTypeDefinition = ( dependencies: VegaVisualizationDependencies ): BaseVisTypeOptions => { @@ -68,6 +73,16 @@ export const createVegaTypeDefinition = ( getSupportedTriggers: () => { return [VIS_EVENT_TO_TRIGGER.applyFilter]; }, + getUsedIndexPattern: async (visParams) => { + try { + const spec = parse(visParams.spec, { legacyRoot: false, keepWsc: true }); + + return extractIndexPatternsFromSpec(spec as VegaSpec); + } catch (e) { + // spec is invalid + } + return []; + }, inspectorAdapters: createInspectorAdapters, }; }; diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js index 25ea77ddbccb42..10f08edef1aa61 100644 --- a/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js +++ b/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js @@ -27,7 +27,8 @@ import { i18n } from '@kbn/i18n'; import { TooltipHandler } from './vega_tooltip'; import { esFilters } from '../../../data/public'; -import { getEnableExternalUrls } from '../services'; +import { getEnableExternalUrls, getData } from '../services'; +import { extractIndexPatternsFromSpec } from '../lib/extract_index_pattern'; vega.scheme('elastic', euiPaletteColorBlind()); @@ -65,7 +66,6 @@ export class VegaBaseView { this._filterManager = opts.filterManager; this._fireEvent = opts.fireEvent; this._timefilter = opts.timefilter; - this._findIndex = opts.findIndex; this._view = null; this._vegaViewConfig = null; this._$messages = null; @@ -127,6 +127,48 @@ export class VegaBaseView { } } + /** + * Find index pattern by its title, if not given, gets it from spec or a defaults one + * @param {string} [index] + * @returns {Promise} index id + */ + async findIndex(index) { + const { indexPatterns } = getData(); + let idxObj; + + if (index) { + [idxObj] = await indexPatterns.find(index); + if (!idxObj) { + throw new Error( + i18n.translate('visTypeVega.vegaParser.baseView.indexNotFoundErrorMessage', { + defaultMessage: 'Index {index} not found', + values: { index: `"${index}"` }, + }) + ); + } + } else { + [idxObj] = await extractIndexPatternsFromSpec( + this._parser.isVegaLite ? this._parser.vlspec : this._parser.spec + ); + + if (!idxObj) { + const defaultIdx = await indexPatterns.getDefault(); + + if (defaultIdx) { + idxObj = defaultIdx; + } else { + throw new Error( + i18n.translate('visTypeVega.vegaParser.baseView.unableToFindDefaultIndexErrorMessage', { + defaultMessage: 'Unable to find default index', + }) + ); + } + } + } + + return idxObj.id; + } + createViewConfig() { const config = { // eslint-disable-next-line import/namespace @@ -261,7 +303,7 @@ export class VegaBaseView { * @param {string} [index] as defined in Kibana, or default if missing */ async addFilterHandler(query, index) { - const indexId = await this._findIndex(index); + const indexId = await this.findIndex(index); const filter = esFilters.buildQueryFilter(query, indexId); this._fireEvent({ name: 'applyFilter', data: { filters: [filter] } }); @@ -272,7 +314,7 @@ export class VegaBaseView { * @param {string} [index] as defined in Kibana, or default if missing */ async removeFilterHandler(query, index) { - const indexId = await this._findIndex(index); + const indexId = await this.findIndex(index); const filterToRemove = esFilters.buildQueryFilter(query, indexId); const currentFilters = this._filterManager.getFilters(); diff --git a/src/plugins/vis_type_vega/public/vega_visualization.test.js b/src/plugins/vis_type_vega/public/vega_visualization.test.js index a2214e139a296a..8a073ca32b94a2 100644 --- a/src/plugins/vis_type_vega/public/vega_visualization.test.js +++ b/src/plugins/vis_type_vega/public/vega_visualization.test.js @@ -30,7 +30,7 @@ import vegaMapGraph from './test_utils/vega_map_test.json'; import { VegaParser } from './data_model/vega_parser'; import { SearchAPI } from './data_model/search_api'; -import { setInjectedVars, setData, setSavedObjects, setNotifications } from './services'; +import { setInjectedVars, setData, setNotifications } from './services'; import { coreMock } from '../../../core/public/mocks'; import { dataPluginMock } from '../../data/public/mocks'; @@ -76,7 +76,6 @@ describe('VegaVisualizations', () => { enableExternalUrls: true, }); setData(dataPluginStart); - setSavedObjects(coreStart.savedObjects); setNotifications(coreStart.notifications); vegaVisualizationDependencies = { diff --git a/src/plugins/vis_type_vega/public/vega_visualization.ts b/src/plugins/vis_type_vega/public/vega_visualization.ts index 58c436bcd4be4c..554ac8962df463 100644 --- a/src/plugins/vis_type_vega/public/vega_visualization.ts +++ b/src/plugins/vis_type_vega/public/vega_visualization.ts @@ -20,13 +20,12 @@ import { i18n } from '@kbn/i18n'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; import { VegaParser } from './data_model/vega_parser'; import { VegaVisualizationDependencies } from './plugin'; -import { getNotifications, getData, getSavedObjects } from './services'; +import { getNotifications, getData } from './services'; import type { VegaView } from './vega_view/vega_view'; export const createVegaVisualization = ({ getServiceSettings }: VegaVisualizationDependencies) => class VegaVisualization { private readonly dataPlugin = getData(); - private readonly savedObjectsClient = getSavedObjects(); private vegaView: InstanceType | null = null; constructor( @@ -34,39 +33,6 @@ export const createVegaVisualization = ({ getServiceSettings }: VegaVisualizatio private fireEvent: IInterpreterRenderHandlers['event'] ) {} - /** - * Find index pattern by its title, of if not given, gets default - * @param {string} [index] - * @returns {Promise} index id - */ - async findIndex(index: string) { - const { indexPatterns } = this.dataPlugin; - let idxObj; - - if (index) { - // @ts-expect-error - idxObj = indexPatterns.findByTitle(this.savedObjectsClient, index); - if (!idxObj) { - throw new Error( - i18n.translate('visTypeVega.visualization.indexNotFoundErrorMessage', { - defaultMessage: 'Index {index} not found', - values: { index: `"${index}"` }, - }) - ); - } - } else { - idxObj = await indexPatterns.getDefault(); - if (!idxObj) { - throw new Error( - i18n.translate('visTypeVega.visualization.unableToFindDefaultIndexErrorMessage', { - defaultMessage: 'Unable to find default index', - }) - ); - } - } - return idxObj.id; - } - async render(visData: VegaParser) { const { toasts } = getNotifications(); @@ -112,7 +78,6 @@ export const createVegaVisualization = ({ getServiceSettings }: VegaVisualizatio serviceSettings, filterManager, timefilter, - findIndex: this.findIndex.bind(this), }; if (vegaParser.useMap) { diff --git a/src/plugins/vis_type_vislib/jest.config.js b/src/plugins/vis_type_vislib/jest.config.js new file mode 100644 index 00000000000000..1324ec1404b3e2 --- /dev/null +++ b/src/plugins/vis_type_vislib/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/vis_type_vislib'], +}; diff --git a/src/plugins/visualizations/jest.config.js b/src/plugins/visualizations/jest.config.js new file mode 100644 index 00000000000000..b1c5067cfe4a9c --- /dev/null +++ b/src/plugins/visualizations/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/visualizations'], +}; diff --git a/src/plugins/visualizations/public/embeddable/get_index_pattern.ts b/src/plugins/visualizations/public/embeddable/get_index_pattern.ts index c12c95145fe44c..22993eb6a2f6a1 100644 --- a/src/plugins/visualizations/public/embeddable/get_index_pattern.ts +++ b/src/plugins/visualizations/public/embeddable/get_index_pattern.ts @@ -18,37 +18,19 @@ */ import { VisSavedObject } from '../types'; -import { - indexPatterns, - IIndexPattern, - IndexPatternAttributes, -} from '../../../../plugins/data/public'; -import { getUISettings, getSavedObjects } from '../services'; +import type { IndexPattern } from '../../../../plugins/data/public'; +import { getIndexPatterns } from '../services'; export async function getIndexPattern( savedVis: VisSavedObject -): Promise { +): Promise { if (savedVis.visState.type !== 'metrics') { return savedVis.searchSource!.getField('index'); } - const savedObjectsClient = getSavedObjects().client; - const defaultIndex = getUISettings().get('defaultIndex'); + const indexPatternsClient = getIndexPatterns(); - if (savedVis.visState.params.index_pattern) { - const indexPatternObjects = await savedObjectsClient.find({ - type: 'index-pattern', - fields: ['title', 'fields'], - search: `"${savedVis.visState.params.index_pattern}"`, - searchFields: ['title'], - }); - const [indexPattern] = indexPatternObjects.savedObjects.map(indexPatterns.getFromSavedObject); - return indexPattern; - } - - const savedObject = await savedObjectsClient.get( - 'index-pattern', - defaultIndex - ); - return indexPatterns.getFromSavedObject(savedObject); + return savedVis.visState.params.index_pattern + ? (await indexPatternsClient.find(`"${savedVis.visState.params.index_pattern}"`))[0] + : await indexPatternsClient.getDefault(); } diff --git a/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap b/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap index 03a355c604c4d7..3ff0c83961e2a5 100644 --- a/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap +++ b/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap @@ -2,8 +2,6 @@ exports[`visualize loader pipeline helpers: build pipeline buildPipeline calls toExpression on vis_type if it exists 1`] = `"kibana | kibana_context | test"`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles input_control_vis function 1`] = `"input_control_vis visConfig='{\\"some\\":\\"nested\\",\\"data\\":{\\"here\\":true}}' "`; - exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function with buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"bucket\\":1}' "`; exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function without buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}}' "`; diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts index 653542bd8837de..57c58a99f09ea6 100644 --- a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts @@ -92,15 +92,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => { uiState = {}; }); - it('handles input_control_vis function', () => { - const params = { - some: 'nested', - data: { here: true }, - }; - const actual = buildPipelineVisFunction.input_control_vis(params, schemasDef, uiState); - expect(actual).toMatchSnapshot(); - }); - describe('handles region_map function', () => { it('without buckets', () => { const params = { metric: {} }; diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.ts b/src/plugins/visualizations/public/legacy/build_pipeline.ts index 0c244876ca6a36..29f6ec9b069a7e 100644 --- a/src/plugins/visualizations/public/legacy/build_pipeline.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.ts @@ -219,9 +219,6 @@ export const prepareDimension = (variable: string, data: any) => { }; export const buildPipelineVisFunction: BuildPipelineVisFunction = { - input_control_vis: (params) => { - return `input_control_vis ${prepareJson('visConfig', params)}`; - }, region_map: (params, schemas) => { const visConfig = { ...params, diff --git a/src/plugins/visualize/jest.config.js b/src/plugins/visualize/jest.config.js new file mode 100644 index 00000000000000..6657f4092068fe --- /dev/null +++ b/src/plugins/visualize/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/visualize'], +}; diff --git a/src/plugins/visualize/public/application/components/visualize_top_nav.tsx b/src/plugins/visualize/public/application/components/visualize_top_nav.tsx index b207529c456a14..4b328801361465 100644 --- a/src/plugins/visualize/public/application/components/visualize_top_nav.tsx +++ b/src/plugins/visualize/public/application/components/visualize_top_nav.tsx @@ -20,7 +20,6 @@ import React, { memo, useCallback, useMemo, useState, useEffect } from 'react'; import { AppMountParameters, OverlayRef } from 'kibana/public'; -import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import { useKibana } from '../../../../kibana_react/public'; import { @@ -31,6 +30,7 @@ import { } from '../types'; import { APP_NAME } from '../visualize_constants'; import { getTopNavConfig } from '../utils'; +import type { IndexPattern } from '../../../../data/public'; interface VisualizeTopNavProps { currentAppState: VisualizeAppState; @@ -118,7 +118,9 @@ const TopNav = ({ stateTransfer, onAppLeave, ]); - const [indexPattern, setIndexPattern] = useState(vis.data.indexPattern); + const [indexPatterns, setIndexPatterns] = useState( + vis.data.indexPattern ? [vis.data.indexPattern] : [] + ); const showDatePicker = () => { // tsvb loads without an indexPattern initially (TODO investigate). // hide timefilter only if timeFieldName is explicitly undefined. @@ -165,14 +167,27 @@ const TopNav = ({ ]); useEffect(() => { - if (!vis.data.indexPattern) { - services.data.indexPatterns.getDefault().then((index) => { - if (index) { - setIndexPattern(index); + const asyncSetIndexPattern = async () => { + let indexes: IndexPattern[] | undefined; + + if (vis.type.getUsedIndexPattern) { + indexes = await vis.type.getUsedIndexPattern(vis.params); + } + if (!indexes || !indexes.length) { + const defaultIndex = await services.data.indexPatterns.getDefault(); + if (defaultIndex) { + indexes = [defaultIndex]; } - }); + } + if (indexes) { + setIndexPatterns(indexes); + } + }; + + if (!vis.data.indexPattern) { + asyncSetIndexPattern(); } - }, [services.data.indexPatterns, vis.data.indexPattern]); + }, [vis.params, vis.type, services.data.indexPatterns, vis.data.indexPattern]); return isChromeVisible ? ( /** @@ -189,7 +204,7 @@ const TopNav = ({ onQuerySubmit={handleRefresh} savedQueryId={currentAppState.savedQuery} onSavedQueryIdChange={stateContainer.transitions.updateSavedQuery} - indexPatterns={indexPattern ? [indexPattern] : undefined} + indexPatterns={indexPatterns} screenTitle={vis.title} showAutoRefreshOnly={!showDatePicker()} showDatePicker={showDatePicker()} @@ -207,7 +222,7 @@ const TopNav = ({ /src/setup_node_env'], +}; diff --git a/src/test_utils/__tests__/get_url.js b/src/test_utils/__tests__/get_url.js deleted file mode 100644 index c9dd4d5a6d6168..00000000000000 --- a/src/test_utils/__tests__/get_url.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import getUrl from '../get_url'; - -describe('getUrl', function () { - it('should convert to a url', function () { - const url = getUrl( - { - protocol: 'http', - hostname: 'localhost', - }, - { - pathname: 'foo', - } - ); - - expect(url).to.be('http://localhost/foo'); - }); - - it('should convert to a url with port', function () { - const url = getUrl( - { - protocol: 'http', - hostname: 'localhost', - port: 9220, - }, - { - pathname: 'foo', - } - ); - - expect(url).to.be('http://localhost:9220/foo'); - }); - - it('should convert to a secure hashed url', function () { - expect( - getUrl( - { - protocol: 'https', - hostname: 'localhost', - }, - { - pathname: 'foo', - hash: 'bar', - } - ) - ).to.be('https://localhost/foo#bar'); - }); -}); diff --git a/src/test_utils/get_url.js b/src/test_utils/get_url.js deleted file mode 100644 index 182cb8e6e118db..00000000000000 --- a/src/test_utils/get_url.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import url from 'url'; - -/** - * Converts a config and a pathname to a url - * @param {object} config A url config - * example: - * { - * protocol: 'http', - * hostname: 'localhost', - * port: 9220, - * auth: kibanaTestUser.username + ':' + kibanaTestUser.password - * } - * @param {object} app The params to append - * example: - * { - * pathname: 'app/kibana', - * hash: '/discover' - * } - * @return {string} - */ - -export default function getUrl(config, app) { - return url.format(_.assign({}, config, app)); -} - -getUrl.noAuth = function getUrlNoAuth(config, app) { - config = _.pickBy(config, function (val, param) { - return param !== 'auth'; - }); - return getUrl(config, app); -}; - -getUrl.baseUrl = function getBaseUrl(config) { - return url.format(_.pick(config, 'protocol', 'hostname', 'port')); -}; diff --git a/src/test_utils/jest.config.js b/src/test_utils/jest.config.js new file mode 100644 index 00000000000000..b7e77413598c00 --- /dev/null +++ b/src/test_utils/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/src/test_utils'], +}; diff --git a/src/test_utils/public/key_map.ts b/src/test_utils/public/key_map.ts deleted file mode 100644 index aac3c6b2db3e0e..00000000000000 --- a/src/test_utils/public/key_map.ts +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export const keyMap: { [key: number]: string } = { - 8: 'backspace', - 9: 'tab', - 13: 'enter', - 16: 'shift', - 17: 'ctrl', - 18: 'alt', - 19: 'pause', - 20: 'capsLock', - 27: 'escape', - 32: 'space', - 33: 'pageUp', - 34: 'pageDown', - 35: 'end', - 36: 'home', - 37: 'left', - 38: 'up', - 39: 'right', - 40: 'down', - 45: 'insert', - 46: 'delete', - 48: '0', - 49: '1', - 50: '2', - 51: '3', - 52: '4', - 53: '5', - 54: '6', - 55: '7', - 56: '8', - 57: '9', - 65: 'a', - 66: 'b', - 67: 'c', - 68: 'd', - 69: 'e', - 70: 'f', - 71: 'g', - 72: 'h', - 73: 'i', - 74: 'j', - 75: 'k', - 76: 'l', - 77: 'm', - 78: 'n', - 79: 'o', - 80: 'p', - 81: 'q', - 82: 'r', - 83: 's', - 84: 't', - 85: 'u', - 86: 'v', - 87: 'w', - 88: 'x', - 89: 'y', - 90: 'z', - 91: 'leftWindowKey', - 92: 'rightWindowKey', - 93: 'selectKey', - 96: '0', - 97: '1', - 98: '2', - 99: '3', - 100: '4', - 101: '5', - 102: '6', - 103: '7', - 104: '8', - 105: '9', - 106: 'multiply', - 107: 'add', - 109: 'subtract', - 110: 'period', - 111: 'divide', - 112: 'f1', - 113: 'f2', - 114: 'f3', - 115: 'f4', - 116: 'f5', - 117: 'f6', - 118: 'f7', - 119: 'f8', - 120: 'f9', - 121: 'f10', - 122: 'f11', - 123: 'f12', - 144: 'numLock', - 145: 'scrollLock', - 186: 'semiColon', - 187: 'equalSign', - 188: 'comma', - 189: 'dash', - 190: 'period', - 191: 'forwardSlash', - 192: 'graveAccent', - 219: 'openBracket', - 220: 'backSlash', - 221: 'closeBracket', - 222: 'singleQuote', - 224: 'meta', -}; diff --git a/src/test_utils/tsconfig.json b/src/test_utils/tsconfig.json deleted file mode 100644 index 2fa89e29898914..00000000000000 --- a/src/test_utils/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "./target", - "composite": true, - "emitDeclarationOnly": true, - "declaration": true, - "declarationMap": true - }, - "include": [ - "public/**/*" - ], - "exclude": [ - "target" - ] -} diff --git a/test/api_integration/apis/saved_objects/migrations.ts b/test/api_integration/apis/saved_objects/migrations.ts index b5fa16558514a5..fa9c2fd1a2d7fe 100644 --- a/test/api_integration/apis/saved_objects/migrations.ts +++ b/test/api_integration/apis/saved_objects/migrations.ts @@ -54,8 +54,7 @@ function getLogMock() { export default ({ getService }: FtrProviderContext) => { const esClient = getService('es'); - // FLAKY: https://github.com/elastic/kibana/issues/84445 - describe.skip('Kibana index migration', () => { + describe('Kibana index migration', () => { before(() => esClient.indices.delete({ index: '.migrate-*' })); it('Migrates an existing index that has never been migrated before', async () => { @@ -313,7 +312,10 @@ export default ({ getService }: FtrProviderContext) => { result // @ts-expect-error destIndex exists only on MigrationResult status: 'migrated'; .map(({ status, destIndex }) => ({ status, destIndex })) - .sort((a) => (a.destIndex ? 0 : 1)) + .sort(({ destIndex: a }, { destIndex: b }) => + // sort by destIndex in ascending order, keeping falsy values at the end + (a && !b) || a < b ? -1 : (!a && b) || a > b ? 1 : 0 + ) ).to.eql([ { status: 'migrated', destIndex: '.migration-c_2' }, { status: 'skipped', destIndex: undefined }, diff --git a/test/api_integration/apis/telemetry/telemetry_local.js b/test/api_integration/apis/telemetry/telemetry_local.js index b3d34d5910fc3b..a025a65779e9c2 100644 --- a/test/api_integration/apis/telemetry/telemetry_local.js +++ b/test/api_integration/apis/telemetry/telemetry_local.js @@ -62,6 +62,8 @@ export default function ({ getService }) { expect(body.length).to.be(1); const stats = body[0]; expect(stats.collection).to.be('local'); + expect(stats.collectionSource).to.be('local'); + expect(stats.license).to.be.undefined; // OSS cannot get the license expect(stats.stack_stats.kibana.count).to.be.a('number'); expect(stats.stack_stats.kibana.indices).to.be.a('number'); expect(stats.stack_stats.kibana.os.platforms[0].platform).to.be.a('string'); diff --git a/test/common/services/deployment.ts b/test/common/services/deployment.ts index 88389b57dd1db2..cad7a87e39f7c8 100644 --- a/test/common/services/deployment.ts +++ b/test/common/services/deployment.ts @@ -19,8 +19,7 @@ import { get } from 'lodash'; import fetch from 'node-fetch'; -// @ts-ignore not TS yet -import getUrl from '../../../src/test_utils/get_url'; +import { getUrl } from '@kbn/test'; import { FtrProviderContext } from '../ftr_provider_context'; diff --git a/test/functional/apps/discover/_doc_table.ts b/test/functional/apps/discover/_doc_table.ts index 49b160cc703124..20fda144b338e9 100644 --- a/test/functional/apps/discover/_doc_table.ts +++ b/test/functional/apps/discover/_doc_table.ts @@ -31,8 +31,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { defaultIndex: 'logstash-*', }; - // Failing: See https://github.com/elastic/kibana/issues/82445 - describe.skip('discover doc table', function describeIndexTests() { + describe('discover doc table', function describeIndexTests() { const defaultRowsLimit = 50; const rowsHardLimit = 500; diff --git a/test/functional/jest.config.js b/test/functional/jest.config.js new file mode 100644 index 00000000000000..60dce5d95773a9 --- /dev/null +++ b/test/functional/jest.config.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/test/functional'], +}; diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index 19f35ee3083bd8..f52343a9d913bd 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -21,9 +21,8 @@ import { delay } from 'bluebird'; import expect from '@kbn/expect'; // @ts-ignore import fetch from 'node-fetch'; +import { getUrl } from '@kbn/test'; import { FtrProviderContext } from '../ftr_provider_context'; -// @ts-ignore not TS yet -import getUrl from '../../../src/test_utils/get_url'; export function CommonPageProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); diff --git a/test/functional/page_objects/context_page.ts b/test/functional/page_objects/context_page.ts index 9cbada532cde32..ce5ffad87e8f38 100644 --- a/test/functional/page_objects/context_page.ts +++ b/test/functional/page_objects/context_page.ts @@ -18,9 +18,8 @@ */ import rison from 'rison-node'; +import { getUrl } from '@kbn/test'; import { FtrProviderContext } from '../ftr_provider_context'; -// @ts-ignore not TS yet -import getUrl from '../../../src/test_utils/get_url'; const DEFAULT_INITIAL_STATE = { columns: ['@message'], diff --git a/test/scripts/checks/jest_configs.sh b/test/scripts/checks/jest_configs.sh new file mode 100644 index 00000000000000..28cb1386c748ff --- /dev/null +++ b/test/scripts/checks/jest_configs.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +checks-reporter-with-killswitch "Check Jest Configs" node scripts/check_jest_configs diff --git a/test/scripts/checks/plugins_with_circular_deps.sh b/test/scripts/checks/plugins_with_circular_deps.sh new file mode 100644 index 00000000000000..77880243538d2a --- /dev/null +++ b/test/scripts/checks/plugins_with_circular_deps.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +checks-reporter-with-killswitch "Check plugins with circular dependencies" \ + node scripts/find_plugins_with_circular_deps diff --git a/test/server_integration/http/platform/headers.ts b/test/server_integration/http/platform/headers.ts index 50cfa5c7022312..9361d3131b7199 100644 --- a/test/server_integration/http/platform/headers.ts +++ b/test/server_integration/http/platform/headers.ts @@ -18,11 +18,9 @@ */ import Http from 'http'; import Url from 'url'; +import { getUrl } from '@kbn/test'; import { FtrProviderContext } from '../../services/types'; -// @ts-ignore -import getUrl from '../../../../src/test_utils/get_url'; - const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); const oneSec = 1_000; diff --git a/tsconfig.base.json b/tsconfig.base.json index 111c9dbc949de0..247813da51cfb3 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -6,10 +6,6 @@ "kibana": ["./kibana"], "kibana/public": ["src/core/public"], "kibana/server": ["src/core/server"], - "plugins/*": ["src/legacy/core_plugins/*/public/"], - "test_utils/*": [ - "src/test_utils/public/*" - ], "fixtures/*": ["src/fixtures/*"] }, // Support .tsx files and transform JSX into calls to React.createElement diff --git a/tsconfig.json b/tsconfig.json index 6e137e445762de..02048414f678e3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "incremental": false }, - "include": ["kibana.d.ts", "src/**/*", "typings/**/*", "test_utils/**/*"], + "include": ["kibana.d.ts", "src/**/*", "typings/**/*"], "exclude": [ "src/**/__fixtures__/**/*", "src/core/**/*", @@ -20,7 +20,6 @@ "src/plugins/telemetry_collection_manager/**/*", "src/plugins/url_forwarding/**/*", "src/plugins/usage_collection/**/*", - "src/test_utils/**/*" // In the build we actually exclude **/public/**/* from this config so that // we can run the TSC on both this and the .browser version of this config // file, but if we did it during development IDEs would not be able to find @@ -42,6 +41,5 @@ { "path": "./src/plugins/telemetry_collection_manager/tsconfig.json" }, { "path": "./src/plugins/url_forwarding/tsconfig.json" }, { "path": "./src/plugins/usage_collection/tsconfig.json" }, - { "path": "./src/test_utils/tsconfig.json" } ] } diff --git a/tsconfig.refs.json b/tsconfig.refs.json index 55d63f516b998f..a99d4d57d3f0ac 100644 --- a/tsconfig.refs.json +++ b/tsconfig.refs.json @@ -15,6 +15,5 @@ { "path": "./src/plugins/telemetry_collection_manager/tsconfig.json" }, { "path": "./src/plugins/url_forwarding/tsconfig.json" }, { "path": "./src/plugins/usage_collection/tsconfig.json" }, - { "path": "./src/test_utils/tsconfig.json" } ] } diff --git a/vars/tasks.groovy b/vars/tasks.groovy index b6bcc0d93f9c0b..fd96c2bbf8e78c 100644 --- a/vars/tasks.groovy +++ b/vars/tasks.groovy @@ -6,12 +6,14 @@ def check() { tasks([ kibanaPipeline.scriptTask('Check Telemetry Schema', 'test/scripts/checks/telemetry.sh'), kibanaPipeline.scriptTask('Check TypeScript Projects', 'test/scripts/checks/ts_projects.sh'), + kibanaPipeline.scriptTask('Check Jest Configs', 'test/scripts/checks/jest_configs.sh'), kibanaPipeline.scriptTask('Check Doc API Changes', 'test/scripts/checks/doc_api_changes.sh'), kibanaPipeline.scriptTask('Check Types', 'test/scripts/checks/type_check.sh'), kibanaPipeline.scriptTask('Check Bundle Limits', 'test/scripts/checks/bundle_limits.sh'), kibanaPipeline.scriptTask('Check i18n', 'test/scripts/checks/i18n.sh'), kibanaPipeline.scriptTask('Check File Casing', 'test/scripts/checks/file_casing.sh'), kibanaPipeline.scriptTask('Check Licenses', 'test/scripts/checks/licenses.sh'), + kibanaPipeline.scriptTask('Check Plugins With Circular Dependencies', 'test/scripts/checks/plugins_with_circular_deps.sh'), kibanaPipeline.scriptTask('Verify NOTICE', 'test/scripts/checks/verify_notice.sh'), kibanaPipeline.scriptTask('Test Projects', 'test/scripts/checks/test_projects.sh'), kibanaPipeline.scriptTask('Test Hardening', 'test/scripts/checks/test_hardening.sh'), diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js deleted file mode 100644 index 2ddc58500d15e4..00000000000000 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export function createJestConfig({ kibanaDirectory, rootDir }) { - return { - preset: '@kbn/test', - rootDir: kibanaDirectory, - roots: [`${rootDir}/plugins`], - reporters: [ - 'default', - [ - `${kibanaDirectory}/packages/kbn-test/target/jest/junit_reporter`, - { - reportName: 'X-Pack Jest Tests', - }, - ], - ], - }; -} diff --git a/x-pack/dev-tools/jest/index.js b/x-pack/dev-tools/jest/index.js deleted file mode 100644 index c22f8625c5778c..00000000000000 --- a/x-pack/dev-tools/jest/index.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { run } from 'jest'; -import { resolve } from 'path'; - -import { createJestConfig } from './create_jest_config'; - -export function runJest() { - process.env.NODE_ENV = process.env.NODE_ENV || 'test'; - const config = JSON.stringify( - createJestConfig({ - kibanaDirectory: resolve(__dirname, '../../..'), - rootDir: resolve(__dirname, '../..'), - xPackKibanaDirectory: resolve(__dirname, '../..'), - }) - ); - - const argv = [...process.argv.slice(2), '--config', config]; - - return run(argv); -} diff --git a/x-pack/jest.config.js b/x-pack/jest.config.js new file mode 100644 index 00000000000000..8b3f717b40e661 --- /dev/null +++ b/x-pack/jest.config.js @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + rootDir: '..', + projects: ['/x-pack/plugins/*/jest.config.js'], + reporters: [ + 'default', + ['/packages/kbn-test/target/jest/junit_reporter', { reportName: 'X-Pack Jest Tests' }], + ], +}; diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md index 432a4bfff7a6b9..fb0293dca5ff44 100644 --- a/x-pack/plugins/actions/README.md +++ b/x-pack/plugins/actions/README.md @@ -529,7 +529,7 @@ The PagerDuty action uses the [V2 Events API](https://v2.developer.pagerduty.com | Property | Description | Type | | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | | eventAction | One of `trigger` _(default)_, `resolve`, or `acknowlege`. See [event action](https://v2.developer.pagerduty.com/docs/events-api-v2#event-action) for more details. | string _(optional)_ | -| dedupKey | All actions sharing this key will be associated with the same PagerDuty alert. Used to correlate trigger and resolution. Defaults to `action:`. The maximum length is **255** characters. See [alert deduplication](https://v2.developer.pagerduty.com/docs/events-api-v2#alert-de-duplication) for details. | string _(optional)_ | +| dedupKey | All actions sharing this key will be associated with the same PagerDuty alert. Used to correlate trigger and resolution. The maximum length is **255** characters. See [alert deduplication](https://v2.developer.pagerduty.com/docs/events-api-v2#alert-de-duplication) for details. | string _(optional)_ | | summary | A text summary of the event, defaults to `No summary provided`. The maximum length is **1024** characters. | string _(optional)_ | | source | The affected system, preferably a hostname or fully qualified domain name. Defaults to `Kibana Action `. | string _(optional)_ | | severity | The perceived severity of on the affected system. This can be one of `critical`, `error`, `warning` or `info`_(default)_. | string _(optional)_ | diff --git a/x-pack/plugins/actions/jest.config.js b/x-pack/plugins/actions/jest.config.js new file mode 100644 index 00000000000000..2aaa277079ad37 --- /dev/null +++ b/x-pack/plugins/actions/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/actions'], +}; diff --git a/x-pack/plugins/alerting_builtins/jest.config.js b/x-pack/plugins/alerting_builtins/jest.config.js new file mode 100644 index 00000000000000..05fe793a157df2 --- /dev/null +++ b/x-pack/plugins/alerting_builtins/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/alerting_builtins'], +}; diff --git a/x-pack/plugins/alerts/common/builtin_action_groups.ts b/x-pack/plugins/alerts/common/builtin_action_groups.ts index d31f75357d370d..d9c5ae613f7879 100644 --- a/x-pack/plugins/alerts/common/builtin_action_groups.ts +++ b/x-pack/plugins/alerts/common/builtin_action_groups.ts @@ -6,13 +6,13 @@ import { i18n } from '@kbn/i18n'; import { ActionGroup } from './alert_type'; -export const ResolvedActionGroup: ActionGroup = { - id: 'resolved', - name: i18n.translate('xpack.alerts.builtinActionGroups.resolved', { - defaultMessage: 'Resolved', +export const RecoveredActionGroup: ActionGroup = { + id: 'recovered', + name: i18n.translate('xpack.alerts.builtinActionGroups.recovered', { + defaultMessage: 'Recovered', }), }; export function getBuiltinActionGroups(): ActionGroup[] { - return [ResolvedActionGroup]; + return [RecoveredActionGroup]; } diff --git a/x-pack/plugins/alerts/jest.config.js b/x-pack/plugins/alerts/jest.config.js new file mode 100644 index 00000000000000..d5c9ce5bbf4c2b --- /dev/null +++ b/x-pack/plugins/alerts/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/alerts'], +}; diff --git a/x-pack/plugins/alerts/server/alert_type_registry.test.ts b/x-pack/plugins/alerts/server/alert_type_registry.test.ts index 8dc387f96addba..b04871a047e4b4 100644 --- a/x-pack/plugins/alerts/server/alert_type_registry.test.ts +++ b/x-pack/plugins/alerts/server/alert_type_registry.test.ts @@ -105,8 +105,8 @@ describe('register()', () => { name: 'Default', }, { - id: 'resolved', - name: 'Resolved', + id: 'recovered', + name: 'Recovered', }, ], defaultActionGroupId: 'default', @@ -117,7 +117,7 @@ describe('register()', () => { expect(() => registry.register(alertType)).toThrowError( new Error( - `Alert type [id="${alertType.id}"] cannot be registered. Action groups [resolved] are reserved by the framework.` + `Alert type [id="${alertType.id}"] cannot be registered. Action groups [recovered] are reserved by the framework.` ) ); }); @@ -229,8 +229,8 @@ describe('get()', () => { "name": "Default", }, Object { - "id": "resolved", - "name": "Resolved", + "id": "recovered", + "name": "Recovered", }, ], "actionVariables": Object { @@ -287,8 +287,8 @@ describe('list()', () => { "name": "Test Action Group", }, Object { - "id": "resolved", - "name": "Resolved", + "id": "recovered", + "name": "Recovered", }, ], "actionVariables": Object { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts index 9bd61c0fe66d2d..0a764ea7685913 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts @@ -122,7 +122,7 @@ describe('getAlertInstanceSummary()', () => { .addActiveInstance('instance-previously-active', 'action group B') .advanceTime(10000) .addExecute() - .addResolvedInstance('instance-previously-active') + .addRecoveredInstance('instance-previously-active') .addActiveInstance('instance-currently-active', 'action group A') .getEvents(); const eventsResult = { diff --git a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts index f9e4a2908d6ce3..1d5ebe2b5911e9 100644 --- a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts +++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.test.ts @@ -6,7 +6,7 @@ import { SanitizedAlert, AlertInstanceSummary } from '../types'; import { IValidatedEvent } from '../../../event_log/server'; -import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER } from '../plugin'; +import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER, LEGACY_EVENT_LOG_ACTIONS } from '../plugin'; import { alertInstanceSummaryFromEventLog } from './alert_instance_summary_from_event_log'; const ONE_HOUR_IN_MILLIS = 60 * 60 * 1000; @@ -189,7 +189,43 @@ describe('alertInstanceSummaryFromEventLog', () => { .addActiveInstance('instance-1', 'action group A') .advanceTime(10000) .addExecute() - .addResolvedInstance('instance-1') + .addRecoveredInstance('instance-1') + .getEvents(); + + const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ + alert, + events, + dateStart, + dateEnd, + }); + + const { lastRun, status, instances } = summary; + expect({ lastRun, status, instances }).toMatchInlineSnapshot(` + Object { + "instances": Object { + "instance-1": Object { + "actionGroupId": undefined, + "activeStartDate": undefined, + "muted": false, + "status": "OK", + }, + }, + "lastRun": "2020-06-18T00:00:10.000Z", + "status": "OK", + } + `); + }); + + test('legacy alert with currently inactive instance', async () => { + const alert = createAlert({}); + const eventsFactory = new EventsFactory(); + const events = eventsFactory + .addExecute() + .addNewInstance('instance-1') + .addActiveInstance('instance-1', 'action group A') + .advanceTime(10000) + .addExecute() + .addLegacyResolvedInstance('instance-1') .getEvents(); const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ @@ -224,7 +260,7 @@ describe('alertInstanceSummaryFromEventLog', () => { .addActiveInstance('instance-1', 'action group A') .advanceTime(10000) .addExecute() - .addResolvedInstance('instance-1') + .addRecoveredInstance('instance-1') .getEvents(); const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ @@ -406,7 +442,7 @@ describe('alertInstanceSummaryFromEventLog', () => { .advanceTime(10000) .addExecute() .addActiveInstance('instance-1', 'action group A') - .addResolvedInstance('instance-2') + .addRecoveredInstance('instance-2') .getEvents(); const summary: AlertInstanceSummary = alertInstanceSummaryFromEventLog({ @@ -451,7 +487,7 @@ describe('alertInstanceSummaryFromEventLog', () => { .advanceTime(10000) .addExecute() .addActiveInstance('instance-1', 'action group A') - .addResolvedInstance('instance-2') + .addRecoveredInstance('instance-2') .advanceTime(10000) .addExecute() .addActiveInstance('instance-1', 'action group B') @@ -561,12 +597,24 @@ export class EventsFactory { return this; } - addResolvedInstance(instanceId: string): EventsFactory { + addRecoveredInstance(instanceId: string): EventsFactory { + this.events.push({ + '@timestamp': this.date, + event: { + provider: EVENT_LOG_PROVIDER, + action: EVENT_LOG_ACTIONS.recoveredInstance, + }, + kibana: { alerting: { instance_id: instanceId } }, + }); + return this; + } + + addLegacyResolvedInstance(instanceId: string): EventsFactory { this.events.push({ '@timestamp': this.date, event: { provider: EVENT_LOG_PROVIDER, - action: EVENT_LOG_ACTIONS.resolvedInstance, + action: LEGACY_EVENT_LOG_ACTIONS.resolvedInstance, }, kibana: { alerting: { instance_id: instanceId } }, }); diff --git a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts index 8fed97a74435dc..6fed8b4aa4ee6a 100644 --- a/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts +++ b/x-pack/plugins/alerts/server/lib/alert_instance_summary_from_event_log.ts @@ -6,7 +6,7 @@ import { SanitizedAlert, AlertInstanceSummary, AlertInstanceStatus } from '../types'; import { IEvent } from '../../../event_log/server'; -import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER } from '../plugin'; +import { EVENT_LOG_ACTIONS, EVENT_LOG_PROVIDER, LEGACY_EVENT_LOG_ACTIONS } from '../plugin'; export interface AlertInstanceSummaryFromEventLogParams { alert: SanitizedAlert; @@ -80,7 +80,8 @@ export function alertInstanceSummaryFromEventLog( status.status = 'Active'; status.actionGroupId = event?.kibana?.alerting?.action_group_id; break; - case EVENT_LOG_ACTIONS.resolvedInstance: + case LEGACY_EVENT_LOG_ACTIONS.resolvedInstance: + case EVENT_LOG_ACTIONS.recoveredInstance: status.status = 'OK'; status.activeStartDate = undefined; status.actionGroupId = undefined; diff --git a/x-pack/plugins/alerts/server/plugin.ts b/x-pack/plugins/alerts/server/plugin.ts index 4bfb44425544a9..bafb89c64076b0 100644 --- a/x-pack/plugins/alerts/server/plugin.ts +++ b/x-pack/plugins/alerts/server/plugin.ts @@ -82,9 +82,12 @@ export const EVENT_LOG_ACTIONS = { execute: 'execute', executeAction: 'execute-action', newInstance: 'new-instance', - resolvedInstance: 'resolved-instance', + recoveredInstance: 'recovered-instance', activeInstance: 'active-instance', }; +export const LEGACY_EVENT_LOG_ACTIONS = { + resolvedInstance: 'resolved-instance', +}; export interface PluginSetupContract { registerType: AlertTypeRegistry['register']; diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts index 07d08f5837d54a..d4c4f746392c39 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.test.ts @@ -26,12 +26,12 @@ import { alertsMock, alertsClientMock } from '../mocks'; import { eventLoggerMock } from '../../../event_log/server/event_logger.mock'; import { IEventLogger } from '../../../event_log/server'; import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; -import { Alert, ResolvedActionGroup } from '../../common'; +import { Alert, RecoveredActionGroup } from '../../common'; import { omit } from 'lodash'; const alertType = { id: 'test', name: 'My test alert', - actionGroups: [{ id: 'default', name: 'Default' }, ResolvedActionGroup], + actionGroups: [{ id: 'default', name: 'Default' }, RecoveredActionGroup], defaultActionGroupId: 'default', executor: jest.fn(), producer: 'alerts', @@ -114,7 +114,7 @@ describe('Task Runner', () => { }, }, { - group: ResolvedActionGroup.id, + group: RecoveredActionGroup.id, id: '2', actionTypeId: 'action', params: { @@ -517,7 +517,7 @@ describe('Task Runner', () => { `); }); - test('fire resolved actions for execution for the alertInstances which is in the resolved state', async () => { + test('fire recovered actions for execution for the alertInstances which is in the recovered state', async () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); @@ -650,7 +650,7 @@ describe('Task Runner', () => { Array [ Object { "event": Object { - "action": "resolved-instance", + "action": "recovered-instance", }, "kibana": Object { "alerting": Object { @@ -666,7 +666,7 @@ describe('Task Runner', () => { }, ], }, - "message": "test:1: 'alert-name' resolved instance: '2'", + "message": "test:1: 'alert-name' instance '2' has recovered", }, ], Array [ diff --git a/x-pack/plugins/alerts/server/task_runner/task_runner.ts b/x-pack/plugins/alerts/server/task_runner/task_runner.ts index 24d96788c33959..5a7247ac50ea06 100644 --- a/x-pack/plugins/alerts/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerts/server/task_runner/task_runner.ts @@ -39,7 +39,7 @@ import { IEvent, IEventLogger, SAVED_OBJECT_REL_PRIMARY } from '../../../event_l import { isAlertSavedObjectNotFoundError } from '../lib/is_alert_not_found_error'; import { AlertsClient } from '../alerts_client'; import { partiallyUpdateAlert } from '../saved_objects'; -import { ResolvedActionGroup } from '../../common'; +import { RecoveredActionGroup } from '../../common'; const FALLBACK_RETRY_INTERVAL = '5m'; @@ -219,7 +219,7 @@ export class TaskRunner { alertInstance.hasScheduledActions() ); - generateNewAndResolvedInstanceEvents({ + generateNewAndRecoveredInstanceEvents({ eventLogger, originalAlertInstances, currentAlertInstances: instancesWithScheduledActions, @@ -229,7 +229,7 @@ export class TaskRunner { }); if (!muteAll) { - scheduleActionsForResolvedInstances( + scheduleActionsForRecoveredInstances( alertInstances, executionHandler, originalAlertInstances, @@ -436,7 +436,7 @@ export class TaskRunner { } } -interface GenerateNewAndResolvedInstanceEventsParams { +interface GenerateNewAndRecoveredInstanceEventsParams { eventLogger: IEventLogger; originalAlertInstances: Dictionary; currentAlertInstances: Dictionary; @@ -445,18 +445,20 @@ interface GenerateNewAndResolvedInstanceEventsParams { namespace: string | undefined; } -function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInstanceEventsParams) { +function generateNewAndRecoveredInstanceEvents( + params: GenerateNewAndRecoveredInstanceEventsParams +) { const { eventLogger, alertId, namespace, currentAlertInstances, originalAlertInstances } = params; const originalAlertInstanceIds = Object.keys(originalAlertInstances); const currentAlertInstanceIds = Object.keys(currentAlertInstances); const newIds = without(currentAlertInstanceIds, ...originalAlertInstanceIds); - const resolvedIds = without(originalAlertInstanceIds, ...currentAlertInstanceIds); + const recoveredIds = without(originalAlertInstanceIds, ...currentAlertInstanceIds); - for (const id of resolvedIds) { + for (const id of recoveredIds) { const actionGroup = originalAlertInstances[id].getLastScheduledActions()?.group; - const message = `${params.alertLabel} resolved instance: '${id}'`; - logInstanceEvent(id, EVENT_LOG_ACTIONS.resolvedInstance, message, actionGroup); + const message = `${params.alertLabel} instance '${id}' has recovered`; + logInstanceEvent(id, EVENT_LOG_ACTIONS.recoveredInstance, message, actionGroup); } for (const id of newIds) { @@ -496,7 +498,7 @@ function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInst } } -function scheduleActionsForResolvedInstances( +function scheduleActionsForRecoveredInstances( alertInstancesMap: Record, executionHandler: ReturnType, originalAlertInstances: Record, @@ -505,22 +507,22 @@ function scheduleActionsForResolvedInstances( ) { const currentAlertInstanceIds = Object.keys(currentAlertInstances); const originalAlertInstanceIds = Object.keys(originalAlertInstances); - const resolvedIds = without( + const recoveredIds = without( originalAlertInstanceIds, ...currentAlertInstanceIds, ...mutedInstanceIds ); - for (const id of resolvedIds) { + for (const id of recoveredIds) { const instance = alertInstancesMap[id]; - instance.updateLastScheduledActions(ResolvedActionGroup.id); + instance.updateLastScheduledActions(RecoveredActionGroup.id); instance.unscheduleActions(); executionHandler({ - actionGroup: ResolvedActionGroup.id, + actionGroup: RecoveredActionGroup.id, context: {}, state: {}, alertInstanceId: id, }); - instance.scheduleActions(ResolvedActionGroup.id); + instance.scheduleActions(RecoveredActionGroup.id); } } diff --git a/x-pack/plugins/apm/jest.config.js b/x-pack/plugins/apm/jest.config.js index 2a5ef9ad0c2a7b..a0e98eebf65cbf 100644 --- a/x-pack/plugins/apm/jest.config.js +++ b/x-pack/plugins/apm/jest.config.js @@ -4,34 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -// This is an APM-specific Jest configuration which overrides the x-pack -// configuration. It's intended for use in development and does not run in CI, -// which runs the entire x-pack suite. Run `npx jest`. - -require('../../../src/setup_node_env'); - -const { createJestConfig } = require('../../dev-tools/jest/create_jest_config'); -const { resolve } = require('path'); - -const rootDir = resolve(__dirname, '.'); -const kibanaDirectory = resolve(__dirname, '../../..'); - -const jestConfig = createJestConfig({ kibanaDirectory, rootDir }); - module.exports = { - ...jestConfig, - reporters: ['default'], - roots: [`${rootDir}/common`, `${rootDir}/public`, `${rootDir}/server`], - collectCoverage: true, - collectCoverageFrom: [ - ...(jestConfig.collectCoverageFrom || []), - '**/*.{js,mjs,jsx,ts,tsx}', - '!**/*.stories.{js,mjs,ts,tsx}', - '!**/dev_docs/**', - '!**/e2e/**', - '!**/target/**', - '!**/typings/**', - ], - coverageDirectory: `${rootDir}/target/coverage/jest`, - coverageReporters: ['html'], + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/apm'], }; diff --git a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx index b90f606d276eb1..399a24a8bc1650 100644 --- a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { render, fireEvent, waitFor } from '@testing-library/react'; import { MissingJobsAlert } from './anomaly_detection_setup_link'; -import * as hooks from '../../hooks/useFetcher'; +import * as hooks from '../../hooks/use_fetcher'; async function renderTooltipAnchor({ jobs, diff --git a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx index e6fc80ed7c3b7f..8a1d73c8189445 100644 --- a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx @@ -16,14 +16,14 @@ import { getEnvironmentLabel, } from '../../../common/environment_filter_values'; import { getAPMHref } from '../../components/shared/Links/apm/APMLink'; -import { useApmPluginContext } from '../../hooks/useApmPluginContext'; -import { FETCH_STATUS, useFetcher } from '../../hooks/useFetcher'; -import { useLicense } from '../../hooks/useLicense'; -import { useUrlParams } from '../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context'; +import { FETCH_STATUS, useFetcher } from '../../hooks/use_fetcher'; +import { useLicenseContext } from '../../context/license/use_license_context'; +import { useUrlParams } from '../../context/url_params_context/use_url_params'; import { APIReturnType } from '../../services/rest/createCallApmApi'; import { units } from '../../style/variables'; -export type AnomalyDetectionApiResponse = APIReturnType<'GET /api/apm/settings/anomaly-detection'>; +export type AnomalyDetectionApiResponse = APIReturnType<'GET /api/apm/settings/anomaly-detection/jobs'>; const DEFAULT_DATA = { jobs: [], hasLegacyJobs: false }; @@ -32,7 +32,7 @@ export function AnomalyDetectionSetupLink() { const environment = uiFilters.environment; const { core } = useApmPluginContext(); const canGetJobs = !!core.application.capabilities.ml?.canGetJobs; - const license = useLicense(); + const license = useLicenseContext(); const hasValidLicense = license?.isActive && license?.hasAtLeast('platinum'); const { basePath } = core.http; @@ -57,7 +57,7 @@ export function AnomalyDetectionSetupLink() { export function MissingJobsAlert({ environment }: { environment?: string }) { const { data = DEFAULT_DATA, status } = useFetcher( (callApmApi) => - callApmApi({ endpoint: `GET /api/apm/settings/anomaly-detection` }), + callApmApi({ endpoint: `GET /api/apm/settings/anomaly-detection/jobs` }), [], { preservePreviousData: false, showToastOnError: false } ); diff --git a/x-pack/plugins/apm/public/application/action_menu/index.tsx b/x-pack/plugins/apm/public/application/action_menu/index.tsx index 1713ef61fac1ee..438eb2bca7f243 100644 --- a/x-pack/plugins/apm/public/application/action_menu/index.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/index.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { useParams } from 'react-router-dom'; import { getAlertingCapabilities } from '../../components/alerting/get_alert_capabilities'; import { getAPMHref } from '../../components/shared/Links/apm/APMLink'; -import { useApmPluginContext } from '../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context'; import { AlertingPopoverAndFlyout } from './alerting_popover_flyout'; import { AnomalyDetectionSetupLink } from './anomaly_detection_setup_link'; diff --git a/x-pack/plugins/apm/public/application/application.test.tsx b/x-pack/plugins/apm/public/application/application.test.tsx index 75b7835c13151b..c5091b1b554cc9 100644 --- a/x-pack/plugins/apm/public/application/application.test.tsx +++ b/x-pack/plugins/apm/public/application/application.test.tsx @@ -8,7 +8,7 @@ import { act } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import { Observable } from 'rxjs'; import { AppMountParameters, CoreStart, HttpSetup } from 'src/core/public'; -import { mockApmPluginContextValue } from '../context/ApmPluginContext/MockApmPluginContext'; +import { mockApmPluginContextValue } from '../context/apm_plugin/mock_apm_plugin_context'; import { ApmPluginSetupDeps } from '../plugin'; import { createCallApmApi } from '../services/rest/createCallApmApi'; import { renderApp } from './'; diff --git a/x-pack/plugins/apm/public/application/csmApp.tsx b/x-pack/plugins/apm/public/application/csmApp.tsx index 7fcbe7c518cd0b..4d16643a83fe98 100644 --- a/x-pack/plugins/apm/public/application/csmApp.tsx +++ b/x-pack/plugins/apm/public/application/csmApp.tsx @@ -20,8 +20,8 @@ import { APMRouteDefinition } from '../application/routes'; import { renderAsRedirectTo } from '../components/app/Main/route_config'; import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange'; import { RumHome, UX_LABEL } from '../components/app/RumDashboard/RumHome'; -import { ApmPluginContext } from '../context/ApmPluginContext'; -import { UrlParamsProvider } from '../context/UrlParamsContext'; +import { ApmPluginContext } from '../context/apm_plugin/apm_plugin_context'; +import { UrlParamsProvider } from '../context/url_params_context/url_params_context'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; import { ConfigSchema } from '../index'; import { ApmPluginSetupDeps, ApmPluginStartDeps } from '../plugin'; diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx index 79c29867cb8e31..9c4413765a5006 100644 --- a/x-pack/plugins/apm/public/application/index.tsx +++ b/x-pack/plugins/apm/public/application/index.tsx @@ -25,9 +25,9 @@ import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPat import { ApmPluginContext, ApmPluginContextValue, -} from '../context/ApmPluginContext'; -import { LicenseProvider } from '../context/LicenseContext'; -import { UrlParamsProvider } from '../context/UrlParamsContext'; +} from '../context/apm_plugin/apm_plugin_context'; +import { LicenseProvider } from '../context/license/license_context'; +import { UrlParamsProvider } from '../context/url_params_context/url_params_context'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; import { ApmPluginSetupDeps } from '../plugin'; import { createCallApmApi } from '../services/rest/createCallApmApi'; diff --git a/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.stories.tsx b/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.stories.tsx index 1a565ab8708bc5..f4f2be0a6e8897 100644 --- a/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.stories.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { ErrorCountAlertTrigger } from '.'; -import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../../../context/ApmPluginContext/MockApmPluginContext'; +} from '../../../context/apm_plugin/mock_apm_plugin_context'; export default { title: 'app/ErrorCountAlertTrigger', diff --git a/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx index a465b90e7bf05f..efa792ff442734 100644 --- a/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx @@ -10,8 +10,8 @@ import { useParams } from 'react-router-dom'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ALERT_TYPES_CONFIG, AlertType } from '../../../../common/alert_types'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; -import { useEnvironments } from '../../../hooks/useEnvironments'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { EnvironmentField, ServiceField, IsAboveField } from '../fields'; import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; @@ -34,7 +34,11 @@ export function ErrorCountAlertTrigger(props: Props) { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams } = useUrlParams(); const { start, end } = urlParams; - const { environmentOptions } = useEnvironments({ serviceName, start, end }); + const { environmentOptions } = useEnvironmentsFetcher({ + serviceName, + start, + end, + }); const defaults = { threshold: 25, diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.stories.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.stories.tsx index d20aae29fb8ce5..8b2d4e235ac256 100644 --- a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.stories.tsx @@ -8,12 +8,12 @@ import { cloneDeep, merge } from 'lodash'; import React, { ComponentType } from 'react'; import { MemoryRouter, Route } from 'react-router-dom'; import { TransactionDurationAlertTrigger } from '.'; -import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { MockUrlParamsContextProvider } from '../../../context/UrlParamsContext/MockUrlParamsContextProvider'; +} from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; export default { title: 'app/TransactionDurationAlertTrigger', diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx index b7220de8079c98..3566850aa24c46 100644 --- a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx @@ -10,8 +10,8 @@ import { map } from 'lodash'; import React from 'react'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; -import { useEnvironments } from '../../../hooks/useEnvironments'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; @@ -21,7 +21,7 @@ import { TransactionTypeField, IsAboveField, } from '../fields'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; interface AlertParams { windowSize: number; @@ -63,10 +63,14 @@ interface Props { export function TransactionDurationAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; const { urlParams } = useUrlParams(); - const { transactionTypes } = useApmService(); + const { transactionTypes } = useApmServiceContext(); const { serviceName } = useParams<{ serviceName?: string }>(); const { start, end, transactionType } = urlParams; - const { environmentOptions } = useEnvironments({ serviceName, start, end }); + const { environmentOptions } = useEnvironmentsFetcher({ + serviceName, + start, + end, + }); if (!transactionTypes.length || !serviceName) { return null; diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx index e13ed6c1bcd6f3..ff5939c6013758 100644 --- a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx @@ -9,8 +9,8 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { ANOMALY_SEVERITY } from '../../../../../ml/common'; import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; -import { useEnvironments } from '../../../hooks/useEnvironments'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression'; import { @@ -23,7 +23,7 @@ import { ServiceField, TransactionTypeField, } from '../fields'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; interface Params { windowSize: number; @@ -47,10 +47,14 @@ interface Props { export function TransactionDurationAnomalyAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; const { urlParams } = useUrlParams(); - const { transactionTypes } = useApmService(); + const { transactionTypes } = useApmServiceContext(); const { serviceName } = useParams<{ serviceName?: string }>(); const { start, end, transactionType } = urlParams; - const { environmentOptions } = useEnvironments({ serviceName, start, end }); + const { environmentOptions } = useEnvironmentsFetcher({ + serviceName, + start, + end, + }); if (serviceName && !transactionTypes.length) { return null; diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx index 464409ed332e8e..f723febde389d6 100644 --- a/x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx @@ -7,8 +7,8 @@ import { useParams } from 'react-router-dom'; import React from 'react'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ALERT_TYPES_CONFIG, AlertType } from '../../../../common/alert_types'; -import { useEnvironments } from '../../../hooks/useEnvironments'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; @@ -18,7 +18,7 @@ import { EnvironmentField, IsAboveField, } from '../fields'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; interface AlertParams { windowSize: number; @@ -38,10 +38,14 @@ interface Props { export function TransactionErrorRateAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; const { urlParams } = useUrlParams(); - const { transactionTypes } = useApmService(); + const { transactionTypes } = useApmServiceContext(); const { serviceName } = useParams<{ serviceName?: string }>(); const { start, end, transactionType } = urlParams; - const { environmentOptions } = useEnvironments({ serviceName, start, end }); + const { environmentOptions } = useEnvironmentsFetcher({ + serviceName, + start, + end, + }); if (serviceName && !transactionTypes.length) { return null; diff --git a/x-pack/plugins/apm/public/components/app/Correlations/ErrorCorrelations.tsx b/x-pack/plugins/apm/public/components/app/Correlations/ErrorCorrelations.tsx index 3ad71b52b6037d..07ab89afd41088 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/ErrorCorrelations.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/ErrorCorrelations.tsx @@ -17,8 +17,8 @@ import { import React, { useState } from 'react'; import { useParams } from 'react-router-dom'; import { EuiTitle, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { APIReturnType, callApmApi, diff --git a/x-pack/plugins/apm/public/components/app/Correlations/LatencyCorrelations.tsx b/x-pack/plugins/apm/public/components/app/Correlations/LatencyCorrelations.tsx index 4364731501b898..30659cf3f93191 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/LatencyCorrelations.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/LatencyCorrelations.tsx @@ -19,8 +19,8 @@ import React, { useState } from 'react'; import { useParams } from 'react-router-dom'; import { EuiTitle, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { getDurationFormatter } from '../../../../common/utils/formatters'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { APIReturnType, callApmApi, diff --git a/x-pack/plugins/apm/public/components/app/Correlations/SignificantTermsTable.tsx b/x-pack/plugins/apm/public/components/app/Correlations/SignificantTermsTable.tsx index b74517902f89be..350f64367b766f 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/SignificantTermsTable.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/SignificantTermsTable.tsx @@ -10,7 +10,7 @@ import { useHistory } from 'react-router-dom'; import { EuiBasicTable } from '@elastic/eui'; import { asPercent, asInteger } from '../../../../common/utils/formatters'; import { APIReturnType } from '../../../services/rest/createCallApmApi'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { createHref } from '../../shared/Links/url_helpers'; type CorrelationsApiResponse = diff --git a/x-pack/plugins/apm/public/components/app/Correlations/index.tsx b/x-pack/plugins/apm/public/components/app/Correlations/index.tsx index b0f6b83485e39a..16a21e28fc08d7 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/index.tsx @@ -19,10 +19,10 @@ import { } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; import { enableCorrelations } from '../../../../common/ui_settings_keys'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { LatencyCorrelations } from './LatencyCorrelations'; import { ErrorCorrelations } from './ErrorCorrelations'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { createHref } from '../../shared/Links/url_helpers'; export function Correlations() { diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx index 643064b2f31764..c0ce2ed388a12b 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx @@ -22,7 +22,7 @@ import { useHistory } from 'react-router-dom'; import styled from 'styled-components'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; +import type { IUrlParams } from '../../../../context/url_params_context/types'; import { px, unit, units } from '../../../../style/variables'; import { TransactionDetailLink } from '../../../shared/Links/apm/TransactionDetailLink'; import { DiscoverErrorLink } from '../../../shared/Links/DiscoverLinks/DiscoverErrorLink'; diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx index 159f111bee04c3..ab99c6ffa8da03 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx @@ -20,7 +20,7 @@ import d3 from 'd3'; import React from 'react'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { asRelativeDateTimeRange } from '../../../../../common/utils/formatters'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; type ErrorDistributionAPIResponse = APIReturnType<'GET /api/apm/services/{serviceName}/errors/distribution'>; diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx index dc97642dec357c..95ebd5d4036de2 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx @@ -21,14 +21,15 @@ import { RouteComponentProps } from 'react-router-dom'; import styled from 'styled-components'; import { useTrackPageview } from '../../../../../observability/public'; import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; -import { useFetcher } from '../../../hooks/useFetcher'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { callApmApi } from '../../../services/rest/createCallApmApi'; import { fontFamilyCode, fontSizes, px, units } from '../../../style/variables'; import { ApmHeader } from '../../shared/ApmHeader'; import { SearchBar } from '../../shared/search_bar'; import { DetailView } from './DetailView'; import { ErrorDistribution } from './Distribution'; +import { useErrorGroupDistributionFetcher } from '../../../hooks/use_error_group_distribution_fetcher'; const Titles = styled.div` margin-bottom: ${px(units.plus)}; @@ -88,24 +89,10 @@ export function ErrorGroupDetails({ location, match }: ErrorGroupDetailsProps) { } }, [serviceName, start, end, groupId, uiFilters]); - const { data: errorDistributionData } = useFetcher(() => { - if (start && end) { - return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/errors/distribution', - params: { - path: { - serviceName, - }, - query: { - start, - end, - groupId, - uiFilters: JSON.stringify(uiFilters), - }, - }, - }); - } - }, [serviceName, start, end, groupId, uiFilters]); + const { errorDistributionData } = useErrorGroupDistributionFetcher({ + serviceName, + groupId, + }); useTrackPageview({ app: 'apm', path: 'error_group_details' }); useTrackPageview({ app: 'apm', path: 'error_group_details', delay: 15000 }); diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx index 84b72b62248b0f..4022caedadaabc 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx @@ -6,8 +6,8 @@ import { mount } from 'enzyme'; import React from 'react'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; -import { MockUrlParamsContextProvider } from '../../../../../context/UrlParamsContext/MockUrlParamsContextProvider'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../../../context/url_params_context/mock_url_params_context_provider'; import { mockMoment, toJson } from '../../../../../utils/testHelpers'; import { ErrorGroupList } from '../index'; import props from './props.json'; diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx index be1078ea860c30..200a5f467491bd 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx @@ -19,7 +19,7 @@ import { truncate, unit, } from '../../../../style/variables'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { ManagedTable } from '../../../shared/ManagedTable'; import { ErrorDetailLink } from '../../../shared/Links/apm/ErrorDetailLink'; import { TimestampTooltip } from '../../../shared/TimestampTooltip'; diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx index e2a02a2f3e7aec..71cb8e0e016022 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx @@ -16,13 +16,14 @@ import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; -import { useFetcher } from '../../../hooks/useFetcher'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { callApmApi } from '../../../services/rest/createCallApmApi'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { SearchBar } from '../../shared/search_bar'; import { ErrorDistribution } from '../ErrorGroupDetails/Distribution'; import { ErrorGroupList } from './List'; +import { useErrorGroupDistributionFetcher } from '../../../hooks/use_error_group_distribution_fetcher'; interface ErrorGroupOverviewProps { serviceName: string; @@ -30,26 +31,11 @@ interface ErrorGroupOverviewProps { function ErrorGroupOverview({ serviceName }: ErrorGroupOverviewProps) { const { urlParams, uiFilters } = useUrlParams(); - const { start, end, sortField, sortDirection } = urlParams; - - const { data: errorDistributionData } = useFetcher(() => { - if (start && end) { - return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/errors/distribution', - params: { - path: { - serviceName, - }, - query: { - start, - end, - uiFilters: JSON.stringify(uiFilters), - }, - }, - }); - } - }, [serviceName, start, end, uiFilters]); + const { errorDistributionData } = useErrorGroupDistributionFetcher({ + serviceName, + groupId: undefined, + }); const { data: errorGroupListData } = useFetcher(() => { const normalizedSortDirection = sortDirection === 'asc' ? 'asc' : 'desc'; diff --git a/x-pack/plugins/apm/public/components/app/Home/Home.test.tsx b/x-pack/plugins/apm/public/components/app/Home/Home.test.tsx index ab4ca1dfbb49d5..148e0733b93ca2 100644 --- a/x-pack/plugins/apm/public/components/app/Home/Home.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Home/Home.test.tsx @@ -7,7 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; import { Home } from '../Home'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; describe('Home component', () => { it('should render services', () => { 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 ce8f2b0ba611a3..839c087305bd85 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 @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; -import { ApmServiceContextProvider } from '../../../../context/apm_service_context'; +import { ApmServiceContextProvider } from '../../../../context/apm_service/apm_service_context'; import { UNIDENTIFIED_SERVICE_NODES_LABEL } from '../../../../../common/i18n'; import { SERVICE_NODE_NAME_MISSING } from '../../../../../common/service_nodes'; import { APMRouteDefinition } from '../../../../application/routes'; diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx index ac1668a54ab952..10c8417223c773 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import { useFetcher } from '../../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../../hooks/use_fetcher'; import { toQuery } from '../../../../shared/Links/url_helpers'; import { Settings } from '../../../Settings'; import { AgentConfigurationCreateEdit } from '../../../Settings/AgentConfigurations/AgentConfigurationCreateEdit'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx index 3787202f5dee4c..6a56dbf40b33b3 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx @@ -27,7 +27,7 @@ import moment from 'moment'; import React from 'react'; import { useHistory } from 'react-router-dom'; import { useUiSetting$ } from '../../../../../../../../src/plugins/kibana_react/public'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; import { ChartWrapper } from '../ChartWrapper'; import { I18LABELS } from '../translations'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx index 237d33a6a89a38..9fdb34935fee5e 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx @@ -14,7 +14,7 @@ import { EuiToolTip, EuiIconTip, } from '@elastic/eui'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { useUxQuery } from '../hooks/useUxQuery'; import { formatToSec } from '../UXMetrics/KeyUXMetrics'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx index 4c4f7110cafb90..2e6c5c8e23ee52 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx @@ -16,8 +16,8 @@ import { } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { CsmSharedContext } from '../CsmSharedContext'; import { ErrorDetailLink } from '../../../shared/Links/apm/ErrorDetailLink'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx index 4b94b98704da76..d7bc94e6564f19 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx @@ -6,8 +6,8 @@ import React, { useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { BreakdownFilter } from '../Breakdowns/BreakdownFilter'; import { PageLoadDistChart } from '../Charts/PageLoadDistChart'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts index c3f4ab44179fe3..5c545a63d6d056 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useFetcher } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { PercentileRange } from './index'; interface Props { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx index 84668f4b06d772..b339cc7774d754 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx @@ -6,8 +6,8 @@ import React, { useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { BreakdownFilter } from '../Breakdowns/BreakdownFilter'; import { PageViewsChart } from '../Charts/PageViewsChart'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx index 6c7e2e22a9893b..8d759d80352d79 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx @@ -8,9 +8,9 @@ import React from 'react'; import { EuiFlexItem } from '@elastic/eui'; import { EnvironmentFilter } from '../../../shared/EnvironmentFilter'; import { ServiceNameFilter } from '../URLFilter/ServiceNameFilter'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { RUM_AGENT_NAMES } from '../../../../../common/agent_name'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { UserPercentile } from '../UserPercentile'; export function MainFilters() { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx index e4e9109f007e75..c810bd3e7c4893 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx @@ -20,7 +20,7 @@ import { PageLoadAndViews } from './Panels/PageLoadAndViews'; import { VisitorBreakdownsPanel } from './Panels/VisitorBreakdowns'; import { useBreakPoints } from './hooks/useBreakPoints'; import { getPercentileLabel } from './UXMetrics/translations'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; export function RumDashboard() { const { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx index b70621b1e4cbc4..756014004cc9bb 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx @@ -8,7 +8,7 @@ import { EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useEffect, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; -import { useUrlParams } from '../../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../../../shared/Links/url_helpers'; interface Props { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx index abafdf089748bf..a492938deffab6 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { createMemoryHistory } from 'history'; -import * as fetcherHook from '../../../../../../hooks/useFetcher'; +import * as fetcherHook from '../../../../../../hooks/use_fetcher'; import { SelectableUrlList } from '../SelectableUrlList'; import { render } from '../../../utils/test_helper'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx index 67692a9a8554b8..61f75a430706c1 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx @@ -8,8 +8,8 @@ import useDebounce from 'react-use/lib/useDebounce'; import React, { useEffect, useState, FormEvent, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; import { EuiTitle } from '@elastic/eui'; -import { useUrlParams } from '../../../../../hooks/useUrlParams'; -import { useFetcher } from '../../../../../hooks/useFetcher'; +import { useUrlParams } from '../../../../../context/url_params_context/use_url_params'; +import { useFetcher } from '../../../../../hooks/use_fetcher'; import { I18LABELS } from '../../translations'; import { fromQuery, toQuery } from '../../../../shared/Links/url_helpers'; import { formatToSec } from '../../UXMetrics/KeyUXMetrics'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx index ef829ebf7f0cf5..655cdaaca933b0 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx @@ -10,9 +10,9 @@ import { useHistory } from 'react-router-dom'; import { omit } from 'lodash'; import { URLSearch } from './URLSearch'; import { UrlList } from './UrlList'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; -import { removeUndefinedProps } from '../../../../context/UrlParamsContext/helpers'; +import { removeUndefinedProps } from '../../../../context/url_params_context/helpers'; import { LocalUIFilterName } from '../../../../../common/ui_filter'; export function URLFilter() { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx index 2ded35deb58f2d..690595caa6c0e0 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx @@ -20,7 +20,7 @@ import { TBT_LABEL, TBT_TOOLTIP, } from './translations'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { useUxQuery } from '../hooks/useUxQuery'; import { UXMetrics } from '../../../../../../observability/public'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx index 3a6323a747a702..baa9cb7dd74f9f 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { render } from '@testing-library/react'; -import * as fetcherHook from '../../../../../hooks/useFetcher'; +import * as fetcherHook from '../../../../../hooks/use_fetcher'; import { KeyUXMetrics } from '../KeyUXMetrics'; describe('KeyUXMetrics', () => { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx index 95a42ce3018f12..392b42cba12e57 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx @@ -15,11 +15,11 @@ import { } from '@elastic/eui'; import { I18LABELS } from '../translations'; import { KeyUXMetrics } from './KeyUXMetrics'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { useUxQuery } from '../hooks/useUxQuery'; import { CoreVitals } from '../../../../../../observability/public'; import { CsmSharedContext } from '../CsmSharedContext'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { getPercentileLabel } from './translations'; export function UXMetrics() { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx index 04c7e3cc00287f..260c775c7129b5 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx @@ -8,7 +8,7 @@ import React, { useCallback, useEffect } from 'react'; import { EuiSelect } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; import { I18LABELS } from '../translations'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx index ce9485690b9301..77d5697c317504 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui'; import { VisitorBreakdownChart } from '../Charts/VisitorBreakdownChart'; import { I18LABELS, VisitorBreakdownLabel } from '../translations'; -import { useFetcher } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; export function VisitorBreakdown() { const { urlParams, uiFilters } = useUrlParams(); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx index 3a5c3d80ca7d19..eff03c58e99912 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx @@ -21,7 +21,7 @@ import { isErrorEmbeddable, } from '../../../../../../../../src/plugins/embeddable/public'; import { useLayerList } from './useLayerList'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { RenderTooltipContentParams } from '../../../../../../maps/public'; import { MapToolTip } from './MapToolTip'; import { useMapFilters } from './useMapFilters'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts index a1cdf7bb646e5d..54bfa81f26add3 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts @@ -22,7 +22,7 @@ import { } from '../../../../../../maps/common/constants'; import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../../../../src/plugins/apm_oss/public'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { SERVICE_NAME, TRANSACTION_TYPE, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts index 774ac23d231966..c5cf081311f66a 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts @@ -5,7 +5,7 @@ */ import { useMemo } from 'react'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FieldFilter as Filter } from '../../../../../../../../src/plugins/data/common'; import { CLIENT_GEO_COUNTRY_ISO_CODE, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useUxQuery.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useUxQuery.ts index 16396dc9fc15bd..c8cd2c2c64da8f 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useUxQuery.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useUxQuery.ts @@ -5,7 +5,7 @@ */ import { useMemo } from 'react'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; export function useUxQuery() { const { urlParams, uiFilters } = useUrlParams(); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/utils/test_helper.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/utils/test_helper.tsx index 5522cad5690bc3..d5b8cd83d437c4 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/utils/test_helper.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/utils/test_helper.tsx @@ -13,7 +13,7 @@ import { Router } from 'react-router-dom'; import { MemoryHistory } from 'history'; import { EuiThemeProvider } from '../../../../../../observability/public'; import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public'; -import { UrlParamsProvider } from '../../../../context/UrlParamsContext'; +import { UrlParamsProvider } from '../../../../context/url_params_context/url_params_context'; export const core = ({ http: { diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.test.tsx index 1187b71dff8256..659f9f63d0cfa0 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.test.tsx @@ -9,7 +9,7 @@ import { render } from '@testing-library/react'; import cytoscape from 'cytoscape'; import React, { ReactNode } from 'react'; import { ThemeContext } from 'styled-components'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; import { Controls } from './Controls'; import { CytoscapeContext } from './Cytoscape'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx index b4408e20c04d2f..a23fa72314aed8 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx @@ -8,9 +8,9 @@ import { EuiButtonIcon, EuiPanel, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext, useEffect, useState } from 'react'; import styled from 'styled-components'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { useTheme } from '../../../hooks/useTheme'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { useTheme } from '../../../hooks/use_theme'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { getAPMHref } from '../../shared/Links/apm/APMLink'; import { APMQueryParams } from '../../shared/Links/url_helpers'; import { CytoscapeContext } from './Cytoscape'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx index 8a76c5f7bd8f1a..1dea95d369966b 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx @@ -16,7 +16,7 @@ import React, { useRef, useState, } from 'react'; -import { useTheme } from '../../../hooks/useTheme'; +import { useTheme } from '../../../hooks/use_theme'; import { getCytoscapeOptions } from './cytoscape_options'; import { useCytoscapeEventHandlers } from './use_cytoscape_event_handlers'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.tsx index 63a9cf985959f5..07e88294caadbc 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; import { CytoscapeContext } from './Cytoscape'; -import { useTheme } from '../../../hooks/useTheme'; +import { useTheme } from '../../../hooks/use_theme'; const EmptyBannerContainer = styled.div` margin: ${({ theme }) => theme.eui.gutterTypes.gutterSmall}; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx index 788e5f25b6310a..36c0c9f37f9c71 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx @@ -18,7 +18,7 @@ import { getServiceHealthStatus, getServiceHealthStatusColor, } from '../../../../../common/service_health_status'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; import { fontSize, px } from '../../../../style/variables'; import { asInteger, asDuration } from '../../../../../common/utils/formatters'; import { MLJobLink } from '../../../shared/Links/MachineLearningLinks/MLJobLink'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx index f98a7a1b33dd88..d9f33b8fca4cd4 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx @@ -7,7 +7,7 @@ import React, { ReactNode } from 'react'; import { Buttons } from './Buttons'; import { render } from '@testing-library/react'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; function Wrapper({ children }: { children?: ReactNode }) { return {children}; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx index 8670cf623c2532..56110a89ed888c 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx @@ -9,8 +9,8 @@ import { EuiButton, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { MouseEvent } from 'react'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { getAPMHref } from '../../../shared/Links/apm/APMLink'; import { APMQueryParams } from '../../../shared/Links/url_helpers'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx index 70eb5eaf8e576c..313b262508c613 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx @@ -8,8 +8,8 @@ import cytoscape from 'cytoscape'; import { HttpSetup } from 'kibana/public'; import React, { ComponentType } from 'react'; import { EuiThemeProvider } from '../../../../../../observability/public'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; -import { MockUrlParamsContextProvider } from '../../../../context/UrlParamsContext/MockUrlParamsContextProvider'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider'; import { createCallApmApi } from '../../../../services/rest/createCallApmApi'; import { CytoscapeContext } from '../Cytoscape'; import { Popover } from './'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsFetcher.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsFetcher.tsx index be8c5cf8cd4355..3b737c6fa4170f 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsFetcher.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsFetcher.tsx @@ -15,8 +15,8 @@ import { i18n } from '@kbn/i18n'; import { isNumber } from 'lodash'; import { ServiceNodeStats } from '../../../../../common/service_map'; import { ServiceStatsList } from './ServiceStatsList'; -import { useFetcher, FETCH_STATUS } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { AnomalyDetection } from './AnomalyDetection'; import { ServiceAnomalyStats } from '../../../../../common/anomaly_detection'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx index 7b7e3b46bb317a..036d02531f7944 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx @@ -15,7 +15,7 @@ import React, { } from 'react'; import { EuiPopover } from '@elastic/eui'; import cytoscape from 'cytoscape'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; import { SERVICE_NAME } from '../../../../../common/elasticsearch_fieldnames'; import { CytoscapeContext } from '../Cytoscape'; import { getAnimationOptions } from '../cytoscape_options'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape_options.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape_options.ts index d8a8a3c8e9ab4d..e2a54f60486826 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape_options.ts +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape_options.ts @@ -15,7 +15,7 @@ import { getServiceHealthStatusColor, ServiceHealthStatus, } from '../../../../common/service_health_status'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { defaultIcon, iconForNode } from './icons'; export const popoverWidth = 280; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/empty_banner.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/empty_banner.test.tsx index ae27d4d3baf759..a1fb7e7077add6 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/empty_banner.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/empty_banner.test.tsx @@ -7,7 +7,7 @@ import { act, waitFor } from '@testing-library/react'; import cytoscape from 'cytoscape'; import React, { ReactNode } from 'react'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; import { renderWithTheme } from '../../../utils/testHelpers'; import { CytoscapeContext } from './Cytoscape'; import { EmptyBanner } from './EmptyBanner'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx index 2a5b4ce44ff467..97e507d7cc8718 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx @@ -11,9 +11,9 @@ import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; import { License } from '../../../../../licensing/common/license'; import { EuiThemeProvider } from '../../../../../observability/public'; import { FETCH_STATUS } from '../../../../../observability/public/hooks/use_fetcher'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { LicenseContext } from '../../../context/LicenseContext'; -import * as useFetcherModule from '../../../hooks/useFetcher'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { LicenseContext } from '../../../context/license/license_context'; +import * as useFetcherModule from '../../../hooks/use_fetcher'; import { ServiceMap } from './'; const KibanaReactContext = createKibanaReactContext({ diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx index 1731d3f9430d42..48a7f8f77ab840 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -13,10 +13,10 @@ import { isActivePlatinumLicense, SERVICE_MAP_TIMEOUT_ERROR, } from '../../../../common/service_map'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; -import { useLicense } from '../../../hooks/useLicense'; -import { useTheme } from '../../../hooks/useTheme'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { useLicenseContext } from '../../../context/license/use_license_context'; +import { useTheme } from '../../../hooks/use_theme'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { callApmApi } from '../../../services/rest/createCallApmApi'; import { DatePicker } from '../../shared/DatePicker'; import { LicensePrompt } from '../../shared/LicensePrompt'; @@ -70,7 +70,7 @@ export function ServiceMap({ serviceName, }: PropsWithChildren) { const theme = useTheme(); - const license = useLicense(); + const license = useLicenseContext(); const { urlParams } = useUrlParams(); const { data = { elements: [] }, status, error } = useFetcher(() => { diff --git a/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx index 74e7b652d0ebe0..c4c227deb69184 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx @@ -21,8 +21,8 @@ import { asInteger, asPercent, } from '../../../../common/utils/formatters'; -import { useFetcher } from '../../../hooks/useFetcher'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { px, truncate, unit } from '../../../style/variables'; import { ServiceNodeMetricOverviewLink } from '../../shared/Links/apm/ServiceNodeMetricOverviewLink'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx index 7c0869afe0cd1a..18067a43861bdb 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx @@ -21,7 +21,7 @@ import { omitAllOption, getOptionLabel, } from '../../../../../../../common/agent_configuration/all_option'; -import { useFetcher, FETCH_STATUS } from '../../../../../../hooks/useFetcher'; +import { useFetcher, FETCH_STATUS } from '../../../../../../hooks/use_fetcher'; import { FormRowSelect } from './FormRowSelect'; import { APMLink } from '../../../../../shared/Links/apm/APMLink'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingsPage.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingsPage.tsx index 54440559070ad8..7e1146596dd877 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingsPage.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingsPage.tsx @@ -32,8 +32,8 @@ import { validateSetting, } from '../../../../../../../common/agent_configuration/setting_definitions'; import { AgentName } from '../../../../../../../typings/es_schemas/ui/fields/agent'; -import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; -import { FETCH_STATUS } from '../../../../../../hooks/useFetcher'; +import { useApmPluginContext } from '../../../../../../context/apm_plugin/use_apm_plugin_context'; +import { FETCH_STATUS } from '../../../../../../hooks/use_fetcher'; import { saveConfig } from './saveConfig'; import { SettingFormRow } from './SettingFormRow'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx index db3f2c374a1ae6..5ca643428e49c1 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx @@ -14,13 +14,13 @@ import { storiesOf } from '@storybook/react'; import React from 'react'; import { HttpSetup } from 'kibana/public'; import { AgentConfiguration } from '../../../../../../common/agent_configuration/configuration_types'; -import { FETCH_STATUS } from '../../../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../../../hooks/use_fetcher'; import { createCallApmApi } from '../../../../../services/rest/createCallApmApi'; import { AgentConfigurationCreateEdit } from './index'; import { ApmPluginContext, ApmPluginContextValue, -} from '../../../../../context/ApmPluginContext'; +} from '../../../../../context/apm_plugin/apm_plugin_context'; import { EuiThemeProvider } from '../../../../../../../observability/public'; storiesOf( diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx index 4f94f255a4e4c2..998175c895557b 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx @@ -14,7 +14,7 @@ import { AgentConfiguration, AgentConfigurationIntake, } from '../../../../../../common/agent_configuration/configuration_types'; -import { FetcherResult } from '../../../../../hooks/useFetcher'; +import { FetcherResult } from '../../../../../hooks/use_fetcher'; import { fromQuery, toQuery } from '../../../../shared/Links/url_helpers'; import { ServicePage } from './ServicePage/ServicePage'; import { SettingsPage } from './SettingsPage/SettingsPage'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx index adae50db85ada1..958aafa8159df7 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx @@ -13,7 +13,7 @@ import { APIReturnType, callApmApi, } from '../../../../../services/rest/createCallApmApi'; -import { useApmPluginContext } from '../../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context'; type Config = APIReturnType<'GET /api/apm/settings/agent-configuration'>[0]; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx index 81079d78a148a5..be4edbe2ea270f 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx @@ -18,9 +18,9 @@ import React, { useState } from 'react'; import { useLocation } from 'react-router-dom'; import { APIReturnType } from '../../../../../services/rest/createCallApmApi'; import { getOptionLabel } from '../../../../../../common/agent_configuration/all_option'; -import { useApmPluginContext } from '../../../../../hooks/useApmPluginContext'; -import { FETCH_STATUS } from '../../../../../hooks/useFetcher'; -import { useTheme } from '../../../../../hooks/useTheme'; +import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context'; +import { FETCH_STATUS } from '../../../../../hooks/use_fetcher'; +import { useTheme } from '../../../../../hooks/use_theme'; import { px, units } from '../../../../../style/variables'; import { createAgentConfigurationHref, diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx index 12c63f8702f256..c408d5e960cf3b 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx @@ -16,8 +16,8 @@ import { isEmpty } from 'lodash'; import React from 'react'; import { useLocation } from 'react-router-dom'; import { useTrackPageview } from '../../../../../../observability/public'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { createAgentConfigurationHref } from '../../../shared/Links/apm/agentConfigurationLinks'; import { AgentConfigurationList } from './List'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx index 53794ca9965ff8..2adf85181886c8 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx @@ -7,8 +7,8 @@ import { render } from '@testing-library/react'; import React from 'react'; import { ApmIndices } from '.'; -import * as hooks from '../../../../hooks/useFetcher'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import * as hooks from '../../../../hooks/use_fetcher'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; describe('ApmIndices', () => { it('should not get stuck in infinite loop', () => { diff --git a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx index a1ef9ddd87271a..5a5d20cde9ade4 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx @@ -19,10 +19,10 @@ import { EuiButton, EuiButtonEmpty, } from '@elastic/eui'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; import { clearCache } from '../../../../services/rest/callApi'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; const APM_INDEX_LABELS = [ { diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx index 5014584c3928af..ffcb85384642ac 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx @@ -10,7 +10,7 @@ import { NotificationsStart } from 'kibana/public'; import React, { useState } from 'react'; import { px, unit } from '../../../../../../style/variables'; import { callApmApi } from '../../../../../../services/rest/createCallApmApi'; -import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../../../context/apm_plugin/use_apm_plugin_context'; interface Props { onDelete: () => void; diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/index.tsx index c6566af3a8b61b..f9c5aa17e411a8 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/index.tsx @@ -15,7 +15,7 @@ import { import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; import { Filter } from '../../../../../../../common/custom_link/custom_link_types'; -import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../../../context/apm_plugin/use_apm_plugin_context'; import { FiltersSection } from './FiltersSection'; import { FlyoutFooter } from './FlyoutFooter'; import { LinkSection } from './LinkSection'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx index 96a634828f6696..1da7d415b56609 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx @@ -14,15 +14,15 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import * as apmApi from '../../../../../services/rest/createCallApmApi'; import { License } from '../../../../../../../licensing/common/license'; -import * as hooks from '../../../../../hooks/useFetcher'; -import { LicenseContext } from '../../../../../context/LicenseContext'; +import * as hooks from '../../../../../hooks/use_fetcher'; +import { LicenseContext } from '../../../../../context/license/license_context'; import { CustomLinkOverview } from '.'; import { expectTextsInDocument, expectTextsNotInDocument, } from '../../../../../utils/testHelpers'; import * as saveCustomLink from './CreateEditCustomLinkFlyout/saveCustomLink'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; const data = [ { diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx index 771a8c6154dc04..6b5c7d583ee8a8 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx @@ -17,8 +17,8 @@ import { isEmpty } from 'lodash'; import React, { useEffect, useState } from 'react'; import { INVALID_LICENSE } from '../../../../../../common/custom_link'; import { CustomLink } from '../../../../../../common/custom_link/custom_link_types'; -import { FETCH_STATUS, useFetcher } from '../../../../../hooks/useFetcher'; -import { useLicense } from '../../../../../hooks/useLicense'; +import { FETCH_STATUS, useFetcher } from '../../../../../hooks/use_fetcher'; +import { useLicenseContext } from '../../../../../context/license/use_license_context'; import { LicensePrompt } from '../../../../shared/LicensePrompt'; import { CreateCustomLinkButton } from './CreateCustomLinkButton'; import { CreateEditCustomLinkFlyout } from './CreateEditCustomLinkFlyout'; @@ -26,7 +26,7 @@ import { CustomLinkTable } from './CustomLinkTable'; import { EmptyPrompt } from './EmptyPrompt'; export function CustomLinkOverview() { - const license = useLicense(); + const license = useLicenseContext(); const hasValidLicense = license?.isActive && license?.hasAtLeast('gold'); const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); diff --git a/x-pack/plugins/apm/public/components/app/Settings/Settings.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/Settings.test.tsx index 21da12477b0246..cfef7ca937f66e 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/Settings.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/Settings.test.tsx @@ -5,7 +5,7 @@ */ import { render } from '@testing-library/react'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; import React, { ReactNode } from 'react'; import { Settings } from './'; import { createMemoryHistory } from 'history'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx index ccc1778e9fbdea..e709c7e104472a 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx @@ -21,8 +21,8 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ML_ERRORS } from '../../../../../common/anomaly_detection'; -import { useFetcher, FETCH_STATUS } from '../../../../hooks/useFetcher'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { createJobs } from './create_jobs'; import { getEnvironmentLabel } from '../../../../../common/environment_filter_values'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx index 2cda5fcf859099..addfd64a9ef624 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx @@ -9,15 +9,15 @@ import { EuiTitle, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { EuiPanel, EuiEmptyPrompt } from '@elastic/eui'; import { ML_ERRORS } from '../../../../../common/anomaly_detection'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { JobsList } from './jobs_list'; import { AddEnvironments } from './add_environments'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { LicensePrompt } from '../../../shared/LicensePrompt'; -import { useLicense } from '../../../../hooks/useLicense'; +import { useLicenseContext } from '../../../../context/license/use_license_context'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; -export type AnomalyDetectionApiResponse = APIReturnType<'GET /api/apm/settings/anomaly-detection'>; +export type AnomalyDetectionApiResponse = APIReturnType<'GET /api/apm/settings/anomaly-detection/jobs'>; const DEFAULT_VALUE: AnomalyDetectionApiResponse = { jobs: [], @@ -27,7 +27,7 @@ const DEFAULT_VALUE: AnomalyDetectionApiResponse = { export function AnomalyDetection() { const plugin = useApmPluginContext(); const canGetJobs = !!plugin.core.application.capabilities.ml?.canGetJobs; - const license = useLicense(); + const license = useLicenseContext(); const hasValidLicense = license?.isActive && license?.hasAtLeast('platinum'); const [viewAddEnvironments, setViewAddEnvironments] = useState(false); @@ -36,7 +36,7 @@ export function AnomalyDetection() { (callApmApi) => { if (canGetJobs) { return callApmApi({ - endpoint: `GET /api/apm/settings/anomaly-detection`, + endpoint: `GET /api/apm/settings/anomaly-detection/jobs`, }); } }, diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx index 137dcfcdbb4f07..8d6a0740a8a080 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx @@ -16,7 +16,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { ITableColumn, ManagedTable } from '../../../shared/ManagedTable'; import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt'; import { MLJobLink } from '../../../shared/Links/MachineLearningLinks/MLJobLink'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/legacy_jobs_callout.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/legacy_jobs_callout.tsx index 1844e5754cfba5..9e21eb2ffc870a 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/legacy_jobs_callout.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/legacy_jobs_callout.tsx @@ -7,7 +7,7 @@ import { EuiCallOut, EuiButton } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useMlHref } from '../../../../../../ml/public'; export function LegacyJobsCallout() { diff --git a/x-pack/plugins/apm/public/components/app/Settings/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/index.tsx index c9c577285ee807..e974f05fbe9941 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/index.tsx @@ -16,7 +16,7 @@ import React, { ReactNode } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { HeaderMenuPortal } from '../../../../../observability/public'; import { ActionMenu } from '../../../application/action_menu'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { getAPMHref } from '../../shared/Links/apm/APMLink'; import { HomeLink } from '../../shared/Links/apm/HomeLink'; diff --git a/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx b/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx index 1a41ffe1f606f0..3f325f17af82d0 100644 --- a/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx @@ -8,8 +8,8 @@ import { EuiEmptyPrompt } from '@elastic/eui'; import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; import styled from 'styled-components'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { getRedirectToTransactionDetailPageUrl } from './get_redirect_to_transaction_detail_page_url'; import { getRedirectToTracePageUrl } from './get_redirect_to_trace_page_url'; @@ -27,7 +27,7 @@ export function TraceLink({ match }: RouteComponentProps<{ traceId: string }>) { (callApmApi) => { if (traceId) { return callApmApi({ - endpoint: 'GET /api/apm/transaction/{traceId}', + endpoint: 'GET /api/apm/traces/{traceId}/root_transaction', params: { path: { traceId, diff --git a/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx b/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx index e7c0400290dcb7..c07e00ef387c9c 100644 --- a/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx @@ -8,13 +8,13 @@ import { shallow } from 'enzyme'; import React, { ReactNode } from 'react'; import { MemoryRouter, RouteComponentProps } from 'react-router-dom'; import { TraceLink } from './'; -import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../../../context/ApmPluginContext/MockApmPluginContext'; -import * as hooks from '../../../hooks/useFetcher'; -import * as urlParamsHooks from '../../../hooks/useUrlParams'; +} from '../../../context/apm_plugin/mock_apm_plugin_context'; +import * as hooks from '../../../hooks/use_fetcher'; +import * as urlParamsHooks from '../../../context/url_params_context/use_url_params'; function Wrapper({ children }: { children?: ReactNode }) { return ( diff --git a/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx b/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx index cbab2c44132f31..ab10d6b4f46a02 100644 --- a/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx @@ -8,8 +8,8 @@ import { EuiFlexGroup, EuiFlexItem, EuiPage, EuiPanel } from '@elastic/eui'; import React, { useMemo } from 'react'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { APIReturnType } from '../../../services/rest/createCallApmApi'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { SearchBar } from '../../shared/search_bar'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx index 003f2ed05b09ed..bebd5bdabbae37 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx @@ -27,8 +27,8 @@ import { ValuesType } from 'utility-types'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { useTheme } from '../../../../../../observability/public'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; +import type { IUrlParams } from '../../../../context/url_params_context/types'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { unit } from '../../../../style/variables'; import { ChartContainer } from '../../../shared/charts/chart_container'; import { EmptyMessage } from '../../../shared/EmptyMessage'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx index 48413d6207ee3e..43732c23aea647 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx @@ -10,7 +10,7 @@ import { Location } from 'history'; import React from 'react'; import { useHistory } from 'react-router-dom'; import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; +import type { IUrlParams } from '../../../../context/url_params_context/types'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; import { TransactionMetadata } from '../../../shared/MetadataTable/TransactionMetadata'; import { WaterfallContainer } from './WaterfallContainer'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx index e3ba02ce42c2e1..5217c2abb11dc4 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx @@ -9,7 +9,7 @@ import { MemoryRouter } from 'react-router-dom'; import { EuiThemeProvider } from '../../../../../../../observability/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { TraceAPIResponse } from '../../../../../../server/lib/traces/get_trace'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; import { WaterfallContainer } from './index'; import { getWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers'; import { diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx index 501ca6d33d5af1..806350679df55c 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx @@ -6,7 +6,7 @@ import { Location } from 'history'; import React from 'react'; -import { IUrlParams } from '../../../../../context/UrlParamsContext/types'; +import { IUrlParams } from '../../../../../context/url_params_context/types'; import { ServiceLegends } from './ServiceLegends'; import { IWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers'; import { Waterfall } from './Waterfall'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts index f78fe39120d8d1..0f6bebd9037cce 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts @@ -5,7 +5,7 @@ */ import { Location } from 'history'; -import { IUrlParams } from '../../../../../context/UrlParamsContext/types'; +import { IUrlParams } from '../../../../../context/url_params_context/types'; export const location = { pathname: '/services/opbeans-go/transactions/view', diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx index c9420dbb81cb99..d90fe393c94a45 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx @@ -18,7 +18,7 @@ import { Location } from 'history'; import React, { useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; +import type { IUrlParams } from '../../../../context/url_params_context/types'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt'; import { TransactionSummary } from '../../../shared/Summary/TransactionSummary'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx index 8f335ddc71c726..c491b9f0e1eff9 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx @@ -17,19 +17,19 @@ import React, { useMemo } from 'react'; import { isEmpty, flatten } from 'lodash'; import { useHistory } from 'react-router-dom'; import { RouteComponentProps } from 'react-router-dom'; -import { useTransactionCharts } from '../../../hooks/useTransactionCharts'; -import { useTransactionDistribution } from '../../../hooks/useTransactionDistribution'; -import { useWaterfall } from '../../../hooks/useWaterfall'; +import { useTransactionChartsFetcher } from '../../../hooks/use_transaction_charts_fetcher'; +import { useTransactionDistributionFetcher } from '../../../hooks/use_transaction_distribution_fetcher'; +import { useWaterfallFetcher } from './use_waterfall_fetcher'; import { ApmHeader } from '../../shared/ApmHeader'; import { TransactionCharts } from '../../shared/charts/transaction_charts'; import { TransactionDistribution } from './Distribution'; import { WaterfallWithSummmary } from './WaterfallWithSummmary'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; -import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event_context'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; import { fromQuery, toQuery } from '../../shared/Links/url_helpers'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { HeightRetainer } from '../../shared/HeightRetainer'; import { Correlations } from '../Correlations'; @@ -50,18 +50,20 @@ export function TransactionDetails({ const { urlParams } = useUrlParams(); const history = useHistory(); const { - data: distributionData, - status: distributionStatus, - } = useTransactionDistribution(urlParams); + distributionData, + distributionStatus, + } = useTransactionDistributionFetcher(); const { - data: transactionChartsData, - status: transactionChartsStatus, - } = useTransactionCharts(); + transactionChartsData, + transactionChartsStatus, + } = useTransactionChartsFetcher(); - const { waterfall, exceedsMax, status: waterfallStatus } = useWaterfall( - urlParams - ); + const { + waterfall, + exceedsMax, + status: waterfallStatus, + } = useWaterfallFetcher(); const { transactionName, transactionType } = urlParams; useTrackPageview({ app: 'apm', path: 'transaction_details' }); diff --git a/x-pack/plugins/apm/public/hooks/useWaterfall.ts b/x-pack/plugins/apm/public/components/app/TransactionDetails/use_waterfall_fetcher.ts similarity index 75% rename from x-pack/plugins/apm/public/hooks/useWaterfall.ts rename to x-pack/plugins/apm/public/components/app/TransactionDetails/use_waterfall_fetcher.ts index 6264ec45088a25..7458fa79bd1f3f 100644 --- a/x-pack/plugins/apm/public/hooks/useWaterfall.ts +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/use_waterfall_fetcher.ts @@ -5,9 +5,9 @@ */ import { useMemo } from 'react'; -import { IUrlParams } from '../context/UrlParamsContext/types'; -import { useFetcher } from './useFetcher'; -import { getWaterfall } from '../components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { getWaterfall } from './WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; const INITIAL_DATA = { root: undefined, @@ -15,7 +15,8 @@ const INITIAL_DATA = { errorsPerTransaction: {}, }; -export function useWaterfall(urlParams: IUrlParams) { +export function useWaterfallFetcher() { + const { urlParams } = useUrlParams(); const { traceId, start, end, transactionId } = urlParams; const { data = INITIAL_DATA, status, error } = useFetcher( (callApmApi) => { diff --git a/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx b/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx index 003bd6ba4c122b..ae0dd85b6a8b55 100644 --- a/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx +++ b/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import React, { ReactNode } from 'react'; import { isJavaAgentName, isRumAgentName } from '../../../../common/agent_name'; import { enableServiceOverview } from '../../../../common/ui_settings_keys'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { useErrorOverviewHref } from '../../shared/Links/apm/ErrorOverviewLink'; import { useMetricOverviewHref } from '../../shared/Links/apm/MetricOverviewLink'; import { useServiceMapHref } from '../../shared/Links/apm/ServiceMapLink'; @@ -23,7 +23,7 @@ import { ServiceMetrics } from '../service_metrics'; import { ServiceNodeOverview } from '../ServiceNodeOverview'; import { ServiceOverview } from '../service_overview'; import { TransactionOverview } from '../transaction_overview'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; interface Tab { key: string; @@ -44,7 +44,7 @@ interface Props { } export function ServiceDetailTabs({ serviceName, tab }: Props) { - const { agentName } = useApmService(); + const { agentName } = useApmServiceContext(); const { uiSettings } = useApmPluginContext().core; const overviewTab = { diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx index e8ad3e65b1a47e..3b97849b077902 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx @@ -10,7 +10,7 @@ import { getServiceHealthStatusLabel, ServiceHealthStatus, } from '../../../../../common/service_health_status'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; export function HealthBadge({ healthStatus, diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx index 39cb73d2a0dd9b..1c6fa9fe0447e1 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx @@ -7,7 +7,7 @@ import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { ServiceHealthStatus } from '../../../../../common/service_health_status'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { mockMoment, renderWithTheme } from '../../../../utils/testHelpers'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { ServiceList, SERVICE_COLUMNS } from './'; diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx index 3c84b3982642d4..b1d725bba0ca90 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx @@ -17,17 +17,17 @@ import url from 'url'; import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; -import { useAnomalyDetectionJobs } from '../../../hooks/useAnomalyDetectionJobs'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { useLocalStorage } from '../../../hooks/useLocalStorage'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { SearchBar } from '../../shared/search_bar'; import { Correlations } from '../Correlations'; import { NoServicesMessage } from './no_services_message'; import { ServiceList } from './ServiceList'; import { MLCallout } from './ServiceList/MLCallout'; +import { useAnomalyDetectionJobsFetcher } from './use_anomaly_detection_jobs_fetcher'; const initialData = { items: [], @@ -37,12 +37,10 @@ const initialData = { let hasDisplayedToast = false; -export function ServiceInventory() { +function useServicesFetcher() { + const { urlParams, uiFilters } = useUrlParams(); const { core } = useApmPluginContext(); - const { - urlParams: { start, end }, - uiFilters, - } = useUrlParams(); + const { start, end } = urlParams; const { data = initialData, status } = useFetcher( (callApmApi) => { if (start && end) { @@ -92,6 +90,13 @@ export function ServiceInventory() { } }, [data.hasLegacyData, core.http.basePath, core.notifications.toasts]); + return { servicesData: data, servicesStatus: status }; +} + +export function ServiceInventory() { + const { core } = useApmPluginContext(); + const { servicesData, servicesStatus } = useServicesFetcher(); + // The page is called "service inventory" to avoid confusion with the // "service overview", but this is tracked in some dashboards because it's the // initial landing page for APM, so it stays as "services_overview" (plural.) @@ -110,9 +115,9 @@ export function ServiceInventory() { ); const { - data: anomalyDetectionJobsData, - status: anomalyDetectionJobsStatus, - } = useAnomalyDetectionJobs(); + anomalyDetectionJobsData, + anomalyDetectionJobsStatus, + } = useAnomalyDetectionJobsFetcher(); const [userHasDismissedCallout, setUserHasDismissedCallout] = useLocalStorage( 'apm.userHasDismissedServiceInventoryMlCallout', @@ -148,11 +153,11 @@ export function ServiceInventory() { } /> diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx index 0fc2a2b4cdcef0..cf1ccfbd36aaf3 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx @@ -6,8 +6,8 @@ import { render } from '@testing-library/react'; import React, { ReactNode } from 'react'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { NoServicesMessage } from './no_services_message'; function Wrapper({ children }: { children?: ReactNode }) { diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx index d2763c6632c652..b20efc440312c8 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { KibanaLink } from '../../shared/Links/KibanaLink'; import { SetupInstructionsLink } from '../../shared/Links/SetupInstructionsLink'; import { LoadingStatePrompt } from '../../shared/LoadingStatePrompt'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { ErrorStatePrompt } from '../../shared/ErrorStatePrompt'; interface Props { diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx index de5e92664a769c..1c838a01d05c74 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx @@ -13,17 +13,17 @@ import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; import { ServiceHealthStatus } from '../../../../common/service_health_status'; import { ServiceInventory } from '.'; import { EuiThemeProvider } from '../../../../../observability/public'; -import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../../../context/ApmPluginContext/MockApmPluginContext'; -import * as useAnomalyDetectionJobs from '../../../hooks/useAnomalyDetectionJobs'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +} from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import * as useLocalUIFilters from '../../../hooks/useLocalUIFilters'; -import * as useDynamicIndexPatternHooks from '../../../hooks/useDynamicIndexPattern'; +import * as useDynamicIndexPatternHooks from '../../../hooks/use_dynamic_index_pattern'; import { SessionStorageMock } from '../../../services/__test__/SessionStorageMock'; -import { MockUrlParamsContextProvider } from '../../../context/UrlParamsContext/MockUrlParamsContextProvider'; +import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; +import * as hook from './use_anomaly_detection_jobs_fetcher'; const KibanaReactContext = createKibanaReactContext({ usageCollection: { reportUiStats: () => {} }, @@ -80,19 +80,13 @@ describe('ServiceInventory', () => { status: FETCH_STATUS.SUCCESS, }); - jest - .spyOn(useAnomalyDetectionJobs, 'useAnomalyDetectionJobs') - .mockReturnValue({ - status: FETCH_STATUS.SUCCESS, - data: { - jobs: [], - hasLegacyJobs: false, - }, - refetch: () => undefined, - }); + jest.spyOn(hook, 'useAnomalyDetectionJobsFetcher').mockReturnValue({ + anomalyDetectionJobsStatus: FETCH_STATUS.SUCCESS, + anomalyDetectionJobsData: { jobs: [], hasLegacyJobs: false }, + }); jest - .spyOn(useDynamicIndexPatternHooks, 'useDynamicIndexPattern') + .spyOn(useDynamicIndexPatternHooks, 'useDynamicIndexPatternFetcher') .mockReturnValue({ indexPattern: undefined, status: FETCH_STATUS.SUCCESS, diff --git a/x-pack/plugins/apm/public/hooks/useAnomalyDetectionJobs.ts b/x-pack/plugins/apm/public/components/app/service_inventory/use_anomaly_detection_jobs_fetcher.ts similarity index 50% rename from x-pack/plugins/apm/public/hooks/useAnomalyDetectionJobs.ts rename to x-pack/plugins/apm/public/components/app/service_inventory/use_anomaly_detection_jobs_fetcher.ts index 5bb36720e7b9bd..901841ac4d593f 100644 --- a/x-pack/plugins/apm/public/hooks/useAnomalyDetectionJobs.ts +++ b/x-pack/plugins/apm/public/components/app/service_inventory/use_anomaly_detection_jobs_fetcher.ts @@ -3,16 +3,15 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { useFetcher } from '../../../hooks/use_fetcher'; -import { useFetcher } from './useFetcher'; - -export function useAnomalyDetectionJobs() { - return useFetcher( +export function useAnomalyDetectionJobsFetcher() { + const { data, status } = useFetcher( (callApmApi) => - callApmApi({ - endpoint: `GET /api/apm/settings/anomaly-detection`, - }), + callApmApi({ endpoint: `GET /api/apm/settings/anomaly-detection/jobs` }), [], { showToastOnError: false } ); + + return { anomalyDetectionJobsData: data, anomalyDetectionJobsStatus: status }; } diff --git a/x-pack/plugins/apm/public/components/app/service_metrics/index.tsx b/x-pack/plugins/apm/public/components/app/service_metrics/index.tsx index d0f8fc1e613323..bf99f5c87fa6a8 100644 --- a/x-pack/plugins/apm/public/components/app/service_metrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_metrics/index.tsx @@ -13,10 +13,10 @@ import { EuiFlexGroup, } from '@elastic/eui'; import React, { useMemo } from 'react'; -import { useServiceMetricCharts } from '../../../hooks/useServiceMetricCharts'; +import { useServiceMetricChartsFetcher } from '../../../hooks/use_service_metric_charts_fetcher'; import { MetricsChart } from '../../shared/charts/metrics_chart'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event_context'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; import { Projection } from '../../../../common/projections'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { SearchBar } from '../../shared/search_bar'; @@ -31,7 +31,9 @@ export function ServiceMetrics({ serviceName, }: ServiceMetricsProps) { const { urlParams } = useUrlParams(); - const { data, status } = useServiceMetricCharts(urlParams, agentName); + const { data, status } = useServiceMetricChartsFetcher({ + serviceNodeName: undefined, + }); const { start, end } = urlParams; const localFiltersConfig: React.ComponentProps< diff --git a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.test.tsx b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.test.tsx index c6f7e68e4f4d06..0ba45fae15fef6 100644 --- a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { ServiceNodeMetrics } from '.'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; import { RouteComponentProps } from 'react-router-dom'; describe('ServiceNodeMetrics', () => { diff --git a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx index 11de40b47ff861..aa1d9cccbdfa62 100644 --- a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx @@ -22,11 +22,11 @@ import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; import styled from 'styled-components'; import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes'; -import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event_context'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; -import { useServiceMetricCharts } from '../../../hooks/useServiceMetricCharts'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { useServiceMetricChartsFetcher } from '../../../hooks/use_service_metric_charts_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { px, truncate, unit } from '../../../style/variables'; import { ApmHeader } from '../../shared/ApmHeader'; import { MetricsChart } from '../../shared/charts/metrics_chart'; @@ -58,12 +58,8 @@ type ServiceNodeMetricsProps = RouteComponentProps<{ export function ServiceNodeMetrics({ match }: ServiceNodeMetricsProps) { const { urlParams, uiFilters } = useUrlParams(); const { serviceName, serviceNodeName } = match.params; - const { agentName } = useApmService(); - const { data } = useServiceMetricCharts( - urlParams, - agentName, - serviceNodeName - ); + const { agentName } = useApmServiceContext(); + const { data } = useServiceMetricChartsFetcher({ serviceNodeName }); const { start, end } = urlParams; const { data: { host, containerId } = INITIAL_DATA, status } = useFetcher( diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 15125128d97816..dcb407d27e6901 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -15,7 +15,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { useTrackPageview } from '../../../../../observability/public'; import { isRumAgentName } from '../../../../common/agent_name'; -import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event_context'; +import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; import { TransactionBreakdownChart } from '../../shared/charts/transaction_breakdown_chart'; import { TransactionErrorRateChart } from '../../shared/charts/transaction_error_rate_chart'; import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink'; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx index b364f027538a6d..949f5cce0a64f9 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx @@ -8,17 +8,17 @@ import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { CoreStart } from 'src/core/public'; import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; -import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { MockUrlParamsContextProvider } from '../../../context/UrlParamsContext/MockUrlParamsContextProvider'; -import * as useDynamicIndexPatternHooks from '../../../hooks/useDynamicIndexPattern'; -import * as useFetcherHooks from '../../../hooks/useFetcher'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; -import * as useAnnotationsHooks from '../../../hooks/use_annotations'; -import * as useTransactionBreakdownHooks from '../../../hooks/use_transaction_breakdown'; +} from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; +import * as useDynamicIndexPatternHooks from '../../../hooks/use_dynamic_index_pattern'; +import * as useFetcherHooks from '../../../hooks/use_fetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import * as useAnnotationsHooks from '../../../context/annotations/use_annotations_context'; +import * as useTransactionBreakdownHooks from '../../shared/charts/transaction_breakdown_chart/use_transaction_breakdown'; import { renderWithTheme } from '../../../utils/testHelpers'; import { ServiceOverview } from './'; @@ -56,10 +56,10 @@ function Wrapper({ children }: { children?: ReactNode }) { describe('ServiceOverview', () => { it('renders', () => { jest - .spyOn(useAnnotationsHooks, 'useAnnotations') + .spyOn(useAnnotationsHooks, 'useAnnotationsContext') .mockReturnValue({ annotations: [] }); jest - .spyOn(useDynamicIndexPatternHooks, 'useDynamicIndexPattern') + .spyOn(useDynamicIndexPatternHooks, 'useDynamicIndexPatternFetcher') .mockReturnValue({ indexPattern: undefined, status: FETCH_STATUS.SUCCESS, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx index b4228878dd9f5b..6e183924a80a73 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx @@ -14,8 +14,8 @@ import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; import styled from 'styled-components'; import { asInteger } from '../../../../../common/utils/formatters'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; import { px, truncate, unit } from '../../../../style/variables'; import { SparkPlotWithValueLabel } from '../../../shared/charts/spark_plot/spark_plot_with_value_label'; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx index 94d92bfbe89ddc..1662f44d1e4216 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx @@ -9,10 +9,10 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { useParams } from 'react-router-dom'; import { asTransactionRate } from '../../../../common/utils/formatters'; -import { useFetcher } from '../../../hooks/useFetcher'; -import { useTheme } from '../../../hooks/useTheme'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { useTheme } from '../../../hooks/use_theme'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { callApmApi } from '../../../services/rest/createCallApmApi'; import { TimeseriesChart } from '../../shared/charts/timeseries_chart'; @@ -24,7 +24,7 @@ export function ServiceOverviewThroughputChart({ const theme = useTheme(); const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); - const { transactionType } = useApmService(); + const { transactionType } = useApmServiceContext(); const { start, end } = urlParams; const { data, status } = useFetcher(() => { diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx index e241bc2fed05a4..6b02a44dcc2f46 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx @@ -21,8 +21,8 @@ import { asTransactionRate, } from '../../../../../common/utils/formatters'; import { px, truncate, unit } from '../../../../style/variables'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APIReturnType, callApmApi, diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/TransactionList/TransactionList.stories.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/TransactionList/TransactionList.stories.tsx index 953397b9f3d5fb..c14c31afe0445e 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/TransactionList/TransactionList.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/TransactionList/TransactionList.stories.tsx @@ -7,7 +7,7 @@ import React, { ComponentType } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { TransactionList } from './'; type TransactionGroup = APIReturnType<'GET /api/apm/services/{serviceName}/transaction_groups'>['items'][0]; diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx index 28a27c034265a9..9ff4ad916b174c 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx @@ -23,10 +23,10 @@ import { useLocation } from 'react-router-dom'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types'; -import { IUrlParams } from '../../../context/UrlParamsContext/types'; -import { useTransactionCharts } from '../../../hooks/useTransactionCharts'; -import { useTransactionList } from '../../../hooks/useTransactionList'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { IUrlParams } from '../../../context/url_params_context/types'; +import { useTransactionChartsFetcher } from '../../../hooks/use_transaction_charts_fetcher'; +import { useTransactionListFetcher } from './use_transaction_list'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { TransactionCharts } from '../../shared/charts/transaction_charts'; import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; import { fromQuery, toQuery } from '../../shared/Links/url_helpers'; @@ -37,7 +37,7 @@ import { Correlations } from '../Correlations'; import { TransactionList } from './TransactionList'; import { useRedirect } from './useRedirect'; import { UserExperienceCallout } from './user_experience_callout'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; function getRedirectLocation({ location, @@ -68,22 +68,22 @@ interface TransactionOverviewProps { export function TransactionOverview({ serviceName }: TransactionOverviewProps) { const location = useLocation(); const { urlParams } = useUrlParams(); - const { transactionType, transactionTypes } = useApmService(); + const { transactionType, transactionTypes } = useApmServiceContext(); // redirect to first transaction type useRedirect(getRedirectLocation({ location, transactionType, urlParams })); const { - data: transactionCharts, - status: transactionChartsStatus, - } = useTransactionCharts(); + transactionChartsData, + transactionChartsStatus, + } = useTransactionChartsFetcher(); useTrackPageview({ app: 'apm', path: 'transaction_overview' }); useTrackPageview({ app: 'apm', path: 'transaction_overview', delay: 15000 }); const { - data: transactionListData, - status: transactionListStatus, - } = useTransactionList(urlParams); + transactionListData, + transactionListStatus, + } = useTransactionListFetcher(); const localFiltersConfig: React.ComponentProps< typeof LocalUIFilters @@ -134,7 +134,7 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { )} diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx index d4a8b3a46991c0..93d56ea19024e0 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx @@ -10,13 +10,13 @@ import { CoreStart } from 'kibana/public'; import React from 'react'; import { Router } from 'react-router-dom'; import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { ApmServiceContextProvider } from '../../../context/apm_service_context'; -import { UrlParamsProvider } from '../../../context/UrlParamsContext'; -import { IUrlParams } from '../../../context/UrlParamsContext/types'; -import * as useFetcherHook from '../../../hooks/useFetcher'; -import * as useServiceTransactionTypesHook from '../../../hooks/use_service_transaction_types'; -import * as useServiceAgentNameHook from '../../../hooks/use_service_agent_name'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { ApmServiceContextProvider } from '../../../context/apm_service/apm_service_context'; +import { UrlParamsProvider } from '../../../context/url_params_context/url_params_context'; +import { IUrlParams } from '../../../context/url_params_context/types'; +import * as useFetcherHook from '../../../hooks/use_fetcher'; +import * as useServiceTransactionTypesHook from '../../../context/apm_service/use_service_transaction_types_fetcher'; +import * as useServiceAgentNameHook from '../../../context/apm_service/use_service_agent_name_fetcher'; import { disableConsoleWarning, renderWithTheme, @@ -46,15 +46,17 @@ function setup({ // mock transaction types jest - .spyOn(useServiceTransactionTypesHook, 'useServiceTransactionTypes') + .spyOn(useServiceTransactionTypesHook, 'useServiceTransactionTypesFetcher') .mockReturnValue(serviceTransactionTypes); // mock agent - jest.spyOn(useServiceAgentNameHook, 'useServiceAgentName').mockReturnValue({ - agentName: 'nodejs', - error: undefined, - status: useFetcherHook.FETCH_STATUS.SUCCESS, - }); + jest + .spyOn(useServiceAgentNameHook, 'useServiceAgentNameFetcher') + .mockReturnValue({ + agentName: 'nodejs', + error: undefined, + status: useFetcherHook.FETCH_STATUS.SUCCESS, + }); jest.spyOn(useFetcherHook, 'useFetcher').mockReturnValue({} as any); diff --git a/x-pack/plugins/apm/public/hooks/useTransactionList.ts b/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts similarity index 75% rename from x-pack/plugins/apm/public/hooks/useTransactionList.ts rename to x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts index 92b54beb715db7..78883ec2cf0d39 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionList.ts +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts @@ -5,10 +5,9 @@ */ import { useParams } from 'react-router-dom'; -import { useUiFilters } from '../context/UrlParamsContext'; -import { IUrlParams } from '../context/UrlParamsContext/types'; -import { APIReturnType } from '../services/rest/createCallApmApi'; -import { useFetcher } from './useFetcher'; +import { APIReturnType } from '../../../services/rest/createCallApmApi'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; type TransactionsAPIResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transaction_groups'>; @@ -18,10 +17,10 @@ const DEFAULT_RESPONSE: Partial = { bucketSize: 0, }; -export function useTransactionList(urlParams: IUrlParams) { +export function useTransactionListFetcher() { + const { urlParams, uiFilters } = useUrlParams(); const { serviceName } = useParams<{ serviceName?: string }>(); const { transactionType, start, end } = urlParams; - const uiFilters = useUiFilters(urlParams); const { data = DEFAULT_RESPONSE, error, status } = useFetcher( (callApmApi) => { if (serviceName && start && end && transactionType) { @@ -43,8 +42,8 @@ export function useTransactionList(urlParams: IUrlParams) { ); return { - data, - status, - error, + transactionListData: data, + transactionListStatus: status, + transactionListError: error, }; } diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/user_experience_callout.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/user_experience_callout.tsx index 41e84d4acfba5b..6e1154a458d6e9 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/user_experience_callout.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/user_experience_callout.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiButton, EuiCallOut, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; export function UserExperienceCallout() { const { core } = useApmPluginContext(); diff --git a/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx b/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx index 56501d8c916f47..dd88b1ea7eb734 100644 --- a/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx @@ -8,8 +8,8 @@ import { EuiTitle } from '@elastic/eui'; import React, { ComponentType } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { HttpSetup } from '../../../../../../../src/core/public'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { MockUrlParamsContextProvider } from '../../../context/UrlParamsContext/MockUrlParamsContextProvider'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; import { createCallApmApi } from '../../../services/rest/createCallApmApi'; import { ApmHeader } from './'; diff --git a/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx b/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx index a806a3ea601541..04e03cda6a61e8 100644 --- a/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx @@ -9,7 +9,7 @@ import React, { ReactNode } from 'react'; import styled from 'styled-components'; import { HeaderMenuPortal } from '../../../../../observability/public'; import { ActionMenu } from '../../../application/action_menu'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { EnvironmentFilter } from '../EnvironmentFilter'; const HeaderFlexGroup = styled(EuiFlexGroup)` diff --git a/x-pack/plugins/apm/public/components/shared/DatePicker/date_picker.test.tsx b/x-pack/plugins/apm/public/components/shared/DatePicker/date_picker.test.tsx index 520cc2f423ddde..222c27cc7ed6d8 100644 --- a/x-pack/plugins/apm/public/components/shared/DatePicker/date_picker.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/DatePicker/date_picker.test.tsx @@ -10,12 +10,12 @@ import { mount } from 'enzyme'; import { createMemoryHistory } from 'history'; import React, { ReactNode } from 'react'; import { Router } from 'react-router-dom'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; import { UrlParamsContext, useUiFilters, -} from '../../../context/UrlParamsContext'; -import { IUrlParams } from '../../../context/UrlParamsContext/types'; +} from '../../../context/url_params_context/url_params_context'; +import { IUrlParams } from '../../../context/url_params_context/types'; import { DatePicker } from './'; const history = createMemoryHistory(); diff --git a/x-pack/plugins/apm/public/components/shared/DatePicker/index.tsx b/x-pack/plugins/apm/public/components/shared/DatePicker/index.tsx index f35cc067489118..f847ce0b6e96fa 100644 --- a/x-pack/plugins/apm/public/components/shared/DatePicker/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/DatePicker/index.tsx @@ -8,8 +8,8 @@ import { EuiSuperDatePicker } from '@elastic/eui'; import React, { useEffect } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; import { UI_SETTINGS } from '../../../../../../../src/plugins/data/common'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { clearCache } from '../../../services/rest/callApi'; import { fromQuery, toQuery } from '../Links/url_helpers'; import { TimePickerQuickRange, TimePickerTimeDefaults } from './typings'; diff --git a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx index cace4c2770f370..4522cfa7195fd4 100644 --- a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx @@ -13,8 +13,8 @@ import { ENVIRONMENT_ALL, ENVIRONMENT_NOT_DEFINED, } from '../../../../common/environment_filter_values'; -import { useEnvironments } from '../../../hooks/useEnvironments'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../Links/url_helpers'; function updateEnvironmentUrl( @@ -67,7 +67,7 @@ export function EnvironmentFilter() { const { environment } = uiFilters; const { start, end } = urlParams; - const { environments, status = 'loading' } = useEnvironments({ + const { environments, status = 'loading' } = useEnvironmentsFetcher({ serviceName, start, end, diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts b/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts index e7dd03db6b63cf..2276704edc342e 100644 --- a/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts +++ b/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts @@ -13,7 +13,7 @@ import { TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { UIProcessorEvent } from '../../../../common/processor_event'; -import { IUrlParams } from '../../../context/UrlParamsContext/types'; +import { IUrlParams } from '../../../context/url_params_context/types'; export function getBoolFilter({ groupId, diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx index 2ef93fc32200e7..5284e3f6aa0113 100644 --- a/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx @@ -14,9 +14,9 @@ import { IIndexPattern, QuerySuggestion, } from '../../../../../../../src/plugins/data/public'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { useDynamicIndexPattern } from '../../../hooks/useDynamicIndexPattern'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { useDynamicIndexPatternFetcher } from '../../../hooks/use_dynamic_index_pattern'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../Links/url_helpers'; import { getBoolFilter } from './get_bool_filter'; // @ts-expect-error @@ -65,7 +65,7 @@ export function KueryBar() { const example = examples[processorEvent || 'defaults']; - const { indexPattern } = useDynamicIndexPattern(processorEvent); + const { indexPattern } = useDynamicIndexPatternFetcher(processorEvent); const placeholder = i18n.translate('xpack.apm.kueryBar.placeholder', { defaultMessage: `Search {event, select, diff --git a/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx b/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx index 1819e71a49753a..bd68e7db777145 100644 --- a/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx @@ -9,7 +9,7 @@ import { LicensePrompt } from '.'; import { ApmPluginContext, ApmPluginContextValue, -} from '../../../context/ApmPluginContext'; +} from '../../../context/apm_plugin/apm_plugin_context'; const contextMock = ({ core: { http: { basePath: { prepend: () => {} } } }, diff --git a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx index 93b5672aa54f98..70286655bba882 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx @@ -12,7 +12,7 @@ import { useLocation } from 'react-router-dom'; import rison, { RisonValue } from 'rison-node'; import url from 'url'; import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../../../../src/plugins/apm_oss/public'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { getTimepickerRisonData } from '../rison_helpers'; interface Props { diff --git a/x-pack/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx index 8c2829a515f831..e2447cc7a67a53 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx @@ -6,7 +6,7 @@ import { EuiLink, EuiLinkAnchorProps } from '@elastic/eui'; import React from 'react'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; // union type constisting of valid guide sections that we link to type DocsSection = '/apm/get-started' | '/x-pack' | '/apm/server' | '/kibana'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/InfraLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/InfraLink.tsx index 630235e54c9fa5..6d4bbbbfc2f804 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/InfraLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/InfraLink.tsx @@ -9,7 +9,7 @@ import { IBasePath } from 'kibana/public'; import React from 'react'; import url from 'url'; import { InfraAppId } from '../../../../../infra/public'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { fromQuery } from './url_helpers'; interface InfraQueryParams { diff --git a/x-pack/plugins/apm/public/components/shared/Links/KibanaLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/KibanaLink.tsx index 8aa0d4f5a33543..ab44374f48167f 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/KibanaLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/KibanaLink.tsx @@ -7,7 +7,7 @@ import { EuiLink, EuiLinkAnchorProps } from '@elastic/eui'; import React from 'react'; import url from 'url'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; interface Props extends EuiLinkAnchorProps { path?: string; diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx index 5fbcd475cb47ba..7bf017fb239e3c 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx @@ -6,9 +6,9 @@ import { EuiLink } from '@elastic/eui'; import React from 'react'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useMlHref, ML_PAGES } from '../../../../../../ml/public'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; interface MlRisonData { ml?: { diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.ts b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.ts index 0f671fd363c75a..eabef034bf3d99 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.ts +++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useMlHref } from '../../../../../../ml/public'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; export function useTimeSeriesExplorerHref({ jobId, diff --git a/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx index 0ff73d91d7c5b0..68bee36dbe2832 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx @@ -7,7 +7,7 @@ import { EuiButton, EuiButtonEmpty, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; const SETUP_INSTRUCTIONS_LABEL = i18n.translate( 'xpack.apm.setupInstructionsButtonLabel', diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/APMLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/APMLink.tsx index 41c932bf9c9f55..98046193e3807e 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/APMLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/APMLink.tsx @@ -11,8 +11,8 @@ import React from 'react'; import { useLocation } from 'react-router-dom'; import url from 'url'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APMQueryParams, fromQuery, toQuery } from '../url_helpers'; interface Props extends EuiLinkAnchorProps { diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx index 30b91fe2564f16..dcf21de7dca8df 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APMQueryParams } from '../url_helpers'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx index fbae80203f03bb..de7130e8786081 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APMQueryParams } from '../url_helpers'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeMetricOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeMetricOverviewLink.tsx index 2553ec4353194d..afdb177e467d82 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeMetricOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeMetricOverviewLink.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { APMLink, APMLinkExtendProps } from './APMLink'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { pickKeys } from '../../../../../common/utils/pick_keys'; interface Props extends APMLinkExtendProps { diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeOverviewLink.tsx index 0a9553bcbfe6ca..c107b436717c2d 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeOverviewLink.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { pickKeys } from '../../../../../common/utils/pick_keys'; import { APMQueryParams } from '../url_helpers'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/TraceOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/TraceOverviewLink.tsx index 6aa362707800fd..caa1498e6df87a 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/TraceOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/TraceOverviewLink.tsx @@ -11,7 +11,7 @@ */ import React from 'react'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APMQueryParams } from '../url_helpers'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionDetailLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionDetailLink.tsx index c9b26b557512ca..ee798e0208c2ba 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionDetailLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionDetailLink.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { APMLink, APMLinkExtendProps } from './APMLink'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { pickKeys } from '../../../../../common/utils/pick_keys'; interface Props extends APMLinkExtendProps { diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx index 23e795b026d0cf..92ff1b8a68ac02 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { pickKeys } from '../../../../../common/utils/pick_keys'; import { APMQueryParams } from '../url_helpers'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx index 039d9dcb1c0ed9..318a1590be77c3 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx @@ -11,7 +11,7 @@ */ import React from 'react'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APMQueryParams } from '../url_helpers'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; diff --git a/x-pack/plugins/apm/public/components/shared/LocalUIFilters/TransactionTypeFilter/index.tsx b/x-pack/plugins/apm/public/components/shared/LocalUIFilters/TransactionTypeFilter/index.tsx index e6d266091ae521..43f7b089a2965d 100644 --- a/x-pack/plugins/apm/public/components/shared/LocalUIFilters/TransactionTypeFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/LocalUIFilters/TransactionTypeFilter/index.tsx @@ -13,7 +13,7 @@ import { import { i18n } from '@kbn/i18n'; import React from 'react'; import { useHistory } from 'react-router-dom'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../Links/url_helpers'; interface Props { diff --git a/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx b/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx index 9db563a0f6ba87..6f62fd24e71ea9 100644 --- a/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx @@ -8,7 +8,7 @@ import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; import { orderBy } from 'lodash'; import React, { ReactNode, useCallback, useMemo } from 'react'; import { useHistory } from 'react-router-dom'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../Links/url_helpers'; // TODO: this should really be imported from EUI diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx index e95122f54aff1e..8f44d98cecdf7b 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx @@ -9,7 +9,7 @@ import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { ErrorMetadata } from '..'; import { APMError } from '../../../../../../typings/es_schemas/ui/apm_error'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, expectTextsNotInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx index 1f10d923e351e3..c97e5061873478 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx @@ -9,7 +9,7 @@ import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { SpanMetadata } from '..'; import { Span } from '../../../../../../typings/es_schemas/ui/span'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, expectTextsNotInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx index 8359716fc69666..4080a300ba17f0 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx @@ -9,7 +9,7 @@ import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { TransactionMetadata } from '..'; import { Transaction } from '../../../../../../typings/es_schemas/ui/transaction'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, expectTextsNotInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx index 8e53aa4aa10896..8a4cd588c82600 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx @@ -8,7 +8,7 @@ import { render } from '@testing-library/react'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { MetadataTable } from '..'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument } from '../../../../utils/testHelpers'; import { SectionsWithRows } from '../helper'; diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx index 1d2ac4d18a2a7b..283433fa37bf91 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx @@ -17,7 +17,7 @@ import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React, { useCallback } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; import { HeightRetainer } from '../HeightRetainer'; import { fromQuery, toQuery } from '../Links/url_helpers'; diff --git a/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx b/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx index ed33c59af36f4d..83c2acb57e3c73 100644 --- a/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx +++ b/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import { EuiBadge } from '@elastic/eui'; -import { useTheme } from '../../../hooks/useTheme'; +import { useTheme } from '../../../hooks/use_theme'; import { px } from '../../../../public/style/variables'; import { units } from '../../../style/variables'; diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkToolbar.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkToolbar.test.tsx index 0241167aba1fb0..777200099976ec 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkToolbar.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkToolbar.test.tsx @@ -7,7 +7,7 @@ import { act, fireEvent, render } from '@testing-library/react'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, expectTextsNotInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx index db7a284f6adff6..c4547595645a26 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx @@ -10,8 +10,8 @@ import { MemoryRouter } from 'react-router-dom'; import { CustomLinkMenuSection } from '.'; import { CustomLink as CustomLinkType } from '../../../../../common/custom_link/custom_link_types'; import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; -import * as useFetcher from '../../../../hooks/useFetcher'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; +import * as useFetcher from '../../../../hooks/use_fetcher'; import { expectTextsInDocument, expectTextsNotInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx index 2825363b101976..0a67db0f15b329 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx @@ -22,7 +22,7 @@ import { import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; import { CustomLinkList } from './CustomLinkList'; import { CustomLinkToolbar } from './CustomLinkToolbar'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/useFetcher'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { LoadingStatePrompt } from '../../LoadingStatePrompt'; import { px } from '../../../../style/variables'; import { CreateEditCustomLinkFlyout } from '../../../app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout'; diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx index 15a85113406e15..3f74b80bab064b 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx @@ -18,9 +18,9 @@ import { SectionTitle, } from '../../../../../observability/public'; import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { useLicense } from '../../../hooks/useLicense'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { useLicenseContext } from '../../../context/license/use_license_context'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { CustomLinkMenuSection } from './CustomLinkMenuSection'; import { getSections } from './sections'; @@ -39,7 +39,7 @@ function ActionMenuButton({ onClick }: { onClick: () => void }) { } export function TransactionActionMenu({ transaction }: Props) { - const license = useLicense(); + const license = useLicenseContext(); const hasGoldLicense = license?.isActive && license?.hasAtLeast('gold'); const { core } = useApmPluginContext(); diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx index 9b5f00f76eeb21..8cb863c8fc3852 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx @@ -9,9 +9,9 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { License } from '../../../../../../licensing/common/license'; import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; -import { LicenseContext } from '../../../../context/LicenseContext'; -import * as hooks from '../../../../hooks/useFetcher'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; +import { LicenseContext } from '../../../../context/license/license_context'; +import * as hooks from '../../../../hooks/use_fetcher'; import * as apmApi from '../../../../services/rest/createCallApmApi'; import { expectTextsInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts index 4433865b44991e..c77de875dc84f7 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts @@ -10,7 +10,7 @@ import { isEmpty, pickBy } from 'lodash'; import moment from 'moment'; import url from 'url'; import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; -import { IUrlParams } from '../../../context/UrlParamsContext/types'; +import { IUrlParams } from '../../../context/url_params_context/types'; import { getDiscoverHref } from '../Links/DiscoverLinks/DiscoverLink'; import { getDiscoverQuery } from '../Links/DiscoverLinks/DiscoverTransactionLink'; import { getInfraHref } from '../Links/InfraLink'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx index 1a2a90c9fb3c30..eebb9e8d23d98e 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx @@ -6,7 +6,7 @@ import React from 'react'; import styled from 'styled-components'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; import { fontSizes, px, units } from '../../../../style/variables'; export enum Shape { diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx index 37d3664e98acda..a6b46f4a64691d 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EuiToolTip } from '@elastic/eui'; import styled from 'styled-components'; import { asDuration } from '../../../../../../common/utils/formatters'; -import { useTheme } from '../../../../../hooks/useTheme'; +import { useTheme } from '../../../../../hooks/use_theme'; import { px, units } from '../../../../../style/variables'; import { Legend } from '../../Legend'; import { AgentMark } from '../../../../app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx index abe81185635b50..e69b23cf5f0086 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx @@ -8,7 +8,7 @@ import { fireEvent } from '@testing-library/react'; import { act } from '@testing-library/react-hooks'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, renderWithTheme, diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx index de63e2323ddac6..c6847bd5e674dc 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx @@ -8,12 +8,12 @@ import React, { useState } from 'react'; import { EuiPopover, EuiText } from '@elastic/eui'; import styled from 'styled-components'; import { asDuration } from '../../../../../../common/utils/formatters'; -import { useTheme } from '../../../../../hooks/useTheme'; +import { useTheme } from '../../../../../hooks/use_theme'; import { TRACE_ID, TRANSACTION_ID, } from '../../../../../../common/elasticsearch_fieldnames'; -import { useUrlParams } from '../../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../../context/url_params_context/use_url_params'; import { px, unit, units } from '../../../../../style/variables'; import { ErrorMark } from '../../../../app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks'; import { ErrorDetailLink } from '../../../Links/apm/ErrorDetailLink'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx index cb5a44432dcbc4..dcdfee22e3cfce 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx @@ -9,7 +9,7 @@ import { inRange } from 'lodash'; import { Sticky } from 'react-sticky'; import { XAxis, XYPlot } from 'react-vis'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; import { px } from '../../../../style/variables'; import { Mark } from './'; import { LastTickValue } from './LastTickValue'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx index 5ea2e4cfedf18c..ee1c899123994c 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { VerticalGridLines, XYPlot } from 'react-vis'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; import { Mark } from '../../../app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks'; import { PlotValues } from './plotUtils'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx index c0e8f869ce647e..359eadfc55cff5 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx @@ -5,7 +5,7 @@ */ import { render } from '@testing-library/react'; import React from 'react'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { ChartContainer } from './chart_container'; describe('ChartContainer', () => { diff --git a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx index b4486f1e9b94ad..ef58430e1e31e4 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx @@ -7,7 +7,7 @@ import { EuiLoadingChart, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; interface Props { hasData: boolean; diff --git a/x-pack/plugins/apm/public/components/shared/charts/metrics_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/metrics_chart/index.tsx index 9a561571df5a7c..506c27051b5116 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/metrics_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/metrics_chart/index.tsx @@ -15,7 +15,7 @@ import { // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { GenericMetricsChart } from '../../../../../server/lib/metrics/transform_metrics_chart'; import { Maybe } from '../../../../../typings/common'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; import { TimeseriesChart } from '../timeseries_chart'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/spark_plot_with_value_label/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/spark_plot_with_value_label/index.tsx index 3819ed30d104a0..3bfcba63685b65 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/spark_plot_with_value_label/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/spark_plot_with_value_label/index.tsx @@ -7,7 +7,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; import { px, unit } from '../../../../../style/variables'; -import { useTheme } from '../../../../../hooks/useTheme'; +import { useTheme } from '../../../../../hooks/use_theme'; import { SparkPlot } from '../'; type Color = diff --git a/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx b/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx index a857707ca0c750..689f80e01247ed 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx @@ -29,11 +29,11 @@ import { useHistory } from 'react-router-dom'; import { useChartTheme } from '../../../../../observability/public'; import { asAbsoluteDateTime } from '../../../../common/utils/formatters'; import { RectCoordinate, TimeSeries } from '../../../../typings/timeseries'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; -import { useTheme } from '../../../hooks/useTheme'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { useAnnotations } from '../../../hooks/use_annotations'; -import { useChartPointerEvent } from '../../../hooks/use_chart_pointer_event'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { useTheme } from '../../../hooks/use_theme'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useAnnotationsContext } from '../../../context/annotations/use_annotations_context'; +import { useChartPointerEventContext } from '../../../context/chart_pointer_event/use_chart_pointer_event_context'; import { AnomalySeries } from '../../../selectors/chart_selectors'; import { unit } from '../../../style/variables'; import { ChartContainer } from './chart_container'; @@ -72,9 +72,9 @@ export function TimeseriesChart({ }: Props) { const history = useHistory(); const chartRef = React.createRef(); - const { annotations } = useAnnotations(); + const { annotations } = useAnnotationsContext(); const chartTheme = useChartTheme(); - const { pointerEvent, setPointerEvent } = useChartPointerEvent(); + const { pointerEvent, setPointerEvent } = useChartPointerEventContext(); const { urlParams } = useUrlParams(); const theme = useTheme(); diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/index.tsx index 4d9a1637bea760..38a980fbcd90a8 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/index.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { useTransactionBreakdown } from '../../../../hooks/use_transaction_breakdown'; +import { useTransactionBreakdown } from './use_transaction_breakdown'; import { TransactionBreakdownChartContents } from './transaction_breakdown_chart_contents'; export function TransactionBreakdownChart({ diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx index 20056a6831adf4..0eda922519f852 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx @@ -28,11 +28,11 @@ import { asPercent, } from '../../../../../common/utils/formatters'; import { TimeSeries } from '../../../../../typings/timeseries'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; -import { useTheme } from '../../../../hooks/useTheme'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { useAnnotations } from '../../../../hooks/use_annotations'; -import { useChartPointerEvent } from '../../../../hooks/use_chart_pointer_event'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; +import { useTheme } from '../../../../hooks/use_theme'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useAnnotationsContext } from '../../../../context/annotations/use_annotations_context'; +import { useChartPointerEventContext } from '../../../../context/chart_pointer_event/use_chart_pointer_event_context'; import { unit } from '../../../../style/variables'; import { ChartContainer } from '../../charts/chart_container'; import { onBrushEnd } from '../../charts/helper/helper'; @@ -52,9 +52,9 @@ export function TransactionBreakdownChartContents({ }: Props) { const history = useHistory(); const chartRef = React.createRef(); - const { annotations } = useAnnotations(); + const { annotations } = useAnnotationsContext(); const chartTheme = useChartTheme(); - const { pointerEvent, setPointerEvent } = useChartPointerEvent(); + const { pointerEvent, setPointerEvent } = useChartPointerEventContext(); const { urlParams } = useUrlParams(); const theme = useTheme(); const { start, end } = urlParams; diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_breakdown.ts b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts similarity index 80% rename from x-pack/plugins/apm/public/hooks/use_transaction_breakdown.ts rename to x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts index f1671ed7aa6d90..ff744d763ecaed 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_breakdown.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts @@ -5,15 +5,15 @@ */ import { useParams } from 'react-router-dom'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; -import { useApmService } from './use_apm_service'; +import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; export function useTransactionBreakdown() { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); const { start, end, transactionName } = urlParams; - const { transactionType } = useApmService(); + const { transactionType } = useApmServiceContext(); const { data = { timeseries: undefined }, error, status } = useFetcher( (callApmApi) => { diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx index ac117bbbd922a4..bb7c0a9104fc7b 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx @@ -20,11 +20,11 @@ import { TRANSACTION_ROUTE_CHANGE, } from '../../../../../common/transaction_types'; import { asTransactionRate } from '../../../../../common/utils/formatters'; -import { AnnotationsContextProvider } from '../../../../context/annotations_context'; -import { ChartPointerEventContextProvider } from '../../../../context/chart_pointer_event_context'; -import { LicenseContext } from '../../../../context/LicenseContext'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; +import { AnnotationsContextProvider } from '../../../../context/annotations/annotations_context'; +import { ChartPointerEventContextProvider } from '../../../../context/chart_pointer_event/chart_pointer_event_context'; +import { LicenseContext } from '../../../../context/license/license_context'; +import type { IUrlParams } from '../../../../context/url_params_context/types'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { ITransactionChartData } from '../../../../selectors/chart_selectors'; import { TimeseriesChart } from '../timeseries_chart'; import { TransactionBreakdownChart } from '../transaction_breakdown_chart'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx index ee5cd8960d3357..f0569ea1a07521 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx @@ -10,7 +10,7 @@ import { isEmpty } from 'lodash'; import React from 'react'; import { useParams } from 'react-router-dom'; import styled from 'styled-components'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { MLJobLink } from '../../Links/MachineLearningLinks/MLJobLink'; interface Props { diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx index 00472df95c4b1c..06a5e7baef79b9 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx @@ -9,9 +9,9 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { useParams } from 'react-router-dom'; import { asPercent } from '../../../../../common/utils/formatters'; -import { useFetcher } from '../../../../hooks/useFetcher'; -import { useTheme } from '../../../../hooks/useTheme'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useTheme } from '../../../../hooks/use_theme'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; import { TimeseriesChart } from '../timeseries_chart'; diff --git a/x-pack/plugins/apm/public/components/shared/table_fetch_wrapper/index.tsx b/x-pack/plugins/apm/public/components/shared/table_fetch_wrapper/index.tsx index e8d62cd8bd85b1..9538d46960fc94 100644 --- a/x-pack/plugins/apm/public/components/shared/table_fetch_wrapper/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/table_fetch_wrapper/index.tsx @@ -5,7 +5,7 @@ */ import React, { ReactNode } from 'react'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { ErrorStatePrompt } from '../ErrorStatePrompt'; export function TableFetchWrapper({ diff --git a/x-pack/plugins/apm/public/context/annotations_context.tsx b/x-pack/plugins/apm/public/context/annotations/annotations_context.tsx similarity index 83% rename from x-pack/plugins/apm/public/context/annotations_context.tsx rename to x-pack/plugins/apm/public/context/annotations/annotations_context.tsx index 4e09a3d227b113..77285f976d8508 100644 --- a/x-pack/plugins/apm/public/context/annotations_context.tsx +++ b/x-pack/plugins/apm/public/context/annotations/annotations_context.tsx @@ -6,10 +6,10 @@ import React, { createContext } from 'react'; import { useParams } from 'react-router-dom'; -import { Annotation } from '../../common/annotations'; -import { useFetcher } from '../hooks/useFetcher'; -import { useUrlParams } from '../hooks/useUrlParams'; -import { callApmApi } from '../services/rest/createCallApmApi'; +import { Annotation } from '../../../common/annotations'; +import { useFetcher } from '../../hooks/use_fetcher'; +import { useUrlParams } from '../url_params_context/use_url_params'; +import { callApmApi } from '../../services/rest/createCallApmApi'; export const AnnotationsContext = createContext({ annotations: [] } as { annotations: Annotation[]; diff --git a/x-pack/plugins/apm/public/hooks/use_annotations.ts b/x-pack/plugins/apm/public/context/annotations/use_annotations_context.ts similarity index 80% rename from x-pack/plugins/apm/public/hooks/use_annotations.ts rename to x-pack/plugins/apm/public/context/annotations/use_annotations_context.ts index 1cd9a7e65dda27..7fdc602b1916e4 100644 --- a/x-pack/plugins/apm/public/hooks/use_annotations.ts +++ b/x-pack/plugins/apm/public/context/annotations/use_annotations_context.ts @@ -5,9 +5,9 @@ */ import { useContext } from 'react'; -import { AnnotationsContext } from '../context/annotations_context'; +import { AnnotationsContext } from './annotations_context'; -export function useAnnotations() { +export function useAnnotationsContext() { const context = useContext(AnnotationsContext); if (!context) { diff --git a/x-pack/plugins/apm/public/context/ApmPluginContext/index.tsx b/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx similarity index 94% rename from x-pack/plugins/apm/public/context/ApmPluginContext/index.tsx rename to x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx index 44952e64db59c6..0e26db4820ea1a 100644 --- a/x-pack/plugins/apm/public/context/ApmPluginContext/index.tsx +++ b/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx @@ -6,7 +6,7 @@ import { AppMountParameters, CoreStart } from 'kibana/public'; import { createContext } from 'react'; -import { ConfigSchema } from '../../'; +import { ConfigSchema } from '../..'; import { ApmPluginSetupDeps } from '../../plugin'; export interface ApmPluginContextValue { diff --git a/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx similarity index 97% rename from x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx rename to x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx index 25e7f23a00125a..7ab46c65c90d9c 100644 --- a/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx +++ b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx @@ -5,7 +5,7 @@ */ import React, { ReactNode } from 'react'; import { Observable, of } from 'rxjs'; -import { ApmPluginContext, ApmPluginContextValue } from '.'; +import { ApmPluginContext, ApmPluginContextValue } from './apm_plugin_context'; import { ConfigSchema } from '../..'; import { UI_SETTINGS } from '../../../../../../src/plugins/data/common'; import { createCallApmApi } from '../../services/rest/createCallApmApi'; diff --git a/x-pack/plugins/apm/public/hooks/useApmPluginContext.ts b/x-pack/plugins/apm/public/context/apm_plugin/use_apm_plugin_context.ts similarity index 84% rename from x-pack/plugins/apm/public/hooks/useApmPluginContext.ts rename to x-pack/plugins/apm/public/context/apm_plugin/use_apm_plugin_context.ts index 80a04edbca8584..7c480ea3da2755 100644 --- a/x-pack/plugins/apm/public/hooks/useApmPluginContext.ts +++ b/x-pack/plugins/apm/public/context/apm_plugin/use_apm_plugin_context.ts @@ -5,7 +5,7 @@ */ import { useContext } from 'react'; -import { ApmPluginContext } from '../context/ApmPluginContext'; +import { ApmPluginContext } from './apm_plugin_context'; export function useApmPluginContext() { return useContext(ApmPluginContext); diff --git a/x-pack/plugins/apm/public/context/apm_service_context.test.tsx b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.test.tsx similarity index 100% rename from x-pack/plugins/apm/public/context/apm_service_context.test.tsx rename to x-pack/plugins/apm/public/context/apm_service/apm_service_context.test.tsx diff --git a/x-pack/plugins/apm/public/context/apm_service_context.tsx b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx similarity index 75% rename from x-pack/plugins/apm/public/context/apm_service_context.tsx rename to x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx index 2f1b33dea5aa67..b07763aed7b001 100644 --- a/x-pack/plugins/apm/public/context/apm_service_context.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx @@ -5,15 +5,15 @@ */ import React, { createContext, ReactNode } from 'react'; -import { isRumAgentName } from '../../common/agent_name'; +import { isRumAgentName } from '../../../common/agent_name'; import { TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST, -} from '../../common/transaction_types'; -import { useServiceTransactionTypes } from '../hooks/use_service_transaction_types'; -import { useUrlParams } from '../hooks/useUrlParams'; -import { useServiceAgentName } from '../hooks/use_service_agent_name'; -import { IUrlParams } from './UrlParamsContext/types'; +} from '../../../common/transaction_types'; +import { useServiceTransactionTypesFetcher } from './use_service_transaction_types_fetcher'; +import { useUrlParams } from '../url_params_context/use_url_params'; +import { useServiceAgentNameFetcher } from './use_service_agent_name_fetcher'; +import { IUrlParams } from '../url_params_context/types'; export const APMServiceContext = createContext<{ agentName?: string; @@ -27,8 +27,8 @@ export function ApmServiceContextProvider({ children: ReactNode; }) { const { urlParams } = useUrlParams(); - const { agentName } = useServiceAgentName(); - const transactionTypes = useServiceTransactionTypes(); + const { agentName } = useServiceAgentNameFetcher(); + const transactionTypes = useServiceTransactionTypesFetcher(); const transactionType = getTransactionType({ urlParams, transactionTypes, diff --git a/x-pack/plugins/apm/public/hooks/use_apm_service.ts b/x-pack/plugins/apm/public/context/apm_service/use_apm_service_context.ts similarity index 75% rename from x-pack/plugins/apm/public/hooks/use_apm_service.ts rename to x-pack/plugins/apm/public/context/apm_service/use_apm_service_context.ts index bc80c3771c39d8..85c135f36719fa 100644 --- a/x-pack/plugins/apm/public/hooks/use_apm_service.ts +++ b/x-pack/plugins/apm/public/context/apm_service/use_apm_service_context.ts @@ -5,8 +5,8 @@ */ import { useContext } from 'react'; -import { APMServiceContext } from '../context/apm_service_context'; +import { APMServiceContext } from './apm_service_context'; -export function useApmService() { +export function useApmServiceContext() { return useContext(APMServiceContext); } diff --git a/x-pack/plugins/apm/public/hooks/use_service_agent_name.ts b/x-pack/plugins/apm/public/context/apm_service/use_service_agent_name_fetcher.ts similarity index 83% rename from x-pack/plugins/apm/public/hooks/use_service_agent_name.ts rename to x-pack/plugins/apm/public/context/apm_service/use_service_agent_name_fetcher.ts index 199f14532f7b45..9a1d969ec2c33a 100644 --- a/x-pack/plugins/apm/public/hooks/use_service_agent_name.ts +++ b/x-pack/plugins/apm/public/context/apm_service/use_service_agent_name_fetcher.ts @@ -5,10 +5,10 @@ */ import { useParams } from 'react-router-dom'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; +import { useFetcher } from '../../hooks/use_fetcher'; +import { useUrlParams } from '../url_params_context/use_url_params'; -export function useServiceAgentName() { +export function useServiceAgentNameFetcher() { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams } = useUrlParams(); const { start, end } = urlParams; diff --git a/x-pack/plugins/apm/public/hooks/use_service_transaction_types.tsx b/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx similarity index 83% rename from x-pack/plugins/apm/public/hooks/use_service_transaction_types.tsx rename to x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx index 9d8892ac79b7d7..85a10cc273bac8 100644 --- a/x-pack/plugins/apm/public/hooks/use_service_transaction_types.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx @@ -5,12 +5,12 @@ */ import { useParams } from 'react-router-dom'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; +import { useFetcher } from '../../hooks/use_fetcher'; +import { useUrlParams } from '../url_params_context/use_url_params'; const INITIAL_DATA = { transactionTypes: [] }; -export function useServiceTransactionTypes() { +export function useServiceTransactionTypesFetcher() { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams } = useUrlParams(); const { start, end } = urlParams; diff --git a/x-pack/plugins/apm/public/context/chart_pointer_event_context.tsx b/x-pack/plugins/apm/public/context/chart_pointer_event/chart_pointer_event_context.tsx similarity index 100% rename from x-pack/plugins/apm/public/context/chart_pointer_event_context.tsx rename to x-pack/plugins/apm/public/context/chart_pointer_event/chart_pointer_event_context.tsx diff --git a/x-pack/plugins/apm/public/hooks/use_chart_pointer_event.tsx b/x-pack/plugins/apm/public/context/chart_pointer_event/use_chart_pointer_event_context.tsx similarity index 78% rename from x-pack/plugins/apm/public/hooks/use_chart_pointer_event.tsx rename to x-pack/plugins/apm/public/context/chart_pointer_event/use_chart_pointer_event_context.tsx index 058ec594e2d228..bf53273104d600 100644 --- a/x-pack/plugins/apm/public/hooks/use_chart_pointer_event.tsx +++ b/x-pack/plugins/apm/public/context/chart_pointer_event/use_chart_pointer_event_context.tsx @@ -5,9 +5,9 @@ */ import { useContext } from 'react'; -import { ChartPointerEventContext } from '../context/chart_pointer_event_context'; +import { ChartPointerEventContext } from './chart_pointer_event_context'; -export function useChartPointerEvent() { +export function useChartPointerEventContext() { const context = useContext(ChartPointerEventContext); if (!context) { diff --git a/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx b/x-pack/plugins/apm/public/context/license/Invalid_license_notification.tsx similarity index 100% rename from x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx rename to x-pack/plugins/apm/public/context/license/Invalid_license_notification.tsx diff --git a/x-pack/plugins/apm/public/context/LicenseContext/index.tsx b/x-pack/plugins/apm/public/context/license/license_context.tsx similarity index 87% rename from x-pack/plugins/apm/public/context/LicenseContext/index.tsx rename to x-pack/plugins/apm/public/context/license/license_context.tsx index e6615a2fc98bf0..557f135fa4c0e9 100644 --- a/x-pack/plugins/apm/public/context/LicenseContext/index.tsx +++ b/x-pack/plugins/apm/public/context/license/license_context.tsx @@ -7,8 +7,8 @@ import React from 'react'; import useObservable from 'react-use/lib/useObservable'; import { ILicense } from '../../../../licensing/public'; -import { useApmPluginContext } from '../../hooks/useApmPluginContext'; -import { InvalidLicenseNotification } from './InvalidLicenseNotification'; +import { useApmPluginContext } from '../apm_plugin/use_apm_plugin_context'; +import { InvalidLicenseNotification } from './Invalid_license_notification'; export const LicenseContext = React.createContext( undefined diff --git a/x-pack/plugins/apm/public/hooks/useLicense.ts b/x-pack/plugins/apm/public/context/license/use_license_context.ts similarity index 77% rename from x-pack/plugins/apm/public/hooks/useLicense.ts rename to x-pack/plugins/apm/public/context/license/use_license_context.ts index ca828e49706a82..e86bb78d127abc 100644 --- a/x-pack/plugins/apm/public/hooks/useLicense.ts +++ b/x-pack/plugins/apm/public/context/license/use_license_context.ts @@ -5,8 +5,8 @@ */ import { useContext } from 'react'; -import { LicenseContext } from '../context/LicenseContext'; +import { LicenseContext } from './license_context'; -export function useLicense() { +export function useLicenseContext() { return useContext(LicenseContext); } diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/constants.ts b/x-pack/plugins/apm/public/context/url_params_context/constants.ts similarity index 100% rename from x-pack/plugins/apm/public/context/UrlParamsContext/constants.ts rename to x-pack/plugins/apm/public/context/url_params_context/constants.ts diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/helpers.ts b/x-pack/plugins/apm/public/context/url_params_context/helpers.ts similarity index 100% rename from x-pack/plugins/apm/public/context/UrlParamsContext/helpers.ts rename to x-pack/plugins/apm/public/context/url_params_context/helpers.ts diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/MockUrlParamsContextProvider.tsx b/x-pack/plugins/apm/public/context/url_params_context/mock_url_params_context_provider.tsx similarity index 93% rename from x-pack/plugins/apm/public/context/UrlParamsContext/MockUrlParamsContextProvider.tsx rename to x-pack/plugins/apm/public/context/url_params_context/mock_url_params_context_provider.tsx index fd01e057ac3ded..b593cbd57a9a9a 100644 --- a/x-pack/plugins/apm/public/context/UrlParamsContext/MockUrlParamsContextProvider.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/mock_url_params_context_provider.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { IUrlParams } from './types'; -import { UrlParamsContext, useUiFilters } from '.'; +import { UrlParamsContext, useUiFilters } from './url_params_context'; const defaultUrlParams = { page: 0, diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts similarity index 100% rename from x-pack/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts rename to x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/types.ts b/x-pack/plugins/apm/public/context/url_params_context/types.ts similarity index 100% rename from x-pack/plugins/apm/public/context/UrlParamsContext/types.ts rename to x-pack/plugins/apm/public/context/url_params_context/types.ts diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/url_params_context.test.tsx b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx similarity index 98% rename from x-pack/plugins/apm/public/context/UrlParamsContext/url_params_context.test.tsx rename to x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx index 3a6ccce178cd4c..6b57039678e0ae 100644 --- a/x-pack/plugins/apm/public/context/UrlParamsContext/url_params_context.test.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx @@ -5,7 +5,7 @@ */ import * as React from 'react'; -import { UrlParamsContext, UrlParamsProvider } from './'; +import { UrlParamsContext, UrlParamsProvider } from './url_params_context'; import { mount } from 'enzyme'; import { Location, History } from 'history'; import { MemoryRouter, Router } from 'react-router-dom'; diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/index.tsx b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx similarity index 98% rename from x-pack/plugins/apm/public/context/UrlParamsContext/index.tsx rename to x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx index 5682009019d7f8..0a3f8459ff0023 100644 --- a/x-pack/plugins/apm/public/context/UrlParamsContext/index.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx @@ -15,7 +15,7 @@ import { withRouter } from 'react-router-dom'; import { uniqueId, mapValues } from 'lodash'; import { IUrlParams } from './types'; import { getParsedDate } from './helpers'; -import { resolveUrlParams } from './resolveUrlParams'; +import { resolveUrlParams } from './resolve_url_params'; import { UIFilters } from '../../../typings/ui_filters'; import { localUIFilterNames, diff --git a/x-pack/plugins/apm/public/hooks/useUrlParams.tsx b/x-pack/plugins/apm/public/context/url_params_context/use_url_params.tsx similarity index 84% rename from x-pack/plugins/apm/public/hooks/useUrlParams.tsx rename to x-pack/plugins/apm/public/context/url_params_context/use_url_params.tsx index b9f47046812be4..1bf071d9db35e1 100644 --- a/x-pack/plugins/apm/public/hooks/useUrlParams.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/use_url_params.tsx @@ -5,7 +5,7 @@ */ import { useContext } from 'react'; -import { UrlParamsContext } from '../context/UrlParamsContext'; +import { UrlParamsContext } from './url_params_context'; export function useUrlParams() { return useContext(UrlParamsContext); diff --git a/x-pack/plugins/apm/public/hooks/useCallApi.ts b/x-pack/plugins/apm/public/hooks/useCallApi.ts index 3fec36e7fb24ba..79e439c3f7e7a5 100644 --- a/x-pack/plugins/apm/public/hooks/useCallApi.ts +++ b/x-pack/plugins/apm/public/hooks/useCallApi.ts @@ -6,7 +6,7 @@ import { useMemo } from 'react'; import { callApi } from '../services/rest/callApi'; -import { useApmPluginContext } from './useApmPluginContext'; +import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; import { FetchOptions } from '../../common/fetch_options'; export function useCallApi() { diff --git a/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts b/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts index b4a354c2316339..66edb84378a457 100644 --- a/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts +++ b/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts @@ -5,7 +5,7 @@ */ import url from 'url'; -import { useApmPluginContext } from './useApmPluginContext'; +import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; export function useKibanaUrl( /** The path to the plugin */ path: string, diff --git a/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts b/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts index da1797fd712b15..551e92f8ba0340 100644 --- a/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts +++ b/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts @@ -15,10 +15,10 @@ import { // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../server/lib/ui_filters/local_ui_filters/config'; import { fromQuery, toQuery } from '../components/shared/Links/url_helpers'; -import { removeUndefinedProps } from '../context/UrlParamsContext/helpers'; +import { removeUndefinedProps } from '../context/url_params_context/helpers'; import { useCallApi } from './useCallApi'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; +import { useFetcher } from './use_fetcher'; +import { useUrlParams } from '../context/url_params_context/use_url_params'; import { LocalUIFilterName } from '../../common/ui_filter'; const getInitialData = ( diff --git a/x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx b/x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx index dcd6ed0ba49347..9127bd3adc69ec 100644 --- a/x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx +++ b/x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx @@ -9,11 +9,11 @@ import produce from 'immer'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { routes } from '../components/app/Main/route_config'; -import { ApmPluginContextValue } from '../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../context/ApmPluginContext/MockApmPluginContext'; +} from '../context/apm_plugin/mock_apm_plugin_context'; import { useBreadcrumbs } from './use_breadcrumbs'; function createWrapper(path: string) { diff --git a/x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts b/x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts index 640170bf3bff27..089381cbe05b5b 100644 --- a/x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts +++ b/x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts @@ -16,7 +16,7 @@ import { } from 'react-router-dom'; import { APMRouteDefinition, BreadcrumbTitle } from '../application/routes'; import { getAPMHref } from '../components/shared/Links/apm/APMLink'; -import { useApmPluginContext } from './useApmPluginContext'; +import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; interface BreadcrumbWithoutLink extends ChromeBreadcrumb { match: Match>; diff --git a/x-pack/plugins/apm/public/hooks/useDynamicIndexPattern.ts b/x-pack/plugins/apm/public/hooks/use_dynamic_index_pattern.ts similarity index 89% rename from x-pack/plugins/apm/public/hooks/useDynamicIndexPattern.ts rename to x-pack/plugins/apm/public/hooks/use_dynamic_index_pattern.ts index d0e12d8537846a..becdf1f9ecc5e4 100644 --- a/x-pack/plugins/apm/public/hooks/useDynamicIndexPattern.ts +++ b/x-pack/plugins/apm/public/hooks/use_dynamic_index_pattern.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useFetcher } from './useFetcher'; +import { useFetcher } from './use_fetcher'; import { UIProcessorEvent } from '../../common/processor_event'; -export function useDynamicIndexPattern( +export function useDynamicIndexPatternFetcher( processorEvent: UIProcessorEvent | undefined ) { const { data, status } = useFetcher( diff --git a/x-pack/plugins/apm/public/hooks/useEnvironments.tsx b/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx similarity index 94% rename from x-pack/plugins/apm/public/hooks/useEnvironments.tsx rename to x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx index 05ac780aefbde8..1ad151b8c7e907 100644 --- a/x-pack/plugins/apm/public/hooks/useEnvironments.tsx +++ b/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx @@ -5,7 +5,7 @@ */ import { useMemo } from 'react'; -import { useFetcher } from './useFetcher'; +import { useFetcher } from './use_fetcher'; import { ENVIRONMENT_ALL, ENVIRONMENT_NOT_DEFINED, @@ -23,7 +23,7 @@ function getEnvironmentOptions(environments: string[]) { return [ENVIRONMENT_ALL, ...environmentOptions]; } -export function useEnvironments({ +export function useEnvironmentsFetcher({ serviceName, start, end, diff --git a/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx new file mode 100644 index 00000000000000..1c17be884ebf56 --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useUrlParams } from '../context/url_params_context/use_url_params'; +import { useFetcher } from './use_fetcher'; + +export function useErrorGroupDistributionFetcher({ + serviceName, + groupId, +}: { + serviceName: string; + groupId: string | undefined; +}) { + const { urlParams, uiFilters } = useUrlParams(); + const { start, end } = urlParams; + const { data } = useFetcher( + (callApmApi) => { + if (start && end) { + return callApmApi({ + endpoint: 'GET /api/apm/services/{serviceName}/errors/distribution', + params: { + path: { serviceName }, + query: { + start, + end, + groupId, + uiFilters: JSON.stringify(uiFilters), + }, + }, + }); + } + }, + [serviceName, start, end, groupId, uiFilters] + ); + + return { errorDistributionData: data }; +} diff --git a/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx index e837851828d94f..e6f3b71af8a859 100644 --- a/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx @@ -7,8 +7,8 @@ import { render, waitFor } from '@testing-library/react'; import React from 'react'; import { delay } from '../utils/testHelpers'; -import { useFetcher } from './useFetcher'; -import { MockApmPluginContextWrapper } from '../context/ApmPluginContext/MockApmPluginContext'; +import { useFetcher } from './use_fetcher'; +import { MockApmPluginContextWrapper } from '../context/apm_plugin/mock_apm_plugin_context'; const wrapper = MockApmPluginContextWrapper; diff --git a/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx similarity index 96% rename from x-pack/plugins/apm/public/hooks/useFetcher.test.tsx rename to x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx index 59dd9455c724c9..9b4ad6bc9bb517 100644 --- a/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx @@ -6,9 +6,9 @@ import { renderHook, RenderHookResult } from '@testing-library/react-hooks'; import { delay } from '../utils/testHelpers'; -import { FetcherResult, useFetcher } from './useFetcher'; -import { MockApmPluginContextWrapper } from '../context/ApmPluginContext/MockApmPluginContext'; -import { ApmPluginContextValue } from '../context/ApmPluginContext'; +import { FetcherResult, useFetcher } from './use_fetcher'; +import { MockApmPluginContextWrapper } from '../context/apm_plugin/mock_apm_plugin_context'; +import { ApmPluginContextValue } from '../context/apm_plugin/apm_plugin_context'; // Wrap the hook with a provider so it can useApmPluginContext const wrapper = MockApmPluginContextWrapper; diff --git a/x-pack/plugins/apm/public/hooks/useFetcher.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx similarity index 98% rename from x-pack/plugins/apm/public/hooks/useFetcher.tsx rename to x-pack/plugins/apm/public/hooks/use_fetcher.tsx index 6add0e8a2b4802..a9a4871dc87071 100644 --- a/x-pack/plugins/apm/public/hooks/useFetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx @@ -9,7 +9,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import { IHttpFetchError } from 'src/core/public'; import { toMountPoint } from '../../../../../src/plugins/kibana_react/public'; import { APMClient, callApmApi } from '../services/rest/createCallApmApi'; -import { useApmPluginContext } from './useApmPluginContext'; +import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; export enum FETCH_STATUS { LOADING = 'loading', diff --git a/x-pack/plugins/apm/public/hooks/useServiceMetricCharts.ts b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts similarity index 75% rename from x-pack/plugins/apm/public/hooks/useServiceMetricCharts.ts rename to x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts index 7a54c6ffc6dbeb..c888c515895631 100644 --- a/x-pack/plugins/apm/public/hooks/useServiceMetricCharts.ts +++ b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts @@ -7,22 +7,23 @@ import { useParams } from 'react-router-dom'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { MetricsChartsByAgentAPIResponse } from '../../server/lib/metrics/get_metrics_chart_data_by_agent'; -import { useUiFilters } from '../context/UrlParamsContext'; -import { IUrlParams } from '../context/UrlParamsContext/types'; -import { useFetcher } from './useFetcher'; +import { useUrlParams } from '../context/url_params_context/use_url_params'; +import { useApmServiceContext } from '../context/apm_service/use_apm_service_context'; +import { useFetcher } from './use_fetcher'; const INITIAL_DATA: MetricsChartsByAgentAPIResponse = { charts: [], }; -export function useServiceMetricCharts( - urlParams: IUrlParams, - agentName?: string, - serviceNodeName?: string -) { +export function useServiceMetricChartsFetcher({ + serviceNodeName, +}: { + serviceNodeName: string | undefined; +}) { + const { urlParams, uiFilters } = useUrlParams(); + const { agentName } = useApmServiceContext(); const { serviceName } = useParams<{ serviceName?: string }>(); const { start, end } = urlParams; - const uiFilters = useUiFilters(urlParams); const { data = INITIAL_DATA, error, status } = useFetcher( (callApmApi) => { if (serviceName && start && end && agentName) { diff --git a/x-pack/plugins/apm/public/hooks/useTheme.tsx b/x-pack/plugins/apm/public/hooks/use_theme.tsx similarity index 100% rename from x-pack/plugins/apm/public/hooks/useTheme.tsx rename to x-pack/plugins/apm/public/hooks/use_theme.tsx diff --git a/x-pack/plugins/apm/public/hooks/useTransactionCharts.ts b/x-pack/plugins/apm/public/hooks/use_transaction_charts_fetcher.ts similarity index 82% rename from x-pack/plugins/apm/public/hooks/useTransactionCharts.ts rename to x-pack/plugins/apm/public/hooks/use_transaction_charts_fetcher.ts index c790ac57edc3bf..f5105e38b985e2 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionCharts.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_charts_fetcher.ts @@ -7,10 +7,10 @@ import { useMemo } from 'react'; import { useParams } from 'react-router-dom'; import { getTransactionCharts } from '../selectors/chart_selectors'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; +import { useFetcher } from './use_fetcher'; +import { useUrlParams } from '../context/url_params_context/use_url_params'; -export function useTransactionCharts() { +export function useTransactionChartsFetcher() { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams: { transactionType, start, end, transactionName }, @@ -45,8 +45,8 @@ export function useTransactionCharts() { ); return { - data: memoizedData, - status, - error, + transactionChartsData: memoizedData, + transactionChartsStatus: status, + transactionChartsError: error, }; } diff --git a/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts b/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts similarity index 89% rename from x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts rename to x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts index 9cbfee37d12530..74222e8ffe038c 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts @@ -6,12 +6,11 @@ import { flatten, omit, isEmpty } from 'lodash'; import { useHistory, useParams } from 'react-router-dom'; -import { IUrlParams } from '../context/UrlParamsContext/types'; -import { useFetcher } from './useFetcher'; -import { useUiFilters } from '../context/UrlParamsContext'; +import { useFetcher } from './use_fetcher'; import { toQuery, fromQuery } from '../components/shared/Links/url_helpers'; import { maybe } from '../../common/utils/maybe'; import { APIReturnType } from '../services/rest/createCallApmApi'; +import { useUrlParams } from '../context/url_params_context/use_url_params'; type APIResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transaction_groups/distribution'>; @@ -21,8 +20,9 @@ const INITIAL_DATA = { bucketSize: 0, }; -export function useTransactionDistribution(urlParams: IUrlParams) { +export function useTransactionDistributionFetcher() { const { serviceName } = useParams<{ serviceName?: string }>(); + const { urlParams, uiFilters } = useUrlParams(); const { start, end, @@ -31,10 +31,8 @@ export function useTransactionDistribution(urlParams: IUrlParams) { traceId, transactionName, } = urlParams; - const uiFilters = useUiFilters(urlParams); const history = useHistory(); - const { data = INITIAL_DATA, status, error } = useFetcher( async (callApmApi) => { if (serviceName && start && end && transactionType && transactionName) { @@ -96,5 +94,9 @@ export function useTransactionDistribution(urlParams: IUrlParams) { [serviceName, start, end, transactionType, transactionName, uiFilters] ); - return { data, status, error }; + return { + distributionData: data, + distributionStatus: status, + distributionError: error, + }; } diff --git a/x-pack/plugins/apm/public/selectors/chart_selectors.ts b/x-pack/plugins/apm/public/selectors/chart_selectors.ts index 2fdcaf9e4e6751..37bd04e5d99800 100644 --- a/x-pack/plugins/apm/public/selectors/chart_selectors.ts +++ b/x-pack/plugins/apm/public/selectors/chart_selectors.ts @@ -17,7 +17,7 @@ import { RectCoordinate, TimeSeries, } from '../../typings/timeseries'; -import { IUrlParams } from '../context/UrlParamsContext/types'; +import { IUrlParams } from '../context/url_params_context/types'; import { getEmptySeries } from '../components/shared/charts/helper/get_empty_series'; import { httpStatusCodeToColor } from '../utils/httpStatusCodeToColor'; import { asDuration, asTransactionRate } from '../../common/utils/formatters'; diff --git a/x-pack/plugins/apm/public/utils/testHelpers.tsx b/x-pack/plugins/apm/public/utils/testHelpers.tsx index 87dfeb95b63263..21c87c18be363b 100644 --- a/x-pack/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/plugins/apm/public/utils/testHelpers.tsx @@ -24,8 +24,8 @@ import { PromiseReturnType } from '../../../observability/typings/common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { APMConfig } from '../../server'; import { UIFilters } from '../../typings/ui_filters'; -import { MockApmPluginContextWrapper } from '../context/ApmPluginContext/MockApmPluginContext'; -import { UrlParamsProvider } from '../context/UrlParamsContext'; +import { MockApmPluginContextWrapper } from '../context/apm_plugin/mock_apm_plugin_context'; +import { UrlParamsProvider } from '../context/url_params_context/url_params_context'; const originalConsoleWarn = console.warn; // eslint-disable-line no-console /** diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/merge_transaction_group_data.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/merge_transaction_group_data.ts index f9266baddaf271..5f53bfa18c4688 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/merge_transaction_group_data.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_groups/merge_transaction_group_data.ts @@ -46,26 +46,26 @@ export function mergeTransactionGroupData({ const timeseriesBuckets = groupBucket?.timeseries.buckets ?? []; return timeseriesBuckets.reduce( - (prev, point) => { + (acc, point) => { return { - ...prev, + ...acc, latency: { - ...prev.latency, - timeseries: prev.latency.timeseries.concat({ + ...acc.latency, + timeseries: acc.latency.timeseries.concat({ x: point.key, y: point.avg_latency.value, }), }, throughput: { - ...prev.throughput, - timeseries: prev.throughput.timeseries.concat({ + ...acc.throughput, + timeseries: acc.throughput.timeseries.concat({ x: point.key, y: point.transaction_count.value / deltaAsMinutes, }), }, errorRate: { - ...prev.errorRate, - timeseries: prev.errorRate.timeseries.concat({ + ...acc.errorRate, + timeseries: acc.errorRate.timeseries.concat({ x: point.key, y: point.transaction_count.value > 0 diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index 9334ce60a3f9e1..4f7f6320185bf9 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -42,8 +42,11 @@ import { } from './settings/apm_indices'; import { metricsChartsRoute } from './metrics'; import { serviceNodesRoute } from './service_nodes'; -import { tracesRoute, tracesByIdRoute } from './traces'; -import { transactionByTraceIdRoute } from './transaction'; +import { + tracesRoute, + tracesByIdRoute, + rootTransactionByTraceIdRoute, +} from './traces'; import { correlationsForSlowTransactionsRoute, correlationsForFailedTransactionsRoute, @@ -147,6 +150,7 @@ const createApmApi = () => { // Traces .add(tracesRoute) .add(tracesByIdRoute) + .add(rootTransactionByTraceIdRoute) // Transaction groups .add(transactionGroupsBreakdownRoute) @@ -166,9 +170,6 @@ const createApmApi = () => { .add(serviceNodesLocalFiltersRoute) .add(uiFiltersEnvironmentsRoute) - // Transaction - .add(transactionByTraceIdRoute) - // Service map .add(serviceMapRoute) .add(serviceMapServiceNodeRoute) diff --git a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts index e7405ad16a63eb..49708e4edb7323 100644 --- a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts +++ b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts @@ -19,7 +19,7 @@ import { notifyFeatureUsage } from '../../feature'; // get ML anomaly detection jobs for each environment export const anomalyDetectionJobsRoute = createRoute({ - endpoint: 'GET /api/apm/settings/anomaly-detection', + endpoint: 'GET /api/apm/settings/anomaly-detection/jobs', options: { tags: ['access:apm', 'access:ml:canGetJobs'], }, diff --git a/x-pack/plugins/apm/server/routes/traces.ts b/x-pack/plugins/apm/server/routes/traces.ts index 0c79d391e1fd70..373dc9b8b6ecd3 100644 --- a/x-pack/plugins/apm/server/routes/traces.ts +++ b/x-pack/plugins/apm/server/routes/traces.ts @@ -11,6 +11,7 @@ import { getTransactionGroupList } from '../lib/transaction_groups'; import { createRoute } from './create_route'; import { rangeRt, uiFiltersRt } from './default_api_types'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; +import { getRootTransactionByTraceId } from '../lib/transactions/get_transaction_by_trace'; export const tracesRoute = createRoute({ endpoint: 'GET /api/apm/traces', @@ -44,3 +45,18 @@ export const tracesByIdRoute = createRoute({ return getTrace(context.params.path.traceId, setup); }, }); + +export const rootTransactionByTraceIdRoute = createRoute({ + endpoint: 'GET /api/apm/traces/{traceId}/root_transaction', + params: t.type({ + path: t.type({ + traceId: t.string, + }), + }), + options: { tags: ['access:apm'] }, + handler: async ({ context, request }) => { + const { traceId } = context.params.path; + const setup = await setupRequest(context, request); + return getRootTransactionByTraceId(traceId, setup); + }, +}); diff --git a/x-pack/plugins/apm/server/routes/transaction.ts b/x-pack/plugins/apm/server/routes/transaction.ts deleted file mode 100644 index 3294d2e9a82279..00000000000000 --- a/x-pack/plugins/apm/server/routes/transaction.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as t from 'io-ts'; -import { setupRequest } from '../lib/helpers/setup_request'; -import { getRootTransactionByTraceId } from '../lib/transactions/get_transaction_by_trace'; -import { createRoute } from './create_route'; - -export const transactionByTraceIdRoute = createRoute({ - endpoint: 'GET /api/apm/transaction/{traceId}', - params: t.type({ - path: t.type({ - traceId: t.string, - }), - }), - options: { tags: ['access:apm'] }, - handler: async ({ context, request }) => { - const { traceId } = context.params.path; - const setup = await setupRequest(context, request); - return getRootTransactionByTraceId(traceId, setup); - }, -}); diff --git a/x-pack/plugins/audit_trail/jest.config.js b/x-pack/plugins/audit_trail/jest.config.js new file mode 100644 index 00000000000000..31de78fc6bbd9c --- /dev/null +++ b/x-pack/plugins/audit_trail/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/audit_trail'], +}; diff --git a/x-pack/plugins/beats_management/jest.config.js b/x-pack/plugins/beats_management/jest.config.js new file mode 100644 index 00000000000000..8ffbb97b1656d9 --- /dev/null +++ b/x-pack/plugins/beats_management/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/beats_management'], +}; diff --git a/x-pack/plugins/canvas/jest.config.js b/x-pack/plugins/canvas/jest.config.js new file mode 100644 index 00000000000000..d010fb8c150bc6 --- /dev/null +++ b/x-pack/plugins/canvas/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/canvas'], +}; diff --git a/x-pack/plugins/canvas/scripts/jest.js b/x-pack/plugins/canvas/scripts/jest.js index a91431a0141c54..9dd8bac88e1e2d 100644 --- a/x-pack/plugins/canvas/scripts/jest.js +++ b/x-pack/plugins/canvas/scripts/jest.js @@ -4,89 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -const { run } = require('@kbn/dev-utils'); -const { runXPackScript } = require('./_helpers'); +const { resolve } = require('path'); +process.argv.push('--config', resolve(__dirname, '../jest.config.js')); -// Due to https://github.com/facebook/jest/issues/7267, folders that start with `.` -// are ignored if using watchman. Disabling watchman makes testing slow. So -// we're making this script allow -run( - ({ log, flags }) => { - const { all, storybook, update, coverage } = flags; - let { path } = flags; - let options = []; - process.argv.splice(2, process.argv.length - 2); +const storybookPosition = process.argv.indexOf('--storybook'); +const allPosition = process.argv.indexOf('--all'); - if (path) { - log.info(`Limiting tests to ${path}...`); - path = 'plugins/canvas/' + path; - } else { - path = 'plugins/canvas'; - } +console.log(` +A helper proxying to the following command: - if (coverage) { - log.info(`Collecting test coverage and writing to canvas/coverage...`); - options = [ - '--coverage', - '--collectCoverageFrom', // Ignore TS definition files - `!${path}/**/*.d.ts`, - '--collectCoverageFrom', // Ignore build directories - `!${path}/**/build/**`, - '--collectCoverageFrom', // Ignore coverage on test files - `!${path}/**/__tests__/**/*`, - '--collectCoverageFrom', // Ignore coverage on example files - `!${path}/**/__examples__/**/*`, - '--collectCoverageFrom', // Ignore flot files - `!${path}/**/flot-charts/**`, - '--collectCoverageFrom', // Ignore coverage files - `!${path}/**/coverage/**`, - '--collectCoverageFrom', // Ignore scripts - `!${path}/**/scripts/**`, - '--collectCoverageFrom', // Ignore mock files - `!${path}/**/mocks/**`, - '--collectCoverageFrom', // Include JS files - `${path}/**/*.js`, - '--collectCoverageFrom', // Include TS/X files - `${path}/**/*.ts*`, - '--coverageDirectory', // Output to canvas/coverage - 'plugins/canvas/coverage', - ]; - } - // Mitigation for https://github.com/facebook/jest/issues/7267 - if (all || storybook) { - options = options.concat(['--no-cache', '--no-watchman']); - } + yarn jest --config x-pack/plugins/canvas/jest.config.js - if (all) { - log.info('Running all available tests. This will take a while...'); - } else if (storybook) { - path = 'plugins/canvas/storybook'; - log.info('Running Storybook Snapshot tests...'); - } else { - log.info('Running tests. This does not include Storybook Snapshots...'); - } +Provides the following additional options: + --all Runs all tests and snapshots. Slower. + --storybook Runs Storybook Snapshot tests only. +`); - if (update) { - log.info('Updating any Jest snapshots...'); - options.push('-u'); - } +if (storybookPosition > -1) { + process.argv.splice(storybookPosition, 1); - runXPackScript('jest', [path].concat(options)); - }, - { - description: ` - Jest test runner for Canvas. By default, will not include Storybook Snapshots. - `, - flags: { - boolean: ['all', 'storybook', 'update', 'coverage'], - string: ['path'], - help: ` - --all Runs all tests and snapshots. Slower. - --storybook Runs Storybook Snapshot tests only. - --update Updates Storybook Snapshot tests. - --path Runs any tests at a given path. - --coverage Collect coverage statistics. - `, - }, - } -); + console.log('Running Storybook Snapshot tests only'); + process.argv.push('canvas/storybook/'); +} else if (allPosition > -1) { + process.argv.splice(allPosition, 1); + console.log('Running all available tests. This will take a while...'); +} else { + console.log('Running tests. This does not include Storybook Snapshots...'); + process.argv.push('--modulePathIgnorePatterns="/canvas/storybook/"'); +} + +if (process.env.NODE_ENV == null) { + process.env.NODE_ENV = 'test'; +} + +require('jest').run(); diff --git a/x-pack/plugins/canvas/storybook/dll_contexts.js b/x-pack/plugins/canvas/storybook/dll_contexts.js index 02ceafd0b39837..8397f2f2e75f62 100644 --- a/x-pack/plugins/canvas/storybook/dll_contexts.js +++ b/x-pack/plugins/canvas/storybook/dll_contexts.js @@ -10,6 +10,3 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths require('../../../../src/core/server/core_app/assets/legacy_light_theme.css'); - -const json = require.context('../shareable_runtime/test/workpads', false, /\.json$/); -json.keys().forEach((key) => json(key)); diff --git a/x-pack/plugins/case/jest.config.js b/x-pack/plugins/case/jest.config.js new file mode 100644 index 00000000000000..8095c70bc4a140 --- /dev/null +++ b/x-pack/plugins/case/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/case'], +}; diff --git a/x-pack/plugins/cloud/jest.config.js b/x-pack/plugins/cloud/jest.config.js new file mode 100644 index 00000000000000..e3844a97e5692d --- /dev/null +++ b/x-pack/plugins/cloud/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/cloud'], +}; diff --git a/x-pack/plugins/code/jest.config.js b/x-pack/plugins/code/jest.config.js new file mode 100644 index 00000000000000..2b2b078cc966c6 --- /dev/null +++ b/x-pack/plugins/code/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/code'], +}; diff --git a/x-pack/plugins/cross_cluster_replication/jest.config.js b/x-pack/plugins/cross_cluster_replication/jest.config.js new file mode 100644 index 00000000000000..6202a45413906e --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/cross_cluster_replication'], +}; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_actions_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_actions_provider.js new file mode 100644 index 00000000000000..df3017ebf92a4a --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_actions_provider.js @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { FollowerIndexPauseProvider } from './follower_index_pause_provider'; +import { FollowerIndexResumeProvider } from './follower_index_resume_provider'; +import { FollowerIndexUnfollowProvider } from './follower_index_unfollow_provider'; + +export const FollowerIndexActionsProvider = (props) => { + return ( + + {(pauseFollowerIndex) => ( + + {(resumeFollowerIndex) => ( + + {(unfollowLeaderIndex) => { + const { children } = props; + return children(() => ({ + pauseFollowerIndex, + resumeFollowerIndex, + unfollowLeaderIndex, + })); + }} + + )} + + )} + + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_pause_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_pause_provider.js similarity index 95% rename from x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_pause_provider.js rename to x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_pause_provider.js index 9c1e8255d069c2..7d1168d8316316 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_pause_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_pause_provider.js @@ -11,9 +11,9 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; -import { pauseFollowerIndex } from '../store/actions'; -import { arrify } from '../../../common/services/utils'; -import { areAllSettingsDefault } from '../services/follower_index_default_settings'; +import { pauseFollowerIndex } from '../../store/actions'; +import { arrify } from '../../../../common/services/utils'; +import { areAllSettingsDefault } from '../../services/follower_index_default_settings'; class FollowerIndexPauseProviderUi extends PureComponent { static propTypes = { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_resume_provider.js similarity index 95% rename from x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js rename to x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_resume_provider.js index 101e3df6bf710c..86f8c0447e7342 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_resume_provider.js @@ -10,10 +10,10 @@ import { connect } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiConfirmModal, EuiLink, EuiOverlayMask } from '@elastic/eui'; -import { reactRouterNavigate } from '../../../../../../src/plugins/kibana_react/public'; -import { routing } from '../services/routing'; -import { resumeFollowerIndex } from '../store/actions'; -import { arrify } from '../../../common/services/utils'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; +import { routing } from '../../services/routing'; +import { resumeFollowerIndex } from '../../store/actions'; +import { arrify } from '../../../../common/services/utils'; class FollowerIndexResumeProviderUi extends PureComponent { static propTypes = { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_unfollow_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_unfollow_provider.js similarity index 97% rename from x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_unfollow_provider.js rename to x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_unfollow_provider.js index 68b6b970ad90ba..f9644aa20c2c2a 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_unfollow_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_unfollow_provider.js @@ -11,8 +11,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; -import { unfollowLeaderIndex } from '../store/actions'; -import { arrify } from '../../../common/services/utils'; +import { unfollowLeaderIndex } from '../../store/actions'; +import { arrify } from '../../../../common/services/utils'; class FollowerIndexUnfollowProviderUi extends PureComponent { static propTypes = { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/index.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/index.js new file mode 100644 index 00000000000000..fe1a7d82a56a19 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/index.js @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +export { FollowerIndexActionsProvider } from './follower_index_actions_provider'; +export { FollowerIndexPauseProvider } from './follower_index_pause_provider'; +export { FollowerIndexResumeProvider } from './follower_index_resume_provider'; +export { FollowerIndexUnfollowProvider } from './follower_index_unfollow_provider'; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/index.js b/x-pack/plugins/cross_cluster_replication/public/app/components/index.js index 6d971bff03981f..55609fa85fb11b 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/index.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/index.js @@ -12,9 +12,10 @@ export { AutoFollowPatternForm } from './auto_follow_pattern_form'; export { AutoFollowPatternDeleteProvider } from './auto_follow_pattern_delete_provider'; export { AutoFollowPatternPageTitle } from './auto_follow_pattern_page_title'; export { AutoFollowPatternIndicesPreview } from './auto_follow_pattern_indices_preview'; -export { FollowerIndexPauseProvider } from './follower_index_pause_provider'; -export { FollowerIndexResumeProvider } from './follower_index_resume_provider'; -export { FollowerIndexUnfollowProvider } from './follower_index_unfollow_provider'; +export { FollowerIndexPauseProvider } from './follower_index_actions_providers'; +export { FollowerIndexResumeProvider } from './follower_index_actions_providers'; +export { FollowerIndexUnfollowProvider } from './follower_index_actions_providers'; +export { FollowerIndexActionsProvider } from './follower_index_actions_providers'; export { FollowerIndexForm } from './follower_index_form'; export { FollowerIndexPageTitle } from './follower_index_page_title'; export { FormEntryRow } from './form_entry_row'; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js index 2309dece3f92be..dd5fe6f2128083 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { PureComponent, Fragment } from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { i18n } from '@kbn/i18n'; import { @@ -13,7 +13,6 @@ import { EuiLoadingKibana, EuiOverlayMask, EuiHealth, - EuiIcon, } from '@elastic/eui'; import { API_STATUS, UIM_AUTO_FOLLOW_PATTERN_SHOW_DETAILS_CLICK } from '../../../../../constants'; import { @@ -23,6 +22,33 @@ import { import { routing } from '../../../../../services/routing'; import { trackUiMetric } from '../../../../../services/track_ui_metric'; +const actionI18nTexts = { + pause: i18n.translate( + 'xpack.crossClusterReplication.autoFollowPatternList.table.actionPauseDescription', + { + defaultMessage: 'Pause replication', + } + ), + resume: i18n.translate( + 'xpack.crossClusterReplication.autoFollowPatternList.table.actionResumeDescription', + { + defaultMessage: 'Resume replication', + } + ), + edit: i18n.translate( + 'xpack.crossClusterReplication.autoFollowPatternList.table.actionEditDescription', + { + defaultMessage: 'Edit auto-follow pattern', + } + ), + delete: i18n.translate( + 'xpack.crossClusterReplication.autoFollowPatternList.table.actionDeleteDescription', + { + defaultMessage: 'Delete auto-follow pattern', + } + ), +}; + const getFilteredPatterns = (autoFollowPatterns, queryText) => { if (queryText) { const normalizedSearchText = queryText.toLowerCase(); @@ -93,7 +119,7 @@ export class AutoFollowPatternTable extends PureComponent { }); }; - getTableColumns() { + getTableColumns(deleteAutoFollowPattern) { const { selectAutoFollowPattern } = this.props; return [ @@ -200,88 +226,34 @@ export class AutoFollowPatternTable extends PureComponent { ), actions: [ { - render: ({ name, active }) => { - const label = active - ? i18n.translate( - 'xpack.crossClusterReplication.autoFollowPatternList.table.actionPauseDescription', - { - defaultMessage: 'Pause replication', - } - ) - : i18n.translate( - 'xpack.crossClusterReplication.autoFollowPatternList.table.actionResumeDescription', - { - defaultMessage: 'Resume replication', - } - ); - - return ( - { - if (event.stopPropagation) { - event.stopPropagation(); - } - if (active) { - this.props.pauseAutoFollowPattern(name); - } else { - this.props.resumeAutoFollowPattern(name); - } - }} - data-test-subj={active ? 'contextMenuPauseButton' : 'contextMenuResumeButton'} - > - - {label} - - ); - }, + name: actionI18nTexts.pause, + description: actionI18nTexts.pause, + icon: 'pause', + onClick: (item) => this.props.pauseAutoFollowPattern(item.name), + available: (item) => item.active, + 'data-test-subj': 'contextMenuPauseButton', }, { - render: ({ name }) => { - const label = i18n.translate( - 'xpack.crossClusterReplication.autoFollowPatternList.table.actionEditDescription', - { - defaultMessage: 'Edit auto-follow pattern', - } - ); - - return ( - routing.navigate(routing.getAutoFollowPatternPath(name))} - data-test-subj="contextMenuEditButton" - > - - {label} - - ); - }, + name: actionI18nTexts.resume, + description: actionI18nTexts.resume, + icon: 'play', + onClick: (item) => this.props.resumeAutoFollowPattern(item.name), + available: (item) => !item.active, + 'data-test-subj': 'contextMenuResumeButton', }, { - render: ({ name }) => { - const label = i18n.translate( - 'xpack.crossClusterReplication.autoFollowPatternList.table.actionDeleteDescription', - { - defaultMessage: 'Delete auto-follow pattern', - } - ); - - return ( - - {(deleteAutoFollowPattern) => ( - deleteAutoFollowPattern(name)} - data-test-subj="contextMenuDeleteButton" - > - - {label} - - )} - - ); - }, + name: actionI18nTexts.edit, + description: actionI18nTexts.edit, + icon: 'pencil', + onClick: (item) => routing.navigate(routing.getAutoFollowPatternPath(item.name)), + 'data-test-subj': 'contextMenuEditButton', + }, + { + name: actionI18nTexts.delete, + description: actionI18nTexts.delete, + icon: 'trash', + onClick: (item) => deleteAutoFollowPattern(item.name), + 'data-test-subj': 'contextMenuDeleteButton', }, ], width: '100px', @@ -339,26 +311,30 @@ export class AutoFollowPatternTable extends PureComponent { }; return ( - - ({ - 'data-test-subj': 'row', - })} - cellProps={(item, column) => ({ - 'data-test-subj': `cell_${column.field}`, - })} - data-test-subj="autoFollowPatternListTable" - /> - {this.renderLoading()} - + + {(deleteAutoFollowPattern) => ( + <> + ({ + 'data-test-subj': 'row', + })} + cellProps={(item, column) => ({ + 'data-test-subj': `cell_${column.field}`, + })} + data-test-subj="autoFollowPatternListTable" + /> + {this.renderLoading()} + + )} + ); } } diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js index 0c57b3f7330cfd..2ea73e272b24e6 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { PureComponent, Fragment } from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { - EuiIcon, EuiHealth, EuiInMemoryTable, EuiLink, @@ -17,15 +16,38 @@ import { EuiOverlayMask, } from '@elastic/eui'; import { API_STATUS, UIM_FOLLOWER_INDEX_SHOW_DETAILS_CLICK } from '../../../../../constants'; -import { - FollowerIndexPauseProvider, - FollowerIndexResumeProvider, - FollowerIndexUnfollowProvider, -} from '../../../../../components'; +import { FollowerIndexActionsProvider } from '../../../../../components'; import { routing } from '../../../../../services/routing'; import { trackUiMetric } from '../../../../../services/track_ui_metric'; import { ContextMenu } from '../context_menu'; +const actionI18nTexts = { + pause: i18n.translate( + 'xpack.crossClusterReplication.followerIndexList.table.actionPauseDescription', + { + defaultMessage: 'Pause replication', + } + ), + resume: i18n.translate( + 'xpack.crossClusterReplication.followerIndexList.table.actionResumeDescription', + { + defaultMessage: 'Resume replication', + } + ), + edit: i18n.translate( + 'xpack.crossClusterReplication.followerIndexList.table.actionEditDescription', + { + defaultMessage: 'Edit follower index', + } + ), + unfollow: i18n.translate( + 'xpack.crossClusterReplication.followerIndexList.table.actionUnfollowDescription', + { + defaultMessage: 'Unfollow leader index', + } + ), +}; + const getFilteredIndices = (followerIndices, queryText) => { if (queryText) { const normalizedSearchText = queryText.toLowerCase(); @@ -101,91 +123,43 @@ export class FollowerIndicesTable extends PureComponent { routing.navigate(uri); }; - getTableColumns() { + getTableColumns(actionHandlers) { const { selectFollowerIndex } = this.props; const actions = [ - /* Pause or resume follower index */ + /* Pause follower index */ { - render: (followerIndex) => { - const { name, isPaused } = followerIndex; - const label = isPaused - ? i18n.translate( - 'xpack.crossClusterReplication.followerIndexList.table.actionResumeDescription', - { - defaultMessage: 'Resume replication', - } - ) - : i18n.translate( - 'xpack.crossClusterReplication.followerIndexList.table.actionPauseDescription', - { - defaultMessage: 'Pause replication', - } - ); - - return isPaused ? ( - - {(resumeFollowerIndex) => ( - resumeFollowerIndex(name)} data-test-subj="resumeButton"> - - {label} - - )} - - ) : ( - - {(pauseFollowerIndex) => ( - pauseFollowerIndex(followerIndex)} - data-test-subj="pauseButton" - > - - {label} - - )} - - ); - }, + name: actionI18nTexts.pause, + description: actionI18nTexts.pause, + icon: 'pause', + onClick: (item) => actionHandlers.pauseFollowerIndex(item), + available: (item) => !item.isPaused, + 'data-test-subj': 'pauseButton', + }, + /* Resume follower index */ + { + name: actionI18nTexts.resume, + description: actionI18nTexts.resume, + icon: 'play', + onClick: (item) => actionHandlers.resumeFollowerIndex(item.name), + available: (item) => item.isPaused, + 'data-test-subj': 'resumeButton', }, /* Edit follower index */ { - render: ({ name }) => { - const label = i18n.translate( - 'xpack.crossClusterReplication.followerIndexList.table.actionEditDescription', - { - defaultMessage: 'Edit follower index', - } - ); - - return ( - this.editFollowerIndex(name)} data-test-subj="editButton"> - - {label} - - ); - }, + name: actionI18nTexts.edit, + description: actionI18nTexts.edit, + onClick: (item) => this.editFollowerIndex(item.name), + icon: 'pencil', + 'data-test-subj': 'editButton', }, /* Unfollow leader index */ { - render: ({ name }) => { - const label = i18n.translate( - 'xpack.crossClusterReplication.followerIndexList.table.actionUnfollowDescription', - { - defaultMessage: 'Unfollow leader index', - } - ); - - return ( - - {(unfollowLeaderIndex) => ( - unfollowLeaderIndex(name)} data-test-subj="unfollowButton"> - - {label} - - )} - - ); - }, + name: actionI18nTexts.unfollow, + description: actionI18nTexts.unfollow, + onClick: (item) => actionHandlers.unfollowLeaderIndex(item.name), + icon: 'indexFlush', + 'data-test-subj': 'unfollowButton', }, ]; @@ -321,26 +295,33 @@ export class FollowerIndicesTable extends PureComponent { }; return ( - - ({ - 'data-test-subj': 'row', - })} - cellProps={(item, column) => ({ - 'data-test-subj': `cell-${column.field}`, - })} - data-test-subj="followerIndexListTable" - /> - {this.renderLoading()} - + + {(getActionHandlers) => { + const actionHandlers = getActionHandlers(); + return ( + <> + ({ + 'data-test-subj': 'row', + })} + cellProps={(item, column) => ({ + 'data-test-subj': `cell-${column.field}`, + })} + data-test-subj="followerIndexListTable" + /> + {this.renderLoading()} + + ); + }} + ); } } diff --git a/x-pack/plugins/dashboard_enhanced/jest.config.js b/x-pack/plugins/dashboard_enhanced/jest.config.js new file mode 100644 index 00000000000000..5aeb423383c418 --- /dev/null +++ b/x-pack/plugins/dashboard_enhanced/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/dashboard_enhanced'], +}; diff --git a/x-pack/plugins/dashboard_mode/jest.config.js b/x-pack/plugins/dashboard_mode/jest.config.js new file mode 100644 index 00000000000000..062ad302da7c41 --- /dev/null +++ b/x-pack/plugins/dashboard_mode/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/dashboard_mode'], +}; diff --git a/x-pack/plugins/data_enhanced/jest.config.js b/x-pack/plugins/data_enhanced/jest.config.js new file mode 100644 index 00000000000000..b0b1e2d94b40ab --- /dev/null +++ b/x-pack/plugins/data_enhanced/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/data_enhanced'], +}; diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index fa3206446f9fcf..a3b37e47287e50 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -68,6 +68,8 @@ export class DataEnhancedPlugin React.createElement( createConnectedBackgroundSessionIndicator({ sessionService: plugins.data.search.session, + application: core.application, + timeFilter: plugins.data.query.timefilter.timefilter, }) ) ), diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts index f4d7422d1c7e2b..20b55d9688edba 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts @@ -9,9 +9,10 @@ import { EnhancedSearchInterceptor } from './search_interceptor'; import { CoreSetup, CoreStart } from 'kibana/public'; import { UI_SETTINGS } from '../../../../../src/plugins/data/common'; import { AbortError } from '../../../../../src/plugins/kibana_utils/public'; -import { SearchTimeoutError } from 'src/plugins/data/public'; +import { ISessionService, SearchTimeoutError, SessionState } from 'src/plugins/data/public'; import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; import { bfetchPluginMock } from '../../../../../src/plugins/bfetch/public/mocks'; +import { BehaviorSubject } from 'rxjs'; const timeTravel = (msToRun = 0) => { jest.advanceTimersByTime(msToRun); @@ -43,11 +44,18 @@ function mockFetchImplementation(responses: any[]) { describe('EnhancedSearchInterceptor', () => { let mockUsageCollector: any; + let sessionService: jest.Mocked; + let sessionState$: BehaviorSubject; beforeEach(() => { mockCoreSetup = coreMock.createSetup(); mockCoreStart = coreMock.createStart(); + sessionState$ = new BehaviorSubject(SessionState.None); const dataPluginMockStart = dataPluginMock.createStartContract(); + sessionService = { + ...(dataPluginMockStart.search.session as jest.Mocked), + state$: sessionState$, + }; fetchMock = jest.fn(); mockCoreSetup.uiSettings.get.mockImplementation((name: string) => { @@ -87,7 +95,7 @@ describe('EnhancedSearchInterceptor', () => { http: mockCoreSetup.http, uiSettings: mockCoreSetup.uiSettings, usageCollector: mockUsageCollector, - session: dataPluginMockStart.search.session, + session: sessionService, }); }); @@ -144,6 +152,7 @@ describe('EnhancedSearchInterceptor', () => { }, }, ]; + mockFetchImplementation(responses); const response = searchInterceptor.search({}, { pollInterval: 0 }); @@ -361,6 +370,54 @@ describe('EnhancedSearchInterceptor', () => { expect(fetchMock).toHaveBeenCalledTimes(2); expect(mockCoreSetup.http.delete).toHaveBeenCalled(); }); + + test('should NOT DELETE a running SAVED async search on abort', async () => { + const sessionId = 'sessionId'; + sessionService.getSessionId.mockImplementation(() => sessionId); + const responses = [ + { + time: 10, + value: { + isPartial: true, + isRunning: true, + id: 1, + }, + }, + { + time: 300, + value: { + isPartial: false, + isRunning: false, + id: 1, + }, + }, + ]; + mockFetchImplementation(responses); + + const abortController = new AbortController(); + setTimeout(() => abortController.abort(), 250); + + const response = searchInterceptor.search( + {}, + { abortSignal: abortController.signal, pollInterval: 0, sessionId } + ); + response.subscribe({ next, error }); + + await timeTravel(10); + + expect(next).toHaveBeenCalled(); + expect(error).not.toHaveBeenCalled(); + + sessionState$.next(SessionState.BackgroundLoading); + + await timeTravel(240); + + expect(error).toHaveBeenCalled(); + expect(error.mock.calls[0][0]).toBeInstanceOf(AbortError); + + expect(fetchMock).toHaveBeenCalledTimes(2); + expect(mockCoreSetup.http.delete).not.toHaveBeenCalled(); + }); }); describe('cancelPending', () => { @@ -395,4 +452,108 @@ describe('EnhancedSearchInterceptor', () => { expect(mockUsageCollector.trackQueriesCancelled).toBeCalledTimes(1); }); }); + + describe('session', () => { + beforeEach(() => { + const responses = [ + { + time: 10, + value: { + isPartial: true, + isRunning: true, + id: 1, + }, + }, + { + time: 300, + value: { + isPartial: false, + isRunning: false, + id: 1, + }, + }, + ]; + + mockFetchImplementation(responses); + }); + + test('should track searches', async () => { + const sessionId = 'sessionId'; + sessionService.getSessionId.mockImplementation(() => sessionId); + + const untrack = jest.fn(); + sessionService.trackSearch.mockImplementation(() => untrack); + + const response = searchInterceptor.search({}, { pollInterval: 0, sessionId }); + response.subscribe({ next, error }); + await timeTravel(10); + expect(sessionService.trackSearch).toBeCalledTimes(1); + expect(untrack).not.toBeCalled(); + await timeTravel(300); + expect(sessionService.trackSearch).toBeCalledTimes(1); + expect(untrack).toBeCalledTimes(1); + }); + + test('session service should be able to cancel search', async () => { + const sessionId = 'sessionId'; + sessionService.getSessionId.mockImplementation(() => sessionId); + + const untrack = jest.fn(); + sessionService.trackSearch.mockImplementation(() => untrack); + + const response = searchInterceptor.search({}, { pollInterval: 0, sessionId }); + response.subscribe({ next, error }); + await timeTravel(10); + expect(sessionService.trackSearch).toBeCalledTimes(1); + + const abort = sessionService.trackSearch.mock.calls[0][0].abort; + expect(abort).toBeInstanceOf(Function); + + abort(); + + await timeTravel(10); + + expect(error).toHaveBeenCalled(); + expect(error.mock.calls[0][0]).toBeInstanceOf(AbortError); + }); + + test("don't track non current session searches", async () => { + const sessionId = 'sessionId'; + sessionService.getSessionId.mockImplementation(() => sessionId); + + const untrack = jest.fn(); + sessionService.trackSearch.mockImplementation(() => untrack); + + const response1 = searchInterceptor.search( + {}, + { pollInterval: 0, sessionId: 'something different' } + ); + response1.subscribe({ next, error }); + + const response2 = searchInterceptor.search({}, { pollInterval: 0, sessionId: undefined }); + response2.subscribe({ next, error }); + + await timeTravel(10); + expect(sessionService.trackSearch).toBeCalledTimes(0); + }); + + test("don't track if no current session", async () => { + sessionService.getSessionId.mockImplementation(() => undefined); + + const untrack = jest.fn(); + sessionService.trackSearch.mockImplementation(() => untrack); + + const response1 = searchInterceptor.search( + {}, + { pollInterval: 0, sessionId: 'something different' } + ); + response1.subscribe({ next, error }); + + const response2 = searchInterceptor.search({}, { pollInterval: 0, sessionId: undefined }); + response2.subscribe({ next, error }); + + await timeTravel(10); + expect(sessionService.trackSearch).toBeCalledTimes(0); + }); + }); }); diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts index 9aa35b460b1e85..0e87c093d2a8d9 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts @@ -5,13 +5,14 @@ */ import { throwError, Subscription } from 'rxjs'; -import { tap, finalize, catchError } from 'rxjs/operators'; +import { tap, finalize, catchError, filter, take, skip } from 'rxjs/operators'; import { TimeoutErrorMode, SearchInterceptor, SearchInterceptorDeps, UI_SETTINGS, IKibanaSearchRequest, + SessionState, } from '../../../../../src/plugins/data/public'; import { AbortError } from '../../../../../src/plugins/kibana_utils/common'; import { ENHANCED_ES_SEARCH_STRATEGY, IAsyncSearchOptions, pollSearch } from '../../common'; @@ -54,7 +55,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { }; public search({ id, ...request }: IKibanaSearchRequest, options: IAsyncSearchOptions = {}) { - const { combinedSignal, timeoutSignal, cleanup } = this.setupAbortSignal({ + const { combinedSignal, timeoutSignal, cleanup, abort } = this.setupAbortSignal({ abortSignal: options.abortSignal, timeout: this.searchTimeout, }); @@ -63,16 +64,41 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { const search = () => this.runSearch({ id, ...request }, searchOptions); this.pendingCount$.next(this.pendingCount$.getValue() + 1); + const isCurrentSession = () => + !!options.sessionId && options.sessionId === this.deps.session.getSessionId(); + + const untrackSearch = isCurrentSession() && this.deps.session.trackSearch({ abort }); + + // track if this search's session will be send to background + // if yes, then we don't need to cancel this search when it is aborted + let isSavedToBackground = false; + const savedToBackgroundSub = + isCurrentSession() && + this.deps.session.state$ + .pipe( + skip(1), // ignore any state, we are only interested in transition x -> BackgroundLoading + filter((state) => isCurrentSession() && state === SessionState.BackgroundLoading), + take(1) + ) + .subscribe(() => { + isSavedToBackground = true; + }); return pollSearch(search, { ...options, abortSignal: combinedSignal }).pipe( tap((response) => (id = response.id)), catchError((e: AbortError) => { - if (id) this.deps.http.delete(`/internal/search/${strategy}/${id}`); + if (id && !isSavedToBackground) this.deps.http.delete(`/internal/search/${strategy}/${id}`); return throwError(this.handleSearchError(e, timeoutSignal, options)); }), finalize(() => { this.pendingCount$.next(this.pendingCount$.getValue() - 1); cleanup(); + if (untrackSearch && isCurrentSession()) { + untrackSearch(); + } + if (savedToBackgroundSub) { + savedToBackgroundSub.unsubscribe(); + } }) ); } diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx index 9cef76c62279c1..4a6a852be755b5 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx @@ -7,24 +7,33 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { BackgroundSessionIndicator } from './background_session_indicator'; -import { BackgroundSessionViewState } from '../connected_background_session_indicator'; +import { SessionState } from '../../../../../../../src/plugins/data/public'; storiesOf('components/BackgroundSessionIndicator', module).add('default', () => ( <>
- +
- +
- +
- +
- + +
+
+
)); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx index 5b7ab2e4f9b1f0..b7d342300f3119 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx @@ -8,8 +8,8 @@ import React, { ReactNode } from 'react'; import { screen, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { BackgroundSessionIndicator } from './background_session_indicator'; -import { BackgroundSessionViewState } from '../connected_background_session_indicator'; import { IntlProvider } from 'react-intl'; +import { SessionState } from '../../../../../../../src/plugins/data/public'; function Container({ children }: { children?: ReactNode }) { return {children}; @@ -19,7 +19,7 @@ test('Loading state', async () => { const onCancel = jest.fn(); render( - + ); @@ -33,10 +33,7 @@ test('Completed state', async () => { const onSave = jest.fn(); render( - + ); @@ -50,10 +47,7 @@ test('Loading in the background state', async () => { const onCancel = jest.fn(); render( - + ); @@ -64,30 +58,26 @@ test('Loading in the background state', async () => { }); test('BackgroundCompleted state', async () => { - const onViewSession = jest.fn(); render( ); await userEvent.click(screen.getByLabelText('Results loaded in the background')); - await userEvent.click(screen.getByText('View background sessions')); - - expect(onViewSession).toBeCalled(); + expect(screen.getByRole('link', { name: 'View background sessions' }).getAttribute('href')).toBe( + '__link__' + ); }); test('Restored state', async () => { const onRefresh = jest.fn(); render( - + ); @@ -96,3 +86,27 @@ test('Restored state', async () => { expect(onRefresh).toBeCalled(); }); + +test('Canceled state', async () => { + const onRefresh = jest.fn(); + render( + + + + ); + + await userEvent.click(screen.getByLabelText('Canceled')); + await userEvent.click(screen.getByText('Refresh')); + + expect(onRefresh).toBeCalled(); +}); + +test('Disabled state', async () => { + render( + + + + ); + + expect(screen.getByTestId('backgroundSessionIndicator').querySelector('button')).toBeDisabled(); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx index b55bd6b6553716..ce77686c4f3c10 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx @@ -19,22 +19,29 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { BackgroundSessionViewState } from '../connected_background_session_indicator'; + import './background_session_indicator.scss'; +import { SessionState } from '../../../../../../../src/plugins/data/public/'; export interface BackgroundSessionIndicatorProps { - state: BackgroundSessionViewState; + state: SessionState; onContinueInBackground?: () => void; onCancel?: () => void; - onViewBackgroundSessions?: () => void; + viewBackgroundSessionsLink?: string; onSaveResults?: () => void; onRefresh?: () => void; + disabled?: boolean; + disabledReasonText?: string; } type ActionButtonProps = BackgroundSessionIndicatorProps & { buttonProps: EuiButtonEmptyProps }; const CancelButton = ({ onCancel = () => {}, buttonProps = {} }: ActionButtonProps) => ( - + {}, buttonProps = {}, }: ActionButtonProps) => ( - + {}, + viewBackgroundSessionsLink = 'management', buttonProps = {}, }: ActionButtonProps) => ( - // TODO: make this a link - + {}, buttonProps = {} }: ActionButtonProps) => ( - + {}, buttonProps = {} }: ActionButtonP ); const SaveButton = ({ onSaveResults = () => {}, buttonProps = {} }: ActionButtonProps) => ( - + {}, buttonProps = {} }: ActionButton ); const backgroundSessionIndicatorViewStateToProps: { - [state in BackgroundSessionViewState]: { - button: Pick & { tooltipText: string }; + [state in SessionState]: { + button: Pick & { + tooltipText: string; + }; popover: { text: string; primaryAction?: React.ComponentType; secondaryAction?: React.ComponentType; }; - }; + } | null; } = { - [BackgroundSessionViewState.Loading]: { + [SessionState.None]: null, + [SessionState.Loading]: { button: { color: 'subdued', iconType: 'clock', @@ -116,7 +141,7 @@ const backgroundSessionIndicatorViewStateToProps: { secondaryAction: ContinueInBackgroundButton, }, }, - [BackgroundSessionViewState.Completed]: { + [SessionState.Completed]: { button: { color: 'subdued', iconType: 'checkInCircleFilled', @@ -141,7 +166,7 @@ const backgroundSessionIndicatorViewStateToProps: { secondaryAction: ViewBackgroundSessionsButton, }, }, - [BackgroundSessionViewState.BackgroundLoading]: { + [SessionState.BackgroundLoading]: { button: { iconType: EuiLoadingSpinner, 'aria-label': i18n.translate( @@ -165,7 +190,7 @@ const backgroundSessionIndicatorViewStateToProps: { secondaryAction: ViewBackgroundSessionsButton, }, }, - [BackgroundSessionViewState.BackgroundCompleted]: { + [SessionState.BackgroundCompleted]: { button: { color: 'success', iconType: 'checkInCircleFilled', @@ -192,7 +217,7 @@ const backgroundSessionIndicatorViewStateToProps: { primaryAction: ViewBackgroundSessionsButton, }, }, - [BackgroundSessionViewState.Restored]: { + [SessionState.Restored]: { button: { color: 'warning', iconType: 'refresh', @@ -217,6 +242,25 @@ const backgroundSessionIndicatorViewStateToProps: { secondaryAction: ViewBackgroundSessionsButton, }, }, + [SessionState.Canceled]: { + button: { + color: 'subdued', + iconType: 'refresh', + 'aria-label': i18n.translate('xpack.data.backgroundSessionIndicator.canceledIconAriaLabel', { + defaultMessage: 'Canceled', + }), + tooltipText: i18n.translate('xpack.data.backgroundSessionIndicator.canceledTooltipText', { + defaultMessage: 'Search was canceled', + }), + }, + popover: { + text: i18n.translate('xpack.data.backgroundSessionIndicator.canceledText', { + defaultMessage: 'Search was canceled', + }), + primaryAction: RefreshButton, + secondaryAction: ViewBackgroundSessionsButton, + }, + }, }; const VerticalDivider: React.FC = () => ( @@ -228,7 +272,9 @@ export const BackgroundSessionIndicator: React.FC setIsPopoverOpen((isOpen) => !isOpen); const closePopover = () => setIsPopoverOpen(false); - const { button, popover } = backgroundSessionIndicatorViewStateToProps[props.state]; + if (!backgroundSessionIndicatorViewStateToProps[props.state]) return null; + + const { button, popover } = backgroundSessionIndicatorViewStateToProps[props.state]!; return ( + } @@ -255,6 +303,7 @@ export const BackgroundSessionIndicator: React.FC diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx index d97d10512783c7..e08773c6a8a760 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx @@ -5,17 +5,37 @@ */ import React from 'react'; -import { render, waitFor } from '@testing-library/react'; +import { render, waitFor, screen, act } from '@testing-library/react'; import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; import { createConnectedBackgroundSessionIndicator } from './connected_background_session_indicator'; import { BehaviorSubject } from 'rxjs'; -import { ISessionService } from '../../../../../../../src/plugins/data/public'; +import { + ISessionService, + RefreshInterval, + SessionState, + TimefilterContract, +} from '../../../../../../../src/plugins/data/public'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; -const sessionService = dataPluginMock.createStartContract().search - .session as jest.Mocked; +const coreStart = coreMock.createStart(); +const dataStart = dataPluginMock.createStartContract(); +const sessionService = dataStart.search.session as jest.Mocked; + +const refreshInterval$ = new BehaviorSubject({ value: 0, pause: true }); +const timeFilter = dataStart.query.timefilter.timefilter as jest.Mocked; +timeFilter.getRefreshIntervalUpdate$.mockImplementation(() => refreshInterval$); +timeFilter.getRefreshInterval.mockImplementation(() => refreshInterval$.getValue()); + +beforeEach(() => { + refreshInterval$.next({ value: 0, pause: true }); +}); test("shouldn't show indicator in case no active search session", async () => { - const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ sessionService }); + const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ + sessionService, + application: coreStart.application, + timeFilter, + }); const { getByTestId, container } = render(); // make sure `backgroundSessionIndicator` isn't appearing after some time (lazy-loading) @@ -26,11 +46,36 @@ test("shouldn't show indicator in case no active search session", async () => { }); test('should show indicator in case there is an active search session', async () => { - const session$ = new BehaviorSubject('session_id'); - sessionService.getSession$.mockImplementation(() => session$); - sessionService.getSessionId.mockImplementation(() => session$.getValue()); - const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ sessionService }); + const state$ = new BehaviorSubject(SessionState.Loading); + const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ + sessionService: { ...sessionService, state$ }, + application: coreStart.application, + timeFilter, + }); const { getByTestId } = render(); await waitFor(() => getByTestId('backgroundSessionIndicator')); }); + +test('should be disabled during auto-refresh', async () => { + const state$ = new BehaviorSubject(SessionState.Loading); + const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ + sessionService: { ...sessionService, state$ }, + application: coreStart.application, + timeFilter, + }); + + render(); + + await waitFor(() => screen.getByTestId('backgroundSessionIndicator')); + + expect( + screen.getByTestId('backgroundSessionIndicator').querySelector('button') + ).not.toBeDisabled(); + + act(() => { + refreshInterval$.next({ value: 0, pause: false }); + }); + + expect(screen.getByTestId('backgroundSessionIndicator').querySelector('button')).toBeDisabled(); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx index d097a1aecb66af..b80295d87d2023 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx @@ -5,28 +5,67 @@ */ import React from 'react'; +import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'; import useObservable from 'react-use/lib/useObservable'; -import { distinctUntilChanged, map } from 'rxjs/operators'; +import { i18n } from '@kbn/i18n'; import { BackgroundSessionIndicator } from '../background_session_indicator'; -import { ISessionService } from '../../../../../../../src/plugins/data/public/'; -import { BackgroundSessionViewState } from './background_session_view_state'; +import { ISessionService, TimefilterContract } from '../../../../../../../src/plugins/data/public/'; +import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public'; +import { ApplicationStart } from '../../../../../../../src/core/public'; export interface BackgroundSessionIndicatorDeps { sessionService: ISessionService; + timeFilter: TimefilterContract; + application: ApplicationStart; } export const createConnectedBackgroundSessionIndicator = ({ sessionService, + application, + timeFilter, }: BackgroundSessionIndicatorDeps): React.FC => { - const sessionId$ = sessionService.getSession$(); - const hasActiveSession$ = sessionId$.pipe( - map((sessionId) => !!sessionId), - distinctUntilChanged() - ); + const isAutoRefreshEnabled = () => !timeFilter.getRefreshInterval().pause; + const isAutoRefreshEnabled$ = timeFilter + .getRefreshIntervalUpdate$() + .pipe(map(isAutoRefreshEnabled), distinctUntilChanged()); return () => { - const isSession = useObservable(hasActiveSession$, !!sessionService.getSessionId()); - if (!isSession) return null; - return ; + const state = useObservable(sessionService.state$.pipe(debounceTime(500))); + const autoRefreshEnabled = useObservable(isAutoRefreshEnabled$, isAutoRefreshEnabled()); + let disabled = false; + let disabledReasonText: string = ''; + + if (autoRefreshEnabled) { + disabled = true; + disabledReasonText = i18n.translate( + 'xpack.data.backgroundSessionIndicator.disabledDueToAutoRefreshMessage', + { + defaultMessage: 'Send to background is not available when auto refresh is enabled.', + } + ); + } + + if (!state) return null; + return ( + + { + sessionService.save(); + }} + onSaveResults={() => { + sessionService.save(); + }} + onRefresh={() => { + sessionService.refresh(); + }} + onCancel={() => { + sessionService.cancel(); + }} + disabled={disabled} + disabledReasonText={disabledReasonText} + /> + + ); }; }; diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/index.ts b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/index.ts index adbb6edbbfcf3f..223a0537129df5 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/index.ts +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/index.ts @@ -8,4 +8,3 @@ export { BackgroundSessionIndicatorDeps, createConnectedBackgroundSessionIndicator, } from './connected_background_session_indicator'; -export { BackgroundSessionViewState } from './background_session_view_state'; diff --git a/x-pack/plugins/discover_enhanced/jest.config.js b/x-pack/plugins/discover_enhanced/jest.config.js new file mode 100644 index 00000000000000..00e040beba4116 --- /dev/null +++ b/x-pack/plugins/discover_enhanced/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/discover_enhanced'], +}; diff --git a/x-pack/plugins/drilldowns/jest.config.js b/x-pack/plugins/drilldowns/jest.config.js new file mode 100644 index 00000000000000..a7d79f8dac378e --- /dev/null +++ b/x-pack/plugins/drilldowns/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/drilldowns'], +}; diff --git a/x-pack/plugins/embeddable_enhanced/jest.config.js b/x-pack/plugins/embeddable_enhanced/jest.config.js new file mode 100644 index 00000000000000..c5c62f98ca2f3d --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/embeddable_enhanced'], +}; diff --git a/x-pack/plugins/encrypted_saved_objects/jest.config.js b/x-pack/plugins/encrypted_saved_objects/jest.config.js new file mode 100644 index 00000000000000..0883bdb224dd03 --- /dev/null +++ b/x-pack/plugins/encrypted_saved_objects/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/encrypted_saved_objects'], +}; diff --git a/x-pack/plugins/enterprise_search/jest.config.js b/x-pack/plugins/enterprise_search/jest.config.js new file mode 100644 index 00000000000000..db6a25a1f7efd9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/enterprise_search'], +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/constants.ts b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/constants.ts new file mode 100644 index 00000000000000..b2b76d5b987b99 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/constants.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const INDEXING_STATUS_PROGRESS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.indexingStatus.progress.title', + { + defaultMessage: 'Indexing progress', + } +); + +export const INDEXING_STATUS_HAS_ERRORS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.indexingStatus.hasErrors.title', + { + defaultMessage: 'Several documents have field conversion errors.', + } +); + +export const INDEXING_STATUS_HAS_ERRORS_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.indexingStatus.hasErrors.button', + { + defaultMessage: 'View errors', + } +); diff --git a/x-pack/plugins/telemetry_collection_xpack/common/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/index.ts similarity index 68% rename from x-pack/plugins/telemetry_collection_xpack/common/index.ts rename to x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/index.ts index 2b08ebe2e7bbf2..4a97f11e8f0ee3 100644 --- a/x-pack/plugins/telemetry_collection_xpack/common/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/index.ts @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const PLUGIN_ID = 'telemetryCollectionXpack'; -export const PLUGIN_NAME = 'telemetry_collection_xpack'; +export { IndexingStatus } from './indexing_status'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.test.tsx new file mode 100644 index 00000000000000..42cb6c229ad638 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.test.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../__mocks__/kea.mock'; +import '../../__mocks__/shallow_useeffect.mock'; + +import { setMockActions, setMockValues } from '../../__mocks__'; + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { EuiPanel } from '@elastic/eui'; + +import { IndexingStatusContent } from './indexing_status_content'; +import { IndexingStatusErrors } from './indexing_status_errors'; +import { IndexingStatus } from './indexing_status'; + +describe('IndexingStatus', () => { + const getItemDetailPath = jest.fn(); + const onComplete = jest.fn(); + const setGlobalIndexingStatus = jest.fn(); + const fetchIndexingStatus = jest.fn(); + + const props = { + percentageComplete: 50, + numDocumentsWithErrors: 1, + activeReindexJobId: 12, + viewLinkPath: '/path', + statusPath: '/other_path', + itemId: '1', + getItemDetailPath, + onComplete, + setGlobalIndexingStatus, + }; + + beforeEach(() => { + setMockActions({ fetchIndexingStatus }); + }); + + it('renders', () => { + setMockValues({ + percentageComplete: 50, + numDocumentsWithErrors: 0, + }); + const wrapper = shallow(); + + expect(wrapper.find(EuiPanel)).toHaveLength(1); + expect(wrapper.find(IndexingStatusContent)).toHaveLength(1); + expect(fetchIndexingStatus).toHaveBeenCalled(); + }); + + it('renders errors', () => { + setMockValues({ + percentageComplete: 100, + numDocumentsWithErrors: 1, + }); + const wrapper = shallow(); + + expect(wrapper.find(IndexingStatusErrors)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.tsx new file mode 100644 index 00000000000000..b2109b7ef3f0bd --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect } from 'react'; + +import { useValues, useActions } from 'kea'; + +import { EuiPanel, EuiSpacer } from '@elastic/eui'; + +import { IndexingStatusContent } from './indexing_status_content'; +import { IndexingStatusErrors } from './indexing_status_errors'; +import { IndexingStatusLogic } from './indexing_status_logic'; + +import { IIndexingStatus } from '../types'; + +export interface IIndexingStatusProps { + viewLinkPath: string; + itemId: string; + statusPath: string; + getItemDetailPath?(itemId: string): string; + onComplete(numDocumentsWithErrors: number): void; + setGlobalIndexingStatus?(activeReindexJob: IIndexingStatus): void; +} + +export const IndexingStatus: React.FC = ({ + viewLinkPath, + statusPath, + onComplete, +}) => { + const { percentageComplete, numDocumentsWithErrors } = useValues(IndexingStatusLogic); + const { fetchIndexingStatus } = useActions(IndexingStatusLogic); + + useEffect(() => { + fetchIndexingStatus({ statusPath, onComplete }); + }, []); + + return ( + <> + {percentageComplete < 100 && ( + + + + )} + {percentageComplete === 100 && numDocumentsWithErrors > 0 && ( + <> + + + + )} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.test.tsx new file mode 100644 index 00000000000000..9fe0e890e6943e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.test.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { EuiProgress, EuiTitle } from '@elastic/eui'; + +import { IndexingStatusContent } from './indexing_status_content'; + +describe('IndexingStatusContent', () => { + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiTitle)).toHaveLength(1); + expect(wrapper.find(EuiProgress)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.tsx new file mode 100644 index 00000000000000..a0c67388621a82 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { EuiProgress, EuiSpacer, EuiTitle } from '@elastic/eui'; + +import { INDEXING_STATUS_PROGRESS_TITLE } from './constants'; + +interface IIndexingStatusContentProps { + percentageComplete: number; +} + +export const IndexingStatusContent: React.FC = ({ + percentageComplete, +}) => ( +
+ +

{INDEXING_STATUS_PROGRESS_TITLE}

+
+ + +
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.test.tsx new file mode 100644 index 00000000000000..fc706aee659a5a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.test.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { EuiCallOut, EuiButton } from '@elastic/eui'; + +import { EuiLinkTo } from '../react_router_helpers'; + +import { IndexingStatusErrors } from './indexing_status_errors'; + +describe('IndexingStatusErrors', () => { + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiButton)).toHaveLength(1); + expect(wrapper.find(EuiCallOut)).toHaveLength(1); + expect(wrapper.find(EuiLinkTo)).toHaveLength(1); + expect(wrapper.find(EuiLinkTo).prop('to')).toEqual('/path'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.tsx new file mode 100644 index 00000000000000..a928400b2338ca --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { EuiButton, EuiCallOut } from '@elastic/eui'; + +import { EuiLinkTo } from '../react_router_helpers'; + +import { INDEXING_STATUS_HAS_ERRORS_TITLE, INDEXING_STATUS_HAS_ERRORS_BUTTON } from './constants'; + +interface IIndexingStatusErrorsProps { + viewLinkPath: string; +} + +export const IndexingStatusErrors: React.FC = ({ viewLinkPath }) => ( + +

{INDEXING_STATUS_HAS_ERRORS_TITLE}

+ + {INDEXING_STATUS_HAS_ERRORS_BUTTON} + +
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_logic.test.ts new file mode 100644 index 00000000000000..9fa5fe0f84babf --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_logic.test.ts @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resetContext } from 'kea'; + +jest.mock('../http', () => ({ + HttpLogic: { + values: { http: { get: jest.fn() } }, + }, +})); +import { HttpLogic } from '../http'; + +jest.mock('../flash_messages', () => ({ + flashAPIErrors: jest.fn(), +})); +import { flashAPIErrors } from '../flash_messages'; + +import { IndexingStatusLogic } from './indexing_status_logic'; + +describe('IndexingStatusLogic', () => { + let unmount: any; + + const mockStatusResponse = { + percentageComplete: 50, + numDocumentsWithErrors: 3, + activeReindexJobId: 1, + }; + + beforeEach(() => { + jest.clearAllMocks(); + resetContext({}); + unmount = IndexingStatusLogic.mount(); + }); + + it('has expected default values', () => { + expect(IndexingStatusLogic.values).toEqual({ + percentageComplete: 100, + numDocumentsWithErrors: 0, + }); + }); + + describe('setIndexingStatus', () => { + it('sets reducers', () => { + IndexingStatusLogic.actions.setIndexingStatus(mockStatusResponse); + + expect(IndexingStatusLogic.values.percentageComplete).toEqual( + mockStatusResponse.percentageComplete + ); + expect(IndexingStatusLogic.values.numDocumentsWithErrors).toEqual( + mockStatusResponse.numDocumentsWithErrors + ); + }); + }); + + describe('fetchIndexingStatus', () => { + jest.useFakeTimers(); + const statusPath = '/api/workplace_search/path/123'; + const onComplete = jest.fn(); + const TIMEOUT = 3000; + + it('calls API and sets values', async () => { + const setIndexingStatusSpy = jest.spyOn(IndexingStatusLogic.actions, 'setIndexingStatus'); + const promise = Promise.resolve(mockStatusResponse); + (HttpLogic.values.http.get as jest.Mock).mockReturnValue(promise); + + IndexingStatusLogic.actions.fetchIndexingStatus({ statusPath, onComplete }); + jest.advanceTimersByTime(TIMEOUT); + + expect(HttpLogic.values.http.get).toHaveBeenCalledWith(statusPath); + await promise; + + expect(setIndexingStatusSpy).toHaveBeenCalledWith(mockStatusResponse); + }); + + it('handles error', async () => { + const promise = Promise.reject('An error occured'); + (HttpLogic.values.http.get as jest.Mock).mockReturnValue(promise); + + IndexingStatusLogic.actions.fetchIndexingStatus({ statusPath, onComplete }); + jest.advanceTimersByTime(TIMEOUT); + + try { + await promise; + } catch { + // Do nothing + } + expect(flashAPIErrors).toHaveBeenCalledWith('An error occured'); + }); + + it('handles indexing complete state', async () => { + const promise = Promise.resolve({ ...mockStatusResponse, percentageComplete: 100 }); + (HttpLogic.values.http.get as jest.Mock).mockReturnValue(promise); + IndexingStatusLogic.actions.fetchIndexingStatus({ statusPath, onComplete }); + jest.advanceTimersByTime(TIMEOUT); + + await promise; + + expect(clearInterval).toHaveBeenCalled(); + expect(onComplete).toHaveBeenCalledWith(mockStatusResponse.numDocumentsWithErrors); + }); + + it('handles unmounting', async () => { + unmount(); + expect(clearInterval).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_logic.ts new file mode 100644 index 00000000000000..cb484f19c157f7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_logic.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { kea, MakeLogicType } from 'kea'; + +import { HttpLogic } from '../http'; +import { IIndexingStatus } from '../types'; +import { flashAPIErrors } from '../flash_messages'; + +interface IndexingStatusProps { + statusPath: string; + onComplete(numDocumentsWithErrors: number): void; +} + +interface IndexingStatusActions { + fetchIndexingStatus(props: IndexingStatusProps): IndexingStatusProps; + setIndexingStatus({ + percentageComplete, + numDocumentsWithErrors, + }: IIndexingStatus): IIndexingStatus; +} + +interface IndexingStatusValues { + percentageComplete: number; + numDocumentsWithErrors: number; +} + +let pollingInterval: number; + +export const IndexingStatusLogic = kea>({ + actions: { + fetchIndexingStatus: ({ statusPath, onComplete }) => ({ statusPath, onComplete }), + setIndexingStatus: ({ numDocumentsWithErrors, percentageComplete }) => ({ + numDocumentsWithErrors, + percentageComplete, + }), + }, + reducers: { + percentageComplete: [ + 100, + { + setIndexingStatus: (_, { percentageComplete }) => percentageComplete, + }, + ], + numDocumentsWithErrors: [ + 0, + { + setIndexingStatus: (_, { numDocumentsWithErrors }) => numDocumentsWithErrors, + }, + ], + }, + listeners: ({ actions }) => ({ + fetchIndexingStatus: ({ statusPath, onComplete }: IndexingStatusProps) => { + const { http } = HttpLogic.values; + + pollingInterval = window.setInterval(async () => { + try { + const response: IIndexingStatus = await http.get(statusPath); + if (response.percentageComplete >= 100) { + clearInterval(pollingInterval); + onComplete(response.numDocumentsWithErrors); + } + actions.setIndexingStatus(response); + } catch (e) { + flashAPIErrors(e); + } + }, 3000); + }, + }), + events: () => ({ + beforeUnmount() { + clearInterval(pollingInterval); + }, + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/cloud/instructions.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/cloud/instructions.test.tsx index 3c93e3fd49dcc5..56ed5f182fbb37 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/cloud/instructions.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/cloud/instructions.test.tsx @@ -27,7 +27,7 @@ describe('CloudSetupInstructions', () => { ); const cloudDeploymentLink = wrapper.find(EuiLink).first(); expect(cloudDeploymentLink.prop('href')).toEqual( - 'https://cloud.elastic.co/deployments/some-id' + 'https://cloud.elastic.co/deployments/some-id/edit' ); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/cloud/instructions.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/cloud/instructions.tsx index 7a7dfa62dbe398..383fd4b11108a4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/cloud/instructions.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/setup_guide/cloud/instructions.tsx @@ -23,18 +23,18 @@ export const CloudSetupInstructions: React.FC = ({ productName, cloudDepl steps={[ { title: i18n.translate('xpack.enterpriseSearch.setupGuide.cloud.step1.title', { - defaultMessage: 'Edit your Elastic Cloud deployment’s configuration', + defaultMessage: 'Edit your deployment’s configuration', }), children: (

- Visit the Elastic Cloud console + editDeploymentLink: cloudDeploymentLink ? ( + + edit your deployment ) : ( 'Visit the Elastic Cloud console' diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts new file mode 100644 index 00000000000000..3866d1a7199e40 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface IIndexingStatus { + percentageComplete: number; + numDocumentsWithErrors: number; + activeReindexJobId: number; +} diff --git a/x-pack/plugins/enterprise_search/server/collectors/lib/telemetry.test.ts b/x-pack/plugins/enterprise_search/server/collectors/lib/telemetry.test.ts index 6cf0be9fd1f313..b4b7a489c6ea35 100644 --- a/x-pack/plugins/enterprise_search/server/collectors/lib/telemetry.test.ts +++ b/x-pack/plugins/enterprise_search/server/collectors/lib/telemetry.test.ts @@ -61,7 +61,7 @@ describe('Telemetry helpers', () => { expect(incrementCounterMock).toHaveBeenCalledWith( 'app_search_telemetry', 'app_search_telemetry', - 'ui_clicked.button' + ['ui_clicked.button'] ); expect(response).toEqual({ success: true }); }); diff --git a/x-pack/plugins/enterprise_search/server/collectors/lib/telemetry.ts b/x-pack/plugins/enterprise_search/server/collectors/lib/telemetry.ts index cd8ad72bf8358c..deba94fc0bd5e8 100644 --- a/x-pack/plugins/enterprise_search/server/collectors/lib/telemetry.ts +++ b/x-pack/plugins/enterprise_search/server/collectors/lib/telemetry.ts @@ -55,7 +55,7 @@ export async function incrementUICounter({ await internalRepository.incrementCounter( id, id, - `${uiAction}.${metric}` // e.g., ui_viewed.setup_guide + [`${uiAction}.${metric}`] // e.g., ui_viewed.setup_guide ); return { success: true }; diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts index 62f4dceeac363a..d97a587e57ff25 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts @@ -19,6 +19,9 @@ import { registerAccountPrepareSourcesRoute, registerAccountSourceSearchableRoute, registerAccountSourceDisplaySettingsConfig, + registerAccountSourceSchemasRoute, + registerAccountSourceReindexJobRoute, + registerAccountSourceReindexJobStatusRoute, registerOrgSourcesRoute, registerOrgSourcesStatusRoute, registerOrgSourceRoute, @@ -31,6 +34,9 @@ import { registerOrgPrepareSourcesRoute, registerOrgSourceSearchableRoute, registerOrgSourceDisplaySettingsConfig, + registerOrgSourceSchemasRoute, + registerOrgSourceReindexJobRoute, + registerOrgSourceReindexJobStatusRoute, registerOrgSourceOauthConfigurationsRoute, registerOrgSourceOauthConfigurationRoute, } from './sources'; @@ -523,6 +529,139 @@ describe('sources routes', () => { }); }); + describe('GET /api/workplace_search/account/sources/{id}/schemas', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/account/sources/{id}/schemas', + payload: 'params', + }); + + registerAccountSourceSchemasRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + id: '123', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/sources/123/schemas', + }); + }); + }); + + describe('POST /api/workplace_search/account/sources/{id}/schemas', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'post', + path: '/api/workplace_search/account/sources/{id}/schemas', + payload: 'body', + }); + + registerAccountSourceSchemasRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + const mockRequest = { + params: { id: '123' }, + body: {}, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/sources/123/schemas', + body: mockRequest.body, + }); + }); + }); + + describe('GET /api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}', + payload: 'params', + }); + + registerAccountSourceReindexJobRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + source_id: '123', + job_id: '345', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/sources/123/reindex_job/345', + }); + }); + }); + + describe('GET /api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}/status', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}/status', + payload: 'params', + }); + + registerAccountSourceReindexJobStatusRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + source_id: '123', + job_id: '345', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/sources/123/reindex_job/345/status', + }); + }); + }); + describe('GET /api/workplace_search/org/sources', () => { let mockRouter: MockRouter; @@ -1000,6 +1139,139 @@ describe('sources routes', () => { }); }); + describe('GET /api/workplace_search/org/sources/{id}/schemas', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/org/sources/{id}/schemas', + payload: 'params', + }); + + registerOrgSourceSchemasRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + id: '123', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/org/sources/123/schemas', + }); + }); + }); + + describe('POST /api/workplace_search/org/sources/{id}/schemas', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'post', + path: '/api/workplace_search/org/sources/{id}/schemas', + payload: 'body', + }); + + registerOrgSourceSchemasRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + const mockRequest = { + params: { id: '123' }, + body: {}, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/org/sources/123/schemas', + body: mockRequest.body, + }); + }); + }); + + describe('GET /api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}', + payload: 'params', + }); + + registerOrgSourceReindexJobRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + source_id: '123', + job_id: '345', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/org/sources/123/reindex_job/345', + }); + }); + }); + + describe('GET /api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}/status', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}/status', + payload: 'params', + }); + + registerOrgSourceReindexJobStatusRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + source_id: '123', + job_id: '345', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/org/sources/123/reindex_job/345/status', + }); + }); + }); + describe('GET /api/workplace_search/org/settings/connectors', () => { let mockRouter: MockRouter; diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts index d43a4252c7e1f8..9beac109be5106 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts @@ -339,6 +339,89 @@ export function registerAccountSourceDisplaySettingsConfig({ ); } +export function registerAccountSourceSchemasRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/account/sources/{id}/schemas', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/sources/${request.params.id}/schemas`, + })(context, request, response); + } + ); + + router.post( + { + path: '/api/workplace_search/account/sources/{id}/schemas', + validate: { + body: schema.object({}), + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/sources/${request.params.id}/schemas`, + body: request.body, + })(context, request, response); + } + ); +} + +export function registerAccountSourceReindexJobRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}', + validate: { + params: schema.object({ + source_id: schema.string(), + job_id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/sources/${request.params.source_id}/reindex_job/${request.params.job_id}`, + })(context, request, response); + } + ); +} + +export function registerAccountSourceReindexJobStatusRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}/status', + validate: { + params: schema.object({ + source_id: schema.string(), + job_id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/sources/${request.params.source_id}/reindex_job/${request.params.job_id}/status`, + })(context, request, response); + } + ); +} + export function registerOrgSourcesRoute({ router, enterpriseSearchRequestHandler, @@ -638,6 +721,89 @@ export function registerOrgSourceDisplaySettingsConfig({ ); } +export function registerOrgSourceSchemasRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/org/sources/{id}/schemas', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/org/sources/${request.params.id}/schemas`, + })(context, request, response); + } + ); + + router.post( + { + path: '/api/workplace_search/org/sources/{id}/schemas', + validate: { + body: schema.object({}), + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/org/sources/${request.params.id}/schemas`, + body: request.body, + })(context, request, response); + } + ); +} + +export function registerOrgSourceReindexJobRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}', + validate: { + params: schema.object({ + source_id: schema.string(), + job_id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/org/sources/${request.params.source_id}/reindex_job/${request.params.job_id}`, + })(context, request, response); + } + ); +} + +export function registerOrgSourceReindexJobStatusRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}/status', + validate: { + params: schema.object({ + source_id: schema.string(), + job_id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/org/sources/${request.params.source_id}/reindex_job/${request.params.job_id}/status`, + })(context, request, response); + } + ); +} + export function registerOrgSourceOauthConfigurationsRoute({ router, enterpriseSearchRequestHandler, @@ -741,6 +907,9 @@ export const registerSourcesRoutes = (dependencies: RouteDependencies) => { registerAccountPrepareSourcesRoute(dependencies); registerAccountSourceSearchableRoute(dependencies); registerAccountSourceDisplaySettingsConfig(dependencies); + registerAccountSourceSchemasRoute(dependencies); + registerAccountSourceReindexJobRoute(dependencies); + registerAccountSourceReindexJobStatusRoute(dependencies); registerOrgSourcesRoute(dependencies); registerOrgSourcesStatusRoute(dependencies); registerOrgSourceRoute(dependencies); @@ -753,6 +922,9 @@ export const registerSourcesRoutes = (dependencies: RouteDependencies) => { registerOrgPrepareSourcesRoute(dependencies); registerOrgSourceSearchableRoute(dependencies); registerOrgSourceDisplaySettingsConfig(dependencies); + registerOrgSourceSchemasRoute(dependencies); + registerOrgSourceReindexJobRoute(dependencies); + registerOrgSourceReindexJobStatusRoute(dependencies); registerOrgSourceOauthConfigurationsRoute(dependencies); registerOrgSourceOauthConfigurationRoute(dependencies); }; diff --git a/x-pack/plugins/event_log/jest.config.js b/x-pack/plugins/event_log/jest.config.js new file mode 100644 index 00000000000000..bb847d3b3c7ce4 --- /dev/null +++ b/x-pack/plugins/event_log/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/event_log'], +}; diff --git a/x-pack/plugins/features/jest.config.js b/x-pack/plugins/features/jest.config.js new file mode 100644 index 00000000000000..e500d35bbbd609 --- /dev/null +++ b/x-pack/plugins/features/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/features'], +}; diff --git a/x-pack/plugins/file_upload/jest.config.js b/x-pack/plugins/file_upload/jest.config.js new file mode 100644 index 00000000000000..6a042a4cc5c1e1 --- /dev/null +++ b/x-pack/plugins/file_upload/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/file_upload'], +}; diff --git a/x-pack/plugins/fleet/jest.config.js b/x-pack/plugins/fleet/jest.config.js new file mode 100644 index 00000000000000..521cb7467b1967 --- /dev/null +++ b/x-pack/plugins/fleet/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/fleet'], +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/app.tsx b/x-pack/plugins/fleet/public/applications/fleet/app.tsx index 766ad961674af5..ed91c1cb1479ca 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/app.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/app.tsx @@ -186,16 +186,9 @@ export const FleetAppContext: React.FC<{ /** For testing purposes only */ routerHistory?: History; }> = memo( - ({ - children, - startServices, - config, - history, - kibanaVersion, - extensions, - routerHistory = createHashHistory(), - }) => { + ({ children, startServices, config, history, kibanaVersion, extensions, routerHistory }) => { const isDarkMode = useObservable(startServices.uiSettings.get$('theme:darkMode')); + const [routerHistoryInstance] = useState(routerHistory || createHashHistory()); return ( @@ -207,7 +200,7 @@ export const FleetAppContext: React.FC<{ - + {children} diff --git a/x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts b/x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts index ecd4227a54b655..2fce7f8f5e825e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts @@ -78,8 +78,8 @@ export const pagePathGetters: { `/policies/${policyId}/edit-integration/${packagePolicyId}`, fleet: () => '/fleet', fleet_agent_list: ({ kuery }) => `/fleet/agents${kuery ? `?kuery=${kuery}` : ''}`, - fleet_agent_details: ({ agentId, tabId }) => - `/fleet/agents/${agentId}${tabId ? `/${tabId}` : ''}`, + fleet_agent_details: ({ agentId, tabId, logQuery }) => + `/fleet/agents/${agentId}${tabId ? `/${tabId}` : ''}${logQuery ? `?_q=${logQuery}` : ''}`, fleet_enrollment_tokens: () => '/fleet/enrollment-tokens', data_streams: () => '/data-streams', }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx index a68dbe52555ffe..4b471e661d8806 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx @@ -78,7 +78,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { const [isLoadingSecondStep, setIsLoadingSecondStep] = useState(false); // Retrieve agent count - const agentPolicyId = useMemo(() => agentPolicy?.id, [agentPolicy?.id]); + const agentPolicyId = agentPolicy?.id; useEffect(() => { const getAgentCount = async () => { const { data } = await sendGetAgentStatus({ policyId: agentPolicyId }); @@ -331,15 +331,20 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { updatePackagePolicy={updatePackagePolicy} validationResults={validationResults!} /> - + + {/* Only show the out-of-box configuration step if a UI extension is NOT registered */} + {!ExtensionView && ( + + )} + {/* If an Agent Policy and a package has been selected, then show UI extension (if any) */} - {packagePolicy.policy_id && packagePolicy.package?.name && ExtensionView && ( + {ExtensionView && packagePolicy.policy_id && packagePolicy.package?.name && ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx index 671bc829af82a8..e8c1ef6469925c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx @@ -22,7 +22,6 @@ import { Loading } from '../../../components'; import { PackagePolicyValidationResults } from './services'; import { PackagePolicyInputPanel } from './components'; import { CreatePackagePolicyFrom } from './types'; -import { useUIExtension } from '../../../hooks/use_ui_extension'; const findStreamsForInputType = ( inputType: string, @@ -63,12 +62,6 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{ validationResults, submitAttempted, }) => { - const hasUiExtension = - useUIExtension( - packageInfo.name, - from === 'edit' ? 'package-policy-edit' : 'package-policy-create' - ) !== undefined; - // Configure inputs (and their streams) // Assume packages only export one config template for now const renderConfigureInputs = () => @@ -112,7 +105,7 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{ })} - ) : hasUiExtension ? null : ( + ) : ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index c0db51873e52ef..8f798445b2362f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -244,6 +244,7 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => { packagePolicyName: packagePolicy.name, }, }), + 'data-test-subj': 'policyUpdateSuccessToast', text: agentCount && agentPolicy ? i18n.translate('xpack.fleet.editPackagePolicy.updatedNotificationMessage', { @@ -305,20 +306,23 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => { validationResults={validationResults!} /> - + {/* Only show the out-of-box configuration step if a UI extension is NOT registered */} + {!ExtensionView && ( + + )} - {packagePolicy.policy_id && + {ExtensionView && + packagePolicy.policy_id && packagePolicy.package?.name && - originalPackagePolicy && - ExtensionView && ( + originalPackagePolicy && ( { iconType="save" color="primary" fill + data-test-subj="saveIntegration" > = memo(({ agent, agentPolicy }) => { - const { getHref } = useLink(); - const kibanaVersion = useKibanaVersion(); - return ( - - {[ - { - title: i18n.translate('xpack.fleet.agentDetails.hostNameLabel', { - defaultMessage: 'Host name', - }), - description: - typeof agent.local_metadata.host === 'object' && - typeof agent.local_metadata.host.hostname === 'string' - ? agent.local_metadata.host.hostname - : '-', - }, - { - title: i18n.translate('xpack.fleet.agentDetails.hostIdLabel', { - defaultMessage: 'Agent ID', - }), - description: agent.id, - }, - { - title: i18n.translate('xpack.fleet.agentDetails.statusLabel', { - defaultMessage: 'Status', - }), - description: , - }, - { - title: i18n.translate('xpack.fleet.agentDetails.agentPolicyLabel', { - defaultMessage: 'Agent policy', - }), - description: agentPolicy ? ( - - {agentPolicy.name || agent.policy_id} - - ) : ( - agent.policy_id || '-' - ), - }, - { - title: i18n.translate('xpack.fleet.agentDetails.versionLabel', { - defaultMessage: 'Agent version', - }), - description: - typeof agent.local_metadata.elastic === 'object' && - typeof agent.local_metadata.elastic.agent === 'object' && - typeof agent.local_metadata.elastic.agent.version === 'string' ? ( - - - {agent.local_metadata.elastic.agent.version} - - {isAgentUpgradeable(agent, kibanaVersion) ? ( - - - -   - - - - ) : null} - - ) : ( - '-' - ), - }, - { - title: i18n.translate('xpack.fleet.agentDetails.releaseLabel', { - defaultMessage: 'Agent release', - }), - description: - typeof agent.local_metadata.elastic === 'object' && - typeof agent.local_metadata.elastic.agent === 'object' && - typeof agent.local_metadata.elastic.agent.snapshot === 'boolean' - ? agent.local_metadata.elastic.agent.snapshot === true - ? 'snapshot' - : 'stable' - : '-', - }, - { - title: i18n.translate('xpack.fleet.agentDetails.logLevel', { - defaultMessage: 'Log level', - }), - description: - typeof agent.local_metadata.elastic === 'object' && - typeof agent.local_metadata.elastic.agent === 'object' && - typeof agent.local_metadata.elastic.agent.log_level === 'string' - ? agent.local_metadata.elastic.agent.log_level - : '-', - }, - { - title: i18n.translate('xpack.fleet.agentDetails.platformLabel', { - defaultMessage: 'Platform', - }), - description: - typeof agent.local_metadata.os === 'object' && - typeof agent.local_metadata.os.platform === 'string' - ? agent.local_metadata.os.platform - : '-', - }, - ].map(({ title, description }) => { - return ( - - - {title} - - - {description} - - - ); - })} - - ); -}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx new file mode 100644 index 00000000000000..0cad0b4d487d0e --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx @@ -0,0 +1,159 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { memo, useMemo } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiAccordion, + EuiTitle, + EuiPanel, + EuiButtonIcon, + EuiBasicTable, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import styled from 'styled-components'; +import { Agent, AgentPolicy, PackagePolicy, PackagePolicyInput } from '../../../../../types'; +import { useLink } from '../../../../../hooks'; +import { PackageIcon } from '../../../../../components'; +import { displayInputType, getLogsQueryByInputType } from './input_type_utils'; + +const StyledEuiAccordion = styled(EuiAccordion)` + .ingest-integration-title-button { + padding: ${(props) => props.theme.eui.paddingSizes.m} + ${(props) => props.theme.eui.paddingSizes.m}; + border-bottom: 1px solid ${(props) => props.theme.eui.euiColorLightShade}; + } +`; + +const CollapsablePanel: React.FC<{ id: string; title: React.ReactNode }> = ({ + id, + title, + children, +}) => { + return ( + + + {children} + + + ); +}; + +export const AgentDetailsIntegration: React.FunctionComponent<{ + agent: Agent; + agentPolicy: AgentPolicy; + packagePolicy: PackagePolicy; +}> = memo(({ agent, agentPolicy, packagePolicy }) => { + const { getHref } = useLink(); + + const inputs = useMemo(() => { + return packagePolicy.inputs.filter((input) => input.enabled); + }, [packagePolicy.inputs]); + + const columns = [ + { + field: 'type', + width: '100%', + name: i18n.translate('xpack.fleet.agentDetailsIntegrations.inputTypeLabel', { + defaultMessage: 'Input', + }), + render: (inputType: string) => { + return displayInputType(inputType); + }, + }, + { + name: i18n.translate('xpack.fleet.agentDetailsIntegrations.actionsLabel', { + defaultMessage: 'Actions', + }), + field: 'type', + width: 'auto', + render: (inputType: string) => { + return ( + + ); + }, + }, + ]; + + return ( + +

+ + + {packagePolicy.package ? ( + + ) : ( + + )} + + + + {packagePolicy.name} + + + +

+ + } + > + tableLayout="auto" items={inputs} columns={columns} /> + + ); +}); + +export const AgentDetailsIntegrationsSection: React.FunctionComponent<{ + agent: Agent; + agentPolicy?: AgentPolicy; +}> = memo(({ agent, agentPolicy }) => { + if (!agentPolicy || !agentPolicy.package_policies) { + return null; + } + + return ( + + {(agentPolicy.package_policies as PackagePolicy[]).map((packagePolicy) => { + return ( + + + + ); + })} + + ); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx new file mode 100644 index 00000000000000..a19f6658ef93fd --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx @@ -0,0 +1,185 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { memo } from 'react'; +import { + EuiDescriptionList, + EuiDescriptionListTitle, + EuiDescriptionListDescription, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EuiText } from '@elastic/eui'; +import { EuiIcon } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { Agent, AgentPolicy } from '../../../../../types'; +import { useKibanaVersion, useLink } from '../../../../../hooks'; +import { isAgentUpgradeable } from '../../../../../services'; +import { AgentPolicyPackageBadges } from '../../../components/agent_policy_package_badges'; +import { LinkAndRevision } from '../../../../../components'; + +export const AgentDetailsOverviewSection: React.FunctionComponent<{ + agent: Agent; + agentPolicy?: AgentPolicy; +}> = memo(({ agent, agentPolicy }) => { + const { getHref } = useLink(); + const kibanaVersion = useKibanaVersion(); + return ( + + + {[ + { + title: i18n.translate('xpack.fleet.agentDetails.hostIdLabel', { + defaultMessage: 'Agent ID', + }), + description: agent.id, + }, + { + title: i18n.translate('xpack.fleet.agentDetails.agentPolicyLabel', { + defaultMessage: 'Agent policy', + }), + description: agentPolicy ? ( + + {agentPolicy.name || agentPolicy.id} + + ) : ( + agent.policy_id || '-' + ), + }, + { + title: i18n.translate('xpack.fleet.agentDetails.versionLabel', { + defaultMessage: 'Agent version', + }), + description: + typeof agent.local_metadata?.elastic?.agent?.version === 'string' ? ( + + + {agent.local_metadata.elastic.agent.version} + + {isAgentUpgradeable(agent, kibanaVersion) ? ( + + + +   + + + + ) : null} + + ) : ( + '-' + ), + }, + { + title: i18n.translate('xpack.fleet.agentDetails.enrollmentTokenLabel', { + defaultMessage: 'Enrollment token', + }), + description: '-', // Fixme when we have the enrollment tokenhttps://github.com/elastic/kibana/issues/61269 + }, + { + title: i18n.translate('xpack.fleet.agentDetails.integrationsLabel', { + defaultMessage: 'Integrations', + }), + description: agent.policy_id ? ( + + ) : null, + }, + { + title: i18n.translate('xpack.fleet.agentDetails.hostNameLabel', { + defaultMessage: 'Host name', + }), + description: + typeof agent.local_metadata?.host?.hostname === 'string' + ? agent.local_metadata.host.hostname + : '-', + }, + { + title: i18n.translate('xpack.fleet.agentDetails.logLevel', { + defaultMessage: 'Logging level', + }), + description: + typeof agent.local_metadata?.elastic?.agent?.log_level === 'string' + ? agent.local_metadata.elastic.agent.log_level + : '-', + }, + { + title: i18n.translate('xpack.fleet.agentDetails.releaseLabel', { + defaultMessage: 'Agent release', + }), + description: + typeof agent.local_metadata?.elastic?.agent?.snapshot === 'boolean' + ? agent.local_metadata.elastic.agent.snapshot === true + ? 'snapshot' + : 'stable' + : '-', + }, + { + title: i18n.translate('xpack.fleet.agentDetails.platformLabel', { + defaultMessage: 'Platform', + }), + description: + typeof agent.local_metadata?.os?.platform === 'string' + ? agent.local_metadata.os.platform + : '-', + }, + { + title: i18n.translate('xpack.fleet.agentDetails.monitorLogsLabel', { + defaultMessage: 'Monitor logs', + }), + description: agentPolicy?.monitoring_enabled?.includes('logs') ? ( + + ) : ( + + ), + }, + { + title: i18n.translate('xpack.fleet.agentDetails.monitorMetricsLabel', { + defaultMessage: 'Monitor metrics', + }), + description: agentPolicy?.monitoring_enabled?.includes('metrics') ? ( + + ) : ( + + ), + }, + ].map(({ title, description }) => { + return ( + + + + {title} + + + {description} + + + + ); + })} + + + ); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/index.tsx new file mode 100644 index 00000000000000..0b83fb4cc64e1b --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/index.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { memo } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { Agent, AgentPolicy } from '../../../../../types'; +import { AgentDetailsOverviewSection } from './agent_details_overview'; +import { AgentDetailsIntegrationsSection } from './agent_details_integrations'; + +export const AgentDetailsContent: React.FunctionComponent<{ + agent: Agent; + agentPolicy?: AgentPolicy; +}> = memo(({ agent, agentPolicy }) => { + return ( + <> + + + +

+ +

+
+ + +
+ + +

+ +

+
+ + +
+
+ + ); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/input_type_utils.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/input_type_utils.ts new file mode 100644 index 00000000000000..62b7a294e1750f --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/input_type_utils.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { + STATE_DATASET_FIELD, + AGENT_DATASET_FILEBEAT, + AGENT_DATASET_METRICBEAT, +} from '../agent_logs/constants'; + +export function displayInputType(inputType: string): string { + if (inputType === 'logfile') { + return i18n.translate('xpack.fleet.agentDetailsIntegrations.inputTypeLogText', { + defaultMessage: 'Logs', + }); + } + if (inputType === 'endpoint') { + return i18n.translate('xpack.fleet.agentDetailsIntegrations.inputTypeEndpointText', { + defaultMessage: 'Endpoint', + }); + } + if (inputType.match(/\/metrics$/)) { + return i18n.translate('xpack.fleet.agentDetailsIntegrations.inputTypeMetricsText', { + defaultMessage: 'Metrics', + }); + } + + return inputType; +} + +export function getLogsQueryByInputType(inputType: string) { + if (inputType === 'logfile') { + return `(${STATE_DATASET_FIELD}:!(${AGENT_DATASET_FILEBEAT}))`; + } + if (inputType.match(/\/metrics$/)) { + return `(${STATE_DATASET_FIELD}:!(${AGENT_DATASET_METRICBEAT}))`; + } + + return ''; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/constants.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/constants.tsx index 89fe1a916605de..4ee1618a385849 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/constants.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/constants.tsx @@ -7,6 +7,8 @@ import { AgentLogsState } from './agent_logs'; export const AGENT_LOG_INDEX_PATTERN = 'logs-elastic_agent-*,logs-elastic_agent.*-*'; export const AGENT_DATASET = 'elastic_agent'; +export const AGENT_DATASET_FILEBEAT = 'elastic_agent.filebeat'; +export const AGENT_DATASET_METRICBEAT = 'elastic_agent.metricbeat'; export const AGENT_DATASET_PATTERN = 'elastic_agent.*'; export const AGENT_ID_FIELD = { name: 'elastic_agent.id', @@ -34,6 +36,8 @@ export const DEFAULT_LOGS_STATE: AgentLogsState = { query: '', }; +export const STATE_DATASET_FIELD = 'datasets'; + export const AGENT_LOG_LEVELS = { ERROR: 'error', WARNING: 'warning', diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx index f3714bbb532236..34893dccd93a4a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx @@ -5,7 +5,6 @@ */ import React, { useMemo, useCallback } from 'react'; import { useRouteMatch, Switch, Route, useLocation } from 'react-router-dom'; -import styled from 'styled-components'; import { EuiFlexGroup, EuiFlexItem, @@ -17,7 +16,7 @@ import { EuiDescriptionListDescription, } from '@elastic/eui'; import { Props as EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { EuiIconTip } from '@elastic/eui'; import { Agent, AgentPolicy, AgentDetailsReassignPolicyAction } from '../../../types'; @@ -38,12 +37,6 @@ import { AgentLogs, AgentDetailsActionMenu, AgentDetailsContent } from './compon import { useIntraAppState } from '../../../hooks/use_intra_app_state'; import { isAgentUpgradeable } from '../../../services'; -const Divider = styled.div` - width: 0; - height: 100%; - border-left: ${(props) => props.theme.eui.euiBorderThin}; -`; - export const AgentDetailsPage: React.FunctionComponent = () => { const { params: { agentId, tabId = '' }, @@ -78,6 +71,8 @@ export const AgentDetailsPage: React.FunctionComponent = () => { } }, [routeState, navigateToApp]); + const host = agentData?.item?.local_metadata?.host; + const headerLeftContent = useMemo( () => ( @@ -99,9 +94,8 @@ export const AgentDetailsPage: React.FunctionComponent = () => {

{isLoading && isInitialRequest ? ( - ) : typeof agentData?.item?.local_metadata?.host === 'object' && - typeof agentData?.item?.local_metadata?.host?.hostname === 'string' ? ( - agentData.item.local_metadata.host.hostname + ) : typeof host === 'object' && typeof host?.hostname === 'string' ? ( + host.hostname ) : ( { ), - [agentData?.item?.local_metadata?.host, agentId, getHref, isInitialRequest, isLoading] + [host, agentId, getHref, isInitialRequest, isLoading] ); const headerRightContent = useMemo( () => agentData && agentData.item ? ( - + {[ { label: i18n.translate('xpack.fleet.agentDetails.statusLabel', { @@ -130,7 +124,16 @@ export const AgentDetailsPage: React.FunctionComponent = () => { }), content: , }, - { isDivider: true }, + { + label: i18n.translate('xpack.fleet.agentDetails.lastActivityLabel', { + defaultMessage: 'Last activity', + }), + content: agentData.item.last_checkin ? ( + + ) : ( + '-' + ), + }, { label: i18n.translate('xpack.fleet.agentDetails.policyLabel', { defaultMessage: 'Policy', @@ -148,7 +151,6 @@ export const AgentDetailsPage: React.FunctionComponent = () => { agentData.item.policy_id || '-' ), }, - { isDivider: true }, { label: i18n.translate('xpack.fleet.agentDetails.agentVersionLabel', { defaultMessage: 'Agent version', @@ -187,7 +189,6 @@ export const AgentDetailsPage: React.FunctionComponent = () => { '-' ), }, - { isDivider: true }, { content: ( { }, ].map((item, index) => ( - {item.isDivider ?? false ? ( - - ) : item.label ? ( - + {item.label ? ( + {item.label} {item.content} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx index 45017ac8532da7..40d91f13db6590 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_health.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; -import { EuiHealth, EuiToolTip } from '@elastic/eui'; +import { EuiBadge, EuiToolTip } from '@elastic/eui'; import { Agent } from '../../../types'; interface Props { @@ -13,79 +13,52 @@ interface Props { } const Status = { - Online: ( - - - + Healthy: ( + + + ), Offline: ( - + - + ), Inactive: ( - - - - ), - Warning: ( - - - - ), - Error: ( - - - - ), - Degraded: ( - - - - ), - Enrolling: ( - - - + + + ), - Unenrolling: ( - + Unhealthy: ( + - + ), - Upgrading: ( - + Updating: ( + - + ), }; function getStatusComponent(agent: Agent): React.ReactElement { switch (agent.status) { + case 'warning': case 'error': - return Status.Error; case 'degraded': - return Status.Degraded; + return Status.Unhealthy; case 'inactive': return Status.Inactive; case 'offline': return Status.Offline; - case 'warning': - return Status.Warning; case 'unenrolling': - return Status.Unenrolling; case 'enrolling': - return Status.Enrolling; case 'updating': - return Status.Upgrading; + return Status.Updating; default: - return Status.Online; + return Status.Healthy; } } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx index 08835cc872b82a..ff8e4868b1fdf8 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_policy_package_badges.tsx @@ -3,53 +3,74 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiSpacer, EuiText, EuiFlexGroup, EuiFlexItem, EuiBadge } from '@elastic/eui'; -import { PackagePolicy } from '../../../types'; +import { PackagePolicy, PackagePolicyPackage } from '../../../types'; import { useGetOneAgentPolicy } from '../../../hooks'; import { PackageIcon } from '../../../components/package_icon'; interface Props { agentPolicyId: string; + hideTitle?: boolean; } -export const AgentPolicyPackageBadges: React.FunctionComponent = ({ agentPolicyId }) => { +export const AgentPolicyPackageBadges: React.FunctionComponent = ({ + agentPolicyId, + hideTitle, +}) => { const agentPolicyRequest = useGetOneAgentPolicy(agentPolicyId); const agentPolicy = agentPolicyRequest.data ? agentPolicyRequest.data.item : null; - if (!agentPolicy) { + const packages = useMemo(() => { + if (!agentPolicy) { + return; + } + + const uniquePackages = new Map(); + + (agentPolicy.package_policies as PackagePolicy[]).forEach(({ package: pkg }) => { + if (!pkg) { + return; + } + + if (!uniquePackages.has(pkg.name) || uniquePackages.get(pkg.name)!.version < pkg.version) { + uniquePackages.set(pkg.name, pkg); + } + }); + + return [...uniquePackages.values()]; + }, [agentPolicy]); + + if (!agentPolicy || !packages) { return null; } + return ( <> - - {agentPolicy.package_policies.length}, - }} - /> - - - {(agentPolicy.package_policies as PackagePolicy[]).map((packagePolicy, idx) => { - if (!packagePolicy.package) { - return null; - } + {!hideTitle && ( + <> + + {packages.length}, + }} + /> + + + + )} + {packages.map((pkg, idx) => { return ( - + - {packagePolicy.package.title} + {pkg.title} ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx index ed607e361bd6e5..d9aeba23726729 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx @@ -46,6 +46,9 @@ function useCreateApiKeyForm( policy_id: policyIdInput.value, }), }); + if (res.error) { + throw res.error; + } policyIdInput.clear(); apiKeyNameInput.clear(); setIsLoading(false); diff --git a/x-pack/plugins/fleet/public/applications/fleet/types/index.ts b/x-pack/plugins/fleet/public/applications/fleet/types/index.ts index ded1447954aff7..dd80c1ad77b85d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/types/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/types/index.ts @@ -22,6 +22,7 @@ export { NewPackagePolicyInputStream, PackagePolicyConfigRecord, PackagePolicyConfigRecordEntry, + PackagePolicyPackage, Output, DataStream, // API schema - misc setup, status diff --git a/x-pack/plugins/fleet/public/applications/fleet/types/ui_extensions.ts b/x-pack/plugins/fleet/public/applications/fleet/types/ui_extensions.ts index a8a961ca949b63..d35e5f4744449c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/types/ui_extensions.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/types/ui_extensions.ts @@ -28,13 +28,17 @@ export interface PackagePolicyEditExtensionComponentProps { newPolicy: NewPackagePolicy; /** * A callback that should be executed anytime a change to the Integration Policy needs to - * be reported back to the Fleet Policy Edit page + * be reported back to the Fleet Policy Edit page. + * + * **NOTE:** + * this callback will be recreated everytime the policy data changes, thus logic around its + * invocation should take that into consideration in order to avoid an endless loop. */ onChange: (opts: { /** is current form state is valid */ isValid: boolean; /** The updated Integration Policy to be merged back and included in the API call */ - updatedPolicy: NewPackagePolicy; + updatedPolicy: Partial; }) => void; } diff --git a/x-pack/plugins/fleet/server/errors/index.ts b/x-pack/plugins/fleet/server/errors/index.ts index e3ca6a9b48dcfe..d6fa79a2baeba0 100644 --- a/x-pack/plugins/fleet/server/errors/index.ts +++ b/x-pack/plugins/fleet/server/errors/index.ts @@ -5,7 +5,11 @@ */ /* eslint-disable max-classes-per-file */ -export { defaultIngestErrorHandler, ingestErrorToResponseOptions } from './handlers'; +export { + defaultIngestErrorHandler, + ingestErrorToResponseOptions, + isLegacyESClientError, +} from './handlers'; export class IngestManagerError extends Error { constructor(message?: string) { @@ -24,3 +28,4 @@ export class PackageUnsupportedMediaTypeError extends IngestManagerError {} export class PackageInvalidArchiveError extends IngestManagerError {} export class PackageCacheError extends IngestManagerError {} export class PackageOperationNotSupportedError extends IngestManagerError {} +export class FleetAdminUserInvalidError extends IngestManagerError {} diff --git a/x-pack/plugins/fleet/server/services/api_keys/security.ts b/x-pack/plugins/fleet/server/services/api_keys/security.ts index dfd53d55fbbf5d..5fdf8626a9fb22 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/security.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/security.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest, FakeRequest, SavedObjectsClientContract } from 'src/core/server'; +import type { Request } from '@hapi/hapi'; +import { KibanaRequest, SavedObjectsClientContract } from '../../../../../../src/core/server'; +import { FleetAdminUserInvalidError, isLegacyESClientError } from '../../errors'; import { CallESAsCurrentUser } from '../../types'; import { appContextService } from '../app_context'; import { outputService } from '../output'; @@ -18,22 +20,38 @@ export async function createAPIKey( if (!adminUser) { throw new Error('No admin user configured'); } - const request: FakeRequest = { + const request = KibanaRequest.from(({ + path: '/', + route: { settings: {} }, + url: { href: '/' }, + raw: { req: { url: '/' } }, headers: { authorization: `Basic ${Buffer.from(`${adminUser.username}:${adminUser.password}`).toString( 'base64' )}`, }, - }; + } as unknown) as Request); const security = appContextService.getSecurity(); if (!security) { throw new Error('Missing security plugin'); } - return security.authc.createAPIKey(request as KibanaRequest, { - name, - role_descriptors: roleDescriptors, - }); + try { + const key = await security.authc.createAPIKey(request, { + name, + role_descriptors: roleDescriptors, + }); + + return key; + } catch (err) { + if (isLegacyESClientError(err) && err.statusCode === 401) { + // Clear Fleet admin user cache as the user is probably not valid anymore + outputService.invalidateCache(); + throw new FleetAdminUserInvalidError(`Fleet Admin user is invalid: ${err.message}`); + } + + throw err; + } } export async function authenticate(callCluster: CallESAsCurrentUser) { try { @@ -51,20 +69,36 @@ export async function invalidateAPIKey(soClient: SavedObjectsClientContract, id: if (!adminUser) { throw new Error('No admin user configured'); } - const request: FakeRequest = { + const request = KibanaRequest.from(({ + path: '/', + route: { settings: {} }, + url: { href: '/' }, + raw: { req: { url: '/' } }, headers: { authorization: `Basic ${Buffer.from(`${adminUser.username}:${adminUser.password}`).toString( 'base64' )}`, }, - }; + } as unknown) as Request); const security = appContextService.getSecurity(); if (!security) { throw new Error('Missing security plugin'); } - return security.authc.invalidateAPIKey(request as KibanaRequest, { - id, - }); + try { + const res = await security.authc.invalidateAPIKey(request, { + id, + }); + + return res; + } catch (err) { + if (isLegacyESClientError(err) && err.statusCode === 401) { + // Clear Fleet admin user cache as the user is probably not valid anymore + outputService.invalidateCache(); + throw new FleetAdminUserInvalidError(`Fleet Admin user is invalid: ${err.message}`); + } + + throw err; + } } diff --git a/x-pack/plugins/fleet/server/services/epm/archive/validation.ts b/x-pack/plugins/fleet/server/services/epm/archive/validation.ts index dc7a91e08799cb..b93a9119cd4df8 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/validation.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/validation.ts @@ -5,7 +5,7 @@ */ import yaml from 'js-yaml'; -import { uniq } from 'lodash'; +import { pick, uniq } from 'lodash'; import { ArchivePackage, RegistryPolicyTemplate, @@ -21,6 +21,42 @@ import { pkgToPkgKey } from '../registry'; const MANIFESTS: Record = {}; const MANIFEST_NAME = 'manifest.yml'; +// not sure these are 100% correct but they do the job here +// keeping them local until others need them +type OptionalPropertyOf = Exclude< + { + [K in keyof T]: T extends Record ? never : K; + }[keyof T], + undefined +>; +type RequiredPropertyOf = Exclude>; + +type RequiredPackageProp = RequiredPropertyOf; +type OptionalPackageProp = OptionalPropertyOf; +// pro: guarantee only supplying known values. these keys must be in ArchivePackage. no typos or new values +// pro: any values added to these lists will be passed through by default +// pro & con: values do need to be shadowed / repeated from ArchivePackage, but perhaps we want to limit values +const requiredArchivePackageProps: readonly RequiredPackageProp[] = [ + 'name', + 'version', + 'description', + 'type', + 'categories', + 'format_version', +] as const; + +const optionalArchivePackageProps: readonly OptionalPackageProp[] = [ + 'title', + 'release', + 'readme', + 'screenshots', + 'icons', + 'assets', + 'internal', + 'data_streams', + 'policy_templates', +] as const; + // TODO: everything below performs verification of manifest.yml files, and hence duplicates functionality already implemented in the // package registry. At some point this should probably be replaced (or enhanced) with verification based on // https://github.com/elastic/package-spec/ @@ -58,43 +94,43 @@ function parseAndVerifyArchive(paths: string[]): ArchivePackage { } // ... which must be valid YAML - let manifest; + let manifest: ArchivePackage; try { manifest = yaml.load(manifestBuffer.toString()); } catch (error) { throw new PackageInvalidArchiveError(`Could not parse top-level package manifest: ${error}.`); } - // Package name and version from the manifest must match those from the toplevel directory - const pkgKey = pkgToPkgKey({ name: manifest.name, version: manifest.version }); - if (toplevelDir !== pkgKey) { + // must have mandatory fields + const reqGiven = pick(manifest, requiredArchivePackageProps); + const requiredKeysMatch = + Object.keys(reqGiven).toString() === requiredArchivePackageProps.toString(); + if (!requiredKeysMatch) { + const list = requiredArchivePackageProps.join(', '); throw new PackageInvalidArchiveError( - `Name ${manifest.name} and version ${manifest.version} do not match top-level directory ${toplevelDir}` + `Invalid top-level package manifest: one or more fields missing of ${list}` ); } - const { name, version, description, type, categories, format_version: formatVersion } = manifest; - // check for mandatory fields - if (!(name && version && description && type && categories && formatVersion)) { + // at least have all required properties + // get optional values and combine into one object for the remaining operations + const optGiven = pick(manifest, optionalArchivePackageProps); + const parsed: ArchivePackage = { ...reqGiven, ...optGiven }; + + // Package name and version from the manifest must match those from the toplevel directory + const pkgKey = pkgToPkgKey({ name: parsed.name, version: parsed.version }); + if (toplevelDir !== pkgKey) { throw new PackageInvalidArchiveError( - 'Invalid top-level package manifest: one or more fields missing of name, version, description, type, categories, format_version' + `Name ${parsed.name} and version ${parsed.version} do not match top-level directory ${toplevelDir}` ); } - const dataStreams = parseAndVerifyDataStreams(paths, name, version); - const policyTemplates = parseAndVerifyPolicyTemplates(manifest); + parsed.data_streams = parseAndVerifyDataStreams(paths, parsed.name, parsed.version); + parsed.policy_templates = parseAndVerifyPolicyTemplates(manifest); - return { - name, - version, - description, - type, - categories, - format_version: formatVersion, - data_streams: dataStreams, - policy_templates: policyTemplates, - }; + return parsed; } + function parseAndVerifyDataStreams( paths: string[], pkgName: string, diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts index 05f552b558205b..1af7ce149dfc09 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts @@ -5,7 +5,7 @@ */ import { SavedObject, SavedObjectsClientContract } from 'src/core/server'; -import { InstallablePackage, InstallSource } from '../../../../common'; +import { InstallablePackage, InstallSource, MAX_TIME_COMPLETE_INSTALL } from '../../../../common'; import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants'; import { AssetReference, @@ -46,15 +46,29 @@ export async function _installPackage({ installSource: InstallSource; }): Promise { const { name: pkgName, version: pkgVersion } = packageInfo; - // add the package installation to the saved object. - // if some installation already exists, just update install info + // if some installation already exists if (installedPkg) { - await savedObjectsClient.update(PACKAGES_SAVED_OBJECT_TYPE, pkgName, { - install_version: pkgVersion, - install_status: 'installing', - install_started_at: new Date().toISOString(), - install_source: installSource, - }); + // if the installation is currently running, don't try to install + // instead, only return already installed assets + if ( + installedPkg.attributes.install_status === 'installing' && + Date.now() - Date.parse(installedPkg.attributes.install_started_at) < + MAX_TIME_COMPLETE_INSTALL + ) { + let assets: AssetReference[] = []; + assets = assets.concat(installedPkg.attributes.installed_es); + assets = assets.concat(installedPkg.attributes.installed_kibana); + return assets; + } else { + // if no installation is running, or the installation has been running longer than MAX_TIME_COMPLETE_INSTALL + // (it might be stuck) update the saved object and proceed + await savedObjectsClient.update(PACKAGES_SAVED_OBJECT_TYPE, pkgName, { + install_version: pkgVersion, + install_status: 'installing', + install_started_at: new Date().toISOString(), + install_source: installSource, + }); + } } else { await createInstallation({ savedObjectsClient, diff --git a/x-pack/plugins/global_search/jest.config.js b/x-pack/plugins/global_search/jest.config.js new file mode 100644 index 00000000000000..2ad904d8c57c44 --- /dev/null +++ b/x-pack/plugins/global_search/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/global_search'], +}; diff --git a/x-pack/plugins/global_search_bar/jest.config.js b/x-pack/plugins/global_search_bar/jest.config.js new file mode 100644 index 00000000000000..5b03d4a3f90d7e --- /dev/null +++ b/x-pack/plugins/global_search_bar/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/global_search_bar'], +}; diff --git a/x-pack/plugins/global_search_bar/public/components/search_bar.tsx b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx index 3746e636066a93..ecd1c92bfcee68 100644 --- a/x-pack/plugins/global_search_bar/public/components/search_bar.tsx +++ b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx @@ -83,7 +83,7 @@ const resultToOption = (result: GlobalSearchResult): EuiSelectableTemplateSitewi }; if (type === 'application') { - option.meta = [{ text: meta?.categoryLabel as string }]; + option.meta = [{ text: (meta?.categoryLabel as string) ?? '' }]; } else { option.meta = [{ text: cleanMeta(type) }]; } diff --git a/x-pack/plugins/global_search_providers/jest.config.js b/x-pack/plugins/global_search_providers/jest.config.js new file mode 100644 index 00000000000000..3bd70206c936c7 --- /dev/null +++ b/x-pack/plugins/global_search_providers/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/global_search_providers'], +}; 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 2831550da00d97..7beed42de4c4f4 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 @@ -28,6 +28,7 @@ const createApp = (props: Partial = {}): PublicAppInfo => ({ status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.visible, chromeless: false, + searchDeepLinks: [], ...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 5ef15a8cf2ea47..33fd358f61acac 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 @@ -10,7 +10,7 @@ import { PublicAppInfo, DEFAULT_APP_CATEGORIES, } from 'src/core/public'; -import { appToResult, getAppResults, scoreApp } from './get_app_results'; +import { AppLink, appToResult, getAppResults, scoreApp } from './get_app_results'; const createApp = (props: Partial = {}): PublicAppInfo => ({ id: 'app1', @@ -19,9 +19,17 @@ const createApp = (props: Partial = {}): PublicAppInfo => ({ status: AppStatus.accessible, navLinkStatus: AppNavLinkStatus.visible, chromeless: false, + searchDeepLinks: [], ...props, }); +const createAppLink = (props: Partial = {}): AppLink => ({ + id: props.id ?? 'app1', + path: props.appRoute ?? '/app/app1', + subLinkTitles: [], + app: createApp(props), +}); + describe('getAppResults', () => { it('retrieves the matching results', () => { const apps = [ @@ -34,43 +42,82 @@ describe('getAppResults', () => { expect(results.length).toBe(1); expect(results[0]).toEqual(expect.objectContaining({ id: 'dashboard', score: 100 })); }); + + it('creates multiple links for apps with searchDeepLinks', () => { + const apps = [ + createApp({ + searchDeepLinks: [ + { id: 'sub1', title: 'Sub1', path: '/sub1', searchDeepLinks: [] }, + { + id: 'sub2', + title: 'Sub2', + path: '/sub2', + searchDeepLinks: [ + { id: 'sub2sub1', title: 'Sub2Sub1', path: '/sub2/sub1', searchDeepLinks: [] }, + ], + }, + ], + }), + ]; + + const results = getAppResults('App 1', apps); + + expect(results.length).toBe(4); + expect(results.map(({ title }) => title)).toEqual([ + 'App 1', + 'App 1 / Sub1', + 'App 1 / Sub2', + 'App 1 / Sub2 / Sub2Sub1', + ]); + }); + + it('only includes searchDeepLinks when search term is non-empty', () => { + const apps = [ + createApp({ + searchDeepLinks: [{ id: 'sub1', title: 'Sub1', path: '/sub1', searchDeepLinks: [] }], + }), + ]; + + expect(getAppResults('', apps).length).toBe(1); + expect(getAppResults('App 1', apps).length).toBe(2); + }); }); describe('scoreApp', () => { describe('when the term is included in the title', () => { it('returns 100 if the app title is an exact match', () => { - expect(scoreApp('dashboard', createApp({ title: 'dashboard' }))).toBe(100); - expect(scoreApp('dashboard', createApp({ title: 'DASHBOARD' }))).toBe(100); - expect(scoreApp('DASHBOARD', createApp({ title: 'DASHBOARD' }))).toBe(100); - expect(scoreApp('dashBOARD', createApp({ title: 'DASHboard' }))).toBe(100); + expect(scoreApp('dashboard', createAppLink({ title: 'dashboard' }))).toBe(100); + expect(scoreApp('dashboard', createAppLink({ title: 'DASHBOARD' }))).toBe(100); + expect(scoreApp('DASHBOARD', createAppLink({ title: 'DASHBOARD' }))).toBe(100); + expect(scoreApp('dashBOARD', createAppLink({ title: 'DASHboard' }))).toBe(100); }); it('returns 90 if the app title starts with the term', () => { - expect(scoreApp('dash', createApp({ title: 'dashboard' }))).toBe(90); - expect(scoreApp('DASH', createApp({ title: 'dashboard' }))).toBe(90); + expect(scoreApp('dash', createAppLink({ title: 'dashboard' }))).toBe(90); + expect(scoreApp('DASH', createAppLink({ title: 'dashboard' }))).toBe(90); }); it('returns 75 if the term in included in the app title', () => { - expect(scoreApp('board', createApp({ title: 'dashboard' }))).toBe(75); - expect(scoreApp('shboa', createApp({ title: 'dashboard' }))).toBe(75); + expect(scoreApp('board', createAppLink({ title: 'dashboard' }))).toBe(75); + expect(scoreApp('shboa', createAppLink({ title: 'dashboard' }))).toBe(75); }); }); describe('when the term is not included in the title', () => { it('returns the levenshtein ratio if superior or equal to 60', () => { - expect(scoreApp('0123456789', createApp({ title: '012345' }))).toBe(60); - expect(scoreApp('--1234567-', createApp({ title: '123456789' }))).toBe(60); + expect(scoreApp('0123456789', createAppLink({ title: '012345' }))).toBe(60); + expect(scoreApp('--1234567-', createAppLink({ title: '123456789' }))).toBe(60); }); it('returns 0 if the levenshtein ratio is inferior to 60', () => { - expect(scoreApp('0123456789', createApp({ title: '12345' }))).toBe(0); - expect(scoreApp('1-2-3-4-5', createApp({ title: '123456789' }))).toBe(0); + expect(scoreApp('0123456789', createAppLink({ title: '12345' }))).toBe(0); + expect(scoreApp('1-2-3-4-5', createAppLink({ title: '123456789' }))).toBe(0); }); }); }); describe('appToResult', () => { it('converts an app to a result', () => { - const app = createApp({ + const app = createAppLink({ id: 'foo', title: 'Foo', euiIconType: 'fooIcon', @@ -92,7 +139,7 @@ describe('appToResult', () => { }); it('converts an app without category to a result', () => { - const app = createApp({ + const app = createAppLink({ id: 'foo', title: 'Foo', euiIconType: 'fooIcon', @@ -111,4 +158,28 @@ describe('appToResult', () => { score: 42, }); }); + + it('includes the app name in sub links', () => { + const app = createApp(); + const appLink: AppLink = { + id: 'app1-sub', + app, + path: '/sub1', + subLinkTitles: ['Sub1'], + }; + + expect(appToResult(appLink, 42).title).toEqual('App 1 / Sub1'); + }); + + it('does not include the app name in sub links for Stack Management', () => { + const app = createApp({ id: 'management' }); + const appLink: AppLink = { + id: 'management-sub', + app, + path: '/sub1', + subLinkTitles: ['Sub1'], + }; + + expect(appToResult(appLink, 42).title).toEqual('Sub1'); + }); }); 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 c4e1a9532d144c..01e6e87f30c94d 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 @@ -5,22 +5,41 @@ */ import levenshtein from 'js-levenshtein'; -import { PublicAppInfo } from 'src/core/public'; +import { PublicAppInfo, PublicAppSearchDeepLinkInfo } from 'src/core/public'; import { GlobalSearchProviderResult } from '../../../global_search/public'; +/** Type used internally to represent an application unrolled into its separate searchDeepLinks */ +export interface AppLink { + id: string; + app: PublicAppInfo; + subLinkTitles: string[]; + path: string; +} + export const getAppResults = ( term: string, apps: PublicAppInfo[] ): GlobalSearchProviderResult[] => { - return apps - .map((app) => ({ app, score: scoreApp(term, app) })) - .filter(({ score }) => score > 0) - .map(({ app, score }) => appToResult(app, score)); + return ( + apps + // Unroll all searchDeepLinks, only if there is a search term + .flatMap((app) => + term.length > 0 + ? flattenDeepLinks(app) + : [{ id: app.id, app, path: app.appRoute, subLinkTitles: [] }] + ) + .map((appLink) => ({ + appLink, + score: scoreApp(term, appLink), + })) + .filter(({ score }) => score > 0) + .map(({ appLink, score }) => appToResult(appLink, score)) + ); }; -export const scoreApp = (term: string, { title }: PublicAppInfo): number => { +export const scoreApp = (term: string, appLink: AppLink): number => { term = term.toLowerCase(); - title = title.toLowerCase(); + const title = [appLink.app.title, ...appLink.subLinkTitles].join(' ').toLowerCase(); // shortcuts to avoid calculating the distance when there is an exact match somewhere. if (title === term) { @@ -43,17 +62,61 @@ export const scoreApp = (term: string, { title }: PublicAppInfo): number => { return 0; }; -export const appToResult = (app: PublicAppInfo, score: number): GlobalSearchProviderResult => { +export const appToResult = (appLink: AppLink, score: number): GlobalSearchProviderResult => { + const titleParts = + // Stack Management app should not include the app title in the concatenated link label + appLink.app.id === 'management' && appLink.subLinkTitles.length > 0 + ? appLink.subLinkTitles + : [appLink.app.title, ...appLink.subLinkTitles]; + return { - id: app.id, - title: app.title, + id: appLink.id, + // Concatenate title using slashes + title: titleParts.join(' / '), type: 'application', - icon: app.euiIconType, - url: app.appRoute, + icon: appLink.app.euiIconType, + url: appLink.path, meta: { - categoryId: app.category?.id ?? null, - categoryLabel: app.category?.label ?? null, + categoryId: appLink.app.category?.id ?? null, + categoryLabel: appLink.app.category?.label ?? null, }, score, }; }; + +const flattenDeepLinks = ( + app: PublicAppInfo, + deepLink?: PublicAppSearchDeepLinkInfo +): AppLink[] => { + if (!deepLink) { + return [ + { + id: app.id, + app, + path: app.appRoute, + subLinkTitles: [], + }, + ...app.searchDeepLinks.flatMap((appDeepLink) => flattenDeepLinks(app, appDeepLink)), + ]; + } + + return [ + ...(deepLink.path + ? [ + { + id: `${app.id}-${deepLink.id}`, + app, + subLinkTitles: [deepLink.title], + path: `${app.appRoute}${deepLink.path}`, + }, + ] + : []), + ...deepLink.searchDeepLinks + .flatMap((deepDeepLink) => flattenDeepLinks(app, deepDeepLink)) + .map((deepAppLink) => ({ + ...deepAppLink, + // shift current sublink title into array of sub-sublink titles + subLinkTitles: [deepLink.title, ...deepAppLink.subLinkTitles], + })), + ]; +}; diff --git a/x-pack/plugins/graph/jest.config.js b/x-pack/plugins/graph/jest.config.js new file mode 100644 index 00000000000000..da729b0fae223a --- /dev/null +++ b/x-pack/plugins/graph/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/graph'], +}; diff --git a/x-pack/plugins/graph/public/angular/graph_client_workspace.js b/x-pack/plugins/graph/public/angular/graph_client_workspace.js index 5cc06bad4c4238..785e221b798650 100644 --- a/x-pack/plugins/graph/public/angular/graph_client_workspace.js +++ b/x-pack/plugins/graph/public/angular/graph_client_workspace.js @@ -1187,7 +1187,7 @@ function GraphWorkspace(options) { // Search for connections between the selected nodes. searcher(self.options.indexName, searchReq, function (data) { - const numDocsMatched = data.hits.total; + const numDocsMatched = data.hits.total.value; const buckets = data.aggregations.matrix.buckets; const vertices = nodesForLinking.map(function (existingNode) { return { diff --git a/x-pack/plugins/graph/public/application.ts b/x-pack/plugins/graph/public/application.ts index 90e87ff4ec85e3..c7cf2554c5c606 100644 --- a/x-pack/plugins/graph/public/application.ts +++ b/x-pack/plugins/graph/public/application.ts @@ -32,7 +32,7 @@ import { // @ts-ignore import { initGraphApp } from './app'; import { Plugin as DataPlugin, IndexPatternsContract } from '../../../../src/plugins/data/public'; -import { LicensingPluginSetup } from '../../licensing/public'; +import { LicensingPluginStart } from '../../licensing/public'; import { checkLicense } from '../common/check_license'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../src/plugins/navigation/public'; import { Storage } from '../../../../src/plugins/kibana_utils/public'; @@ -60,7 +60,7 @@ export interface GraphDependencies { capabilities: Record>; coreStart: AppMountContext['core']; navigation: NavigationStart; - licensing: LicensingPluginSetup; + licensing: LicensingPluginStart; chrome: ChromeStart; toastNotifications: ToastsStart; indexPatterns: IndexPatternsContract; diff --git a/x-pack/plugins/graph/public/plugin.ts b/x-pack/plugins/graph/public/plugin.ts index eb0089feaa072c..b1efdeacacc3e1 100644 --- a/x-pack/plugins/graph/public/plugin.ts +++ b/x-pack/plugins/graph/public/plugin.ts @@ -18,7 +18,7 @@ import { NavigationPublicPluginStart as NavigationStart } from '../../../../src/ import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { toggleNavLink } from './services/toggle_nav_link'; -import { LicensingPluginSetup } from '../../licensing/public'; +import { LicensingPluginStart } from '../../licensing/public'; import { checkLicense } from '../common/check_license'; import { FeatureCatalogueCategory, @@ -30,12 +30,12 @@ import { ConfigSchema } from '../config'; import { SavedObjectsStart } from '../../../../src/plugins/saved_objects/public'; export interface GraphPluginSetupDependencies { - licensing: LicensingPluginSetup; home?: HomePublicPluginSetup; } export interface GraphPluginStartDependencies { navigation: NavigationStart; + licensing: LicensingPluginStart; data: DataPublicPluginStart; savedObjects: SavedObjectsStart; kibanaLegacy: KibanaLegacyStart; @@ -44,16 +44,9 @@ export interface GraphPluginStartDependencies { export class GraphPlugin implements Plugin { - private licensing: LicensingPluginSetup | null = null; - constructor(private initializerContext: PluginInitializerContext) {} - setup( - core: CoreSetup, - { licensing, home }: GraphPluginSetupDependencies - ) { - this.licensing = licensing; - + setup(core: CoreSetup, { home }: GraphPluginSetupDependencies) { if (home) { home.featureCatalogue.register({ id: 'graph', @@ -92,7 +85,7 @@ export class GraphPlugin return renderApp({ ...params, pluginInitializerContext: this.initializerContext, - licensing, + licensing: pluginsStart.licensing, core: coreStart, navigation: pluginsStart.navigation, data: pluginsStart.data, @@ -115,11 +108,8 @@ export class GraphPlugin }); } - start(core: CoreStart, { home }: GraphPluginStartDependencies) { - if (this.licensing === null) { - throw new Error('Start called before setup'); - } - this.licensing.license$.subscribe((license) => { + start(core: CoreStart, { home, licensing }: GraphPluginStartDependencies) { + licensing.license$.subscribe((license) => { const licenseInformation = checkLicense(license); toggleNavLink(licenseInformation, core.chrome.navLinks); if (home && !licenseInformation.enableAppLink) { diff --git a/x-pack/plugins/graph/server/routes/explore.ts b/x-pack/plugins/graph/server/routes/explore.ts index a1e24695413c96..762186d69d8191 100644 --- a/x-pack/plugins/graph/server/routes/explore.ts +++ b/x-pack/plugins/graph/server/routes/explore.ts @@ -4,12 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ +import { errors } from '@elastic/elasticsearch'; import { IRouter } from 'kibana/server'; import { schema } from '@kbn/config-schema'; import Boom from '@hapi/boom'; -import { get } from 'lodash'; import { LicenseState, verifyApiAccess } from '../lib/license_state'; +interface ErrorResponse { + error?: { + root_cause?: Array<{ type: string; reason: string }>; + }; +} + export function registerExploreRoute({ router, licenseState, @@ -31,11 +37,7 @@ export function registerExploreRoute({ async ( { core: { - elasticsearch: { - legacy: { - client: { callAsCurrentUser: callCluster }, - }, - }, + elasticsearch: { client: esClient }, }, }, request, @@ -46,32 +48,31 @@ export function registerExploreRoute({ try { return response.ok({ body: { - resp: await callCluster('transport.request', { - path: '/' + encodeURIComponent(request.body.index) + '/_graph/explore', - body: request.body.query, - method: 'POST', - query: {}, - }), + resp: ( + await esClient.asCurrentUser.transport.request({ + path: '/' + encodeURIComponent(request.body.index) + '/_graph/explore', + body: request.body.query, + method: 'POST', + }) + ).body, }, }); } catch (error) { - // Extract known reasons for bad choice of field - const relevantCause = get( - error, - 'body.error.root_cause', - [] as Array<{ type: string; reason: string }> - ).find((cause: { type: string; reason: string }) => { - return ( - cause.reason.includes('Fielddata is disabled on text fields') || - cause.reason.includes('No support for examining floating point') || - cause.reason.includes('Sample diversifying key must be a single valued-field') || - cause.reason.includes('Failed to parse query') || - cause.type === 'parsing_exception' - ); - }); + if (error instanceof errors.ResponseError) { + const errorBody: ErrorResponse = error.body; + const relevantCause = (errorBody.error?.root_cause ?? []).find((cause) => { + return ( + cause.reason.includes('Fielddata is disabled on text fields') || + cause.reason.includes('No support for examining floating point') || + cause.reason.includes('Sample diversifying key must be a single valued-field') || + cause.reason.includes('Failed to parse query') || + cause.type === 'parsing_exception' + ); + }); - if (relevantCause) { - throw Boom.badRequest(relevantCause.reason); + if (relevantCause) { + throw Boom.badRequest(relevantCause.reason); + } } return response.internalError({ diff --git a/x-pack/plugins/graph/server/routes/search.ts b/x-pack/plugins/graph/server/routes/search.ts index e1d430eeb311a5..7d05f9ab6888c2 100644 --- a/x-pack/plugins/graph/server/routes/search.ts +++ b/x-pack/plugins/graph/server/routes/search.ts @@ -31,11 +31,7 @@ export function registerSearchRoute({ { core: { uiSettings: { client: uiSettings }, - elasticsearch: { - legacy: { - client: { callAsCurrentUser: callCluster }, - }, - }, + elasticsearch: { client: esClient }, }, }, request, @@ -47,12 +43,14 @@ export function registerSearchRoute({ try { return response.ok({ body: { - resp: await callCluster('search', { - index: request.body.index, - body: request.body.body, - rest_total_hits_as_int: true, - ignore_throttled: !includeFrozen, - }), + resp: ( + await esClient.asCurrentUser.search({ + index: request.body.index, + body: request.body.body, + track_total_hits: true, + ignore_throttled: !includeFrozen, + }) + ).body, }, }); } catch (error) { diff --git a/x-pack/plugins/index_lifecycle_management/jest.config.js b/x-pack/plugins/index_lifecycle_management/jest.config.js new file mode 100644 index 00000000000000..906f4ff3960ae0 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/index_lifecycle_management'], +}; diff --git a/x-pack/plugins/index_management/jest.config.js b/x-pack/plugins/index_management/jest.config.js new file mode 100644 index 00000000000000..d389a91675210c --- /dev/null +++ b/x-pack/plugins/index_management/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/index_management'], +}; diff --git a/x-pack/plugins/infra/jest.config.js b/x-pack/plugins/infra/jest.config.js new file mode 100644 index 00000000000000..507f94a2d6685c --- /dev/null +++ b/x-pack/plugins/infra/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/infra'], +}; diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index 14785f64cffac0..1941ec6326ddb6 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -9,7 +9,7 @@ import moment from 'moment'; import { getCustomMetricLabel } from '../../../../common/formatters/get_custom_metric_label'; import { toMetricOpt } from '../../../../common/snapshot_metric_i18n'; import { AlertStates, InventoryMetricConditions } from './types'; -import { ResolvedActionGroup } from '../../../../../alerts/common'; +import { RecoveredActionGroup } from '../../../../../alerts/common'; import { AlertExecutorOptions } from '../../../../../alerts/server'; import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types'; import { InfraBackendLibs } from '../../infra_types'; @@ -103,7 +103,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = } if (reason) { const actionGroupId = - nextState === AlertStates.OK ? ResolvedActionGroup.id : FIRED_ACTIONS.id; + nextState === AlertStates.OK ? RecoveredActionGroup.id : FIRED_ACTIONS.id; alertInstance.scheduleActions(actionGroupId, { group: item, alertState: stateToAlertMessage[nextState], diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index b31afba8ac9cc3..a1d6428f3b52be 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -6,7 +6,7 @@ import { createMetricThresholdExecutor, FIRED_ACTIONS } from './metric_threshold_executor'; import { Comparator, AlertStates } from './types'; import * as mocks from './test_mocks'; -import { ResolvedActionGroup } from '../../../../../alerts/common'; +import { RecoveredActionGroup } from '../../../../../alerts/common'; import { AlertExecutorOptions } from '../../../../../alerts/server'; import { alertsMock, @@ -367,7 +367,7 @@ describe('The metric threshold alert type', () => { expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); expect(getState(instanceID).alertState).toBe(AlertStates.ALERT); await execute([2]); - expect(mostRecentAction(instanceID).id).toBe(ResolvedActionGroup.id); + expect(mostRecentAction(instanceID).id).toBe(RecoveredActionGroup.id); expect(getState(instanceID).alertState).toBe(AlertStates.OK); }); test('does not continue to send a recovery alert if the metric is still OK', async () => { @@ -383,7 +383,7 @@ describe('The metric threshold alert type', () => { expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); expect(getState(instanceID).alertState).toBe(AlertStates.ALERT); await execute([2]); - expect(mostRecentAction(instanceID).id).toBe(ResolvedActionGroup.id); + expect(mostRecentAction(instanceID).id).toBe(RecoveredActionGroup.id); expect(getState(instanceID).alertState).toBe(AlertStates.OK); }); }); diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index 7c3918c37ebbf5..60790648d9a9b9 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -6,7 +6,7 @@ import { first, last } from 'lodash'; import { i18n } from '@kbn/i18n'; import moment from 'moment'; -import { ResolvedActionGroup } from '../../../../../alerts/common'; +import { RecoveredActionGroup } from '../../../../../alerts/common'; import { AlertExecutorOptions } from '../../../../../alerts/server'; import { InfraBackendLibs } from '../../infra_types'; import { @@ -89,7 +89,7 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => const firstResult = first(alertResults); const timestamp = (firstResult && firstResult[group].timestamp) ?? moment().toISOString(); const actionGroupId = - nextState === AlertStates.OK ? ResolvedActionGroup.id : FIRED_ACTIONS.id; + nextState === AlertStates.OK ? RecoveredActionGroup.id : FIRED_ACTIONS.id; alertInstance.scheduleActions(actionGroupId, { group, alertState: stateToAlertMessage[nextState], diff --git a/x-pack/plugins/ingest_manager/jest.config.js b/x-pack/plugins/ingest_manager/jest.config.js new file mode 100644 index 00000000000000..8aff85670176b2 --- /dev/null +++ b/x-pack/plugins/ingest_manager/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/ingest_manager'], +}; diff --git a/x-pack/plugins/ingest_pipelines/jest.config.js b/x-pack/plugins/ingest_pipelines/jest.config.js new file mode 100644 index 00000000000000..48ce7dea0b5bab --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/ingest_pipelines'], +}; diff --git a/x-pack/plugins/lens/jest.config.js b/x-pack/plugins/lens/jest.config.js new file mode 100644 index 00000000000000..bcb80519c5ef5a --- /dev/null +++ b/x-pack/plugins/lens/jest.config.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/lens'], +}; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx index 93b4a4e3bea207..3d453cd078b7fe 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx @@ -88,7 +88,7 @@ function LayerPanels( const layerIds = activeVisualization.getLayerIds(visualizationState); return ( - + {layerIds.map((layerId, index) => ( setActiveDimension(initialActiveDimensionState)} + handleClose={() => { + if (layerDatasource.updateStateOnCloseDimension) { + const newState = layerDatasource.updateStateOnCloseDimension({ + state: layerDatasourceState, + layerId, + columnId: activeId!, + }); + props.updateDatasource(datasourceId, newState); + } + setActiveDimension(initialActiveDimensionState); + }} panel={ <> {activeGroup && activeId && (