diff --git a/.ci/Jenkinsfile_security_cypress b/.ci/Jenkinsfile_security_cypress
index d7f702a56563f..f7b02cd1c4ab1 100644
--- a/.ci/Jenkinsfile_security_cypress
+++ b/.ci/Jenkinsfile_security_cypress
@@ -10,7 +10,8 @@ kibanaPipeline(timeoutMinutes: 180) {
) {
catchError {
withEnv([
- 'CI_PARALLEL_PROCESS_NUMBER=1'
+ 'CI_PARALLEL_PROCESS_NUMBER=1',
+ 'IGNORE_SHIP_CI_STATS_ERROR=true',
]) {
def job = 'xpack-securityCypress'
diff --git a/.ci/es-snapshots/Jenkinsfile_verify_es b/.ci/es-snapshots/Jenkinsfile_verify_es
index 11a39faa9aed0..736a71b73d14d 100644
--- a/.ci/es-snapshots/Jenkinsfile_verify_es
+++ b/.ci/es-snapshots/Jenkinsfile_verify_es
@@ -26,10 +26,12 @@ kibanaPipeline(timeoutMinutes: 150) {
message: "[${SNAPSHOT_VERSION}] ES Snapshot Verification Failure",
) {
retryable.enable(2)
- withEnv(["ES_SNAPSHOT_MANIFEST=${SNAPSHOT_MANIFEST}"]) {
+ withEnv([
+ "ES_SNAPSHOT_MANIFEST=${SNAPSHOT_MANIFEST}",
+ 'IGNORE_SHIP_CI_STATS_ERROR=true',
+ ]) {
parallel([
'kibana-intake-agent': workers.intake('kibana-intake', './test/scripts/jenkins_unit.sh'),
- 'x-pack-intake-agent': workers.intake('x-pack-intake', './test/scripts/jenkins_xpack.sh'),
'kibana-oss-agent': workers.functional('kibana-oss-tests', { kibanaPipeline.buildOss() }, [
'oss-ciGroup1': kibanaPipeline.ossCiGroupProcess(1),
'oss-ciGroup2': kibanaPipeline.ossCiGroupProcess(2),
diff --git a/.ci/jobs.yml b/.ci/jobs.yml
index b05e834f5a459..6aa93d4a1056a 100644
--- a/.ci/jobs.yml
+++ b/.ci/jobs.yml
@@ -2,7 +2,6 @@
JOB:
- kibana-intake
- - x-pack-intake
- kibana-firefoxSmoke
- kibana-ciGroup1
- kibana-ciGroup2
diff --git a/.eslintrc.js b/.eslintrc.js
index 9430b9bf24466..7608bcb40a0b9 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1119,6 +1119,39 @@ module.exports = {
// All files
files: ['x-pack/plugins/enterprise_search/**/*.{ts,tsx}'],
rules: {
+ 'import/order': [
+ 'error',
+ {
+ groups: ['unknown', ['builtin', 'external'], 'internal', 'parent', 'sibling', 'index'],
+ pathGroups: [
+ {
+ pattern:
+ '{../../../../../../,../../../../../,../../../../,../../../,../../,../}{common/,*}__mocks__{*,/**}',
+ group: 'unknown',
+ },
+ {
+ pattern: '{**,.}/*.mock',
+ group: 'unknown',
+ },
+ {
+ pattern: 'react*',
+ group: 'external',
+ position: 'before',
+ },
+ {
+ pattern: '{@elastic/**,@kbn/**,src/**}',
+ group: 'internal',
+ },
+ ],
+ pathGroupsExcludedImportTypes: [],
+ alphabetize: {
+ order: 'asc',
+ caseInsensitive: true,
+ },
+ 'newlines-between': 'always-and-inside-groups',
+ },
+ ],
+ 'import/newline-after-import': 'error',
'react-hooks/exhaustive-deps': 'off',
'react/jsx-boolean-value': ['error', 'never'],
},
diff --git a/dev_docs/assets/applications.png b/dev_docs/assets/applications.png
new file mode 100644
index 0000000000000..409f3416136ec
Binary files /dev/null and b/dev_docs/assets/applications.png differ
diff --git a/dev_docs/assets/platform_plugins_core.png b/dev_docs/assets/platform_plugins_core.png
new file mode 100644
index 0000000000000..a1b7df4483c3f
Binary files /dev/null and b/dev_docs/assets/platform_plugins_core.png differ
diff --git a/dev_docs/kibana_platform_plugin_intro.mdx b/dev_docs/kibana_platform_plugin_intro.mdx
index 3303561fae069..ce8b8b8b54756 100644
--- a/dev_docs/kibana_platform_plugin_intro.mdx
+++ b/dev_docs/kibana_platform_plugin_intro.mdx
@@ -11,45 +11,34 @@ From an end user perspective, Kibana is a tool for interacting with Elasticsearc
to visualize and analyze data.
From a developer perspective, Kibana is a platform that provides a set of tools to build not only the UI you see in Kibana today, but
-a wide variety of applications that can be used to explore, visualize, and act upon data in Elasticsearch. The platform provides developers the ability to build applications, or inject extra functionality into
+a wide variety of applications that can be used to explore, visualize, and act upon data in Elasticsearch. The platform provides developers the ability
+to build applications, or inject extra functionality into
already existing applications. Did you know that almost everything you see in the
Kibana UI is built inside a plugin? If you removed all plugins from Kibana, you'd be left with an empty navigation menu, and a set of
developer tools. The Kibana platform is a blank canvas, just waiting for a developer to come along and create something!
![Kibana personas](assets/kibana_platform_plugin_end_user.png)
+
+## Platform services
-## Plugins vs The Platform
+Plugins have access to three kinds of public services:
-The core platform provides the most basic and fundamental tools neccessary for building a plugin, like creating saved objects,
-routing, application registration, and notifications. The Core platform is not a plugin itself, although
-there are some plugins that provide platform functionality. For example, the
- provides basic utilities to search, query, and filter data in Elasticsearch.
-This code is not part of Core, but is still fundamental for building a plugin,
- and we strongly encourage using this service over querying Elasticsearch directly.
-
-
-We currently have three kinds of public services:
-
- - platform services provided by `core`
- - platform services provided by plugins, that can, and should, be used by every plugin (e.g. ) .
- - shared services provided by plugins, that are only relevant for only a few, specific plugins (e.g. "presentation utils").
-
-Two common questions we encounter are:
+ - Platform services provided by `core` ()
+ - Platform services provided by plugins ()
+ - Shared services provided by plugins, that are only relevant for only a few, specific plugins (e.g. "presentation utils").
-1. Which services are platform services?
-2. What is the difference between platform code supplied by core, and platform code supplied by plugins?
+ The first two items are what make up "Platform services".
-We don't have great answers to those questions today. Currently, the best answers we have are:
+
-1. Platform plugins are _usually_ plugins that are managed by the Platform Group, but we are starting to see some exceptions.
-2. `core` code contains the most fundamental and stable services needed for plugin development. Everything else goes in a plugin.
+We try to put only the most stable and fundamental code into `Core`, while more application focused functionality goes in a plugin, but the heuristic isn't
+clear, and we haven't done a great job of sticking to it. For example, notifications and toasts are core services, but data and search are plugin services.
-We will continue to focus on adding clarity around these types of services and what developers can expect from each.
-
-
+Today it looks something like this.
+![Core vs platform plugins vs plugins](assets/platform_plugins_core.png)
+
-
When the Kibana platform and plugin infrastructure was built, we thought of two types of code: core services, and other plugin services. We planned to keep the most stable and fundamental
code needed to build plugins inside core.
@@ -70,125 +59,62 @@ Another side effect of having many small plugins is that common code often ends
We recognize the need to better clarify the relationship between core functionality, platform-like plugin functionality, and functionality exposed by other plugins.
It's something we will be working on!
-
-The main difference between core functionality and functionality supplied by plugins, is in how it is accessed. Core is
-passed to plugins as the first parameter to their `start` and `setup` lifecycle functions, while plugin supplied functionality is passed as the
-second parameter. Plugin dependencies must be declared explicitly inside the `kibana.json` file. Core functionality is always provided. Read the
-section on for more information.
-
-## The anatomy of a plugin
-
-Plugins are defined as classes and present themselves to Kibana through a simple wrapper function. A plugin can have browser-side code, server-side code,
-or both. There is no architectural difference between a plugin in the browser and a plugin on the server. In both places, you describe your plugin similarly,
-and you interact with Core and other plugins in the same way.
-
-The basic file structure of a Kibana plugin named demo that has both client-side and server-side code would be:
-
-```
-plugins/
- demo
- kibana.json [1]
- public
- index.ts [2]
- plugin.ts [3]
- server
- index.ts [4]
- plugin.ts [5]
-```
-
-### [1] kibana.json
-
-`kibana.json` is a static manifest file that is used to identify the plugin and to specify if this plugin has server-side code, browser-side code, or both:
-
-```
-{
- "id": "demo",
- "version": "kibana",
- "server": true,
- "ui": true
-}
-```
-
-### [2] public/index.ts
-
-`public/index.ts` is the entry point into the client-side code of this plugin. It must export a function named plugin, which will receive a standard set of
- core capabilities as an argument. It should return an instance of its plugin class for Kibana to load.
-
-```
-import type { PluginInitializerContext } from 'kibana/server';
-import { DemoPlugin } from './plugin';
-
-export function plugin(initializerContext: PluginInitializerContext) {
- return new DemoPlugin(initializerContext);
-}
-```
-
-### [3] public/plugin.ts
-
-`public/plugin.ts` is the client-side plugin definition itself. Technically speaking, it does not need to be a class or even a separate file from the entry
- point, but all plugins at Elastic should be consistent in this way.
-
+We will continue to focus on adding clarity around these types of services and what developers can expect from each.
- ```ts
-import type { Plugin, PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server';
-export class DemoPlugin implements Plugin {
- constructor(initializerContext: PluginInitializerContext) {}
+
- public setup(core: CoreSetup) {
- // called when plugin is setting up during Kibana's startup sequence
- }
+### Core services
- public start(core: CoreStart) {
- // called after all plugins are set up
- }
+Sometimes referred to just as Core, Core services provide the most basic and fundamental tools neccessary for building a plugin, like creating saved objects,
+routing, application registration, and notifications. The Core platform is not a plugin itself, although
+there are some plugins that provide platform functionality. We call these .
- public stop() {
- // called when plugin is torn down during Kibana's shutdown sequence
- }
-}
- ```
+### Platform plugins
+Plugins that provide fundamental services and functionality to extend and customize Kibana, for example, the
+ plugin. There is no official way to tell if a plugin is a platform plugin or not.
+Platform plugins are _usually_ plugins that are managed by the Platform Group, but we are starting to see some exceptions.
-### [4] server/index.ts
+## Plugins
-`server/index.ts` is the entry-point into the server-side code of this plugin. It is identical in almost every way to the client-side entry-point:
+Plugins are code that is written to extend and customize Kibana. Plugin's don't have to be part of the Kibana repo, though the Kibana
+repo does contain many plugins! Plugins add customizations by
+using provided by .
+Sometimes people confuse the term "plugin" and "application". While often there is a 1:1 relationship between a plugin and an application, it is not always the case.
+A plugin may register many applications, or none.
-### [5] server/plugin.ts
+### Applications
-`server/plugin.ts` is the server-side plugin definition. The shape of this plugin is the same as it’s client-side counter-part:
+Applications are top level pages in the Kibana UI. Dashboard, Canvas, Maps, App Search, etc, are all examples of applications:
-```ts
-import type { Plugin, PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server';
+![applications in kibana](./assets/applications.png)
-export class DemoPlugin implements Plugin {
- constructor(initializerContext: PluginInitializerContext) {}
+A plugin can register an application by
+adding it to core's application .
- public setup(core: CoreSetup) {
- // called when plugin is setting up during Kibana's startup sequence
- }
+### Public plugin API
- public start(core: CoreStart) {
- // called after all plugins are set up
- }
+A plugin's public API consists of everything exported from a plugin's ,
+as well as from the top level `index.ts` files that exist in the three "scope" folders:
- public stop() {
- // called when plugin is torn down during Kibana's shutdown sequence
- }
-}
-```
+- common/index.ts
+- public/index.ts
+- server/index.ts
-Kibana does not impose any technical restrictions on how the the internals of a plugin are architected, though there are certain
-considerations related to how plugins integrate with core APIs and APIs exposed by other plugins that may greatly impact how they are built.
+Any plugin that exports something from those files, or from the lifecycle methods, is exposing a public service. We sometimes call these things "plugin services" or
+"shared services".
-## Plugin lifecycles & Core services
+## Lifecycle methods
-The various independent domains that make up core are represented by a series of services. Those services expose public interfaces that are provided to all plugins.
-Services expose different features at different parts of their lifecycle. We describe the lifecycle of core services and plugins with specifically-named functions on the service definition.
+Core, and plugins, expose different features at different parts of their lifecycle. We describe the lifecycle of core services and plugins with
+ specifically-named functions on the service definition.
-Kibana has three lifecycles: setup, start, and stop. Each plugin’s setup function is called sequentially while Kibana is setting up on the server or when it is being loaded in the browser. The start functions are called sequentially after setup has been completed for all plugins. The stop functions are called sequentially while Kibana is gracefully shutting down the server or when the browser tab or window is being closed.
+Kibana has three lifecycles: setup, start, and stop. Each plugin’s setup function is called sequentially while Kibana is setting up
+ on the server or when it is being loaded in the browser. The start functions are called sequentially after setup has been completed for all plugins.
+ The stop functions are called sequentially while Kibana is gracefully shutting down the server or when the browser tab or window is being closed.
The table below explains how each lifecycle relates to the state of Kibana.
@@ -201,105 +127,18 @@ The table below explains how each lifecycle relates to the state of Kibana.
Different service interfaces can and will be passed to setup, start, and stop because certain functionality makes sense in the context of a running plugin while other types
of functionality may have restrictions or may only make sense in the context of a plugin that is stopping.
-## How plugin's interact with each other, and Core
-
-The lifecycle-specific contracts exposed by core services are always passed as the first argument to the equivalent lifecycle function in a plugin.
-For example, the core http service exposes a function createRouter to all plugin setup functions. To use this function to register an HTTP route handler,
-a plugin just accesses it off of the first argument:
-
-```ts
-import type { CoreSetup } from 'kibana/server';
-
-export class DemoPlugin {
- public setup(core: CoreSetup) {
- const router = core.http.createRouter();
- // handler is called when '/path' resource is requested with `GET` method
- router.get({ path: '/path', validate: false }, (context, req, res) => res.ok({ content: 'ok' }));
- }
-}
-```
-
-Unlike core, capabilities exposed by plugins are not automatically injected into all plugins.
-Instead, if a plugin wishes to use the public interface provided by another plugin, it must first declare that plugin as a
- dependency in it’s kibana.json manifest file.
-
-** foobar plugin.ts: **
-
-```
-import type { Plugin } from 'kibana/server';
-export interface FoobarPluginSetup { [1]
- getFoo(): string;
-}
-
-export interface FoobarPluginStart { [1]
- getBar(): string;
-}
-
-export class MyPlugin implements Plugin {
- public setup(): FoobarPluginSetup {
- return {
- getFoo() {
- return 'foo';
- },
- };
- }
-
- public start(): FoobarPluginStart {
- return {
- getBar() {
- return 'bar';
- },
- };
- }
-}
-```
-[1] We highly encourage plugin authors to explicitly declare public interfaces for their plugins.
-
-
-** demo kibana.json**
-
-```
-{
- "id": "demo",
- "requiredPlugins": ["foobar"],
- "server": true,
- "ui": true
-}
-```
-
-With that specified in the plugin manifest, the appropriate interfaces are then available via the second argument of setup and/or start:
-
-```ts
-import type { CoreSetup, CoreStart } from 'kibana/server';
-import type { FoobarPluginSetup, FoobarPluginStart } from '../../foobar/server';
-
-interface DemoSetupPlugins { [1]
- foobar: FoobarPluginSetup;
-}
-
-interface DemoStartPlugins {
- foobar: FoobarPluginStart;
-}
-
-export class DemoPlugin {
- public setup(core: CoreSetup, plugins: DemoSetupPlugins) { [2]
- const { foobar } = plugins;
- foobar.getFoo(); // 'foo'
- foobar.getBar(); // throws because getBar does not exist
- }
-
- public start(core: CoreStart, plugins: DemoStartPlugins) { [3]
- const { foobar } = plugins;
- foobar.getFoo(); // throws because getFoo does not exist
- foobar.getBar(); // 'bar'
- }
-
- public stop() {}
-}
-```
-
-[1] The interface for plugin’s dependencies must be manually composed. You can do this by importing the appropriate type from the plugin and constructing an interface where the property name is the plugin’s ID.
-
-[2] These manually constructed types should then be used to specify the type of the second argument to the plugin.
-
-[3] Notice that the type for the setup and start lifecycles are different. Plugin lifecycle functions can only access the APIs that are exposed during that lifecycle.
+## Extension points
+
+An extension point is a function provided by core, or a plugin's plugin API, that can be used by other
+plugins to customize the Kibana experience. Examples of extension points are:
+
+- core.application.register (The extension point talked about above)
+- core.notifications.toasts.addSuccess
+- core.overlays.showModal
+- embeddables.registerEmbeddableFactory
+- uiActions.registerAction
+- core.saedObjects.registerType
+
+## Follow up material
+
+Learn how to build your own plugin by following
\ No newline at end of file
diff --git a/dev_docs/tutorials/building_a_plugin.mdx b/dev_docs/tutorials/building_a_plugin.mdx
new file mode 100644
index 0000000000000..cee5a9a399de5
--- /dev/null
+++ b/dev_docs/tutorials/building_a_plugin.mdx
@@ -0,0 +1,226 @@
+---
+id: kibDevTutorialBuildAPlugin
+slug: /kibana-dev-docs/tutorials/build-a-plugin
+title: Kibana plugin tutorial
+summary: Anatomy of a Kibana plugin and how to build one
+date: 2021-02-05
+tags: ['kibana','onboarding', 'dev', 'tutorials']
+---
+
+Prereading material:
+
+-
+
+## The anatomy of a plugin
+
+Plugins are defined as classes and present themselves to Kibana through a simple wrapper function. A plugin can have browser-side code, server-side code,
+or both. There is no architectural difference between a plugin in the browser and a plugin on the server. In both places, you describe your plugin similarly,
+and you interact with Core and other plugins in the same way.
+
+The basic file structure of a Kibana plugin named demo that has both client-side and server-side code would be:
+
+```
+plugins/
+ demo
+ kibana.json [1]
+ public
+ index.ts [2]
+ plugin.ts [3]
+ server
+ index.ts [4]
+ plugin.ts [5]
+ common
+ index.ts [6]
+```
+
+### [1] kibana.json
+
+`kibana.json` is a static manifest file that is used to identify the plugin and to specify if this plugin has server-side code, browser-side code, or both:
+
+```
+{
+ "id": "demo",
+ "version": "kibana",
+ "server": true,
+ "ui": true
+}
+```
+
+### [2] public/index.ts
+
+`public/index.ts` is the entry point into the client-side code of this plugin. It must export a function named plugin, which will receive a standard set of
+ core capabilities as an argument. It should return an instance of its plugin class for Kibana to load.
+
+```
+import type { PluginInitializerContext } from 'kibana/server';
+import { DemoPlugin } from './plugin';
+
+export function plugin(initializerContext: PluginInitializerContext) {
+ return new DemoPlugin(initializerContext);
+}
+```
+
+### [3] public/plugin.ts
+
+`public/plugin.ts` is the client-side plugin definition itself. Technically speaking, it does not need to be a class or even a separate file from the entry
+ point, but all plugins at Elastic should be consistent in this way.
+
+
+ ```ts
+import type { Plugin, PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server';
+
+export class DemoPlugin implements Plugin {
+ constructor(initializerContext: PluginInitializerContext) {}
+
+ public setup(core: CoreSetup) {
+ // called when plugin is setting up during Kibana's startup sequence
+ }
+
+ public start(core: CoreStart) {
+ // called after all plugins are set up
+ }
+
+ public stop() {
+ // called when plugin is torn down during Kibana's shutdown sequence
+ }
+}
+ ```
+
+
+### [4] server/index.ts
+
+`server/index.ts` is the entry-point into the server-side code of this plugin. It is identical in almost every way to the client-side entry-point:
+
+### [5] server/plugin.ts
+
+`server/plugin.ts` is the server-side plugin definition. The shape of this plugin is the same as it’s client-side counter-part:
+
+```ts
+import type { Plugin, PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server';
+
+export class DemoPlugin implements Plugin {
+ constructor(initializerContext: PluginInitializerContext) {}
+
+ public setup(core: CoreSetup) {
+ // called when plugin is setting up during Kibana's startup sequence
+ }
+
+ public start(core: CoreStart) {
+ // called after all plugins are set up
+ }
+
+ public stop() {
+ // called when plugin is torn down during Kibana's shutdown sequence
+ }
+}
+```
+
+Kibana does not impose any technical restrictions on how the the internals of a plugin are architected, though there are certain
+considerations related to how plugins integrate with core APIs and APIs exposed by other plugins that may greatly impact how they are built.
+
+### [6] common/index.ts
+
+`common/index.ts` is the entry-point into code that can be used both server-side or client side.
+
+## How plugin's interact with each other, and Core
+
+The lifecycle-specific contracts exposed by core services are always passed as the first argument to the equivalent lifecycle function in a plugin.
+For example, the core http service exposes a function createRouter to all plugin setup functions. To use this function to register an HTTP route handler,
+a plugin just accesses it off of the first argument:
+
+```ts
+import type { CoreSetup } from 'kibana/server';
+
+export class DemoPlugin {
+ public setup(core: CoreSetup) {
+ const router = core.http.createRouter();
+ // handler is called when '/path' resource is requested with `GET` method
+ router.get({ path: '/path', validate: false }, (context, req, res) => res.ok({ content: 'ok' }));
+ }
+}
+```
+
+Unlike core, capabilities exposed by plugins are not automatically injected into all plugins.
+Instead, if a plugin wishes to use the public interface provided by another plugin, it must first declare that plugin as a
+ dependency in it’s kibana.json manifest file.
+
+** foobar plugin.ts: **
+
+```
+import type { Plugin } from 'kibana/server';
+export interface FoobarPluginSetup { [1]
+ getFoo(): string;
+}
+
+export interface FoobarPluginStart { [1]
+ getBar(): string;
+}
+
+export class MyPlugin implements Plugin {
+ public setup(): FoobarPluginSetup {
+ return {
+ getFoo() {
+ return 'foo';
+ },
+ };
+ }
+
+ public start(): FoobarPluginStart {
+ return {
+ getBar() {
+ return 'bar';
+ },
+ };
+ }
+}
+```
+[1] We highly encourage plugin authors to explicitly declare public interfaces for their plugins.
+
+
+** demo kibana.json**
+
+```
+{
+ "id": "demo",
+ "requiredPlugins": ["foobar"],
+ "server": true,
+ "ui": true
+}
+```
+
+With that specified in the plugin manifest, the appropriate interfaces are then available via the second argument of setup and/or start:
+
+```ts
+import type { CoreSetup, CoreStart } from 'kibana/server';
+import type { FoobarPluginSetup, FoobarPluginStart } from '../../foobar/server';
+
+interface DemoSetupPlugins { [1]
+ foobar: FoobarPluginSetup;
+}
+
+interface DemoStartPlugins {
+ foobar: FoobarPluginStart;
+}
+
+export class DemoPlugin {
+ public setup(core: CoreSetup, plugins: DemoSetupPlugins) { [2]
+ const { foobar } = plugins;
+ foobar.getFoo(); // 'foo'
+ foobar.getBar(); // throws because getBar does not exist
+ }
+
+ public start(core: CoreStart, plugins: DemoStartPlugins) { [3]
+ const { foobar } = plugins;
+ foobar.getFoo(); // throws because getFoo does not exist
+ foobar.getBar(); // 'bar'
+ }
+
+ public stop() {}
+}
+```
+
+[1] The interface for plugin’s dependencies must be manually composed. You can do this by importing the appropriate type from the plugin and constructing an interface where the property name is the plugin’s ID.
+
+[2] These manually constructed types should then be used to specify the type of the second argument to the plugin.
+
+[3] Notice that the type for the setup and start lifecycles are different. Plugin lifecycle functions can only access the APIs that are exposed during that lifecycle.
diff --git a/docs/developer/contributing/development-ci-metrics.asciidoc b/docs/developer/contributing/development-ci-metrics.asciidoc
index 3e49686fb67f0..2efe4e7c60a7d 100644
--- a/docs/developer/contributing/development-ci-metrics.asciidoc
+++ b/docs/developer/contributing/development-ci-metrics.asciidoc
@@ -121,13 +121,20 @@ Changes to the {kib-repo}blob/{branch}/packages/kbn-optimizer/limits.yml[`limits
[[ci-metric-validating-limits]]
=== Validating `page load bundle size` limits
-Once you've fixed any issues discovered while diagnosing overages you probably should just push the changes to your PR and let CI validate them.
+While you're trying to track down changes which will improve the bundle size, try running the following command locally:
-If you have a pretty powerful dev machine, or the necessary patience/determination, you can validate the limits locally by running the following command:
+[source,shell]
+-----------
+node scripts/build_kibana_platform_plugins --dist --watch --focus {pluginId}
+-----------
+
+This will build the front-end bundles for your plugin and only the plugins your plugin depends on. Whenever you make changes the bundles are rebuilt and you can inspect the metrics of that build in the `target/public/metrics.json` file within your plugin. This file will be updated as you save changes to the source and should be helpful to determine if your changes are lowering the `page load asset size` enough.
+
+If you only want to run the build once you can run:
[source,shell]
-----------
-node scripts/build_kibana_platform_plugins --validate-limits
+node scripts/build_kibana_platform_plugins --validate-limits --focus {pluginId}
-----------
This command needs to apply production optimizations to get the right sizes, which means that the optimizer will take significantly longer to run and on most developmer machines will consume all of your machines resources for 20 minutes or more. If you'd like to multi-task while this is running you might need to limit the number of workers using the `--max-workers` flag.
\ No newline at end of file
diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc
index 6587d5dc422b4..263addc98ee62 100644
--- a/docs/developer/plugin-list.asciidoc
+++ b/docs/developer/plugin-list.asciidoc
@@ -443,10 +443,6 @@ using the CURL scripts in the scripts folder.
|Visualize geo data from Elasticsearch or 3rd party geo-services.
-|{kib-repo}blob/{branch}/x-pack/plugins/maps_file_upload/README.md[mapsFileUpload]
-|Deprecated - plugin targeted for removal and will get merged into file_upload plugin
-
-
|{kib-repo}blob/{branch}/x-pack/plugins/maps_legacy_licensing/README.md[mapsLegacyLicensing]
|This plugin provides access to the detailed tile map services from Elastic.
diff --git a/docs/migration/migrate_8_0.asciidoc b/docs/migration/migrate_8_0.asciidoc
index 14eff4594c813..5452621440ed8 100644
--- a/docs/migration/migrate_8_0.asciidoc
+++ b/docs/migration/migrate_8_0.asciidoc
@@ -52,7 +52,18 @@ for example, `logstash-*`.
==== Default logging timezone is now the system's timezone
*Details:* In prior releases the timezone used in logs defaulted to UTC. We now use the host machine's timezone by default.
-*Impact:* To restore the previous behavior, in kibana.yml set `logging.timezone: UTC`.
+*Impact:* To restore the previous behavior, in kibana.yml use the pattern layout, with a date modifier:
+[source,yaml]
+-------------------
+logging:
+ appenders:
+ console:
+ kind: console
+ layout:
+ kind: pattern
+ pattern: "%date{ISO8601_TZ}{UTC}"
+-------------------
+See https://github.com/elastic/kibana/pull/90368 for more details.
[float]
==== Responses are never logged by default
diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc
index ecdb41c897b12..9b9c26fd0e1db 100644
--- a/docs/setup/settings.asciidoc
+++ b/docs/setup/settings.asciidoc
@@ -309,7 +309,8 @@ suppress all logging output. *Default: `false`*
| Set to the canonical time zone ID
(for example, `America/Los_Angeles`) to log events using that time zone.
For possible values, refer to
-https://en.wikipedia.org/wiki/List_of_tz_database_time_zones[database time zones]. *Default: `UTC`*
+https://en.wikipedia.org/wiki/List_of_tz_database_time_zones[database time zones].
+When not set, log events use the host timezone
| [[logging-verbose]] `logging.verbose:` {ess-icon}
| Set to `true` to log all events, including system usage information and all
diff --git a/docs/user/alerting/alert-types.asciidoc b/docs/user/alerting/alert-types.asciidoc
index 279739e95b522..016ecc3167298 100644
--- a/docs/user/alerting/alert-types.asciidoc
+++ b/docs/user/alerting/alert-types.asciidoc
@@ -130,12 +130,13 @@ image::images/alert-types-es-query-select.png[Choosing an ES query alert type]
[float]
==== Defining the conditions
-The ES query alert has 4 clauses that define the condition to detect.
+The ES query alert has 5 clauses that define the condition to detect.
[role="screenshot"]
image::images/alert-types-es-query-conditions.png[Four clauses define the condition to detect]
Index:: This clause requires an *index or index pattern* and a *time field* that will be used for the *time window*.
+Size:: This clause specifies the number of documents to pass to the configured actions when the the threshold condition is met.
ES query:: This clause specifies the ES DSL query to execute. The number of documents that match this query will be evaulated against the threshold
condition. Aggregations are not supported at this time.
Threshold:: This clause defines a threshold value and a comparison operator (`is above`, `is above or equals`, `is below`, `is below or equals`, or `is between`). The number of documents that match the specified query is compared to this threshold.
diff --git a/docs/user/alerting/images/alert-types-es-query-conditions.png b/docs/user/alerting/images/alert-types-es-query-conditions.png
index ce2bd6a42a4b5..3cbba5eb4950e 100644
Binary files a/docs/user/alerting/images/alert-types-es-query-conditions.png and b/docs/user/alerting/images/alert-types-es-query-conditions.png differ
diff --git a/jest.config.integration.js b/jest.config.integration.js
index df9fa9029aaa3..50767932a52d7 100644
--- a/jest.config.integration.js
+++ b/jest.config.integration.js
@@ -17,6 +17,7 @@ module.exports = {
testPathIgnorePatterns: preset.testPathIgnorePatterns.filter(
(pattern) => !pattern.includes('integration_tests')
),
+ setupFilesAfterEnv: ['/packages/kbn-test/target/jest/setup/after_env.integration.js'],
reporters: [
'default',
[
@@ -24,5 +25,7 @@ module.exports = {
{ reportName: 'Jest Integration Tests' },
],
],
- setupFilesAfterEnv: ['/packages/kbn-test/target/jest/setup/after_env.integration.js'],
+ coverageReporters: !!process.env.CI
+ ? [['json', { file: 'jest-integration.json' }]]
+ : ['html', 'text'],
};
diff --git a/jest.config.js b/jest.config.js
index 89f66b5ee462f..03dc832ba170c 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -7,6 +7,14 @@
*/
module.exports = {
+ preset: '@kbn/test',
rootDir: '.',
- projects: [...require('./jest.config.oss').projects, ...require('./x-pack/jest.config').projects],
+ projects: [
+ '/packages/*/jest.config.js',
+ '/src/*/jest.config.js',
+ '/src/legacy/*/jest.config.js',
+ '/src/plugins/*/jest.config.js',
+ '/test/*/jest.config.js',
+ '/x-pack/plugins/*/jest.config.js',
+ ],
};
diff --git a/jest.config.oss.js b/jest.config.oss.js
deleted file mode 100644
index fcd704382f39d..0000000000000
--- a/jest.config.oss.js
+++ /dev/null
@@ -1,19 +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
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-module.exports = {
- preset: '@kbn/test',
- rootDir: '.',
- projects: [
- '/packages/*/jest.config.js',
- '/src/*/jest.config.js',
- '/src/legacy/*/jest.config.js',
- '/src/plugins/*/jest.config.js',
- '/test/*/jest.config.js',
- ],
-};
diff --git a/package.json b/package.json
index 7144745f2ae35..aac576dbc3561 100644
--- a/package.json
+++ b/package.json
@@ -97,7 +97,7 @@
"@babel/runtime": "^7.12.5",
"@elastic/datemath": "link:packages/elastic-datemath",
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary",
- "@elastic/ems-client": "7.11.0",
+ "@elastic/ems-client": "7.12.0",
"@elastic/eui": "31.4.0",
"@elastic/filesaver": "1.1.2",
"@elastic/good": "^9.0.1-kibana3",
diff --git a/packages/kbn-dev-utils/src/ci_stats_reporter/ship_ci_stats_cli.ts b/packages/kbn-dev-utils/src/ci_stats_reporter/ship_ci_stats_cli.ts
index 1ee78518bb801..4d07b54b8cf03 100644
--- a/packages/kbn-dev-utils/src/ci_stats_reporter/ship_ci_stats_cli.ts
+++ b/packages/kbn-dev-utils/src/ci_stats_reporter/ship_ci_stats_cli.ts
@@ -22,10 +22,18 @@ export function shipCiStatsCli() {
throw createFlagError('expected --metrics to be a string');
}
+ const maybeFail = (message: string) => {
+ const error = createFailError(message);
+ if (process.env.IGNORE_SHIP_CI_STATS_ERROR === 'true') {
+ error.exitCode = 0;
+ }
+ return error;
+ };
+
const reporter = CiStatsReporter.fromEnv(log);
if (!reporter.isEnabled()) {
- throw createFailError('unable to initilize the CI Stats reporter');
+ throw maybeFail('unable to initilize the CI Stats reporter');
}
for (const path of metricPaths) {
@@ -35,7 +43,7 @@ export function shipCiStatsCli() {
if (await reporter.metrics(JSON.parse(json))) {
log.success('shipped metrics from', path);
} else {
- throw createFailError('failed to ship metrics');
+ throw maybeFail('failed to ship metrics');
}
}
},
diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml
index 490c2ccc19d7d..a1e40c06f6fa1 100644
--- a/packages/kbn-optimizer/limits.yml
+++ b/packages/kbn-optimizer/limits.yml
@@ -104,4 +104,4 @@ pageLoadAssetSize:
presentationUtil: 28545
spacesOss: 18817
osquery: 107090
- mapsFileUpload: 23775
+ fileUpload: 25664
diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js
index 9ed11c4fe5fdd..717be8f413b48 100644
--- a/packages/kbn-test/jest-preset.js
+++ b/packages/kbn-test/jest-preset.js
@@ -19,7 +19,9 @@ module.exports = {
coveragePathIgnorePatterns: ['/node_modules/', '.*\\.d\\.ts'],
// A list of reporter names that Jest uses when writing coverage reports
- coverageReporters: !!process.env.CODE_COVERAGE ? ['json'] : ['html', 'text'],
+ coverageReporters: !!process.env.CODE_COVERAGE
+ ? [['json', { file: 'jest.json' }]]
+ : ['html', 'text'],
// An array of file extensions your modules use
moduleFileExtensions: ['js', 'mjs', 'json', 'ts', 'tsx', 'node'],
diff --git a/rfcs/text/0013_saved_object_migrations.md b/rfcs/text/0013_saved_object_migrations.md
index 6f5ab280a4612..88879e5e706eb 100644
--- a/rfcs/text/0013_saved_object_migrations.md
+++ b/rfcs/text/0013_saved_object_migrations.md
@@ -248,45 +248,49 @@ Note:
6. Use the reindexed legacy `.kibana_pre6.5.0_001` as the source for the rest of the migration algorithm.
3. If `.kibana` and `.kibana_7.10.0` both exists and are pointing to the same index this version's migration has already been completed.
1. Because the same version can have plugins enabled at any point in time,
- perform the mappings update in step (7) and migrate outdated documents
- with step (8).
- 2. Skip to step (10) to start serving traffic.
+ migrate outdated documents with step (9) and perform the mappings update in step (10).
+ 2. Skip to step (12) to start serving traffic.
4. Fail the migration if:
1. `.kibana` is pointing to an index that belongs to a later version of Kibana .e.g. `.kibana_7.12.0_001`
2. (Only in 8.x) The source index contains documents that belong to an unknown Saved Object type (from a disabled plugin). Log an error explaining that the plugin that created these documents needs to be enabled again or that these objects should be deleted. See section (4.2.1.4).
-5. Mark the source index as read-only and wait for all in-flight operations to drain (requires https://github.com/elastic/elasticsearch/pull/58094). This prevents any further writes from outdated nodes. Assuming this API is similar to the existing `//_close` API, we expect to receive `"acknowledged" : true` and `"shards_acknowledged" : true`. If all shards don’t acknowledge within the timeout, retry the operation until it succeeds.
-6. Clone the source index into a new target index which has writes enabled. All nodes on the same version will use the same fixed index name e.g. `.kibana_7.10.0_001`. The `001` postfix isn't used by Kibana, but allows for re-indexing an index should this be required by an Elasticsearch upgrade. E.g. re-index `.kibana_7.10.0_001` into `.kibana_7.10.0_002` and point the `.kibana_7.10.0` alias to `.kibana_7.10.0_002`.
- 1. `POST /.kibana_n/_clone/.kibana_7.10.0_001?wait_for_active_shards=all {"settings": {"index.blocks.write": false}}`. Ignore errors if the clone already exists.
- 2. Wait for the cloning to complete `GET /_cluster/health/.kibana_7.10.0_001?wait_for_status=green&timeout=60s` If cloning doesn’t complete within the 60s timeout, log a warning for visibility and poll again.
-7. Update the mappings of the target index
+5. Set a write block on the source index. This prevents any further writes from outdated nodes.
+6. Create a new temporary index `.kibana_7.10.0_reindex_temp` with `dynamic: false` on the top-level mappings so that any kind of document can be written to the index. This allows us to write untransformed documents to the index which might have fields which have been removed from the latest mappings defined by the plugin. Define minimal mappings for the `migrationVersion` and `type` fields so that we're still able to search for outdated documents that need to be transformed.
+ 1. Ignore errors if the target index already exists.
+7. Reindex the source index into the new temporary index.
+ 1. Use `op_type=create` `conflicts=proceed` and `wait_for_completion=false` so that multiple instances can perform the reindex in parallel but only one write per document will succeed.
+ 2. Wait for the reindex task to complete. If reindexing doesn’t complete within the 60s timeout, log a warning for visibility and poll again.
+8. Clone the temporary index into the target index `.kibana_7.10.0_001`. Since any further writes will only happen against the cloned target index this prevents a lost delete from occuring where one instance finishes the migration and deletes a document and another instance's reindex operation re-creates the deleted document.
+ 1. Set a write block on the temporary index
+ 2. Clone the temporary index into the target index while specifying that the target index should have writes enabled.
+ 3. If the clone operation fails because the target index already exist, ignore the error and wait for the target index to become green before proceeding.
+ 4. (The `001` postfix in the target index name isn't used by Kibana, but allows for re-indexing an index should this be required by an Elasticsearch upgrade. E.g. re-index `.kibana_7.10.0_001` into `.kibana_7.10.0_002` and point the `.kibana_7.10.0` alias to `.kibana_7.10.0_002`.)
+9. Transform documents by reading batches of outdated documents from the target index then transforming and updating them with optimistic concurrency control.
+ 1. Ignore any version conflict errors.
+ 2. If a document transform throws an exception, add the document to a failure list and continue trying to transform all other documents. If any failures occured, log the complete list of documents that failed to transform. Fail the migration.
+10. Update the mappings of the target index
1. Retrieve the existing mappings including the `migrationMappingPropertyHashes` metadata.
- 2. Update the mappings with `PUT /.kibana_7.10.0_001/_mapping`. The API deeply merges any updates so this won't remove the mappings of any plugins that were enabled in a previous version but are now disabled.
+ 2. Update the mappings with `PUT /.kibana_7.10.0_001/_mapping`. The API deeply merges any updates so this won't remove the mappings of any plugins that are disabled on this instance but have been enabled on another instance that also migrated this index.
3. Ensure that fields are correctly indexed using the target index's latest mappings `POST /.kibana_7.10.0_001/_update_by_query?conflicts=proceed`. In the future we could optimize this query by only targeting documents:
1. That belong to a known saved object type.
- 2. Which don't have outdated migrationVersion numbers since these will be transformed anyway.
- 3. That belong to a type whose mappings were changed by comparing the `migrationMappingPropertyHashes`. (Metadata, unlike the mappings isn't commutative, so there is a small chance that the metadata hashes do not accurately reflect the latest mappings, however, this will just result in an less efficient query).
-8. Transform documents by reading batches of outdated documents from the target index then transforming and updating them with optimistic concurrency control.
- 1. Ignore any version conflict errors.
- 2. If a document transform throws an exception, add the document to a failure list and continue trying to transform all other documents. If any failures occured, log the complete list of documents that failed to transform. Fail the migration.
-9. Mark the migration as complete. This is done as a single atomic
+11. Mark the migration as complete. This is done as a single atomic
operation (requires https://github.com/elastic/elasticsearch/pull/58100)
- to guarantees when multiple versions of Kibana are performing the
+ to guarantee that when multiple versions of Kibana are performing the
migration in parallel, only one version will win. E.g. if 7.11 and 7.12
are started in parallel and migrate from a 7.9 index, either 7.11 or 7.12
should succeed and accept writes, but not both.
- 3. Checks that `.kibana` alias is still pointing to the source index
- 4. Points the `.kibana_7.10.0` and `.kibana` aliases to the target index.
- 5. If this fails with a "required alias [.kibana] does not exist" error fetch `.kibana` again:
+ 1. Check that `.kibana` alias is still pointing to the source index
+ 2. Point the `.kibana_7.10.0` and `.kibana` aliases to the target index.
+ 3. Remove the temporary index `.kibana_7.10.0_reindex_temp`
+ 4. If this fails with a "required alias [.kibana] does not exist" error or "index_not_found_exception" for the temporary index, fetch `.kibana` again:
1. If `.kibana` is _not_ pointing to our target index fail the migration.
- 2. If `.kibana` is pointing to our target index the migration has succeeded and we can proceed to step (10).
-10. Start serving traffic. All saved object reads/writes happen through the
+ 2. If `.kibana` is pointing to our target index the migration has succeeded and we can proceed to step (12).
+12. Start serving traffic. All saved object reads/writes happen through the
version-specific alias `.kibana_7.10.0`.
Together with the limitations, this algorithm ensures that migrations are
idempotent. If two nodes are started simultaneously, both of them will start
transforming documents in that version's target index, but because migrations
are idempotent, it doesn’t matter which node’s writes win.
-
#### Known weaknesses:
(Also present in our existing migration algorithm since v7.4)
When the task manager index gets reindexed a reindex script is applied.
diff --git a/src/core/server/config/deprecation/core_deprecations.test.ts b/src/core/server/config/deprecation/core_deprecations.test.ts
index 70ca91b0d6317..4d7dafd2162c2 100644
--- a/src/core/server/config/deprecation/core_deprecations.test.ts
+++ b/src/core/server/config/deprecation/core_deprecations.test.ts
@@ -298,4 +298,24 @@ describe('core deprecations', () => {
expect(messages).toEqual([]);
});
});
+
+ describe('logging.timezone', () => {
+ it('warns when ops events are used', () => {
+ const { messages } = applyCoreDeprecations({
+ logging: { timezone: 'GMT' },
+ });
+ expect(messages).toMatchInlineSnapshot(`
+ Array [
+ "\\"logging.timezone\\" has been deprecated and will be removed in 8.0. To set the timezone moving forward, please add a timezone date modifier to the log pattern in your logging configuration. For more details, see https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.md",
+ ]
+ `);
+ });
+
+ it('does not warn when other events are configured', () => {
+ const { messages } = applyCoreDeprecations({
+ logging: { events: { log: '*' } },
+ });
+ expect(messages).toEqual([]);
+ });
+ });
});
diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts
index 0db53cdb2e8be..fbdbaeb14fd59 100644
--- a/src/core/server/config/deprecation/core_deprecations.ts
+++ b/src/core/server/config/deprecation/core_deprecations.ts
@@ -127,6 +127,18 @@ const requestLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, l
return settings;
};
+const timezoneLoggingDeprecation: ConfigDeprecation = (settings, fromPath, log) => {
+ if (has(settings, 'logging.timezone')) {
+ log(
+ '"logging.timezone" has been deprecated and will be removed ' +
+ 'in 8.0. To set the timezone moving forward, please add a timezone date modifier to the log pattern ' +
+ 'in your logging configuration. For more details, see ' +
+ 'https://github.com/elastic/kibana/blob/master/src/core/server/logging/README.md'
+ );
+ }
+ return settings;
+};
+
export const coreDeprecationProvider: ConfigDeprecationProvider = ({ rename, unusedFromRoot }) => [
unusedFromRoot('savedObjects.indexCheckTimeout'),
unusedFromRoot('server.xsrf.token'),
@@ -163,4 +175,5 @@ export const coreDeprecationProvider: ConfigDeprecationProvider = ({ rename, unu
mapManifestServiceUrlDeprecation,
opsLoggingEventDeprecation,
requestLoggingEventDeprecation,
+ timezoneLoggingDeprecation,
];
diff --git a/src/core/server/legacy/integration_tests/logging.test.ts b/src/core/server/legacy/integration_tests/logging.test.ts
index 6588f4270fe18..321eb81708f1e 100644
--- a/src/core/server/legacy/integration_tests/logging.test.ts
+++ b/src/core/server/legacy/integration_tests/logging.test.ts
@@ -87,7 +87,7 @@ describe('logging service', () => {
const loggedString = getPlatformLogsFromMock(mockConsoleLog);
expect(loggedString).toMatchInlineSnapshot(`
Array [
- "[xxxx-xx-xxTxx:xx:xx.xxxZ][INFO ][test-file] handled by NP",
+ "[xxxx-xx-xxTxx:xx:xx.xxx-xx:xx][INFO ][test-file] handled by NP",
]
`);
});
@@ -131,9 +131,9 @@ describe('logging service', () => {
expect(getPlatformLogsFromMock(mockConsoleLog)).toMatchInlineSnapshot(`
Array [
- "[xxxx-xx-xxTxx:xx:xx.xxxZ][INFO ][test-file] info",
- "[xxxx-xx-xxTxx:xx:xx.xxxZ][WARN ][test-file] warn",
- "[xxxx-xx-xxTxx:xx:xx.xxxZ][ERROR][test-file] error",
+ "[xxxx-xx-xxTxx:xx:xx.xxx-xx:xx][INFO ][test-file] info",
+ "[xxxx-xx-xxTxx:xx:xx.xxx-xx:xx][WARN ][test-file] warn",
+ "[xxxx-xx-xxTxx:xx:xx.xxx-xx:xx][ERROR][test-file] error",
]
`);
@@ -162,9 +162,9 @@ describe('logging service', () => {
expect(getPlatformLogsFromMock(mockConsoleLog)).toMatchInlineSnapshot(`
Array [
- "[xxxx-xx-xxTxx:xx:xx.xxxZ][INFO ][test-file] info",
- "[xxxx-xx-xxTxx:xx:xx.xxxZ][WARN ][test-file] warn",
- "[xxxx-xx-xxTxx:xx:xx.xxxZ][ERROR][test-file] error",
+ "[xxxx-xx-xxTxx:xx:xx.xxx-xx:xx][INFO ][test-file] info",
+ "[xxxx-xx-xxTxx:xx:xx.xxx-xx:xx][WARN ][test-file] warn",
+ "[xxxx-xx-xxTxx:xx:xx.xxx-xx:xx][ERROR][test-file] error",
]
`);
@@ -199,9 +199,9 @@ describe('logging service', () => {
expect(getPlatformLogsFromMock(mockConsoleLog)).toMatchInlineSnapshot(`
Array [
- "[xxxx-xx-xxTxx:xx:xx.xxxZ][INFO ][test-file] info",
- "[xxxx-xx-xxTxx:xx:xx.xxxZ][WARN ][test-file] warn",
- "[xxxx-xx-xxTxx:xx:xx.xxxZ][ERROR][test-file] error",
+ "[xxxx-xx-xxTxx:xx:xx.xxx-xx:xx][INFO ][test-file] info",
+ "[xxxx-xx-xxTxx:xx:xx.xxx-xx:xx][WARN ][test-file] warn",
+ "[xxxx-xx-xxTxx:xx:xx.xxx-xx:xx][ERROR][test-file] error",
]
`);
diff --git a/src/core/server/logging/README.md b/src/core/server/logging/README.md
index b0759defb8803..9e3da1f3e0d71 100644
--- a/src/core/server/logging/README.md
+++ b/src/core/server/logging/README.md
@@ -110,7 +110,8 @@ Example of `%meta` output:
##### date
Outputs the date of the logging event. The date conversion specifier may be followed by a set of braces containing a name of predefined date format and canonical timezone name.
-Timezone name is expected to be one from [TZ database name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
+Timezone name is expected to be one from [TZ database name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
+Timezone defaults to the host timezone when not explicitly specified.
Example of `%date` output:
| Conversion pattern | Example |
@@ -410,22 +411,22 @@ loggerWithNestedContext.debug('Message with `debug` log level.');
And assuming logger for `server` context with `console` appender and `trace` level was used, console output will look like this:
```bash
-[2017-07-25T18:54:41.639Z][TRACE][server] Message with `trace` log level.
-[2017-07-25T18:54:41.639Z][DEBUG][server] Message with `debug` log level.
-[2017-07-25T18:54:41.639Z][INFO ][server] Message with `info` log level.
-[2017-07-25T18:54:41.639Z][WARN ][server] Message with `warn` log level.
-[2017-07-25T18:54:41.639Z][ERROR][server] Message with `error` log level.
-[2017-07-25T18:54:41.639Z][FATAL][server] Message with `fatal` log level.
-
-[2017-07-25T18:54:41.639Z][TRACE][server.http] Message with `trace` log level.
-[2017-07-25T18:54:41.639Z][DEBUG][server.http] Message with `debug` log level.
+[2017-07-25T11:54:41.639-07:00][TRACE][server] Message with `trace` log level.
+[2017-07-25T11:54:41.639-07:00][DEBUG][server] Message with `debug` log level.
+[2017-07-25T11:54:41.639-07:00][INFO ][server] Message with `info` log level.
+[2017-07-25T11:54:41.639-07:00][WARN ][server] Message with `warn` log level.
+[2017-07-25T11:54:41.639-07:00][ERROR][server] Message with `error` log level.
+[2017-07-25T11:54:41.639-07:00][FATAL][server] Message with `fatal` log level.
+
+[2017-07-25T11:54:41.639-07:00][TRACE][server.http] Message with `trace` log level.
+[2017-07-25T11:54:41.639-07:00][DEBUG][server.http] Message with `debug` log level.
```
The log will be less verbose with `warn` level for the `server` context:
```bash
-[2017-07-25T18:54:41.639Z][WARN ][server] Message with `warn` log level.
-[2017-07-25T18:54:41.639Z][ERROR][server] Message with `error` log level.
-[2017-07-25T18:54:41.639Z][FATAL][server] Message with `fatal` log level.
+[2017-07-25T11:54:41.639-07:00][WARN ][server] Message with `warn` log level.
+[2017-07-25T11:54:41.639-07:00][ERROR][server] Message with `error` log level.
+[2017-07-25T11:54:41.639-07:00][FATAL][server] Message with `fatal` log level.
```
### Logging config migration
@@ -488,7 +489,7 @@ logging.root.level: all
#### logging.timezone
Set to the canonical timezone id to log events using that timezone. New logging config allows
-to [specify timezone](#date) for `layout: pattern`.
+to [specify timezone](#date) for `layout: pattern`. Defaults to host timezone when not specified.
```yaml
logging:
appenders:
@@ -530,7 +531,7 @@ TBD
| Parameter | Platform log record in **pattern** format | Legacy Platform log record **text** format |
| --------------- | ------------------------------------------ | ------------------------------------------ |
-| @timestamp | ISO8601 `2012-01-31T23:33:22.011Z` | Absolute `23:33:22.011` |
+| @timestamp | ISO8601_TZ `2012-01-31T23:33:22.011-05:00` | Absolute `23:33:22.011` |
| context | `parent.child` | `['parent', 'child']` |
| level | `DEBUG` | `['debug']` |
| meta | stringified JSON object `{"to": "v8"}` | N/A |
diff --git a/src/core/server/logging/__snapshots__/logging_system.test.ts.snap b/src/core/server/logging/__snapshots__/logging_system.test.ts.snap
index cbe0e352a0f3a..8013aec4a06fd 100644
--- a/src/core/server/logging/__snapshots__/logging_system.test.ts.snap
+++ b/src/core/server/logging/__snapshots__/logging_system.test.ts.snap
@@ -1,20 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`appends records via multiple appenders.: console logs 1`] = `"[2012-01-31T23:33:22.011Z][INFO ][some-context] You know, just for your info."`;
+exports[`appends records via multiple appenders.: console logs 1`] = `"[2012-01-31T18:33:22.011-05:00][INFO ][some-context] You know, just for your info."`;
exports[`appends records via multiple appenders.: file logs 1`] = `
-"[2012-01-31T23:33:22.011Z][WARN ][tests] Config is not ready!
+"[2012-01-31T13:33:22.011-05:00][WARN ][tests] Config is not ready!
"
`;
exports[`appends records via multiple appenders.: file logs 2`] = `
-"[2012-01-31T23:33:22.011Z][ERROR][tests.child] Too bad that config is not ready :/
+"[2012-01-31T08:33:22.011-05:00][ERROR][tests.child] Too bad that config is not ready :/
"
`;
exports[`asLoggerFactory() only allows to create new loggers. 1`] = `
Object {
- "@timestamp": "2012-01-31T18:33:22.011-05:00",
+ "@timestamp": "2012-01-30T22:33:22.011-05:00",
"log": Object {
"level": "TRACE",
"logger": "test.context",
@@ -28,7 +28,7 @@ Object {
exports[`asLoggerFactory() only allows to create new loggers. 2`] = `
Object {
- "@timestamp": "2012-01-31T13:33:22.011-05:00",
+ "@timestamp": "2012-01-30T17:33:22.011-05:00",
"log": Object {
"level": "INFO",
"logger": "test.context",
@@ -43,7 +43,7 @@ Object {
exports[`asLoggerFactory() only allows to create new loggers. 3`] = `
Object {
- "@timestamp": "2012-01-31T08:33:22.011-05:00",
+ "@timestamp": "2012-01-30T12:33:22.011-05:00",
"log": Object {
"level": "FATAL",
"logger": "test.context",
@@ -87,7 +87,7 @@ Object {
exports[`uses \`root\` logger if context is not specified. 1`] = `
Array [
Array [
- "[2012-01-31T23:33:22.011Z][INFO ][root] This message goes to a root context.",
+ "[2012-01-31T03:33:22.011-05:00][INFO ][root] This message goes to a root context.",
],
]
`;
diff --git a/src/core/server/logging/layouts/__snapshots__/pattern_layout.test.ts.snap b/src/core/server/logging/layouts/__snapshots__/pattern_layout.test.ts.snap
index 8988f3019d509..54e46ca7f520e 100644
--- a/src/core/server/logging/layouts/__snapshots__/pattern_layout.test.ts.snap
+++ b/src/core/server/logging/layouts/__snapshots__/pattern_layout.test.ts.snap
@@ -12,29 +12,29 @@ exports[`\`format()\` correctly formats record with custom pattern. 5`] = `"mock
exports[`\`format()\` correctly formats record with custom pattern. 6`] = `"mock-message-6-context-6-message-6"`;
-exports[`\`format()\` correctly formats record with full pattern. 1`] = `"[2012-02-01T14:30:22.011Z][FATAL][context-1] Some error stack"`;
+exports[`\`format()\` correctly formats record with full pattern. 1`] = `"[2012-02-01T09:30:22.011-05:00][FATAL][context-1] Some error stack"`;
-exports[`\`format()\` correctly formats record with full pattern. 2`] = `"[2012-02-01T14:30:22.011Z][ERROR][context-2] message-2"`;
+exports[`\`format()\` correctly formats record with full pattern. 2`] = `"[2012-02-01T09:30:22.011-05:00][ERROR][context-2] message-2"`;
-exports[`\`format()\` correctly formats record with full pattern. 3`] = `"[2012-02-01T14:30:22.011Z][WARN ][context-3] message-3"`;
+exports[`\`format()\` correctly formats record with full pattern. 3`] = `"[2012-02-01T09:30:22.011-05:00][WARN ][context-3] message-3"`;
-exports[`\`format()\` correctly formats record with full pattern. 4`] = `"[2012-02-01T14:30:22.011Z][DEBUG][context-4] message-4"`;
+exports[`\`format()\` correctly formats record with full pattern. 4`] = `"[2012-02-01T09:30:22.011-05:00][DEBUG][context-4] message-4"`;
-exports[`\`format()\` correctly formats record with full pattern. 5`] = `"[2012-02-01T14:30:22.011Z][INFO ][context-5] message-5"`;
+exports[`\`format()\` correctly formats record with full pattern. 5`] = `"[2012-02-01T09:30:22.011-05:00][INFO ][context-5] message-5"`;
-exports[`\`format()\` correctly formats record with full pattern. 6`] = `"[2012-02-01T14:30:22.011Z][TRACE][context-6] message-6"`;
+exports[`\`format()\` correctly formats record with full pattern. 6`] = `"[2012-02-01T09:30:22.011-05:00][TRACE][context-6] message-6"`;
-exports[`\`format()\` correctly formats record with highlighting. 1`] = `[2012-02-01T14:30:22.011Z][FATAL][context-1] Some error stack`;
+exports[`\`format()\` correctly formats record with highlighting. 1`] = `[2012-02-01T09:30:22.011-05:00][FATAL][context-1] Some error stack`;
-exports[`\`format()\` correctly formats record with highlighting. 2`] = `[2012-02-01T14:30:22.011Z][ERROR][context-2] message-2`;
+exports[`\`format()\` correctly formats record with highlighting. 2`] = `[2012-02-01T09:30:22.011-05:00][ERROR][context-2] message-2`;
-exports[`\`format()\` correctly formats record with highlighting. 3`] = `[2012-02-01T14:30:22.011Z][WARN ][context-3] message-3`;
+exports[`\`format()\` correctly formats record with highlighting. 3`] = `[2012-02-01T09:30:22.011-05:00][WARN ][context-3] message-3`;
-exports[`\`format()\` correctly formats record with highlighting. 4`] = `[2012-02-01T14:30:22.011Z][DEBUG][context-4] message-4`;
+exports[`\`format()\` correctly formats record with highlighting. 4`] = `[2012-02-01T09:30:22.011-05:00][DEBUG][context-4] message-4`;
-exports[`\`format()\` correctly formats record with highlighting. 5`] = `[2012-02-01T14:30:22.011Z][INFO ][context-5] message-5`;
+exports[`\`format()\` correctly formats record with highlighting. 5`] = `[2012-02-01T09:30:22.011-05:00][INFO ][context-5] message-5`;
-exports[`\`format()\` correctly formats record with highlighting. 6`] = `[2012-02-01T14:30:22.011Z][TRACE][context-6] message-6`;
+exports[`\`format()\` correctly formats record with highlighting. 6`] = `[2012-02-01T09:30:22.011-05:00][TRACE][context-6] message-6`;
exports[`allows specifying the PID in custom pattern 1`] = `"5355-context-1-Some error stack"`;
diff --git a/src/core/server/logging/layouts/conversions/date.ts b/src/core/server/logging/layouts/conversions/date.ts
index c1f871282c5de..66aad5b42354a 100644
--- a/src/core/server/logging/layouts/conversions/date.ts
+++ b/src/core/server/logging/layouts/conversions/date.ts
@@ -22,11 +22,14 @@ const formats = {
UNIX_MILLIS: 'UNIX_MILLIS',
};
-function formatDate(date: Date, dateFormat: string = formats.ISO8601, timezone?: string): string {
+function formatDate(
+ date: Date,
+ dateFormat: string = formats.ISO8601_TZ,
+ timezone?: string
+): string {
const momentDate = moment(date);
- if (timezone) {
- momentDate.tz(timezone);
- }
+ momentDate.tz(timezone ?? moment.tz.guess());
+
switch (dateFormat) {
case formats.ISO8601:
return momentDate.toISOString();
diff --git a/src/core/server/logging/layouts/pattern_layout.test.ts b/src/core/server/logging/layouts/pattern_layout.test.ts
index d291516524be0..7dd3c7c51f833 100644
--- a/src/core/server/logging/layouts/pattern_layout.test.ts
+++ b/src/core/server/logging/layouts/pattern_layout.test.ts
@@ -122,7 +122,9 @@ test('`format()` correctly formats record with meta data.', () => {
to: 'v8',
},
})
- ).toBe('[2012-02-01T14:30:22.011Z][DEBUG][context-meta]{"from":"v7","to":"v8"} message-meta');
+ ).toBe(
+ '[2012-02-01T09:30:22.011-05:00][DEBUG][context-meta]{"from":"v7","to":"v8"} message-meta'
+ );
expect(
layout.format({
@@ -133,7 +135,7 @@ test('`format()` correctly formats record with meta data.', () => {
pid: 5355,
meta: {},
})
- ).toBe('[2012-02-01T14:30:22.011Z][DEBUG][context-meta]{} message-meta');
+ ).toBe('[2012-02-01T09:30:22.011-05:00][DEBUG][context-meta]{} message-meta');
expect(
layout.format({
@@ -143,7 +145,7 @@ test('`format()` correctly formats record with meta data.', () => {
timestamp,
pid: 5355,
})
- ).toBe('[2012-02-01T14:30:22.011Z][DEBUG][context-meta] message-meta');
+ ).toBe('[2012-02-01T09:30:22.011-05:00][DEBUG][context-meta] message-meta');
});
test('`format()` correctly formats record with highlighting.', () => {
@@ -187,10 +189,10 @@ describe('format', () => {
timestamp,
pid: 5355,
};
- it('uses ISO8601 as default', () => {
+ it('uses ISO8601_TZ as default', () => {
const layout = new PatternLayout();
- expect(layout.format(record)).toBe('[2012-02-01T14:30:22.011Z][DEBUG][context] message');
+ expect(layout.format(record)).toBe('[2012-02-01T09:30:22.011-05:00][DEBUG][context] message');
});
describe('supports specifying a predefined format', () => {
diff --git a/src/dev/code_coverage/shell_scripts/extract_archives.sh b/src/dev/code_coverage/shell_scripts/extract_archives.sh
index 376467f9f2e55..14b35f8786d02 100644
--- a/src/dev/code_coverage/shell_scripts/extract_archives.sh
+++ b/src/dev/code_coverage/shell_scripts/extract_archives.sh
@@ -6,7 +6,7 @@ EXTRACT_DIR=/tmp/extracted_coverage
mkdir -p $EXTRACT_DIR
echo "### Extracting downloaded artifacts"
-for x in kibana-intake x-pack-intake kibana-oss-tests kibana-xpack-tests; do
+for x in kibana-intake kibana-oss-tests kibana-xpack-tests; do
tar -xzf $DOWNLOAD_DIR/coverage/${x}/kibana-coverage.tar.gz -C $EXTRACT_DIR || echo "### Error 'tarring': ${x}"
done
diff --git a/src/dev/typescript/build_refs.ts b/src/dev/typescript/build_refs.ts
index ff6a81843972c..77d6eb2abc612 100644
--- a/src/dev/typescript/build_refs.ts
+++ b/src/dev/typescript/build_refs.ts
@@ -7,12 +7,10 @@
*/
import execa from 'execa';
-import Path from 'path';
import { run, ToolingLog } from '@kbn/dev-utils';
export async function buildAllRefs(log: ToolingLog) {
await buildRefs(log, 'tsconfig.refs.json');
- await buildRefs(log, Path.join('x-pack', 'tsconfig.refs.json'));
}
async function buildRefs(log: ToolingLog, projectPath: string) {
diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx
index ee731db0ced65..c7d5db970db42 100644
--- a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx
+++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx
@@ -49,11 +49,16 @@ function makeDefaultServices(): DashboardAppServices {
hits,
});
};
+ const dashboardPanelStorage = ({
+ getDashboardIdsWithUnsavedChanges: jest
+ .fn()
+ .mockResolvedValue(['dashboardUnsavedOne', 'dashboardUnsavedTwo']),
+ } as unknown) as DashboardPanelStorage;
+
return {
savedObjects: savedObjectsPluginMock.createStartContract(),
embeddable: embeddablePluginMock.createInstance().doStart(),
dashboardCapabilities: {} as DashboardCapabilities,
- dashboardPanelStorage: {} as DashboardPanelStorage,
initializerContext: {} as PluginInitializerContext,
chrome: chromeServiceMock.createStartContract(),
navigation: {} as NavigationPublicPluginStart,
@@ -68,6 +73,7 @@ function makeDefaultServices(): DashboardAppServices {
restorePreviousUrl: () => {},
onAppLeave: (handler) => {},
allowByValueEmbeddables: true,
+ dashboardPanelStorage,
savedDashboards,
core,
};
diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx
index 24d08ad06cc3b..c12385a29c4ec 100644
--- a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx
+++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx
@@ -8,7 +8,7 @@
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiLink, EuiButton, EuiEmptyPrompt } from '@elastic/eui';
-import React, { Fragment, useCallback, useEffect, useMemo } from 'react';
+import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react';
import { attemptLoadDashboardByTitle } from '../lib';
import { DashboardAppServices, DashboardRedirect } from '../types';
import { getDashboardBreadcrumb, dashboardListingTable } from '../../dashboard_strings';
@@ -48,6 +48,10 @@ export const DashboardListing = ({
},
} = useKibana();
+ const [unsavedDashboardIds, setUnsavedDashboardIds] = useState(
+ dashboardPanelStorage.getDashboardIdsWithUnsavedChanges()
+ );
+
// Set breadcrumbs useEffect
useEffect(() => {
setBreadcrumbs([
@@ -135,8 +139,12 @@ export const DashboardListing = ({
);
const deleteItems = useCallback(
- (dashboards: Array<{ id: string }>) => savedDashboards.delete(dashboards.map((d) => d.id)),
- [savedDashboards]
+ (dashboards: Array<{ id: string }>) => {
+ dashboards.map((d) => dashboardPanelStorage.clearPanels(d.id));
+ setUnsavedDashboardIds(dashboardPanelStorage.getDashboardIdsWithUnsavedChanges());
+ return savedDashboards.delete(dashboards.map((d) => d.id));
+ },
+ [savedDashboards, dashboardPanelStorage]
);
const editItem = useCallback(
@@ -179,7 +187,13 @@ export const DashboardListing = ({
tableColumns,
}}
>
-
+
+ setUnsavedDashboardIds(dashboardPanelStorage.getDashboardIdsWithUnsavedChanges())
+ }
+ />
);
};
diff --git a/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.test.tsx b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.test.tsx
index 119b2d559b68a..13688b4061be9 100644
--- a/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.test.tsx
+++ b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.test.tsx
@@ -17,8 +17,8 @@ import { KibanaContextProvider } from '../../services/kibana_react';
import { SavedObjectLoader } from '../../services/saved_objects';
import { DashboardPanelStorage } from '../lib';
import { DASHBOARD_PANELS_UNSAVED_ID } from '../lib/dashboard_panel_storage';
-import { DashboardAppServices, DashboardRedirect } from '../types';
-import { DashboardUnsavedListing } from './dashboard_unsaved_listing';
+import { DashboardAppServices } from '../types';
+import { DashboardUnsavedListing, DashboardUnsavedListingProps } from './dashboard_unsaved_listing';
const mockedDashboards: { [key: string]: DashboardSavedObject } = {
dashboardUnsavedOne: {
@@ -39,16 +39,11 @@ function makeDefaultServices(): DashboardAppServices {
const core = coreMock.createStart();
core.overlays.openConfirm = jest.fn().mockResolvedValue(true);
const savedDashboards = {} as SavedObjectLoader;
- savedDashboards.get = jest.fn().mockImplementation((id: string) => mockedDashboards[id]);
+ savedDashboards.get = jest
+ .fn()
+ .mockImplementation((id: string) => Promise.resolve(mockedDashboards[id]));
const dashboardPanelStorage = {} as DashboardPanelStorage;
dashboardPanelStorage.clearPanels = jest.fn();
- dashboardPanelStorage.getDashboardIdsWithUnsavedChanges = jest
- .fn()
- .mockImplementation(() => [
- 'dashboardUnsavedOne',
- 'dashboardUnsavedTwo',
- 'dashboardUnsavedThree',
- ]);
return ({
dashboardPanelStorage,
savedDashboards,
@@ -56,14 +51,18 @@ function makeDefaultServices(): DashboardAppServices {
} as unknown) as DashboardAppServices;
}
-const makeDefaultProps = () => ({ redirectTo: jest.fn() });
+const makeDefaultProps = (): DashboardUnsavedListingProps => ({
+ redirectTo: jest.fn(),
+ unsavedDashboardIds: ['dashboardUnsavedOne', 'dashboardUnsavedTwo', 'dashboardUnsavedThree'],
+ refreshUnsavedDashboards: jest.fn(),
+});
function mountWith({
services: incomingServices,
props: incomingProps,
}: {
services?: DashboardAppServices;
- props?: { redirectTo: DashboardRedirect };
+ props?: DashboardUnsavedListingProps;
}) {
const services = incomingServices ?? makeDefaultServices();
const props = incomingProps ?? makeDefaultProps();
@@ -89,11 +88,9 @@ describe('Unsaved listing', () => {
});
it('Does not attempt to get unsaved dashboard id', async () => {
- const services = makeDefaultServices();
- services.dashboardPanelStorage.getDashboardIdsWithUnsavedChanges = jest
- .fn()
- .mockImplementation(() => ['dashboardUnsavedOne', DASHBOARD_PANELS_UNSAVED_ID]);
- mountWith({ services });
+ const props = makeDefaultProps();
+ props.unsavedDashboardIds = ['dashboardUnsavedOne', DASHBOARD_PANELS_UNSAVED_ID];
+ const { services } = mountWith({ props });
await waitFor(() => {
expect(services.savedDashboards.get).toHaveBeenCalledTimes(1);
});
@@ -115,11 +112,9 @@ describe('Unsaved listing', () => {
});
it('Redirects to new dashboard when continue editing clicked', async () => {
- const services = makeDefaultServices();
- services.dashboardPanelStorage.getDashboardIdsWithUnsavedChanges = jest
- .fn()
- .mockImplementation(() => [DASHBOARD_PANELS_UNSAVED_ID]);
- const { props, component } = mountWith({ services });
+ const props = makeDefaultProps();
+ props.unsavedDashboardIds = [DASHBOARD_PANELS_UNSAVED_ID];
+ const { component } = mountWith({ props });
const getEditButton = () => findTestSubject(component, `edit-unsaved-New-Dashboard`);
await waitFor(() => {
component.update();
@@ -150,4 +145,34 @@ describe('Unsaved listing', () => {
);
});
});
+
+ it('removes unsaved changes from any dashboard which errors on fetch', async () => {
+ const services = makeDefaultServices();
+ const props = makeDefaultProps();
+ services.savedDashboards.get = jest.fn().mockImplementation((id: string) => {
+ if (id === 'failCase1' || id === 'failCase2') {
+ return Promise.reject(new Error());
+ }
+ return Promise.resolve(mockedDashboards[id]);
+ });
+
+ props.unsavedDashboardIds = [
+ 'dashboardUnsavedOne',
+ 'dashboardUnsavedTwo',
+ 'dashboardUnsavedThree',
+ 'failCase1',
+ 'failCase2',
+ ];
+ const { component } = mountWith({ services, props });
+ waitFor(() => {
+ component.update();
+ expect(services.dashboardPanelStorage.clearPanels).toHaveBeenCalledWith('failCase1');
+ expect(services.dashboardPanelStorage.clearPanels).toHaveBeenCalledWith('failCase2');
+
+ // clearing panels from dashboard with errors should cause getDashboardIdsWithUnsavedChanges to be called again.
+ expect(
+ services.dashboardPanelStorage.getDashboardIdsWithUnsavedChanges
+ ).toHaveBeenCalledTimes(2);
+ });
+ });
});
diff --git a/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx
index d7b9564d9d1e3..db50cfb638d64 100644
--- a/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx
+++ b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx
@@ -106,7 +106,17 @@ interface UnsavedItemMap {
[key: string]: DashboardSavedObject;
}
-export const DashboardUnsavedListing = ({ redirectTo }: { redirectTo: DashboardRedirect }) => {
+export interface DashboardUnsavedListingProps {
+ refreshUnsavedDashboards: () => void;
+ redirectTo: DashboardRedirect;
+ unsavedDashboardIds: string[];
+}
+
+export const DashboardUnsavedListing = ({
+ redirectTo,
+ unsavedDashboardIds,
+ refreshUnsavedDashboards,
+}: DashboardUnsavedListingProps) => {
const {
services: {
dashboardPanelStorage,
@@ -116,9 +126,6 @@ export const DashboardUnsavedListing = ({ redirectTo }: { redirectTo: DashboardR
} = useKibana();
const [items, setItems] = useState({});
- const [dashboardIds, setDashboardIds] = useState(
- dashboardPanelStorage.getDashboardIdsWithUnsavedChanges()
- );
const onOpen = useCallback(
(id?: string) => {
@@ -133,48 +140,63 @@ export const DashboardUnsavedListing = ({ redirectTo }: { redirectTo: DashboardR
overlays,
() => {
dashboardPanelStorage.clearPanels(id);
- setDashboardIds(dashboardPanelStorage.getDashboardIdsWithUnsavedChanges());
+ refreshUnsavedDashboards();
},
createConfirmStrings.getCancelButtonText()
);
},
- [overlays, dashboardPanelStorage]
+ [overlays, refreshUnsavedDashboards, dashboardPanelStorage]
);
useEffect(() => {
- if (dashboardIds?.length === 0) {
+ if (unsavedDashboardIds?.length === 0) {
return;
}
let canceled = false;
- const dashPromises = dashboardIds
+ const dashPromises = unsavedDashboardIds
.filter((id) => id !== DASHBOARD_PANELS_UNSAVED_ID)
- .map((dashboardId) => savedDashboards.get(dashboardId));
- Promise.all(dashPromises).then((dashboards: DashboardSavedObject[]) => {
+ .map((dashboardId) => {
+ return (savedDashboards.get(dashboardId) as Promise).catch(
+ () => dashboardId
+ );
+ });
+ Promise.all(dashPromises).then((dashboards: Array) => {
const dashboardMap = {};
if (canceled) {
return;
}
- setItems(
- dashboards.reduce((map, dashboard) => {
- return {
- ...map,
- [dashboard.id || DASHBOARD_PANELS_UNSAVED_ID]: dashboard,
- };
- }, dashboardMap)
- );
+ let hasError = false;
+ const newItems = dashboards.reduce((map, dashboard) => {
+ if (typeof dashboard === 'string') {
+ hasError = true;
+ dashboardPanelStorage.clearPanels(dashboard);
+ return map;
+ }
+ return {
+ ...map,
+ [dashboard.id || DASHBOARD_PANELS_UNSAVED_ID]: dashboard,
+ };
+ }, dashboardMap);
+ if (hasError) {
+ refreshUnsavedDashboards();
+ return;
+ }
+ setItems(newItems);
});
return () => {
canceled = true;
};
- }, [dashboardIds, savedDashboards]);
+ }, [savedDashboards, dashboardPanelStorage, refreshUnsavedDashboards, unsavedDashboardIds]);
- return dashboardIds.length === 0 ? null : (
+ return unsavedDashboardIds.length === 0 ? null : (
<>
1)}
+ title={dashboardUnsavedListingStrings.getUnsavedChangesTitle(
+ unsavedDashboardIds.length > 1
+ )}
>
- {dashboardIds.map((dashboardId: string) => {
+ {unsavedDashboardIds.map((dashboardId: string) => {
const title: string | undefined =
dashboardId === DASHBOARD_PANELS_UNSAVED_ID
? getNewDashboardTitle()
diff --git a/src/plugins/data/common/search/tabify/__snapshots__/tabify_docs.test.ts.snap b/src/plugins/data/common/search/tabify/__snapshots__/tabify_docs.test.ts.snap
index 6cc191a67633c..22276335a0599 100644
--- a/src/plugins/data/common/search/tabify/__snapshots__/tabify_docs.test.ts.snap
+++ b/src/plugins/data/common/search/tabify/__snapshots__/tabify_docs.test.ts.snap
@@ -1,5 +1,113 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`tabifyDocs combines meta fields if meta option is set 1`] = `
+Object {
+ "columns": Array [
+ Object {
+ "id": "fieldTest",
+ "meta": Object {
+ "field": "fieldTest",
+ "index": "test-index",
+ "params": Object {
+ "id": "number",
+ },
+ "type": "number",
+ },
+ "name": "fieldTest",
+ },
+ Object {
+ "id": "invalidMapping",
+ "meta": Object {
+ "field": "invalidMapping",
+ "index": "test-index",
+ "params": undefined,
+ "type": "number",
+ },
+ "name": "invalidMapping",
+ },
+ Object {
+ "id": "nested",
+ "meta": Object {
+ "field": "nested",
+ "index": "test-index",
+ "params": undefined,
+ "type": "object",
+ },
+ "name": "nested",
+ },
+ Object {
+ "id": "sourceTest",
+ "meta": Object {
+ "field": "sourceTest",
+ "index": "test-index",
+ "params": Object {
+ "id": "number",
+ },
+ "type": "number",
+ },
+ "name": "sourceTest",
+ },
+ Object {
+ "id": "_id",
+ "meta": Object {
+ "field": "_id",
+ "index": "test-index",
+ "params": undefined,
+ "type": "string",
+ },
+ "name": "_id",
+ },
+ Object {
+ "id": "_index",
+ "meta": Object {
+ "field": "_index",
+ "index": "test-index",
+ "params": undefined,
+ "type": "string",
+ },
+ "name": "_index",
+ },
+ Object {
+ "id": "_score",
+ "meta": Object {
+ "field": "_score",
+ "index": "test-index",
+ "params": undefined,
+ "type": "number",
+ },
+ "name": "_score",
+ },
+ Object {
+ "id": "_type",
+ "meta": Object {
+ "field": "_type",
+ "index": "test-index",
+ "params": undefined,
+ "type": "string",
+ },
+ "name": "_type",
+ },
+ ],
+ "rows": Array [
+ Object {
+ "_id": "hit-id-value",
+ "_index": "hit-index-value",
+ "_score": 77,
+ "_type": "hit-type-value",
+ "fieldTest": 123,
+ "invalidMapping": 345,
+ "nested": Array [
+ Object {
+ "field": 123,
+ },
+ ],
+ "sourceTest": 123,
+ },
+ ],
+ "type": "datatable",
+}
+`;
+
exports[`tabifyDocs converts fields by default 1`] = `
Object {
"columns": Array [
@@ -47,9 +155,53 @@ Object {
},
"name": "sourceTest",
},
+ Object {
+ "id": "_id",
+ "meta": Object {
+ "field": "_id",
+ "index": "test-index",
+ "params": undefined,
+ "type": "string",
+ },
+ "name": "_id",
+ },
+ Object {
+ "id": "_index",
+ "meta": Object {
+ "field": "_index",
+ "index": "test-index",
+ "params": undefined,
+ "type": "string",
+ },
+ "name": "_index",
+ },
+ Object {
+ "id": "_score",
+ "meta": Object {
+ "field": "_score",
+ "index": "test-index",
+ "params": undefined,
+ "type": "number",
+ },
+ "name": "_score",
+ },
+ Object {
+ "id": "_type",
+ "meta": Object {
+ "field": "_type",
+ "index": "test-index",
+ "params": undefined,
+ "type": "string",
+ },
+ "name": "_type",
+ },
],
"rows": Array [
Object {
+ "_id": "hit-id-value",
+ "_index": "hit-index-value",
+ "_score": 77,
+ "_type": "hit-type-value",
"fieldTest": 123,
"invalidMapping": 345,
"nested": Array [
@@ -111,9 +263,53 @@ Object {
},
"name": "sourceTest",
},
+ Object {
+ "id": "_id",
+ "meta": Object {
+ "field": "_id",
+ "index": "test-index",
+ "params": undefined,
+ "type": "string",
+ },
+ "name": "_id",
+ },
+ Object {
+ "id": "_index",
+ "meta": Object {
+ "field": "_index",
+ "index": "test-index",
+ "params": undefined,
+ "type": "string",
+ },
+ "name": "_index",
+ },
+ Object {
+ "id": "_score",
+ "meta": Object {
+ "field": "_score",
+ "index": "test-index",
+ "params": undefined,
+ "type": "number",
+ },
+ "name": "_score",
+ },
+ Object {
+ "id": "_type",
+ "meta": Object {
+ "field": "_type",
+ "index": "test-index",
+ "params": undefined,
+ "type": "string",
+ },
+ "name": "_type",
+ },
],
"rows": Array [
Object {
+ "_id": "hit-id-value",
+ "_index": "hit-index-value",
+ "_score": 77,
+ "_type": "hit-type-value",
"fieldTest": 123,
"invalidMapping": 345,
"nested": Array [
@@ -175,9 +371,53 @@ Object {
},
"name": "sourceTest",
},
+ Object {
+ "id": "_id",
+ "meta": Object {
+ "field": "_id",
+ "index": "test-index",
+ "params": undefined,
+ "type": "string",
+ },
+ "name": "_id",
+ },
+ Object {
+ "id": "_index",
+ "meta": Object {
+ "field": "_index",
+ "index": "test-index",
+ "params": undefined,
+ "type": "string",
+ },
+ "name": "_index",
+ },
+ Object {
+ "id": "_score",
+ "meta": Object {
+ "field": "_score",
+ "index": "test-index",
+ "params": undefined,
+ "type": "number",
+ },
+ "name": "_score",
+ },
+ Object {
+ "id": "_type",
+ "meta": Object {
+ "field": "_type",
+ "index": "test-index",
+ "params": undefined,
+ "type": "string",
+ },
+ "name": "_type",
+ },
],
"rows": Array [
Object {
+ "_id": "hit-id-value",
+ "_index": "hit-index-value",
+ "_score": 77,
+ "_type": "hit-type-value",
"fieldTest": 123,
"invalidMapping": 345,
"nested": Array [
@@ -235,9 +475,53 @@ Object {
},
"name": "sourceTest",
},
+ Object {
+ "id": "_id",
+ "meta": Object {
+ "field": "_id",
+ "index": undefined,
+ "params": undefined,
+ "type": "string",
+ },
+ "name": "_id",
+ },
+ Object {
+ "id": "_index",
+ "meta": Object {
+ "field": "_index",
+ "index": undefined,
+ "params": undefined,
+ "type": "string",
+ },
+ "name": "_index",
+ },
+ Object {
+ "id": "_score",
+ "meta": Object {
+ "field": "_score",
+ "index": undefined,
+ "params": undefined,
+ "type": "number",
+ },
+ "name": "_score",
+ },
+ Object {
+ "id": "_type",
+ "meta": Object {
+ "field": "_type",
+ "index": undefined,
+ "params": undefined,
+ "type": "string",
+ },
+ "name": "_type",
+ },
],
"rows": Array [
Object {
+ "_id": "hit-id-value",
+ "_index": "hit-index-value",
+ "_score": 77,
+ "_type": "hit-type-value",
"fieldTest": 123,
"invalidMapping": 345,
"nested": Array [
diff --git a/src/plugins/data/common/search/tabify/tabify_docs.test.ts b/src/plugins/data/common/search/tabify/tabify_docs.test.ts
index c81e39f4c156a..52e12aeee1ae6 100644
--- a/src/plugins/data/common/search/tabify/tabify_docs.test.ts
+++ b/src/plugins/data/common/search/tabify/tabify_docs.test.ts
@@ -37,6 +37,10 @@ describe('tabifyDocs', () => {
hits: {
hits: [
{
+ _id: 'hit-id-value',
+ _index: 'hit-index-value',
+ _type: 'hit-type-value',
+ _score: 77,
_source: { sourceTest: 123 },
fields: { fieldTest: 123, invalidMapping: 345, nested: [{ field: 123 }] },
},
@@ -59,6 +63,11 @@ describe('tabifyDocs', () => {
expect(table).toMatchSnapshot();
});
+ it('combines meta fields if meta option is set', () => {
+ const table = tabifyDocs(response, index, { meta: true });
+ expect(table).toMatchSnapshot();
+ });
+
it('works without provided index pattern', () => {
const table = tabifyDocs(response);
expect(table).toMatchSnapshot();
diff --git a/src/plugins/data/common/search/tabify/tabify_docs.ts b/src/plugins/data/common/search/tabify/tabify_docs.ts
index eaf43d9fd6ff6..b4806283e63f2 100644
--- a/src/plugins/data/common/search/tabify/tabify_docs.ts
+++ b/src/plugins/data/common/search/tabify/tabify_docs.ts
@@ -11,6 +11,12 @@ import { isPlainObject } from 'lodash';
import { IndexPattern } from '../../index_patterns/index_patterns';
import { Datatable, DatatableColumn, DatatableColumnType } from '../../../../expressions/common';
+export interface TabifyDocsOptions {
+ shallow?: boolean;
+ source?: boolean;
+ meta?: boolean;
+}
+
export function flattenHit(
hit: SearchResponse['hits']['hits'][0],
indexPattern?: IndexPattern,
@@ -56,12 +62,13 @@ export function flattenHit(
if (params?.source !== false && hit._source) {
flatten(hit._source as Record);
}
- return flat;
-}
+ if (params?.meta !== false) {
+ // combine the fields that Discover allows to add as columns
+ const { _id, _index, _type, _score } = hit;
+ flatten({ _id, _index, _score, _type });
+ }
-export interface TabifyDocsOptions {
- shallow?: boolean;
- source?: boolean;
+ return flat;
}
export const tabifyDocs = (
diff --git a/test/scripts/jenkins_unit.sh b/test/scripts/jenkins_unit.sh
index 6e28f9c3ef56a..9e387f97a016e 100755
--- a/test/scripts/jenkins_unit.sh
+++ b/test/scripts/jenkins_unit.sh
@@ -2,12 +2,6 @@
source test/scripts/jenkins_test_setup.sh
-rename_coverage_file() {
- test -f target/kibana-coverage/jest/coverage-final.json \
- && mv target/kibana-coverage/jest/coverage-final.json \
- target/kibana-coverage/jest/$1-coverage-final.json
-}
-
if [[ -z "$CODE_COVERAGE" ]] ; then
# Lint
./test/scripts/lint/eslint.sh
@@ -34,13 +28,8 @@ if [[ -z "$CODE_COVERAGE" ]] ; then
./test/scripts/checks/test_hardening.sh
else
echo " -> Running jest tests with coverage"
- node scripts/jest --ci --verbose --coverage --config jest.config.oss.js || true;
- rename_coverage_file "oss"
- echo ""
- echo ""
+ node scripts/jest --ci --verbose --maxWorkers=6 --coverage || true;
+
echo " -> Running jest integration tests with coverage"
- node --max-old-space-size=8192 scripts/jest_integration --ci --verbose --coverage || true;
- rename_coverage_file "oss-integration"
- echo ""
- echo ""
+ node scripts/jest_integration --ci --verbose --coverage || true;
fi
diff --git a/test/scripts/jenkins_xpack.sh b/test/scripts/jenkins_xpack.sh
deleted file mode 100755
index 66fb5ae5370bc..0000000000000
--- a/test/scripts/jenkins_xpack.sh
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env bash
-
-source test/scripts/jenkins_test_setup.sh
-
-if [[ -z "$CODE_COVERAGE" ]] ; then
- echo " -> Running jest tests"
-
- ./test/scripts/test/xpack_jest_unit.sh
-else
- echo " -> Build runtime for canvas"
- # build runtime for canvas
- echo "NODE_ENV=$NODE_ENV"
- node ./x-pack/plugins/canvas/scripts/shareable_runtime
- echo " -> Running jest tests with coverage"
- cd x-pack
- node --max-old-space-size=6144 scripts/jest --ci --verbose --maxWorkers=5 --coverage --config jest.config.js || true;
- # rename file in order to be unique one
- test -f ../target/kibana-coverage/jest/coverage-final.json \
- && mv ../target/kibana-coverage/jest/coverage-final.json \
- ../target/kibana-coverage/jest/xpack-coverage-final.json
- echo ""
- echo ""
-fi
diff --git a/test/scripts/test/jest_unit.sh b/test/scripts/test/jest_unit.sh
index 88c0fe528b88c..1442a0f728727 100755
--- a/test/scripts/test/jest_unit.sh
+++ b/test/scripts/test/jest_unit.sh
@@ -2,5 +2,7 @@
source src/dev/ci_setup/setup_env.sh
+export NODE_OPTIONS="--max-old-space-size=2048"
+
checks-reporter-with-killswitch "Jest Unit Tests" \
- node scripts/jest --config jest.config.oss.js --ci --verbose --maxWorkers=5
+ node scripts/jest --ci --verbose --maxWorkers=8
diff --git a/test/scripts/test/xpack_jest_unit.sh b/test/scripts/test/xpack_jest_unit.sh
deleted file mode 100755
index 33b1c8a2b5183..0000000000000
--- a/test/scripts/test/xpack_jest_unit.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/usr/bin/env bash
-
-source src/dev/ci_setup/setup_env.sh
-
-checks-reporter-with-killswitch "X-Pack Jest" \
- node scripts/jest x-pack --ci --verbose --maxWorkers=5
diff --git a/tsconfig.refs.json b/tsconfig.refs.json
index 17b1fc5dc1fe9..d5482a85856fe 100644
--- a/tsconfig.refs.json
+++ b/tsconfig.refs.json
@@ -55,5 +55,60 @@
{ "path": "./src/plugins/visualizations/tsconfig.json" },
{ "path": "./src/plugins/visualize/tsconfig.json" },
{ "path": "./src/plugins/index_pattern_management/tsconfig.json" },
+ { "path": "./x-pack/plugins/actions/tsconfig.json" },
+ { "path": "./x-pack/plugins/alerts/tsconfig.json" },
+ { "path": "./x-pack/plugins/beats_management/tsconfig.json" },
+ { "path": "./x-pack/plugins/canvas/tsconfig.json" },
+ { "path": "./x-pack/plugins/cloud/tsconfig.json" },
+ { "path": "./x-pack/plugins/code/tsconfig.json" },
+ { "path": "./x-pack/plugins/console_extensions/tsconfig.json" },
+ { "path": "./x-pack/plugins/dashboard_enhanced/tsconfig.json" },
+ { "path": "./x-pack/plugins/data_enhanced/tsconfig.json" },
+ { "path": "./x-pack/plugins/dashboard_mode/tsconfig.json" },
+ { "path": "./x-pack/plugins/discover_enhanced/tsconfig.json" },
+ { "path": "./x-pack/plugins/embeddable_enhanced/tsconfig.json" },
+ { "path": "./x-pack/plugins/encrypted_saved_objects/tsconfig.json" },
+ { "path": "./x-pack/plugins/enterprise_search/tsconfig.json" },
+ { "path": "./x-pack/plugins/event_log/tsconfig.json" },
+ { "path": "./x-pack/plugins/features/tsconfig.json" },
+ { "path": "./x-pack/plugins/file_upload/tsconfig.json" },
+ { "path": "./x-pack/plugins/fleet/tsconfig.json" },
+ { "path": "./x-pack/plugins/global_search_bar/tsconfig.json" },
+ { "path": "./x-pack/plugins/global_search_providers/tsconfig.json" },
+ { "path": "./x-pack/plugins/global_search/tsconfig.json" },
+ { "path": "./x-pack/plugins/graph/tsconfig.json" },
+ { "path": "./x-pack/plugins/grokdebugger/tsconfig.json" },
+ { "path": "./x-pack/plugins/infra/tsconfig.json" },
+ { "path": "./x-pack/plugins/ingest_pipelines/tsconfig.json" },
+ { "path": "./x-pack/plugins/lens/tsconfig.json" },
+ { "path": "./x-pack/plugins/license_management/tsconfig.json" },
+ { "path": "./x-pack/plugins/licensing/tsconfig.json" },
+ { "path": "./x-pack/plugins/maps_legacy_licensing/tsconfig.json" },
+ { "path": "./x-pack/plugins/maps/tsconfig.json" },
+ { "path": "./x-pack/plugins/ml/tsconfig.json" },
+ { "path": "./x-pack/plugins/observability/tsconfig.json" },
+ { "path": "./x-pack/plugins/painless_lab/tsconfig.json" },
+ { "path": "./x-pack/plugins/reporting/tsconfig.json" },
+ { "path": "./x-pack/plugins/saved_objects_tagging/tsconfig.json" },
+ { "path": "./x-pack/plugins/searchprofiler/tsconfig.json" },
+ { "path": "./x-pack/plugins/security/tsconfig.json" },
+ { "path": "./x-pack/plugins/snapshot_restore/tsconfig.json" },
+ { "path": "./x-pack/plugins/spaces/tsconfig.json" },
+ { "path": "./x-pack/plugins/stack_alerts/tsconfig.json" },
+ { "path": "./x-pack/plugins/task_manager/tsconfig.json" },
+ { "path": "./x-pack/plugins/telemetry_collection_xpack/tsconfig.json" },
+ { "path": "./x-pack/plugins/transform/tsconfig.json" },
+ { "path": "./x-pack/plugins/translations/tsconfig.json" },
+ { "path": "./x-pack/plugins/triggers_actions_ui/tsconfig.json" },
+ { "path": "./x-pack/plugins/ui_actions_enhanced/tsconfig.json" },
+ { "path": "./x-pack/plugins/upgrade_assistant/tsconfig.json" },
+ { "path": "./x-pack/plugins/runtime_fields/tsconfig.json" },
+ { "path": "./x-pack/plugins/index_management/tsconfig.json" },
+ { "path": "./x-pack/plugins/watcher/tsconfig.json" },
+ { "path": "./x-pack/plugins/rollup/tsconfig.json"},
+ { "path": "./x-pack/plugins/remote_clusters/tsconfig.json"},
+ { "path": "./x-pack/plugins/cross_cluster_replication/tsconfig.json"},
+ { "path": "./x-pack/plugins/index_lifecycle_management/tsconfig.json"},
+ { "path": "./x-pack/plugins/uptime/tsconfig.json" },
]
}
diff --git a/vars/kibanaCoverage.groovy b/vars/kibanaCoverage.groovy
index 609d8f78aeb96..e393f3a5d2150 100644
--- a/vars/kibanaCoverage.groovy
+++ b/vars/kibanaCoverage.groovy
@@ -197,13 +197,6 @@ def ingest(jobName, buildNumber, buildUrl, timestamp, previousSha, teamAssignmen
def runTests() {
parallel([
'kibana-intake-agent': workers.intake('kibana-intake', './test/scripts/jenkins_unit.sh'),
- 'x-pack-intake-agent': {
- withEnv([
- 'NODE_ENV=test' // Needed for jest tests only
- ]) {
- workers.intake('x-pack-intake', './test/scripts/jenkins_xpack.sh')()
- }
- },
'kibana-oss-agent' : workers.functional(
'kibana-oss-tests',
{ kibanaPipeline.buildOss() },
diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy
index 17349f6b566dc..5efcea3edb9bb 100644
--- a/vars/kibanaPipeline.groovy
+++ b/vars/kibanaPipeline.groovy
@@ -186,20 +186,21 @@ def uploadCoverageArtifacts(prefix, pattern) {
def withGcsArtifactUpload(workerName, closure) {
def uploadPrefix = "kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/${workerName}"
def ARTIFACT_PATTERNS = [
+ 'target/junit/**/*',
'target/kibana-*',
- 'target/test-metrics/*',
+ 'target/kibana-coverage/**/*',
'target/kibana-security-solution/**/*.png',
- 'target/junit/**/*',
+ 'target/test-metrics/*',
'target/test-suites-ci-plan.json',
- 'test/**/screenshots/session/*.png',
- 'test/**/screenshots/failure/*.png',
'test/**/screenshots/diff/*.png',
+ 'test/**/screenshots/failure/*.png',
+ 'test/**/screenshots/session/*.png',
'test/functional/failure_debug/html/*.html',
- 'x-pack/test/**/screenshots/session/*.png',
- 'x-pack/test/**/screenshots/failure/*.png',
'x-pack/test/**/screenshots/diff/*.png',
- 'x-pack/test/functional/failure_debug/html/*.html',
+ 'x-pack/test/**/screenshots/failure/*.png',
+ 'x-pack/test/**/screenshots/session/*.png',
'x-pack/test/functional/apps/reporting/reports/session/*.pdf',
+ 'x-pack/test/functional/failure_debug/html/*.html',
]
withEnv([
@@ -462,15 +463,10 @@ def allCiTasks() {
}
},
jest: {
- workers.ci(name: 'jest', size: 'c2-8', ramDisk: true) {
+ workers.ci(name: 'jest', size: 'n2-standard-16', ramDisk: false) {
scriptTask('Jest Unit Tests', 'test/scripts/test/jest_unit.sh')()
}
},
- xpackJest: {
- workers.ci(name: 'xpack-jest', size: 'c2-8', ramDisk: true) {
- scriptTask('X-Pack Jest Unit Tests', 'test/scripts/test/xpack_jest_unit.sh')()
- }
- },
])
}
diff --git a/vars/workers.groovy b/vars/workers.groovy
index e1684f7aadb43..5d3328bc8a3c4 100644
--- a/vars/workers.groovy
+++ b/vars/workers.groovy
@@ -19,8 +19,8 @@ def label(size) {
return 'docker && tests-xl-highmem'
case 'xxl':
return 'docker && tests-xxl && gobld/machineType:custom-64-270336'
- case 'c2-8':
- return 'docker && linux && immutable && gobld/machineType:c2-standard-8'
+ case 'n2-standard-16':
+ return 'docker && linux && immutable && gobld/machineType:n2-standard-16'
}
error "unknown size '${size}'"
diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json
index f95c4286b3f26..c09198b3874a1 100644
--- a/x-pack/.i18nrc.json
+++ b/x-pack/.i18nrc.json
@@ -20,7 +20,7 @@
"xpack.endpoint": "plugins/endpoint",
"xpack.enterpriseSearch": "plugins/enterprise_search",
"xpack.features": "plugins/features",
- "xpack.fileUpload": "plugins/maps_file_upload",
+ "xpack.fileUpload": "plugins/file_upload",
"xpack.globalSearch": ["plugins/global_search"],
"xpack.globalSearchBar": ["plugins/global_search_bar"],
"xpack.graph": ["plugins/graph"],
diff --git a/x-pack/jest.config.js b/x-pack/jest.config.js
deleted file mode 100644
index 231004359632b..0000000000000
--- a/x-pack/jest.config.js
+++ /dev/null
@@ -1,12 +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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-module.exports = {
- preset: '@kbn/test',
- rootDir: '..',
- projects: ['/x-pack/plugins/*/jest.config.js'],
-};
diff --git a/x-pack/plugins/actions/server/action_type_registry.test.ts b/x-pack/plugins/actions/server/action_type_registry.test.ts
index 813e47c2e9957..c8972d8113f16 100644
--- a/x-pack/plugins/actions/server/action_type_registry.test.ts
+++ b/x-pack/plugins/actions/server/action_type_registry.test.ts
@@ -26,9 +26,7 @@ beforeEach(() => {
actionTypeRegistryParams = {
licensing: licensingMock.createSetup(),
taskManager: mockTaskManager,
- taskRunnerFactory: new TaskRunnerFactory(
- new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
- ),
+ taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true })),
actionsConfigUtils: mockedActionsConfig,
licenseState: mockedLicenseState,
preconfiguredActions: [
diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts
index 1bea3e1fc356d..3bd8bb5f1ba52 100644
--- a/x-pack/plugins/actions/server/actions_client.test.ts
+++ b/x-pack/plugins/actions/server/actions_client.test.ts
@@ -59,9 +59,7 @@ beforeEach(() => {
actionTypeRegistryParams = {
licensing: licensingMock.createSetup(),
taskManager: mockTaskManager,
- taskRunnerFactory: new TaskRunnerFactory(
- new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
- ),
+ taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true })),
actionsConfigUtils: actionsConfigMock.create(),
licenseState: mockedLicenseState,
preconfiguredActions: [],
@@ -411,9 +409,7 @@ describe('create()', () => {
const localActionTypeRegistryParams = {
licensing: licensingMock.createSetup(),
taskManager: mockTaskManager,
- taskRunnerFactory: new TaskRunnerFactory(
- new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
- ),
+ taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true })),
actionsConfigUtils: localConfigUtils,
licenseState: licenseStateMock.create(),
preconfiguredActions: [],
diff --git a/x-pack/plugins/actions/server/builtin_action_types/index.test.ts b/x-pack/plugins/actions/server/builtin_action_types/index.test.ts
index bad709247d080..10955af2f3b13 100644
--- a/x-pack/plugins/actions/server/builtin_action_types/index.test.ts
+++ b/x-pack/plugins/actions/server/builtin_action_types/index.test.ts
@@ -33,9 +33,7 @@ export function createActionTypeRegistry(): {
const actionTypeRegistry = new ActionTypeRegistry({
taskManager: taskManagerMock.createSetup(),
licensing: licensingMock.createSetup(),
- taskRunnerFactory: new TaskRunnerFactory(
- new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
- ),
+ taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true })),
actionsConfigUtils: actionsConfigMock.create(),
licenseState: licenseStateMock.create(),
preconfiguredActions: [],
diff --git a/x-pack/plugins/actions/server/create_execute_function.test.ts b/x-pack/plugins/actions/server/create_execute_function.test.ts
index aaf11669c1d03..d4100537fa6b8 100644
--- a/x-pack/plugins/actions/server/create_execute_function.test.ts
+++ b/x-pack/plugins/actions/server/create_execute_function.test.ts
@@ -28,7 +28,7 @@ describe('execute()', () => {
const executeFn = createExecutionEnqueuerFunction({
taskManager: mockTaskManager,
actionTypeRegistry,
- isESOUsingEphemeralEncryptionKey: false,
+ isESOCanEncrypt: true,
preconfiguredActions: [],
});
savedObjectsClient.get.mockResolvedValueOnce({
@@ -87,7 +87,7 @@ describe('execute()', () => {
const executeFn = createExecutionEnqueuerFunction({
taskManager: mockTaskManager,
actionTypeRegistry: actionTypeRegistryMock.create(),
- isESOUsingEphemeralEncryptionKey: false,
+ isESOCanEncrypt: true,
preconfiguredActions: [
{
id: '123',
@@ -158,10 +158,10 @@ describe('execute()', () => {
);
});
- test('throws when passing isESOUsingEphemeralEncryptionKey with true as a value', async () => {
+ test('throws when passing isESOCanEncrypt with false as a value', async () => {
const executeFn = createExecutionEnqueuerFunction({
taskManager: mockTaskManager,
- isESOUsingEphemeralEncryptionKey: true,
+ isESOCanEncrypt: false,
actionTypeRegistry: actionTypeRegistryMock.create(),
preconfiguredActions: [],
});
@@ -173,7 +173,7 @@ describe('execute()', () => {
apiKey: null,
})
).rejects.toThrowErrorMatchingInlineSnapshot(
- `"Unable to execute action because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."`
+ `"Unable to execute action because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."`
);
});
@@ -181,7 +181,7 @@ describe('execute()', () => {
const mockedActionTypeRegistry = actionTypeRegistryMock.create();
const executeFn = createExecutionEnqueuerFunction({
taskManager: mockTaskManager,
- isESOUsingEphemeralEncryptionKey: false,
+ isESOCanEncrypt: true,
actionTypeRegistry: mockedActionTypeRegistry,
preconfiguredActions: [],
});
@@ -211,7 +211,7 @@ describe('execute()', () => {
const mockedActionTypeRegistry = actionTypeRegistryMock.create();
const executeFn = createExecutionEnqueuerFunction({
taskManager: mockTaskManager,
- isESOUsingEphemeralEncryptionKey: false,
+ isESOCanEncrypt: true,
actionTypeRegistry: mockedActionTypeRegistry,
preconfiguredActions: [
{
diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts
index 0d75c0b410e44..025b4d3107798 100644
--- a/x-pack/plugins/actions/server/create_execute_function.ts
+++ b/x-pack/plugins/actions/server/create_execute_function.ts
@@ -14,7 +14,7 @@ import { isSavedObjectExecutionSource } from './lib';
interface CreateExecuteFunctionOptions {
taskManager: TaskManagerStartContract;
- isESOUsingEphemeralEncryptionKey: boolean;
+ isESOCanEncrypt: boolean;
actionTypeRegistry: ActionTypeRegistryContract;
preconfiguredActions: PreConfiguredAction[];
}
@@ -33,16 +33,16 @@ export type ExecutionEnqueuer = (
export function createExecutionEnqueuerFunction({
taskManager,
actionTypeRegistry,
- isESOUsingEphemeralEncryptionKey,
+ isESOCanEncrypt,
preconfiguredActions,
}: CreateExecuteFunctionOptions) {
return async function execute(
unsecuredSavedObjectsClient: SavedObjectsClientContract,
{ id, params, spaceId, source, apiKey }: ExecuteOptions
) {
- if (isESOUsingEphemeralEncryptionKey === true) {
+ if (!isESOCanEncrypt) {
throw new Error(
- `Unable to execute action because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
+ `Unable to execute action because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
);
}
diff --git a/x-pack/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts
index e9b72f9bf0e4e..8ec94c4d4a552 100644
--- a/x-pack/plugins/actions/server/lib/action_executor.test.ts
+++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts
@@ -17,7 +17,7 @@ import { ActionType } from '../types';
import { actionsMock, actionsClientMock } from '../mocks';
import { pick } from 'lodash';
-const actionExecutor = new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false });
+const actionExecutor = new ActionExecutor({ isESOCanEncrypt: true });
const services = actionsMock.createServices();
const actionsClient = actionsClientMock.create();
@@ -310,8 +310,8 @@ test('should not throws an error if actionType is preconfigured', async () => {
});
});
-test('throws an error when passing isESOUsingEphemeralEncryptionKey with value of true', async () => {
- const customActionExecutor = new ActionExecutor({ isESOUsingEphemeralEncryptionKey: true });
+test('throws an error when passing isESOCanEncrypt with value of false', async () => {
+ const customActionExecutor = new ActionExecutor({ isESOCanEncrypt: false });
customActionExecutor.initialize({
logger: loggingSystemMock.create().get(),
spaces: spacesMock,
@@ -325,7 +325,7 @@ test('throws an error when passing isESOUsingEphemeralEncryptionKey with value o
await expect(
customActionExecutor.execute(executeParams)
).rejects.toThrowErrorMatchingInlineSnapshot(
- `"Unable to execute action because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."`
+ `"Unable to execute action because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."`
);
});
diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts
index 7a54f88e2f27c..6deaa4d587904 100644
--- a/x-pack/plugins/actions/server/lib/action_executor.ts
+++ b/x-pack/plugins/actions/server/lib/action_executor.ts
@@ -48,10 +48,10 @@ export type ActionExecutorContract = PublicMethodsOf;
export class ActionExecutor {
private isInitialized = false;
private actionExecutorContext?: ActionExecutorContext;
- private readonly isESOUsingEphemeralEncryptionKey: boolean;
+ private readonly isESOCanEncrypt: boolean;
- constructor({ isESOUsingEphemeralEncryptionKey }: { isESOUsingEphemeralEncryptionKey: boolean }) {
- this.isESOUsingEphemeralEncryptionKey = isESOUsingEphemeralEncryptionKey;
+ constructor({ isESOCanEncrypt }: { isESOCanEncrypt: boolean }) {
+ this.isESOCanEncrypt = isESOCanEncrypt;
}
public initialize(actionExecutorContext: ActionExecutorContext) {
@@ -72,9 +72,9 @@ export class ActionExecutor {
throw new Error('ActionExecutor not initialized');
}
- if (this.isESOUsingEphemeralEncryptionKey === true) {
+ if (!this.isESOCanEncrypt) {
throw new Error(
- `Unable to execute action because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
+ `Unable to execute action because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
);
}
diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts
index e42fc363f328b..9e101f2ee76b0 100644
--- a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts
+++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts
@@ -84,18 +84,14 @@ beforeEach(() => {
});
test(`throws an error if factory isn't initialized`, () => {
- const factory = new TaskRunnerFactory(
- new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
- );
+ const factory = new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true }));
expect(() =>
factory.create({ taskInstance: mockedTaskInstance })
).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory not initialized"`);
});
test(`throws an error if factory is already initialized`, () => {
- const factory = new TaskRunnerFactory(
- new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
- );
+ const factory = new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true }));
factory.initialize(taskRunnerFactoryInitializerParams);
expect(() =>
factory.initialize(taskRunnerFactoryInitializerParams)
diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts
index 187cba9d3240c..0e916220ca946 100644
--- a/x-pack/plugins/actions/server/plugin.test.ts
+++ b/x-pack/plugins/actions/server/plugin.test.ts
@@ -51,25 +51,21 @@ describe('Actions Plugin', () => {
};
});
- it('should log warning when Encrypted Saved Objects plugin is using an ephemeral encryption key', async () => {
- // coreMock.createSetup doesn't support Plugin generics
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- await plugin.setup(coreSetup as any, pluginsSetup);
- expect(pluginsSetup.encryptedSavedObjects.usingEphemeralEncryptionKey).toEqual(true);
+ it('should log warning when Encrypted Saved Objects plugin is missing encryption key', async () => {
+ await plugin.setup(coreSetup, pluginsSetup);
+ expect(pluginsSetup.encryptedSavedObjects.canEncrypt).toEqual(false);
expect(context.logger.get().warn).toHaveBeenCalledWith(
- 'APIs are disabled because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.'
+ 'APIs are disabled because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.'
);
});
describe('routeHandlerContext.getActionsClient()', () => {
- it('should not throw error when ESO plugin not using a generated key', async () => {
- // coreMock.createSetup doesn't support Plugin generics
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- await plugin.setup(coreSetup as any, {
+ it('should not throw error when ESO plugin has encryption key', async () => {
+ await plugin.setup(coreSetup, {
...pluginsSetup,
encryptedSavedObjects: {
...pluginsSetup.encryptedSavedObjects,
- usingEphemeralEncryptionKey: false,
+ canEncrypt: true,
},
});
@@ -99,10 +95,8 @@ describe('Actions Plugin', () => {
actionsContextHandler!.getActionsClient();
});
- it('should throw error when ESO plugin using a generated key', async () => {
- // coreMock.createSetup doesn't support Plugin generics
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- await plugin.setup(coreSetup as any, pluginsSetup);
+ it('should throw error when ESO plugin is missing encryption key', async () => {
+ await plugin.setup(coreSetup, pluginsSetup);
expect(coreSetup.http.registerRouteHandlerContext).toHaveBeenCalledTimes(1);
const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0] as [
@@ -123,7 +117,7 @@ describe('Actions Plugin', () => {
httpServerMock.createResponseFactory()
)) as unknown) as ActionsApiRequestHandlerContext;
expect(() => actionsContextHandler!.getActionsClient()).toThrowErrorMatchingInlineSnapshot(
- `"Unable to create actions client because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."`
+ `"Unable to create actions client because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."`
);
});
});
@@ -234,14 +228,12 @@ describe('Actions Plugin', () => {
expect(pluginStart.isActionExecutable('preconfiguredServerLog', '.server-log')).toBe(true);
});
- it('should not throw error when ESO plugin not using a generated key', async () => {
- // coreMock.createSetup doesn't support Plugin generics
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- await plugin.setup(coreSetup as any, {
+ it('should not throw error when ESO plugin has encryption key', async () => {
+ await plugin.setup(coreSetup, {
...pluginsSetup,
encryptedSavedObjects: {
...pluginsSetup.encryptedSavedObjects,
- usingEphemeralEncryptionKey: false,
+ canEncrypt: true,
},
});
const pluginStart = await plugin.start(coreStart, pluginsStart);
@@ -249,17 +241,15 @@ describe('Actions Plugin', () => {
await pluginStart.getActionsClientWithRequest(httpServerMock.createKibanaRequest());
});
- it('should throw error when ESO plugin using generated key', async () => {
- // coreMock.createSetup doesn't support Plugin generics
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- await plugin.setup(coreSetup as any, pluginsSetup);
+ it('should throw error when ESO plugin is missing encryption key', async () => {
+ await plugin.setup(coreSetup, pluginsSetup);
const pluginStart = await plugin.start(coreStart, pluginsStart);
- expect(pluginsSetup.encryptedSavedObjects.usingEphemeralEncryptionKey).toEqual(true);
+ expect(pluginsSetup.encryptedSavedObjects.canEncrypt).toEqual(false);
await expect(
pluginStart.getActionsClientWithRequest(httpServerMock.createKibanaRequest())
).rejects.toThrowErrorMatchingInlineSnapshot(
- `"Unable to create actions client because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."`
+ `"Unable to create actions client because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."`
);
});
});
diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts
index 8fbacc71d30cb..c4159c80e806f 100644
--- a/x-pack/plugins/actions/server/plugin.ts
+++ b/x-pack/plugins/actions/server/plugin.ts
@@ -144,7 +144,7 @@ export class ActionsPlugin implements Plugin
) => {
- if (isESOUsingEphemeralEncryptionKey === true) {
+ if (isESOCanEncrypt !== true) {
throw new Error(
- `Unable to create actions client because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
+ `Unable to create actions client because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
);
}
@@ -314,7 +313,7 @@ export class ActionsPlugin implements Plugin => {
const {
actionTypeRegistry,
- isESOUsingEphemeralEncryptionKey,
+ isESOCanEncrypt,
preconfiguredActions,
actionExecutor,
instantiateAuthorization,
@@ -448,9 +447,9 @@ export class ActionsPlugin implements Plugin {
- if (isESOUsingEphemeralEncryptionKey === true) {
+ if (isESOCanEncrypt !== true) {
throw new Error(
- `Unable to create actions client because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
+ `Unable to create actions client because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
);
}
return new ActionsClient({
@@ -468,7 +467,7 @@ export class ActionsPlugin implements Plugin {
let coreSetup: ReturnType;
let pluginsSetup: jest.Mocked;
- it('should log warning when Encrypted Saved Objects plugin is using an ephemeral encryption key', async () => {
+ it('should log warning when Encrypted Saved Objects plugin is missing encryption key', async () => {
const context = coreMock.createPluginInitializerContext({
healthCheck: {
interval: '5m',
@@ -40,7 +40,7 @@ describe('Alerting Plugin', () => {
const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup();
const setupMocks = coreMock.createSetup();
- // need await to test number of calls of setupMocks.status.set, becuase it is under async function which awaiting core.getStartServices()
+ // need await to test number of calls of setupMocks.status.set, because it is under async function which awaiting core.getStartServices()
await plugin.setup(setupMocks, {
licensing: licensingMock.createSetup(),
encryptedSavedObjects: encryptedSavedObjectsSetup,
@@ -51,9 +51,9 @@ describe('Alerting Plugin', () => {
});
expect(setupMocks.status.set).toHaveBeenCalledTimes(1);
- expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true);
+ expect(encryptedSavedObjectsSetup.canEncrypt).toEqual(false);
expect(context.logger.get().warn).toHaveBeenCalledWith(
- 'APIs are disabled because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.'
+ 'APIs are disabled because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.'
);
});
@@ -110,7 +110,7 @@ describe('Alerting Plugin', () => {
describe('start()', () => {
describe('getAlertsClientWithRequest()', () => {
- it('throws error when encryptedSavedObjects plugin has usingEphemeralEncryptionKey set to true', async () => {
+ it('throws error when encryptedSavedObjects plugin is missing encryption key', async () => {
const context = coreMock.createPluginInitializerContext({
healthCheck: {
interval: '5m',
@@ -141,15 +141,15 @@ describe('Alerting Plugin', () => {
taskManager: taskManagerMock.createStart(),
});
- expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true);
+ expect(encryptedSavedObjectsSetup.canEncrypt).toEqual(false);
expect(() =>
startContract.getAlertsClientWithRequest({} as KibanaRequest)
).toThrowErrorMatchingInlineSnapshot(
- `"Unable to create alerts client because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."`
+ `"Unable to create alerts client because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."`
);
});
- it(`doesn't throw error when encryptedSavedObjects plugin has usingEphemeralEncryptionKey set to false`, async () => {
+ it(`doesn't throw error when encryptedSavedObjects plugin has encryption key`, async () => {
const context = coreMock.createPluginInitializerContext({
healthCheck: {
interval: '5m',
@@ -163,7 +163,7 @@ describe('Alerting Plugin', () => {
const encryptedSavedObjectsSetup = {
...encryptedSavedObjectsMock.createSetup(),
- usingEphemeralEncryptionKey: false,
+ canEncrypt: true,
};
plugin.setup(coreMock.createSetup(), {
licensing: licensingMock.createSetup(),
diff --git a/x-pack/plugins/alerts/server/plugin.ts b/x-pack/plugins/alerts/server/plugin.ts
index aaec0bb8a080d..8dba4453d5682 100644
--- a/x-pack/plugins/alerts/server/plugin.ts
+++ b/x-pack/plugins/alerts/server/plugin.ts
@@ -153,7 +153,7 @@ export class AlertingPlugin {
private alertTypeRegistry?: AlertTypeRegistry;
private readonly taskRunnerFactory: TaskRunnerFactory;
private licenseState: ILicenseState | null = null;
- private isESOUsingEphemeralEncryptionKey?: boolean;
+ private isESOCanEncrypt?: boolean;
private security?: SecurityPluginSetup;
private readonly alertsClientFactory: AlertsClientFactory;
private readonly telemetryLogger: Logger;
@@ -189,12 +189,11 @@ export class AlertingPlugin {
};
});
- this.isESOUsingEphemeralEncryptionKey =
- plugins.encryptedSavedObjects.usingEphemeralEncryptionKey;
+ this.isESOCanEncrypt = plugins.encryptedSavedObjects.canEncrypt;
- if (this.isESOUsingEphemeralEncryptionKey) {
+ if (!this.isESOCanEncrypt) {
this.logger.warn(
- 'APIs are disabled because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.'
+ 'APIs are disabled because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.'
);
}
@@ -311,7 +310,7 @@ export class AlertingPlugin {
public start(core: CoreStart, plugins: AlertingPluginsStart): PluginStartContract {
const {
- isESOUsingEphemeralEncryptionKey,
+ isESOCanEncrypt,
logger,
taskRunnerFactory,
alertTypeRegistry,
@@ -353,9 +352,9 @@ export class AlertingPlugin {
});
const getAlertsClientWithRequest = (request: KibanaRequest) => {
- if (isESOUsingEphemeralEncryptionKey === true) {
+ if (isESOCanEncrypt !== true) {
throw new Error(
- `Unable to create alerts client because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
+ `Unable to create alerts client because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
);
}
return alertsClientFactory!.create(request, core.savedObjects);
diff --git a/x-pack/plugins/alerts/server/routes/health.test.ts b/x-pack/plugins/alerts/server/routes/health.test.ts
index 38bae896e40ba..22df0e6a00046 100644
--- a/x-pack/plugins/alerts/server/routes/health.test.ts
+++ b/x-pack/plugins/alerts/server/routes/health.test.ts
@@ -47,8 +47,7 @@ describe('healthRoute', () => {
const router = httpServiceMock.createRouter();
const licenseState = licenseStateMock.create();
- const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
- encryptedSavedObjects.usingEphemeralEncryptionKey = false;
+ const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
healthRoute(router, licenseState, encryptedSavedObjects);
const [config] = router.get.mock.calls[0];
@@ -60,8 +59,7 @@ describe('healthRoute', () => {
const router = httpServiceMock.createRouter();
const licenseState = licenseStateMock.create();
- const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
- encryptedSavedObjects.usingEphemeralEncryptionKey = false;
+ const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
healthRoute(router, licenseState, encryptedSavedObjects);
const [, handler] = router.get.mock.calls[0];
@@ -85,12 +83,11 @@ describe('healthRoute', () => {
`);
});
- it('evaluates whether Encrypted Saved Objects is using an ephemeral encryption key', async () => {
+ it('evaluates whether Encrypted Saved Objects is missing encryption key', async () => {
const router = httpServiceMock.createRouter();
const licenseState = licenseStateMock.create();
- const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
- encryptedSavedObjects.usingEphemeralEncryptionKey = true;
+ const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: false });
healthRoute(router, licenseState, encryptedSavedObjects);
const [, handler] = router.get.mock.calls[0];
@@ -129,8 +126,7 @@ describe('healthRoute', () => {
const router = httpServiceMock.createRouter();
const licenseState = licenseStateMock.create();
- const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
- encryptedSavedObjects.usingEphemeralEncryptionKey = false;
+ const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
healthRoute(router, licenseState, encryptedSavedObjects);
const [, handler] = router.get.mock.calls[0];
@@ -169,8 +165,7 @@ describe('healthRoute', () => {
const router = httpServiceMock.createRouter();
const licenseState = licenseStateMock.create();
- const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
- encryptedSavedObjects.usingEphemeralEncryptionKey = false;
+ const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
healthRoute(router, licenseState, encryptedSavedObjects);
const [, handler] = router.get.mock.calls[0];
@@ -209,8 +204,7 @@ describe('healthRoute', () => {
const router = httpServiceMock.createRouter();
const licenseState = licenseStateMock.create();
- const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
- encryptedSavedObjects.usingEphemeralEncryptionKey = false;
+ const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
healthRoute(router, licenseState, encryptedSavedObjects);
const [, handler] = router.get.mock.calls[0];
@@ -249,8 +243,7 @@ describe('healthRoute', () => {
const router = httpServiceMock.createRouter();
const licenseState = licenseStateMock.create();
- const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
- encryptedSavedObjects.usingEphemeralEncryptionKey = false;
+ const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
healthRoute(router, licenseState, encryptedSavedObjects);
const [, handler] = router.get.mock.calls[0];
@@ -291,8 +284,7 @@ describe('healthRoute', () => {
const router = httpServiceMock.createRouter();
const licenseState = licenseStateMock.create();
- const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
- encryptedSavedObjects.usingEphemeralEncryptionKey = false;
+ const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
healthRoute(router, licenseState, encryptedSavedObjects);
const [, handler] = router.get.mock.calls[0];
diff --git a/x-pack/plugins/alerts/server/routes/health.ts b/x-pack/plugins/alerts/server/routes/health.ts
index 24b3642ca2085..9e1f01041e091 100644
--- a/x-pack/plugins/alerts/server/routes/health.ts
+++ b/x-pack/plugins/alerts/server/routes/health.ts
@@ -55,7 +55,7 @@ export function healthRoute(
const frameworkHealth: AlertingFrameworkHealth = {
isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled),
- hasPermanentEncryptionKey: !encryptedSavedObjects.usingEphemeralEncryptionKey,
+ hasPermanentEncryptionKey: encryptedSavedObjects.canEncrypt,
alertingFrameworkHeath,
};
diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts
index f585515f79b0c..a1b3af6a9f943 100644
--- a/x-pack/plugins/apm/public/plugin.ts
+++ b/x-pack/plugins/apm/public/plugin.ts
@@ -162,7 +162,24 @@ export class ApmPlugin implements Plugin {
order: 8500,
euiIconType: 'logoObservability',
category: DEFAULT_APP_CATEGORIES.observability,
-
+ meta: {
+ keywords: [
+ 'RUM',
+ 'Real User Monitoring',
+ 'DEM',
+ 'Digital Experience Monitoring',
+ 'EUM',
+ 'End User Monitoring',
+ 'UX',
+ 'Javascript',
+ 'APM',
+ 'Mobile',
+ 'digital',
+ 'performance',
+ 'web performance',
+ 'web perf',
+ ],
+ },
async mount(params: AppMountParameters) {
// Load application bundle and Get start service
const [{ renderApp }, [coreStart, corePlugins]] = await Promise.all([
diff --git a/x-pack/plugins/beats_management/public/application.tsx b/x-pack/plugins/beats_management/public/application.tsx
index 6e81809b9c493..5a9b0a768856e 100644
--- a/x-pack/plugins/beats_management/public/application.tsx
+++ b/x-pack/plugins/beats_management/public/application.tsx
@@ -7,11 +7,13 @@
import * as euiVars from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import ReactDOM from 'react-dom';
import { Router } from 'react-router-dom';
import { ThemeProvider } from 'styled-components';
import { Provider as UnstatedProvider, Subscribe } from 'unstated';
+import { EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui';
import { Background } from './components/layouts/background';
import { BreadcrumbProvider } from './components/navigation/breadcrumb';
import { Breadcrumb } from './components/navigation/breadcrumb/breadcrumb';
@@ -37,6 +39,38 @@ export const renderApp = ({ element, history }: ManagementAppMountParams, libs:
defaultMessage: 'Management',
})}
/>
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+
)}
diff --git a/x-pack/plugins/cross_cluster_replication/tsconfig.json b/x-pack/plugins/cross_cluster_replication/tsconfig.json
new file mode 100644
index 0000000000000..9c7590b9c2553
--- /dev/null
+++ b/x-pack/plugins/cross_cluster_replication/tsconfig.json
@@ -0,0 +1,31 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": {
+ "composite": true,
+ "outDir": "./target/types",
+ "emitDeclarationOnly": true,
+ "declaration": true,
+ "declarationMap": true
+ },
+ "include": [
+ "common/**/*",
+ "public/**/*",
+ "server/**/*",
+ ],
+ "references": [
+ { "path": "../../../src/core/tsconfig.json" },
+ // required plugins
+ { "path": "../../../src/plugins/home/tsconfig.json" },
+ { "path": "../licensing/tsconfig.json" },
+ { "path": "../../../src/plugins/management/tsconfig.json" },
+ { "path": "../remote_clusters/tsconfig.json" },
+ { "path": "../index_management/tsconfig.json" },
+ { "path": "../features/tsconfig.json" },
+ // optional plugins
+ { "path": "../../../src/plugins/usage_collection/tsconfig.json" },
+ // required bundles
+ { "path": "../../../src/plugins/kibana_react/tsconfig.json" },
+ { "path": "../../../src/plugins/es_ui_shared/tsconfig.json" },
+ { "path": "../../../src/plugins/data/tsconfig.json" },
+ ]
+}
diff --git a/x-pack/plugins/encrypted_saved_objects/server/config.test.ts b/x-pack/plugins/encrypted_saved_objects/server/config.test.ts
index 3633dae824a2b..1cc5f7974cb13 100644
--- a/x-pack/plugins/encrypted_saved_objects/server/config.test.ts
+++ b/x-pack/plugins/encrypted_saved_objects/server/config.test.ts
@@ -5,10 +5,7 @@
* 2.0.
*/
-jest.mock('crypto', () => ({ randomBytes: jest.fn() }));
-
-import { loggingSystemMock } from 'src/core/server/mocks';
-import { createConfig, ConfigSchema } from './config';
+import { ConfigSchema } from './config';
describe('config schema', () => {
it('generates proper defaults', () => {
@@ -32,6 +29,17 @@ describe('config schema', () => {
}
`);
+ expect(ConfigSchema.validate({ encryptionKey: 'z'.repeat(32) }, { dist: true }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "enabled": true,
+ "encryptionKey": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz",
+ "keyRotation": Object {
+ "decryptionOnlyKeys": Array [],
+ },
+ }
+ `);
+
expect(ConfigSchema.validate({}, { dist: true })).toMatchInlineSnapshot(`
Object {
"enabled": true,
@@ -79,6 +87,18 @@ describe('config schema', () => {
);
});
+ it('should not allow `null` value for the encryption key', () => {
+ expect(() => ConfigSchema.validate({ encryptionKey: null })).toThrowErrorMatchingInlineSnapshot(
+ `"[encryptionKey]: expected value of type [string] but got [null]"`
+ );
+
+ expect(() =>
+ ConfigSchema.validate({ encryptionKey: null }, { dist: true })
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"[encryptionKey]: expected value of type [string] but got [null]"`
+ );
+ });
+
it('should throw error if any of the xpack.encryptedSavedObjects.keyRotation.decryptionOnlyKeys is less than 32 characters', () => {
expect(() =>
ConfigSchema.validate({
@@ -121,43 +141,3 @@ describe('config schema', () => {
);
});
});
-
-describe('createConfig()', () => {
- it('should log a warning, set xpack.encryptedSavedObjects.encryptionKey and usingEphemeralEncryptionKey=true when encryptionKey is not set', () => {
- const mockRandomBytes = jest.requireMock('crypto').randomBytes;
- mockRandomBytes.mockReturnValue('ab'.repeat(16));
-
- const logger = loggingSystemMock.create().get();
- const config = createConfig(ConfigSchema.validate({}, { dist: true }), logger);
- expect(config).toEqual({
- enabled: true,
- encryptionKey: 'ab'.repeat(16),
- keyRotation: { decryptionOnlyKeys: [] },
- usingEphemeralEncryptionKey: true,
- });
-
- expect(loggingSystemMock.collect(logger).warn).toMatchInlineSnapshot(`
- Array [
- Array [
- "Generating a random key for xpack.encryptedSavedObjects.encryptionKey. To decrypt encrypted saved objects attributes after restart, please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.",
- ],
- ]
- `);
- });
-
- it('should not log a warning and set usingEphemeralEncryptionKey=false when encryptionKey is set', async () => {
- const logger = loggingSystemMock.create().get();
- const config = createConfig(
- ConfigSchema.validate({ encryptionKey: 'supersecret'.repeat(3) }, { dist: true }),
- logger
- );
- expect(config).toEqual({
- enabled: true,
- encryptionKey: 'supersecret'.repeat(3),
- keyRotation: { decryptionOnlyKeys: [] },
- usingEphemeralEncryptionKey: false,
- });
-
- expect(loggingSystemMock.collect(logger).warn).toEqual([]);
- });
-});
diff --git a/x-pack/plugins/encrypted_saved_objects/server/config.ts b/x-pack/plugins/encrypted_saved_objects/server/config.ts
index 40db0187162d0..2bcf0e9b69511 100644
--- a/x-pack/plugins/encrypted_saved_objects/server/config.ts
+++ b/x-pack/plugins/encrypted_saved_objects/server/config.ts
@@ -5,11 +5,9 @@
* 2.0.
*/
-import crypto from 'crypto';
import { schema, TypeOf } from '@kbn/config-schema';
-import { Logger } from 'src/core/server';
-export type ConfigType = ReturnType;
+export type ConfigType = TypeOf;
export const ConfigSchema = schema.object(
{
@@ -33,23 +31,3 @@ export const ConfigSchema = schema.object(
},
}
);
-
-export function createConfig(config: TypeOf, logger: Logger) {
- let encryptionKey = config.encryptionKey;
- const usingEphemeralEncryptionKey = encryptionKey === undefined;
- if (encryptionKey === undefined) {
- logger.warn(
- 'Generating a random key for xpack.encryptedSavedObjects.encryptionKey. ' +
- 'To decrypt encrypted saved objects attributes after restart, ' +
- 'please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.'
- );
-
- encryptionKey = crypto.randomBytes(16).toString('hex');
- }
-
- return {
- ...config,
- encryptionKey,
- usingEphemeralEncryptionKey,
- };
-}
diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts
index 1760a85806786..f70810943d179 100644
--- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts
+++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts
@@ -226,6 +226,72 @@ describe('#stripOrDecryptAttributes', () => {
);
});
});
+
+ describe('without encryption key', () => {
+ beforeEach(() => {
+ service = new EncryptedSavedObjectsService({
+ logger: loggingSystemMock.create().get(),
+ audit: mockAuditLogger,
+ });
+ });
+
+ it('does not fail if none of attributes are supposed to be encrypted', async () => {
+ const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
+
+ service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attrFour']) });
+
+ await expect(
+ service.stripOrDecryptAttributes({ id: 'known-id', type: 'known-type-1' }, attributes)
+ ).resolves.toEqual({ attributes: { attrOne: 'one', attrTwo: 'two', attrThree: 'three' } });
+ });
+
+ it('does not fail if there are attributes are supposed to be encrypted, but should be stripped', async () => {
+ const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
+
+ service.registerType({
+ type: 'known-type-1',
+ attributesToEncrypt: new Set(['attrOne', 'attrThree']),
+ });
+
+ await expect(
+ service.stripOrDecryptAttributes({ id: 'known-id', type: 'known-type-1' }, attributes)
+ ).resolves.toEqual({ attributes: { attrTwo: 'two' } });
+ });
+
+ it('fails if needs to decrypt any attribute', async () => {
+ service.registerType({
+ type: 'known-type-1',
+ attributesToEncrypt: new Set([
+ 'attrOne',
+ { key: 'attrThree', dangerouslyExposeValue: true },
+ ]),
+ });
+
+ const mockUser = mockAuthenticatedUser();
+ const { attributes, error } = await service.stripOrDecryptAttributes(
+ { type: 'known-type-1', id: 'object-id' },
+ { attrOne: 'one', attrTwo: 'two', attrThree: 'three' },
+ undefined,
+ { user: mockUser }
+ );
+
+ expect(attributes).toEqual({ attrTwo: 'two' });
+
+ const encryptionError = error as EncryptionError;
+ expect(encryptionError.attributeName).toBe('attrThree');
+ expect(encryptionError.message).toBe('Unable to decrypt attribute "attrThree"');
+ expect(encryptionError.cause).toEqual(
+ new Error('Decryption is disabled because of missing decryption keys.')
+ );
+
+ expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledTimes(1);
+ expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith(
+ 'attrThree',
+ { type: 'known-type-1', id: 'object-id' },
+ mockUser
+ );
+ });
+ });
});
describe('#encryptAttributes', () => {
@@ -465,6 +531,58 @@ describe('#encryptAttributes', () => {
mockUser
);
});
+
+ describe('without encryption key', () => {
+ beforeEach(() => {
+ service = new EncryptedSavedObjectsService({
+ logger: loggingSystemMock.create().get(),
+ audit: mockAuditLogger,
+ });
+ });
+
+ it('does not fail if none of attributes are supposed to be encrypted', async () => {
+ const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
+
+ service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attrFour']) });
+
+ await expect(
+ service.encryptAttributes({ type: 'known-type-1', id: 'object-id' }, attributes)
+ ).resolves.toEqual({
+ attrOne: 'one',
+ attrTwo: 'two',
+ attrThree: 'three',
+ });
+ expect(mockAuditLogger.encryptAttributesSuccess).not.toHaveBeenCalled();
+ });
+
+ it('fails if needs to encrypt any attribute', async () => {
+ const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
+ service.registerType({
+ type: 'known-type-1',
+ attributesToEncrypt: new Set(['attrOne', 'attrThree']),
+ });
+
+ const mockUser = mockAuthenticatedUser();
+ await expect(
+ service.encryptAttributes({ type: 'known-type-1', id: 'object-id' }, attributes, {
+ user: mockUser,
+ })
+ ).rejects.toThrowError(EncryptionError);
+
+ expect(attributes).toEqual({
+ attrOne: 'one',
+ attrTwo: 'two',
+ attrThree: 'three',
+ });
+ expect(mockAuditLogger.encryptAttributesSuccess).not.toHaveBeenCalled();
+ expect(mockAuditLogger.encryptAttributeFailure).toHaveBeenCalledTimes(1);
+ expect(mockAuditLogger.encryptAttributeFailure).toHaveBeenCalledWith(
+ 'attrOne',
+ { type: 'known-type-1', id: 'object-id' },
+ mockUser
+ );
+ });
+ });
});
describe('#decryptAttributes', () => {
@@ -1099,6 +1217,88 @@ describe('#decryptAttributes', () => {
expect(decryptionOnlyCryptoTwo.decrypt).not.toHaveBeenCalled();
});
});
+
+ describe('without encryption key', () => {
+ beforeEach(() => {
+ service = new EncryptedSavedObjectsService({
+ logger: loggingSystemMock.create().get(),
+ audit: mockAuditLogger,
+ });
+ });
+
+ it('does not fail if none of attributes are supposed to be decrypted', async () => {
+ const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
+
+ service = new EncryptedSavedObjectsService({
+ decryptionOnlyCryptos: [],
+ logger: loggingSystemMock.create().get(),
+ audit: mockAuditLogger,
+ });
+ service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attrFour']) });
+
+ await expect(
+ service.decryptAttributes({ type: 'known-type-1', id: 'object-id' }, attributes)
+ ).resolves.toEqual({
+ attrOne: 'one',
+ attrTwo: 'two',
+ attrThree: 'three',
+ });
+ expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled();
+ });
+
+ it('does not fail if can decrypt attributes with decryption only keys', async () => {
+ const decryptionOnlyCryptoOne = createNodeCryptMock('old-key-one');
+ decryptionOnlyCryptoOne.decrypt.mockImplementation(
+ async (encryptedOutput: string | Buffer, aad?: string) => `${encryptedOutput}||${aad}`
+ );
+
+ service = new EncryptedSavedObjectsService({
+ decryptionOnlyCryptos: [decryptionOnlyCryptoOne],
+ logger: loggingSystemMock.create().get(),
+ audit: mockAuditLogger,
+ });
+ service.registerType({
+ type: 'known-type-1',
+ attributesToEncrypt: new Set(['attrOne', 'attrThree', 'attrFour']),
+ });
+
+ const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three', attrFour: null };
+ await expect(
+ service.decryptAttributes({ type: 'known-type-1', id: 'object-id' }, attributes)
+ ).resolves.toEqual({
+ attrOne: 'one||["known-type-1","object-id",{"attrTwo":"two"}]',
+ attrTwo: 'two',
+ attrThree: 'three||["known-type-1","object-id",{"attrTwo":"two"}]',
+ attrFour: null,
+ });
+ expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1);
+ expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith(
+ ['attrOne', 'attrThree'],
+ { type: 'known-type-1', id: 'object-id' },
+ undefined
+ );
+ });
+
+ it('fails if needs to decrypt any attribute', async () => {
+ const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
+
+ service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attrOne']) });
+
+ const mockUser = mockAuthenticatedUser();
+ await expect(
+ service.decryptAttributes({ type: 'known-type-1', id: 'object-id' }, attributes, {
+ user: mockUser,
+ })
+ ).rejects.toThrowError(EncryptionError);
+
+ expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled();
+ expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith(
+ 'attrOne',
+ { type: 'known-type-1', id: 'object-id' },
+ mockUser
+ );
+ });
+ });
});
describe('#encryptAttributesSync', () => {
@@ -1283,6 +1483,58 @@ describe('#encryptAttributesSync', () => {
attrThree: 'three',
});
});
+
+ describe('without encryption key', () => {
+ beforeEach(() => {
+ service = new EncryptedSavedObjectsService({
+ logger: loggingSystemMock.create().get(),
+ audit: mockAuditLogger,
+ });
+ });
+
+ it('does not fail if none of attributes are supposed to be encrypted', () => {
+ const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
+
+ service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attrFour']) });
+
+ expect(
+ service.encryptAttributesSync({ type: 'known-type-1', id: 'object-id' }, attributes)
+ ).toEqual({
+ attrOne: 'one',
+ attrTwo: 'two',
+ attrThree: 'three',
+ });
+ expect(mockAuditLogger.encryptAttributesSuccess).not.toHaveBeenCalled();
+ });
+
+ it('fails if needs to encrypt any attribute', () => {
+ const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
+ service.registerType({
+ type: 'known-type-1',
+ attributesToEncrypt: new Set(['attrOne', 'attrThree']),
+ });
+
+ const mockUser = mockAuthenticatedUser();
+ expect(() =>
+ service.encryptAttributesSync({ type: 'known-type-1', id: 'object-id' }, attributes, {
+ user: mockUser,
+ })
+ ).toThrowError(EncryptionError);
+
+ expect(attributes).toEqual({
+ attrOne: 'one',
+ attrTwo: 'two',
+ attrThree: 'three',
+ });
+ expect(mockAuditLogger.encryptAttributesSuccess).not.toHaveBeenCalled();
+ expect(mockAuditLogger.encryptAttributeFailure).toHaveBeenCalledTimes(1);
+ expect(mockAuditLogger.encryptAttributeFailure).toHaveBeenCalledWith(
+ 'attrOne',
+ { type: 'known-type-1', id: 'object-id' },
+ mockUser
+ );
+ });
+ });
});
describe('#decryptAttributesSync', () => {
@@ -1784,4 +2036,86 @@ describe('#decryptAttributesSync', () => {
expect(decryptionOnlyCryptoTwo.decryptSync).not.toHaveBeenCalled();
});
});
+
+ describe('without encryption key', () => {
+ beforeEach(() => {
+ service = new EncryptedSavedObjectsService({
+ logger: loggingSystemMock.create().get(),
+ audit: mockAuditLogger,
+ });
+ });
+
+ it('does not fail if none of attributes are supposed to be decrypted', () => {
+ const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
+
+ service = new EncryptedSavedObjectsService({
+ decryptionOnlyCryptos: [],
+ logger: loggingSystemMock.create().get(),
+ audit: mockAuditLogger,
+ });
+ service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attrFour']) });
+
+ expect(
+ service.decryptAttributesSync({ type: 'known-type-1', id: 'object-id' }, attributes)
+ ).toEqual({
+ attrOne: 'one',
+ attrTwo: 'two',
+ attrThree: 'three',
+ });
+ expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled();
+ });
+
+ it('does not fail if can decrypt attributes with decryption only keys', () => {
+ const decryptionOnlyCryptoOne = createNodeCryptMock('old-key-one');
+ decryptionOnlyCryptoOne.decryptSync.mockImplementation(
+ (encryptedOutput: string | Buffer, aad?: string) => `${encryptedOutput}||${aad}`
+ );
+
+ service = new EncryptedSavedObjectsService({
+ decryptionOnlyCryptos: [decryptionOnlyCryptoOne],
+ logger: loggingSystemMock.create().get(),
+ audit: mockAuditLogger,
+ });
+ service.registerType({
+ type: 'known-type-1',
+ attributesToEncrypt: new Set(['attrOne', 'attrThree', 'attrFour']),
+ });
+
+ const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three', attrFour: null };
+ expect(
+ service.decryptAttributesSync({ type: 'known-type-1', id: 'object-id' }, attributes)
+ ).toEqual({
+ attrOne: 'one||["known-type-1","object-id",{"attrTwo":"two"}]',
+ attrTwo: 'two',
+ attrThree: 'three||["known-type-1","object-id",{"attrTwo":"two"}]',
+ attrFour: null,
+ });
+ expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1);
+ expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith(
+ ['attrOne', 'attrThree'],
+ { type: 'known-type-1', id: 'object-id' },
+ undefined
+ );
+ });
+
+ it('fails if needs to decrypt any attribute', () => {
+ const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
+
+ service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attrOne']) });
+
+ const mockUser = mockAuthenticatedUser();
+ expect(() =>
+ service.decryptAttributesSync({ type: 'known-type-1', id: 'object-id' }, attributes, {
+ user: mockUser,
+ })
+ ).toThrowError(EncryptionError);
+
+ expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled();
+ expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith(
+ 'attrOne',
+ { type: 'known-type-1', id: 'object-id' },
+ mockUser
+ );
+ });
+ });
});
diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts
index 91a3cfc921624..23aef07ff8781 100644
--- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts
+++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts
@@ -77,7 +77,7 @@ interface EncryptedSavedObjectsServiceOptions {
/**
* NodeCrypto instance used for both encryption and decryption.
*/
- primaryCrypto: Crypto;
+ primaryCrypto?: Crypto;
/**
* NodeCrypto instances used ONLY for decryption (i.e. rotated encryption keys).
@@ -293,12 +293,17 @@ export class EncryptedSavedObjectsService {
let iteratorResult = iterator.next();
while (!iteratorResult.done) {
const [attributeValue, encryptionAAD] = iteratorResult.value;
- try {
- iteratorResult = iterator.next(
- await this.options.primaryCrypto.encrypt(attributeValue, encryptionAAD)
- );
- } catch (err) {
- iterator.throw!(err);
+ // We check this inside of the iterator to throw only if we do need to encrypt anything.
+ if (this.options.primaryCrypto) {
+ try {
+ iteratorResult = iterator.next(
+ await this.options.primaryCrypto.encrypt(attributeValue, encryptionAAD)
+ );
+ } catch (err) {
+ iterator.throw!(err);
+ }
+ } else {
+ iterator.throw!(new Error('Encryption is disabled because of missing encryption key.'));
}
}
@@ -324,12 +329,17 @@ export class EncryptedSavedObjectsService {
let iteratorResult = iterator.next();
while (!iteratorResult.done) {
const [attributeValue, encryptionAAD] = iteratorResult.value;
- try {
- iteratorResult = iterator.next(
- this.options.primaryCrypto.encryptSync(attributeValue, encryptionAAD)
- );
- } catch (err) {
- iterator.throw!(err);
+ // We check this inside of the iterator to throw only if we do need to encrypt anything.
+ if (this.options.primaryCrypto) {
+ try {
+ iteratorResult = iterator.next(
+ this.options.primaryCrypto.encryptSync(attributeValue, encryptionAAD)
+ );
+ } catch (err) {
+ iterator.throw!(err);
+ }
+ } else {
+ iterator.throw!(new Error('Encryption is disabled because of missing encryption key.'));
}
}
@@ -358,7 +368,11 @@ export class EncryptedSavedObjectsService {
while (!iteratorResult.done) {
const [attributeValue, encryptionAAD] = iteratorResult.value;
- let decryptionError;
+ // We check this inside of the iterator to throw only if we do need to decrypt anything.
+ let decryptionError =
+ decrypters.length === 0
+ ? new Error('Decryption is disabled because of missing decryption keys.')
+ : undefined;
for (const decrypter of decrypters) {
try {
iteratorResult = iterator.next(await decrypter.decrypt(attributeValue, encryptionAAD));
@@ -402,7 +416,11 @@ export class EncryptedSavedObjectsService {
while (!iteratorResult.done) {
const [attributeValue, encryptionAAD] = iteratorResult.value;
- let decryptionError;
+ // We check this inside of the iterator to throw only if we do need to decrypt anything.
+ let decryptionError =
+ decrypters.length === 0
+ ? new Error('Decryption is disabled because of missing decryption keys.')
+ : undefined;
for (const decrypter of decrypters) {
try {
iteratorResult = iterator.next(decrypter.decryptSync(attributeValue, encryptionAAD));
@@ -541,6 +559,9 @@ export class EncryptedSavedObjectsService {
return this.options.decryptionOnlyCryptos;
}
- return [this.options.primaryCrypto, ...(this.options.decryptionOnlyCryptos ?? [])];
+ return [
+ ...(this.options.primaryCrypto ? [this.options.primaryCrypto] : []),
+ ...(this.options.decryptionOnlyCryptos ?? []),
+ ];
}
}
diff --git a/x-pack/plugins/encrypted_saved_objects/server/mocks.ts b/x-pack/plugins/encrypted_saved_objects/server/mocks.ts
index 6c8196b2ae03c..edb55513aabf5 100644
--- a/x-pack/plugins/encrypted_saved_objects/server/mocks.ts
+++ b/x-pack/plugins/encrypted_saved_objects/server/mocks.ts
@@ -8,11 +8,13 @@
import { EncryptedSavedObjectsPluginSetup, EncryptedSavedObjectsPluginStart } from './plugin';
import { EncryptedSavedObjectsClient, EncryptedSavedObjectsClientOptions } from './saved_objects';
-function createEncryptedSavedObjectsSetupMock() {
+function createEncryptedSavedObjectsSetupMock(
+ { canEncrypt }: { canEncrypt: boolean } = { canEncrypt: false }
+) {
return {
registerType: jest.fn(),
__legacyCompat: { registerLegacyAPI: jest.fn() },
- usingEphemeralEncryptionKey: true,
+ canEncrypt,
createMigration: jest.fn(),
} as jest.Mocked;
}
diff --git a/x-pack/plugins/encrypted_saved_objects/server/plugin.test.ts b/x-pack/plugins/encrypted_saved_objects/server/plugin.test.ts
index 823a6b0afa9dc..e71332b1c5aa7 100644
--- a/x-pack/plugins/encrypted_saved_objects/server/plugin.test.ts
+++ b/x-pack/plugins/encrypted_saved_objects/server/plugin.test.ts
@@ -19,12 +19,28 @@ describe('EncryptedSavedObjects Plugin', () => {
);
expect(plugin.setup(coreMock.createSetup(), { security: securityMock.createSetup() }))
.toMatchInlineSnapshot(`
- Object {
- "createMigration": [Function],
- "registerType": [Function],
- "usingEphemeralEncryptionKey": true,
- }
- `);
+ Object {
+ "canEncrypt": false,
+ "createMigration": [Function],
+ "registerType": [Function],
+ }
+ `);
+ });
+
+ it('exposes proper contract when encryption key is set', () => {
+ const plugin = new EncryptedSavedObjectsPlugin(
+ coreMock.createPluginInitializerContext(
+ ConfigSchema.validate({ encryptionKey: 'z'.repeat(32) }, { dist: true })
+ )
+ );
+ expect(plugin.setup(coreMock.createSetup(), { security: securityMock.createSetup() }))
+ .toMatchInlineSnapshot(`
+ Object {
+ "canEncrypt": true,
+ "createMigration": [Function],
+ "registerType": [Function],
+ }
+ `);
});
});
diff --git a/x-pack/plugins/encrypted_saved_objects/server/plugin.ts b/x-pack/plugins/encrypted_saved_objects/server/plugin.ts
index e846b133c26e0..c99d6bd32287d 100644
--- a/x-pack/plugins/encrypted_saved_objects/server/plugin.ts
+++ b/x-pack/plugins/encrypted_saved_objects/server/plugin.ts
@@ -6,10 +6,9 @@
*/
import nodeCrypto from '@elastic/node-crypto';
-import { Logger, PluginInitializerContext, CoreSetup, Plugin } from 'src/core/server';
-import { TypeOf } from '@kbn/config-schema';
-import { SecurityPluginSetup } from '../../security/server';
-import { createConfig, ConfigSchema } from './config';
+import type { Logger, PluginInitializerContext, CoreSetup, Plugin } from 'src/core/server';
+import type { SecurityPluginSetup } from '../../security/server';
+import type { ConfigType } from './config';
import {
EncryptedSavedObjectsService,
EncryptedSavedObjectTypeRegistration,
@@ -26,8 +25,11 @@ export interface PluginsSetup {
}
export interface EncryptedSavedObjectsPluginSetup {
+ /**
+ * Indicates if Saved Object encryption is possible. Requires an encryption key to be explicitly set via `xpack.encryptedSavedObjects.encryptionKey`.
+ */
+ canEncrypt: boolean;
registerType: (typeRegistration: EncryptedSavedObjectTypeRegistration) => void;
- usingEphemeralEncryptionKey: boolean;
createMigration: CreateEncryptedSavedObjectsMigrationFn;
}
@@ -50,19 +52,24 @@ export class EncryptedSavedObjectsPlugin
}
public setup(core: CoreSetup, deps: PluginsSetup): EncryptedSavedObjectsPluginSetup {
- const config = createConfig(
- this.initializerContext.config.get>(),
- this.initializerContext.logger.get('config')
- );
- const auditLogger = new EncryptedSavedObjectsAuditLogger(
- deps.security?.audit.getLogger('encryptedSavedObjects')
- );
+ const config = this.initializerContext.config.get();
+ const canEncrypt = config.encryptionKey !== undefined;
+ if (!canEncrypt) {
+ this.logger.warn(
+ 'Saved objects encryption key is not set. This will severely limit Kibana functionality. ' +
+ 'Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.'
+ );
+ }
- const primaryCrypto = nodeCrypto({ encryptionKey: config.encryptionKey });
+ const primaryCrypto = config.encryptionKey
+ ? nodeCrypto({ encryptionKey: config.encryptionKey })
+ : undefined;
const decryptionOnlyCryptos = config.keyRotation.decryptionOnlyKeys.map((decryptionKey) =>
nodeCrypto({ encryptionKey: decryptionKey })
);
-
+ const auditLogger = new EncryptedSavedObjectsAuditLogger(
+ deps.security?.audit.getLogger('encryptedSavedObjects')
+ );
const service = Object.freeze(
new EncryptedSavedObjectsService({
primaryCrypto,
@@ -94,9 +101,9 @@ export class EncryptedSavedObjectsPlugin
});
return {
+ canEncrypt,
registerType: (typeRegistration: EncryptedSavedObjectTypeRegistration) =>
service.registerType(typeRegistration),
- usingEphemeralEncryptionKey: config.usingEphemeralEncryptionKey,
createMigration: getCreateMigration(
service,
(typeRegistration: EncryptedSavedObjectTypeRegistration) => {
diff --git a/x-pack/plugins/encrypted_saved_objects/server/routes/index.mock.ts b/x-pack/plugins/encrypted_saved_objects/server/routes/index.mock.ts
index c2dbc4c163b44..32ac1617f4a7e 100644
--- a/x-pack/plugins/encrypted_saved_objects/server/routes/index.mock.ts
+++ b/x-pack/plugins/encrypted_saved_objects/server/routes/index.mock.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { ConfigSchema, createConfig } from '../config';
+import { ConfigSchema, ConfigType } from '../config';
import { httpServiceMock, loggingSystemMock } from '../../../../../src/core/server/mocks';
import { encryptionKeyRotationServiceMock } from '../crypto/index.mock';
@@ -14,7 +14,7 @@ export const routeDefinitionParamsMock = {
create: (config: Record = {}) => ({
router: httpServiceMock.createRouter(),
logger: loggingSystemMock.create().get(),
- config: createConfig(ConfigSchema.validate(config), loggingSystemMock.create().get()),
+ config: ConfigSchema.validate(config) as ConfigType,
encryptionKeyRotationService: encryptionKeyRotationServiceMock.create(),
}),
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts
index f284fef370f02..ecc7b991f0761 100644
--- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea.mock.ts
@@ -11,11 +11,11 @@
* NOTE: These variable names MUST start with 'mock*' in order for
* Jest to accept its use within a jest.mock()
*/
+import { mockFlashMessagesValues, mockFlashMessagesActions } from './flash_messages_logic.mock';
+import { mockHttpValues } from './http_logic.mock';
import { mockKibanaValues } from './kibana_logic.mock';
import { mockLicensingValues } from './licensing_logic.mock';
-import { mockHttpValues } from './http_logic.mock';
import { mockTelemetryActions } from './telemetry_logic.mock';
-import { mockFlashMessagesValues, mockFlashMessagesActions } from './flash_messages_logic.mock';
export const mockAllValues = {
...mockKibanaValues,
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_logic.mock.ts
index a201a2b56c25c..d8d66e5ee1998 100644
--- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_logic.mock.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_logic.mock.ts
@@ -6,6 +6,7 @@
*/
import { chartPluginMock } from '../../../../../../src/plugins/charts/public/mocks';
+
import { mockHistory } from './';
export const mockKibanaValues = {
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_async.mock.tsx b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_async.mock.tsx
index 27e8a1421f462..2b5c06df37e8c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_async.mock.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_async.mock.tsx
@@ -6,8 +6,9 @@
*/
import React from 'react';
-import { act } from 'react-dom/test-utils';
+
import { mount, ReactWrapper } from 'enzyme';
+import { act } from 'react-dom/test-utils';
import { mountWithIntl } from './';
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_i18n.mock.tsx b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_i18n.mock.tsx
index a5a2891d3699c..3a98616082412 100644
--- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_i18n.mock.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_with_i18n.mock.tsx
@@ -6,7 +6,9 @@
*/
import React from 'react';
+
import { mount } from 'enzyme';
+
import { I18nProvider } from '@kbn/i18n/react';
/**
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_with_i18n.mock.tsx b/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_with_i18n.mock.tsx
index 224d71ac579a0..0127804374163 100644
--- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_with_i18n.mock.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/shallow_with_i18n.mock.tsx
@@ -8,6 +8,7 @@
import React from 'react';
import { shallow, mount, ReactWrapper } from 'enzyme';
+
import { I18nProvider, __IntlProvider } from '@kbn/i18n/react';
// Use fake component to extract `intl` property to use in tests.
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts
index 86f3993728e06..e5b0a702897bf 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts
@@ -5,9 +5,9 @@
* 2.0.
*/
+import { DEFAULT_INITIAL_APP_DATA } from '../../../common/__mocks__';
import { LogicMounter } from '../__mocks__';
-import { DEFAULT_INITIAL_APP_DATA } from '../../../common/__mocks__';
import { AppLogic } from './app_logic';
describe('AppLogic', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts
index 8a55b7c0add94..c33a0e89d2aee 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.ts
@@ -8,6 +8,7 @@
import { kea, MakeLogicType } from 'kea';
import { InitialAppData } from '../../../common/types';
+
import { ConfiguredLimits, Account, Role } from './types';
import { getRoleAbilities } from './utils/role';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_layout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_layout.test.tsx
index 5248833d827b2..1a4e05c04f319 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_layout.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_layout.test.tsx
@@ -11,14 +11,15 @@ import { mockKibanaValues, setMockValues, setMockActions, rerender } from '../..
import React from 'react';
import { useParams } from 'react-router-dom';
+
import { shallow } from 'enzyme';
-import { Loading } from '../../../shared/loading';
import { FlashMessages } from '../../../shared/flash_messages';
+import { Loading } from '../../../shared/loading';
import { LogRetentionCallout } from '../log_retention';
-import { AnalyticsHeader, AnalyticsUnavailable } from './components';
import { AnalyticsLayout } from './analytics_layout';
+import { AnalyticsHeader, AnalyticsUnavailable } from './components';
describe('AnalyticsLayout', () => {
const { history } = mockKibanaValues;
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_layout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_layout.tsx
index 88d0f77541166..0c90267c1dbad 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_layout.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_layout.tsx
@@ -7,18 +7,21 @@
import React, { useEffect } from 'react';
import { useParams } from 'react-router-dom';
+
import { useValues, useActions } from 'kea';
+
import { EuiSpacer } from '@elastic/eui';
-import { KibanaLogic } from '../../../shared/kibana';
import { FlashMessages } from '../../../shared/flash_messages';
+import { KibanaLogic } from '../../../shared/kibana';
import { Loading } from '../../../shared/loading';
import { LogRetentionCallout, LogRetentionOptions } from '../log_retention';
-import { AnalyticsLogic } from './';
import { AnalyticsHeader, AnalyticsUnavailable } from './components';
+import { AnalyticsLogic } from './';
+
interface Props {
title: string;
isQueryView?: boolean;
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.test.ts
index 6ca9eb23c962b..ad612e48c770a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.test.ts
@@ -19,6 +19,7 @@ jest.mock('../engine', () => ({
import { nextTick } from '@kbn/test/jest';
import { DEFAULT_START_DATE, DEFAULT_END_DATE } from './constants';
+
import { AnalyticsLogic } from './';
describe('AnalyticsLogic', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.ts
index e978d2c65398e..de0828f6d71ea 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.ts
@@ -8,9 +8,9 @@
import { kea, MakeLogicType } from 'kea';
import queryString from 'query-string';
-import { KibanaLogic } from '../../../shared/kibana';
-import { HttpLogic } from '../../../shared/http';
import { flashAPIErrors } from '../../../shared/flash_messages';
+import { HttpLogic } from '../../../shared/http';
+import { KibanaLogic } from '../../../shared/kibana';
import { EngineLogic } from '../engine';
import { DEFAULT_START_DATE, DEFAULT_END_DATE } from './constants';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.test.tsx
index 3f6bf77024c1e..3940151d3b7cd 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_router.test.tsx
@@ -8,9 +8,11 @@
import '../../__mocks__/engine_logic.mock';
import React from 'react';
-import { shallow } from 'enzyme';
+
import { Route, Switch } from 'react-router-dom';
+import { shallow } from 'enzyme';
+
import { AnalyticsRouter } from './';
describe('AnalyticsRouter', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_cards.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_cards.test.tsx
index 84ee392c2419e..8883d0d1ffcbd 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_cards.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_cards.test.tsx
@@ -6,7 +6,9 @@
*/
import React from 'react';
+
import { shallow } from 'enzyme';
+
import { EuiStat } from '@elastic/eui';
import { AnalyticsCards } from './';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_cards.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_cards.tsx
index 417fa0cc48f65..b08e391f845e6 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_cards.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_cards.tsx
@@ -6,6 +6,7 @@
*/
import React from 'react';
+
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat } from '@elastic/eui';
interface Props {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_chart.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_chart.test.tsx
index dcea1f81e53eb..51238d62bdac7 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_chart.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_chart.test.tsx
@@ -8,7 +8,9 @@
import { mockKibanaValues } from '../../../../__mocks__';
import React from 'react';
+
import { shallow } from 'enzyme';
+
import { Chart, Settings, LineSeries, Axis } from '@elastic/charts';
import { AnalyticsChart } from './';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_chart.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_chart.tsx
index 686cadda02f63..fa33389503beb 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_chart.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_chart.tsx
@@ -6,9 +6,11 @@
*/
import React from 'react';
+
import { useValues } from 'kea';
import moment from 'moment';
+
import { Chart, Settings, LineSeries, CurveType, Axis } from '@elastic/charts';
import { KibanaLogic } from '../../../../shared/kibana';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_header.test.tsx
index 3faf2b03097f7..952c4c2517a0e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_header.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_header.test.tsx
@@ -8,13 +8,16 @@
import { setMockValues, mockKibanaValues } from '../../../../__mocks__';
import React, { ReactElement } from 'react';
+
import { shallow, ShallowWrapper } from 'enzyme';
import moment, { Moment } from 'moment';
+
import { EuiPageHeader, EuiSelect, EuiDatePickerRange, EuiButton } from '@elastic/eui';
import { LogRetentionTooltip } from '../../log_retention';
import { DEFAULT_START_DATE, DEFAULT_END_DATE } from '../constants';
+
import { AnalyticsHeader } from './';
describe('AnalyticsHeader', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_header.tsx
index 3986f7859bfd2..8a87a5e8c211c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_header.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_header.tsx
@@ -6,12 +6,11 @@
*/
import React, { useState } from 'react';
-import { useValues } from 'kea';
-import queryString from 'query-string';
+import { useValues } from 'kea';
import moment from 'moment';
+import queryString from 'query-string';
-import { i18n } from '@kbn/i18n';
import {
EuiPageHeader,
EuiPageHeaderSection,
@@ -23,11 +22,12 @@ import {
EuiDatePicker,
EuiButton,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { AnalyticsLogic } from '../';
import { KibanaLogic } from '../../../../shared/kibana';
import { LogRetentionTooltip, LogRetentionOptions } from '../../log_retention';
-import { AnalyticsLogic } from '../';
import { DEFAULT_START_DATE, DEFAULT_END_DATE, SERVER_DATE_FORMAT } from '../constants';
import { convertTagsToSelectOptions } from '../utils';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_search.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_search.test.tsx
index 4a3bbda5120bc..89fa5b4cc4b73 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_search.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_search.test.tsx
@@ -9,7 +9,9 @@ import { mockKibanaValues } from '../../../../__mocks__';
import '../../../__mocks__/engine_logic.mock';
import React from 'react';
+
import { shallow } from 'enzyme';
+
import { EuiFieldSearch } from '@elastic/eui';
import { AnalyticsSearch } from './';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_search.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_search.tsx
index 922e096701e84..4f2b525aaa168 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_search.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_search.tsx
@@ -6,10 +6,11 @@
*/
import React, { useState } from 'react';
+
import { useValues } from 'kea';
-import { i18n } from '@kbn/i18n';
import { EuiFlexGroup, EuiFlexItem, EuiFieldSearch, EuiButton, EuiSpacer } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { KibanaLogic } from '../../../../shared/kibana';
import { ENGINE_ANALYTICS_QUERY_DETAIL_PATH } from '../../../routes';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_section.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_section.test.tsx
index 981173e2a915b..56e30e6061173 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_section.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_section.test.tsx
@@ -6,6 +6,7 @@
*/
import React from 'react';
+
import { shallow } from 'enzyme';
import { AnalyticsSection } from './';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.test.tsx
index 0788edfdda427..2eac65fc21091 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.test.tsx
@@ -9,6 +9,7 @@ import { mountWithIntl, mockKibanaValues } from '../../../../../__mocks__';
import '../../../../__mocks__/engine_logic.mock';
import React from 'react';
+
import { EuiBasicTable, EuiBadge, EuiEmptyPrompt } from '@elastic/eui';
import { AnalyticsTable } from './';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.tsx
index 8e9853233cbed..a580047f1f635 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/analytics_table.tsx
@@ -6,10 +6,12 @@
*/
import React from 'react';
-import { i18n } from '@kbn/i18n';
+
import { EuiBasicTable, EuiBasicTableColumn, EuiEmptyPrompt } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { Query } from '../../types';
+
import {
TERM_COLUMN_PROPS,
TAGS_COLUMN,
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/inline_tags_list.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/inline_tags_list.test.tsx
index 9ad2cc32f99c5..9204fa6e75fa7 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/inline_tags_list.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/inline_tags_list.test.tsx
@@ -6,7 +6,9 @@
*/
import React from 'react';
+
import { shallow } from 'enzyme';
+
import { EuiBadge, EuiToolTip } from '@elastic/eui';
import { InlineTagsList } from './inline_tags_list';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/inline_tags_list.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/inline_tags_list.tsx
index 421ff1eedf278..908b096c80a9e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/inline_tags_list.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/inline_tags_list.tsx
@@ -6,8 +6,9 @@
*/
import React from 'react';
-import { i18n } from '@kbn/i18n';
+
import { EuiBadgeGroup, EuiBadge, EuiToolTip } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { Query } from '../../types';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/query_clicks_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/query_clicks_table.test.tsx
index 4396f91136258..cc8f13299c57f 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/query_clicks_table.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/query_clicks_table.test.tsx
@@ -9,6 +9,7 @@ import { mountWithIntl } from '../../../../../__mocks__';
import '../../../../__mocks__/engine_logic.mock';
import React from 'react';
+
import { EuiBasicTable, EuiLink, EuiBadge, EuiEmptyPrompt } from '@elastic/eui';
import { QueryClicksTable } from './';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/query_clicks_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/query_clicks_table.tsx
index 7c333623df6c0..4a93724ff5245 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/query_clicks_table.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/query_clicks_table.tsx
@@ -7,15 +7,16 @@
import React from 'react';
-import { i18n } from '@kbn/i18n';
import { EuiBasicTable, EuiBasicTableColumn, EuiEmptyPrompt } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { EuiLinkTo } from '../../../../../shared/react_router_helpers';
import { ENGINE_DOCUMENT_DETAIL_PATH } from '../../../../routes';
-import { generateEnginePath } from '../../../engine';
import { DOCUMENTS_TITLE } from '../../../documents';
+import { generateEnginePath } from '../../../engine';
import { QueryClick } from '../../types';
+
import { FIRST_COLUMN_PROPS, TAGS_COLUMN, COUNT_COLUMN_PROPS } from './shared_columns';
interface Props {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.test.tsx
index fdbbd326c47a1..a5a582d3747bc 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.test.tsx
@@ -9,6 +9,7 @@ import { mountWithIntl, mockKibanaValues } from '../../../../../__mocks__';
import '../../../../__mocks__/engine_logic.mock';
import React from 'react';
+
import { EuiBasicTable, EuiBadge, EuiEmptyPrompt } from '@elastic/eui';
import { RecentQueriesTable } from './';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.tsx
index 20e50e633b321..7724ac5c393ec 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/recent_queries_table.tsx
@@ -7,11 +7,12 @@
import React from 'react';
+import { EuiBasicTable, EuiBasicTableColumn, EuiEmptyPrompt } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedDate, FormattedTime } from '@kbn/i18n/react';
-import { EuiBasicTable, EuiBasicTableColumn, EuiEmptyPrompt } from '@elastic/eui';
import { RecentQuery } from '../../types';
+
import {
TERM_COLUMN_PROPS,
TAGS_COLUMN,
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx
index 0612fac1c07ed..9d8365a2f7af1 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx
@@ -6,14 +6,15 @@
*/
import React from 'react';
+
import { i18n } from '@kbn/i18n';
-import { EuiLinkTo } from '../../../../../shared/react_router_helpers';
import { KibanaLogic } from '../../../../../shared/kibana';
+import { EuiLinkTo } from '../../../../../shared/react_router_helpers';
import { ENGINE_ANALYTICS_QUERY_DETAIL_PATH } from '../../../../routes';
import { generateEnginePath } from '../../../engine';
-
import { Query, RecentQuery } from '../../types';
+
import { InlineTagsList } from './inline_tags_list';
/**
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_unavailable.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_unavailable.test.tsx
index ddc0e4636b3ad..e2ff440615dfc 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_unavailable.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_unavailable.test.tsx
@@ -6,7 +6,9 @@
*/
import React from 'react';
+
import { shallow } from 'enzyme';
+
import { EuiEmptyPrompt } from '@elastic/eui';
import { FlashMessages } from '../../../../shared/flash_messages';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_unavailable.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_unavailable.tsx
index 2ef020d2f4992..388570b32b6d2 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_unavailable.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_unavailable.tsx
@@ -6,8 +6,9 @@
*/
import React from 'react';
-import { i18n } from '@kbn/i18n';
+
import { EuiEmptyPrompt } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { FlashMessages } from '../../../../shared/flash_messages';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/constants.ts
index a04a9474ce5ae..75001f5bc86d6 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/constants.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/constants.ts
@@ -6,6 +6,7 @@
*/
import moment from 'moment';
+
import { i18n } from '@kbn/i18n';
export const ANALYTICS_TITLE = i18n.translate(
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/utils.ts
index 2d00c906b2aec..db679b0f387e8 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/utils.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/utils.ts
@@ -6,11 +6,12 @@
*/
import moment from 'moment';
-import { i18n } from '@kbn/i18n';
+
import { EuiSelectProps } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
-import { SERVER_DATE_FORMAT } from './constants';
import { ChartData } from './components/analytics_chart';
+import { SERVER_DATE_FORMAT } from './constants';
interface ConvertToChartData {
data: number[];
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.test.tsx
index 065b2208648bf..d8921ff0d3723 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.test.tsx
@@ -9,6 +9,7 @@ import { setMockValues } from '../../../../__mocks__';
import '../../../__mocks__/engine_logic.mock';
import React from 'react';
+
import { shallow } from 'enzyme';
import {
@@ -18,6 +19,7 @@ import {
AnalyticsTable,
RecentQueriesTable,
} from '../components';
+
import { Analytics, ViewAllButton } from './analytics';
describe('Analytics overview', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.tsx
index 09b1ff45c6122..a4f0bc356ac78 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/analytics.tsx
@@ -6,10 +6,11 @@
*/
import React from 'react';
+
import { useValues } from 'kea';
-import { i18n } from '@kbn/i18n';
import { EuiSpacer, EuiTitle } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { EuiButtonTo } from '../../../../shared/react_router_helpers';
import {
@@ -21,6 +22,8 @@ import {
} from '../../../routes';
import { generateEnginePath } from '../../engine';
+import { AnalyticsLayout } from '../analytics_layout';
+import { AnalyticsSection, AnalyticsTable, RecentQueriesTable } from '../components';
import {
ANALYTICS_TITLE,
TOTAL_QUERIES,
@@ -32,9 +35,7 @@ import {
TOP_QUERIES_NO_CLICKS,
RECENT_QUERIES,
} from '../constants';
-import { AnalyticsLayout } from '../analytics_layout';
-import { AnalyticsSection, AnalyticsTable, RecentQueriesTable } from '../components';
-import { AnalyticsLogic, AnalyticsCards, AnalyticsChart, convertToChartData } from '../';
+import { AnalyticsLogic, AnalyticsCards, AnalyticsChart, convertToChartData } from '../index';
export const Analytics: React.FC = () => {
const {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.test.tsx
index 050770944edcd..978f11ddfd5cd 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.test.tsx
@@ -10,12 +10,14 @@ import { setMockValues } from '../../../../__mocks__';
import React from 'react';
import { useParams } from 'react-router-dom';
+
import { shallow } from 'enzyme';
import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome';
import { AnalyticsLayout } from '../analytics_layout';
import { AnalyticsCards, AnalyticsChart, QueryClicksTable } from '../components';
+
import { QueryDetail } from './';
describe('QueryDetail', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.tsx
index 96587eb528710..f00c4e29a7190 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.tsx
@@ -6,10 +6,11 @@
*/
import React from 'react';
+
import { useValues } from 'kea';
-import { i18n } from '@kbn/i18n';
import { EuiSpacer } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome';
import { BreadcrumbTrail } from '../../../../shared/kibana_chrome/generate_breadcrumbs';
@@ -17,7 +18,7 @@ import { useDecodedParams } from '../../../utils/encode_path_params';
import { AnalyticsLayout } from '../analytics_layout';
import { AnalyticsSection, QueryClicksTable } from '../components';
-import { AnalyticsLogic, AnalyticsCards, AnalyticsChart, convertToChartData } from '../';
+import { AnalyticsLogic, AnalyticsCards, AnalyticsChart, convertToChartData } from '../index';
const QUERY_DETAIL_TITLE = i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.analytics.queryDetail.title',
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/recent_queries.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/recent_queries.test.tsx
index 40577fb2d4447..21d515a7b9795 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/recent_queries.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/recent_queries.test.tsx
@@ -8,9 +8,11 @@
import { setMockValues } from '../../../../__mocks__';
import React from 'react';
+
import { shallow } from 'enzyme';
import { RecentQueriesTable } from '../components';
+
import { RecentQueries } from './';
describe('RecentQueries', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/recent_queries.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/recent_queries.tsx
index e5380258894ae..bb0c3c4d32244 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/recent_queries.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/recent_queries.tsx
@@ -6,12 +6,13 @@
*/
import React from 'react';
+
import { useValues } from 'kea';
-import { RECENT_QUERIES } from '../constants';
+import { AnalyticsLogic } from '../';
import { AnalyticsLayout } from '../analytics_layout';
import { AnalyticsSearch, RecentQueriesTable } from '../components';
-import { AnalyticsLogic } from '../';
+import { RECENT_QUERIES } from '../constants';
export const RecentQueries: React.FC = () => {
const { recentQueries } = useValues(AnalyticsLogic);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries.test.tsx
index b037e6bf1d64e..46b2b37958435 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries.test.tsx
@@ -8,9 +8,11 @@
import { setMockValues } from '../../../../__mocks__';
import React from 'react';
+
import { shallow } from 'enzyme';
import { AnalyticsTable } from '../components';
+
import { TopQueries } from './';
describe('TopQueries', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries.tsx
index 76d523d16ee11..6459126560b3a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries.tsx
@@ -6,12 +6,13 @@
*/
import React from 'react';
+
import { useValues } from 'kea';
-import { TOP_QUERIES } from '../constants';
+import { AnalyticsLogic } from '../';
import { AnalyticsLayout } from '../analytics_layout';
import { AnalyticsSearch, AnalyticsTable } from '../components';
-import { AnalyticsLogic } from '../';
+import { TOP_QUERIES } from '../constants';
export const TopQueries: React.FC = () => {
const { topQueries } = useValues(AnalyticsLogic);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_clicks.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_clicks.test.tsx
index 1248a49fc5a9c..83212160d1350 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_clicks.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_clicks.test.tsx
@@ -8,9 +8,11 @@
import { setMockValues } from '../../../../__mocks__';
import React from 'react';
+
import { shallow } from 'enzyme';
import { AnalyticsTable } from '../components';
+
import { TopQueriesNoClicks } from './';
describe('TopQueriesNoClicks', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_clicks.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_clicks.tsx
index 604ab96b871e7..8e2591697feaa 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_clicks.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_clicks.tsx
@@ -6,12 +6,13 @@
*/
import React from 'react';
+
import { useValues } from 'kea';
-import { TOP_QUERIES_NO_CLICKS } from '../constants';
+import { AnalyticsLogic } from '../';
import { AnalyticsLayout } from '../analytics_layout';
import { AnalyticsSearch, AnalyticsTable } from '../components';
-import { AnalyticsLogic } from '../';
+import { TOP_QUERIES_NO_CLICKS } from '../constants';
export const TopQueriesNoClicks: React.FC = () => {
const { topQueriesNoClicks } = useValues(AnalyticsLogic);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_results.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_results.test.tsx
index 3cb77b3c7afbc..dfc5b9c93ab64 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_results.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_results.test.tsx
@@ -8,9 +8,11 @@
import { setMockValues } from '../../../../__mocks__';
import React from 'react';
+
import { shallow } from 'enzyme';
import { AnalyticsTable } from '../components';
+
import { TopQueriesNoResults } from './';
describe('TopQueriesNoResults', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_results.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_results.tsx
index 425fdf8e88559..e093a5130d204 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_results.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_no_results.tsx
@@ -6,12 +6,13 @@
*/
import React from 'react';
+
import { useValues } from 'kea';
-import { TOP_QUERIES_NO_RESULTS } from '../constants';
+import { AnalyticsLogic } from '../';
import { AnalyticsLayout } from '../analytics_layout';
import { AnalyticsSearch, AnalyticsTable } from '../components';
-import { AnalyticsLogic } from '../';
+import { TOP_QUERIES_NO_RESULTS } from '../constants';
export const TopQueriesNoResults: React.FC = () => {
const { topQueriesNoResults } = useValues(AnalyticsLogic);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_with_clicks.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_with_clicks.test.tsx
index 83be03e95d2cf..fb967ca06b387 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_with_clicks.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_with_clicks.test.tsx
@@ -8,9 +8,11 @@
import { setMockValues } from '../../../../__mocks__';
import React from 'react';
+
import { shallow } from 'enzyme';
import { AnalyticsTable } from '../components';
+
import { TopQueriesWithClicks } from './';
describe('TopQueriesWithClicks', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_with_clicks.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_with_clicks.tsx
index bec096019035b..87e276a8382c3 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_with_clicks.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/top_queries_with_clicks.tsx
@@ -6,12 +6,13 @@
*/
import React from 'react';
+
import { useValues } from 'kea';
-import { TOP_QUERIES_WITH_CLICKS } from '../constants';
+import { AnalyticsLogic } from '../';
import { AnalyticsLayout } from '../analytics_layout';
import { AnalyticsSearch, AnalyticsTable } from '../components';
-import { AnalyticsLogic } from '../';
+import { TOP_QUERIES_WITH_CLICKS } from '../constants';
export const TopQueriesWithClicks: React.FC = () => {
const { topQueriesWithClicks } = useValues(AnalyticsLogic);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts
index 2e28e5a272643..0fb118548a67b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts
@@ -6,6 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
+
import { DOCS_PREFIX } from '../../routes';
export const CREDENTIALS_TITLE = i18n.translate(
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.test.tsx
index cc783e7c056e2..48fcf4b8c5b66 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.test.tsx
@@ -9,12 +9,15 @@ import { setMockValues, setMockActions } from '../../../__mocks__/kea.mock';
import { unmountHandler } from '../../../__mocks__/shallow_useeffect.mock';
import React from 'react';
+
import { shallow } from 'enzyme';
-import { Credentials } from './credentials';
import { EuiCopy, EuiLoadingContent, EuiPageContentBody } from '@elastic/eui';
import { externalUrl } from '../../../shared/enterprise_search_url';
+
+import { Credentials } from './credentials';
+
import { CredentialsFlyout } from './credentials_flyout';
describe('Credentials', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.tsx
index 0266b64f97104..266e9467c300d 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials.tsx
@@ -6,6 +6,7 @@
*/
import React, { useEffect } from 'react';
+
import { useActions, useValues } from 'kea';
import {
@@ -24,14 +25,14 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
+import { externalUrl } from '../../../shared/enterprise_search_url/external_url';
import { FlashMessages } from '../../../shared/flash_messages';
+import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
-import { CredentialsLogic } from './credentials_logic';
-import { externalUrl } from '../../../shared/enterprise_search_url/external_url';
import { CREDENTIALS_TITLE } from './constants';
-import { CredentialsList } from './credentials_list';
import { CredentialsFlyout } from './credentials_flyout';
+import { CredentialsList } from './credentials_list';
+import { CredentialsLogic } from './credentials_logic';
export const Credentials: React.FC = () => {
const { initializeCredentialsData, resetCredentials, showCredentialsForm } = useActions(
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/body.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/body.test.tsx
index 8b5a59b82c19b..595bc1bcbb828 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/body.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/body.test.tsx
@@ -8,12 +8,15 @@
import { setMockValues, setMockActions } from '../../../../__mocks__/kea.mock';
import React from 'react';
+
import { shallow } from 'enzyme';
+
import { EuiFlyoutBody, EuiForm } from '@elastic/eui';
import { ApiTokenTypes } from '../constants';
import { defaultApiToken } from '../credentials_logic';
+import { CredentialsFlyoutBody } from './body';
import {
FormKeyName,
FormKeyType,
@@ -21,7 +24,6 @@ import {
FormKeyEngineAccess,
FormKeyUpdateWarning,
} from './form_components';
-import { CredentialsFlyoutBody } from './body';
describe('CredentialsFlyoutBody', () => {
const values = {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/body.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/body.tsx
index f3de25fe0a25d..def165f3f82a2 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/body.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/body.tsx
@@ -6,12 +6,14 @@
*/
import React from 'react';
+
import { useValues, useActions } from 'kea';
+
import { EuiFlyoutBody, EuiForm } from '@elastic/eui';
import { FlashMessages } from '../../../../shared/flash_messages';
-import { CredentialsLogic } from '../credentials_logic';
import { ApiTokenTypes } from '../constants';
+import { CredentialsLogic } from '../credentials_logic';
import {
FormKeyName,
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/footer.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/footer.test.tsx
index 036fe881c7d0d..23e85b92bb8b4 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/footer.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/footer.test.tsx
@@ -8,7 +8,9 @@
import { setMockValues, setMockActions } from '../../../../__mocks__/kea.mock';
import React from 'react';
+
import { shallow } from 'enzyme';
+
import { EuiFlyoutFooter, EuiButtonEmpty } from '@elastic/eui';
import { CredentialsFlyoutFooter } from './footer';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/footer.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/footer.tsx
index dc2d52a073b36..c05bd82c6206e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/footer.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/footer.tsx
@@ -6,7 +6,9 @@
*/
import React from 'react';
+
import { useValues, useActions } from 'kea';
+
import {
EuiFlyoutFooter,
EuiFlexGroup,
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.test.tsx
index 51a737ce8c826..7247deb09f12b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.test.tsx
@@ -8,7 +8,9 @@
import { setMockValues, setMockActions, rerender } from '../../../../../__mocks__';
import React from 'react';
+
import { shallow } from 'enzyme';
+
import { EuiRadio, EuiCheckbox } from '@elastic/eui';
import { FormKeyEngineAccess, EngineSelection } from './key_engine_access';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.tsx
index 2a9e8cf153dca..0d6ebfe437927 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_engine_access.tsx
@@ -6,7 +6,9 @@
*/
import React from 'react';
+
import { useValues, useActions } from 'kea';
+
import {
EuiFormRow,
EuiRadio,
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_name.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_name.test.tsx
index 27f95f2ba7cd8..d54d0c89c90cb 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_name.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_name.test.tsx
@@ -8,7 +8,9 @@
import { setMockValues, setMockActions } from '../../../../../__mocks__/kea.mock';
import React from 'react';
+
import { shallow } from 'enzyme';
+
import { EuiFieldText, EuiFormRow } from '@elastic/eui';
import { FormKeyName } from './';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_name.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_name.tsx
index cb4dce76dfcc1..f4f4f5f0aaaaa 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_name.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_name.tsx
@@ -6,7 +6,9 @@
*/
import React from 'react';
+
import { useValues, useActions } from 'kea';
+
import { EuiFormRow, EuiFieldText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.test.tsx
index 8cfa5b3c4571a..cf45576d691cf 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.test.tsx
@@ -8,7 +8,9 @@
import { setMockValues, setMockActions } from '../../../../../__mocks__/kea.mock';
import React from 'react';
+
import { shallow } from 'enzyme';
+
import { EuiCheckbox } from '@elastic/eui';
import { FormKeyReadWriteAccess } from './';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx
index f9653159b4403..0b631089c3984 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_read_write_access.tsx
@@ -6,7 +6,9 @@
*/
import React from 'react';
+
import { useValues, useActions } from 'kea';
+
import { EuiCheckbox, EuiText, EuiTitle, EuiSpacer, EuiPanel } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.test.tsx
index 9cf6c82184579..5de2c7fda53ca 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.test.tsx
@@ -8,10 +8,13 @@
import { setMockValues, setMockActions } from '../../../../../__mocks__/kea.mock';
import React from 'react';
+
import { shallow } from 'enzyme';
+
import { EuiSelect } from '@elastic/eui';
import { ApiTokenTypes, TOKEN_TYPE_INFO } from '../../constants';
+
import { FormKeyType } from './';
describe('FormKeyType', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.tsx
index a8cc16b3b30e7..60308274fbb76 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.tsx
@@ -6,13 +6,15 @@
*/
import React from 'react';
+
import { useValues, useActions } from 'kea';
+
import { EuiFormRow, EuiSelect, EuiText, EuiLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { AppLogic } from '../../../../app_logic';
-import { CredentialsLogic } from '../../credentials_logic';
import { TOKEN_TYPE_DESCRIPTION, TOKEN_TYPE_INFO, DOCS_HREF } from '../../constants';
+import { CredentialsLogic } from '../../credentials_logic';
export const FormKeyType: React.FC = () => {
const { myRole } = useValues(AppLogic);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_update_warning.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_update_warning.test.tsx
index 073c4ec1c92bf..38eec0b371576 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_update_warning.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_update_warning.test.tsx
@@ -6,7 +6,9 @@
*/
import React from 'react';
+
import { shallow } from 'enzyme';
+
import { EuiCallOut } from '@elastic/eui';
import { FormKeyUpdateWarning } from './';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_update_warning.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_update_warning.tsx
index 87cda9590f5cb..c24eebea9178b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_update_warning.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_update_warning.tsx
@@ -6,6 +6,7 @@
*/
import React from 'react';
+
import { EuiSpacer, EuiCallOut } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.test.tsx
index 0772a395dbe71..8ee7f810c1fa5 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.test.tsx
@@ -8,7 +8,9 @@
import { setMockValues } from '../../../../__mocks__/kea.mock';
import React from 'react';
+
import { shallow } from 'enzyme';
+
import { EuiFlyoutHeader } from '@elastic/eui';
import { ApiTokenTypes } from '../constants';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.tsx
index a9efcbe371c4f..586ddc5c22b97 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/header.tsx
@@ -6,12 +6,14 @@
*/
import React from 'react';
+
import { useValues } from 'kea';
+
import { EuiFlyoutHeader, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { CredentialsLogic } from '../credentials_logic';
import { FLYOUT_ARIA_LABEL_ID } from '../constants';
+import { CredentialsLogic } from '../credentials_logic';
export const CredentialsFlyoutHeader: React.FC = () => {
const { activeApiToken } = useValues(CredentialsLogic);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/index.test.tsx
index 1f7408857857a..9932b8ca227b0 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/index.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/index.test.tsx
@@ -8,7 +8,9 @@
import { setMockActions } from '../../../../__mocks__/kea.mock';
import React from 'react';
+
import { shallow } from 'enzyme';
+
import { EuiFlyout } from '@elastic/eui';
import { CredentialsFlyout } from './';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/index.tsx
index 1335a3cdeea18..2ee73a6b80b5a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/index.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/index.tsx
@@ -6,14 +6,17 @@
*/
import React from 'react';
+
import { useActions } from 'kea';
+
import { EuiPortal, EuiFlyout } from '@elastic/eui';
-import { CredentialsLogic } from '../credentials_logic';
import { FLYOUT_ARIA_LABEL_ID } from '../constants';
-import { CredentialsFlyoutHeader } from './header';
+import { CredentialsLogic } from '../credentials_logic';
+
import { CredentialsFlyoutBody } from './body';
import { CredentialsFlyoutFooter } from './footer';
+import { CredentialsFlyoutHeader } from './header';
export const CredentialsFlyout: React.FC = () => {
const { hideCredentialsForm } = useActions(CredentialsLogic);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx
index dd3d8ef8069ba..8c52df30bfc67 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx
@@ -8,15 +8,18 @@
import { setMockValues, setMockActions } from '../../../../__mocks__/kea.mock';
import React from 'react';
+
import { shallow } from 'enzyme';
+
import { EuiBasicTable, EuiCopy, EuiEmptyPrompt } from '@elastic/eui';
-import { ApiToken } from '../types';
+import { HiddenText } from '../../../../shared/hidden_text';
import { ApiTokenTypes } from '../constants';
+import { ApiToken } from '../types';
-import { HiddenText } from '../../../../shared/hidden_text';
import { Key } from './key';
-import { CredentialsList } from './credentials_list';
+
+import { CredentialsList } from './';
describe('Credentials', () => {
const apiToken: ApiToken = {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx
index 9d220469347f2..f23479017a680 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx
@@ -6,19 +6,21 @@
*/
import React, { useMemo } from 'react';
-import { EuiBasicTable, EuiBasicTableColumn, EuiCopy, EuiEmptyPrompt } from '@elastic/eui';
-import { CriteriaWithPagination } from '@elastic/eui/src/components/basic_table/basic_table';
+
import { useActions, useValues } from 'kea';
+import { EuiBasicTable, EuiBasicTableColumn, EuiCopy, EuiEmptyPrompt } from '@elastic/eui';
+import { CriteriaWithPagination } from '@elastic/eui/src/components/basic_table/basic_table';
import { i18n } from '@kbn/i18n';
-import { CredentialsLogic } from '../credentials_logic';
-import { Key } from './key';
import { HiddenText } from '../../../../shared/hidden_text';
-import { ApiToken } from '../types';
import { TOKEN_TYPE_DISPLAY_NAMES } from '../constants';
-import { apiTokenSort } from '../utils/api_token_sort';
+import { CredentialsLogic } from '../credentials_logic';
+import { ApiToken } from '../types';
import { getModeDisplayText, getEnginesDisplayText } from '../utils';
+import { apiTokenSort } from '../utils/api_token_sort';
+
+import { Key } from './key';
export const CredentialsList: React.FC = () => {
const { deleteApiKey, fetchCredentials, showCredentialsForm } = useActions(CredentialsLogic);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.test.tsx
index c18302db9ddfd..5e042319ae613 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.test.tsx
@@ -6,7 +6,9 @@
*/
import React from 'react';
+
import { shallow } from 'enzyme';
+
import { EuiButtonIcon } from '@elastic/eui';
import { Key } from './key';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx
index 940453c83a1fe..ff14379b9aecc 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx
@@ -6,6 +6,7 @@
*/
import React from 'react';
+
import { EuiButtonIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts
index 005f487772d80..c9d6a43ebbbae 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts
@@ -17,6 +17,7 @@ jest.mock('../../app_logic', () => ({
import { nextTick } from '@kbn/test/jest';
import { AppLogic } from '../../app_logic';
+
import { ApiTokenTypes } from './constants';
import { CredentialsLogic } from './credentials_logic';
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts
index 25cd1be93836d..ff4600872c589 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.ts
@@ -7,19 +7,19 @@
import { kea, MakeLogicType } from 'kea';
-import { formatApiName } from '../../utils/format_api_name';
-import { ApiTokenTypes, CREATE_MESSAGE, UPDATE_MESSAGE, DELETE_MESSAGE } from './constants';
-
-import { HttpLogic } from '../../../shared/http';
+import { Meta } from '../../../../../common/types';
import {
clearFlashMessages,
setSuccessMessage,
flashAPIErrors,
} from '../../../shared/flash_messages';
+import { HttpLogic } from '../../../shared/http';
import { AppLogic } from '../../app_logic';
-
-import { Meta } from '../../../../../common/types';
import { Engine } from '../../types';
+import { formatApiName } from '../../utils/format_api_name';
+
+import { ApiTokenTypes, CREATE_MESSAGE, UPDATE_MESSAGE, DELETE_MESSAGE } from './constants';
+
import { ApiToken, CredentialsDetails, TokenReadWrite } from './types';
export const defaultApiToken: ApiToken = {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/types.ts
index ddc81658eed2c..0427d25add49b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/types.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/types.ts
@@ -6,6 +6,7 @@
*/
import { Engine } from '../../types';
+
import { ApiTokenTypes } from './constants';
export interface CredentialsDetails {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.test.ts
index 1f84caa7e1ef7..70277d6cb7f22 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/api_token_sort.test.ts
@@ -5,11 +5,12 @@
* 2.0.
*/
-import { apiTokenSort } from '.';
import { ApiTokenTypes } from '../constants';
import { ApiToken } from '../types';
+import { apiTokenSort } from '.';
+
describe('apiTokenSort', () => {
const apiToken: ApiToken = {
name: '',
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.test.tsx
index e92957405a524..71d00efa2a868 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/utils/get_engines_display_text.test.tsx
@@ -6,22 +6,24 @@
*/
import React from 'react';
+
import { shallow } from 'enzyme';
-import { getEnginesDisplayText } from './get_engines_display_text';
-import { ApiToken } from '../types';
import { ApiTokenTypes } from '../constants';
+import { ApiToken } from '../types';
-const apiToken: ApiToken = {
- name: '',
- type: ApiTokenTypes.Private,
- read: true,
- write: true,
- access_all_engines: true,
- engines: ['engine1', 'engine2', 'engine3'],
-};
+import { getEnginesDisplayText } from './get_engines_display_text';
describe('getEnginesDisplayText', () => {
+ const apiToken: ApiToken = {
+ name: '',
+ type: ApiTokenTypes.Private,
+ read: true,
+ write: true,
+ access_all_engines: true,
+ engines: ['engine1', 'engine2', 'engine3'],
+ };
+
it('returns "--" when the token is an admin token', () => {
const wrapper = shallow(