diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 4e9954eb18a39..6519bf9c493f9 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -150,8 +150,11 @@
# Pulse
/packages/kbn-analytics/ @elastic/pulse
/src/legacy/core_plugins/ui_metric/ @elastic/pulse
+/src/plugins/telemetry/ @elastic/pulse
+/src/plugins/telemetry_collection_manager/ @elastic/pulse
+/src/plugins/telemetry_management_section/ @elastic/pulse
/src/plugins/usage_collection/ @elastic/pulse
-/x-pack/legacy/plugins/telemetry/ @elastic/pulse
+/x-pack/plugins/telemetry_collection_xpack/ @elastic/pulse
# Kibana Alerting Services
/x-pack/legacy/plugins/alerting/ @elastic/kibana-alerting-services
@@ -182,6 +185,7 @@
/x-pack/plugins/remote_clusters/ @elastic/es-ui
/x-pack/legacy/plugins/rollup/ @elastic/es-ui
/x-pack/plugins/searchprofiler/ @elastic/es-ui
+/x-pack/plugins/painless_lab/ @elastic/es-ui
/x-pack/legacy/plugins/snapshot_restore/ @elastic/es-ui
/x-pack/legacy/plugins/upgrade_assistant/ @elastic/es-ui
/x-pack/plugins/upgrade_assistant/ @elastic/es-ui
diff --git a/.gitignore b/.gitignore
index efb5c57774633..bd7a954f950e9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,3 +44,6 @@ package-lock.json
*.sublime-*
npm-debug.log*
.tern-project
+x-pack/legacy/plugins/apm/tsconfig.json
+apm.tsconfig.json
+/x-pack/legacy/plugins/apm/e2e/snapshots.js
diff --git a/.i18nrc.json b/.i18nrc.json
index bffe99bf3654b..78c4be6f4a356 100644
--- a/.i18nrc.json
+++ b/.i18nrc.json
@@ -3,6 +3,7 @@
"common.ui": "src/legacy/ui",
"console": "src/plugins/console",
"core": "src/core",
+ "discover": "src/plugins/discover",
"dashboard": "src/plugins/dashboard",
"data": "src/plugins/data",
"embeddableApi": "src/plugins/embeddable",
@@ -35,8 +36,8 @@
"server": "src/legacy/server",
"statusPage": "src/legacy/core_plugins/status_page",
"telemetry": [
- "src/legacy/core_plugins/telemetry",
- "src/plugins/telemetry"
+ "src/plugins/telemetry",
+ "src/plugins/telemetry_management_section"
],
"tileMap": "src/legacy/core_plugins/tile_map",
"timelion": ["src/legacy/core_plugins/timelion", "src/legacy/core_plugins/vis_type_timelion", "src/plugins/timelion"],
diff --git a/docs/development/core/public/kibana-plugin-core-public.app.mount.md b/docs/development/core/public/kibana-plugin-core-public.app.mount.md
index c42f73ced95af..8a9dfd9e2e972 100644
--- a/docs/development/core/public/kibana-plugin-core-public.app.mount.md
+++ b/docs/development/core/public/kibana-plugin-core-public.app.mount.md
@@ -14,5 +14,5 @@ mount: AppMount | AppMountDeprecated
## Remarks
-When function has two arguments, it will be called with a [context](./kibana-plugin-core-public.appmountcontext.md) as the first argument. This behavior is \*\*deprecated\*\*, and consumers should instead use [CoreSetup.getStartServices()](./kibana-plugin-core-public.coresetup.getstartservices.md).
+When function has two arguments, it will be called with a [context](./kibana-plugin-core-public.appmountcontext.md) as the first argument. This behavior is \*\*deprecated\*\*, and consumers should instead use [CoreSetup.getStartServices](./kibana-plugin-core-public.coresetup.getstartservices.md).
diff --git a/docs/development/core/public/kibana-plugin-core-public.applicationsetup.md b/docs/development/core/public/kibana-plugin-core-public.applicationsetup.md
index e5554be515077..fc99e2208220f 100644
--- a/docs/development/core/public/kibana-plugin-core-public.applicationsetup.md
+++ b/docs/development/core/public/kibana-plugin-core-public.applicationsetup.md
@@ -17,5 +17,5 @@ export interface ApplicationSetup
| --- | --- |
| [register(app)](./kibana-plugin-core-public.applicationsetup.register.md) | Register an mountable application to the system. |
| [registerAppUpdater(appUpdater$)](./kibana-plugin-core-public.applicationsetup.registerappupdater.md) | Register an application updater that can be used to change the [AppUpdatableFields](./kibana-plugin-core-public.appupdatablefields.md) fields of all applications at runtime.This is meant to be used by plugins that needs to updates the whole list of applications. To only updates a specific application, use the updater$
property of the registered application instead. |
-| [registerMountContext(contextName, provider)](./kibana-plugin-core-public.applicationsetup.registermountcontext.md) | Register a context provider for application mounting. Will only be available to applications that depend on the plugin that registered this context. Deprecated, use [CoreSetup.getStartServices()](./kibana-plugin-core-public.coresetup.getstartservices.md). |
+| [registerMountContext(contextName, provider)](./kibana-plugin-core-public.applicationsetup.registermountcontext.md) | Register a context provider for application mounting. Will only be available to applications that depend on the plugin that registered this context. Deprecated, use [CoreSetup.getStartServices](./kibana-plugin-core-public.coresetup.getstartservices.md). |
diff --git a/docs/development/core/public/kibana-plugin-core-public.applicationsetup.registermountcontext.md b/docs/development/core/public/kibana-plugin-core-public.applicationsetup.registermountcontext.md
index 92a7ae1c0deee..1735d5df943ae 100644
--- a/docs/development/core/public/kibana-plugin-core-public.applicationsetup.registermountcontext.md
+++ b/docs/development/core/public/kibana-plugin-core-public.applicationsetup.registermountcontext.md
@@ -8,7 +8,7 @@
>
>
-Register a context provider for application mounting. Will only be available to applications that depend on the plugin that registered this context. Deprecated, use [CoreSetup.getStartServices()](./kibana-plugin-core-public.coresetup.getstartservices.md).
+Register a context provider for application mounting. Will only be available to applications that depend on the plugin that registered this context. Deprecated, use [CoreSetup.getStartServices](./kibana-plugin-core-public.coresetup.getstartservices.md).
Signature:
diff --git a/docs/development/core/public/kibana-plugin-core-public.applicationstart.md b/docs/development/core/public/kibana-plugin-core-public.applicationstart.md
index 834411de5d57c..a93bc61bac527 100644
--- a/docs/development/core/public/kibana-plugin-core-public.applicationstart.md
+++ b/docs/development/core/public/kibana-plugin-core-public.applicationstart.md
@@ -24,5 +24,5 @@ export interface ApplicationStart
| --- | --- |
| [getUrlForApp(appId, options)](./kibana-plugin-core-public.applicationstart.geturlforapp.md) | Returns an URL to a given app, including the global base path. By default, the URL is relative (/basePath/app/my-app). Use the absolute
option to generate an absolute url (http://host:port/basePath/app/my-app)Note that when generating absolute urls, the protocol, host and port are determined from the browser location. |
| [navigateToApp(appId, options)](./kibana-plugin-core-public.applicationstart.navigatetoapp.md) | Navigate to a given app |
-| [registerMountContext(contextName, provider)](./kibana-plugin-core-public.applicationstart.registermountcontext.md) | Register a context provider for application mounting. Will only be available to applications that depend on the plugin that registered this context. Deprecated, use [CoreSetup.getStartServices()](./kibana-plugin-core-public.coresetup.getstartservices.md). |
+| [registerMountContext(contextName, provider)](./kibana-plugin-core-public.applicationstart.registermountcontext.md) | Register a context provider for application mounting. Will only be available to applications that depend on the plugin that registered this context. Deprecated, use [CoreSetup.getStartServices](./kibana-plugin-core-public.coresetup.getstartservices.md). |
diff --git a/docs/development/core/public/kibana-plugin-core-public.applicationstart.registermountcontext.md b/docs/development/core/public/kibana-plugin-core-public.applicationstart.registermountcontext.md
index 6e0fbb46e9a1e..11f661c4af2b3 100644
--- a/docs/development/core/public/kibana-plugin-core-public.applicationstart.registermountcontext.md
+++ b/docs/development/core/public/kibana-plugin-core-public.applicationstart.registermountcontext.md
@@ -8,7 +8,7 @@
>
>
-Register a context provider for application mounting. Will only be available to applications that depend on the plugin that registered this context. Deprecated, use [CoreSetup.getStartServices()](./kibana-plugin-core-public.coresetup.getstartservices.md).
+Register a context provider for application mounting. Will only be available to applications that depend on the plugin that registered this context. Deprecated, use [CoreSetup.getStartServices](./kibana-plugin-core-public.coresetup.getstartservices.md).
Signature:
diff --git a/docs/development/core/public/kibana-plugin-core-public.appmountcontext.md b/docs/development/core/public/kibana-plugin-core-public.appmountcontext.md
index d0b243859aab0..52a36b0b56f02 100644
--- a/docs/development/core/public/kibana-plugin-core-public.appmountcontext.md
+++ b/docs/development/core/public/kibana-plugin-core-public.appmountcontext.md
@@ -8,7 +8,7 @@
>
>
-The context object received when applications are mounted to the DOM. Deprecated, use [CoreSetup.getStartServices()](./kibana-plugin-core-public.coresetup.getstartservices.md).
+The context object received when applications are mounted to the DOM. Deprecated, use [CoreSetup.getStartServices](./kibana-plugin-core-public.coresetup.getstartservices.md).
Signature:
diff --git a/docs/development/core/public/kibana-plugin-core-public.appmountdeprecated.md b/docs/development/core/public/kibana-plugin-core-public.appmountdeprecated.md
index 130689882495a..66b8a69d84a38 100644
--- a/docs/development/core/public/kibana-plugin-core-public.appmountdeprecated.md
+++ b/docs/development/core/public/kibana-plugin-core-public.appmountdeprecated.md
@@ -18,5 +18,5 @@ export declare type AppMountDeprecated = (contex
## Remarks
-When function has two arguments, it will be called with a [context](./kibana-plugin-core-public.appmountcontext.md) as the first argument. This behavior is \*\*deprecated\*\*, and consumers should instead use [CoreSetup.getStartServices()](./kibana-plugin-core-public.coresetup.getstartservices.md).
+When function has two arguments, it will be called with a [context](./kibana-plugin-core-public.appmountcontext.md) as the first argument. This behavior is \*\*deprecated\*\*, and consumers should instead use [CoreSetup.getStartServices](./kibana-plugin-core-public.coresetup.getstartservices.md).
diff --git a/docs/development/core/public/kibana-plugin-core-public.coresetup.getstartservices.md b/docs/development/core/public/kibana-plugin-core-public.coresetup.getstartservices.md
index 91b906cf83d01..e4fec4eae31b1 100644
--- a/docs/development/core/public/kibana-plugin-core-public.coresetup.getstartservices.md
+++ b/docs/development/core/public/kibana-plugin-core-public.coresetup.getstartservices.md
@@ -2,16 +2,12 @@
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [CoreSetup](./kibana-plugin-core-public.coresetup.md) > [getStartServices](./kibana-plugin-core-public.coresetup.getstartservices.md)
-## CoreSetup.getStartServices() method
+## CoreSetup.getStartServices property
-Allows plugins to get access to APIs available in start inside async handlers, such as [App.mount](./kibana-plugin-core-public.app.mount.md). Promise will not resolve until Core and plugin dependencies have completed `start`.
+[StartServicesAccessor](./kibana-plugin-core-public.startservicesaccessor.md)
Signature:
```typescript
-getStartServices(): Promise<[CoreStart, TPluginsStart]>;
+getStartServices: StartServicesAccessor;
```
-Returns:
-
-`Promise<[CoreStart, TPluginsStart]>`
-
diff --git a/docs/development/core/public/kibana-plugin-core-public.coresetup.md b/docs/development/core/public/kibana-plugin-core-public.coresetup.md
index f211b740e84a3..c039bc19348cc 100644
--- a/docs/development/core/public/kibana-plugin-core-public.coresetup.md
+++ b/docs/development/core/public/kibana-plugin-core-public.coresetup.md
@@ -19,14 +19,9 @@ export interface CoreSetup
| [application](./kibana-plugin-core-public.coresetup.application.md) | ApplicationSetup
| [ApplicationSetup](./kibana-plugin-core-public.applicationsetup.md) |
| [context](./kibana-plugin-core-public.coresetup.context.md) | ContextSetup
| [ContextSetup](./kibana-plugin-core-public.contextsetup.md) |
| [fatalErrors](./kibana-plugin-core-public.coresetup.fatalerrors.md) | FatalErrorsSetup
| [FatalErrorsSetup](./kibana-plugin-core-public.fatalerrorssetup.md) |
+| [getStartServices](./kibana-plugin-core-public.coresetup.getstartservices.md) | StartServicesAccessor<TPluginsStart>
| [StartServicesAccessor](./kibana-plugin-core-public.startservicesaccessor.md) |
| [http](./kibana-plugin-core-public.coresetup.http.md) | HttpSetup
| [HttpSetup](./kibana-plugin-core-public.httpsetup.md) |
| [injectedMetadata](./kibana-plugin-core-public.coresetup.injectedmetadata.md) | {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
}
| exposed temporarily until https://github.com/elastic/kibana/issues/41990 done use \*only\* to retrieve config values. There is no way to set injected values in the new platform. Use the legacy platform API instead. |
| [notifications](./kibana-plugin-core-public.coresetup.notifications.md) | NotificationsSetup
| [NotificationsSetup](./kibana-plugin-core-public.notificationssetup.md) |
| [uiSettings](./kibana-plugin-core-public.coresetup.uisettings.md) | IUiSettingsClient
| [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) |
-## Methods
-
-| Method | Description |
-| --- | --- |
-| [getStartServices()](./kibana-plugin-core-public.coresetup.getstartservices.md) | Allows plugins to get access to APIs available in start inside async handlers, such as [App.mount](./kibana-plugin-core-public.app.mount.md). Promise will not resolve until Core and plugin dependencies have completed start
. |
-
diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md
index b8aa56eb2941b..adc87de2b9e7e 100644
--- a/docs/development/core/public/kibana-plugin-core-public.md
+++ b/docs/development/core/public/kibana-plugin-core-public.md
@@ -38,7 +38,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [AppLeaveDefaultAction](./kibana-plugin-core-public.appleavedefaultaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-core-public.appleavehandler.md) to execute the default behaviour when leaving the application.See |
| [ApplicationSetup](./kibana-plugin-core-public.applicationsetup.md) | |
| [ApplicationStart](./kibana-plugin-core-public.applicationstart.md) | |
-| [AppMountContext](./kibana-plugin-core-public.appmountcontext.md) | The context object received when applications are mounted to the DOM. Deprecated, use [CoreSetup.getStartServices()](./kibana-plugin-core-public.coresetup.getstartservices.md). |
+| [AppMountContext](./kibana-plugin-core-public.appmountcontext.md) | The context object received when applications are mounted to the DOM. Deprecated, use [CoreSetup.getStartServices](./kibana-plugin-core-public.coresetup.getstartservices.md). |
| [AppMountParameters](./kibana-plugin-core-public.appmountparameters.md) | |
| [Capabilities](./kibana-plugin-core-public.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. |
| [ChromeBadge](./kibana-plugin-core-public.chromebadge.md) | |
@@ -153,6 +153,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value |
| [SavedObjectAttributeSingle](./kibana-plugin-core-public.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) |
| [SavedObjectsClientContract](./kibana-plugin-core-public.savedobjectsclientcontract.md) | SavedObjectsClientContract as implemented by the [SavedObjectsClient](./kibana-plugin-core-public.savedobjectsclient.md) |
+| [StartServicesAccessor](./kibana-plugin-core-public.startservicesaccessor.md) | Allows plugins to get access to APIs available in start inside async handlers, such as [App.mount](./kibana-plugin-core-public.app.mount.md). Promise will not resolve until Core and plugin dependencies have completed start
. |
| [StringValidation](./kibana-plugin-core-public.stringvalidation.md) | Allows regex objects or a regex string |
| [Toast](./kibana-plugin-core-public.toast.md) | |
| [ToastInput](./kibana-plugin-core-public.toastinput.md) | Inputs for [IToasts](./kibana-plugin-core-public.itoasts.md) APIs. |
diff --git a/docs/development/core/public/kibana-plugin-core-public.startservicesaccessor.md b/docs/development/core/public/kibana-plugin-core-public.startservicesaccessor.md
new file mode 100644
index 0000000000000..02e896a6b47e5
--- /dev/null
+++ b/docs/development/core/public/kibana-plugin-core-public.startservicesaccessor.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [StartServicesAccessor](./kibana-plugin-core-public.startservicesaccessor.md)
+
+## StartServicesAccessor type
+
+Allows plugins to get access to APIs available in start inside async handlers, such as [App.mount](./kibana-plugin-core-public.app.mount.md). Promise will not resolve until Core and plugin dependencies have completed `start`.
+
+Signature:
+
+```typescript
+export declare type StartServicesAccessor = () => Promise<[CoreStart, TPluginsStart]>;
+```
diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.getstartservices.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.getstartservices.md
index 10a656363c0d0..ea8e610ee56de 100644
--- a/docs/development/core/server/kibana-plugin-core-server.coresetup.getstartservices.md
+++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.getstartservices.md
@@ -2,16 +2,12 @@
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreSetup](./kibana-plugin-core-server.coresetup.md) > [getStartServices](./kibana-plugin-core-server.coresetup.getstartservices.md)
-## CoreSetup.getStartServices() method
+## CoreSetup.getStartServices property
-Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed `start`. This should only be used inside handlers registered during `setup` that will only be executed after `start` lifecycle.
+[StartServicesAccessor](./kibana-plugin-core-server.startservicesaccessor.md)
Signature:
```typescript
-getStartServices(): Promise<[CoreStart, TPluginsStart]>;
+getStartServices: StartServicesAccessor;
```
-Returns:
-
-`Promise<[CoreStart, TPluginsStart]>`
-
diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.md
index 5b5803629cc86..b0eba8ac78063 100644
--- a/docs/development/core/server/kibana-plugin-core-server.coresetup.md
+++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.md
@@ -19,15 +19,10 @@ export interface CoreSetup
| [capabilities](./kibana-plugin-core-server.coresetup.capabilities.md) | CapabilitiesSetup
| [CapabilitiesSetup](./kibana-plugin-core-server.capabilitiessetup.md) |
| [context](./kibana-plugin-core-server.coresetup.context.md) | ContextSetup
| [ContextSetup](./kibana-plugin-core-server.contextsetup.md) |
| [elasticsearch](./kibana-plugin-core-server.coresetup.elasticsearch.md) | ElasticsearchServiceSetup
| [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) |
+| [getStartServices](./kibana-plugin-core-server.coresetup.getstartservices.md) | StartServicesAccessor<TPluginsStart>
| [StartServicesAccessor](./kibana-plugin-core-server.startservicesaccessor.md) |
| [http](./kibana-plugin-core-server.coresetup.http.md) | HttpServiceSetup
| [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) |
| [metrics](./kibana-plugin-core-server.coresetup.metrics.md) | MetricsServiceSetup
| [MetricsServiceSetup](./kibana-plugin-core-server.metricsservicesetup.md) |
| [savedObjects](./kibana-plugin-core-server.coresetup.savedobjects.md) | SavedObjectsServiceSetup
| [SavedObjectsServiceSetup](./kibana-plugin-core-server.savedobjectsservicesetup.md) |
| [uiSettings](./kibana-plugin-core-server.coresetup.uisettings.md) | UiSettingsServiceSetup
| [UiSettingsServiceSetup](./kibana-plugin-core-server.uisettingsservicesetup.md) |
| [uuid](./kibana-plugin-core-server.coresetup.uuid.md) | UuidServiceSetup
| [UuidServiceSetup](./kibana-plugin-core-server.uuidservicesetup.md) |
-## Methods
-
-| Method | Description |
-| --- | --- |
-| [getStartServices()](./kibana-plugin-core-server.coresetup.getstartservices.md) | Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed start
. This should only be used inside handlers registered during setup
that will only be executed after start
lifecycle. |
-
diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md
index 54cf496b2d6af..a1158dc853918 100644
--- a/docs/development/core/server/kibana-plugin-core-server.md
+++ b/docs/development/core/server/kibana-plugin-core-server.md
@@ -259,6 +259,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [SavedObjectsFieldMapping](./kibana-plugin-core-server.savedobjectsfieldmapping.md) | Describe a [saved object type mapping](./kibana-plugin-core-server.savedobjectstypemappingdefinition.md) field.Please refer to [elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html) For the mapping documentation |
| [ScopeableRequest](./kibana-plugin-core-server.scopeablerequest.md) | A user credentials container. It accommodates the necessary auth credentials to impersonate the current user.See [KibanaRequest](./kibana-plugin-core-server.kibanarequest.md). |
| [SharedGlobalConfig](./kibana-plugin-core-server.sharedglobalconfig.md) | |
+| [StartServicesAccessor](./kibana-plugin-core-server.startservicesaccessor.md) | Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed start
. This should only be used inside handlers registered during setup
that will only be executed after start
lifecycle. |
| [StringValidation](./kibana-plugin-core-server.stringvalidation.md) | Allows regex objects or a regex string |
| [UiSettingsType](./kibana-plugin-core-server.uisettingstype.md) | UI element type to represent the settings. |
diff --git a/docs/development/core/server/kibana-plugin-core-server.startservicesaccessor.md b/docs/development/core/server/kibana-plugin-core-server.startservicesaccessor.md
new file mode 100644
index 0000000000000..4de781fc99cc1
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-core-server.startservicesaccessor.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [StartServicesAccessor](./kibana-plugin-core-server.startservicesaccessor.md)
+
+## StartServicesAccessor type
+
+Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed `start`. This should only be used inside handlers registered during `setup` that will only be executed after `start` lifecycle.
+
+Signature:
+
+```typescript
+export declare type StartServicesAccessor = () => Promise<[CoreStart, TPluginsStart]>;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
index ea77d6f39389b..6964c070097c5 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md
@@ -18,7 +18,9 @@
| [IndexPatternSelect](./kibana-plugin-plugins-data-public.indexpatternselect.md) | |
| [OptionedParamType](./kibana-plugin-plugins-data-public.optionedparamtype.md) | |
| [Plugin](./kibana-plugin-plugins-data-public.plugin.md) | |
+| [RequestTimeoutError](./kibana-plugin-plugins-data-public.requesttimeouterror.md) | Class used to signify that a request timed out. Useful for applications to conditionally handle this type of error differently than other errors. |
| [SearchError](./kibana-plugin-plugins-data-public.searcherror.md) | |
+| [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) | |
| [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) | |
| [TimeHistory](./kibana-plugin-plugins-data-public.timehistory.md) | |
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.requesttimeouterror._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.requesttimeouterror._constructor_.md
new file mode 100644
index 0000000000000..25e472817b46d
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.requesttimeouterror._constructor_.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [RequestTimeoutError](./kibana-plugin-plugins-data-public.requesttimeouterror.md) > [(constructor)](./kibana-plugin-plugins-data-public.requesttimeouterror._constructor_.md)
+
+## RequestTimeoutError.(constructor)
+
+Constructs a new instance of the `RequestTimeoutError` class
+
+Signature:
+
+```typescript
+constructor(message?: string);
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| message | string
| |
+
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.requesttimeouterror.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.requesttimeouterror.md
new file mode 100644
index 0000000000000..84b2fc3fe0b17
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.requesttimeouterror.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [RequestTimeoutError](./kibana-plugin-plugins-data-public.requesttimeouterror.md)
+
+## RequestTimeoutError class
+
+Class used to signify that a request timed out. Useful for applications to conditionally handle this type of error differently than other errors.
+
+Signature:
+
+```typescript
+export declare class RequestTimeoutError extends Error
+```
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)(message)](./kibana-plugin-plugins-data-public.requesttimeouterror._constructor_.md) | | Constructs a new instance of the RequestTimeoutError
class |
+
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor._constructor_.md
new file mode 100644
index 0000000000000..6eabefb9eb912
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor._constructor_.md
@@ -0,0 +1,22 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [(constructor)](./kibana-plugin-plugins-data-public.searchinterceptor._constructor_.md)
+
+## SearchInterceptor.(constructor)
+
+This class should be instantiated with a `requestTimeout` corresponding with how many ms after requests are initiated that they should automatically cancel.
+
+Signature:
+
+```typescript
+constructor(toasts: ToastsStart, application: ApplicationStart, requestTimeout?: number | undefined);
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| toasts | ToastsStart
| |
+| application | ApplicationStart
| |
+| requestTimeout | number | undefined
| |
+
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.abortcontroller.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.abortcontroller.md
new file mode 100644
index 0000000000000..0451a2254dc40
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.abortcontroller.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [abortController](./kibana-plugin-plugins-data-public.searchinterceptor.abortcontroller.md)
+
+## SearchInterceptor.abortController property
+
+`abortController` used to signal all searches to abort.
+
+Signature:
+
+```typescript
+protected abortController: AbortController;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.application.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.application.md
new file mode 100644
index 0000000000000..e44910161aa60
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.application.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [application](./kibana-plugin-plugins-data-public.searchinterceptor.application.md)
+
+## SearchInterceptor.application property
+
+Signature:
+
+```typescript
+protected readonly application: ApplicationStart;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md
new file mode 100644
index 0000000000000..59b107c92424f
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [getPendingCount$](./kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md)
+
+## SearchInterceptor.getPendingCount$ property
+
+Returns an `Observable` over the current number of pending searches. This could mean that one of the search requests is still in flight, or that it has only received partial responses.
+
+Signature:
+
+```typescript
+getPendingCount$: () => import("rxjs").Observable;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.hidetoast.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.hidetoast.md
new file mode 100644
index 0000000000000..59938a755a99e
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.hidetoast.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [hideToast](./kibana-plugin-plugins-data-public.searchinterceptor.hidetoast.md)
+
+## SearchInterceptor.hideToast property
+
+Signature:
+
+```typescript
+protected hideToast: () => void;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.longrunningtoast.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.longrunningtoast.md
new file mode 100644
index 0000000000000..5799039de91bc
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.longrunningtoast.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [longRunningToast](./kibana-plugin-plugins-data-public.searchinterceptor.longrunningtoast.md)
+
+## SearchInterceptor.longRunningToast property
+
+The current long-running toast (if there is one).
+
+Signature:
+
+```typescript
+protected longRunningToast?: Toast;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md
new file mode 100644
index 0000000000000..0c7b123be72af
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.md
@@ -0,0 +1,33 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md)
+
+## SearchInterceptor class
+
+Signature:
+
+```typescript
+export declare class SearchInterceptor
+```
+
+## Constructors
+
+| Constructor | Modifiers | Description |
+| --- | --- | --- |
+| [(constructor)(toasts, application, requestTimeout)](./kibana-plugin-plugins-data-public.searchinterceptor._constructor_.md) | | This class should be instantiated with a requestTimeout
corresponding with how many ms after requests are initiated that they should automatically cancel. |
+
+## Properties
+
+| Property | Modifiers | Type | Description |
+| --- | --- | --- | --- |
+| [abortController](./kibana-plugin-plugins-data-public.searchinterceptor.abortcontroller.md) | | AbortController
| abortController
used to signal all searches to abort. |
+| [application](./kibana-plugin-plugins-data-public.searchinterceptor.application.md) | | ApplicationStart
| |
+| [getPendingCount$](./kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md) | | () => import("rxjs").Observable<number>
| Returns an Observable
over the current number of pending searches. This could mean that one of the search requests is still in flight, or that it has only received partial responses. |
+| [hideToast](./kibana-plugin-plugins-data-public.searchinterceptor.hidetoast.md) | | () => void
| |
+| [longRunningToast](./kibana-plugin-plugins-data-public.searchinterceptor.longrunningtoast.md) | | Toast
| The current long-running toast (if there is one). |
+| [requestTimeout](./kibana-plugin-plugins-data-public.searchinterceptor.requesttimeout.md) | | number | undefined
| |
+| [search](./kibana-plugin-plugins-data-public.searchinterceptor.search.md) | | (search: ISearchGeneric, request: IKibanaSearchRequest, options?: ISearchOptions | undefined) => import("rxjs").Observable<import("../../common/search").IEsSearchResponse<unknown>>
| Searches using the given search
method. Overrides the AbortSignal
with one that will abort either when cancelPending
is called, when the request times out, or when the original AbortSignal
is aborted. Updates the pendingCount
when the request is started/finalized. |
+| [showToast](./kibana-plugin-plugins-data-public.searchinterceptor.showtoast.md) | | () => void
| |
+| [timeoutSubscriptions](./kibana-plugin-plugins-data-public.searchinterceptor.timeoutsubscriptions.md) | | Set<Subscription>
| The subscriptions from scheduling the automatic timeout for each request. |
+| [toasts](./kibana-plugin-plugins-data-public.searchinterceptor.toasts.md) | | ToastsStart
| |
+
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.requesttimeout.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.requesttimeout.md
new file mode 100644
index 0000000000000..3123433762991
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.requesttimeout.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [requestTimeout](./kibana-plugin-plugins-data-public.searchinterceptor.requesttimeout.md)
+
+## SearchInterceptor.requestTimeout property
+
+Signature:
+
+```typescript
+protected readonly requestTimeout?: number | undefined;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md
new file mode 100644
index 0000000000000..80c98ab84fb40
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [search](./kibana-plugin-plugins-data-public.searchinterceptor.search.md)
+
+## SearchInterceptor.search property
+
+Searches using the given `search` method. Overrides the `AbortSignal` with one that will abort either when `cancelPending` is called, when the request times out, or when the original `AbortSignal` is aborted. Updates the `pendingCount` when the request is started/finalized.
+
+Signature:
+
+```typescript
+search: (search: ISearchGeneric, request: IKibanaSearchRequest, options?: ISearchOptions | undefined) => import("rxjs").Observable>;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.showtoast.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.showtoast.md
new file mode 100644
index 0000000000000..e495c72b57215
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.showtoast.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [showToast](./kibana-plugin-plugins-data-public.searchinterceptor.showtoast.md)
+
+## SearchInterceptor.showToast property
+
+Signature:
+
+```typescript
+protected showToast: () => void;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.timeoutsubscriptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.timeoutsubscriptions.md
new file mode 100644
index 0000000000000..072f67591f097
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.timeoutsubscriptions.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [timeoutSubscriptions](./kibana-plugin-plugins-data-public.searchinterceptor.timeoutsubscriptions.md)
+
+## SearchInterceptor.timeoutSubscriptions property
+
+The subscriptions from scheduling the automatic timeout for each request.
+
+Signature:
+
+```typescript
+protected timeoutSubscriptions: Set;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.toasts.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.toasts.md
new file mode 100644
index 0000000000000..4953d17c89c39
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.toasts.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [toasts](./kibana-plugin-plugins-data-public.searchinterceptor.toasts.md)
+
+## SearchInterceptor.toasts property
+
+Signature:
+
+```typescript
+protected readonly toasts: ToastsStart;
+```
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md
index d179b9d9dcd82..e756eb9b72905 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md
@@ -60,6 +60,7 @@
| [esQuery](./kibana-plugin-plugins-data-server.esquery.md) | |
| [fieldFormats](./kibana-plugin-plugins-data-server.fieldformats.md) | |
| [indexPatterns](./kibana-plugin-plugins-data-server.indexpatterns.md) | |
+| [search](./kibana-plugin-plugins-data-server.search.md) | |
## Type Aliases
@@ -69,5 +70,6 @@
| [IFieldFormatsRegistry](./kibana-plugin-plugins-data-server.ifieldformatsregistry.md) | |
| [ISearch](./kibana-plugin-plugins-data-server.isearch.md) | |
| [ISearchCancel](./kibana-plugin-plugins-data-server.isearchcancel.md) | |
+| [ParsedInterval](./kibana-plugin-plugins-data-server.parsedinterval.md) | |
| [TSearchStrategyProvider](./kibana-plugin-plugins-data-server.tsearchstrategyprovider.md) | Search strategy provider creates an instance of a search strategy with the request handler context bound to it. This way every search strategy can use whatever information they require from the request context. |
diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc
index 71bb7b81ea420..a72c15190840a 100644
--- a/docs/setup/settings.asciidoc
+++ b/docs/setup/settings.asciidoc
@@ -193,7 +193,7 @@ that feature would not take any effect.
`logging.rotate.everyBytes:`:: [experimental] *Default: 10485760* The maximum size of a log file (that is `not an exact` limit). After the
limit is reached, a new log file is generated. The default size limit is 10485760 (10 MB) and
-this option should be in the range of 102400 (100KB) to 1073741824 (1GB).
+this option should be in the range of 1048576 (1 MB) to 1073741824 (1 GB).
`logging.rotate.keepFiles:`:: [experimental] *Default: 7* The number of most recent rotated log files to keep
on disk. Older files are deleted during log rotation. The default value is 7. The `logging.rotate.keepFiles`
@@ -203,7 +203,7 @@ option has to be in the range of 2 to 1024 files.
the `logging.rotate.usePolling` is enabled. That option has to be in the range of 5000 to 3600000 milliseconds.
`logging.rotate.usePolling:`:: [experimental] *Default: false* By default we try to understand the best way to monitoring
-the log file. However, there is some systems where it could not be always accurate. In those cases, if needed,
+the log file and warning about it. Please be aware there are some systems where watch api is not accurate. In those cases, in order to get the feature working,
the `polling` method could be used enabling that option.
`logging.silent:`:: *Default: false* Set the value of this setting to `true` to
diff --git a/examples/alerting_example/public/application.tsx b/examples/alerting_example/public/application.tsx
index d71db92d3d421..6ff5a7d0880b8 100644
--- a/examples/alerting_example/public/application.tsx
+++ b/examples/alerting_example/public/application.tsx
@@ -25,6 +25,7 @@ import {
AppMountParameters,
CoreStart,
IUiSettingsClient,
+ DocLinksStart,
ToastsSetup,
} from '../../../src/core/public';
import { DataPublicPluginStart } from '../../../src/plugins/data/public';
@@ -45,6 +46,7 @@ export interface AlertingExampleComponentParams {
data: DataPublicPluginStart;
charts: ChartsPluginStart;
uiSettings: IUiSettingsClient;
+ docLinks: DocLinksStart;
toastNotifications: ToastsSetup;
}
@@ -88,7 +90,7 @@ const AlertingExampleApp = (deps: AlertingExampleComponentParams) => {
};
export const renderApp = (
- { application, notifications, http, uiSettings }: CoreStart,
+ { application, notifications, http, uiSettings, docLinks }: CoreStart,
deps: AlertingExamplePublicStartDeps,
{ appBasePath, element }: AppMountParameters
) => {
@@ -99,6 +101,7 @@ export const renderApp = (
toastNotifications={notifications.toasts}
http={http}
uiSettings={uiSettings}
+ docLinks={docLinks}
{...deps}
/>,
element
diff --git a/examples/alerting_example/public/components/create_alert.tsx b/examples/alerting_example/public/components/create_alert.tsx
index 65b8a9412dcda..0541e0b18a2e1 100644
--- a/examples/alerting_example/public/components/create_alert.tsx
+++ b/examples/alerting_example/public/components/create_alert.tsx
@@ -33,6 +33,7 @@ export const CreateAlert = ({
triggers_actions_ui,
charts,
uiSettings,
+ docLinks,
data,
toastNotifications,
}: AlertingExampleComponentParams) => {
@@ -56,6 +57,7 @@ export const CreateAlert = ({
alertTypeRegistry: triggers_actions_ui.alertTypeRegistry,
toastNotifications,
uiSettings,
+ docLinks,
charts,
dataFieldsFormats: data.fieldFormats,
}}
diff --git a/package.json b/package.json
index 3421bf938cd80..4baffa8719fe3 100644
--- a/package.json
+++ b/package.json
@@ -120,7 +120,7 @@
"@elastic/apm-rum": "^4.6.0",
"@elastic/charts": "^18.1.0",
"@elastic/datemath": "5.0.2",
- "@elastic/ems-client": "7.7.0",
+ "@elastic/ems-client": "7.7.1",
"@elastic/eui": "21.0.1",
"@elastic/filesaver": "1.1.2",
"@elastic/good": "8.1.1-kibana2",
@@ -162,7 +162,7 @@
"color": "1.0.3",
"commander": "3.0.2",
"compare-versions": "3.5.1",
- "core-js": "^3.2.1",
+ "core-js": "^3.6.4",
"css-loader": "^3.4.2",
"d3": "3.5.17",
"d3-cloud": "1.2.5",
@@ -190,7 +190,7 @@
"hjson": "3.2.1",
"hoek": "^5.0.4",
"http-proxy-agent": "^2.1.0",
- "https-proxy-agent": "^2.2.2",
+ "https-proxy-agent": "^5.0.0",
"immer": "^1.5.0",
"inert": "^5.1.0",
"inline-style": "^2.0.0",
@@ -348,7 +348,7 @@
"@types/lru-cache": "^5.1.0",
"@types/markdown-it": "^0.0.7",
"@types/minimatch": "^2.0.29",
- "@types/mocha": "^5.2.7",
+ "@types/mocha": "^7.0.2",
"@types/moment-timezone": "^0.5.12",
"@types/mustache": "^0.8.31",
"@types/node": ">=10.17.17 <10.20.0",
@@ -443,7 +443,7 @@
"jest": "^24.9.0",
"jest-cli": "^24.9.0",
"jest-raw-loader": "^1.0.1",
- "jimp": "0.8.4",
+ "jimp": "^0.9.6",
"json5": "^1.0.1",
"karma": "3.1.4",
"karma-chrome-launcher": "2.2.0",
@@ -456,7 +456,7 @@
"license-checker": "^16.0.0",
"listr": "^0.14.1",
"load-grunt-config": "^3.0.1",
- "mocha": "^6.2.2",
+ "mocha": "^7.1.1",
"mock-http-server": "1.3.0",
"multistream": "^2.1.1",
"murmurhash3js": "3.0.1",
diff --git a/packages/kbn-es/src/artifact.js b/packages/kbn-es/src/artifact.js
index 9ea78386269d9..83dcd1cf36d2e 100644
--- a/packages/kbn-es/src/artifact.js
+++ b/packages/kbn-es/src/artifact.js
@@ -117,11 +117,14 @@ async function getArtifactSpecForSnapshot(urlVersion, license, log) {
const manifest = JSON.parse(json);
const platform = process.platform === 'win32' ? 'windows' : process.platform;
+ const arch = process.arch === 'arm64' ? 'aarch64' : 'x86_64';
+
const archive = manifest.archives.find(
archive =>
archive.version === desiredVersion &&
archive.platform === platform &&
- archive.license === desiredLicense
+ archive.license === desiredLicense &&
+ archive.architecture === arch
);
if (!archive) {
diff --git a/packages/kbn-es/src/artifact.test.js b/packages/kbn-es/src/artifact.test.js
index 453eb1a9a7689..02e4d5318f63f 100644
--- a/packages/kbn-es/src/artifact.test.js
+++ b/packages/kbn-es/src/artifact.test.js
@@ -28,6 +28,7 @@ const log = new ToolingLog();
let MOCKS;
const PLATFORM = process.platform === 'win32' ? 'windows' : process.platform;
+const ARCHITECTURE = process.arch === 'arm64' ? 'aarch64' : 'x86_64';
const MOCK_VERSION = 'test-version';
const MOCK_URL = 'http://127.0.0.1:12345';
const MOCK_FILENAME = 'test-filename';
@@ -38,13 +39,15 @@ const PERMANENT_SNAPSHOT_BASE_URL =
const createArchive = (params = {}) => {
const license = params.license || 'default';
+ const architecture = params.architecture || ARCHITECTURE;
return {
license: 'default',
+ architecture,
version: MOCK_VERSION,
url: MOCK_URL + `/${license}`,
platform: PLATFORM,
- filename: MOCK_FILENAME + `.${license}`,
+ filename: MOCK_FILENAME + `-${architecture}.${license}`,
...params,
};
};
@@ -77,6 +80,12 @@ beforeEach(() => {
valid: {
archives: [createArchive({ license: 'oss' }), createArchive({ license: 'default' })],
},
+ multipleArch: {
+ archives: [
+ createArchive({ architecture: 'fake_arch', license: 'oss' }),
+ createArchive({ architecture: ARCHITECTURE, license: 'oss' }),
+ ],
+ },
};
});
@@ -95,7 +104,7 @@ const artifactTest = (requestedLicense, expectedLicense, fetchTimesCalled = 1) =
expect(artifact.getUrl()).toEqual(MOCK_URL + `/${expectedLicense}`);
expect(artifact.getChecksumUrl()).toEqual(MOCK_URL + `/${expectedLicense}.sha512`);
expect(artifact.getChecksumType()).toEqual('sha512');
- expect(artifact.getFilename()).toEqual(MOCK_FILENAME + `.${expectedLicense}`);
+ expect(artifact.getFilename()).toEqual(MOCK_FILENAME + `-${ARCHITECTURE}.${expectedLicense}`);
};
};
@@ -153,6 +162,17 @@ describe('Artifact', () => {
});
});
+ describe('with snapshots for multiple architectures', () => {
+ beforeEach(() => {
+ mockFetch(MOCKS.multipleArch);
+ });
+
+ it('should return artifact metadata for the correct architecture', async () => {
+ const artifact = await Artifact.getSnapshot('oss', MOCK_VERSION, log);
+ expect(artifact.getFilename()).toEqual(MOCK_FILENAME + `-${ARCHITECTURE}.oss`);
+ });
+ });
+
describe('with custom snapshot manifest URL', () => {
const CUSTOM_URL = 'http://www.creedthoughts.gov.www/creedthoughts';
diff --git a/packages/kbn-spec-to-console/README.md b/packages/kbn-spec-to-console/README.md
index 6729f03b3d4db..bf60afd88f494 100644
--- a/packages/kbn-spec-to-console/README.md
+++ b/packages/kbn-spec-to-console/README.md
@@ -23,10 +23,10 @@ At the root of the Kibana repository, run the following commands:
```sh
# OSS
-yarn spec_to_console -g "/rest-api-spec/src/main/resources/rest-api-spec/api/*" -d "src/legacy/core_plugins/console/server/api_server/spec/generated"
+yarn spec_to_console -g "/rest-api-spec/src/main/resources/rest-api-spec/api/*" -d "src/plugins/console/server/lib/spec_definitions/json"
# X-pack
-yarn spec_to_console -g "/x-pack/plugin/src/test/resources/rest-api-spec/api/*" -d "x-pack/legacy/plugins/console_extensions/spec/generated"
+yarn spec_to_console -g "/x-pack/plugin/src/test/resources/rest-api-spec/api/*" -d "x-pack/plugins/console_extensions/server/spec/generated"
```
### Information used in Console that is not available in the REST spec
diff --git a/packages/kbn-spec-to-console/bin/spec_to_console.js b/packages/kbn-spec-to-console/bin/spec_to_console.js
index 20e870963e4b4..20b42c67f3b89 100644
--- a/packages/kbn-spec-to-console/bin/spec_to_console.js
+++ b/packages/kbn-spec-to-console/bin/spec_to_console.js
@@ -21,6 +21,7 @@ const fs = require('fs');
const path = require('path');
const program = require('commander');
const glob = require('glob');
+const chalk = require('chalk');
const packageJSON = require('../package.json');
const convert = require('../lib/convert');
@@ -37,10 +38,26 @@ if (!program.glob) {
}
const files = glob.sync(program.glob);
-console.log(files.length, files);
+const totalFilesCount = files.length;
+let convertedFilesCount = 0;
+
+console.log(chalk.bold(`Detected files (count: ${totalFilesCount}):`));
+console.log();
+console.log(files);
+console.log();
+
files.forEach(file => {
const spec = JSON.parse(fs.readFileSync(file));
- const output = JSON.stringify(convert(spec), null, 2);
+ const convertedSpec = convert(spec);
+ if (!Object.keys(convertedSpec).length) {
+ console.log(
+ // prettier-ignore
+ `${chalk.yellow('Detected')} ${chalk.grey(file)} but no endpoints were converted; ${chalk.yellow('skipping')}...`
+ );
+ return;
+ }
+ const output = JSON.stringify(convertedSpec, null, 2);
+ ++convertedFilesCount;
if (program.directory) {
const outputName = path.basename(file);
const outputPath = path.resolve(program.directory, outputName);
@@ -54,3 +71,9 @@ files.forEach(file => {
console.log(output);
}
});
+
+console.log();
+// prettier-ignore
+console.log(`${chalk.grey('Converted')} ${chalk.bold(`${convertedFilesCount}/${totalFilesCount}`)} ${chalk.grey('files')}`);
+console.log(`Check your ${chalk.bold('git status')}.`);
+console.log();
diff --git a/packages/kbn-spec-to-console/lib/convert.js b/packages/kbn-spec-to-console/lib/convert.js
index 5dbdd6e1c94e4..88e3693d702e5 100644
--- a/packages/kbn-spec-to-console/lib/convert.js
+++ b/packages/kbn-spec-to-console/lib/convert.js
@@ -36,6 +36,11 @@ module.exports = spec => {
*/
Object.keys(spec).forEach(api => {
const source = spec[api];
+
+ if (source.url.paths.every(path => Boolean(path.deprecated))) {
+ return;
+ }
+
if (!source.url) {
return result;
}
diff --git a/packages/kbn-spec-to-console/lib/convert/params.js b/packages/kbn-spec-to-console/lib/convert/params.js
index 86ac1667282f0..0d1747ae4f685 100644
--- a/packages/kbn-spec-to-console/lib/convert/params.js
+++ b/packages/kbn-spec-to-console/lib/convert/params.js
@@ -47,6 +47,7 @@ module.exports = params => {
case 'date':
case 'string':
case 'number':
+ case 'number|string':
result[param] = defaultValue || '';
break;
case 'list':
diff --git a/packages/kbn-ui-shared-deps/monaco.ts b/packages/kbn-ui-shared-deps/monaco.ts
index 570aca86c484c..42801c69a3e2c 100644
--- a/packages/kbn-ui-shared-deps/monaco.ts
+++ b/packages/kbn-ui-shared-deps/monaco.ts
@@ -25,6 +25,8 @@ import 'monaco-editor/esm/vs/base/worker/defaultWorkerFactory';
import 'monaco-editor/esm/vs/editor/browser/controller/coreCommands.js';
import 'monaco-editor/esm/vs/editor/browser/widget/codeEditorWidget.js';
+import 'monaco-editor/esm/vs/editor/contrib/wordOperations/wordOperations.js'; // Needed for word-wise char navigation
+
import 'monaco-editor/esm/vs/editor/contrib/suggest/suggestController.js'; // Needed for suggestions
import 'monaco-editor/esm/vs/editor/contrib/hover/hover.js'; // Needed for hover
import 'monaco-editor/esm/vs/editor/contrib/parameterHints/parameterHints.js'; // Needed for signature
diff --git a/renovate.json5 b/renovate.json5
index e4836537df703..57f175d1afc8e 100644
--- a/renovate.json5
+++ b/renovate.json5
@@ -297,6 +297,14 @@
'@types/flot',
],
},
+ {
+ groupSlug: 'geojson',
+ groupName: 'geojson related packages',
+ packageNames: [
+ 'geojson',
+ '@types/geojson',
+ ],
+ },
{
groupSlug: 'getopts',
groupName: 'getopts related packages',
diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md
index 1ca9b63a51d18..0d5d300ec3b79 100644
--- a/src/core/MIGRATION.md
+++ b/src/core/MIGRATION.md
@@ -1233,11 +1233,11 @@ This table shows where these uiExports have moved to in the New Platform. In mos
| `chromeNavControls` | [`core.chrome.navControls.register{Left,Right}`](/docs/development/core/public/kibana-plugin-public.chromenavcontrols.md) | |
| `contextMenuActions` | | Should be an API on the devTools plugin. |
| `devTools` | | |
-| `docViews` | | |
+| `docViews` | [`plugins.discover.docViews.addDocView`](./src/plugins/discover/public/doc_views) | Should be an API on the discover plugin. |
| `embeddableActions` | | Should be an API on the embeddables plugin. |
| `embeddableFactories` | | Should be an API on the embeddables plugin. |
-| `fieldFormatEditors` | | |
-| `fieldFormats` | [`plugins.data.fieldFormats`](./src/plugins/data/public/field_formats) | |
+| `fieldFormatEditors` | | |
+| `fieldFormats` | [`plugins.data.fieldFormats`](./src/plugins/data/public/field_formats) | |
| `hacks` | n/a | Just run the code in your plugin's `start` method. |
| `home` | [`plugins.home.featureCatalogue.register`](./src/plugins/home/public/feature_catalogue) | Must add `home` as a dependency in your kibana.json. |
| `indexManagement` | | Should be an API on the indexManagement plugin. |
diff --git a/src/core/public/index.ts b/src/core/public/index.ts
index b91afa3ae7dc0..f72e115fd24ff 100644
--- a/src/core/public/index.ts
+++ b/src/core/public/index.ts
@@ -208,15 +208,21 @@ export interface CoreSetup {
injectedMetadata: {
getInjectedVar: (name: string, defaultValue?: any) => unknown;
};
-
- /**
- * Allows plugins to get access to APIs available in start inside async
- * handlers, such as {@link App.mount}. Promise will not resolve until Core
- * and plugin dependencies have completed `start`.
- */
- getStartServices(): Promise<[CoreStart, TPluginsStart]>;
+ /** {@link StartServicesAccessor} */
+ getStartServices: StartServicesAccessor;
}
+/**
+ * Allows plugins to get access to APIs available in start inside async
+ * handlers, such as {@link App.mount}. Promise will not resolve until Core
+ * and plugin dependencies have completed `start`.
+ *
+ * @public
+ */
+export type StartServicesAccessor = () => Promise<
+ [CoreStart, TPluginsStart]
+>;
+
/**
* Core services exposed to the `Plugin` start lifecycle
*
diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md
index 37212a07ee631..eec12f2348176 100644
--- a/src/core/public/public.api.md
+++ b/src/core/public/public.api.md
@@ -378,7 +378,8 @@ export interface CoreSetup {
context: ContextSetup;
// (undocumented)
fatalErrors: FatalErrorsSetup;
- getStartServices(): Promise<[CoreStart, TPluginsStart]>;
+ // (undocumented)
+ getStartServices: StartServicesAccessor;
// (undocumented)
http: HttpSetup;
// @deprecated
@@ -1235,6 +1236,9 @@ export class SimpleSavedObject {
_version?: SavedObject['version'];
}
+// @public
+export type StartServicesAccessor = () => Promise<[CoreStart, TPluginsStart]>;
+
// @public
export type StringValidation = StringValidationRegex | StringValidationRegexString;
diff --git a/src/core/server/index.ts b/src/core/server/index.ts
index 89fee92a7ef02..1b436bfd72622 100644
--- a/src/core/server/index.ts
+++ b/src/core/server/index.ts
@@ -352,15 +352,22 @@ export interface CoreSetup {
uuid: UuidServiceSetup;
/** {@link MetricsServiceSetup} */
metrics: MetricsServiceSetup;
- /**
- * Allows plugins to get access to APIs available in start inside async handlers.
- * Promise will not resolve until Core and plugin dependencies have completed `start`.
- * This should only be used inside handlers registered during `setup` that will only be executed
- * after `start` lifecycle.
- */
- getStartServices(): Promise<[CoreStart, TPluginsStart]>;
+ /** {@link StartServicesAccessor} */
+ getStartServices: StartServicesAccessor;
}
+/**
+ * Allows plugins to get access to APIs available in start inside async handlers.
+ * Promise will not resolve until Core and plugin dependencies have completed `start`.
+ * This should only be used inside handlers registered during `setup` that will only be executed
+ * after `start` lifecycle.
+ *
+ * @public
+ */
+export type StartServicesAccessor = () => Promise<
+ [CoreStart, TPluginsStart]
+>;
+
/**
* Context passed to the plugins `start` method.
*
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index 229ffc4d21575..6d4181e5e1ab3 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -629,7 +629,8 @@ export interface CoreSetup {
context: ContextSetup;
// (undocumented)
elasticsearch: ElasticsearchServiceSetup;
- getStartServices(): Promise<[CoreStart, TPluginsStart]>;
+ // (undocumented)
+ getStartServices: StartServicesAccessor;
// (undocumented)
http: HttpServiceSetup;
// (undocumented)
@@ -2269,6 +2270,9 @@ export type SharedGlobalConfig = RecursiveReadonly_2<{
path: Pick;
}>;
+// @public
+export type StartServicesAccessor = () => Promise<[CoreStart, TPluginsStart]>;
+
// @public
export type StringValidation = StringValidationRegex | StringValidationRegexString;
diff --git a/src/dev/run_check_lockfile_symlinks.js b/src/dev/run_check_lockfile_symlinks.js
index 54a8cdf638a78..6c6fc54638ee8 100644
--- a/src/dev/run_check_lockfile_symlinks.js
+++ b/src/dev/run_check_lockfile_symlinks.js
@@ -36,6 +36,8 @@ const IGNORE_FILE_GLOBS = [
'**/*fixtures*/**/*',
// cypress isn't used in production, ignore it
'x-pack/legacy/plugins/apm/e2e/*',
+ // apm scripts aren't used in production, ignore them
+ 'x-pack/legacy/plugins/apm/scripts/*',
];
run(async ({ log }) => {
diff --git a/src/legacy/core_plugins/application_usage/mappings.ts b/src/legacy/core_plugins/application_usage/mappings.ts
deleted file mode 100644
index 39adc53f7e9ff..0000000000000
--- a/src/legacy/core_plugins/application_usage/mappings.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export const mappings = {
- application_usage_totals: {
- properties: {
- appId: { type: 'keyword' },
- numberOfClicks: { type: 'long' },
- minutesOnScreen: { type: 'float' },
- },
- },
- application_usage_transactional: {
- properties: {
- timestamp: { type: 'date' },
- appId: { type: 'keyword' },
- numberOfClicks: { type: 'long' },
- minutesOnScreen: { type: 'float' },
- },
- },
-};
diff --git a/src/legacy/core_plugins/application_usage/package.json b/src/legacy/core_plugins/application_usage/package.json
deleted file mode 100644
index 5ab10a2f8d237..0000000000000
--- a/src/legacy/core_plugins/application_usage/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "application_usage",
- "version": "kibana"
-}
\ No newline at end of file
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts
index 5b9fb8c0b6360..8900d017ef81a 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/index.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts
@@ -21,7 +21,6 @@ import { PluginInitializerContext } from 'kibana/public';
import { DashboardPlugin } from './plugin';
export * from './np_ready/dashboard_constants';
-export { createSavedDashboardLoader } from './saved_dashboard/saved_dashboards';
// Core will be looking for this when loading our plugin in the new platform
export const plugin = (context: PluginInitializerContext) => {
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/is_dashboard_doc.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/is_dashboard_doc.ts
index f3bc2a4a4e155..d8f8882a218dd 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/is_dashboard_doc.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/is_dashboard_doc.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { DashboardDoc730ToLatest } from './types';
+import { DashboardDoc730ToLatest } from '../../../../../../plugins/dashboard/public';
import { isDoc } from '../../../migrations/is_doc';
export function isDashboardDoc(
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts
index 2189b53ac81ee..e37c8de08fec4 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts
@@ -44,8 +44,6 @@ import {
RawSavedDashboardPanel620,
RawSavedDashboardPanel630,
RawSavedDashboardPanel640To720,
-} from './types';
-import {
DEFAULT_PANEL_WIDTH,
DEFAULT_PANEL_HEIGHT,
} from '../../../../../../plugins/dashboard/public';
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts
index 6b037fa63cf68..047ec15f9a5d6 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts
@@ -18,17 +18,17 @@
*/
import { i18n } from '@kbn/i18n';
import semver from 'semver';
-import { GridData } from 'src/plugins/dashboard/public';
-
import uuid from 'uuid';
import {
+ GridData,
RawSavedDashboardPanelTo60,
RawSavedDashboardPanel630,
RawSavedDashboardPanel640To720,
RawSavedDashboardPanel730ToLatest,
RawSavedDashboardPanel610,
RawSavedDashboardPanel620,
-} from './types';
+} from '../../../../../../plugins/dashboard/public';
+
import {
SavedDashboardPanelTo60,
SavedDashboardPanel620,
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts
index 86d399d219a26..34bb46ce5d407 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts
@@ -24,7 +24,7 @@ import {
DashboardDoc730ToLatest,
RawSavedDashboardPanel730ToLatest,
DashboardDocPre700,
-} from './types';
+} from '../../../../../../plugins/dashboard/public';
const mockLogger = {
warning: () => {},
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.ts
index 1ab5738cf4752..56856f7b21303 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.ts
@@ -20,7 +20,10 @@
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { SavedObjectsMigrationLogger } from 'src/core/server';
import { inspect } from 'util';
-import { DashboardDoc730ToLatest, DashboardDoc700To720 } from './types';
+import {
+ DashboardDoc730ToLatest,
+ DashboardDoc700To720,
+} from '../../../../../../plugins/dashboard/public';
import { isDashboardDoc } from './is_dashboard_doc';
import { moveFiltersToQuery } from './move_filters_to_query';
import { migratePanelsTo730 } from './migrate_to_730_panels';
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx
index 4e9942767186e..e21033ffe10ec 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx
@@ -22,7 +22,7 @@ import { Subscription } from 'rxjs';
import { History } from 'history';
import { ViewMode } from '../../../../embeddable_api/public/np_ready/public';
-import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard';
+import { SavedObjectDashboard } from '../../../../../../plugins/dashboard/public';
import { DashboardAppState, SavedDashboardPanel } from './types';
import {
IIndexPattern,
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx
index e2236f2894c41..93c657a3ccc2a 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx
@@ -759,7 +759,7 @@ export class DashboardAppController {
* When de-angularizing this code, please call the underlaying action function
* directly and not via the top nav object.
**/
- navActions[TopNavIds.ADD]();
+ navActions[TopNavIds.ADD_EXISTING]();
};
$scope.enterEditMode = () => {
dashboardStateManager.setFullScreenMode(false);
@@ -852,7 +852,8 @@ export class DashboardAppController {
showCloneModal(onClone, currentTitle);
};
- navActions[TopNavIds.ADD] = () => {
+
+ navActions[TopNavIds.ADD_EXISTING] = () => {
if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) {
openAddPanelFlyout({
embeddable: dashboardContainer,
@@ -894,7 +895,8 @@ export class DashboardAppController {
share.toggleShareContextMenu({
anchorElement,
allowEmbed: true,
- allowShortUrl: !dashboardConfig.getHideWriteControls(),
+ allowShortUrl:
+ !dashboardConfig.getHideWriteControls() || dashboardCapabilities.createShortUrl,
shareableUrl: unhashUrl(window.location.href),
objectId: dash.id,
objectType: 'dashboard',
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts
index d26948e06c8cb..9f71b15511a70 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts
@@ -23,7 +23,10 @@ import { Observable, Subscription } from 'rxjs';
import { Moment } from 'moment';
import { History } from 'history';
-import { DashboardContainer } from 'src/plugins/dashboard/public';
+import {
+ DashboardContainer,
+ SavedObjectDashboard,
+} from '../../../../../../plugins/dashboard/public';
import { ViewMode } from '../../../../../../plugins/embeddable/public';
import { migrateLegacyQuery } from '../legacy_imports';
import {
@@ -35,8 +38,6 @@ import {
import { getAppStateDefaults, migrateAppState } from './lib';
import { convertPanelStateToSavedDashboardPanel } from './lib/embeddable_saved_object_converters';
import { FilterUtils } from './lib/filter_utils';
-import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard';
-
import {
DashboardAppState,
DashboardAppStateDefaults,
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.ts
index 7d5a378885470..500ee7e28daa6 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.ts
@@ -17,7 +17,7 @@
* under the License.
*/
import { omit } from 'lodash';
-import { DashboardPanelState } from 'src/plugins/dashboard/public';
+import { DashboardPanelState } from '../../../../../../../plugins/dashboard/public';
import { SavedDashboardPanel } from '../types';
export function convertSavedDashboardPanelToPanelState(
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/get_app_state_defaults.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/get_app_state_defaults.ts
index eceb51f17d164..b3acefeba0146 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/get_app_state_defaults.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/get_app_state_defaults.ts
@@ -18,7 +18,7 @@
*/
import { ViewMode } from '../../../../../../../plugins/embeddable/public';
-import { SavedObjectDashboard } from '../../saved_dashboard/saved_dashboard';
+import { SavedObjectDashboard } from '../../../../../../../plugins/dashboard/public';
import { DashboardAppStateDefaults } from '../types';
export function getAppStateDefaults(
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts
index ec8073c0f72f7..dee279550aa6a 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts
@@ -20,7 +20,7 @@
import _ from 'lodash';
import { RefreshInterval, TimefilterContract } from 'src/plugins/data/public';
import { FilterUtils } from './filter_utils';
-import { SavedObjectDashboard } from '../../saved_dashboard/saved_dashboard';
+import { SavedObjectDashboard } from '../../../../../../../plugins/dashboard/public';
import { DashboardAppState } from '../types';
export function updateSavedDashboard(
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/test_utils/get_saved_dashboard_mock.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/test_utils/get_saved_dashboard_mock.ts
index 60b2a33f720ec..53618f1cfe5fa 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/test_utils/get_saved_dashboard_mock.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/test_utils/get_saved_dashboard_mock.ts
@@ -18,7 +18,7 @@
*/
import { searchSourceMock } from '../../../../../../../plugins/data/public/mocks';
-import { SavedObjectDashboard } from '../../saved_dashboard/saved_dashboard';
+import { SavedObjectDashboard } from '../../../../../../../plugins/dashboard/public/';
export function getSavedDashboardMock(
config?: Partial
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/get_top_nav_config.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/get_top_nav_config.ts
index 7188fab19d6f2..7a3cb4b7dad56 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/get_top_nav_config.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/get_top_nav_config.ts
@@ -48,9 +48,10 @@ export function getTopNavConfig(
];
case ViewMode.EDIT:
return [
+ getCreateNewConfig(actions[TopNavIds.VISUALIZE]),
getSaveConfig(actions[TopNavIds.SAVE]),
getViewConfig(actions[TopNavIds.EXIT_EDIT_MODE]),
- getAddConfig(actions[TopNavIds.ADD]),
+ getAddConfig(actions[TopNavIds.ADD_EXISTING]),
getOptionsConfig(actions[TopNavIds.OPTIONS]),
getShareConfig(actions[TopNavIds.SHARE]),
];
@@ -161,6 +162,25 @@ function getAddConfig(action: NavAction) {
};
}
+/**
+ * @returns {kbnTopNavConfig}
+ */
+function getCreateNewConfig(action: NavAction) {
+ return {
+ emphasize: true,
+ iconType: 'plusInCircle',
+ id: 'addNew',
+ label: i18n.translate('kbn.dashboard.topNave.addNewButtonAriaLabel', {
+ defaultMessage: 'Create new',
+ }),
+ description: i18n.translate('kbn.dashboard.topNave.addNewConfigDescription', {
+ defaultMessage: 'Create a new panel on this dashboard',
+ }),
+ testId: 'dashboardAddNewPanelButton',
+ run: action,
+ };
+}
+
/**
* @returns {kbnTopNavConfig}
*/
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/top_nav_ids.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/top_nav_ids.ts
index c67d6891c18e7..748bfaaab6141 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/top_nav_ids.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/top_nav_ids.ts
@@ -18,7 +18,6 @@
*/
export const TopNavIds = {
- ADD: 'add',
SHARE: 'share',
OPTIONS: 'options',
SAVE: 'save',
@@ -27,4 +26,5 @@ export const TopNavIds = {
CLONE: 'clone',
FULL_SCREEN: 'fullScreenMode',
VISUALIZE: 'visualize',
+ ADD_EXISTING: 'addExisting',
};
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts
index 0f3a7e322ebf3..9f8682f13d811 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts
@@ -25,19 +25,11 @@ import {
RawSavedDashboardPanel630,
RawSavedDashboardPanel640To720,
RawSavedDashboardPanel730ToLatest,
-} from '../migrations/types';
+} from '../../../../../../plugins/dashboard/public';
import { Query, Filter } from '../../../../../../plugins/data/public';
export type NavAction = (anchorElement?: any) => void;
-export interface GridData {
- w: number;
- h: number;
- x: number;
- y: number;
- i: string;
-}
-
/**
* This should always represent the latest dashboard panel shape, after all possible migrations.
*/
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts
index a9ee77921ed4a..7452807454fe7 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts
@@ -49,8 +49,8 @@ import {
KibanaLegacySetup,
KibanaLegacyStart,
} from '../../../../../plugins/kibana_legacy/public';
-import { createSavedDashboardLoader } from './saved_dashboard/saved_dashboards';
import { createKbnUrlTracker } from '../../../../../plugins/kibana_utils/public';
+import { DashboardStart } from '../../../../../plugins/dashboard/public';
export interface DashboardPluginStartDependencies {
data: DataPublicPluginStart;
@@ -58,6 +58,7 @@ export interface DashboardPluginStartDependencies {
navigation: NavigationStart;
share: SharePluginStart;
kibanaLegacy: KibanaLegacyStart;
+ dashboard: DashboardStart;
}
export interface DashboardPluginSetupDependencies {
@@ -74,6 +75,7 @@ export class DashboardPlugin implements Plugin {
navigation: NavigationStart;
share: SharePluginStart;
dashboardConfig: KibanaLegacyStart['dashboardConfig'];
+ dashboard: DashboardStart;
} | null = null;
private appStateUpdater = new BehaviorSubject(() => ({}));
@@ -129,13 +131,9 @@ export class DashboardPlugin implements Plugin {
share,
data: dataStart,
dashboardConfig,
+ dashboard: { getSavedDashboardLoader },
} = this.startDependencies;
- const savedDashboards = createSavedDashboardLoader({
- savedObjectsClient,
- indexPatterns: dataStart.indexPatterns,
- chrome: coreStart.chrome,
- overlays: coreStart.overlays,
- });
+ const savedDashboards = getSavedDashboardLoader();
const deps: RenderDeps = {
pluginInitializerContext: this.initializerContext,
@@ -199,6 +197,7 @@ export class DashboardPlugin implements Plugin {
data,
share,
kibanaLegacy: { dashboardConfig },
+ dashboard,
}: DashboardPluginStartDependencies
) {
this.startDependencies = {
@@ -208,6 +207,7 @@ export class DashboardPlugin implements Plugin {
navigation,
share,
dashboardConfig,
+ dashboard,
};
}
diff --git a/src/legacy/core_plugins/kibana/public/discover/build_services.ts b/src/legacy/core_plugins/kibana/public/discover/build_services.ts
index 282eef0c983eb..f881eb96e4e81 100644
--- a/src/legacy/core_plugins/kibana/public/discover/build_services.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/build_services.ts
@@ -35,10 +35,13 @@ import {
import { DiscoverStartPlugins } from './plugin';
import { SharePluginStart } from '../../../../../plugins/share/public';
-import { DocViewsRegistry } from './np_ready/doc_views/doc_views_registry';
import { ChartsPluginStart } from '../../../../../plugins/charts/public';
import { VisualizationsStart } from '../../../visualizations/public';
-import { createSavedSearchesLoader, SavedSearch } from '../../../../../plugins/discover/public';
+import {
+ createSavedSearchesLoader,
+ DocViewerComponent,
+ SavedSearch,
+} from '../../../../../plugins/discover/public';
export interface DiscoverServices {
addBasePath: (path: string) => string;
@@ -47,7 +50,7 @@ export interface DiscoverServices {
core: CoreStart;
data: DataPublicPluginStart;
docLinks: DocLinksStart;
- docViewsRegistry: DocViewsRegistry;
+ DocViewer: DocViewerComponent;
history: History;
theme: ChartsPluginStart['theme'];
filterManager: FilterManager;
@@ -64,8 +67,7 @@ export interface DiscoverServices {
}
export async function buildServices(
core: CoreStart,
- plugins: DiscoverStartPlugins,
- docViewsRegistry: DocViewsRegistry
+ plugins: DiscoverStartPlugins
): Promise {
const services = {
savedObjectsClient: core.savedObjects.client,
@@ -81,7 +83,7 @@ export async function buildServices(
core,
data: plugins.data,
docLinks: core.docLinks,
- docViewsRegistry,
+ DocViewer: plugins.discover.docViews.DocViewer,
history: createHashHistory(),
theme: plugins.charts.theme,
filterManager: plugins.data.query.filterManager,
diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts
index d369eb9679de6..7a3a6949baa94 100644
--- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts
@@ -75,7 +75,6 @@ export {
EsQuerySortValue,
SortDirection,
} from '../../../../../plugins/data/public';
-export { ElasticSearchHit } from './np_ready/doc_views/doc_views_types';
export { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities';
// @ts-ignore
export { buildPointSeriesData } from 'ui/agg_response/point_series/point_series';
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js
index 674f40d0186e5..9efddc5275069 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js
@@ -26,7 +26,7 @@ import { fetchAnchorProvider } from '../api/anchor';
import { fetchContextProvider } from '../api/context';
import { getQueryParameterActions } from '../query_parameters';
import { FAILURE_REASONS, LOADING_STATUS } from './constants';
-import { MarkdownSimple } from '../../../../../../../kibana_react/public';
+import { MarkdownSimple } from '../../../../../../../../../plugins/kibana_react/public';
export function QueryActionsProvider(Promise) {
const { filterManager, indexPatterns } = getServices();
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name.js
index b020113381992..47e50f3cc3d4b 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name.js
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name.js
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { FieldName } from './field_name/field_name';
+import { FieldName } from '../../../../../../../../plugins/discover/public';
import { getServices, wrapInI18nContext } from '../../../kibana_services';
export function FieldNameDirectiveProvider(reactDirective) {
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_viewer.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_viewer.tsx
similarity index 87%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_viewer.ts
rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_viewer.tsx
index 6ba47b839563b..90e061ac1aa05 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_viewer.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_viewer.tsx
@@ -17,11 +17,15 @@
* under the License.
*/
-import { DocViewer } from '../components/doc_viewer/doc_viewer';
+import React from 'react';
+import { getServices } from '../../kibana_services';
export function createDocViewerDirective(reactDirective: any) {
return reactDirective(
- DocViewer,
+ (props: any) => {
+ const { DocViewer } = getServices();
+ return ;
+ },
[
'hit',
['indexPattern', { watchDepth: 'reference' }],
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/_index.scss
index 0491430e5fddd..7161560f8fda4 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/_index.scss
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/_index.scss
@@ -1,3 +1,2 @@
@import 'fetch_error/index';
@import 'field_chooser/index';
-@import 'doc_viewer/index';
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.test.tsx
index 2278b243ecc14..1d19dc112d193 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.test.tsx
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.test.tsx
@@ -24,19 +24,13 @@ import { ReactWrapper } from 'enzyme';
import { findTestSubject } from '@elastic/eui/lib/test';
import { Doc, DocProps } from './doc';
-jest.mock('../doc_viewer/doc_viewer', () => ({
- DocViewer: () => null,
-}));
-
jest.mock('../../../kibana_services', () => {
return {
getServices: () => ({
metadata: {
branch: 'test',
},
- getDocViewsSorted: () => {
- return [];
- },
+ DocViewer: () => null,
}),
};
});
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.tsx
index 819eb9df592bd..28a17dbdb67b7 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.tsx
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.tsx
@@ -20,9 +20,9 @@ import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiCallOut, EuiLink, EuiLoadingSpinner, EuiPageContent } from '@elastic/eui';
import { IndexPatternsContract } from 'src/plugins/data/public';
-import { DocViewer } from '../doc_viewer/doc_viewer';
import { ElasticRequestState, useEsDocSearch } from './use_es_doc_search';
-import { ElasticSearchHit, getServices } from '../../../kibana_services';
+import { getServices } from '../../../kibana_services';
+import { ElasticSearchHit } from '../../../../../../../../plugins/discover/public';
export interface ElasticSearchResult {
hits: {
@@ -61,6 +61,7 @@ export interface DocProps {
}
export function Doc(props: DocProps) {
+ const { DocViewer } = getServices();
const [reqState, hit, indexPattern] = useEsDocSearch(props);
return (
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/use_es_doc_search.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/use_es_doc_search.ts
index 6cffc2cc533b0..2cd264578a596 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/use_es_doc_search.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/use_es_doc_search.ts
@@ -17,8 +17,9 @@
* under the License.
*/
import { useEffect, useState } from 'react';
-import { ElasticSearchHit, IndexPattern } from '../../../kibana_services';
+import { IndexPattern } from '../../../kibana_services';
import { DocProps } from './doc';
+import { ElasticSearchHit } from '../../../../../../../../plugins/discover/public';
export enum ElasticRequestState {
Loading,
diff --git a/src/legacy/core_plugins/kibana/public/discover/plugin.ts b/src/legacy/core_plugins/kibana/public/discover/plugin.ts
index ba671a64592a5..d3cdeb49fba71 100644
--- a/src/legacy/core_plugins/kibana/public/discover/plugin.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/plugin.ts
@@ -19,7 +19,6 @@
import { BehaviorSubject } from 'rxjs';
import { filter, map } from 'rxjs/operators';
-import { i18n } from '@kbn/i18n';
import { AppMountParameters, CoreSetup, CoreStart, Plugin } from 'kibana/public';
import angular, { auto } from 'angular';
import { UiActionsSetup, UiActionsStart } from 'src/plugins/ui_actions/public';
@@ -41,10 +40,7 @@ import {
KibanaLegacySetup,
AngularRenderedAppUpdater,
} from '../../../../../plugins/kibana_legacy/public';
-import { DocViewsRegistry } from './np_ready/doc_views/doc_views_registry';
-import { DocViewInput, DocViewInputFn } from './np_ready/doc_views/doc_views_types';
-import { DocViewTable } from './np_ready/components/table/table';
-import { JsonCodeBlock } from './np_ready/components/json_code_block/json_code_block';
+import { DiscoverSetup, DiscoverStart } from '../../../../../plugins/discover/public';
import { HomePublicPluginSetup } from '../../../../../plugins/home/public';
import {
VisualizationsStart,
@@ -52,15 +48,6 @@ import {
} from '../../../visualizations/public/np_ready/public';
import { createKbnUrlTracker } from '../../../../../plugins/kibana_utils/public';
-/**
- * These are the interfaces with your public contracts. You should export these
- * for other plugins to use in _their_ `SetupDeps`/`StartDeps` interfaces.
- * @public
- */
-export interface DiscoverSetup {
- addDocView(docViewRaw: DocViewInput | DocViewInputFn): void;
-}
-export type DiscoverStart = void;
export interface DiscoverSetupPlugins {
uiActions: UiActionsSetup;
embeddable: EmbeddableSetup;
@@ -68,6 +55,7 @@ export interface DiscoverSetupPlugins {
home: HomePublicPluginSetup;
visualizations: VisualizationsSetup;
data: DataPublicPluginSetup;
+ discover: DiscoverSetup;
}
export interface DiscoverStartPlugins {
uiActions: UiActionsStart;
@@ -78,6 +66,7 @@ export interface DiscoverStartPlugins {
share: SharePluginStart;
inspector: any;
visualizations: VisualizationsStart;
+ discover: DiscoverStart;
}
const innerAngularName = 'app/discover';
const embeddableAngularName = 'app/discoverEmbeddable';
@@ -87,10 +76,9 @@ const embeddableAngularName = 'app/discoverEmbeddable';
* There are 2 kinds of Angular bootstrapped for rendering, additionally to the main Angular
* Discover provides embeddables, those contain a slimmer Angular
*/
-export class DiscoverPlugin implements Plugin {
+export class DiscoverPlugin implements Plugin {
private servicesInitialized: boolean = false;
private innerAngularInitialized: boolean = false;
- private docViewsRegistry: DocViewsRegistry | null = null;
private embeddableInjector: auto.IInjectorService | null = null;
private getEmbeddableInjector: (() => Promise) | null = null;
private appStateUpdater = new BehaviorSubject(() => ({}));
@@ -103,7 +91,7 @@ export class DiscoverPlugin implements Plugin {
public initializeInnerAngular?: () => void;
public initializeServices?: () => Promise<{ core: CoreStart; plugins: DiscoverStartPlugins }>;
- setup(core: CoreSetup, plugins: DiscoverSetupPlugins): DiscoverSetup {
+ setup(core: CoreSetup, plugins: DiscoverSetupPlugins) {
const { appMounted, appUnMounted, stop: stopUrlTracker } = createKbnUrlTracker({
baseUrl: core.http.basePath.prepend('/app/kibana'),
defaultSubUrl: '#/discover',
@@ -130,21 +118,7 @@ export class DiscoverPlugin implements Plugin {
};
this.getEmbeddableInjector = this.getInjector.bind(this);
- this.docViewsRegistry = new DocViewsRegistry(this.getEmbeddableInjector);
- this.docViewsRegistry.addDocView({
- title: i18n.translate('kbn.discover.docViews.table.tableTitle', {
- defaultMessage: 'Table',
- }),
- order: 10,
- component: DocViewTable,
- });
- this.docViewsRegistry.addDocView({
- title: i18n.translate('kbn.discover.docViews.json.jsonTitle', {
- defaultMessage: 'JSON',
- }),
- order: 20,
- component: JsonCodeBlock,
- });
+ plugins.discover.docViews.setAngularInjectorGetter(this.getEmbeddableInjector);
plugins.kibanaLegacy.registerLegacyApp({
id: 'discover',
title: 'Discover',
@@ -172,14 +146,10 @@ export class DiscoverPlugin implements Plugin {
},
});
registerFeature(plugins.home);
-
this.registerEmbeddable(core, plugins);
- return {
- addDocView: this.docViewsRegistry.addDocView.bind(this.docViewsRegistry),
- };
}
- start(core: CoreStart, plugins: DiscoverStartPlugins): DiscoverStart {
+ start(core: CoreStart, plugins: DiscoverStartPlugins) {
// we need to register the application service at setup, but to render it
// there are some start dependencies necessary, for this reason
// initializeInnerAngular + initializeServices are assigned at start and used
@@ -198,7 +168,7 @@ export class DiscoverPlugin implements Plugin {
if (this.servicesInitialized) {
return { core, plugins };
}
- const services = await buildServices(core, plugins, this.docViewsRegistry!);
+ const services = await buildServices(core, plugins);
setServices(services);
this.servicesInitialized = true;
diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/openmetrics.svg b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/openmetrics.svg
new file mode 100644
index 0000000000000..feccb88a3f34b
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/openmetrics.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts b/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts
index cb9ac0e01bb7f..f3a37e2b7348f 100644
--- a/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts
+++ b/src/legacy/core_plugins/kibana/public/management/saved_object_registry.ts
@@ -21,7 +21,6 @@ import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import { npStart } from 'ui/new_platform';
import { SavedObjectLoader } from '../../../../../plugins/saved_objects/public';
-import { createSavedDashboardLoader } from '../dashboard';
import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy';
import { createSavedSearchesLoader } from '../../../../../plugins/discover/public';
@@ -70,7 +69,7 @@ savedObjectManagementRegistry.register({
savedObjectManagementRegistry.register({
id: 'savedDashboards',
- service: createSavedDashboardLoader(services),
+ service: npStart.plugins.dashboard.getSavedDashboardLoader(),
title: i18n.translate('kbn.dashboard.savedDashboardsTitle', {
defaultMessage: 'dashboards',
}),
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap
index c1241d5d7c1e5..728944f3ccbfe 100644
--- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap
+++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap
@@ -53,6 +53,7 @@ exports[`Relationships should render dashboards normally 1`] = `
"width": "50px",
},
Object {
+ "data-test-subj": "directRelationship",
"dataType": "string",
"field": "relationship",
"name": "Direct relationship",
@@ -72,6 +73,7 @@ exports[`Relationships should render dashboards normally 1`] = `
"actions": Array [
Object {
"available": [Function],
+ "data-test-subj": "relationshipsTableAction-inspect",
"description": "Inspect this saved object",
"icon": "inspect",
"name": "Inspect",
@@ -117,6 +119,7 @@ exports[`Relationships should render dashboards normally 1`] = `
}
pagination={true}
responsive={true}
+ rowProps={[Function]}
search={
Object {
"filters": Array [
@@ -263,6 +266,7 @@ exports[`Relationships should render index patterns normally 1`] = `
"width": "50px",
},
Object {
+ "data-test-subj": "directRelationship",
"dataType": "string",
"field": "relationship",
"name": "Direct relationship",
@@ -282,6 +286,7 @@ exports[`Relationships should render index patterns normally 1`] = `
"actions": Array [
Object {
"available": [Function],
+ "data-test-subj": "relationshipsTableAction-inspect",
"description": "Inspect this saved object",
"icon": "inspect",
"name": "Inspect",
@@ -327,6 +332,7 @@ exports[`Relationships should render index patterns normally 1`] = `
}
pagination={true}
responsive={true}
+ rowProps={[Function]}
search={
Object {
"filters": Array [
@@ -429,6 +435,7 @@ exports[`Relationships should render searches normally 1`] = `
"width": "50px",
},
Object {
+ "data-test-subj": "directRelationship",
"dataType": "string",
"field": "relationship",
"name": "Direct relationship",
@@ -448,6 +455,7 @@ exports[`Relationships should render searches normally 1`] = `
"actions": Array [
Object {
"available": [Function],
+ "data-test-subj": "relationshipsTableAction-inspect",
"description": "Inspect this saved object",
"icon": "inspect",
"name": "Inspect",
@@ -493,6 +501,7 @@ exports[`Relationships should render searches normally 1`] = `
}
pagination={true}
responsive={true}
+ rowProps={[Function]}
search={
Object {
"filters": Array [
@@ -595,6 +604,7 @@ exports[`Relationships should render visualizations normally 1`] = `
"width": "50px",
},
Object {
+ "data-test-subj": "directRelationship",
"dataType": "string",
"field": "relationship",
"name": "Direct relationship",
@@ -614,6 +624,7 @@ exports[`Relationships should render visualizations normally 1`] = `
"actions": Array [
Object {
"available": [Function],
+ "data-test-subj": "relationshipsTableAction-inspect",
"description": "Inspect this saved object",
"icon": "inspect",
"name": "Inspect",
@@ -659,6 +670,7 @@ exports[`Relationships should render visualizations normally 1`] = `
}
pagination={true}
responsive={true}
+ rowProps={[Function]}
search={
Object {
"filters": Array [
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js
index ee9fb70e31fb2..ce3415ad2f0e7 100644
--- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js
+++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js
@@ -135,6 +135,7 @@ export class Relationships extends Component {
aria-label={getSavedObjectLabel(type)}
type={object.meta.icon || 'apps'}
size="s"
+ data-test-subj="relationshipsObjectType"
/>
);
@@ -149,6 +150,7 @@ export class Relationships extends Component {
dataType: 'string',
sortable: false,
width: '125px',
+ 'data-test-subj': 'directRelationship',
render: relationship => {
if (relationship === 'parent') {
return (
@@ -187,10 +189,16 @@ export class Relationships extends Component {
const { path } = object.meta.inAppUrl || {};
const canGoInApp = this.props.canGoInApp(object);
if (!canGoInApp) {
- return {title || getDefaultTitle(object)};
+ return (
+
+ {title || getDefaultTitle(object)}
+
+ );
}
return (
- {title || getDefaultTitle(object)}
+
+ {title || getDefaultTitle(object)}
+
);
},
},
@@ -211,6 +219,7 @@ export class Relationships extends Component {
),
type: 'icon',
icon: 'inspect',
+ 'data-test-subj': 'relationshipsTableAction-inspect',
onClick: object => goInspectObject(object),
available: object => !!object.meta.editUrl,
},
@@ -295,6 +304,9 @@ export class Relationships extends Component {
columns={columns}
pagination={true}
search={search}
+ rowProps={() => ({
+ 'data-test-subj': `relationshipsTableRow`,
+ })}
/>
);
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js
index 386b35399b754..5342693113bca 100644
--- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js
+++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/table.js
@@ -186,6 +186,7 @@ export class Table extends PureComponent {
aria-label={getSavedObjectLabel(type)}
type={object.meta.icon || 'apps'}
size="s"
+ data-test-subj="objectType"
/>
);
diff --git a/src/legacy/core_plugins/kibana_react/package.json b/src/legacy/core_plugins/kibana_react/package.json
deleted file mode 100644
index 3f7cf717a1963..0000000000000
--- a/src/legacy/core_plugins/kibana_react/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "kibana_react",
- "version": "kibana"
-}
diff --git a/src/legacy/core_plugins/kibana_react/public/index.scss b/src/legacy/core_plugins/kibana_react/public/index.scss
deleted file mode 100644
index 14b4687c459e1..0000000000000
--- a/src/legacy/core_plugins/kibana_react/public/index.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-@import 'src/legacy/ui/public/styles/styling_constants';
-
-@import './markdown/index';
diff --git a/src/legacy/core_plugins/telemetry/common/constants.ts b/src/legacy/core_plugins/telemetry/common/constants.ts
deleted file mode 100644
index b44bf319e6627..0000000000000
--- a/src/legacy/core_plugins/telemetry/common/constants.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { i18n } from '@kbn/i18n';
-
-/*
- * config options opt into telemetry
- * @type {string}
- */
-export const CONFIG_TELEMETRY = 'telemetry:optIn';
-/*
- * config description for opting into telemetry
- * @type {string}
- */
-export const getConfigTelemetryDesc = () => {
- return i18n.translate('telemetry.telemetryConfigDescription', {
- defaultMessage:
- 'Help us improve the Elastic Stack by providing usage statistics for basic features. We will not share this data outside of Elastic.',
- });
-};
-
-/**
- * The amount of time, in milliseconds, to wait between reports when enabled.
- *
- * Currently 24 hours.
- * @type {Number}
- */
-export const REPORT_INTERVAL_MS = 86400000;
-
-/**
- * Link to the Elastic Telemetry privacy statement.
- */
-export const PRIVACY_STATEMENT_URL = `https://www.elastic.co/legal/privacy-statement`;
-
-/**
- * The type name used within the Monitoring index to publish localization stats.
- * @type {string}
- */
-export const KIBANA_LOCALIZATION_STATS_TYPE = 'localization';
-
-/**
- * The type name used to publish telemetry plugin stats.
- * @type {string}
- */
-export const TELEMETRY_STATS_TYPE = 'telemetry';
-
-/**
- * UI metric usage type
- * @type {string}
- */
-export const UI_METRIC_USAGE_TYPE = 'ui_metric';
-
-/**
- * Application Usage type
- */
-export const APPLICATION_USAGE_TYPE = 'application_usage';
-
-/**
- * Link to Advanced Settings.
- */
-export const PATH_TO_ADVANCED_SETTINGS = 'kibana#/management/kibana/settings';
-
-/**
- * The type name used within the Monitoring index to publish management stats.
- * @type {string}
- */
-export const KIBANA_STACK_MANAGEMENT_STATS_TYPE = 'stack_management';
diff --git a/src/legacy/core_plugins/telemetry/index.ts b/src/legacy/core_plugins/telemetry/index.ts
deleted file mode 100644
index 1e88e7d65cffd..0000000000000
--- a/src/legacy/core_plugins/telemetry/index.ts
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import * as Rx from 'rxjs';
-import { resolve } from 'path';
-import JoiNamespace from 'joi';
-import { Server } from 'hapi';
-import { PluginInitializerContext } from 'src/core/server';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { getConfigPath } from '../../../core/server/path';
-// @ts-ignore
-import mappings from './mappings.json';
-import {
- telemetryPlugin,
- replaceTelemetryInjectedVars,
- FetcherTask,
- PluginsSetup,
- handleOldSettings,
-} from './server';
-
-const ENDPOINT_VERSION = 'v2';
-
-const telemetry = (kibana: any) => {
- return new kibana.Plugin({
- id: 'telemetry',
- configPrefix: 'telemetry',
- publicDir: resolve(__dirname, 'public'),
- require: ['elasticsearch'],
- config(Joi: typeof JoiNamespace) {
- return Joi.object({
- enabled: Joi.boolean().default(true),
- allowChangingOptInStatus: Joi.boolean().default(true),
- optIn: Joi.when('allowChangingOptInStatus', {
- is: false,
- then: Joi.valid(true).default(true),
- otherwise: Joi.boolean().default(true),
- }),
- // `config` is used internally and not intended to be set
- config: Joi.string().default(getConfigPath()),
- banner: Joi.boolean().default(true),
- url: Joi.when('$dev', {
- is: true,
- then: Joi.string().default(
- `https://telemetry-staging.elastic.co/xpack/${ENDPOINT_VERSION}/send`
- ),
- otherwise: Joi.string().default(
- `https://telemetry.elastic.co/xpack/${ENDPOINT_VERSION}/send`
- ),
- }),
- optInStatusUrl: Joi.when('$dev', {
- is: true,
- then: Joi.string().default(
- `https://telemetry-staging.elastic.co/opt_in_status/${ENDPOINT_VERSION}/send`
- ),
- otherwise: Joi.string().default(
- `https://telemetry.elastic.co/opt_in_status/${ENDPOINT_VERSION}/send`
- ),
- }),
- sendUsageFrom: Joi.string()
- .allow(['server', 'browser'])
- .default('browser'),
- }).default();
- },
- uiExports: {
- managementSections: ['plugins/telemetry/views/management'],
- savedObjectSchemas: {
- telemetry: {
- isNamespaceAgnostic: true,
- },
- },
- async replaceInjectedVars(originalInjectedVars: any, request: any, server: any) {
- const telemetryInjectedVars = await replaceTelemetryInjectedVars(request, server);
- return Object.assign({}, originalInjectedVars, telemetryInjectedVars);
- },
- injectDefaultVars(server: Server) {
- const config = server.config();
- return {
- telemetryEnabled: config.get('telemetry.enabled'),
- telemetryUrl: config.get('telemetry.url'),
- telemetryBanner:
- config.get('telemetry.allowChangingOptInStatus') !== false &&
- config.get('telemetry.banner'),
- telemetryOptedIn: config.get('telemetry.optIn'),
- telemetryOptInStatusUrl: config.get('telemetry.optInStatusUrl'),
- allowChangingOptInStatus: config.get('telemetry.allowChangingOptInStatus'),
- telemetrySendUsageFrom: config.get('telemetry.sendUsageFrom'),
- telemetryNotifyUserAboutOptInDefault: false,
- };
- },
- mappings,
- },
- postInit(server: Server) {
- const fetcherTask = new FetcherTask(server);
- fetcherTask.start();
- },
- async init(server: Server) {
- const { usageCollection } = server.newPlatform.setup.plugins;
- const initializerContext = {
- env: {
- packageInfo: {
- version: server.config().get('pkg.version'),
- },
- },
- config: {
- create() {
- const config = server.config();
- return Rx.of({
- enabled: config.get('telemetry.enabled'),
- optIn: config.get('telemetry.optIn'),
- config: config.get('telemetry.config'),
- banner: config.get('telemetry.banner'),
- url: config.get('telemetry.url'),
- allowChangingOptInStatus: config.get('telemetry.allowChangingOptInStatus'),
- });
- },
- },
- } as PluginInitializerContext;
-
- try {
- await handleOldSettings(server);
- } catch (err) {
- server.log(['warning', 'telemetry'], 'Unable to update legacy telemetry configs.');
- }
-
- const pluginsSetup: PluginsSetup = {
- usageCollection,
- };
-
- const npPlugin = telemetryPlugin(initializerContext);
- await npPlugin.setup(server.newPlatform.setup.core, pluginsSetup, server);
- await npPlugin.start(server.newPlatform.start.core);
- },
- });
-};
-
-// eslint-disable-next-line import/no-default-export
-export default telemetry;
diff --git a/src/legacy/core_plugins/telemetry/mappings.json b/src/legacy/core_plugins/telemetry/mappings.json
deleted file mode 100644
index fa9cc93d6363a..0000000000000
--- a/src/legacy/core_plugins/telemetry/mappings.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "telemetry": {
- "properties": {
- "enabled": {
- "type": "boolean"
- },
- "sendUsageFrom": {
- "ignore_above": 256,
- "type": "keyword"
- },
- "lastReported": {
- "type": "date"
- },
- "lastVersionChecked": {
- "ignore_above": 256,
- "type": "keyword"
- },
- "userHasSeenNotice": {
- "type": "boolean"
- },
- "reportFailureCount": {
- "type": "integer"
- },
- "reportFailureVersion": {
- "ignore_above": 256,
- "type": "keyword"
- }
- }
- }
-}
diff --git a/src/legacy/core_plugins/telemetry/package.json b/src/legacy/core_plugins/telemetry/package.json
deleted file mode 100644
index 979e68cce742f..0000000000000
--- a/src/legacy/core_plugins/telemetry/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "telemetry",
- "version": "kibana"
-}
diff --git a/src/legacy/core_plugins/telemetry/public/views/management/management.tsx b/src/legacy/core_plugins/telemetry/public/views/management/management.tsx
deleted file mode 100644
index c8ae410e0aa57..0000000000000
--- a/src/legacy/core_plugins/telemetry/public/views/management/management.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import React from 'react';
-import routes from 'ui/routes';
-import { npStart, npSetup } from 'ui/new_platform';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { TelemetryManagementSection } from '../../../../../../plugins/telemetry/public/components';
-
-routes.defaults(/\/management/, {
- resolve: {
- telemetryManagementSection() {
- const { telemetry } = npStart.plugins as any;
- const { advancedSettings } = npSetup.plugins as any;
-
- if (telemetry && advancedSettings) {
- const componentRegistry = advancedSettings.component;
- const Component = (props: any) => (
-
- );
-
- componentRegistry.register(
- componentRegistry.componentType.PAGE_FOOTER_COMPONENT,
- Component,
- true
- );
- }
- },
- },
-});
diff --git a/src/legacy/core_plugins/telemetry/server/collection_manager.ts b/src/legacy/core_plugins/telemetry/server/collection_manager.ts
deleted file mode 100644
index ebac4bede85bb..0000000000000
--- a/src/legacy/core_plugins/telemetry/server/collection_manager.ts
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { encryptTelemetry } from './collectors';
-import { CallCluster } from '../../elasticsearch';
-import { UsageCollectionSetup } from '../../../../plugins/usage_collection/server';
-import { Cluster } from '../../elasticsearch';
-import { ESLicense } from './telemetry_collection/get_local_license';
-
-export type EncryptedStatsGetterConfig = { unencrypted: false } & {
- server: any;
- start: string;
- end: string;
-};
-
-export type UnencryptedStatsGetterConfig = { unencrypted: true } & {
- req: any;
- start: string;
- end: string;
-};
-
-export interface ClusterDetails {
- clusterUuid: string;
-}
-
-export interface StatsCollectionConfig {
- usageCollection: UsageCollectionSetup;
- callCluster: CallCluster;
- server: any;
- start: string | number;
- end: string | number;
-}
-
-export interface BasicStatsPayload {
- timestamp: string;
- cluster_uuid: string;
- cluster_name: string;
- version: string;
- cluster_stats: object;
- collection?: string;
- stack_stats: object;
-}
-
-export type StatsGetterConfig = UnencryptedStatsGetterConfig | EncryptedStatsGetterConfig;
-export type ClusterDetailsGetter = (config: StatsCollectionConfig) => Promise;
-export type StatsGetter = (
- clustersDetails: ClusterDetails[],
- config: StatsCollectionConfig
-) => Promise;
-export type LicenseGetter = (
- clustersDetails: ClusterDetails[],
- config: StatsCollectionConfig
-) => Promise<{ [clusterUuid: string]: ESLicense | undefined }>;
-
-interface CollectionConfig {
- title: string;
- priority: number;
- esCluster: string | Cluster;
- statsGetter: StatsGetter;
- clusterDetailsGetter: ClusterDetailsGetter;
- licenseGetter: LicenseGetter;
-}
-interface Collection {
- statsGetter: StatsGetter;
- licenseGetter: LicenseGetter;
- clusterDetailsGetter: ClusterDetailsGetter;
- esCluster: string | Cluster;
- title: string;
-}
-
-export class TelemetryCollectionManager {
- private usageGetterMethodPriority = -1;
- private collections: Collection[] = [];
-
- public setCollection = (collectionConfig: CollectionConfig) => {
- const {
- title,
- priority,
- esCluster,
- statsGetter,
- clusterDetailsGetter,
- licenseGetter,
- } = collectionConfig;
-
- if (typeof priority !== 'number') {
- throw new Error('priority must be set.');
- }
- if (priority === this.usageGetterMethodPriority) {
- throw new Error(`A Usage Getter with the same priority is already set.`);
- }
-
- if (priority > this.usageGetterMethodPriority) {
- if (!statsGetter) {
- throw Error('Stats getter method not set.');
- }
- if (!esCluster) {
- throw Error('esCluster name must be set for the getCluster method.');
- }
- if (!clusterDetailsGetter) {
- throw Error('Cluster UUIds method is not set.');
- }
- if (!licenseGetter) {
- throw Error('License getter method not set.');
- }
-
- this.collections.unshift({
- licenseGetter,
- statsGetter,
- clusterDetailsGetter,
- esCluster,
- title,
- });
- this.usageGetterMethodPriority = priority;
- }
- };
-
- private getStatsCollectionConfig = async (
- collection: Collection,
- config: StatsGetterConfig
- ): Promise => {
- const { start, end } = config;
- const server = config.unencrypted ? config.req.server : config.server;
- const { callWithRequest, callWithInternalUser } =
- typeof collection.esCluster === 'string'
- ? server.plugins.elasticsearch.getCluster(collection.esCluster)
- : collection.esCluster;
- const callCluster = config.unencrypted
- ? (...args: any[]) => callWithRequest(config.req, ...args)
- : callWithInternalUser;
-
- const { usageCollection } = server.newPlatform.setup.plugins;
- return { server, callCluster, start, end, usageCollection };
- };
-
- private getOptInStatsForCollection = async (
- collection: Collection,
- optInStatus: boolean,
- statsCollectionConfig: StatsCollectionConfig
- ) => {
- const clustersDetails = await collection.clusterDetailsGetter(statsCollectionConfig);
- return clustersDetails.map(({ clusterUuid }) => ({
- cluster_uuid: clusterUuid,
- opt_in_status: optInStatus,
- }));
- };
-
- private getUsageForCollection = async (
- collection: Collection,
- statsCollectionConfig: StatsCollectionConfig
- ) => {
- const clustersDetails = await collection.clusterDetailsGetter(statsCollectionConfig);
-
- if (clustersDetails.length === 0) {
- // don't bother doing a further lookup, try next collection.
- return;
- }
-
- const [stats, licenses] = await Promise.all([
- collection.statsGetter(clustersDetails, statsCollectionConfig),
- collection.licenseGetter(clustersDetails, statsCollectionConfig),
- ]);
-
- return stats.map(stat => {
- const license = licenses[stat.cluster_uuid];
- return {
- ...(license ? { license } : {}),
- ...stat,
- collectionSource: collection.title,
- };
- });
- };
-
- public getOptInStats = async (optInStatus: boolean, config: StatsGetterConfig) => {
- for (const collection of this.collections) {
- const statsCollectionConfig = await this.getStatsCollectionConfig(collection, config);
- try {
- const optInStats = await this.getOptInStatsForCollection(
- collection,
- optInStatus,
- statsCollectionConfig
- );
- if (optInStats && optInStats.length) {
- statsCollectionConfig.server.log(
- ['debug', 'telemetry', 'collection'],
- `Got Opt In stats using ${collection.title} collection.`
- );
- if (config.unencrypted) {
- return optInStats;
- }
- const isDev = statsCollectionConfig.server.config().get('env.dev');
- return encryptTelemetry(optInStats, isDev);
- }
- } catch (err) {
- statsCollectionConfig.server.log(
- ['debu', 'telemetry', 'collection'],
- `Failed to collect any opt in stats with registered collections.`
- );
- // swallow error to try next collection;
- }
- }
-
- return [];
- };
- public getStats = async (config: StatsGetterConfig) => {
- for (const collection of this.collections) {
- const statsCollectionConfig = await this.getStatsCollectionConfig(collection, config);
- try {
- const usageData = await this.getUsageForCollection(collection, statsCollectionConfig);
- if (usageData && usageData.length) {
- statsCollectionConfig.server.log(
- ['debug', 'telemetry', 'collection'],
- `Got Usage using ${collection.title} collection.`
- );
- if (config.unencrypted) {
- return usageData;
- }
- const isDev = statsCollectionConfig.server.config().get('env.dev');
- return encryptTelemetry(usageData, isDev);
- }
- } catch (err) {
- statsCollectionConfig.server.log(
- ['debug', 'telemetry', 'collection'],
- `Failed to collect any usage with registered collections.`
- );
- // swallow error to try next collection;
- }
- }
-
- return [];
- };
-}
-
-export const telemetryCollectionManager = new TelemetryCollectionManager();
diff --git a/src/legacy/core_plugins/telemetry/server/plugin.ts b/src/legacy/core_plugins/telemetry/server/plugin.ts
deleted file mode 100644
index 0b9f0526988c8..0000000000000
--- a/src/legacy/core_plugins/telemetry/server/plugin.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import {
- CoreSetup,
- PluginInitializerContext,
- ISavedObjectsRepository,
- CoreStart,
-} from 'src/core/server';
-import { Server } from 'hapi';
-import { registerRoutes } from './routes';
-import { registerCollection } from './telemetry_collection';
-import { UsageCollectionSetup } from '../../../../plugins/usage_collection/server';
-import {
- registerUiMetricUsageCollector,
- registerTelemetryUsageCollector,
- registerLocalizationUsageCollector,
- registerTelemetryPluginUsageCollector,
- registerManagementUsageCollector,
- registerApplicationUsageCollector,
-} from './collectors';
-
-export interface PluginsSetup {
- usageCollection: UsageCollectionSetup;
-}
-
-export class TelemetryPlugin {
- private readonly currentKibanaVersion: string;
- private savedObjectsClient?: ISavedObjectsRepository;
-
- constructor(initializerContext: PluginInitializerContext) {
- this.currentKibanaVersion = initializerContext.env.packageInfo.version;
- }
-
- public setup(core: CoreSetup, { usageCollection }: PluginsSetup, server: Server) {
- const currentKibanaVersion = this.currentKibanaVersion;
-
- registerCollection();
- registerRoutes({ core, currentKibanaVersion, server });
-
- const getSavedObjectsClient = () => this.savedObjectsClient;
-
- registerTelemetryPluginUsageCollector(usageCollection, server);
- registerLocalizationUsageCollector(usageCollection, server);
- registerTelemetryUsageCollector(usageCollection, server);
- registerUiMetricUsageCollector(usageCollection, getSavedObjectsClient);
- registerManagementUsageCollector(usageCollection, server);
- registerApplicationUsageCollector(usageCollection, getSavedObjectsClient);
- }
-
- public start({ savedObjects }: CoreStart) {
- this.savedObjectsClient = savedObjects.createInternalRepository();
- }
-}
diff --git a/src/legacy/core_plugins/telemetry/server/routes/telemetry_opt_in.ts b/src/legacy/core_plugins/telemetry/server/routes/telemetry_opt_in.ts
deleted file mode 100644
index ccbc28f6cbadb..0000000000000
--- a/src/legacy/core_plugins/telemetry/server/routes/telemetry_opt_in.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import Joi from 'joi';
-import moment from 'moment';
-import { boomify } from 'boom';
-import { CoreSetup } from 'src/core/server';
-import { Legacy } from 'kibana';
-import { getTelemetryAllowChangingOptInStatus } from '../telemetry_config';
-import { sendTelemetryOptInStatus } from './telemetry_opt_in_stats';
-
-import {
- TelemetrySavedObjectAttributes,
- updateTelemetrySavedObject,
-} from '../telemetry_repository';
-
-interface RegisterOptInRoutesParams {
- core: CoreSetup;
- currentKibanaVersion: string;
- server: Legacy.Server;
-}
-
-export function registerTelemetryOptInRoutes({
- server,
- currentKibanaVersion,
-}: RegisterOptInRoutesParams) {
- server.route({
- method: 'POST',
- path: '/api/telemetry/v2/optIn',
- options: {
- validate: {
- payload: Joi.object({
- enabled: Joi.bool().required(),
- }),
- },
- },
- handler: async (req: any, h: any) => {
- try {
- const newOptInStatus = req.payload.enabled;
- const attributes: TelemetrySavedObjectAttributes = {
- enabled: newOptInStatus,
- lastVersionChecked: currentKibanaVersion,
- };
- const config = req.server.config();
- const savedObjectsClient = req.getSavedObjectsClient();
- const configTelemetryAllowChangingOptInStatus = config.get(
- 'telemetry.allowChangingOptInStatus'
- );
-
- const allowChangingOptInStatus = getTelemetryAllowChangingOptInStatus({
- telemetrySavedObject: savedObjectsClient,
- configTelemetryAllowChangingOptInStatus,
- });
- if (!allowChangingOptInStatus) {
- return h.response({ error: 'Not allowed to change Opt-in Status.' }).code(400);
- }
-
- const sendUsageFrom = config.get('telemetry.sendUsageFrom');
- if (sendUsageFrom === 'server') {
- const optInStatusUrl = config.get('telemetry.optInStatusUrl');
- await sendTelemetryOptInStatus(
- { optInStatusUrl, newOptInStatus },
- {
- start: moment()
- .subtract(20, 'minutes')
- .toISOString(),
- end: moment().toISOString(),
- server: req.server,
- unencrypted: false,
- }
- );
- }
-
- await updateTelemetrySavedObject(savedObjectsClient, attributes);
- return h.response({}).code(200);
- } catch (err) {
- return boomify(err);
- }
- },
- });
-}
diff --git a/src/legacy/core_plugins/telemetry/server/routes/telemetry_usage_stats.ts b/src/legacy/core_plugins/telemetry/server/routes/telemetry_usage_stats.ts
deleted file mode 100644
index ee3241b0dc2ea..0000000000000
--- a/src/legacy/core_plugins/telemetry/server/routes/telemetry_usage_stats.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import Joi from 'joi';
-import { boomify } from 'boom';
-import { Legacy } from 'kibana';
-import { telemetryCollectionManager } from '../collection_manager';
-
-export function registerTelemetryUsageStatsRoutes(server: Legacy.Server) {
- server.route({
- method: 'POST',
- path: '/api/telemetry/v2/clusters/_stats',
- options: {
- validate: {
- payload: Joi.object({
- unencrypted: Joi.bool(),
- timeRange: Joi.object({
- min: Joi.date().required(),
- max: Joi.date().required(),
- }).required(),
- }),
- },
- },
- handler: async (req: any, h: any) => {
- const config = req.server.config();
- const start = req.payload.timeRange.min;
- const end = req.payload.timeRange.max;
- const unencrypted = req.payload.unencrypted;
-
- try {
- return await telemetryCollectionManager.getStats({
- unencrypted,
- server,
- req,
- start,
- end,
- });
- } catch (err) {
- const isDev = config.get('env.dev');
- if (isDev) {
- // don't ignore errors when running in dev mode
- return boomify(err, { statusCode: err.status || 500 });
- } else {
- const statusCode = unencrypted && err.status === 403 ? 403 : 200;
- // ignore errors and return empty set
- return h.response([]).code(statusCode);
- }
- }
- },
- });
-}
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/replace_injected_vars.ts b/src/legacy/core_plugins/telemetry/server/telemetry_config/replace_injected_vars.ts
deleted file mode 100644
index f09ee8623afac..0000000000000
--- a/src/legacy/core_plugins/telemetry/server/telemetry_config/replace_injected_vars.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { getTelemetrySavedObject } from '../telemetry_repository';
-import { getTelemetryOptIn } from './get_telemetry_opt_in';
-import { getTelemetrySendUsageFrom } from './get_telemetry_send_usage_from';
-import { getTelemetryAllowChangingOptInStatus } from './get_telemetry_allow_changing_opt_in_status';
-import { getNotifyUserAboutOptInDefault } from './get_telemetry_notify_user_about_optin_default';
-
-export async function replaceTelemetryInjectedVars(request: any, server: any) {
- const config = server.config();
- const configTelemetrySendUsageFrom = config.get('telemetry.sendUsageFrom');
- const configTelemetryOptIn = config.get('telemetry.optIn');
- const configTelemetryAllowChangingOptInStatus = config.get('telemetry.allowChangingOptInStatus');
- const isRequestingApplication = request.path.startsWith('/app');
-
- // Prevent interstitial screens (such as the space selector) from prompting for telemetry
- if (!isRequestingApplication) {
- return {
- telemetryOptedIn: false,
- };
- }
-
- const currentKibanaVersion = config.get('pkg.version');
- const savedObjectsClient = server.savedObjects.getScopedSavedObjectsClient(request);
- const telemetrySavedObject = await getTelemetrySavedObject(savedObjectsClient);
- const allowChangingOptInStatus = getTelemetryAllowChangingOptInStatus({
- configTelemetryAllowChangingOptInStatus,
- telemetrySavedObject,
- });
-
- const telemetryOptedIn = getTelemetryOptIn({
- configTelemetryOptIn,
- allowChangingOptInStatus,
- telemetrySavedObject,
- currentKibanaVersion,
- });
-
- const telemetrySendUsageFrom = getTelemetrySendUsageFrom({
- configTelemetrySendUsageFrom,
- telemetrySavedObject,
- });
-
- const telemetryNotifyUserAboutOptInDefault = getNotifyUserAboutOptInDefault({
- telemetrySavedObject,
- allowChangingOptInStatus,
- configTelemetryOptIn,
- telemetryOptedIn,
- });
-
- return {
- telemetryOptedIn,
- telemetrySendUsageFrom,
- telemetryNotifyUserAboutOptInDefault,
- };
-}
diff --git a/src/legacy/core_plugins/ui_metric/index.ts b/src/legacy/core_plugins/ui_metric/index.ts
index 5a4a0ebf1a632..2e5be3d7b0a39 100644
--- a/src/legacy/core_plugins/ui_metric/index.ts
+++ b/src/legacy/core_plugins/ui_metric/index.ts
@@ -25,9 +25,6 @@ export default function(kibana: any) {
id: 'ui_metric',
require: ['kibana', 'elasticsearch'],
publicDir: resolve(__dirname, 'public'),
- uiExports: {
- mappings: require('./mappings.json'),
- },
init() {},
});
}
diff --git a/src/legacy/core_plugins/ui_metric/mappings.json b/src/legacy/core_plugins/ui_metric/mappings.json
deleted file mode 100644
index 113e37e60e48b..0000000000000
--- a/src/legacy/core_plugins/ui_metric/mappings.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "ui-metric": {
- "properties": {
- "count": {
- "type": "integer"
- }
- }
- }
-}
diff --git a/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis_controller.tsx b/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis_controller.tsx
index 4e77bb196b713..3260e9f7d8091 100644
--- a/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis_controller.tsx
+++ b/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis_controller.tsx
@@ -18,7 +18,7 @@
*/
import React from 'react';
-import { Markdown } from '../../kibana_react/public';
+import { Markdown } from '../../../../plugins/kibana_react/public';
import { MarkdownVisParams } from './types';
interface MarkdownVisComponentProps extends MarkdownVisParams {
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/markdown/vis.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/markdown/vis.js
index a806339085450..d8bcf56b48cb9 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/markdown/vis.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/markdown/vis.js
@@ -21,7 +21,7 @@ import React from 'react';
import classNames from 'classnames';
import uuid from 'uuid';
import { get } from 'lodash';
-import { Markdown } from '../../../../../kibana_react/public';
+import { Markdown } from '../../../../../../../plugins/kibana_react/public';
import { ErrorComponent } from '../../error';
import { replaceVars } from '../../lib/replace_vars';
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js
index 356ba08ac2427..f559bc38b6c58 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js
@@ -27,7 +27,7 @@ import { ScaleType } from '@elastic/charts';
import { createTickFormatter } from '../../lib/tick_formatter';
import { TimeSeries } from '../../../visualizations/views/timeseries';
-import { MarkdownSimple } from '../../../../../kibana_react/public';
+import { MarkdownSimple } from '../../../../../../../plugins/kibana_react/public';
import { replaceVars } from '../../lib/replace_vars';
import { getAxisLabelString } from '../../lib/get_axis_label_string';
import { getInterval } from '../../lib/get_interval';
diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js
index a24ffcbaaa49f..769d9ba311281 100644
--- a/src/legacy/server/config/schema.js
+++ b/src/legacy/server/config/schema.js
@@ -133,8 +133,8 @@ export default () =>
.keys({
enabled: Joi.boolean().default(false),
everyBytes: Joi.number()
- // > 100KB
- .greater(102399)
+ // > 1MB
+ .greater(1048576)
// < 1GB
.less(1073741825)
// 10MB
diff --git a/src/legacy/server/i18n/constants.ts b/src/legacy/server/i18n/constants.ts
new file mode 100644
index 0000000000000..96fa420d4c6e1
--- /dev/null
+++ b/src/legacy/server/i18n/constants.ts
@@ -0,0 +1,25 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export const I18N_RC = '.i18nrc.json';
+
+/**
+ * The type name used within the Monitoring index to publish localization stats.
+ */
+export const KIBANA_LOCALIZATION_STATS_TYPE = 'localization';
diff --git a/src/legacy/server/i18n/get_translations_path.js b/src/legacy/server/i18n/get_translations_path.ts
similarity index 85%
rename from src/legacy/server/i18n/get_translations_path.js
rename to src/legacy/server/i18n/get_translations_path.ts
index 6ac3e75e1d4a8..ac7c61dcf8543 100644
--- a/src/legacy/server/i18n/get_translations_path.js
+++ b/src/legacy/server/i18n/get_translations_path.ts
@@ -24,16 +24,20 @@ import globby from 'globby';
const readFileAsync = promisify(readFile);
-export async function getTranslationPaths({ cwd, glob }) {
+interface I18NRCFileStructure {
+ translations?: string[];
+}
+
+export async function getTranslationPaths({ cwd, glob }: { cwd: string; glob: string }) {
const entries = await globby(glob, { cwd });
- const translationPaths = [];
+ const translationPaths: string[] = [];
for (const entry of entries) {
const entryFullPath = resolve(cwd, entry);
const pluginBasePath = dirname(entryFullPath);
try {
const content = await readFileAsync(entryFullPath, 'utf8');
- const { translations } = JSON.parse(content);
+ const { translations } = JSON.parse(content) as I18NRCFileStructure;
if (translations && translations.length) {
translations.forEach(translation => {
const translationFullPath = resolve(pluginBasePath, translation);
diff --git a/src/legacy/server/i18n/index.js b/src/legacy/server/i18n/index.ts
similarity index 62%
rename from src/legacy/server/i18n/index.js
rename to src/legacy/server/i18n/index.ts
index e7fa5d5f6a5c0..9902aaa1e8914 100644
--- a/src/legacy/server/i18n/index.js
+++ b/src/legacy/server/i18n/index.ts
@@ -19,30 +19,35 @@
import { i18n, i18nLoader } from '@kbn/i18n';
import { basename } from 'path';
+import { Server } from 'hapi';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { fromRoot } from '../../../core/server/utils';
import { getTranslationPaths } from './get_translations_path';
import { I18N_RC } from './constants';
+import KbnServer, { KibanaConfig } from '../kbn_server';
+import { registerLocalizationUsageCollector } from './localization';
-export async function i18nMixin(kbnServer, server, config) {
- const locale = config.get('i18n.locale');
+export async function i18nMixin(kbnServer: KbnServer, server: Server, config: KibanaConfig) {
+ const locale = config.get('i18n.locale') as string;
const translationPaths = await Promise.all([
getTranslationPaths({
cwd: fromRoot('.'),
glob: I18N_RC,
}),
- ...config.get('plugins.paths').map(cwd => getTranslationPaths({ cwd, glob: I18N_RC })),
- ...config
- .get('plugins.scanDirs')
- .map(cwd => getTranslationPaths({ cwd, glob: `*/${I18N_RC}` })),
+ ...(config.get('plugins.paths') as string[]).map(cwd =>
+ getTranslationPaths({ cwd, glob: I18N_RC })
+ ),
+ ...(config.get('plugins.scanDirs') as string[]).map(cwd =>
+ getTranslationPaths({ cwd, glob: `*/${I18N_RC}` })
+ ),
getTranslationPaths({
cwd: fromRoot('../kibana-extra'),
glob: `*/${I18N_RC}`,
}),
]);
- const currentTranslationPaths = []
+ const currentTranslationPaths = ([] as string[])
.concat(...translationPaths)
.filter(translationPath => basename(translationPath, '.json') === locale);
i18nLoader.registerTranslationFiles(currentTranslationPaths);
@@ -55,5 +60,14 @@ export async function i18nMixin(kbnServer, server, config) {
})
);
- server.decorate('server', 'getTranslationsFilePaths', () => currentTranslationPaths);
+ const getTranslationsFilePaths = () => currentTranslationPaths;
+
+ server.decorate('server', 'getTranslationsFilePaths', getTranslationsFilePaths);
+
+ if (kbnServer.newPlatform.setup.plugins.usageCollection) {
+ registerLocalizationUsageCollector(kbnServer.newPlatform.setup.plugins.usageCollection, {
+ getLocale: () => config.get('i18n.locale') as string,
+ getTranslationsFilePaths,
+ });
+ }
}
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/localization/file_integrity.test.mocks.ts b/src/legacy/server/i18n/localization/file_integrity.test.mocks.ts
similarity index 100%
rename from src/legacy/core_plugins/telemetry/server/collectors/localization/file_integrity.test.mocks.ts
rename to src/legacy/server/i18n/localization/file_integrity.test.mocks.ts
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/localization/file_integrity.test.ts b/src/legacy/server/i18n/localization/file_integrity.test.ts
similarity index 100%
rename from src/legacy/core_plugins/telemetry/server/collectors/localization/file_integrity.test.ts
rename to src/legacy/server/i18n/localization/file_integrity.test.ts
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/localization/file_integrity.ts b/src/legacy/server/i18n/localization/file_integrity.ts
similarity index 100%
rename from src/legacy/core_plugins/telemetry/server/collectors/localization/file_integrity.ts
rename to src/legacy/server/i18n/localization/file_integrity.ts
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/localization/index.ts b/src/legacy/server/i18n/localization/index.ts
similarity index 100%
rename from src/legacy/core_plugins/telemetry/server/collectors/localization/index.ts
rename to src/legacy/server/i18n/localization/index.ts
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/localization/telemetry_localization_collector.test.ts b/src/legacy/server/i18n/localization/telemetry_localization_collector.test.ts
similarity index 94%
rename from src/legacy/core_plugins/telemetry/server/collectors/localization/telemetry_localization_collector.test.ts
rename to src/legacy/server/i18n/localization/telemetry_localization_collector.test.ts
index eec5cc8a065e4..cbe23da87c767 100644
--- a/src/legacy/core_plugins/telemetry/server/collectors/localization/telemetry_localization_collector.test.ts
+++ b/src/legacy/server/i18n/localization/telemetry_localization_collector.test.ts
@@ -22,16 +22,17 @@ interface TranslationsMock {
}
const createI18nLoaderMock = (translations: TranslationsMock) => {
- return {
+ return ({
getTranslationsByLocale() {
return {
messages: translations,
};
},
- };
+ } as unknown) as typeof i18nLoader;
};
import { getTranslationCount } from './telemetry_localization_collector';
+import { i18nLoader } from '@kbn/i18n';
describe('getTranslationCount', () => {
it('returns 0 if no translations registered', async () => {
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/localization/telemetry_localization_collector.ts b/src/legacy/server/i18n/localization/telemetry_localization_collector.ts
similarity index 71%
rename from src/legacy/core_plugins/telemetry/server/collectors/localization/telemetry_localization_collector.ts
rename to src/legacy/server/i18n/localization/telemetry_localization_collector.ts
index 191565187be14..89566dfd4ef68 100644
--- a/src/legacy/core_plugins/telemetry/server/collectors/localization/telemetry_localization_collector.ts
+++ b/src/legacy/server/i18n/localization/telemetry_localization_collector.ts
@@ -19,25 +19,36 @@
import { i18nLoader } from '@kbn/i18n';
import { size } from 'lodash';
+import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { getIntegrityHashes, Integrities } from './file_integrity';
-import { KIBANA_LOCALIZATION_STATS_TYPE } from '../../../common/constants';
-import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server';
+import { KIBANA_LOCALIZATION_STATS_TYPE } from '../constants';
+
export interface UsageStats {
locale: string;
integrities: Integrities;
labelsCount?: number;
}
-export async function getTranslationCount(loader: any, locale: string): Promise {
+export interface LocalizationUsageCollectorHelpers {
+ getLocale: () => string;
+ getTranslationsFilePaths: () => string[];
+}
+
+export async function getTranslationCount(
+ loader: typeof i18nLoader,
+ locale: string
+): Promise {
const translations = await loader.getTranslationsByLocale(locale);
return size(translations.messages);
}
-export function createCollectorFetch(server: any) {
+export function createCollectorFetch({
+ getLocale,
+ getTranslationsFilePaths,
+}: LocalizationUsageCollectorHelpers) {
return async function fetchUsageStats(): Promise {
- const config = server.config();
- const locale: string = config.get('i18n.locale');
- const translationFilePaths: string[] = server.getTranslationsFilePaths();
+ const locale = getLocale();
+ const translationFilePaths: string[] = getTranslationsFilePaths();
const [labelsCount, integrities] = await Promise.all([
getTranslationCount(i18nLoader, locale),
@@ -54,12 +65,12 @@ export function createCollectorFetch(server: any) {
export function registerLocalizationUsageCollector(
usageCollection: UsageCollectionSetup,
- server: any
+ helpers: LocalizationUsageCollectorHelpers
) {
const collector = usageCollection.makeUsageCollector({
type: KIBANA_LOCALIZATION_STATS_TYPE,
isReady: () => true,
- fetch: createCollectorFetch(server),
+ fetch: createCollectorFetch(helpers),
});
usageCollection.registerCollector(collector);
diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts
index 9952b345fa06f..d222dbb550f11 100644
--- a/src/legacy/server/kbn_server.d.ts
+++ b/src/legacy/server/kbn_server.d.ts
@@ -20,6 +20,7 @@
import { ResponseObject, Server } from 'hapi';
import { UnwrapPromise } from '@kbn/utility-types';
+import { TelemetryCollectionManagerPluginSetup } from 'src/plugins/telemetry_collection_manager/server';
import {
ConfigService,
CoreSetup,
@@ -104,6 +105,7 @@ type KbnMixinFunc = (kbnServer: KbnServer, server: Server, config: any) => Promi
export interface PluginsSetup {
usageCollection: UsageCollectionSetup;
+ telemetryCollectionManager: TelemetryCollectionManagerPluginSetup;
home: HomeServerPluginSetup;
[key: string]: object;
}
diff --git a/src/legacy/server/logging/rotate/log_rotator.test.ts b/src/legacy/server/logging/rotate/log_rotator.test.ts
index c2100546364d4..70842d42f5e1f 100644
--- a/src/legacy/server/logging/rotate/log_rotator.test.ts
+++ b/src/legacy/server/logging/rotate/log_rotator.test.ts
@@ -204,8 +204,8 @@ describe('LogRotator', () => {
expect(logRotator.running).toBe(true);
expect(logRotator.usePolling).toBe(false);
- const usePolling = await logRotator._shouldUsePolling();
- expect(usePolling).toBe(false);
+ const shouldUsePolling = await logRotator._shouldUsePolling();
+ expect(shouldUsePolling).toBe(false);
await logRotator.stop();
});
@@ -231,7 +231,8 @@ describe('LogRotator', () => {
await logRotator.start();
expect(logRotator.running).toBe(true);
- expect(logRotator.usePolling).toBe(true);
+ expect(logRotator.usePolling).toBe(false);
+ expect(logRotator.shouldUsePolling).toBe(true);
await logRotator.stop();
});
@@ -257,7 +258,8 @@ describe('LogRotator', () => {
await logRotator.start();
expect(logRotator.running).toBe(true);
- expect(logRotator.usePolling).toBe(true);
+ expect(logRotator.usePolling).toBe(false);
+ expect(logRotator.shouldUsePolling).toBe(true);
await logRotator.stop();
jest.useRealTimers();
diff --git a/src/legacy/server/logging/rotate/log_rotator.ts b/src/legacy/server/logging/rotate/log_rotator.ts
index 3662910ca5a7b..eeb91fd0f2636 100644
--- a/src/legacy/server/logging/rotate/log_rotator.ts
+++ b/src/legacy/server/logging/rotate/log_rotator.ts
@@ -50,6 +50,7 @@ export class LogRotator {
public usePolling: boolean;
public pollingInterval: number;
private stalkerUsePollingPolicyTestTimeout: NodeJS.Timeout | null;
+ public shouldUsePolling: boolean;
constructor(config: KibanaConfig, server: Server) {
this.config = config;
@@ -64,6 +65,7 @@ export class LogRotator {
this.stalker = null;
this.usePolling = config.get('logging.rotate.usePolling');
this.pollingInterval = config.get('logging.rotate.pollingInterval');
+ this.shouldUsePolling = false;
this.stalkerUsePollingPolicyTestTimeout = null;
}
@@ -150,12 +152,20 @@ export class LogRotator {
}
async _startLogFileSizeMonitor() {
- this.usePolling = await this._shouldUsePolling();
+ this.usePolling = this.config.get('logging.rotate.usePolling');
+ this.shouldUsePolling = await this._shouldUsePolling();
- if (this.usePolling && this.usePolling !== this.config.get('logging.rotate.usePolling')) {
+ if (this.usePolling && !this.shouldUsePolling) {
this.log(
['warning', 'logging:rotate'],
- 'The current environment does not support `fs.watch`. Falling back to polling using `fs.watchFile`'
+ 'Looks like your current environment support a faster algorithm then polling. You can try to disable `usePolling`'
+ );
+ }
+
+ if (!this.usePolling && this.shouldUsePolling) {
+ this.log(
+ ['error', 'logging:rotate'],
+ 'Looks like within your current environment you need to use polling in order to enable log rotator. Please enable `usePolling`'
);
}
diff --git a/src/legacy/server/status/index.js b/src/legacy/server/status/index.js
index a9544049182a7..df02b3c45ec2f 100644
--- a/src/legacy/server/status/index.js
+++ b/src/legacy/server/status/index.js
@@ -57,7 +57,7 @@ export function statusMixin(kbnServer, server, config) {
// init routes
registerStatusPage(kbnServer, server, config);
registerStatusApi(kbnServer, server, config);
- registerStatsApi(usageCollection, server, config);
+ registerStatsApi(usageCollection, server, config, kbnServer);
// expore shared functionality
server.decorate('server', 'getOSInfo', getOSInfo);
diff --git a/src/legacy/server/status/routes/api/register_stats.js b/src/legacy/server/status/routes/api/register_stats.js
index e218c1caf1701..2dd66cb8caff7 100644
--- a/src/legacy/server/status/routes/api/register_stats.js
+++ b/src/legacy/server/status/routes/api/register_stats.js
@@ -21,7 +21,7 @@ import Joi from 'joi';
import boom from 'boom';
import { i18n } from '@kbn/i18n';
import { wrapAuthConfig } from '../../wrap_auth_config';
-import { KIBANA_STATS_TYPE } from '../../constants';
+import { getKibanaInfoForStats } from '../../lib';
const STATS_NOT_READY_MESSAGE = i18n.translate('server.stats.notReadyMessage', {
defaultMessage: 'Stats are not ready yet. Please try again later.',
@@ -37,7 +37,7 @@ const STATS_NOT_READY_MESSAGE = i18n.translate('server.stats.notReadyMessage', {
* - Any other value causes a statusCode 400 response (Bad Request)
* Including ?exclude_usage in the query string excludes the usage stats from the response. Same value semantics as ?extended
*/
-export function registerStatsApi(usageCollection, server, config) {
+export function registerStatsApi(usageCollection, server, config, kbnServer) {
const wrapAuth = wrapAuthConfig(config.get('status.allowAnonymous'));
const getClusterUuid = async callCluster => {
@@ -50,6 +50,17 @@ export function registerStatsApi(usageCollection, server, config) {
return usageCollection.toObject(usage);
};
+ let lastMetrics = null;
+ /* kibana_stats gets singled out from the collector set as it is used
+ * for health-checking Kibana and fetch does not rely on fetching data
+ * from ES */
+ server.newPlatform.setup.core.metrics.getOpsMetrics$().subscribe(metrics => {
+ lastMetrics = {
+ ...metrics,
+ timestamp: new Date().toISOString(),
+ };
+ });
+
server.route(
wrapAuth({
method: 'GET',
@@ -133,15 +144,15 @@ export function registerStatsApi(usageCollection, server, config) {
}
}
- /* kibana_stats gets singled out from the collector set as it is used
- * for health-checking Kibana and fetch does not rely on fetching data
- * from ES */
- const kibanaStatsCollector = usageCollection.getCollectorByType(KIBANA_STATS_TYPE);
- if (!(await kibanaStatsCollector.isReady())) {
+ if (!lastMetrics) {
return boom.serverUnavailable(STATS_NOT_READY_MESSAGE);
}
- let kibanaStats = await kibanaStatsCollector.fetch();
- kibanaStats = usageCollection.toApiFieldNames(kibanaStats);
+ const kibanaStats = usageCollection.toApiFieldNames({
+ ...lastMetrics,
+ kibana: getKibanaInfoForStats(server, kbnServer),
+ last_updated: new Date().toISOString(),
+ collection_interval_in_millis: config.get('ops.interval'),
+ });
return {
...kibanaStats,
diff --git a/src/legacy/ui/public/_index.scss b/src/legacy/ui/public/_index.scss
index 3c3067776a161..87006d9347de4 100644
--- a/src/legacy/ui/public/_index.scss
+++ b/src/legacy/ui/public/_index.scss
@@ -15,7 +15,6 @@
@import './error_url_overflow/index';
@import './exit_full_screen/index';
@import './field_editor/index';
-@import './share/index';
@import './style_compile/index';
@import '../../../plugins/management/public/components/index';
diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
index c58a7d2fbb5cd..67877c5382633 100644
--- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
+++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js
@@ -271,6 +271,12 @@ export const npSetup = {
}),
},
},
+ discover: {
+ docViews: {
+ addDocView: sinon.fake(),
+ setAngularInjectorGetter: sinon.fake(),
+ },
+ },
visTypeVega: {
config: sinon.fake(),
},
@@ -324,6 +330,9 @@ export const npStart = {
getHideWriteControls: sinon.fake(),
},
},
+ dashboard: {
+ getSavedDashboardLoader: sinon.fake(),
+ },
data: {
actions: {
createFiltersFromEvent: Promise.resolve(['yes']),
@@ -459,6 +468,11 @@ export const npStart = {
useChartsTheme: sinon.fake(),
},
},
+ discover: {
+ docViews: {
+ DocViewer: () => null,
+ },
+ },
},
};
diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts
index deb8387fee29c..b315abec1a64b 100644
--- a/src/legacy/ui/public/new_platform/new_platform.ts
+++ b/src/legacy/ui/public/new_platform/new_platform.ts
@@ -22,6 +22,7 @@ import { IScope } from 'angular';
import { UiActionsStart, UiActionsSetup } from 'src/plugins/ui_actions/public';
import { EmbeddableStart, EmbeddableSetup } from 'src/plugins/embeddable/public';
import { createBrowserHistory } from 'history';
+import { DashboardStart } from '../../../../plugins/dashboard/public';
import {
LegacyCoreSetup,
LegacyCoreStart,
@@ -65,6 +66,7 @@ import {
NavigationPublicPluginStart,
} from '../../../../plugins/navigation/public';
import { VisTypeVegaSetup } from '../../../../plugins/vis_type_vega/public';
+import { DiscoverSetup, DiscoverStart } from '../../../../plugins/discover/public';
export interface PluginsSetup {
bfetch: BfetchPublicSetup;
@@ -83,6 +85,7 @@ export interface PluginsSetup {
advancedSettings: AdvancedSettingsSetup;
management: ManagementSetup;
visTypeVega: VisTypeVegaSetup;
+ discover: DiscoverSetup;
telemetry?: TelemetryPluginSetup;
}
@@ -100,7 +103,9 @@ export interface PluginsStart {
share: SharePluginStart;
management: ManagementStart;
advancedSettings: AdvancedSettingsStart;
+ discover: DiscoverStart;
telemetry?: TelemetryPluginStart;
+ dashboard: DashboardStart;
}
export const npSetup = {
diff --git a/src/plugins/advanced_settings/public/management_app/index.tsx b/src/plugins/advanced_settings/public/management_app/index.tsx
index 27d3114051c16..53b8f9983aa27 100644
--- a/src/plugins/advanced_settings/public/management_app/index.tsx
+++ b/src/plugins/advanced_settings/public/management_app/index.tsx
@@ -24,7 +24,7 @@ import { i18n } from '@kbn/i18n';
import { I18nProvider } from '@kbn/i18n/react';
import { AdvancedSettings } from './advanced_settings';
import { ManagementSetup } from '../../../management/public';
-import { CoreSetup } from '../../../../core/public';
+import { StartServicesAccessor } from '../../../../core/public';
import { ComponentRegistry } from '../types';
const title = i18n.translate('advancedSettings.advancedSettingsLabel', {
@@ -48,7 +48,7 @@ export async function registerAdvSettingsMgmntApp({
componentRegistry,
}: {
management: ManagementSetup;
- getStartServices: CoreSetup['getStartServices'];
+ getStartServices: StartServicesAccessor;
componentRegistry: ComponentRegistry['start'];
}) {
const kibanaSection = management.sections.getSection('kibana');
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/cat.aliases.json b/src/plugins/console/server/lib/spec_definitions/json/generated/cat.aliases.json
index 2135bd67e57d8..40b0e56782641 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/cat.aliases.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/cat.aliases.json
@@ -6,7 +6,14 @@
"h": [],
"help": "__flag__",
"s": [],
- "v": "__flag__"
+ "v": "__flag__",
+ "expand_wildcards": [
+ "open",
+ "closed",
+ "hidden",
+ "none",
+ "all"
+ ]
},
"methods": [
"GET"
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/cat.indices.json b/src/plugins/console/server/lib/spec_definitions/json/generated/cat.indices.json
index e6ca1fb575396..410350df13721 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/cat.indices.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/cat.indices.json
@@ -36,7 +36,14 @@
"nanos"
],
"v": "__flag__",
- "include_unloaded_segments": "__flag__"
+ "include_unloaded_segments": "__flag__",
+ "expand_wildcards": [
+ "open",
+ "closed",
+ "hidden",
+ "none",
+ "all"
+ ]
},
"methods": [
"GET"
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/cluster.delete_component_template.json b/src/plugins/console/server/lib/spec_definitions/json/generated/cluster.delete_component_template.json
new file mode 100644
index 0000000000000..e935b8999e6d3
--- /dev/null
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/cluster.delete_component_template.json
@@ -0,0 +1,15 @@
+{
+ "cluster.delete_component_template": {
+ "url_params": {
+ "timeout": "",
+ "master_timeout": ""
+ },
+ "methods": [
+ "DELETE"
+ ],
+ "patterns": [
+ "_component_template/{name}"
+ ],
+ "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-component-templates.html"
+ }
+}
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/cluster.health.json b/src/plugins/console/server/lib/spec_definitions/json/generated/cluster.health.json
index 64ede603c0e0d..1758ea44d92c0 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/cluster.health.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/cluster.health.json
@@ -4,6 +4,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/cluster.state.json b/src/plugins/console/server/lib/spec_definitions/json/generated/cluster.state.json
index ba9c8d427e7bd..fb4a02c603174 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/cluster.state.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/cluster.state.json
@@ -11,6 +11,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
]
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/count.json b/src/plugins/console/server/lib/spec_definitions/json/generated/count.json
index bd69fd0c77ec8..67386eb7c6f1b 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/count.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/count.json
@@ -7,6 +7,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/delete_by_query.json b/src/plugins/console/server/lib/spec_definitions/json/generated/delete_by_query.json
index 2d1636d5f2c02..e01ea8b2dec6d 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/delete_by_query.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/delete_by_query.json
@@ -1,6 +1,7 @@
{
"delete_by_query": {
"url_params": {
+ "analyzer": "",
"analyze_wildcard": "__flag__",
"default_operator": [
"AND",
@@ -17,6 +18,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/field_caps.json b/src/plugins/console/server/lib/spec_definitions/json/generated/field_caps.json
index 5e632018bef25..4bf63d7566788 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/field_caps.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/field_caps.json
@@ -7,6 +7,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.clear_cache.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.clear_cache.json
index f5cf05c9a3f7f..fc84d07df88a4 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.clear_cache.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.clear_cache.json
@@ -9,6 +9,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.close.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.close.json
index 676f20632e63b..1b58a27829bc7 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.close.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.close.json
@@ -8,6 +8,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.create.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.create.json
index 8227e38d3c6d9..1970f88b30958 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.create.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.create.json
@@ -1,6 +1,7 @@
{
"indices.create": {
"url_params": {
+ "include_type_name": "__flag__",
"wait_for_active_shards": "",
"timeout": "",
"master_timeout": ""
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.delete.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.delete.json
index b006d5ea7a3cb..084828108123b 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.delete.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.delete.json
@@ -8,6 +8,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
]
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists.json
index 33c845210ea87..09f6c7fd780f8 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists.json
@@ -7,6 +7,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists_alias.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists_alias.json
index d302bbe6b93de..4b93184ed52f1 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists_alias.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists_alias.json
@@ -6,6 +6,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists_type.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists_type.json
index 70d35e6c453c9..0b11356155b50 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists_type.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.exists_type.json
@@ -6,6 +6,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.flush.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.flush.json
index 0ad1a250229b2..63c86d10a9864 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.flush.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.flush.json
@@ -8,6 +8,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
]
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.forcemerge.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.forcemerge.json
index 0e705e2e721ee..b642d5f04a044 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.forcemerge.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.forcemerge.json
@@ -7,6 +7,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get.json
index 7ca9e88274aa5..6df796ed6c4cf 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get.json
@@ -1,12 +1,14 @@
{
"indices.get": {
"url_params": {
+ "include_type_name": "__flag__",
"local": "__flag__",
"ignore_unavailable": "__flag__",
"allow_no_indices": "__flag__",
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_alias.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_alias.json
index d687cab56630f..95bc74edc5865 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_alias.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_alias.json
@@ -6,6 +6,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_field_mapping.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_field_mapping.json
index ea952435566ed..c95e2efc73fab 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_field_mapping.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_field_mapping.json
@@ -1,12 +1,14 @@
{
"indices.get_field_mapping": {
"url_params": {
+ "include_type_name": "__flag__",
"include_defaults": "__flag__",
"ignore_unavailable": "__flag__",
"allow_no_indices": "__flag__",
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_mapping.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_mapping.json
index 73f4e42262bf2..555137d0e2ee0 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_mapping.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_mapping.json
@@ -1,11 +1,13 @@
{
"indices.get_mapping": {
"url_params": {
+ "include_type_name": "__flag__",
"ignore_unavailable": "__flag__",
"allow_no_indices": "__flag__",
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_settings.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_settings.json
index 1c84258d0fce9..a6777f7a820aa 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_settings.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_settings.json
@@ -7,6 +7,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_template.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_template.json
index f5902929c25cc..d5f52ec76b374 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_template.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_template.json
@@ -1,6 +1,7 @@
{
"indices.get_template": {
"url_params": {
+ "include_type_name": "__flag__",
"flat_settings": "__flag__",
"master_timeout": "",
"local": "__flag__"
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_upgrade.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_upgrade.json
index d781172c54d63..99ac958523084 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_upgrade.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.get_upgrade.json
@@ -6,6 +6,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
]
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.open.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.open.json
index b5c4c5501d05d..6369238739203 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.open.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.open.json
@@ -8,6 +8,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_mapping.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_mapping.json
index 07a62a64b64e1..e36783c815e3f 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_mapping.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_mapping.json
@@ -1,6 +1,7 @@
{
"indices.put_mapping": {
"url_params": {
+ "include_type_name": "__flag__",
"timeout": "",
"master_timeout": "",
"ignore_unavailable": "__flag__",
@@ -8,6 +9,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
]
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_settings.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_settings.json
index fe7b938d2f3fc..a2508cd0fc817 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_settings.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_settings.json
@@ -9,6 +9,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_template.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_template.json
index 54a7625a2713c..e6317bd6eb537 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_template.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.put_template.json
@@ -1,11 +1,10 @@
{
"indices.put_template": {
"url_params": {
+ "include_type_name": "__flag__",
"order": "",
"create": "__flag__",
- "timeout": "",
- "master_timeout": "",
- "flat_settings": "__flag__"
+ "master_timeout": ""
},
"methods": [
"PUT",
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.refresh.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.refresh.json
index 54cd2a869902a..2906349d3fdae 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.refresh.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.refresh.json
@@ -6,6 +6,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
]
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.rollover.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.rollover.json
index 19e0f1f909ab8..7fa76a687eb77 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.rollover.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.rollover.json
@@ -1,6 +1,7 @@
{
"indices.rollover": {
"url_params": {
+ "include_type_name": "__flag__",
"timeout": "",
"dry_run": "__flag__",
"master_timeout": "",
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.segments.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.segments.json
index 9e2eb6efce27e..b3c07150699af 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.segments.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.segments.json
@@ -6,6 +6,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.shard_stores.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.shard_stores.json
index f8e026eb89984..c50f4cf501698 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.shard_stores.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.shard_stores.json
@@ -7,6 +7,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
]
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.stats.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.stats.json
index c3fc0f8f7055f..1fa32265c91ee 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.stats.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.stats.json
@@ -16,6 +16,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.upgrade.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.upgrade.json
index 68ee06dd1b0bd..484115bb9b260 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.upgrade.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.upgrade.json
@@ -5,6 +5,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.validate_query.json b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.validate_query.json
index 33720576ef8a3..315aa13d4b4e8 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/indices.validate_query.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/indices.validate_query.json
@@ -7,6 +7,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/msearch_template.json b/src/plugins/console/server/lib/spec_definitions/json/generated/msearch_template.json
index c2f741066bbdb..0b0ca087b1819 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/msearch_template.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/msearch_template.json
@@ -9,7 +9,8 @@
],
"typed_keys": "__flag__",
"max_concurrent_searches": "",
- "rest_total_hits_as_int": "__flag__"
+ "rest_total_hits_as_int": "__flag__",
+ "ccs_minimize_roundtrips": "__flag__"
},
"methods": [
"GET",
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/rank_eval.json b/src/plugins/console/server/lib/spec_definitions/json/generated/rank_eval.json
index c2bed081124a8..4d73e58bd4c06 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/rank_eval.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/rank_eval.json
@@ -6,6 +6,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/search.json b/src/plugins/console/server/lib/spec_definitions/json/generated/search.json
index eb21b43644d77..78b969d3ed8f2 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/search.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/search.json
@@ -19,6 +19,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/search_shards.json b/src/plugins/console/server/lib/spec_definitions/json/generated/search_shards.json
index cbeb0a429352d..b0819f8e066c8 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/search_shards.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/search_shards.json
@@ -9,6 +9,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
]
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/search_template.json b/src/plugins/console/server/lib/spec_definitions/json/generated/search_template.json
index cf5a5c5f32db3..748326522e5c2 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/search_template.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/search_template.json
@@ -7,6 +7,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
@@ -22,7 +23,8 @@
"explain": "__flag__",
"profile": "__flag__",
"typed_keys": "__flag__",
- "rest_total_hits_as_int": "__flag__"
+ "rest_total_hits_as_int": "__flag__",
+ "ccs_minimize_roundtrips": "__flag__"
},
"methods": [
"GET",
diff --git a/src/plugins/console/server/lib/spec_definitions/json/generated/update_by_query.json b/src/plugins/console/server/lib/spec_definitions/json/generated/update_by_query.json
index 393197949e86c..596f8f8b83963 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/generated/update_by_query.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/generated/update_by_query.json
@@ -18,6 +18,7 @@
"expand_wildcards": [
"open",
"closed",
+ "hidden",
"none",
"all"
],
diff --git a/src/plugins/console/server/lib/spec_definitions/json/overrides/cluster.health.json b/src/plugins/console/server/lib/spec_definitions/json/overrides/cluster.health.json
index 7e1655e680b8f..949b897b29ff4 100644
--- a/src/plugins/console/server/lib/spec_definitions/json/overrides/cluster.health.json
+++ b/src/plugins/console/server/lib/spec_definitions/json/overrides/cluster.health.json
@@ -1,11 +1,38 @@
{
"cluster.health": {
"url_params": {
- "master_timeout": "30s",
- "timeout": "30s",
- "wait_for_relocating_shards": 0,
- "wait_for_active_shards": 0,
- "wait_for_nodes": 0
+ "expand_wildcards": [
+ "open",
+ "closed",
+ "hidden",
+ "none",
+ "all"
+ ],
+ "level": [
+ "cluster",
+ "indices",
+ "shards"
+ ],
+ "local": "__flag__",
+ "master_timeout": "",
+ "timeout": "",
+ "wait_for_active_shards": "",
+ "wait_for_nodes": "",
+ "wait_for_events": [
+ "immediate",
+ "urgent",
+ "high",
+ "normal",
+ "low",
+ "languid"
+ ],
+ "wait_for_no_relocating_shards": "__flag__",
+ "wait_for_no_initializing_shards": "__flag__",
+ "wait_for_status": [
+ "green",
+ "yellow",
+ "red"
+ ]
}
}
}
diff --git a/src/plugins/console/server/lib/spec_definitions/json/overrides/indices.get_template.json b/src/plugins/console/server/lib/spec_definitions/json/overrides/indices.get_template.json
deleted file mode 100644
index e0cbcc9cee2ec..0000000000000
--- a/src/plugins/console/server/lib/spec_definitions/json/overrides/indices.get_template.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "indices.get_template": {
- "patterns": [
- "_template",
- "_template/{template}"
- ]
- }
-}
diff --git a/src/plugins/dashboard/kibana.json b/src/plugins/dashboard/kibana.json
index e5a657555819a..e35599a5f0b66 100644
--- a/src/plugins/dashboard/kibana.json
+++ b/src/plugins/dashboard/kibana.json
@@ -5,7 +5,8 @@
"data",
"embeddable",
"inspector",
- "uiActions"
+ "uiActions",
+ "savedObjects"
],
"optionalPlugins": [
"share"
diff --git a/src/legacy/core_plugins/telemetry/public/views/management/index.ts b/src/plugins/dashboard/public/bwc/index.ts
similarity index 96%
rename from src/legacy/core_plugins/telemetry/public/views/management/index.ts
rename to src/plugins/dashboard/public/bwc/index.ts
index 2e9f064ec80d8..d8f7b5091eb8f 100644
--- a/src/legacy/core_plugins/telemetry/public/views/management/index.ts
+++ b/src/plugins/dashboard/public/bwc/index.ts
@@ -17,4 +17,4 @@
* under the License.
*/
-import './management';
+export * from './types';
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/types.ts b/src/plugins/dashboard/public/bwc/types.ts
similarity index 91%
rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/types.ts
rename to src/plugins/dashboard/public/bwc/types.ts
index c264358a8f81f..e9b9d392e9b7d 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/types.ts
+++ b/src/plugins/dashboard/public/bwc/types.ts
@@ -17,8 +17,27 @@
* under the License.
*/
-import { GridData } from '../np_ready/types';
-import { Doc, DocPre700 } from '../../../migrations/types';
+import { SavedObjectReference } from 'kibana/public';
+import { GridData } from '../../../../plugins/dashboard/public';
+
+export interface SavedObjectAttributes {
+ kibanaSavedObjectMeta: {
+ searchSourceJSON: string;
+ };
+}
+
+export interface Doc {
+ references: SavedObjectReference[];
+ attributes: Attributes;
+ id: string;
+ type: string;
+}
+
+export interface DocPre700 {
+ attributes: Attributes;
+ id: string;
+ type: string;
+}
export interface SavedObjectAttributes {
kibanaSavedObjectMeta: {
diff --git a/src/legacy/core_plugins/application_usage/index.ts b/src/plugins/dashboard/public/dashboard_constants.ts
similarity index 66%
rename from src/legacy/core_plugins/application_usage/index.ts
rename to src/plugins/dashboard/public/dashboard_constants.ts
index 752d6eaa19bb0..0820ebd371004 100644
--- a/src/legacy/core_plugins/application_usage/index.ts
+++ b/src/plugins/dashboard/public/dashboard_constants.ts
@@ -17,15 +17,16 @@
* under the License.
*/
-import { Legacy } from '../../../../kibana';
-import { mappings } from './mappings';
+export const DashboardConstants = {
+ ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM: 'addToDashboard',
+ LANDING_PAGE_PATH: '/dashboards',
+ CREATE_NEW_DASHBOARD_URL: '/dashboard',
+ ADD_EMBEDDABLE_ID: 'addEmbeddableId',
+ ADD_EMBEDDABLE_TYPE: 'addEmbeddableType',
+ DASHBOARDS_ID: 'dashboards',
+ DASHBOARD_ID: 'dashboard',
+};
-// eslint-disable-next-line import/no-default-export
-export default function ApplicationUsagePlugin(kibana: any) {
- const config: Legacy.PluginSpecOptions = {
- id: 'application_usage',
- uiExports: { mappings }, // Needed to define the mappings for the SavedObjects
- };
-
- return new kibana.Plugin(config);
+export function createDashboardEditUrl(id: string) {
+ return `/dashboard/${id}`;
}
diff --git a/src/plugins/dashboard/public/embeddable/index.ts b/src/plugins/dashboard/public/embeddable/index.ts
index 58bfd5eedefcb..fcc5fe5202bd2 100644
--- a/src/plugins/dashboard/public/embeddable/index.ts
+++ b/src/plugins/dashboard/public/embeddable/index.ts
@@ -21,7 +21,7 @@ export { DashboardContainerFactory } from './dashboard_container_factory';
export { DashboardContainer, DashboardContainerInput } from './dashboard_container';
export { createPanelState } from './panel';
-export { DashboardPanelState, GridData } from './types';
+export * from './types';
export {
DASHBOARD_GRID_COLUMN_COUNT,
diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts
index c6846346b64ef..070e437ce52ef 100644
--- a/src/plugins/dashboard/public/index.ts
+++ b/src/plugins/dashboard/public/index.ts
@@ -22,14 +22,43 @@ import './index.scss';
import { PluginInitializerContext } from '../../../core/public';
import { DashboardEmbeddableContainerPublicPlugin } from './plugin';
-export * from './types';
-export * from './actions';
-export * from './embeddable';
+/**
+ * These types can probably be internal once all of dashboard app is migrated into this plugin. Right
+ * now, migrations are still in legacy land.
+ */
+export {
+ DashboardDoc730ToLatest,
+ DashboardDoc700To720,
+ RawSavedDashboardPanelTo60,
+ RawSavedDashboardPanel610,
+ RawSavedDashboardPanel620,
+ RawSavedDashboardPanel630,
+ RawSavedDashboardPanel640To720,
+ RawSavedDashboardPanel730ToLatest,
+ DashboardDocPre700,
+} from './bwc';
-export function plugin(initializerContext: PluginInitializerContext) {
- return new DashboardEmbeddableContainerPublicPlugin(initializerContext);
-}
+export {} from './types';
+export {} from './actions';
+export {
+ DashboardContainer,
+ DashboardContainerInput,
+ DashboardContainerFactory,
+ DASHBOARD_CONTAINER_TYPE,
+ DashboardPanelState,
+ // Types below here can likely be made private when dashboard app moved into this NP plugin.
+ DEFAULT_PANEL_WIDTH,
+ DEFAULT_PANEL_HEIGHT,
+ GridData,
+} from './embeddable';
+
+export { SavedObjectDashboard } from './saved_dashboards';
+export { DashboardStart } from './plugin';
export { DashboardEmbeddableContainerPublicPlugin as Plugin };
export { DASHBOARD_APP_URL_GENERATOR } from './url_generator';
+
+export function plugin(initializerContext: PluginInitializerContext) {
+ return new DashboardEmbeddableContainerPublicPlugin(initializerContext);
+}
diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx
index d663c736e5aed..3d67435e6d8f7 100644
--- a/src/plugins/dashboard/public/plugin.tsx
+++ b/src/plugins/dashboard/public/plugin.tsx
@@ -21,13 +21,18 @@
import * as React from 'react';
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public';
-import { SharePluginSetup } from 'src/plugins/share/public';
+import {
+ CONTEXT_MENU_TRIGGER,
+ EmbeddableSetup,
+ EmbeddableStart,
+} from '../../../plugins/embeddable/public';
+import { DataPublicPluginStart } from '../../../plugins/data/public';
+import { SharePluginSetup } from '../../../plugins/share/public';
import { UiActionsSetup, UiActionsStart } from '../../../plugins/ui_actions/public';
-import { CONTEXT_MENU_TRIGGER, EmbeddableSetup, EmbeddableStart } from './embeddable_plugin';
-import { ExpandPanelAction, ReplacePanelAction } from '.';
+import { ExpandPanelAction, ReplacePanelAction } from './actions';
import { DashboardContainerFactory } from './embeddable/dashboard_container_factory';
import { Start as InspectorStartContract } from '../../../plugins/inspector/public';
-import { getSavedObjectFinder } from '../../../plugins/saved_objects/public';
+import { getSavedObjectFinder, SavedObjectLoader } from '../../../plugins/saved_objects/public';
import {
ExitFullScreenButton as ExitFullScreenButtonUi,
ExitFullScreenButtonProps,
@@ -39,6 +44,7 @@ import {
DASHBOARD_APP_URL_GENERATOR,
createDirectAccessDashboardLinkGenerator,
} from './url_generator';
+import { createSavedDashboardLoader } from './saved_dashboards';
declare module '../../share/public' {
export interface UrlGeneratorStateMapping {
@@ -56,10 +62,13 @@ interface StartDependencies {
embeddable: EmbeddableStart;
inspector: InspectorStartContract;
uiActions: UiActionsStart;
+ data: DataPublicPluginStart;
}
export type Setup = void;
-export type Start = void;
+export interface DashboardStart {
+ getSavedDashboardLoader: () => SavedObjectLoader;
+}
declare module '../../../plugins/ui_actions/public' {
export interface ActionContextMapping {
@@ -69,7 +78,7 @@ declare module '../../../plugins/ui_actions/public' {
}
export class DashboardEmbeddableContainerPublicPlugin
- implements Plugin {
+ implements Plugin {
constructor(initializerContext: PluginInitializerContext) {}
public setup(
@@ -121,9 +130,12 @@ export class DashboardEmbeddableContainerPublicPlugin
embeddable.registerEmbeddableFactory(factory.type, factory);
}
- public start(core: CoreStart, plugins: StartDependencies): Start {
+ public start(core: CoreStart, plugins: StartDependencies): DashboardStart {
const { notifications } = core;
- const { uiActions } = plugins;
+ const {
+ uiActions,
+ data: { indexPatterns },
+ } = plugins;
const SavedObjectFinder = getSavedObjectFinder(core.savedObjects, core.uiSettings);
@@ -135,6 +147,15 @@ export class DashboardEmbeddableContainerPublicPlugin
);
uiActions.registerAction(changeViewAction);
uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, changeViewAction);
+ const savedDashboardLoader = createSavedDashboardLoader({
+ savedObjectsClient: core.savedObjects.client,
+ indexPatterns,
+ chrome: core.chrome,
+ overlays: core.overlays,
+ });
+ return {
+ getSavedDashboardLoader: () => savedDashboardLoader,
+ };
}
public stop() {}
diff --git a/src/plugins/dashboard/public/saved_dashboards/index.ts b/src/plugins/dashboard/public/saved_dashboards/index.ts
new file mode 100644
index 0000000000000..9b7745bd884f7
--- /dev/null
+++ b/src/plugins/dashboard/public/saved_dashboards/index.ts
@@ -0,0 +1,21 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+export * from './saved_dashboard_references';
+export * from './saved_dashboard';
+export * from './saved_dashboards';
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.ts b/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts
similarity index 86%
rename from src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.ts
rename to src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts
index c5ac05b5a77eb..c4ebf4f07a5db 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.ts
+++ b/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts
@@ -20,16 +20,11 @@ import {
createSavedObjectClass,
SavedObject,
SavedObjectKibanaServices,
-} from '../../../../../../plugins/saved_objects/public';
+} from '../../../../plugins/saved_objects/public';
import { extractReferences, injectReferences } from './saved_dashboard_references';
-import {
- Filter,
- ISearchSource,
- Query,
- RefreshInterval,
-} from '../../../../../../plugins/data/public';
-import { createDashboardEditUrl } from '..';
+import { Filter, ISearchSource, Query, RefreshInterval } from '../../../../plugins/data/public';
+import { createDashboardEditUrl } from '../dashboard_constants';
export interface SavedObjectDashboard extends SavedObject {
id?: string;
@@ -49,7 +44,9 @@ export interface SavedObjectDashboard extends SavedObject {
}
// Used only by the savedDashboards service, usually no reason to change this
-export function createSavedDashboardClass(services: SavedObjectKibanaServices) {
+export function createSavedDashboardClass(
+ services: SavedObjectKibanaServices
+): new (id: string) => SavedObjectDashboard {
const SavedObjectClass = createSavedObjectClass(services);
class SavedDashboard extends SavedObjectClass {
// save these objects with the 'dashboard' type
@@ -121,5 +118,7 @@ export function createSavedDashboardClass(services: SavedObjectKibanaServices) {
}
}
- return SavedDashboard;
+ // Unfortunately this throws a typescript error without the casting. I think it's due to the
+ // convoluted way SavedObjects are created.
+ return (SavedDashboard as unknown) as new (id: string) => SavedObjectDashboard;
}
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard_references.test.ts b/src/plugins/dashboard/public/saved_dashboards/saved_dashboard_references.test.ts
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard_references.test.ts
rename to src/plugins/dashboard/public/saved_dashboards/saved_dashboard_references.test.ts
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard_references.ts b/src/plugins/dashboard/public/saved_dashboards/saved_dashboard_references.ts
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard_references.ts
rename to src/plugins/dashboard/public/saved_dashboards/saved_dashboard_references.ts
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboards.ts b/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts
similarity index 67%
rename from src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboards.ts
rename to src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts
index 2ff76da9c5ca6..2a1e64fa88a02 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboards.ts
+++ b/src/plugins/dashboard/public/saved_dashboards/saved_dashboards.ts
@@ -17,13 +17,22 @@
* under the License.
*/
-import {
- SavedObjectLoader,
- SavedObjectKibanaServices,
-} from '../../../../../../plugins/saved_objects/public';
+import { SavedObjectsClientContract, ChromeStart, OverlayStart } from 'kibana/public';
+import { IndexPatternsContract } from '../../../../plugins/data/public';
+import { SavedObjectLoader } from '../../../../plugins/saved_objects/public';
import { createSavedDashboardClass } from './saved_dashboard';
-export function createSavedDashboardLoader(services: SavedObjectKibanaServices) {
+interface Services {
+ savedObjectsClient: SavedObjectsClientContract;
+ indexPatterns: IndexPatternsContract;
+ chrome: ChromeStart;
+ overlays: OverlayStart;
+}
+
+/**
+ * @param services
+ */
+export function createSavedDashboardLoader(services: Services) {
const SavedDashboard = createSavedDashboardClass(services);
return new SavedObjectLoader(SavedDashboard, services.savedObjectsClient, services.chrome);
}
diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts
index b27f899e52523..cb6190e81a778 100644
--- a/src/plugins/data/public/index.ts
+++ b/src/plugins/data/public/index.ts
@@ -376,6 +376,8 @@ export {
TabbedAggColumn,
TabbedAggRow,
TabbedTable,
+ SearchInterceptor,
+ RequestTimeoutError,
} from './search';
// Search namespace
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index c4c91df634d24..e95765b37e6fb 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -7,6 +7,7 @@
import { $Values } from '@kbn/utility-types';
import _ from 'lodash';
import { Action } from 'history';
+import { ApplicationStart } from 'kibana/public';
import { Assign } from '@kbn/utility-types';
import { Breadcrumb } from '@elastic/eui';
import { Component } from 'react';
@@ -49,6 +50,9 @@ import { SavedObjectsClientContract } from 'src/core/public';
import { SearchParams } from 'elasticsearch';
import { SearchResponse as SearchResponse_2 } from 'elasticsearch';
import { SimpleSavedObject } from 'src/core/public';
+import { Subscription } from 'rxjs';
+import { Toast } from 'kibana/public';
+import { ToastsStart } from 'kibana/public';
import { UiActionsSetup } from 'src/plugins/ui_actions/public';
import { UiActionsStart } from 'src/plugins/ui_actions/public';
import { Unit } from '@elastic/datemath';
@@ -1469,6 +1473,13 @@ export interface RefreshInterval {
value: number;
}
+// Warning: (ae-missing-release-tag) "RequestTimeoutError" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public
+export class RequestTimeoutError extends Error {
+ constructor(message?: string);
+}
+
// Warning: (ae-missing-release-tag) "SavedQuery" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -1586,6 +1597,28 @@ export class SearchError extends Error {
type: string;
}
+// Warning: (ae-missing-release-tag) "SearchInterceptor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export class SearchInterceptor {
+ constructor(toasts: ToastsStart, application: ApplicationStart, requestTimeout?: number | undefined);
+ protected abortController: AbortController;
+ // (undocumented)
+ protected readonly application: ApplicationStart;
+ getPendingCount$: () => import("rxjs").Observable;
+ // (undocumented)
+ protected hideToast: () => void;
+ protected longRunningToast?: Toast;
+ // (undocumented)
+ protected readonly requestTimeout?: number | undefined;
+ search: (search: ISearchGeneric, request: IKibanaSearchRequest, options?: ISearchOptions | undefined) => import("rxjs").Observable>;
+ // (undocumented)
+ protected showToast: () => void;
+ protected timeoutSubscriptions: Set;
+ // (undocumented)
+ protected readonly toasts: ToastsStart;
+}
+
// Warning: (ae-missing-release-tag) "SearchRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -1853,21 +1886,21 @@ export type TSearchStrategyProvider = (context: ISearc
// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "getRoutes" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:387:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:404:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:408:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:384:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:384:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:384:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:384:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:33:33 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:37:1 - (ae-forgotten-export) The symbol "QueryStateChange" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:56:5 - (ae-forgotten-export) The symbol "createFiltersFromEvent" needs to be exported by the entry point index.d.ts
diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts
index f3d2d99af5998..1687d749f46e2 100644
--- a/src/plugins/data/public/search/index.ts
+++ b/src/plugins/data/public/search/index.ts
@@ -57,5 +57,6 @@ export {
} from './search_source';
export { SearchInterceptor } from './search_interceptor';
+export { RequestTimeoutError } from './request_timeout_error';
export { FetchOptions } from './fetch';
diff --git a/src/plugins/data/public/search/long_query_notification.tsx b/src/plugins/data/public/search/long_query_notification.tsx
new file mode 100644
index 0000000000000..590fee20db690
--- /dev/null
+++ b/src/plugins/data/public/search/long_query_notification.tsx
@@ -0,0 +1,61 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import React from 'react';
+import { ApplicationStart } from 'kibana/public';
+import { toMountPoint } from '../../../kibana_react/public';
+
+interface Props {
+ application: ApplicationStart;
+}
+
+export function getLongQueryNotification(props: Props) {
+ return toMountPoint();
+}
+
+export function LongQueryNotification(props: Props) {
+ return (
+
+
+
+
+
+ {
+ await props.application.navigateToApp(
+ 'kibana#/management/elasticsearch/license_management'
+ );
+ }}
+ >
+
+
+
+
+
+ );
+}
diff --git a/src/plugins/data/public/search/mocks.ts b/src/plugins/data/public/search/mocks.ts
index 12cf258759a99..b70e889066a45 100644
--- a/src/plugins/data/public/search/mocks.ts
+++ b/src/plugins/data/public/search/mocks.ts
@@ -31,10 +31,8 @@ export const searchSetupMock = {
export const searchStartMock: jest.Mocked = {
aggs: searchAggsStartMock(),
+ setInterceptor: jest.fn(),
search: jest.fn(),
- cancel: jest.fn(),
- getPendingCount$: jest.fn(),
- runBeyondTimeout: jest.fn(),
__LEGACY: {
AggConfig: jest.fn() as any,
AggType: jest.fn(),
diff --git a/src/plugins/data/public/search/search_interceptor.test.ts b/src/plugins/data/public/search/search_interceptor.test.ts
index a89d17464b9e0..bd056271688c1 100644
--- a/src/plugins/data/public/search/search_interceptor.test.ts
+++ b/src/plugins/data/public/search/search_interceptor.test.ts
@@ -18,27 +18,38 @@
*/
import { Observable, Subject } from 'rxjs';
+import { CoreStart } from '../../../../core/public';
+import { coreMock } from '../../../../core/public/mocks';
import { IKibanaSearchRequest } from '../../common/search';
import { RequestTimeoutError } from './request_timeout_error';
import { SearchInterceptor } from './search_interceptor';
jest.useFakeTimers();
-const flushPromises = () => new Promise(resolve => setImmediate(resolve));
const mockSearch = jest.fn();
let searchInterceptor: SearchInterceptor;
+let mockCoreStart: MockedKeys;
describe('SearchInterceptor', () => {
beforeEach(() => {
+ mockCoreStart = coreMock.createStart();
mockSearch.mockClear();
- searchInterceptor = new SearchInterceptor(1000);
+ searchInterceptor = new SearchInterceptor(
+ mockCoreStart.notifications.toasts,
+ mockCoreStart.application,
+ 1000
+ );
});
describe('search', () => {
test('should invoke `search` with the request', () => {
- mockSearch.mockReturnValue(new Observable());
+ const mockResponse = new Subject();
+ mockSearch.mockReturnValue(mockResponse.asObservable());
const mockRequest: IKibanaSearchRequest = {};
- searchInterceptor.search(mockSearch, mockRequest);
+ const response = searchInterceptor.search(mockSearch, mockRequest);
+ mockResponse.complete();
+
+ response.subscribe();
expect(mockSearch.mock.calls[0][0]).toBe(mockRequest);
});
@@ -92,44 +103,6 @@ describe('SearchInterceptor', () => {
});
});
- describe('cancelPending', () => {
- test('should abort all pending requests', async () => {
- mockSearch.mockReturnValue(new Observable());
-
- searchInterceptor.search(mockSearch, {});
- searchInterceptor.search(mockSearch, {});
- searchInterceptor.cancelPending();
-
- await flushPromises();
-
- const areAllRequestsAborted = mockSearch.mock.calls.every(([, { signal }]) => signal.aborted);
- expect(areAllRequestsAborted).toBe(true);
- });
- });
-
- describe('runBeyondTimeout', () => {
- test('should prevent the request from timing out', () => {
- const mockResponse = new Subject();
- mockSearch.mockReturnValue(mockResponse.asObservable());
- const response = searchInterceptor.search(mockSearch, {});
-
- setTimeout(searchInterceptor.runBeyondTimeout, 500);
- setTimeout(() => mockResponse.next('hi'), 250);
- setTimeout(() => mockResponse.complete(), 2000);
-
- const next = jest.fn();
- const complete = jest.fn();
- const error = jest.fn();
- response.subscribe({ next, error, complete });
-
- jest.advanceTimersByTime(2000);
-
- expect(next).toHaveBeenCalledWith('hi');
- expect(error).not.toHaveBeenCalled();
- expect(complete).toHaveBeenCalled();
- });
- });
-
describe('getPendingCount$', () => {
test('should observe the number of pending requests', () => {
let i = 0;
diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts
index 3f83214f6050c..d83ddab807bc5 100644
--- a/src/plugins/data/public/search/search_interceptor.ts
+++ b/src/plugins/data/public/search/search_interceptor.ts
@@ -17,51 +17,59 @@
* under the License.
*/
-import { BehaviorSubject, fromEvent, throwError } from 'rxjs';
-import { mergeMap, takeUntil, finalize } from 'rxjs/operators';
+import { BehaviorSubject, throwError, timer, Subscription, defer, fromEvent } from 'rxjs';
+import { takeUntil, finalize, filter, mergeMapTo } from 'rxjs/operators';
+import { ApplicationStart, Toast, ToastsStart } from 'kibana/public';
import { getCombinedSignal } from '../../common/utils';
import { IKibanaSearchRequest } from '../../common/search';
import { ISearchGeneric, ISearchOptions } from './i_search';
import { RequestTimeoutError } from './request_timeout_error';
+import { getLongQueryNotification } from './long_query_notification';
export class SearchInterceptor {
/**
* `abortController` used to signal all searches to abort.
*/
- private abortController = new AbortController();
+ protected abortController = new AbortController();
/**
- * Observable that emits when the number of pending requests changes.
+ * The number of pending search requests.
*/
- private pendingCount$ = new BehaviorSubject(0);
+ private pendingCount = 0;
/**
- * The IDs from `setTimeout` when scheduling the automatic timeout for each request.
+ * Observable that emits when the number of pending requests changes.
*/
- private timeoutIds: Set = new Set();
+ private pendingCount$ = new BehaviorSubject(this.pendingCount);
/**
- * This class should be instantiated with a `requestTimeout` corresponding with how many ms after
- * requests are initiated that they should automatically cancel.
- * @param requestTimeout Usually config value `elasticsearch.requestTimeout`
+ * The subscriptions from scheduling the automatic timeout for each request.
*/
- constructor(private readonly requestTimeout?: number) {}
+ protected timeoutSubscriptions: Set = new Set();
/**
- * Abort our `AbortController`, which in turn aborts any intercepted searches.
+ * The current long-running toast (if there is one).
*/
- public cancelPending = () => {
- this.abortController.abort();
- this.abortController = new AbortController();
- };
+ protected longRunningToast?: Toast;
/**
- * Un-schedule timing out all of the searches intercepted.
+ * This class should be instantiated with a `requestTimeout` corresponding with how many ms after
+ * requests are initiated that they should automatically cancel.
+ * @param toasts The `core.notifications.toasts` service
+ * @param application The `core.application` service
+ * @param requestTimeout Usually config value `elasticsearch.requestTimeout`
*/
- public runBeyondTimeout = () => {
- this.timeoutIds.forEach(clearTimeout);
- this.timeoutIds.clear();
- };
+ constructor(
+ protected readonly toasts: ToastsStart,
+ protected readonly application: ApplicationStart,
+ protected readonly requestTimeout?: number
+ ) {
+ // When search requests go out, a notification is scheduled allowing users to continue the
+ // request past the timeout. When all search requests complete, we remove the notification.
+ this.getPendingCount$()
+ .pipe(filter(count => count === 0))
+ .subscribe(this.hideToast);
+ }
/**
* Returns an `Observable` over the current number of pending searches. This could mean that one
@@ -81,41 +89,66 @@ export class SearchInterceptor {
request: IKibanaSearchRequest,
options?: ISearchOptions
) => {
- // Schedule this request to automatically timeout after some interval
- const timeoutController = new AbortController();
- const { signal: timeoutSignal } = timeoutController;
- const timeoutId = window.setTimeout(() => {
- timeoutController.abort();
- }, this.requestTimeout);
- this.addTimeoutId(timeoutId);
-
- // Get a combined `AbortSignal` that will be aborted whenever the first of the following occurs:
- // 1. The user manually aborts (via `cancelPending`)
- // 2. The request times out
- // 3. The passed-in signal aborts (e.g. when re-fetching, or whenever the app determines)
- const signals = [this.abortController.signal, timeoutSignal, options?.signal].filter(
- Boolean
- ) as AbortSignal[];
- const combinedSignal = getCombinedSignal(signals);
-
- // If the request timed out, throw a `RequestTimeoutError`
- const timeoutError$ = fromEvent(timeoutSignal, 'abort').pipe(
- mergeMap(() => throwError(new RequestTimeoutError()))
- );
+ // Defer the following logic until `subscribe` is actually called
+ return defer(() => {
+ this.pendingCount$.next(++this.pendingCount);
- return search(request as any, { ...options, signal: combinedSignal }).pipe(
- takeUntil(timeoutError$),
- finalize(() => this.removeTimeoutId(timeoutId))
- );
+ // Schedule this request to automatically timeout after some interval
+ const timeoutController = new AbortController();
+ const { signal: timeoutSignal } = timeoutController;
+ const timeout$ = timer(this.requestTimeout);
+ const subscription = timeout$.subscribe(() => timeoutController.abort());
+ this.timeoutSubscriptions.add(subscription);
+
+ // If the request timed out, throw a `RequestTimeoutError`
+ const timeoutError$ = fromEvent(timeoutSignal, 'abort').pipe(
+ mergeMapTo(throwError(new RequestTimeoutError()))
+ );
+
+ // Schedule the notification to allow users to cancel or wait beyond the timeout
+ const notificationSubscription = timer(10000).subscribe(this.showToast);
+
+ // Get a combined `AbortSignal` that will be aborted whenever the first of the following occurs:
+ // 1. The user manually aborts (via `cancelPending`)
+ // 2. The request times out
+ // 3. The passed-in signal aborts (e.g. when re-fetching, or whenever the app determines)
+ const signals = [
+ this.abortController.signal,
+ timeoutSignal,
+ ...(options?.signal ? [options.signal] : []),
+ ];
+ const combinedSignal = getCombinedSignal(signals);
+
+ return search(request as any, { ...options, signal: combinedSignal }).pipe(
+ takeUntil(timeoutError$),
+ finalize(() => {
+ this.pendingCount$.next(--this.pendingCount);
+ this.timeoutSubscriptions.delete(subscription);
+ notificationSubscription.unsubscribe();
+ })
+ );
+ });
};
- private addTimeoutId(id: number) {
- this.timeoutIds.add(id);
- this.pendingCount$.next(this.timeoutIds.size);
- }
+ protected showToast = () => {
+ if (this.longRunningToast) return;
+ this.longRunningToast = this.toasts.addInfo(
+ {
+ title: 'Your query is taking awhile',
+ text: getLongQueryNotification({
+ application: this.application,
+ }),
+ },
+ {
+ toastLifeTimeMs: Infinity,
+ }
+ );
+ };
- private removeTimeoutId(id: number) {
- this.timeoutIds.delete(id);
- this.pendingCount$.next(this.timeoutIds.size);
- }
+ protected hideToast = () => {
+ if (this.longRunningToast) {
+ this.toasts.remove(this.longRunningToast);
+ delete this.longRunningToast;
+ }
+ };
}
diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts
index 62c7e0468bb88..311a8a2fc6f60 100644
--- a/src/plugins/data/public/search/search_service.ts
+++ b/src/plugins/data/public/search/search_service.ts
@@ -58,6 +58,7 @@ export class SearchService implements Plugin {
private esClient?: LegacyApiCaller;
private readonly aggTypesRegistry = new AggTypesRegistry();
+ private searchInterceptor!: SearchInterceptor;
private registerSearchStrategyProvider = (
name: T,
@@ -98,7 +99,9 @@ export class SearchService implements Plugin {
* TODO: Make this modular so that apps can opt in/out of search collection, or even provide
* their own search collector instances
*/
- const searchInterceptor = new SearchInterceptor(
+ this.searchInterceptor = new SearchInterceptor(
+ core.notifications.toasts,
+ core.application,
core.injectedMetadata.getInjectedVar('esRequestTimeout') as number
);
@@ -114,16 +117,17 @@ export class SearchService implements Plugin {
},
types: aggTypesStart,
},
- cancel: () => searchInterceptor.cancelPending(),
- getPendingCount$: () => searchInterceptor.getPendingCount$(),
- runBeyondTimeout: () => searchInterceptor.runBeyondTimeout(),
search: (request, options, strategyName) => {
const strategyProvider = this.getSearchStrategy(strategyName || DEFAULT_SEARCH_STRATEGY);
const { search } = strategyProvider({
core,
getSearchStrategy: this.getSearchStrategy,
});
- return searchInterceptor.search(search as any, request, options);
+ return this.searchInterceptor.search(search as any, request, options);
+ },
+ setInterceptor: (searchInterceptor: SearchInterceptor) => {
+ // TODO: should an intercepror have a destroy method?
+ this.searchInterceptor = searchInterceptor;
},
__LEGACY: {
esClient: this.esClient!,
diff --git a/src/plugins/data/public/search/types.ts b/src/plugins/data/public/search/types.ts
index 1b551f978b971..03cbfa9f8ed84 100644
--- a/src/plugins/data/public/search/types.ts
+++ b/src/plugins/data/public/search/types.ts
@@ -17,12 +17,12 @@
* under the License.
*/
-import { Observable } from 'rxjs';
import { CoreStart } from 'kibana/public';
import { SearchAggsSetup, SearchAggsStart, SearchAggsStartLegacy } from './aggs';
import { ISearch, ISearchGeneric } from './i_search';
import { TStrategyTypes } from './strategy_types';
import { LegacyApiCaller } from './es_client';
+import { SearchInterceptor } from './search_interceptor';
export interface ISearchContext {
core: CoreStart;
@@ -87,9 +87,7 @@ export interface ISearchSetup {
export interface ISearchStart {
aggs: SearchAggsStart;
- cancel: () => void;
- getPendingCount$: () => Observable;
- runBeyondTimeout: () => void;
+ setInterceptor: (searchInterceptor: SearchInterceptor) => void;
search: ISearchGeneric;
__LEGACY: ISearchStartLegacy & SearchAggsStartLegacy;
}
diff --git a/src/plugins/data/server/kql_telemetry/route.ts b/src/plugins/data/server/kql_telemetry/route.ts
index d5725c859c9a9..dd7ff333e6257 100644
--- a/src/plugins/data/server/kql_telemetry/route.ts
+++ b/src/plugins/data/server/kql_telemetry/route.ts
@@ -17,12 +17,12 @@
* under the License.
*/
-import { CoreSetup, IRouter, Logger } from 'kibana/server';
+import { StartServicesAccessor, IRouter, Logger } from 'kibana/server';
import { schema } from '@kbn/config-schema';
export function registerKqlTelemetryRoute(
router: IRouter,
- getStartServices: CoreSetup['getStartServices'],
+ getStartServices: StartServicesAccessor,
logger: Logger
) {
router.post(
diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md
index 5c231cdc05e61..1abc74fe07ccc 100644
--- a/src/plugins/data/server/server.api.md
+++ b/src/plugins/data/server/server.api.md
@@ -7,7 +7,6 @@
import { APICaller as APICaller_2 } from 'kibana/server';
import Boom from 'boom';
import { BulkIndexDocumentsParams } from 'elasticsearch';
-import { CallCluster as CallCluster_2 } from 'src/legacy/core_plugins/elasticsearch';
import { CatAliasesParams } from 'elasticsearch';
import { CatAllocationParams } from 'elasticsearch';
import { CatCommonParams } from 'elasticsearch';
diff --git a/src/plugins/dev_tools/public/plugin.ts b/src/plugins/dev_tools/public/plugin.ts
index 9ebfeb5387b26..df61271baf879 100644
--- a/src/plugins/dev_tools/public/plugin.ts
+++ b/src/plugins/dev_tools/public/plugin.ts
@@ -132,4 +132,6 @@ export class DevToolsPlugin implements Plugin {
getSortedDevTools: this.getSortedDevTools.bind(this),
};
}
+
+ public stop() {}
}
diff --git a/src/plugins/discover/kibana.json b/src/plugins/discover/kibana.json
new file mode 100644
index 0000000000000..91d6358d44c18
--- /dev/null
+++ b/src/plugins/discover/kibana.json
@@ -0,0 +1,6 @@
+{
+ "id": "discover",
+ "version": "kibana",
+ "server": false,
+ "ui": true
+}
diff --git a/src/plugins/discover/public/components/_index.scss b/src/plugins/discover/public/components/_index.scss
new file mode 100644
index 0000000000000..ff50d4b5dca93
--- /dev/null
+++ b/src/plugins/discover/public/components/_index.scss
@@ -0,0 +1 @@
+@import 'doc_viewer/index';
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap b/src/plugins/discover/public/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap
rename to src/plugins/discover/public/components/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap b/src/plugins/discover/public/components/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap
rename to src/plugins/discover/public/components/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/_doc_viewer.scss b/src/plugins/discover/public/components/doc_viewer/_doc_viewer.scss
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/_doc_viewer.scss
rename to src/plugins/discover/public/components/doc_viewer/_doc_viewer.scss
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/_index.scss b/src/plugins/discover/public/components/doc_viewer/_index.scss
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/_index.scss
rename to src/plugins/discover/public/components/doc_viewer/_index.scss
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer.test.tsx b/src/plugins/discover/public/components/doc_viewer/doc_viewer.test.tsx
similarity index 81%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer.test.tsx
rename to src/plugins/discover/public/components/doc_viewer/doc_viewer.test.tsx
index 15f0f40700abc..6f29f10ddd026 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer.test.tsx
+++ b/src/plugins/discover/public/components/doc_viewer/doc_viewer.test.tsx
@@ -21,37 +21,33 @@ import { mount, shallow } from 'enzyme';
import { DocViewer } from './doc_viewer';
// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
-import { getServices } from '../../../kibana_services';
+import { getDocViewsRegistry } from '../../services';
import { DocViewRenderProps } from '../../doc_views/doc_views_types';
-jest.mock('../../../kibana_services', () => {
+jest.mock('../../services', () => {
let registry: any[] = [];
return {
- getServices: () => ({
- docViewsRegistry: {
- addDocView(view: any) {
- registry.push(view);
- },
- getDocViewsSorted() {
- return registry;
- },
+ getDocViewsRegistry: () => ({
+ addDocView(view: any) {
+ registry.push(view);
+ },
+ getDocViewsSorted() {
+ return registry;
},
resetRegistry: () => {
registry = [];
},
}),
- formatMsg: (x: any) => String(x),
- formatStack: (x: any) => String(x),
};
});
beforeEach(() => {
- (getServices() as any).resetRegistry();
+ (getDocViewsRegistry() as any).resetRegistry();
jest.clearAllMocks();
});
test('Render with 3 different tabs', () => {
- const registry = getServices().docViewsRegistry;
+ const registry = getDocViewsRegistry();
registry.addDocView({ order: 10, title: 'Render function', render: jest.fn() });
registry.addDocView({ order: 20, title: 'React component', component: () => test
});
registry.addDocView({ order: 30, title: 'Invalid doc view' });
@@ -69,7 +65,7 @@ test('Render with 1 tab displaying error message', () => {
return null;
}
- const registry = getServices().docViewsRegistry;
+ const registry = getDocViewsRegistry();
registry.addDocView({
order: 10,
title: 'React component',
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer.tsx b/src/plugins/discover/public/components/doc_viewer/doc_viewer.tsx
similarity index 95%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer.tsx
rename to src/plugins/discover/public/components/doc_viewer/doc_viewer.tsx
index a177d8c29304c..792d9c44400d7 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer.tsx
+++ b/src/plugins/discover/public/components/doc_viewer/doc_viewer.tsx
@@ -18,7 +18,7 @@
*/
import React from 'react';
import { EuiTabbedContent } from '@elastic/eui';
-import { getServices } from '../../../kibana_services';
+import { getDocViewsRegistry } from '../../services';
import { DocViewerTab } from './doc_viewer_tab';
import { DocView, DocViewRenderProps } from '../../doc_views/doc_views_types';
@@ -29,7 +29,7 @@ import { DocView, DocViewRenderProps } from '../../doc_views/doc_views_types';
* a `render` function.
*/
export function DocViewer(renderProps: DocViewRenderProps) {
- const { docViewsRegistry } = getServices();
+ const docViewsRegistry = getDocViewsRegistry();
const tabs = docViewsRegistry
.getDocViewsSorted(renderProps.hit)
.map(({ title, render, component }: DocView, idx: number) => {
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_render_error.tsx b/src/plugins/discover/public/components/doc_viewer/doc_viewer_render_error.tsx
similarity index 94%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_render_error.tsx
rename to src/plugins/discover/public/components/doc_viewer/doc_viewer_render_error.tsx
index 075217add7b52..387e57dc8a7e3 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_render_error.tsx
+++ b/src/plugins/discover/public/components/doc_viewer/doc_viewer_render_error.tsx
@@ -18,7 +18,7 @@
*/
import React from 'react';
import { EuiCallOut, EuiCodeBlock } from '@elastic/eui';
-import { formatMsg, formatStack } from '../../../kibana_services';
+import { formatMsg, formatStack } from '../../../../kibana_legacy/public';
interface Props {
error: Error | string;
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_render_tab.test.tsx b/src/plugins/discover/public/components/doc_viewer/doc_viewer_render_tab.test.tsx
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_render_tab.test.tsx
rename to src/plugins/discover/public/components/doc_viewer/doc_viewer_render_tab.test.tsx
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_render_tab.tsx b/src/plugins/discover/public/components/doc_viewer/doc_viewer_render_tab.tsx
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_render_tab.tsx
rename to src/plugins/discover/public/components/doc_viewer/doc_viewer_render_tab.tsx
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_tab.tsx b/src/plugins/discover/public/components/doc_viewer/doc_viewer_tab.tsx
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc_viewer/doc_viewer_tab.tsx
rename to src/plugins/discover/public/components/doc_viewer/doc_viewer_tab.tsx
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/__snapshots__/field_name.test.tsx.snap b/src/plugins/discover/public/components/field_name/__snapshots__/field_name.test.tsx.snap
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/__snapshots__/field_name.test.tsx.snap
rename to src/plugins/discover/public/components/field_name/__snapshots__/field_name.test.tsx.snap
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_name.test.tsx b/src/plugins/discover/public/components/field_name/field_name.test.tsx
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_name.test.tsx
rename to src/plugins/discover/public/components/field_name/field_name.test.tsx
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_name.tsx b/src/plugins/discover/public/components/field_name/field_name.tsx
similarity index 94%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_name.tsx
rename to src/plugins/discover/public/components/field_name/field_name.tsx
index 1b3b16332fa4f..63518aae28de6 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_name.tsx
+++ b/src/plugins/discover/public/components/field_name/field_name.tsx
@@ -20,8 +20,8 @@ import React from 'react';
import classNames from 'classnames';
import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
-import { FieldIcon, FieldIconProps } from '../../../../../../../../../plugins/kibana_react/public';
-import { shortenDottedString } from '../../../helpers';
+import { FieldIcon, FieldIconProps } from '../../../../kibana_react/public';
+import { shortenDottedString } from '../../helpers';
import { getFieldTypeName } from './field_type_name';
// property field is provided at discover's field chooser
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_type_name.ts b/src/plugins/discover/public/components/field_name/field_type_name.ts
similarity index 66%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_type_name.ts
rename to src/plugins/discover/public/components/field_name/field_type_name.ts
index 0cf428ee48b9d..a67c20fc4f353 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_type_name.ts
+++ b/src/plugins/discover/public/components/field_name/field_type_name.ts
@@ -21,52 +21,52 @@ import { i18n } from '@kbn/i18n';
export function getFieldTypeName(type: string) {
switch (type) {
case 'boolean':
- return i18n.translate('kbn.discover.fieldNameIcons.booleanAriaLabel', {
+ return i18n.translate('discover.fieldNameIcons.booleanAriaLabel', {
defaultMessage: 'Boolean field',
});
case 'conflict':
- return i18n.translate('kbn.discover.fieldNameIcons.conflictFieldAriaLabel', {
+ return i18n.translate('discover.fieldNameIcons.conflictFieldAriaLabel', {
defaultMessage: 'Conflicting field',
});
case 'date':
- return i18n.translate('kbn.discover.fieldNameIcons.dateFieldAriaLabel', {
+ return i18n.translate('discover.fieldNameIcons.dateFieldAriaLabel', {
defaultMessage: 'Date field',
});
case 'geo_point':
- return i18n.translate('kbn.discover.fieldNameIcons.geoPointFieldAriaLabel', {
+ return i18n.translate('discover.fieldNameIcons.geoPointFieldAriaLabel', {
defaultMessage: 'Geo point field',
});
case 'geo_shape':
- return i18n.translate('kbn.discover.fieldNameIcons.geoShapeFieldAriaLabel', {
+ return i18n.translate('discover.fieldNameIcons.geoShapeFieldAriaLabel', {
defaultMessage: 'Geo shape field',
});
case 'ip':
- return i18n.translate('kbn.discover.fieldNameIcons.ipAddressFieldAriaLabel', {
+ return i18n.translate('discover.fieldNameIcons.ipAddressFieldAriaLabel', {
defaultMessage: 'IP address field',
});
case 'murmur3':
- return i18n.translate('kbn.discover.fieldNameIcons.murmur3FieldAriaLabel', {
+ return i18n.translate('discover.fieldNameIcons.murmur3FieldAriaLabel', {
defaultMessage: 'Murmur3 field',
});
case 'number':
- return i18n.translate('kbn.discover.fieldNameIcons.numberFieldAriaLabel', {
+ return i18n.translate('discover.fieldNameIcons.numberFieldAriaLabel', {
defaultMessage: 'Number field',
});
case 'source':
// Note that this type is currently not provided, type for _source is undefined
- return i18n.translate('kbn.discover.fieldNameIcons.sourceFieldAriaLabel', {
+ return i18n.translate('discover.fieldNameIcons.sourceFieldAriaLabel', {
defaultMessage: 'Source field',
});
case 'string':
- return i18n.translate('kbn.discover.fieldNameIcons.stringFieldAriaLabel', {
+ return i18n.translate('discover.fieldNameIcons.stringFieldAriaLabel', {
defaultMessage: 'String field',
});
case 'nested':
- return i18n.translate('kbn.discover.fieldNameIcons.nestedFieldAriaLabel', {
+ return i18n.translate('discover.fieldNameIcons.nestedFieldAriaLabel', {
defaultMessage: 'Nested field',
});
default:
- return i18n.translate('kbn.discover.fieldNameIcons.unknownFieldAriaLabel', {
+ return i18n.translate('discover.fieldNameIcons.unknownFieldAriaLabel', {
defaultMessage: 'Unknown field',
});
}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/json_code_block/__snapshots__/json_code_block.test.tsx.snap b/src/plugins/discover/public/components/json_code_block/__snapshots__/json_code_block.test.tsx.snap
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/json_code_block/__snapshots__/json_code_block.test.tsx.snap
rename to src/plugins/discover/public/components/json_code_block/__snapshots__/json_code_block.test.tsx.snap
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/json_code_block/json_code_block.test.tsx b/src/plugins/discover/public/components/json_code_block/json_code_block.test.tsx
similarity index 95%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/json_code_block/json_code_block.test.tsx
rename to src/plugins/discover/public/components/json_code_block/json_code_block.test.tsx
index 9cab7974c9eb2..7e7f80c6aaa56 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/json_code_block/json_code_block.test.tsx
+++ b/src/plugins/discover/public/components/json_code_block/json_code_block.test.tsx
@@ -19,7 +19,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import { JsonCodeBlock } from './json_code_block';
-import { IndexPattern } from '../../../kibana_services';
+import { IndexPattern } from '../../../../data/public';
it('returns the `JsonCodeEditor` component', () => {
const props = {
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/json_code_block/json_code_block.tsx b/src/plugins/discover/public/components/json_code_block/json_code_block.tsx
similarity index 93%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/json_code_block/json_code_block.tsx
rename to src/plugins/discover/public/components/json_code_block/json_code_block.tsx
index 3331969e351ab..9297ab0dfcf4d 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/json_code_block/json_code_block.tsx
+++ b/src/plugins/discover/public/components/json_code_block/json_code_block.tsx
@@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n';
import { DocViewRenderProps } from '../../doc_views/doc_views_types';
export function JsonCodeBlock({ hit }: DocViewRenderProps) {
- const label = i18n.translate('kbn.discover.docViews.json.codeEditorAriaLabel', {
+ const label = i18n.translate('discover.docViews.json.codeEditorAriaLabel', {
defaultMessage: 'Read only JSON view of an elasticsearch document',
});
return (
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table.test.tsx b/src/plugins/discover/public/components/table/table.test.tsx
similarity index 98%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table.test.tsx
rename to src/plugins/discover/public/components/table/table.test.tsx
index 386f405544a61..91e116c4c6696 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table.test.tsx
+++ b/src/plugins/discover/public/components/table/table.test.tsx
@@ -21,10 +21,7 @@ import { mount } from 'enzyme';
// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
import { DocViewTable } from './table';
-
-import { IndexPattern, indexPatterns } from '../../../kibana_services';
-
-jest.mock('ui/new_platform');
+import { indexPatterns, IndexPattern } from '../../../../data/public';
const indexPattern = {
fields: [
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table.tsx b/src/plugins/discover/public/components/table/table.tsx
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table.tsx
rename to src/plugins/discover/public/components/table/table.tsx
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_helper.test.ts b/src/plugins/discover/public/components/table/table_helper.test.ts
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_helper.test.ts
rename to src/plugins/discover/public/components/table/table_helper.test.ts
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_helper.tsx b/src/plugins/discover/public/components/table/table_helper.tsx
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_helper.tsx
rename to src/plugins/discover/public/components/table/table_helper.tsx
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row.tsx b/src/plugins/discover/public/components/table/table_row.tsx
similarity index 98%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row.tsx
rename to src/plugins/discover/public/components/table/table_row.tsx
index 5b13f6b3655c3..a4d5c57d10b33 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row.tsx
+++ b/src/plugins/discover/public/components/table/table_row.tsx
@@ -26,7 +26,7 @@ import { DocViewTableRowBtnCollapse } from './table_row_btn_collapse';
import { DocViewTableRowBtnFilterExists } from './table_row_btn_filter_exists';
import { DocViewTableRowIconNoMapping } from './table_row_icon_no_mapping';
import { DocViewTableRowIconUnderscore } from './table_row_icon_underscore';
-import { FieldName } from '../../angular/directives/field_name/field_name';
+import { FieldName } from '../field_name/field_name';
export interface Props {
field: string;
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row_btn_collapse.tsx b/src/plugins/discover/public/components/table/table_row_btn_collapse.tsx
similarity index 94%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row_btn_collapse.tsx
rename to src/plugins/discover/public/components/table/table_row_btn_collapse.tsx
index e59f607329d4a..bb5ea4bd20f07 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row_btn_collapse.tsx
+++ b/src/plugins/discover/public/components/table/table_row_btn_collapse.tsx
@@ -26,7 +26,7 @@ export interface Props {
}
export function DocViewTableRowBtnCollapse({ onClick, isCollapsed }: Props) {
- const label = i18n.translate('kbn.discover.docViews.table.toggleFieldDetails', {
+ const label = i18n.translate('discover.docViews.table.toggleFieldDetails', {
defaultMessage: 'Toggle field details',
});
return (
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row_btn_filter_add.tsx b/src/plugins/discover/public/components/table/table_row_btn_filter_add.tsx
similarity index 87%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row_btn_filter_add.tsx
rename to src/plugins/discover/public/components/table/table_row_btn_filter_add.tsx
index 8e2668e26cf08..bd842eb5c6f72 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row_btn_filter_add.tsx
+++ b/src/plugins/discover/public/components/table/table_row_btn_filter_add.tsx
@@ -29,12 +29,12 @@ export interface Props {
export function DocViewTableRowBtnFilterAdd({ onClick, disabled = false }: Props) {
const tooltipContent = disabled ? (
) : (
);
@@ -42,7 +42,7 @@ export function DocViewTableRowBtnFilterAdd({ onClick, disabled = false }: Props
return (
) : (
)
) : (
);
@@ -54,12 +54,9 @@ export function DocViewTableRowBtnFilterExists({
return (
) : (
);
@@ -42,7 +42,7 @@ export function DocViewTableRowBtnFilterRemove({ onClick, disabled = false }: Pr
return (
}
>
Index Patterns page',
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row_icon_underscore.tsx b/src/plugins/discover/public/components/table/table_row_icon_underscore.tsx
similarity index 89%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row_icon_underscore.tsx
rename to src/plugins/discover/public/components/table/table_row_icon_underscore.tsx
index 724b5712cf1fe..791ab18de5175 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row_icon_underscore.tsx
+++ b/src/plugins/discover/public/components/table/table_row_icon_underscore.tsx
@@ -22,13 +22,13 @@ import { i18n } from '@kbn/i18n';
export function DocViewTableRowIconUnderscore() {
const ariaLabel = i18n.translate(
- 'kbn.discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedAriaLabel',
+ 'discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedAriaLabel',
{
defaultMessage: 'Warning',
}
);
const tooltipContent = i18n.translate(
- 'kbn.discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedTooltip',
+ 'discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedTooltip',
{
defaultMessage: 'Field names beginning with {underscoreSign} are not supported',
values: { underscoreSign: '_' },
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/doc_views/doc_views_helpers.tsx b/src/plugins/discover/public/doc_views/doc_views_helpers.tsx
similarity index 100%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/doc_views/doc_views_helpers.tsx
rename to src/plugins/discover/public/doc_views/doc_views_helpers.tsx
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/doc_views/doc_views_registry.ts b/src/plugins/discover/public/doc_views/doc_views_registry.ts
similarity index 82%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/doc_views/doc_views_registry.ts
rename to src/plugins/discover/public/doc_views/doc_views_registry.ts
index 91acf1c7ac4ae..8f4518538be72 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/doc_views/doc_views_registry.ts
+++ b/src/plugins/discover/public/doc_views/doc_views_registry.ts
@@ -23,8 +23,11 @@ import { DocView, DocViewInput, ElasticSearchHit, DocViewInputFn } from './doc_v
export class DocViewsRegistry {
private docViews: DocView[] = [];
+ private angularInjectorGetter: (() => Promise) | null = null;
- constructor(private getInjector: () => Promise) {}
+ setAngularInjectorGetter(injectorGetter: () => Promise) {
+ this.angularInjectorGetter = injectorGetter;
+ }
/**
* Extends and adds the given doc view to the registry array
@@ -33,7 +36,12 @@ export class DocViewsRegistry {
const docView = typeof docViewRaw === 'function' ? docViewRaw() : docViewRaw;
if (docView.directive) {
// convert angular directive to render function for backwards compatibility
- docView.render = convertDirectiveToRenderFn(docView.directive, this.getInjector);
+ docView.render = convertDirectiveToRenderFn(docView.directive, () => {
+ if (!this.angularInjectorGetter) {
+ throw new Error('Angular was not initialized');
+ }
+ return this.angularInjectorGetter();
+ });
}
if (typeof docView.shouldShow !== 'function') {
docView.shouldShow = () => true;
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/doc_views/doc_views_types.ts b/src/plugins/discover/public/doc_views/doc_views_types.ts
similarity index 90%
rename from src/legacy/core_plugins/kibana/public/discover/np_ready/doc_views/doc_views_types.ts
rename to src/plugins/discover/public/doc_views/doc_views_types.ts
index a7828f9f0e7ed..0a4b5bb570bd7 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/doc_views/doc_views_types.ts
+++ b/src/plugins/discover/public/doc_views/doc_views_types.ts
@@ -18,10 +18,10 @@
*/
import { ComponentType } from 'react';
import { IScope } from 'angular';
-import { IndexPattern } from '../../kibana_services';
+import { IndexPattern } from '../../../data/public';
export interface AngularDirective {
- controller: (scope: AngularScope) => void;
+ controller: (...injectedServices: any[]) => void;
template: string;
}
@@ -51,13 +51,14 @@ export interface DocViewRenderProps {
onAddColumn?: (columnName: string) => void;
onRemoveColumn?: (columnName: string) => void;
}
+export type DocViewerComponent = ComponentType;
export type DocViewRenderFn = (
domeNode: HTMLDivElement,
renderProps: DocViewRenderProps
) => () => void;
export interface DocViewInput {
- component?: ComponentType;
+ component?: DocViewerComponent;
directive?: AngularDirective;
order: number;
render?: DocViewRenderFn;
diff --git a/src/legacy/server/i18n/constants.js b/src/plugins/discover/public/helpers/index.ts
similarity index 92%
rename from src/legacy/server/i18n/constants.js
rename to src/plugins/discover/public/helpers/index.ts
index a7a410dbcb5b3..7196c96989e97 100644
--- a/src/legacy/server/i18n/constants.js
+++ b/src/plugins/discover/public/helpers/index.ts
@@ -17,4 +17,4 @@
* under the License.
*/
-export const I18N_RC = '.i18nrc.json';
+export { shortenDottedString } from './shorten_dotted_string';
diff --git a/src/plugins/discover/public/helpers/shorten_dotted_string.ts b/src/plugins/discover/public/helpers/shorten_dotted_string.ts
new file mode 100644
index 0000000000000..9d78a96784339
--- /dev/null
+++ b/src/plugins/discover/public/helpers/shorten_dotted_string.ts
@@ -0,0 +1,26 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+const DOT_PREFIX_RE = /(.).+?\./g;
+
+/**
+ * Convert a dot.notated.string into a short
+ * version (d.n.string)
+ */
+export const shortenDottedString = (input: string) => input.replace(DOT_PREFIX_RE, '$1.');
diff --git a/src/plugins/discover/public/index.scss b/src/plugins/discover/public/index.scss
new file mode 100644
index 0000000000000..841415620d691
--- /dev/null
+++ b/src/plugins/discover/public/index.scss
@@ -0,0 +1 @@
+@import 'components/index';
diff --git a/src/plugins/discover/public/index.ts b/src/plugins/discover/public/index.ts
index c5050147c3d5a..dbc361ee59f49 100644
--- a/src/plugins/discover/public/index.ts
+++ b/src/plugins/discover/public/index.ts
@@ -17,5 +17,18 @@
* under the License.
*/
+import { DiscoverPlugin } from './plugin';
+
+export { DiscoverSetup, DiscoverStart } from './plugin';
+export { DocViewTable } from './components/table/table';
+export { JsonCodeBlock } from './components/json_code_block/json_code_block';
+export { DocViewInput, DocViewInputFn, DocViewerComponent } from './doc_views/doc_views_types';
+export { FieldName } from './components/field_name/field_name';
+export * from './doc_views/doc_views_types';
+
+export function plugin() {
+ return new DiscoverPlugin();
+}
+
export { createSavedSearchesLoader } from './saved_searches/saved_searches';
export { SavedSearchLoader, SavedSearch } from './saved_searches/types';
diff --git a/src/legacy/core_plugins/kibana_react/index.ts b/src/plugins/discover/public/mocks.ts
similarity index 58%
rename from src/legacy/core_plugins/kibana_react/index.ts
rename to src/plugins/discover/public/mocks.ts
index f4083f3d50c34..bb05e3d412001 100644
--- a/src/legacy/core_plugins/kibana_react/index.ts
+++ b/src/plugins/discover/public/mocks.ts
@@ -17,25 +17,31 @@
* under the License.
*/
-import { resolve } from 'path';
-import { Legacy } from '../../../../kibana';
+import { DiscoverSetup, DiscoverStart } from '.';
-// eslint-disable-next-line import/no-default-export
-export default function DataPlugin(kibana: any) {
- const config: Legacy.PluginSpecOptions = {
- id: 'kibana_react',
- require: [],
- config: (Joi: any) => {
- return Joi.object({
- enabled: Joi.boolean().default(true),
- }).default();
+export type Setup = jest.Mocked;
+export type Start = jest.Mocked;
+
+const createSetupContract = (): Setup => {
+ const setupContract: Setup = {
+ docViews: {
+ addDocView: jest.fn(),
+ setAngularInjectorGetter: jest.fn(),
},
- init: (server: Legacy.Server) => ({}),
- uiExports: {
- injectDefaultVars: () => ({}),
- styleSheetPaths: resolve(__dirname, 'public/index.scss'),
+ };
+ return setupContract;
+};
+
+const createStartContract = (): Start => {
+ const startContract: Start = {
+ docViews: {
+ DocViewer: jest.fn(() => null),
},
};
+ return startContract;
+};
- return new kibana.Plugin(config);
-}
+export const discoverPluginMock = {
+ createSetupContract,
+ createStartContract,
+};
diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts
new file mode 100644
index 0000000000000..d2797586bfdfb
--- /dev/null
+++ b/src/plugins/discover/public/plugin.ts
@@ -0,0 +1,110 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { auto } from 'angular';
+import { CoreSetup, Plugin } from 'kibana/public';
+import { DocViewInput, DocViewInputFn, DocViewRenderProps } from './doc_views/doc_views_types';
+import { DocViewsRegistry } from './doc_views/doc_views_registry';
+import { DocViewTable } from './components/table/table';
+import { JsonCodeBlock } from './components/json_code_block/json_code_block';
+import { DocViewer } from './components/doc_viewer/doc_viewer';
+import { setDocViewsRegistry } from './services';
+
+import './index.scss';
+
+/**
+ * @public
+ */
+export interface DiscoverSetup {
+ docViews: {
+ /**
+ * Add new doc view shown along with table view and json view in the details of each document in Discover.
+ * Both react and angular doc views are supported.
+ * @param docViewRaw
+ */
+ addDocView(docViewRaw: DocViewInput | DocViewInputFn): void;
+ /**
+ * Set the angular injector for bootstrapping angular doc views. This is only exposed temporarily to aid
+ * migration to the new platform and will be removed soon.
+ * @deprecated
+ * @param injectorGetter
+ */
+ setAngularInjectorGetter(injectorGetter: () => Promise): void;
+ };
+}
+/**
+ * @public
+ */
+export interface DiscoverStart {
+ docViews: {
+ /**
+ * Component rendering all the doc views for a given document.
+ * This is only exposed temporarily to aid migration to the new platform and will be removed soon.
+ * @deprecated
+ */
+ DocViewer: React.ComponentType;
+ };
+}
+
+/**
+ * Contains Discover, one of the oldest parts of Kibana
+ * There are 2 kinds of Angular bootstrapped for rendering, additionally to the main Angular
+ * Discover provides embeddables, those contain a slimmer Angular
+ */
+export class DiscoverPlugin implements Plugin {
+ private docViewsRegistry: DocViewsRegistry | null = null;
+
+ setup(core: CoreSetup): DiscoverSetup {
+ this.docViewsRegistry = new DocViewsRegistry();
+ setDocViewsRegistry(this.docViewsRegistry);
+ this.docViewsRegistry.addDocView({
+ title: i18n.translate('discover.docViews.table.tableTitle', {
+ defaultMessage: 'Table',
+ }),
+ order: 10,
+ component: DocViewTable,
+ });
+ this.docViewsRegistry.addDocView({
+ title: i18n.translate('discover.docViews.json.jsonTitle', {
+ defaultMessage: 'JSON',
+ }),
+ order: 20,
+ component: JsonCodeBlock,
+ });
+
+ return {
+ docViews: {
+ addDocView: this.docViewsRegistry.addDocView.bind(this.docViewsRegistry),
+ setAngularInjectorGetter: this.docViewsRegistry.setAngularInjectorGetter.bind(
+ this.docViewsRegistry
+ ),
+ },
+ };
+ }
+
+ start() {
+ return {
+ docViews: {
+ DocViewer,
+ },
+ };
+ }
+}
diff --git a/src/plugins/discover/public/saved_searches/_saved_search.ts b/src/plugins/discover/public/saved_searches/_saved_search.ts
index 72983b7835eee..56360b04a49c8 100644
--- a/src/plugins/discover/public/saved_searches/_saved_search.ts
+++ b/src/plugins/discover/public/saved_searches/_saved_search.ts
@@ -16,7 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { createSavedObjectClass, SavedObjectKibanaServices } from '../../../saved_objects/public';
+import {
+ createSavedObjectClass,
+ SavedObject,
+ SavedObjectKibanaServices,
+} from '../../../saved_objects/public';
export function createSavedSearchClass(services: SavedObjectKibanaServices) {
const SavedObjectClass = createSavedObjectClass(services);
@@ -66,5 +70,5 @@ export function createSavedSearchClass(services: SavedObjectKibanaServices) {
}
}
- return SavedSearch;
+ return SavedSearch as new (id: string) => SavedObject;
}
diff --git a/src/plugins/discover/public/services.ts b/src/plugins/discover/public/services.ts
new file mode 100644
index 0000000000000..3a28759d82b71
--- /dev/null
+++ b/src/plugins/discover/public/services.ts
@@ -0,0 +1,25 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { createGetterSetter } from '../../kibana_utils/common';
+import { DocViewsRegistry } from './doc_views/doc_views_registry';
+
+export const [getDocViewsRegistry, setDocViewsRegistry] = createGetterSetter(
+ 'DocViewsRegistry'
+);
diff --git a/src/plugins/embeddable/public/lib/triggers/triggers.ts b/src/plugins/embeddable/public/lib/triggers/triggers.ts
index d7da47a9317a0..7317b0c87c0b6 100644
--- a/src/plugins/embeddable/public/lib/triggers/triggers.ts
+++ b/src/plugins/embeddable/public/lib/triggers/triggers.ts
@@ -28,7 +28,7 @@ export interface EmbeddableVisTriggerContext ManagementSection[],
registerLegacyApp: KibanaLegacySetup['registerLegacyApp'],
getLegacyManagementSections: () => LegacyManagementSection,
- getStartServices: CoreSetup['getStartServices']
+ getStartServices: StartServicesAccessor
) {
this.id = id;
this.title = title;
diff --git a/src/plugins/management/public/management_section.ts b/src/plugins/management/public/management_section.ts
index 2f323c4b6a9cf..483605341ae4c 100644
--- a/src/plugins/management/public/management_section.ts
+++ b/src/plugins/management/public/management_section.ts
@@ -19,7 +19,7 @@
import { CreateSection, RegisterManagementAppArgs } from './types';
import { KibanaLegacySetup } from '../../kibana_legacy/public';
-import { CoreSetup } from '../../../core/public';
+import { StartServicesAccessor } from '../../../core/public';
// @ts-ignore
import { LegacyManagementSection } from './legacy';
import { ManagementApp } from './management_app';
@@ -34,14 +34,14 @@ export class ManagementSection {
private readonly getSections: () => ManagementSection[];
private readonly registerLegacyApp: KibanaLegacySetup['registerLegacyApp'];
private readonly getLegacyManagementSection: () => LegacyManagementSection;
- private readonly getStartServices: CoreSetup['getStartServices'];
+ private readonly getStartServices: StartServicesAccessor;
constructor(
{ id, title, order = 100, euiIconType, icon }: CreateSection,
getSections: () => ManagementSection[],
registerLegacyApp: KibanaLegacySetup['registerLegacyApp'],
getLegacyManagementSection: () => ManagementSection,
- getStartServices: CoreSetup['getStartServices']
+ getStartServices: StartServicesAccessor
) {
this.id = id;
this.title = title;
diff --git a/src/plugins/management/public/management_service.ts b/src/plugins/management/public/management_service.ts
index 4a900345b3843..ed31a22992da8 100644
--- a/src/plugins/management/public/management_service.ts
+++ b/src/plugins/management/public/management_service.ts
@@ -22,7 +22,7 @@ import { KibanaLegacySetup } from '../../kibana_legacy/public';
// @ts-ignore
import { LegacyManagementSection } from './legacy';
import { CreateSection } from './types';
-import { CoreSetup, CoreStart } from '../../../core/public';
+import { StartServicesAccessor, CoreStart } from '../../../core/public';
export class ManagementService {
private sections: ManagementSection[] = [];
@@ -30,7 +30,7 @@ export class ManagementService {
private register(
registerLegacyApp: KibanaLegacySetup['registerLegacyApp'],
getLegacyManagement: () => LegacyManagementSection,
- getStartServices: CoreSetup['getStartServices']
+ getStartServices: StartServicesAccessor
) {
return (section: CreateSection) => {
if (this.getSection(section.id)) {
@@ -71,7 +71,7 @@ export class ManagementService {
public setup(
kibanaLegacy: KibanaLegacySetup,
getLegacyManagement: () => LegacyManagementSection,
- getStartServices: CoreSetup['getStartServices']
+ getStartServices: StartServicesAccessor
) {
const register = this.register.bind(this)(
kibanaLegacy.registerLegacyApp,
diff --git a/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_menu_item.test.tsx.snap b/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_menu_item.test.tsx.snap
new file mode 100644
index 0000000000000..0d54d5d3e9c4a
--- /dev/null
+++ b/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_menu_item.test.tsx.snap
@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`TopNavMenu Should render emphasized item which should be clickable 1`] = `
+
+ Test
+
+`;
diff --git a/src/plugins/navigation/public/top_nav_menu/_index.scss b/src/plugins/navigation/public/top_nav_menu/_index.scss
index 4a0e6af3f7f70..5befe4789dd6c 100644
--- a/src/plugins/navigation/public/top_nav_menu/_index.scss
+++ b/src/plugins/navigation/public/top_nav_menu/_index.scss
@@ -1,7 +1,11 @@
.kbnTopNavMenu__wrapper {
z-index: 5;
- .kbnTopNavMenu {
- padding: $euiSizeS 0px $euiSizeXS;
+ .kbnTopNavMenu {
+ padding: $euiSizeS 0;
+
+ .kbnTopNavItemEmphasized {
+ padding: 0 $euiSizeS;
+ }
}
}
diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx
index 80d1a53cd417f..14ad40f13e388 100644
--- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx
+++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx
@@ -46,7 +46,11 @@ export function TopNavMenu(props: TopNavMenuProps) {
if (!config) return;
return config.map((menuItem: TopNavMenuData, i: number) => {
return (
-
+
);
@@ -66,6 +70,7 @@ export function TopNavMenu(props: TopNavMenuProps) {
void;
export interface TopNavMenuData {
@@ -28,6 +30,9 @@ export interface TopNavMenuData {
className?: string;
disableButton?: boolean | (() => boolean);
tooltip?: string | (() => string);
+ emphasize?: boolean;
+ iconType?: string;
+ iconSide?: ButtonIconSide;
}
export interface RegisteredTopNavMenuData extends TopNavMenuData {
diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx
index 4816ef3c95869..9ba58379c5ce1 100644
--- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx
+++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx
@@ -23,6 +23,15 @@ import { TopNavMenuData } from './top_nav_menu_data';
import { shallowWithIntl } from 'test_utils/enzyme_helpers';
describe('TopNavMenu', () => {
+ const ensureMenuItemDisabled = (data: TopNavMenuData) => {
+ const component = shallowWithIntl();
+ expect(component.prop('isDisabled')).toEqual(true);
+
+ const event = { currentTarget: { value: 'a' } };
+ component.simulate('click', event);
+ expect(data.run).toHaveBeenCalledTimes(0);
+ };
+
it('Should render and click an item', () => {
const data: TopNavMenuData = {
id: 'test',
@@ -60,35 +69,62 @@ describe('TopNavMenu', () => {
expect(data.run).toHaveBeenCalled();
});
- it('Should render disabled item and it shouldnt be clickable', () => {
+ it('Should render emphasized item which should be clickable', () => {
const data: TopNavMenuData = {
id: 'test',
label: 'test',
- disableButton: true,
+ iconType: 'beaker',
+ iconSide: 'right',
+ emphasize: true,
run: jest.fn(),
};
const component = shallowWithIntl();
- expect(component.prop('isDisabled')).toEqual(true);
-
const event = { currentTarget: { value: 'a' } };
component.simulate('click', event);
- expect(data.run).toHaveBeenCalledTimes(0);
+ expect(data.run).toHaveBeenCalledTimes(1);
+ expect(component).toMatchSnapshot();
+ });
+
+ it('Should render disabled item and it shouldnt be clickable', () => {
+ ensureMenuItemDisabled({
+ id: 'test',
+ label: 'test',
+ disableButton: true,
+ run: jest.fn(),
+ });
});
it('Should render item with disable function and it shouldnt be clickable', () => {
- const data: TopNavMenuData = {
+ ensureMenuItemDisabled({
id: 'test',
label: 'test',
disableButton: () => true,
run: jest.fn(),
- };
+ });
+ });
- const component = shallowWithIntl();
- expect(component.prop('isDisabled')).toEqual(true);
+ it('Should render disabled emphasized item which shouldnt be clickable', () => {
+ ensureMenuItemDisabled({
+ id: 'test',
+ label: 'test',
+ iconType: 'beaker',
+ iconSide: 'right',
+ emphasize: true,
+ disableButton: true,
+ run: jest.fn(),
+ });
+ });
- const event = { currentTarget: { value: 'a' } };
- component.simulate('click', event);
- expect(data.run).toHaveBeenCalledTimes(0);
+ it('Should render emphasized item with disable function and it shouldnt be clickable', () => {
+ ensureMenuItemDisabled({
+ id: 'test',
+ label: 'test',
+ iconType: 'beaker',
+ iconSide: 'right',
+ emphasize: true,
+ disableButton: () => true,
+ run: jest.fn(),
+ });
});
});
diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx
index 4d3b72bae6411..92e267f17d08e 100644
--- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx
+++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx
@@ -21,6 +21,7 @@ import { capitalize, isFunction } from 'lodash';
import React, { MouseEvent } from 'react';
import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
+import { EuiButton } from '@elastic/eui';
import { TopNavMenuData } from './top_nav_menu_data';
export function TopNavMenuItem(props: TopNavMenuData) {
@@ -39,14 +40,20 @@ export function TopNavMenuItem(props: TopNavMenuData) {
props.run(e.currentTarget);
}
- const btn = (
-
+ const commonButtonProps = {
+ isDisabled: isDisabled(),
+ onClick: handleClick,
+ iconType: props.iconType,
+ iconSide: props.iconSide,
+ 'data-test-subj': props.testId,
+ };
+
+ const btn = props.emphasize ? (
+
+ {capitalize(props.label || props.id!)}
+
+ ) : (
+
{capitalize(props.label || props.id!)}
);
@@ -54,9 +61,8 @@ export function TopNavMenuItem(props: TopNavMenuData) {
const tooltip = getTooltip();
if (tooltip) {
return {btn};
- } else {
- return btn;
}
+ return btn;
}
TopNavMenuItem.defaultProps = {
diff --git a/src/plugins/saved_objects/public/index.ts b/src/plugins/saved_objects/public/index.ts
index ea92c921efad0..9e0a7c40c043f 100644
--- a/src/plugins/saved_objects/public/index.ts
+++ b/src/plugins/saved_objects/public/index.ts
@@ -21,7 +21,13 @@ import { SavedObjectsPublicPlugin } from './plugin';
export { OnSaveProps, SavedObjectSaveModal, SaveResult, showSaveModal } from './save_modal';
export { getSavedObjectFinder, SavedObjectFinderUi, SavedObjectMetaData } from './finder';
-export { SavedObjectLoader, createSavedObjectClass } from './saved_object';
+export {
+ SavedObjectLoader,
+ createSavedObjectClass,
+ checkForDuplicateTitle,
+ saveWithConfirmation,
+ isErrorNonFatal,
+} from './saved_object';
export { SavedObjectSaveOpts, SavedObjectKibanaServices, SavedObject } from './types';
export const plugin = () => new SavedObjectsPublicPlugin();
diff --git a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx
index 1d145bc97bdb4..95eb56c0e874b 100644
--- a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx
+++ b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx
@@ -289,7 +289,7 @@ export class SavedObjectSaveModal extends React.Component {
{
+ const savedObjectsClient: SavedObjectsClientContract = {} as SavedObjectsClientContract;
+ const overlays: OverlayStart = {} as OverlayStart;
+ const source: SavedObjectAttributes = {} as SavedObjectAttributes;
+ const options: SavedObjectsCreateOptions = {} as SavedObjectsCreateOptions;
+ const savedObject = {
+ getEsType: () => 'test type',
+ title: 'test title',
+ displayName: 'test display name',
+ };
+
+ beforeEach(() => {
+ savedObjectsClient.create = jest.fn();
+ jest.spyOn(deps, 'confirmModalPromise').mockReturnValue(Promise.resolve({} as any));
+ });
+
+ test('should call create of savedObjectsClient', async () => {
+ await saveWithConfirmation(source, savedObject, options, { savedObjectsClient, overlays });
+ expect(savedObjectsClient.create).toHaveBeenCalledWith(
+ savedObject.getEsType(),
+ source,
+ options
+ );
+ });
+
+ test('should call confirmModalPromise when such record exists', async () => {
+ savedObjectsClient.create = jest
+ .fn()
+ .mockImplementation((type, src, opt) =>
+ opt && opt.overwrite ? Promise.resolve({} as any) : Promise.reject({ res: { status: 409 } })
+ );
+
+ await saveWithConfirmation(source, savedObject, options, { savedObjectsClient, overlays });
+ expect(deps.confirmModalPromise).toHaveBeenCalledWith(
+ expect.any(String),
+ expect.any(String),
+ expect.any(String),
+ overlays
+ );
+ });
+
+ test('should call create of savedObjectsClient when overwriting confirmed', async () => {
+ savedObjectsClient.create = jest
+ .fn()
+ .mockImplementation((type, src, opt) =>
+ opt && opt.overwrite ? Promise.resolve({} as any) : Promise.reject({ res: { status: 409 } })
+ );
+
+ await saveWithConfirmation(source, savedObject, options, { savedObjectsClient, overlays });
+ expect(savedObjectsClient.create).toHaveBeenLastCalledWith(savedObject.getEsType(), source, {
+ overwrite: true,
+ ...options,
+ });
+ });
+
+ test('should reject when overwriting denied', async () => {
+ savedObjectsClient.create = jest.fn().mockReturnValue(Promise.reject({ res: { status: 409 } }));
+ jest.spyOn(deps, 'confirmModalPromise').mockReturnValue(Promise.reject());
+
+ expect.assertions(1);
+ await expect(
+ saveWithConfirmation(source, savedObject, options, {
+ savedObjectsClient,
+ overlays,
+ })
+ ).rejects.toThrow(OVERWRITE_REJECTED);
+ });
+});
diff --git a/src/plugins/saved_objects/public/saved_object/helpers/save_with_confirmation.ts b/src/plugins/saved_objects/public/saved_object/helpers/save_with_confirmation.ts
new file mode 100644
index 0000000000000..b413ea19a932d
--- /dev/null
+++ b/src/plugins/saved_objects/public/saved_object/helpers/save_with_confirmation.ts
@@ -0,0 +1,87 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { get } from 'lodash';
+import { i18n } from '@kbn/i18n';
+import {
+ SavedObjectAttributes,
+ SavedObjectsCreateOptions,
+ OverlayStart,
+ SavedObjectsClientContract,
+} from 'kibana/public';
+import { OVERWRITE_REJECTED } from '../../constants';
+import { confirmModalPromise } from './confirm_modal_promise';
+
+/**
+ * Attempts to create the current object using the serialized source. If an object already
+ * exists, a warning message requests an overwrite confirmation.
+ * @param source - serialized version of this object what will be indexed into elasticsearch.
+ * @param savedObject - a simple object that contains properties title and displayName, and getEsType method
+ * @param options - options to pass to the saved object create method
+ * @param services - provides Kibana services savedObjectsClient and overlays
+ * @returns {Promise} - A promise that is resolved with the objects id if the object is
+ * successfully indexed. If the overwrite confirmation was rejected, an error is thrown with
+ * a confirmRejected = true parameter so that case can be handled differently than
+ * a create or index error.
+ * @resolved {SavedObject}
+ */
+export async function saveWithConfirmation(
+ source: SavedObjectAttributes,
+ savedObject: {
+ getEsType(): string;
+ title: string;
+ displayName: string;
+ },
+ options: SavedObjectsCreateOptions,
+ services: { savedObjectsClient: SavedObjectsClientContract; overlays: OverlayStart }
+) {
+ const { savedObjectsClient, overlays } = services;
+ try {
+ return await savedObjectsClient.create(savedObject.getEsType(), source, options);
+ } catch (err) {
+ // record exists, confirm overwriting
+ if (get(err, 'res.status') === 409) {
+ const confirmMessage = i18n.translate(
+ 'savedObjects.confirmModal.overwriteConfirmationMessage',
+ {
+ defaultMessage: 'Are you sure you want to overwrite {title}?',
+ values: { title: savedObject.title },
+ }
+ );
+
+ const title = i18n.translate('savedObjects.confirmModal.overwriteTitle', {
+ defaultMessage: 'Overwrite {name}?',
+ values: { name: savedObject.displayName },
+ });
+ const confirmButtonText = i18n.translate('savedObjects.confirmModal.overwriteButtonLabel', {
+ defaultMessage: 'Overwrite',
+ });
+
+ return confirmModalPromise(confirmMessage, title, confirmButtonText, overlays)
+ .then(() =>
+ savedObjectsClient.create(savedObject.getEsType(), source, {
+ overwrite: true,
+ ...options,
+ })
+ )
+ .catch(() => Promise.reject(new Error(OVERWRITE_REJECTED)));
+ }
+ return await Promise.reject(err);
+ }
+}
diff --git a/src/plugins/saved_objects/public/saved_object/index.ts b/src/plugins/saved_objects/public/saved_object/index.ts
index d3be5ea6df617..178ffaf88f4be 100644
--- a/src/plugins/saved_objects/public/saved_object/index.ts
+++ b/src/plugins/saved_objects/public/saved_object/index.ts
@@ -19,3 +19,6 @@
export { createSavedObjectClass } from './saved_object';
export { SavedObjectLoader } from './saved_object_loader';
+export { checkForDuplicateTitle } from './helpers/check_for_duplicate_title';
+export { saveWithConfirmation } from './helpers/save_with_confirmation';
+export { isErrorNonFatal } from './helpers/save_saved_object';
diff --git a/src/legacy/ui/public/share/_index.scss b/src/plugins/share/public/components/_index.scss
similarity index 100%
rename from src/legacy/ui/public/share/_index.scss
rename to src/plugins/share/public/components/_index.scss
diff --git a/src/legacy/ui/public/share/_share_context_menu.scss b/src/plugins/share/public/components/_share_context_menu.scss
similarity index 100%
rename from src/legacy/ui/public/share/_share_context_menu.scss
rename to src/plugins/share/public/components/_share_context_menu.scss
diff --git a/src/plugins/share/public/index.scss b/src/plugins/share/public/index.scss
new file mode 100644
index 0000000000000..0271fbb8e9026
--- /dev/null
+++ b/src/plugins/share/public/index.scss
@@ -0,0 +1 @@
+@import './components/index'
\ No newline at end of file
diff --git a/src/plugins/share/public/plugin.ts b/src/plugins/share/public/plugin.ts
index 5b638174b4dfb..d02f51af42905 100644
--- a/src/plugins/share/public/plugin.ts
+++ b/src/plugins/share/public/plugin.ts
@@ -17,6 +17,8 @@
* under the License.
*/
+import './index.scss';
+
import { CoreSetup, CoreStart, Plugin } from 'src/core/public';
import { ShareMenuManager, ShareMenuManagerStart } from './services';
import { ShareMenuRegistry, ShareMenuRegistrySetup } from './services';
diff --git a/src/legacy/core_plugins/telemetry/README.md b/src/plugins/telemetry/README.md
similarity index 81%
rename from src/legacy/core_plugins/telemetry/README.md
rename to src/plugins/telemetry/README.md
index 830c08f8e8bed..196d596fb784f 100644
--- a/src/legacy/core_plugins/telemetry/README.md
+++ b/src/plugins/telemetry/README.md
@@ -6,4 +6,4 @@ Telemetry allows Kibana features to have usage tracked in the wild. The general
2. Sending a payload of usage data up to Elastic's telemetry cluster.
3. Viewing usage data in the Kibana instance of the telemetry cluster (Viewing).
-This plugin is responsible for sending usage data to the telemetry cluster. For collecting usage data, use
+This plugin is responsible for sending usage data to the telemetry cluster. For collecting usage data, use the [`usageCollection` plugin](../usage_collection/README.md)
diff --git a/src/plugins/telemetry/common/constants.ts b/src/plugins/telemetry/common/constants.ts
index 7b7694ed9aed7..babd009143c5e 100644
--- a/src/plugins/telemetry/common/constants.ts
+++ b/src/plugins/telemetry/common/constants.ts
@@ -17,13 +17,31 @@
* under the License.
*/
+import { i18n } from '@kbn/i18n';
+
+/**
+ * config options opt into telemetry
+ */
+export const CONFIG_TELEMETRY = 'telemetry:optIn';
+
+/**
+ * config description for opting into telemetry
+ */
+export const getConfigTelemetryDesc = () => {
+ // Can't find where it's used but copying it over from the legacy code just in case...
+ return i18n.translate('telemetry.telemetryConfigDescription', {
+ defaultMessage:
+ 'Help us improve the Elastic Stack by providing usage statistics for basic features. We will not share this data outside of Elastic.',
+ });
+};
+
/**
* The amount of time, in milliseconds, to wait between reports when enabled.
* Currently 24 hours.
*/
export const REPORT_INTERVAL_MS = 86400000;
-/*
+/**
* Key for the localStorage service
*/
export const LOCALSTORAGE_KEY = 'telemetry.data';
@@ -37,3 +55,28 @@ export const PATH_TO_ADVANCED_SETTINGS = 'kibana#/management/kibana/settings';
* Link to the Elastic Telemetry privacy statement.
*/
export const PRIVACY_STATEMENT_URL = `https://www.elastic.co/legal/privacy-statement`;
+
+/**
+ * The type name used to publish telemetry plugin stats.
+ */
+export const TELEMETRY_STATS_TYPE = 'telemetry';
+
+/**
+ * The endpoint version when hitting the remote telemetry service
+ */
+export const ENDPOINT_VERSION = 'v2';
+
+/**
+ * UI metric usage type
+ */
+export const UI_METRIC_USAGE_TYPE = 'ui_metric';
+
+/**
+ * Application Usage type
+ */
+export const APPLICATION_USAGE_TYPE = 'application_usage';
+
+/**
+ * The type name used within the Monitoring index to publish management stats.
+ */
+export const KIBANA_STACK_MANAGEMENT_STATS_TYPE = 'stack_management';
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts b/src/plugins/telemetry/common/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts
similarity index 93%
rename from src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts
rename to src/plugins/telemetry/common/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts
index 9fa4fbc5e0227..d7dcfd606b6ac 100644
--- a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts
+++ b/src/plugins/telemetry/common/telemetry_config/get_telemetry_allow_changing_opt_in_status.ts
@@ -16,7 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { TelemetrySavedObject } from '../telemetry_repository/get_telemetry_saved_object';
+
+import { TelemetrySavedObject } from './types';
interface GetTelemetryAllowChangingOptInStatus {
configTelemetryAllowChangingOptInStatus: boolean;
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_failure_details.test.ts b/src/plugins/telemetry/common/telemetry_config/get_telemetry_failure_details.test.ts
similarity index 100%
rename from src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_failure_details.test.ts
rename to src/plugins/telemetry/common/telemetry_config/get_telemetry_failure_details.test.ts
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_failure_details.ts b/src/plugins/telemetry/common/telemetry_config/get_telemetry_failure_details.ts
similarity index 94%
rename from src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_failure_details.ts
rename to src/plugins/telemetry/common/telemetry_config/get_telemetry_failure_details.ts
index 2952fa96a5cf3..c23ec42be56c4 100644
--- a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_failure_details.ts
+++ b/src/plugins/telemetry/common/telemetry_config/get_telemetry_failure_details.ts
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { TelemetrySavedObject } from '../telemetry_repository/get_telemetry_saved_object';
+import { TelemetrySavedObject } from './types';
interface GetTelemetryFailureDetailsConfig {
telemetrySavedObject: TelemetrySavedObject;
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.test.ts b/src/plugins/telemetry/common/telemetry_config/get_telemetry_notify_user_about_optin_default.test.ts
similarity index 100%
rename from src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.test.ts
rename to src/plugins/telemetry/common/telemetry_config/get_telemetry_notify_user_about_optin_default.test.ts
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.ts b/src/plugins/telemetry/common/telemetry_config/get_telemetry_notify_user_about_optin_default.ts
similarity index 94%
rename from src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.ts
rename to src/plugins/telemetry/common/telemetry_config/get_telemetry_notify_user_about_optin_default.ts
index 8ef3bd8388ecb..19bd1974ffba1 100644
--- a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_notify_user_about_optin_default.ts
+++ b/src/plugins/telemetry/common/telemetry_config/get_telemetry_notify_user_about_optin_default.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { TelemetrySavedObject } from '../telemetry_repository/get_telemetry_saved_object';
+import { TelemetrySavedObject } from './types';
interface NotifyOpts {
allowChangingOptInStatus: boolean;
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.test.ts b/src/plugins/telemetry/common/telemetry_config/get_telemetry_opt_in.test.ts
similarity index 98%
rename from src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.test.ts
rename to src/plugins/telemetry/common/telemetry_config/get_telemetry_opt_in.test.ts
index efc4a020e0ff0..da44abd35517c 100644
--- a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.test.ts
+++ b/src/plugins/telemetry/common/telemetry_config/get_telemetry_opt_in.test.ts
@@ -18,7 +18,7 @@
*/
import { getTelemetryOptIn } from './get_telemetry_opt_in';
-import { TelemetrySavedObject } from '../telemetry_repository/get_telemetry_saved_object';
+import { TelemetrySavedObject } from './types';
describe('getTelemetryOptIn', () => {
it('returns null when saved object not found', () => {
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.ts b/src/plugins/telemetry/common/telemetry_config/get_telemetry_opt_in.ts
similarity index 97%
rename from src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.ts
rename to src/plugins/telemetry/common/telemetry_config/get_telemetry_opt_in.ts
index d83ffdf69b576..7beb5415ad7b1 100644
--- a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_opt_in.ts
+++ b/src/plugins/telemetry/common/telemetry_config/get_telemetry_opt_in.ts
@@ -18,7 +18,7 @@
*/
import semver from 'semver';
-import { TelemetrySavedObject } from '../telemetry_repository/get_telemetry_saved_object';
+import { TelemetrySavedObject } from './types';
interface GetTelemetryOptInConfig {
telemetrySavedObject: TelemetrySavedObject;
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.test.ts b/src/plugins/telemetry/common/telemetry_config/get_telemetry_send_usage_from.test.ts
similarity index 96%
rename from src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.test.ts
rename to src/plugins/telemetry/common/telemetry_config/get_telemetry_send_usage_from.test.ts
index 69868a97a931d..2cf68f0abedea 100644
--- a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.test.ts
+++ b/src/plugins/telemetry/common/telemetry_config/get_telemetry_send_usage_from.test.ts
@@ -18,7 +18,7 @@
*/
import { getTelemetrySendUsageFrom } from './get_telemetry_send_usage_from';
-import { TelemetrySavedObject } from '../telemetry_repository/get_telemetry_saved_object';
+import { TelemetrySavedObject } from './types';
describe('getTelemetrySendUsageFrom', () => {
it('returns kibana.yml config when saved object not found', () => {
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.ts b/src/plugins/telemetry/common/telemetry_config/get_telemetry_send_usage_from.ts
similarity index 93%
rename from src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.ts
rename to src/plugins/telemetry/common/telemetry_config/get_telemetry_send_usage_from.ts
index 9e4ae14b6097c..78dc1d877c47f 100644
--- a/src/legacy/core_plugins/telemetry/server/telemetry_config/get_telemetry_send_usage_from.ts
+++ b/src/plugins/telemetry/common/telemetry_config/get_telemetry_send_usage_from.ts
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { TelemetrySavedObject } from '../telemetry_repository/get_telemetry_saved_object';
+import { TelemetrySavedObject } from './types';
interface GetTelemetryUsageFetcherConfig {
configTelemetrySendUsageFrom: 'browser' | 'server';
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_config/index.ts b/src/plugins/telemetry/common/telemetry_config/index.ts
similarity index 94%
rename from src/legacy/core_plugins/telemetry/server/telemetry_config/index.ts
rename to src/plugins/telemetry/common/telemetry_config/index.ts
index bf9855ce7538e..51eb4dd16105e 100644
--- a/src/legacy/core_plugins/telemetry/server/telemetry_config/index.ts
+++ b/src/plugins/telemetry/common/telemetry_config/index.ts
@@ -17,7 +17,6 @@
* under the License.
*/
-export { replaceTelemetryInjectedVars } from './replace_injected_vars';
export { getTelemetryOptIn } from './get_telemetry_opt_in';
export { getTelemetrySendUsageFrom } from './get_telemetry_send_usage_from';
export { getTelemetryAllowChangingOptInStatus } from './get_telemetry_allow_changing_opt_in_status';
diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_repository/index.ts b/src/plugins/telemetry/common/telemetry_config/types.ts
similarity index 86%
rename from src/legacy/core_plugins/telemetry/server/telemetry_repository/index.ts
rename to src/plugins/telemetry/common/telemetry_config/types.ts
index f1735d1bb2866..7ab37e9544164 100644
--- a/src/legacy/core_plugins/telemetry/server/telemetry_repository/index.ts
+++ b/src/plugins/telemetry/common/telemetry_config/types.ts
@@ -17,9 +17,6 @@
* under the License.
*/
-export { getTelemetrySavedObject, TelemetrySavedObject } from './get_telemetry_saved_object';
-export { updateTelemetrySavedObject } from './update_telemetry_saved_object';
-
export interface TelemetrySavedObjectAttributes {
enabled?: boolean | null;
lastVersionChecked?: string;
@@ -30,3 +27,5 @@ export interface TelemetrySavedObjectAttributes {
reportFailureCount?: number;
reportFailureVersion?: string;
}
+
+export type TelemetrySavedObject = TelemetrySavedObjectAttributes | null | false;
diff --git a/src/plugins/telemetry/kibana.json b/src/plugins/telemetry/kibana.json
index 3a28149276c3e..f623f4f2a565d 100644
--- a/src/plugins/telemetry/kibana.json
+++ b/src/plugins/telemetry/kibana.json
@@ -1,6 +1,10 @@
{
"id": "telemetry",
"version": "kibana",
- "server": false,
- "ui": true
+ "server": true,
+ "ui": true,
+ "requiredPlugins": [
+ "telemetryCollectionManager",
+ "usageCollection"
+ ]
}
diff --git a/src/plugins/telemetry/public/components/index.ts b/src/plugins/telemetry/public/components/index.ts
index f4341154f527a..8fda181b2ed93 100644
--- a/src/plugins/telemetry/public/components/index.ts
+++ b/src/plugins/telemetry/public/components/index.ts
@@ -17,6 +17,4 @@
* under the License.
*/
-export { OptInExampleFlyout } from './opt_in_example_flyout';
-export { TelemetryManagementSection } from './telemetry_management_section';
export { OptedInNoticeBanner } from './opted_in_notice_banner';
diff --git a/src/plugins/telemetry/public/index.ts b/src/plugins/telemetry/public/index.ts
index 2f86d7749bb9b..665c89ba2bffa 100644
--- a/src/plugins/telemetry/public/index.ts
+++ b/src/plugins/telemetry/public/index.ts
@@ -17,9 +17,10 @@
* under the License.
*/
-import { TelemetryPlugin } from './plugin';
+import { PluginInitializerContext } from 'kibana/public';
+import { TelemetryPlugin, TelemetryPluginConfig } from './plugin';
export { TelemetryPluginStart, TelemetryPluginSetup } from './plugin';
-export function plugin() {
- return new TelemetryPlugin();
+export function plugin(initializerContext: PluginInitializerContext) {
+ return new TelemetryPlugin(initializerContext);
}
diff --git a/src/plugins/telemetry/public/mocks.ts b/src/plugins/telemetry/public/mocks.ts
index 93dc13c327509..4e0f02242961a 100644
--- a/src/plugins/telemetry/public/mocks.ts
+++ b/src/plugins/telemetry/public/mocks.ts
@@ -23,8 +23,6 @@ import { overlayServiceMock } from '../../../core/public/overlays/overlay_servic
import { httpServiceMock } from '../../../core/public/http/http_service.mock';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { notificationServiceMock } from '../../../core/public/notifications/notifications_service.mock';
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { injectedMetadataServiceMock } from '../../../core/public/injected_metadata/injected_metadata_service.mock';
import { TelemetryService } from './services/telemetry_service';
import { TelemetryNotifications } from './services/telemetry_notifications/telemetry_notifications';
import { TelemetryPluginStart } from './plugin';
@@ -32,23 +30,19 @@ import { TelemetryPluginStart } from './plugin';
export function mockTelemetryService({
reportOptInStatusChange,
}: { reportOptInStatusChange?: boolean } = {}) {
- const injectedMetadata = injectedMetadataServiceMock.createStartContract();
- injectedMetadata.getInjectedVar.mockImplementation((key: string) => {
- switch (key) {
- case 'telemetryNotifyUserAboutOptInDefault':
- return true;
- case 'allowChangingOptInStatus':
- return true;
- case 'telemetryOptedIn':
- return true;
- default: {
- throw Error(`Unhandled getInjectedVar key "${key}".`);
- }
- }
- });
+ const config = {
+ enabled: true,
+ url: 'http://localhost',
+ optInStatusUrl: 'http://localhost',
+ sendUsageFrom: 'browser' as const,
+ optIn: true,
+ banner: true,
+ allowChangingOptInStatus: true,
+ telemetryNotifyUserAboutOptInDefault: true,
+ };
return new TelemetryService({
- injectedMetadata,
+ config,
http: httpServiceMock.createStartContract(),
notifications: notificationServiceMock.createStartContract(),
reportOptInStatusChange,
diff --git a/src/plugins/telemetry/public/plugin.ts b/src/plugins/telemetry/public/plugin.ts
index 9cfb4ca1ec395..86679227059e6 100644
--- a/src/plugins/telemetry/public/plugin.ts
+++ b/src/plugins/telemetry/public/plugin.ts
@@ -16,9 +16,27 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Plugin, CoreStart, CoreSetup, HttpStart } from '../../../core/public';
+
+import {
+ Plugin,
+ CoreStart,
+ CoreSetup,
+ HttpStart,
+ PluginInitializerContext,
+ SavedObjectsClientContract,
+} from '../../../core/public';
import { TelemetrySender, TelemetryService, TelemetryNotifications } from './services';
+import {
+ TelemetrySavedObjectAttributes,
+ TelemetrySavedObject,
+} from '../common/telemetry_config/types';
+import {
+ getTelemetryAllowChangingOptInStatus,
+ getTelemetryOptIn,
+ getTelemetrySendUsageFrom,
+} from '../common/telemetry_config';
+import { getNotifyUserAboutOptInDefault } from '../common/telemetry_config/get_telemetry_notify_user_about_optin_default';
export interface TelemetryPluginSetup {
telemetryService: TelemetryService;
@@ -29,17 +47,32 @@ export interface TelemetryPluginStart {
telemetryNotifications: TelemetryNotifications;
}
+export interface TelemetryPluginConfig {
+ enabled: boolean;
+ url: string;
+ banner: boolean;
+ allowChangingOptInStatus: boolean;
+ optIn: boolean | null;
+ optInStatusUrl: string;
+ sendUsageFrom: 'browser' | 'server';
+ telemetryNotifyUserAboutOptInDefault?: boolean;
+}
+
export class TelemetryPlugin implements Plugin {
+ private readonly currentKibanaVersion: string;
+ private readonly config: TelemetryPluginConfig;
private telemetrySender?: TelemetrySender;
private telemetryNotifications?: TelemetryNotifications;
private telemetryService?: TelemetryService;
- public setup({ http, injectedMetadata, notifications }: CoreSetup): TelemetryPluginSetup {
- this.telemetryService = new TelemetryService({
- http,
- injectedMetadata,
- notifications,
- });
+ constructor(initializerContext: PluginInitializerContext) {
+ this.currentKibanaVersion = initializerContext.env.packageInfo.version;
+ this.config = initializerContext.config.get();
+ }
+
+ public setup({ http, notifications }: CoreSetup): TelemetryPluginSetup {
+ const config = this.config;
+ this.telemetryService = new TelemetryService({ config, http, notifications });
this.telemetrySender = new TelemetrySender(this.telemetryService);
@@ -48,24 +81,29 @@ export class TelemetryPlugin implements Plugin {
+ application.currentAppId$.subscribe(async () => {
const isUnauthenticated = this.getIsUnauthenticated(http);
if (isUnauthenticated) {
return;
}
+ // Update the telemetry config based as a mix of the config files and saved objects
+ const telemetrySavedObject = await this.getTelemetrySavedObject(savedObjects.client);
+ const updatedConfig = await this.updateConfigsBasedOnSavedObjects(telemetrySavedObject);
+ this.telemetryService!.config = updatedConfig;
+
+ const telemetryBanner = updatedConfig.banner;
+
this.maybeStartTelemetryPoller();
if (telemetryBanner) {
this.maybeShowOptedInNotificationBanner();
@@ -111,4 +149,66 @@ export class TelemetryPlugin implements Plugin {
+ const configTelemetrySendUsageFrom = this.config.sendUsageFrom;
+ const configTelemetryOptIn = this.config.optIn as boolean;
+ const configTelemetryAllowChangingOptInStatus = this.config.allowChangingOptInStatus;
+
+ const currentKibanaVersion = this.currentKibanaVersion;
+
+ const allowChangingOptInStatus = getTelemetryAllowChangingOptInStatus({
+ configTelemetryAllowChangingOptInStatus,
+ telemetrySavedObject,
+ });
+
+ const optIn = getTelemetryOptIn({
+ configTelemetryOptIn,
+ allowChangingOptInStatus,
+ telemetrySavedObject,
+ currentKibanaVersion,
+ });
+
+ const sendUsageFrom = getTelemetrySendUsageFrom({
+ configTelemetrySendUsageFrom,
+ telemetrySavedObject,
+ });
+
+ const telemetryNotifyUserAboutOptInDefault = getNotifyUserAboutOptInDefault({
+ telemetrySavedObject,
+ allowChangingOptInStatus,
+ configTelemetryOptIn,
+ telemetryOptedIn: optIn,
+ });
+
+ return {
+ ...this.config,
+ optIn,
+ sendUsageFrom,
+ telemetryNotifyUserAboutOptInDefault,
+ };
+ }
+
+ private async getTelemetrySavedObject(savedObjectsClient: SavedObjectsClientContract) {
+ try {
+ const { attributes } = await savedObjectsClient.get(
+ 'telemetry',
+ 'telemetry'
+ );
+ return attributes;
+ } catch (error) {
+ const errorCode = error[Symbol('SavedObjectsClientErrorCode')];
+ if (errorCode === 'SavedObjectsClient/notFound') {
+ return null;
+ }
+
+ if (errorCode === 'SavedObjectsClient/forbidden') {
+ return false;
+ }
+
+ throw error;
+ }
+ }
}
diff --git a/src/plugins/telemetry/public/services/telemetry_service.ts b/src/plugins/telemetry/public/services/telemetry_service.ts
index cb91451bd8ef4..cac4e3fdf5f50 100644
--- a/src/plugins/telemetry/public/services/telemetry_service.ts
+++ b/src/plugins/telemetry/public/services/telemetry_service.ts
@@ -20,62 +20,75 @@
import moment from 'moment';
import { i18n } from '@kbn/i18n';
import { CoreStart } from 'kibana/public';
+import { TelemetryPluginConfig } from '../plugin';
interface TelemetryServiceConstructor {
+ config: TelemetryPluginConfig;
http: CoreStart['http'];
- injectedMetadata: CoreStart['injectedMetadata'];
notifications: CoreStart['notifications'];
reportOptInStatusChange?: boolean;
}
export class TelemetryService {
private readonly http: CoreStart['http'];
- private readonly injectedMetadata: CoreStart['injectedMetadata'];
private readonly reportOptInStatusChange: boolean;
private readonly notifications: CoreStart['notifications'];
- private isOptedIn: boolean | null;
- private userHasSeenOptedInNotice: boolean;
+ private readonly defaultConfig: TelemetryPluginConfig;
+ private updatedConfig?: TelemetryPluginConfig;
constructor({
+ config,
http,
- injectedMetadata,
notifications,
reportOptInStatusChange = true,
}: TelemetryServiceConstructor) {
- const isOptedIn = injectedMetadata.getInjectedVar('telemetryOptedIn') as boolean | null;
- const userHasSeenOptedInNotice = injectedMetadata.getInjectedVar(
- 'telemetryNotifyUserAboutOptInDefault'
- ) as boolean;
+ this.defaultConfig = config;
this.reportOptInStatusChange = reportOptInStatusChange;
- this.injectedMetadata = injectedMetadata;
this.notifications = notifications;
this.http = http;
+ }
+
+ public set config(updatedConfig: TelemetryPluginConfig) {
+ this.updatedConfig = updatedConfig;
+ }
+
+ public get config() {
+ return { ...this.defaultConfig, ...this.updatedConfig };
+ }
+
+ public get isOptedIn() {
+ return this.config.optIn;
+ }
+
+ public set isOptedIn(optIn) {
+ this.config = { ...this.config, optIn };
+ }
+
+ public get userHasSeenOptedInNotice() {
+ return this.config.telemetryNotifyUserAboutOptInDefault;
+ }
- this.isOptedIn = isOptedIn;
- this.userHasSeenOptedInNotice = userHasSeenOptedInNotice;
+ public set userHasSeenOptedInNotice(telemetryNotifyUserAboutOptInDefault) {
+ this.config = { ...this.config, telemetryNotifyUserAboutOptInDefault };
}
public getCanChangeOptInStatus = () => {
- const allowChangingOptInStatus = this.injectedMetadata.getInjectedVar(
- 'allowChangingOptInStatus'
- ) as boolean;
+ const allowChangingOptInStatus = this.config.allowChangingOptInStatus;
return allowChangingOptInStatus;
};
public getOptInStatusUrl = () => {
- const telemetryOptInStatusUrl = this.injectedMetadata.getInjectedVar(
- 'telemetryOptInStatusUrl'
- ) as string;
+ const telemetryOptInStatusUrl = this.config.optInStatusUrl;
return telemetryOptInStatusUrl;
};
public getTelemetryUrl = () => {
- const telemetryUrl = this.injectedMetadata.getInjectedVar('telemetryUrl') as string;
+ const telemetryUrl = this.config.url;
return telemetryUrl;
};
public getUserHasSeenOptedInNotice = () => {
- return this.userHasSeenOptedInNotice;
+ return this.config.telemetryNotifyUserAboutOptInDefault || false;
};
public getIsOptedIn = () => {
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/application_usage/index.test.ts b/src/plugins/telemetry/server/collectors/application_usage/index.test.ts
similarity index 91%
rename from src/legacy/core_plugins/telemetry/server/collectors/application_usage/index.test.ts
rename to src/plugins/telemetry/server/collectors/application_usage/index.test.ts
index 1a64100bda692..5a8fa71363ba7 100644
--- a/src/legacy/core_plugins/telemetry/server/collectors/application_usage/index.test.ts
+++ b/src/plugins/telemetry/server/collectors/application_usage/index.test.ts
@@ -17,10 +17,10 @@
* under the License.
*/
-import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server';
-import { savedObjectsRepositoryMock } from '../../../../../../core/server/mocks';
+import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/server';
+import { savedObjectsRepositoryMock } from '../../../../../core/server/mocks';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { CollectorOptions } from '../../../../../../plugins/usage_collection/server/collector/collector';
+import { CollectorOptions } from '../../../../../plugins/usage_collection/server/collector/collector';
import { registerApplicationUsageCollector } from './';
import {
@@ -40,9 +40,12 @@ describe('telemetry_application_usage', () => {
} as any;
const getUsageCollector = jest.fn();
+ const registerType = jest.fn();
const callCluster = jest.fn();
- beforeAll(() => registerApplicationUsageCollector(usageCollectionMock, getUsageCollector));
+ beforeAll(() =>
+ registerApplicationUsageCollector(usageCollectionMock, registerType, getUsageCollector)
+ );
afterAll(() => jest.clearAllTimers());
test('registered collector is set', () => {
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/application_usage/index.ts b/src/plugins/telemetry/server/collectors/application_usage/index.ts
similarity index 100%
rename from src/legacy/core_plugins/telemetry/server/collectors/application_usage/index.ts
rename to src/plugins/telemetry/server/collectors/application_usage/index.ts
diff --git a/src/plugins/telemetry/server/collectors/application_usage/saved_objects_types.ts b/src/plugins/telemetry/server/collectors/application_usage/saved_objects_types.ts
new file mode 100644
index 0000000000000..9f997ab7b5df3
--- /dev/null
+++ b/src/plugins/telemetry/server/collectors/application_usage/saved_objects_types.ts
@@ -0,0 +1,59 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { SavedObjectAttributes, SavedObjectsServiceSetup } from 'kibana/server';
+
+export interface ApplicationUsageTotal extends SavedObjectAttributes {
+ appId: string;
+ minutesOnScreen: number;
+ numberOfClicks: number;
+}
+
+export interface ApplicationUsageTransactional extends ApplicationUsageTotal {
+ timestamp: string;
+}
+
+export function registerMappings(registerType: SavedObjectsServiceSetup['registerType']) {
+ registerType({
+ name: 'application_usage_totals',
+ hidden: false,
+ namespaceAgnostic: true,
+ mappings: {
+ properties: {
+ appId: { type: 'keyword' },
+ numberOfClicks: { type: 'long' },
+ minutesOnScreen: { type: 'float' },
+ },
+ },
+ });
+
+ registerType({
+ name: 'application_usage_transactional',
+ hidden: false,
+ namespaceAgnostic: true,
+ mappings: {
+ properties: {
+ timestamp: { type: 'date' },
+ appId: { type: 'keyword' },
+ numberOfClicks: { type: 'long' },
+ minutesOnScreen: { type: 'float' },
+ },
+ },
+ });
+}
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/application_usage/telemetry_application_usage_collector.ts b/src/plugins/telemetry/server/collectors/application_usage/telemetry_application_usage_collector.ts
similarity index 94%
rename from src/legacy/core_plugins/telemetry/server/collectors/application_usage/telemetry_application_usage_collector.ts
rename to src/plugins/telemetry/server/collectors/application_usage/telemetry_application_usage_collector.ts
index 5c862686a37d9..f52687038bbbc 100644
--- a/src/legacy/core_plugins/telemetry/server/collectors/application_usage/telemetry_application_usage_collector.ts
+++ b/src/plugins/telemetry/server/collectors/application_usage/telemetry_application_usage_collector.ts
@@ -18,10 +18,15 @@
*/
import moment from 'moment';
+import { ISavedObjectsRepository, SavedObjectsServiceSetup } from 'kibana/server';
+import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { APPLICATION_USAGE_TYPE } from '../../../common/constants';
-import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server';
-import { ISavedObjectsRepository, SavedObjectAttributes } from '../../../../../../core/server';
import { findAll } from '../find_all';
+import {
+ ApplicationUsageTotal,
+ ApplicationUsageTransactional,
+ registerMappings,
+} from './saved_objects_types';
/**
* Roll indices every 24h
@@ -36,16 +41,6 @@ export const ROLL_INDICES_START = 5 * 60 * 1000;
export const SAVED_OBJECTS_TOTAL_TYPE = 'application_usage_totals';
export const SAVED_OBJECTS_TRANSACTIONAL_TYPE = 'application_usage_transactional';
-interface ApplicationUsageTotal extends SavedObjectAttributes {
- appId: string;
- minutesOnScreen: number;
- numberOfClicks: number;
-}
-
-interface ApplicationUsageTransactional extends ApplicationUsageTotal {
- timestamp: string;
-}
-
interface ApplicationUsageTelemetryReport {
[appId: string]: {
clicks_total: number;
@@ -61,8 +56,11 @@ interface ApplicationUsageTelemetryReport {
export function registerApplicationUsageCollector(
usageCollection: UsageCollectionSetup,
+ registerType: SavedObjectsServiceSetup['registerType'],
getSavedObjectsClient: () => ISavedObjectsRepository | undefined
) {
+ registerMappings(registerType);
+
const collector = usageCollection.makeUsageCollector({
type: APPLICATION_USAGE_TYPE,
isReady: () => typeof getSavedObjectsClient() !== 'undefined',
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/find_all.test.ts b/src/plugins/telemetry/server/collectors/find_all.test.ts
similarity index 96%
rename from src/legacy/core_plugins/telemetry/server/collectors/find_all.test.ts
rename to src/plugins/telemetry/server/collectors/find_all.test.ts
index 012cda395bc6c..a62c74c0c0838 100644
--- a/src/legacy/core_plugins/telemetry/server/collectors/find_all.test.ts
+++ b/src/plugins/telemetry/server/collectors/find_all.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { savedObjectsRepositoryMock } from '../../../../../core/server/mocks';
+import { savedObjectsRepositoryMock } from '../../../../core/server/mocks';
import { findAll } from './find_all';
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/find_all.ts b/src/plugins/telemetry/server/collectors/find_all.ts
similarity index 100%
rename from src/legacy/core_plugins/telemetry/server/collectors/find_all.ts
rename to src/plugins/telemetry/server/collectors/find_all.ts
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/index.ts b/src/plugins/telemetry/server/collectors/index.ts
similarity index 90%
rename from src/legacy/core_plugins/telemetry/server/collectors/index.ts
rename to src/plugins/telemetry/server/collectors/index.ts
index 6cb7a38b6414f..6eeda080bb3ab 100644
--- a/src/legacy/core_plugins/telemetry/server/collectors/index.ts
+++ b/src/plugins/telemetry/server/collectors/index.ts
@@ -17,10 +17,8 @@
* under the License.
*/
-export { encryptTelemetry } from './encryption';
export { registerTelemetryUsageCollector } from './usage';
export { registerUiMetricUsageCollector } from './ui_metric';
-export { registerLocalizationUsageCollector } from './localization';
export { registerTelemetryPluginUsageCollector } from './telemetry_plugin';
export { registerManagementUsageCollector } from './management';
export { registerApplicationUsageCollector } from './application_usage';
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/management/index.ts b/src/plugins/telemetry/server/collectors/management/index.ts
similarity index 100%
rename from src/legacy/core_plugins/telemetry/server/collectors/management/index.ts
rename to src/plugins/telemetry/server/collectors/management/index.ts
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/management/telemetry_management_collector.ts b/src/plugins/telemetry/server/collectors/management/telemetry_management_collector.ts
similarity index 73%
rename from src/legacy/core_plugins/telemetry/server/collectors/management/telemetry_management_collector.ts
rename to src/plugins/telemetry/server/collectors/management/telemetry_management_collector.ts
index 481b1e9af2a79..7dc4ca64e6bc3 100644
--- a/src/legacy/core_plugins/telemetry/server/collectors/management/telemetry_management_collector.ts
+++ b/src/plugins/telemetry/server/collectors/management/telemetry_management_collector.ts
@@ -17,11 +17,10 @@
* under the License.
*/
-import { Server } from 'hapi';
import { size } from 'lodash';
+import { IUiSettingsClient } from 'kibana/server';
+import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { KIBANA_STACK_MANAGEMENT_STATS_TYPE } from '../../../common/constants';
-import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server';
-import { SavedObjectsClient } from '../../../../../../core/server';
export type UsageStats = Record;
@@ -30,12 +29,12 @@ export async function getTranslationCount(loader: any, locale: string): Promise<
return size(translations.messages);
}
-export function createCollectorFetch(server: Server) {
- return async function fetchUsageStats(): Promise {
- const internalRepo = server.newPlatform.start.core.savedObjects.createInternalRepository();
- const uiSettingsClient = server.newPlatform.start.core.uiSettings.asScopedToClient(
- new SavedObjectsClient(internalRepo)
- );
+export function createCollectorFetch(getUiSettingsClient: () => IUiSettingsClient | undefined) {
+ return async function fetchUsageStats(): Promise {
+ const uiSettingsClient = getUiSettingsClient();
+ if (!uiSettingsClient) {
+ return;
+ }
const user = await uiSettingsClient.getUserProvided();
const modifiedEntries = Object.keys(user)
@@ -51,12 +50,12 @@ export function createCollectorFetch(server: Server) {
export function registerManagementUsageCollector(
usageCollection: UsageCollectionSetup,
- server: any
+ getUiSettingsClient: () => IUiSettingsClient | undefined
) {
const collector = usageCollection.makeUsageCollector({
type: KIBANA_STACK_MANAGEMENT_STATS_TYPE,
- isReady: () => true,
- fetch: createCollectorFetch(server),
+ isReady: () => typeof getUiSettingsClient() !== 'undefined',
+ fetch: createCollectorFetch(getUiSettingsClient),
});
usageCollection.registerCollector(collector);
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/index.ts b/src/plugins/telemetry/server/collectors/telemetry_plugin/index.ts
similarity index 100%
rename from src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/index.ts
rename to src/plugins/telemetry/server/collectors/telemetry_plugin/index.ts
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts b/src/plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts
similarity index 62%
rename from src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts
rename to src/plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts
index 5e25538cbad80..ab90935266d69 100644
--- a/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts
+++ b/src/plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts
@@ -17,10 +17,14 @@
* under the License.
*/
+import { Observable } from 'rxjs';
+import { take } from 'rxjs/operators';
+import { ISavedObjectsRepository, SavedObjectsClient } from '../../../../../core/server';
import { TELEMETRY_STATS_TYPE } from '../../../common/constants';
import { getTelemetrySavedObject, TelemetrySavedObject } from '../../telemetry_repository';
-import { getTelemetryOptIn, getTelemetrySendUsageFrom } from '../../telemetry_config';
-import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server';
+import { getTelemetryOptIn, getTelemetrySendUsageFrom } from '../../../common/telemetry_config';
+import { UsageCollectionSetup } from '../../../../usage_collection/server';
+import { TelemetryConfigType } from '../../config';
export interface TelemetryUsageStats {
opt_in_status?: boolean | null;
@@ -28,21 +32,31 @@ export interface TelemetryUsageStats {
last_reported?: number;
}
-export function createCollectorFetch(server: any) {
+export interface TelemetryPluginUsageCollectorOptions {
+ currentKibanaVersion: string;
+ config$: Observable;
+ getSavedObjectsClient: () => ISavedObjectsRepository | undefined;
+}
+
+export function createCollectorFetch({
+ currentKibanaVersion,
+ config$,
+ getSavedObjectsClient,
+}: TelemetryPluginUsageCollectorOptions) {
return async function fetchUsageStats(): Promise {
- const config = server.config();
- const configTelemetrySendUsageFrom = config.get('telemetry.sendUsageFrom');
- const allowChangingOptInStatus = config.get('telemetry.allowChangingOptInStatus');
- const configTelemetryOptIn = config.get('telemetry.optIn');
- const currentKibanaVersion = config.get('pkg.version');
+ const { sendUsageFrom, allowChangingOptInStatus, optIn = null } = await config$
+ .pipe(take(1))
+ .toPromise();
+ const configTelemetrySendUsageFrom = sendUsageFrom;
+ const configTelemetryOptIn = optIn;
let telemetrySavedObject: TelemetrySavedObject = {};
try {
- const { getSavedObjectsRepository } = server.savedObjects;
- const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin');
- const internalRepository = getSavedObjectsRepository(callWithInternalUser);
- telemetrySavedObject = await getTelemetrySavedObject(internalRepository);
+ const internalRepository = getSavedObjectsClient()!;
+ telemetrySavedObject = await getTelemetrySavedObject(
+ new SavedObjectsClient(internalRepository)
+ );
} catch (err) {
// no-op
}
@@ -65,12 +79,12 @@ export function createCollectorFetch(server: any) {
export function registerTelemetryPluginUsageCollector(
usageCollection: UsageCollectionSetup,
- server: any
+ options: TelemetryPluginUsageCollectorOptions
) {
const collector = usageCollection.makeUsageCollector({
type: TELEMETRY_STATS_TYPE,
- isReady: () => true,
- fetch: createCollectorFetch(server),
+ isReady: () => typeof options.getSavedObjectsClient() !== 'undefined',
+ fetch: createCollectorFetch(options),
});
usageCollection.registerCollector(collector);
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.test.ts b/src/plugins/telemetry/server/collectors/ui_metric/index.test.ts
similarity index 87%
rename from src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.test.ts
rename to src/plugins/telemetry/server/collectors/ui_metric/index.test.ts
index ddb58a7d09bbd..d6667a6384a1f 100644
--- a/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.test.ts
+++ b/src/plugins/telemetry/server/collectors/ui_metric/index.test.ts
@@ -17,10 +17,10 @@
* under the License.
*/
-import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server';
-import { savedObjectsRepositoryMock } from '../../../../../../core/server/mocks';
+import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/server';
+import { savedObjectsRepositoryMock } from '../../../../../core/server/mocks';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-import { CollectorOptions } from '../../../../../../plugins/usage_collection/server/collector/collector';
+import { CollectorOptions } from '../../../../../plugins/usage_collection/server/collector/collector';
import { registerUiMetricUsageCollector } from './';
@@ -33,9 +33,12 @@ describe('telemetry_ui_metric', () => {
} as any;
const getUsageCollector = jest.fn();
+ const registerType = jest.fn();
const callCluster = jest.fn();
- beforeAll(() => registerUiMetricUsageCollector(usageCollectionMock, getUsageCollector));
+ beforeAll(() =>
+ registerUiMetricUsageCollector(usageCollectionMock, registerType, getUsageCollector)
+ );
test('registered collector is set', () => {
expect(collector).not.toBeUndefined();
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.ts b/src/plugins/telemetry/server/collectors/ui_metric/index.ts
similarity index 100%
rename from src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.ts
rename to src/plugins/telemetry/server/collectors/ui_metric/index.ts
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts b/src/plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts
similarity index 82%
rename from src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts
rename to src/plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts
index a7b6850b0b20a..3f6e1836cac7d 100644
--- a/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts
+++ b/src/plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts
@@ -17,9 +17,13 @@
* under the License.
*/
-import { ISavedObjectsRepository, SavedObjectAttributes } from 'kibana/server';
+import {
+ ISavedObjectsRepository,
+ SavedObjectAttributes,
+ SavedObjectsServiceSetup,
+} from 'kibana/server';
+import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { UI_METRIC_USAGE_TYPE } from '../../../common/constants';
-import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server';
import { findAll } from '../find_all';
interface UIMetricsSavedObjects extends SavedObjectAttributes {
@@ -28,8 +32,22 @@ interface UIMetricsSavedObjects extends SavedObjectAttributes {
export function registerUiMetricUsageCollector(
usageCollection: UsageCollectionSetup,
+ registerType: SavedObjectsServiceSetup['registerType'],
getSavedObjectsClient: () => ISavedObjectsRepository | undefined
) {
+ registerType({
+ name: 'ui-metric',
+ hidden: false,
+ namespaceAgnostic: true,
+ mappings: {
+ properties: {
+ count: {
+ type: 'integer',
+ },
+ },
+ },
+ });
+
const collector = usageCollection.makeUsageCollector({
type: UI_METRIC_USAGE_TYPE,
fetch: async () => {
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/usage/ensure_deep_object.test.ts b/src/plugins/telemetry/server/collectors/usage/ensure_deep_object.test.ts
similarity index 100%
rename from src/legacy/core_plugins/telemetry/server/collectors/usage/ensure_deep_object.test.ts
rename to src/plugins/telemetry/server/collectors/usage/ensure_deep_object.test.ts
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/usage/ensure_deep_object.ts b/src/plugins/telemetry/server/collectors/usage/ensure_deep_object.ts
similarity index 100%
rename from src/legacy/core_plugins/telemetry/server/collectors/usage/ensure_deep_object.ts
rename to src/plugins/telemetry/server/collectors/usage/ensure_deep_object.ts
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/usage/index.ts b/src/plugins/telemetry/server/collectors/usage/index.ts
similarity index 100%
rename from src/legacy/core_plugins/telemetry/server/collectors/usage/index.ts
rename to src/plugins/telemetry/server/collectors/usage/index.ts
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts b/src/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts
similarity index 89%
rename from src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts
rename to src/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts
index 78685cd6becc8..f44603f4f19f4 100644
--- a/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts
+++ b/src/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts
@@ -18,7 +18,6 @@
*/
import { writeFileSync, unlinkSync } from 'fs';
-import { Server } from 'hapi';
import { resolve } from 'path';
import { tmpdir } from 'os';
import {
@@ -32,20 +31,6 @@ const mockUsageCollector = () => ({
makeUsageCollector: jest.fn().mockImplementationOnce((arg: object) => arg),
});
-const serverWithConfig = (configPath: string): Server => {
- return {
- config: () => ({
- get: (key: string) => {
- if (key !== 'telemetry.config' && key !== 'xpack.xpack_main.telemetry.config') {
- throw new Error('Expected `telemetry.config`');
- }
-
- return configPath;
- },
- }),
- } as Server;
-};
-
describe('telemetry_usage_collector', () => {
const tempDir = tmpdir();
const tempFiles = {
@@ -129,11 +114,13 @@ describe('telemetry_usage_collector', () => {
// note: it uses the file's path to get the directory, then looks for 'telemetry.yml'
// exclusively, which is indirectly tested by passing it the wrong "file" in the same
// dir
- const server: Server = serverWithConfig(tempFiles.unreadable);
// the `makeUsageCollector` is mocked above to return the argument passed to it
const usageCollector = mockUsageCollector() as any;
- const collectorOptions = createTelemetryUsageCollector(usageCollector, server);
+ const collectorOptions = createTelemetryUsageCollector(
+ usageCollector,
+ () => tempFiles.unreadable
+ );
expect(collectorOptions.type).toBe('static_telemetry');
expect(await collectorOptions.fetch({} as any)).toEqual(expectedObject); // Sending any as the callCluster client because it's not needed in this collector but TS requires it when calling it.
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts b/src/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts
similarity index 87%
rename from src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts
rename to src/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts
index 6919b6959aa8c..3daae90106e9e 100644
--- a/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts
+++ b/src/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts
@@ -18,13 +18,16 @@
*/
import { accessSync, constants, readFileSync, statSync } from 'fs';
-import { Server } from 'hapi';
import { safeLoad } from 'js-yaml';
import { dirname, join } from 'path';
+import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
+
+// eslint-disable-next-line @kbn/eslint/no-restricted-paths
+import { getConfigPath } from '../../../../../core/server/path';
+
// look for telemetry.yml in the same places we expect kibana.yml
import { ensureDeepObject } from './ensure_deep_object';
-import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server';
/**
* The maximum file size before we ignore it (note: this limit is arbitrary).
@@ -77,24 +80,20 @@ export async function readTelemetryFile(path: string): Promise
+ ),
+ };
- return (
-
- );
- }
-);
+ return (
+
+ );
+});
Markdown.displayName = 'Markdown';
diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts b/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts
index e25fb4374bb14..155f63145ca95 100644
--- a/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts
+++ b/x-pack/legacy/plugins/siem/public/components/navigation/breadcrumbs/index.ts
@@ -107,7 +107,22 @@ export const getBreadcrumbsForRoute = (
];
}
if (isCaseRoutes(spyState) && object.navTabs) {
- return [...siemRootBreadcrumb, ...getCaseDetailsBreadcrumbs(spyState)];
+ const tempNav: SearchNavTab = { urlKey: 'case', isDetailPage: false };
+ let urlStateKeys = [getOr(tempNav, spyState.pageName, object.navTabs)];
+ if (spyState.tabName != null) {
+ urlStateKeys = [...urlStateKeys, getOr(tempNav, spyState.tabName, object.navTabs)];
+ }
+
+ return [
+ ...siemRootBreadcrumb,
+ ...getCaseDetailsBreadcrumbs(
+ spyState,
+ urlStateKeys.reduce(
+ (acc: string[], item: SearchNavTab) => [...acc, getSearch(item, object)],
+ []
+ )
+ ),
+ ];
}
if (
spyState != null &&
diff --git a/x-pack/legacy/plugins/siem/public/components/news_feed/news_feed.tsx b/x-pack/legacy/plugins/siem/public/components/news_feed/news_feed.tsx
index 98eea1eaa6454..cd356212b4400 100644
--- a/x-pack/legacy/plugins/siem/public/components/news_feed/news_feed.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/news_feed/news_feed.tsx
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiSpacer } from '@elastic/eui';
import React from 'react';
import { LoadingPlaceholders } from '../page/overview/loading_placeholders';
@@ -30,12 +29,7 @@ const NewsFeedComponent: React.FC = ({ news }) => (
) : news.length === 0 ? (
) : (
- news.map((n: NewsItem) => (
-
-
-
-
- ))
+ news.map((n: NewsItem) => )
)}
>
);
diff --git a/x-pack/legacy/plugins/siem/public/components/news_feed/post/index.tsx b/x-pack/legacy/plugins/siem/public/components/news_feed/post/index.tsx
index cb2542a497f08..9cab78c9f20b1 100644
--- a/x-pack/legacy/plugins/siem/public/components/news_feed/post/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/news_feed/post/index.tsx
@@ -45,6 +45,7 @@ export const Post = React.memo<{ newsItem: NewsItem }>(({ newsItem }) => {
{description}
+
diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx
index 6d00edf28a88f..6c2cd21d808b7 100644
--- a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx
@@ -52,7 +52,10 @@ interface OwnProps {
}
export type OpenTimelineOwnProps = OwnProps &
- Pick &
+ Pick<
+ OpenTimelineProps,
+ 'defaultPageSize' | 'title' | 'importCompleteToggle' | 'setImportCompleteToggle'
+ > &
PropsFromRedux;
/** Returns a collection of selected timeline ids */
@@ -74,7 +77,9 @@ export const StatefulOpenTimelineComponent = React.memo(
defaultPageSize,
hideActions = [],
isModal = false,
+ importCompleteToggle,
onOpenTimeline,
+ setImportCompleteToggle,
timeline,
title,
updateTimeline,
@@ -264,6 +269,7 @@ export const StatefulOpenTimelineComponent = React.memo(
defaultPageSize={defaultPageSize}
isLoading={loading}
itemIdToExpandedNotesRowMap={itemIdToExpandedNotesRowMap}
+ importCompleteToggle={importCompleteToggle}
onAddTimelinesToFavorites={undefined}
onDeleteSelected={onDeleteSelected}
onlyFavorites={onlyFavorites}
@@ -278,6 +284,7 @@ export const StatefulOpenTimelineComponent = React.memo(
query={search}
refetch={refetch}
searchResults={timelines}
+ setImportCompleteToggle={setImportCompleteToggle}
selectedItems={selectedItems}
sortDirection={sortDirection}
sortField={sortField}
diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx
index b1b100349eb86..8b3da4427a362 100644
--- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx
@@ -12,8 +12,10 @@ import { OpenTimelineProps, OpenTimelineResult } from './types';
import { SearchRow } from './search_row';
import { TimelinesTable } from './timelines_table';
import { TitleRow } from './title_row';
-
+import { ImportDataModal } from '../import_data_modal';
import * as i18n from './translations';
+import { importTimelines } from '../../containers/timeline/all/api';
+
import {
UtilityBarGroup,
UtilityBarText,
@@ -31,6 +33,7 @@ export const OpenTimeline = React.memo(
defaultPageSize,
isLoading,
itemIdToExpandedNotesRowMap,
+ importCompleteToggle,
onAddTimelinesToFavorites,
onDeleteSelected,
onlyFavorites,
@@ -47,6 +50,7 @@ export const OpenTimeline = React.memo(
searchResults,
selectedItems,
sortDirection,
+ setImportCompleteToggle,
sortField,
title,
totalSearchResultsCount,
@@ -93,9 +97,25 @@ export const OpenTimeline = React.memo(
);
const onRefreshBtnClick = useCallback(() => {
- if (typeof refetch === 'function') refetch();
+ if (refetch != null) {
+ refetch();
+ }
}, [refetch]);
+ const handleCloseModal = useCallback(() => {
+ if (setImportCompleteToggle != null) {
+ setImportCompleteToggle(false);
+ }
+ }, [setImportCompleteToggle]);
+ const handleComplete = useCallback(() => {
+ if (setImportCompleteToggle != null) {
+ setImportCompleteToggle(false);
+ }
+ if (refetch != null) {
+ refetch();
+ }
+ }, [setImportCompleteToggle, refetch]);
+
return (
<>
(
onComplete={onCompleteEditTimelineAction}
title={actionItem?.title ?? i18n.UNTITLED_TIMELINE}
/>
+
defaultMessage:
'Successfully exported {totalTimelines, plural, =0 {all timelines} =1 {{totalTimelines} timeline} other {{totalTimelines} timelines}}',
});
+
+export const IMPORT_TIMELINE_BTN_TITLE = i18n.translate(
+ 'xpack.siem.timelines.components.importTimelineModal.importTimelineTitle',
+ {
+ defaultMessage: 'Import timeline',
+ }
+);
+
+export const SELECT_TIMELINE = i18n.translate(
+ 'xpack.siem.timelines.components.importTimelineModal.selectTimelineDescription',
+ {
+ defaultMessage: 'Select a SIEM timeline (as exported from the Timeline view) to import',
+ }
+);
+
+export const INITIAL_PROMPT_TEXT = i18n.translate(
+ 'xpack.siem.timelines.components.importTimelineModal.initialPromptTextDescription',
+ {
+ defaultMessage: 'Select or drag and drop a valid timelines_export.ndjson file',
+ }
+);
+
+export const OVERWRITE_WITH_SAME_NAME = i18n.translate(
+ 'xpack.siem.timelines.components.importTimelineModal.overwriteDescription',
+ {
+ defaultMessage: 'Automatically overwrite saved objects with the same timeline ID',
+ }
+);
+
+export const SUCCESSFULLY_IMPORTED_TIMELINES = (totalCount: number) =>
+ i18n.translate(
+ 'xpack.siem.timelines.components.importTimelineModal.successfullyImportedTimelinesTitle',
+ {
+ values: { totalCount },
+ defaultMessage:
+ 'Successfully imported {totalCount} {totalCount, plural, =1 {timeline} other {timelines}}',
+ }
+ );
+
+export const IMPORT_FAILED = i18n.translate(
+ 'xpack.siem.timelines.components.importTimelineModal.importFailedTitle',
+ {
+ defaultMessage: 'Failed to import timelines',
+ }
+);
+
+export const IMPORT_TIMELINE = i18n.translate(
+ 'xpack.siem.timelines.components.importTimelineModal.importTitle',
+ {
+ defaultMessage: 'Import timeline…',
+ }
+);
+
+export const IMPORT_FAILED_DETAILED = (id: string, statusCode: number, message: string) =>
+ i18n.translate('xpack.siem.timelines.components.importTimelineModal.importFailedDetailedTitle', {
+ values: { id, statusCode, message },
+ defaultMessage: 'Timeline ID: {id}\n Status Code: {statusCode}\n Message: {message}',
+ });
diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts b/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts
index b466ea32799d9..1265c056ec506 100644
--- a/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts
+++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts
@@ -120,6 +120,8 @@ export interface OpenTimelineProps {
isLoading: boolean;
/** Required by EuiTable for expandable rows: a map of `TimelineResult.savedObjectId` to rendered notes */
itemIdToExpandedNotesRowMap: Record;
+ /** Display import timelines modal*/
+ importCompleteToggle?: boolean;
/** If this callback is specified, a "Favorite Selected" button will be displayed, and this callback will be invoked when the button is clicked */
onAddTimelinesToFavorites?: OnAddTimelinesToFavorites;
/** If this callback is specified, a "Delete Selected" button will be displayed, and this callback will be invoked when the button is clicked */
@@ -144,13 +146,14 @@ export interface OpenTimelineProps {
pageSize: number;
/** The currently applied search criteria */
query: string;
- /** Refetch timelines data */
+ /** Refetch table */
refetch?: Refetch;
-
/** The results of executing a search */
searchResults: OpenTimelineResult[];
/** the currently-selected timelines in the table */
selectedItems: OpenTimelineResult[];
+ /** Toggle export timelines modal*/
+ setImportCompleteToggle?: React.Dispatch>;
/** the requested sort direction of the query results */
sortDirection: 'asc' | 'desc';
/** the requested field to sort on */
diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/__snapshots__/index.test.tsx.snap
index 85b028cf7cd51..8b7d8efa7ac37 100644
--- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/__snapshots__/index.test.tsx.snap
+++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/__snapshots__/index.test.tsx.snap
@@ -10,14 +10,7 @@ exports[`Tls Table Component Rendering it renders the default Domains table 1`]
},
"node": Object {
"_id": "2fe3bdf168af35b9e0ce5dc583bab007c40d47de",
- "alternativeNames": Array [
- "*.elastic.co",
- "elastic.co",
- ],
- "commonNames": Array [
- "*.elastic.co",
- ],
- "issuerNames": Array [
+ "issuers": Array [
"DigiCert SHA2 Secure Server CA",
],
"ja3": Array [
@@ -27,6 +20,9 @@ exports[`Tls Table Component Rendering it renders the default Domains table 1`]
"notAfter": Array [
"2021-04-22T12:00:00.000Z",
],
+ "subjects": Array [
+ "*.elastic.co",
+ ],
},
},
Object {
@@ -35,13 +31,7 @@ exports[`Tls Table Component Rendering it renders the default Domains table 1`]
},
"node": Object {
"_id": "61749734b3246f1584029deb4f5276c64da00ada",
- "alternativeNames": Array [
- "api.snapcraft.io",
- ],
- "commonNames": Array [
- "api.snapcraft.io",
- ],
- "issuerNames": Array [
+ "issuers": Array [
"DigiCert SHA2 Secure Server CA",
],
"ja3": Array [
@@ -50,6 +40,9 @@ exports[`Tls Table Component Rendering it renders the default Domains table 1`]
"notAfter": Array [
"2019-05-22T12:00:00.000Z",
],
+ "subjects": Array [
+ "api.snapcraft.io",
+ ],
},
},
Object {
@@ -58,14 +51,7 @@ exports[`Tls Table Component Rendering it renders the default Domains table 1`]
},
"node": Object {
"_id": "6560d3b7dd001c989b85962fa64beb778cdae47a",
- "alternativeNames": Array [
- "changelogs.ubuntu.com",
- "manpages.ubuntu.com",
- ],
- "commonNames": Array [
- "changelogs.ubuntu.com",
- ],
- "issuerNames": Array [
+ "issuers": Array [
"Let's Encrypt Authority X3",
],
"ja3": Array [
@@ -74,6 +60,9 @@ exports[`Tls Table Component Rendering it renders the default Domains table 1`]
"notAfter": Array [
"2019-06-27T01:09:59.000Z",
],
+ "subjects": Array [
+ "changelogs.ubuntu.com",
+ ],
},
},
]
diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/columns.tsx
index 44a538871d951..f95475819abc9 100644
--- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/columns.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/columns.tsx
@@ -32,11 +32,11 @@ export const getTlsColumns = (tableId: string): TlsColumns => [
truncateText: false,
hideForMobile: false,
sortable: false,
- render: ({ _id, issuerNames }) =>
+ render: ({ _id, issuers }) =>
getRowItemDraggables({
- rowItems: issuerNames,
- attrName: 'tls.server_certificate.issuer.common_name',
- idPrefix: `${tableId}-${_id}-table-issuerNames`,
+ rowItems: issuers,
+ attrName: 'tls.server.issuer',
+ idPrefix: `${tableId}-${_id}-table-issuers`,
}),
},
{
@@ -45,18 +45,12 @@ export const getTlsColumns = (tableId: string): TlsColumns => [
truncateText: false,
hideForMobile: false,
sortable: false,
- render: ({ _id, alternativeNames, commonNames }) =>
- alternativeNames != null && alternativeNames.length > 0
- ? getRowItemDraggables({
- rowItems: alternativeNames,
- attrName: 'tls.server_certificate.alternative_names',
- idPrefix: `${tableId}-${_id}-table-alternative-name`,
- })
- : getRowItemDraggables({
- rowItems: commonNames,
- attrName: 'tls.server_certificate.subject.common_name',
- idPrefix: `${tableId}-${_id}-table-common-name`,
- }),
+ render: ({ _id, subjects }) =>
+ getRowItemDraggables({
+ rowItems: subjects,
+ attrName: 'tls.server.subject',
+ idPrefix: `${tableId}-${_id}-table-subjects`,
+ }),
},
{
field: 'node._id',
diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/mock.ts b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/mock.ts
index 77148bf50c038..453bd8fc84dfa 100644
--- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/mock.ts
+++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/mock.ts
@@ -12,10 +12,9 @@ export const mockTlsData: TlsData = {
{
node: {
_id: '2fe3bdf168af35b9e0ce5dc583bab007c40d47de',
- alternativeNames: ['*.elastic.co', 'elastic.co'],
- commonNames: ['*.elastic.co'],
+ subjects: ['*.elastic.co'],
ja3: ['7851693188210d3b271aa1713d8c68c2', 'fb4726d465c5f28b84cd6d14cedd13a7'],
- issuerNames: ['DigiCert SHA2 Secure Server CA'],
+ issuers: ['DigiCert SHA2 Secure Server CA'],
notAfter: ['2021-04-22T12:00:00.000Z'],
},
cursor: {
@@ -25,10 +24,9 @@ export const mockTlsData: TlsData = {
{
node: {
_id: '61749734b3246f1584029deb4f5276c64da00ada',
- alternativeNames: ['api.snapcraft.io'],
- commonNames: ['api.snapcraft.io'],
+ subjects: ['api.snapcraft.io'],
ja3: ['839868ad711dc55bde0d37a87f14740d'],
- issuerNames: ['DigiCert SHA2 Secure Server CA'],
+ issuers: ['DigiCert SHA2 Secure Server CA'],
notAfter: ['2019-05-22T12:00:00.000Z'],
},
cursor: {
@@ -38,10 +36,9 @@ export const mockTlsData: TlsData = {
{
node: {
_id: '6560d3b7dd001c989b85962fa64beb778cdae47a',
- alternativeNames: ['changelogs.ubuntu.com', 'manpages.ubuntu.com'],
- commonNames: ['changelogs.ubuntu.com'],
+ subjects: ['changelogs.ubuntu.com'],
ja3: ['da12c94da8021bbaf502907ad086e7bc'],
- issuerNames: ["Let's Encrypt Authority X3"],
+ issuers: ["Let's Encrypt Authority X3"],
notAfter: ['2019-06-27T01:09:59.000Z'],
},
cursor: {
diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/translations.ts b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/translations.ts
index 89d0f58684cbe..ff714204144ec 100644
--- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/translations.ts
@@ -16,7 +16,7 @@ export const TRANSPORT_LAYER_SECURITY = i18n.translate(
export const UNIT = (totalCount: number) =>
i18n.translate('xpack.siem.network.ipDetails.tlsTable.unit', {
values: { totalCount },
- defaultMessage: `{totalCount, plural, =1 {issuer} other {issuers}}`,
+ defaultMessage: `{totalCount, plural, =1 {server certificate} other {server certificates}}`,
});
// Columns
diff --git a/x-pack/legacy/plugins/siem/public/components/recent_cases/filters/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_cases/filters/index.tsx
new file mode 100644
index 0000000000000..edb0b99cbff8b
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/components/recent_cases/filters/index.tsx
@@ -0,0 +1,51 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { EuiButtonGroup, EuiButtonGroupOption } from '@elastic/eui';
+import React, { useCallback, useMemo } from 'react';
+
+import { FilterMode } from '../types';
+
+import * as i18n from '../translations';
+
+const MY_RECENTLY_REPORTED_ID = 'myRecentlyReported';
+
+const toggleButtonIcons: EuiButtonGroupOption[] = [
+ {
+ id: 'recentlyCreated',
+ label: i18n.RECENTLY_CREATED_CASES,
+ iconType: 'folderExclamation',
+ },
+ {
+ id: MY_RECENTLY_REPORTED_ID,
+ label: i18n.MY_RECENTLY_REPORTED_CASES,
+ iconType: 'reporter',
+ },
+];
+
+export const Filters = React.memo<{
+ filterBy: FilterMode;
+ setFilterBy: (filterBy: FilterMode) => void;
+ showMyRecentlyReported: boolean;
+}>(({ filterBy, setFilterBy, showMyRecentlyReported }) => {
+ const options = useMemo(
+ () =>
+ showMyRecentlyReported
+ ? toggleButtonIcons
+ : toggleButtonIcons.filter(x => x.id !== MY_RECENTLY_REPORTED_ID),
+ [showMyRecentlyReported]
+ );
+ const onChange = useCallback(
+ (filterMode: string) => {
+ setFilterBy(filterMode as FilterMode);
+ },
+ [setFilterBy]
+ );
+
+ return ;
+});
+
+Filters.displayName = 'Filters';
diff --git a/x-pack/legacy/plugins/siem/public/components/recent_cases/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_cases/index.tsx
new file mode 100644
index 0000000000000..07246c6c6ec88
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/components/recent_cases/index.tsx
@@ -0,0 +1,80 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { EuiHorizontalRule, EuiLink, EuiText } from '@elastic/eui';
+import React, { useEffect, useMemo, useRef } from 'react';
+
+import { FilterOptions, QueryParams } from '../../containers/case/types';
+import { DEFAULT_QUERY_PARAMS, useGetCases } from '../../containers/case/use_get_cases';
+import { getCaseUrl } from '../link_to/redirect_to_case';
+import { useGetUrlSearch } from '../navigation/use_get_url_search';
+import { LoadingPlaceholders } from '../page/overview/loading_placeholders';
+import { navTabs } from '../../pages/home/home_navigations';
+
+import { NoCases } from './no_cases';
+import { RecentCases } from './recent_cases';
+import * as i18n from './translations';
+
+const usePrevious = (value: FilterOptions) => {
+ const ref = useRef();
+ useEffect(() => {
+ (ref.current as unknown) = value;
+ });
+ return ref.current;
+};
+
+const MAX_CASES_TO_SHOW = 3;
+
+const queryParams: QueryParams = {
+ ...DEFAULT_QUERY_PARAMS,
+ perPage: MAX_CASES_TO_SHOW,
+};
+
+const StatefulRecentCasesComponent = React.memo(
+ ({ filterOptions }: { filterOptions: FilterOptions }) => {
+ const previousFilterOptions = usePrevious(filterOptions);
+ const { data, loading, setFilters } = useGetCases(queryParams);
+ const isLoadingCases = useMemo(
+ () => loading.indexOf('cases') > -1 || loading.indexOf('caseUpdate') > -1,
+ [loading]
+ );
+ const search = useGetUrlSearch(navTabs.case);
+ const allCasesLink = useMemo(
+ () => {i18n.VIEW_ALL_CASES},
+ [search]
+ );
+
+ useEffect(() => {
+ if (previousFilterOptions !== undefined && previousFilterOptions !== filterOptions) {
+ setFilters(filterOptions);
+ }
+ }, [previousFilterOptions, filterOptions, setFilters]);
+
+ const content = useMemo(
+ () =>
+ isLoadingCases ? (
+
+ ) : !isLoadingCases && data.cases.length === 0 ? (
+
+ ) : (
+
+ ),
+ [isLoadingCases, data]
+ );
+
+ return (
+
+ {content}
+
+ {allCasesLink}
+
+ );
+ }
+);
+
+StatefulRecentCasesComponent.displayName = 'StatefulRecentCasesComponent';
+
+export const StatefulRecentCases = React.memo(StatefulRecentCasesComponent);
diff --git a/x-pack/legacy/plugins/siem/public/components/recent_cases/no_cases/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_cases/no_cases/index.tsx
new file mode 100644
index 0000000000000..9f0361311b7b6
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/components/recent_cases/no_cases/index.tsx
@@ -0,0 +1,34 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { EuiLink } from '@elastic/eui';
+import React, { useMemo } from 'react';
+
+import { getCreateCaseUrl } from '../../link_to/redirect_to_case';
+import { useGetUrlSearch } from '../../navigation/use_get_url_search';
+import { navTabs } from '../../../pages/home/home_navigations';
+
+import * as i18n from '../translations';
+
+const NoCasesComponent = () => {
+ const urlSearch = useGetUrlSearch(navTabs.case);
+ const newCaseLink = useMemo(
+ () => {` ${i18n.START_A_NEW_CASE}`},
+ [urlSearch]
+ );
+
+ return (
+ <>
+ {i18n.NO_CASES}
+ {newCaseLink}
+ {'!'}
+ >
+ );
+};
+
+NoCasesComponent.displayName = 'NoCasesComponent';
+
+export const NoCases = React.memo(NoCasesComponent);
diff --git a/x-pack/legacy/plugins/siem/public/components/recent_cases/recent_cases.tsx b/x-pack/legacy/plugins/siem/public/components/recent_cases/recent_cases.tsx
new file mode 100644
index 0000000000000..eb17c75f4111b
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/components/recent_cases/recent_cases.tsx
@@ -0,0 +1,56 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
+import React from 'react';
+import styled from 'styled-components';
+
+import { Case } from '../../containers/case/types';
+import { getCaseDetailsUrl } from '../link_to/redirect_to_case';
+import { Markdown } from '../markdown';
+import { useGetUrlSearch } from '../navigation/use_get_url_search';
+import { navTabs } from '../../pages/home/home_navigations';
+import { IconWithCount } from '../recent_timelines/counts';
+
+import * as i18n from './translations';
+
+const MarkdownContainer = styled.div`
+ max-height: 150px;
+ overflow-y: auto;
+ width: 300px;
+`;
+
+const RecentCasesComponent = ({ cases }: { cases: Case[] }) => {
+ const search = useGetUrlSearch(navTabs.case);
+
+ return (
+ <>
+ {cases.map((c, i) => (
+
+
+
+ {c.title}
+
+
+
+ {c.description && c.description.length && (
+
+
+
+
+
+ )}
+ {i !== cases.length - 1 && }
+
+
+ ))}
+ >
+ );
+};
+
+RecentCasesComponent.displayName = 'RecentCasesComponent';
+
+export const RecentCases = React.memo(RecentCasesComponent);
diff --git a/x-pack/legacy/plugins/siem/public/components/recent_cases/translations.ts b/x-pack/legacy/plugins/siem/public/components/recent_cases/translations.ts
new file mode 100644
index 0000000000000..d2318e5db88c3
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/components/recent_cases/translations.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const COMMENTS = i18n.translate('xpack.siem.recentCases.commentsTooltip', {
+ defaultMessage: 'Comments',
+});
+
+export const MY_RECENTLY_REPORTED_CASES = i18n.translate(
+ 'xpack.siem.overview.myRecentlyReportedCasesButtonLabel',
+ {
+ defaultMessage: 'My recently reported cases',
+ }
+);
+
+export const NO_CASES = i18n.translate('xpack.siem.recentCases.noCasesMessage', {
+ defaultMessage: 'No cases have been created yet. Put your detective hat on and',
+});
+
+export const RECENTLY_CREATED_CASES = i18n.translate(
+ 'xpack.siem.overview.recentlyCreatedCasesButtonLabel',
+ {
+ defaultMessage: 'Recently created cases',
+ }
+);
+
+export const START_A_NEW_CASE = i18n.translate('xpack.siem.recentCases.startNewCaseLink', {
+ defaultMessage: 'start a new case',
+});
+
+export const VIEW_ALL_CASES = i18n.translate('xpack.siem.recentCases.viewAllCasesLink', {
+ defaultMessage: 'View all cases',
+});
diff --git a/x-pack/legacy/plugins/siem/public/components/recent_cases/types.ts b/x-pack/legacy/plugins/siem/public/components/recent_cases/types.ts
new file mode 100644
index 0000000000000..29c7072ce0be6
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/components/recent_cases/types.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export type FilterMode = 'recentlyCreated' | 'myRecentlyReported';
diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/counts/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_timelines/counts/index.tsx
index e04b6319cfb24..c80530b245cf3 100644
--- a/x-pack/legacy/plugins/siem/public/components/recent_timelines/counts/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/counts/index.tsx
@@ -21,7 +21,7 @@ const FlexGroup = styled(EuiFlexGroup)`
margin-right: 16px;
`;
-const IconWithCount = React.memo<{ count: number; icon: string; tooltip: string }>(
+export const IconWithCount = React.memo<{ count: number; icon: string; tooltip: string }>(
({ count, icon, tooltip }) => (
diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/filters/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_timelines/filters/index.tsx
index de8a3de8094d0..d7271197b9cea 100644
--- a/x-pack/legacy/plugins/siem/public/components/recent_timelines/filters/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/filters/index.tsx
@@ -9,15 +9,17 @@ import React from 'react';
import { FilterMode } from '../types';
+import * as i18n from '../translations';
+
const toggleButtonIcons: EuiButtonGroupOption[] = [
{
id: 'favorites',
- label: 'Favorites',
+ label: i18n.FAVORITES,
iconType: 'starFilled',
},
{
id: `recently-updated`,
- label: 'Last updated',
+ label: i18n.LAST_UPDATED,
iconType: 'documentEdit',
},
];
diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx b/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx
index 007665b47dedb..5b851701b973c 100644
--- a/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/index.tsx
@@ -31,6 +31,8 @@ interface OwnProps {
export type Props = OwnProps & PropsFromRedux;
+const PAGE_SIZE = 3;
+
const StatefulRecentTimelinesComponent = React.memo(
({ apolloClient, filterBy, updateIsLoading, updateTimeline }) => {
const onOpenTimeline: OnOpenTimeline = useCallback(
@@ -53,12 +55,18 @@ const StatefulRecentTimelinesComponent = React.memo(
() => {i18n.VIEW_ALL_TIMELINES},
[urlSearch]
);
+ const loadingPlaceholders = useMemo(
+ () => (
+
+ ),
+ [filterBy]
+ );
return (
(
{({ timelines, loading }) => (
<>
{loading ? (
-
+ loadingPlaceholders
) : (
{t.description && t.description.length && (
- <>
-
-
- {t.description}
-
- >
+
+ {t.description}
+
)}
diff --git a/x-pack/legacy/plugins/siem/public/components/recent_timelines/translations.ts b/x-pack/legacy/plugins/siem/public/components/recent_timelines/translations.ts
index e547272fde6e1..f5934aa317242 100644
--- a/x-pack/legacy/plugins/siem/public/components/recent_timelines/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/components/recent_timelines/translations.ts
@@ -13,6 +13,10 @@ export const ERROR_RETRIEVING_USER_DETAILS = i18n.translate(
}
);
+export const FAVORITES = i18n.translate('xpack.siem.recentTimelines.favoritesButtonLabel', {
+ defaultMessage: 'Favorites',
+});
+
export const NO_FAVORITE_TIMELINES = i18n.translate(
'xpack.siem.recentTimelines.noFavoriteTimelinesMessage',
{
@@ -21,6 +25,10 @@ export const NO_FAVORITE_TIMELINES = i18n.translate(
}
);
+export const LAST_UPDATED = i18n.translate('xpack.siem.recentTimelines.lastUpdatedButtonLabel', {
+ defaultMessage: 'Last updated',
+});
+
export const NO_TIMELINES = i18n.translate('xpack.siem.recentTimelines.noTimelinesMessage', {
defaultMessage: "You haven't created any timelines yet. Get out there and start threat hunting!",
});
diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx
index 10aa388449d91..4d2a717153894 100644
--- a/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx
@@ -157,9 +157,7 @@ describe('UrlStateContainer', () => {
).toEqual({
hash: '',
pathname: examplePath,
- search: [CONSTANTS.timelinePage].includes(page)
- ? '?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))'
- : `?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`,
+ search: `?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))`,
state: '',
});
}
diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts
index 2cb1b0c96ad79..9d8a4a8e6a908 100644
--- a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts
+++ b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts
@@ -60,8 +60,20 @@ export const URL_STATE_KEYS: Record = {
CONSTANTS.timerange,
CONSTANTS.timeline,
],
- timeline: [CONSTANTS.timeline, CONSTANTS.timerange],
- case: [],
+ timeline: [
+ CONSTANTS.appQuery,
+ CONSTANTS.filters,
+ CONSTANTS.savedQuery,
+ CONSTANTS.timerange,
+ CONSTANTS.timeline,
+ ],
+ case: [
+ CONSTANTS.appQuery,
+ CONSTANTS.filters,
+ CONSTANTS.savedQuery,
+ CONSTANTS.timerange,
+ CONSTANTS.timeline,
+ ],
};
export type LocationTypes =
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/api.ts b/x-pack/legacy/plugins/siem/public/containers/case/api.ts
index 5ba1f010e0d52..16ee294224bb9 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/api.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/case/api.ts
@@ -13,9 +13,15 @@ import {
CommentRequest,
CommentResponse,
User,
+ CaseUserActionsResponse,
+ CaseExternalServiceRequest,
+ ServiceConnectorCaseParams,
+ ServiceConnectorCaseResponse,
+ ActionTypeExecutorResult,
} from '../../../../../../plugins/case/common/api';
import { KibanaServices } from '../../lib/kibana';
import {
+ ActionLicense,
AllCases,
BulkUpdateStatus,
Case,
@@ -23,16 +29,20 @@ import {
Comment,
FetchCasesProps,
SortFieldCase,
+ CaseUserActions,
} from './types';
import { CASES_URL } from './constants';
import {
convertToCamelCase,
convertAllCasesToCamel,
+ convertArrayToCamelCase,
decodeCaseResponse,
decodeCasesResponse,
decodeCasesFindResponse,
decodeCasesStatusResponse,
decodeCommentResponse,
+ decodeCaseUserActionsResponse,
+ decodeServiceConnectorCaseResponse,
} from './utils';
export const getCase = async (caseId: string, includeComments: boolean = true): Promise => {
@@ -71,6 +81,20 @@ export const getReporters = async (signal: AbortSignal): Promise => {
return response ?? [];
};
+export const getCaseUserActions = async (
+ caseId: string,
+ signal: AbortSignal
+): Promise => {
+ const response = await KibanaServices.get().http.fetch(
+ `${CASES_URL}/${caseId}/user_actions`,
+ {
+ method: 'GET',
+ signal,
+ }
+ );
+ return convertArrayToCamelCase(decodeCaseUserActionsResponse(response)) as CaseUserActions[];
+};
+
export const getCases = async ({
filterOptions = {
search: '',
@@ -161,3 +185,43 @@ export const deleteCases = async (caseIds: string[]): Promise => {
});
return response === 'true' ? true : false;
};
+
+export const pushCase = async (
+ caseId: string,
+ push: CaseExternalServiceRequest,
+ signal: AbortSignal
+): Promise => {
+ const response = await KibanaServices.get().http.fetch(
+ `${CASES_URL}/${caseId}/_push`,
+ {
+ method: 'POST',
+ body: JSON.stringify(push),
+ signal,
+ }
+ );
+ return convertToCamelCase(decodeCaseResponse(response));
+};
+
+export const pushToService = async (
+ connectorId: string,
+ casePushParams: ServiceConnectorCaseParams,
+ signal: AbortSignal
+): Promise => {
+ const response = await KibanaServices.get().http.fetch(
+ `/api/action/${connectorId}/_execute`,
+ {
+ method: 'POST',
+ body: JSON.stringify({ params: casePushParams }),
+ signal,
+ }
+ );
+ return decodeServiceConnectorCaseResponse(response.data);
+};
+
+export const getActionLicense = async (signal: AbortSignal): Promise => {
+ const response = await KibanaServices.get().http.fetch(`/api/action/types`, {
+ method: 'GET',
+ signal,
+ });
+ return response;
+};
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/types.ts b/x-pack/legacy/plugins/siem/public/containers/case/configure/types.ts
index fc7aaa3643d77..d69c23fe02ec9 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/configure/types.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/types.ts
@@ -26,6 +26,7 @@ export interface CaseConfigure {
createdAt: string;
createdBy: ElasticUser;
connectorId: string;
+ connectorName: string;
closureType: ClosureType;
updatedAt: string;
updatedBy: ElasticUser;
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx
index 22ac54093d1dc..a24f8303824c5 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx
@@ -13,6 +13,7 @@ import { ClosureType } from './types';
interface PersistCaseConfigure {
connectorId: string;
+ connectorName: string;
closureType: ClosureType;
}
@@ -24,12 +25,12 @@ export interface ReturnUseCaseConfigure {
}
interface UseCaseConfigure {
- setConnectorId: (newConnectorId: string) => void;
- setClosureType: (newClosureType: ClosureType) => void;
+ setConnector: (newConnectorId: string, newConnectorName?: string) => void;
+ setClosureType?: (newClosureType: ClosureType) => void;
}
export const useCaseConfigure = ({
- setConnectorId,
+ setConnector,
setClosureType,
}: UseCaseConfigure): ReturnUseCaseConfigure => {
const [, dispatchToaster] = useStateToaster();
@@ -48,8 +49,10 @@ export const useCaseConfigure = ({
if (!didCancel) {
setLoading(false);
if (res != null) {
- setConnectorId(res.connectorId);
- setClosureType(res.closureType);
+ setConnector(res.connectorId, res.connectorName);
+ if (setClosureType != null) {
+ setClosureType(res.closureType);
+ }
setVersion(res.version);
}
}
@@ -74,7 +77,7 @@ export const useCaseConfigure = ({
}, []);
const persistCaseConfigure = useCallback(
- async ({ connectorId, closureType }: PersistCaseConfigure) => {
+ async ({ connectorId, connectorName, closureType }: PersistCaseConfigure) => {
let didCancel = false;
const abortCtrl = new AbortController();
const saveCaseConfiguration = async () => {
@@ -83,7 +86,11 @@ export const useCaseConfigure = ({
const res =
version.length === 0
? await postCaseConfigure(
- { connector_id: connectorId, closure_type: closureType },
+ {
+ connector_id: connectorId,
+ connector_name: connectorName,
+ closure_type: closureType,
+ },
abortCtrl.signal
)
: await patchCaseConfigure(
@@ -92,8 +99,10 @@ export const useCaseConfigure = ({
);
if (!didCancel) {
setPersistLoading(false);
- setConnectorId(res.connectorId);
- setClosureType(res.closureType);
+ setConnector(res.connectorId);
+ if (setClosureType) {
+ setClosureType(res.closureType);
+ }
setVersion(res.version);
}
} catch (error) {
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/translations.ts b/x-pack/legacy/plugins/siem/public/containers/case/translations.ts
index 0c8b896e2b426..601db373f041e 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/case/translations.ts
@@ -16,3 +16,10 @@ export const TAG_FETCH_FAILURE = i18n.translate(
defaultMessage: 'Failed to fetch Tags',
}
);
+
+export const SUCCESS_SEND_TO_EXTERNAL_SERVICE = i18n.translate(
+ 'xpack.siem.containers.case.pushToExterService',
+ {
+ defaultMessage: 'Successfully sent to ServiceNow',
+ }
+);
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/types.ts b/x-pack/legacy/plugins/siem/public/containers/case/types.ts
index 44519031e91cb..bbbb13788d53a 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/types.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/case/types.ts
@@ -4,30 +4,53 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { User } from '../../../../../../plugins/case/common/api';
+import { User, UserActionField, UserAction } from '../../../../../../plugins/case/common/api';
export interface Comment {
id: string;
createdAt: string;
createdBy: ElasticUser;
comment: string;
+ pushedAt: string | null;
+ pushedBy: string | null;
updatedAt: string | null;
updatedBy: ElasticUser | null;
version: string;
}
+export interface CaseUserActions {
+ actionId: string;
+ actionField: UserActionField;
+ action: UserAction;
+ actionAt: string;
+ actionBy: ElasticUser;
+ caseId: string;
+ commentId: string | null;
+ newValue: string | null;
+ oldValue: string | null;
+}
+export interface CaseExternalService {
+ pushedAt: string;
+ pushedBy: string;
+ connectorId: string;
+ connectorName: string;
+ externalId: string;
+ externalTitle: string;
+ externalUrl: string;
+}
export interface Case {
id: string;
closedAt: string | null;
closedBy: ElasticUser | null;
comments: Comment[];
- commentIds: string[];
createdAt: string;
createdBy: ElasticUser;
description: string;
+ externalService: CaseExternalService | null;
status: string;
tags: string[];
title: string;
+ totalComment: number;
updatedAt: string | null;
updatedBy: ElasticUser | null;
version: string;
@@ -84,3 +107,10 @@ export interface BulkUpdateStatus {
id: string;
version: string;
}
+export interface ActionLicense {
+ id: string;
+ name: string;
+ enabled: boolean;
+ enabledInConfig: boolean;
+ enabledInLicense: boolean;
+}
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_action_license.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_action_license.tsx
new file mode 100644
index 0000000000000..12f92b2db039b
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_action_license.tsx
@@ -0,0 +1,74 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { useCallback, useEffect, useState } from 'react';
+
+import { errorToToaster, useStateToaster } from '../../components/toasters';
+import { getActionLicense } from './api';
+import * as i18n from './translations';
+import { ActionLicense } from './types';
+
+interface ActionLicenseState {
+ actionLicense: ActionLicense | null;
+ isLoading: boolean;
+ isError: boolean;
+}
+
+const initialData: ActionLicenseState = {
+ actionLicense: null,
+ isLoading: true,
+ isError: false,
+};
+
+export const useGetActionLicense = (): ActionLicenseState => {
+ const [actionLicenseState, setActionLicensesState] = useState(initialData);
+
+ const [, dispatchToaster] = useStateToaster();
+
+ const fetchActionLicense = useCallback(() => {
+ let didCancel = false;
+ const abortCtrl = new AbortController();
+ const fetchData = async () => {
+ setActionLicensesState({
+ ...actionLicenseState,
+ isLoading: true,
+ });
+ try {
+ const response = await getActionLicense(abortCtrl.signal);
+ if (!didCancel) {
+ setActionLicensesState({
+ actionLicense: response.find(l => l.id === '.servicenow') ?? null,
+ isLoading: false,
+ isError: false,
+ });
+ }
+ } catch (error) {
+ if (!didCancel) {
+ errorToToaster({
+ title: i18n.ERROR_TITLE,
+ error: error.body && error.body.message ? new Error(error.body.message) : error,
+ dispatchToaster,
+ });
+ setActionLicensesState({
+ actionLicense: null,
+ isLoading: false,
+ isError: true,
+ });
+ }
+ }
+ };
+ fetchData();
+ return () => {
+ didCancel = true;
+ abortCtrl.abort();
+ };
+ }, [actionLicenseState]);
+
+ useEffect(() => {
+ fetchActionLicense();
+ }, []);
+ return { ...actionLicenseState };
+};
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx
index b70195e2c126f..02b41c9fc720f 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx
@@ -53,14 +53,15 @@ const initialData: Case = {
closedBy: null,
createdAt: '',
comments: [],
- commentIds: [],
createdBy: {
username: '',
},
description: '',
+ externalService: null,
status: '',
tags: [],
title: '',
+ totalComment: 0,
updatedAt: null,
updatedBy: null,
version: '',
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case_user_actions.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case_user_actions.tsx
new file mode 100644
index 0000000000000..4c278bc038134
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case_user_actions.tsx
@@ -0,0 +1,126 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { isEmpty, uniqBy } from 'lodash/fp';
+import { useCallback, useEffect, useState } from 'react';
+
+import { errorToToaster, useStateToaster } from '../../components/toasters';
+import { getCaseUserActions } from './api';
+import * as i18n from './translations';
+import { CaseUserActions, ElasticUser } from './types';
+
+interface CaseUserActionsState {
+ caseUserActions: CaseUserActions[];
+ firstIndexPushToService: number;
+ hasDataToPush: boolean;
+ participants: ElasticUser[];
+ isLoading: boolean;
+ isError: boolean;
+ lastIndexPushToService: number;
+}
+
+const initialData: CaseUserActionsState = {
+ caseUserActions: [],
+ firstIndexPushToService: -1,
+ lastIndexPushToService: -1,
+ hasDataToPush: false,
+ isLoading: true,
+ isError: false,
+ participants: [],
+};
+
+interface UseGetCaseUserActions extends CaseUserActionsState {
+ fetchCaseUserActions: (caseId: string) => void;
+}
+
+const getPushedInfo = (
+ caseUserActions: CaseUserActions[]
+): { firstIndexPushToService: number; lastIndexPushToService: number; hasDataToPush: boolean } => {
+ const firstIndexPushToService = caseUserActions.findIndex(
+ cua => cua.action === 'push-to-service'
+ );
+ const lastIndexPushToService = caseUserActions
+ .map(cua => cua.action)
+ .lastIndexOf('push-to-service');
+
+ const hasDataToPush =
+ lastIndexPushToService === -1 || lastIndexPushToService < caseUserActions.length - 1;
+ return {
+ firstIndexPushToService,
+ lastIndexPushToService,
+ hasDataToPush,
+ };
+};
+
+export const useGetCaseUserActions = (caseId: string): UseGetCaseUserActions => {
+ const [caseUserActionsState, setCaseUserActionsState] = useState(
+ initialData
+ );
+
+ const [, dispatchToaster] = useStateToaster();
+
+ const fetchCaseUserActions = useCallback(
+ (thisCaseId: string) => {
+ let didCancel = false;
+ const abortCtrl = new AbortController();
+ const fetchData = async () => {
+ setCaseUserActionsState({
+ ...caseUserActionsState,
+ isLoading: true,
+ });
+ try {
+ const response = await getCaseUserActions(thisCaseId, abortCtrl.signal);
+ if (!didCancel) {
+ // Attention Future developer
+ // We are removing the first item because it will always be the creation of the case
+ // and we do not want it to simplify our life
+ const participants = !isEmpty(response)
+ ? uniqBy('actionBy.username', response).map(cau => cau.actionBy)
+ : [];
+ const caseUserActions = !isEmpty(response) ? response.slice(1) : [];
+ setCaseUserActionsState({
+ caseUserActions,
+ ...getPushedInfo(caseUserActions),
+ isLoading: false,
+ isError: false,
+ participants,
+ });
+ }
+ } catch (error) {
+ if (!didCancel) {
+ errorToToaster({
+ title: i18n.ERROR_TITLE,
+ error: error.body && error.body.message ? new Error(error.body.message) : error,
+ dispatchToaster,
+ });
+ setCaseUserActionsState({
+ caseUserActions: [],
+ firstIndexPushToService: -1,
+ lastIndexPushToService: -1,
+ hasDataToPush: false,
+ isLoading: false,
+ isError: true,
+ participants: [],
+ });
+ }
+ }
+ };
+ fetchData();
+ return () => {
+ didCancel = true;
+ abortCtrl.abort();
+ };
+ },
+ [caseUserActionsState]
+ );
+
+ useEffect(() => {
+ if (!isEmpty(caseId)) {
+ fetchCaseUserActions(caseId);
+ }
+ }, [caseId]);
+ return { ...caseUserActionsState, fetchCaseUserActions };
+};
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx
index 6c4a6ac4fe58a..ae7b8f3c043fa 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx
@@ -88,6 +88,20 @@ const dataFetchReducer = (state: UseGetCasesState, action: Action): UseGetCasesS
}
};
+export const DEFAULT_FILTER_OPTIONS: FilterOptions = {
+ search: '',
+ reporters: [],
+ status: 'open',
+ tags: [],
+};
+
+export const DEFAULT_QUERY_PARAMS: QueryParams = {
+ page: DEFAULT_TABLE_ACTIVE_PAGE,
+ perPage: DEFAULT_TABLE_LIMIT,
+ sortField: SortFieldCase.createdAt,
+ sortOrder: 'desc',
+};
+
const initialData: AllCases = {
cases: [],
countClosedCases: null,
@@ -109,23 +123,14 @@ interface UseGetCases extends UseGetCasesState {
setQueryParams: (queryParams: QueryParams) => void;
setSelectedCases: (mySelectedCases: Case[]) => void;
}
-export const useGetCases = (): UseGetCases => {
+
+export const useGetCases = (initialQueryParams?: QueryParams): UseGetCases => {
const [state, dispatch] = useReducer(dataFetchReducer, {
data: initialData,
- filterOptions: {
- search: '',
- reporters: [],
- status: 'open',
- tags: [],
- },
+ filterOptions: DEFAULT_FILTER_OPTIONS,
isError: false,
loading: [],
- queryParams: {
- page: DEFAULT_TABLE_ACTIVE_PAGE,
- perPage: DEFAULT_TABLE_LIMIT,
- sortField: SortFieldCase.createdAt,
- sortOrder: 'desc',
- },
+ queryParams: initialQueryParams ?? DEFAULT_QUERY_PARAMS,
selectedCases: [],
});
const [, dispatchToaster] = useStateToaster();
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.tsx
new file mode 100644
index 0000000000000..b6fb15f4fa083
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_post_push_to_service.tsx
@@ -0,0 +1,183 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { useReducer, useCallback } from 'react';
+
+import {
+ ServiceConnectorCaseResponse,
+ ServiceConnectorCaseParams,
+} from '../../../../../../plugins/case/common/api';
+import { errorToToaster, useStateToaster, displaySuccessToast } from '../../components/toasters';
+
+import { getCase, pushToService, pushCase } from './api';
+import * as i18n from './translations';
+import { Case } from './types';
+
+interface PushToServiceState {
+ serviceData: ServiceConnectorCaseResponse | null;
+ pushedCaseData: Case | null;
+ isLoading: boolean;
+ isError: boolean;
+}
+type Action =
+ | { type: 'FETCH_INIT' }
+ | { type: 'FETCH_SUCCESS_PUSH_SERVICE'; payload: ServiceConnectorCaseResponse | null }
+ | { type: 'FETCH_SUCCESS_PUSH_CASE'; payload: Case | null }
+ | { type: 'FETCH_FAILURE' };
+
+const dataFetchReducer = (state: PushToServiceState, action: Action): PushToServiceState => {
+ switch (action.type) {
+ case 'FETCH_INIT':
+ return {
+ ...state,
+ isLoading: true,
+ isError: false,
+ };
+ case 'FETCH_SUCCESS_PUSH_SERVICE':
+ return {
+ ...state,
+ isLoading: false,
+ isError: false,
+ serviceData: action.payload ?? null,
+ };
+ case 'FETCH_SUCCESS_PUSH_CASE':
+ return {
+ ...state,
+ isLoading: false,
+ isError: false,
+ pushedCaseData: action.payload ?? null,
+ };
+ case 'FETCH_FAILURE':
+ return {
+ ...state,
+ isLoading: false,
+ isError: true,
+ };
+ default:
+ return state;
+ }
+};
+
+interface PushToServiceRequest {
+ caseId: string;
+ connectorId: string;
+ connectorName: string;
+ updateCase: (newCase: Case) => void;
+}
+
+interface UsePostPushToService extends PushToServiceState {
+ postPushToService: ({ caseId, connectorId, updateCase }: PushToServiceRequest) => void;
+}
+
+export const usePostPushToService = (): UsePostPushToService => {
+ const [state, dispatch] = useReducer(dataFetchReducer, {
+ serviceData: null,
+ pushedCaseData: null,
+ isLoading: false,
+ isError: false,
+ });
+ const [, dispatchToaster] = useStateToaster();
+
+ const postPushToService = useCallback(
+ async ({ caseId, connectorId, connectorName, updateCase }: PushToServiceRequest) => {
+ let cancel = false;
+ const abortCtrl = new AbortController();
+ try {
+ dispatch({ type: 'FETCH_INIT' });
+ const casePushData = await getCase(caseId);
+ const responseService = await pushToService(
+ connectorId,
+ formatServiceRequestData(casePushData),
+ abortCtrl.signal
+ );
+ const responseCase = await pushCase(
+ caseId,
+ {
+ connector_id: connectorId,
+ connector_name: connectorName,
+ external_id: responseService.incidentId,
+ external_title: responseService.number,
+ external_url: responseService.url,
+ },
+ abortCtrl.signal
+ );
+ if (!cancel) {
+ dispatch({ type: 'FETCH_SUCCESS_PUSH_SERVICE', payload: responseService });
+ dispatch({ type: 'FETCH_SUCCESS_PUSH_CASE', payload: responseCase });
+ updateCase(responseCase);
+ displaySuccessToast(i18n.SUCCESS_SEND_TO_EXTERNAL_SERVICE, dispatchToaster);
+ }
+ } catch (error) {
+ if (!cancel) {
+ errorToToaster({
+ title: i18n.ERROR_TITLE,
+ error: error.body && error.body.message ? new Error(error.body.message) : error,
+ dispatchToaster,
+ });
+ dispatch({ type: 'FETCH_FAILURE' });
+ }
+ }
+ return () => {
+ cancel = true;
+ abortCtrl.abort();
+ };
+ },
+ []
+ );
+
+ return { ...state, postPushToService };
+};
+
+const formatServiceRequestData = (myCase: Case): ServiceConnectorCaseParams => {
+ const {
+ id: caseId,
+ createdAt,
+ createdBy,
+ comments,
+ description,
+ externalService,
+ title,
+ updatedAt,
+ updatedBy,
+ } = myCase;
+
+ return {
+ caseId,
+ createdAt,
+ createdBy: {
+ fullName: createdBy.fullName ?? null,
+ username: createdBy?.username,
+ },
+ comments: comments.map(c => ({
+ commentId: c.id,
+ comment: c.comment,
+ createdAt: c.createdAt,
+ createdBy: {
+ fullName: c.createdBy.fullName ?? null,
+ username: c.createdBy.username,
+ },
+ updatedAt: c.updatedAt,
+ updatedBy:
+ c.updatedBy != null
+ ? {
+ fullName: c.updatedBy.fullName ?? null,
+ username: c.updatedBy.username,
+ }
+ : null,
+ })),
+ description,
+ incidentId: externalService?.externalId ?? null,
+ title,
+ updatedAt,
+ updatedBy:
+ updatedBy != null
+ ? {
+ fullName: updatedBy.fullName ?? null,
+ username: updatedBy.username,
+ }
+ : null,
+ };
+};
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx
index 987620469901b..f8af088f7e03b 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx
@@ -25,6 +25,7 @@ interface NewCaseState {
export interface UpdateByKey {
updateKey: UpdateKey;
updateValue: CaseRequest[UpdateKey];
+ fetchCaseUserActions?: (caseId: string) => void;
}
type Action =
@@ -64,6 +65,7 @@ const dataFetchReducer = (state: NewCaseState, action: Action): NewCaseState =>
interface UseUpdateCase extends NewCaseState {
updateCaseProperty: (updates: UpdateByKey) => void;
+ updateCase: (newCase: Case) => void;
}
export const useUpdateCase = (caseId: string, initialData: Case): UseUpdateCase => {
const [state, dispatch] = useReducer(dataFetchReducer, {
@@ -74,8 +76,12 @@ export const useUpdateCase = (caseId: string, initialData: Case): UseUpdateCase
});
const [, dispatchToaster] = useStateToaster();
+ const updateCase = useCallback((newCase: Case) => {
+ dispatch({ type: 'FETCH_SUCCESS', payload: newCase });
+ }, []);
+
const dispatchUpdateCaseProperty = useCallback(
- async ({ updateKey, updateValue }: UpdateByKey) => {
+ async ({ fetchCaseUserActions, updateKey, updateValue }: UpdateByKey) => {
let cancel = false;
try {
dispatch({ type: 'FETCH_INIT', payload: updateKey });
@@ -85,6 +91,9 @@ export const useUpdateCase = (caseId: string, initialData: Case): UseUpdateCase
state.caseData.version
);
if (!cancel) {
+ if (fetchCaseUserActions != null) {
+ fetchCaseUserActions(caseId);
+ }
dispatch({ type: 'FETCH_SUCCESS', payload: response[0] });
}
} catch (error) {
@@ -104,5 +113,5 @@ export const useUpdateCase = (caseId: string, initialData: Case): UseUpdateCase
[state]
);
- return { ...state, updateCaseProperty: dispatchUpdateCaseProperty };
+ return { ...state, updateCase, updateCaseProperty: dispatchUpdateCaseProperty };
};
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx
index a40a1100ca735..c1b2bfde30126 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/case/use_update_comment.tsx
@@ -70,8 +70,15 @@ const dataFetchReducer = (state: CommentUpdateState, action: Action): CommentUpd
}
};
+interface UpdateComment {
+ caseId: string;
+ commentId: string;
+ commentUpdate: string;
+ fetchUserActions: () => void;
+}
+
interface UseUpdateComment extends CommentUpdateState {
- updateComment: (caseId: string, commentId: string, commentUpdate: string) => void;
+ updateComment: ({ caseId, commentId, commentUpdate, fetchUserActions }: UpdateComment) => void;
addPostedComment: Dispatch;
}
@@ -84,7 +91,7 @@ export const useUpdateComment = (comments: Comment[]): UseUpdateComment => {
const [, dispatchToaster] = useStateToaster();
const dispatchUpdateComment = useCallback(
- async (caseId: string, commentId: string, commentUpdate: string) => {
+ async ({ caseId, commentId, commentUpdate, fetchUserActions }: UpdateComment) => {
let cancel = false;
try {
dispatch({ type: 'FETCH_INIT', payload: commentId });
@@ -98,6 +105,7 @@ export const useUpdateComment = (comments: Comment[]): UseUpdateComment => {
currentComment.version
);
if (!cancel) {
+ fetchUserActions();
dispatch({ type: 'FETCH_SUCCESS', payload: { update: response, commentId } });
}
} catch (error) {
diff --git a/x-pack/legacy/plugins/siem/public/containers/case/utils.ts b/x-pack/legacy/plugins/siem/public/containers/case/utils.ts
index 8f24d5a435240..ce23ac6c440b6 100644
--- a/x-pack/legacy/plugins/siem/public/containers/case/utils.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/case/utils.ts
@@ -23,6 +23,10 @@ import {
CommentResponseRt,
CasesConfigureResponse,
CaseConfigureResponseRt,
+ CaseUserActionsResponse,
+ CaseUserActionsResponseRt,
+ ServiceConnectorCaseResponseRt,
+ ServiceConnectorCaseResponse,
} from '../../../../../../plugins/case/common/api';
import { ToasterError } from '../../components/toasters';
import { AllCases, Case } from './types';
@@ -86,3 +90,15 @@ export const decodeCaseConfigureResponse = (respCase?: CasesConfigureResponse) =
CaseConfigureResponseRt.decode(respCase),
fold(throwErrors(createToasterPlainError), identity)
);
+
+export const decodeCaseUserActionsResponse = (respUserActions?: CaseUserActionsResponse) =>
+ pipe(
+ CaseUserActionsResponseRt.decode(respUserActions),
+ fold(throwErrors(createToasterPlainError), identity)
+ );
+
+export const decodeServiceConnectorCaseResponse = (respPushCase?: ServiceConnectorCaseResponse) =>
+ pipe(
+ ServiceConnectorCaseResponseRt.decode(respPushCase),
+ fold(throwErrors(createToasterPlainError), identity)
+ );
diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.test.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.test.ts
index 8fdc6a67f7d71..e8019659d49c6 100644
--- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.test.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.test.ts
@@ -39,7 +39,7 @@ describe('Detections Rules API', () => {
await addRule({ rule: ruleMock, signal: abortCtrl.signal });
expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules', {
body:
- '{"description":"some desc","enabled":true,"false_positives":[],"filters":[],"from":"now-360s","index":["apm-*-transaction*","auditbeat-*","endgame-*","filebeat-*","packetbeat-*","winlogbeat-*"],"interval":"5m","rule_id":"bbd3106e-b4b5-4d7c-a1a2-47531d6a2baf","language":"kuery","risk_score":75,"name":"Test rule","query":"user.email: \'root@elastic.co\'","references":[],"severity":"high","tags":["APM"],"to":"now","type":"query","threat":[]}',
+ '{"description":"some desc","enabled":true,"false_positives":[],"filters":[],"from":"now-360s","index":["apm-*-transaction*","auditbeat-*","endgame-*","filebeat-*","packetbeat-*","winlogbeat-*"],"interval":"5m","rule_id":"bbd3106e-b4b5-4d7c-a1a2-47531d6a2baf","language":"kuery","risk_score":75,"name":"Test rule","query":"user.email: \'root@elastic.co\'","references":[],"severity":"high","tags":["APM"],"to":"now","type":"query","threat":[],"throttle":null}',
method: 'POST',
signal: abortCtrl.signal,
});
@@ -291,7 +291,7 @@ describe('Detections Rules API', () => {
await duplicateRules({ rules: rulesMock.data });
expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/rules/_bulk_create', {
body:
- '[{"description":"Elastic Endpoint detected Credential Dumping. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.","enabled":false,"false_positives":[],"from":"now-660s","index":["endgame-*"],"interval":"10m","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":73,"name":"Credential Dumping - Detected - Elastic Endpoint [Duplicate]","query":"event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:detection","filters":[],"references":[],"severity":"high","tags":["Elastic","Endpoint"],"to":"now","type":"query","threat":[],"version":1},{"description":"Elastic Endpoint detected an Adversary Behavior. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.","enabled":false,"false_positives":[],"from":"now-660s","index":["endgame-*"],"interval":"10m","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":47,"name":"Adversary Behavior - Detected - Elastic Endpoint [Duplicate]","query":"event.kind:alert and event.module:endgame and event.action:rules_engine_event","filters":[],"references":[],"severity":"medium","tags":["Elastic","Endpoint"],"to":"now","type":"query","threat":[],"version":1}]',
+ '[{"actions":[],"description":"Elastic Endpoint detected Credential Dumping. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.","enabled":false,"false_positives":[],"from":"now-660s","index":["endgame-*"],"interval":"10m","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":73,"name":"Credential Dumping - Detected - Elastic Endpoint [Duplicate]","query":"event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:detection","filters":[],"references":[],"severity":"high","tags":["Elastic","Endpoint"],"to":"now","type":"query","threat":[],"throttle":null,"version":1},{"actions":[],"description":"Elastic Endpoint detected an Adversary Behavior. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.","enabled":false,"false_positives":[],"from":"now-660s","index":["endgame-*"],"interval":"10m","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":47,"name":"Adversary Behavior - Detected - Elastic Endpoint [Duplicate]","query":"event.kind:alert and event.module:endgame and event.action:rules_engine_event","filters":[],"references":[],"severity":"medium","tags":["Elastic","Endpoint"],"to":"now","type":"query","threat":[],"throttle":null,"version":1}]',
method: 'POST',
});
});
diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/mock.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/mock.ts
index 51526c0ab9949..59782e8a36338 100644
--- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/mock.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/mock.ts
@@ -32,9 +32,11 @@ export const ruleMock: NewRule = {
to: 'now',
type: 'query',
threat: [],
+ throttle: null,
};
export const savedRuleMock: Rule = {
+ actions: [],
created_at: 'mm/dd/yyyyTHH:MM:sssz',
created_by: 'mockUser',
description: 'some desc',
@@ -65,6 +67,7 @@ export const savedRuleMock: Rule = {
to: 'now',
type: 'query',
threat: [],
+ throttle: null,
updated_at: 'mm/dd/yyyyTHH:MM:sssz',
updated_by: 'mockUser',
};
@@ -75,6 +78,7 @@ export const rulesMock: FetchRulesResponse = {
total: 2,
data: [
{
+ actions: [],
created_at: '2020-02-14T19:49:28.178Z',
updated_at: '2020-02-14T19:49:28.320Z',
created_by: 'elastic',
@@ -103,9 +107,11 @@ export const rulesMock: FetchRulesResponse = {
to: 'now',
type: 'query',
threat: [],
+ throttle: null,
version: 1,
},
{
+ actions: [],
created_at: '2020-02-14T19:49:28.189Z',
updated_at: '2020-02-14T19:49:28.326Z',
created_by: 'elastic',
@@ -133,6 +139,7 @@ export const rulesMock: FetchRulesResponse = {
to: 'now',
type: 'query',
threat: [],
+ throttle: null,
version: 1,
},
],
diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts
index c75d7b78cf92f..3ec3e6d2b3036 100644
--- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts
@@ -13,6 +13,19 @@ export const RuleTypeSchema = t.keyof({
});
export type RuleType = t.TypeOf;
+/**
+ * Params is an "record", since it is a type of AlertActionParams which is action templates.
+ * @see x-pack/plugins/alerting/common/alert.ts
+ */
+export const action = t.exact(
+ t.type({
+ group: t.string,
+ id: t.string,
+ action_type_id: t.string,
+ params: t.record(t.string, t.any),
+ })
+);
+
export const NewRuleSchema = t.intersection([
t.type({
description: t.string,
@@ -24,6 +37,7 @@ export const NewRuleSchema = t.intersection([
type: RuleTypeSchema,
}),
t.partial({
+ actions: t.array(action),
anomaly_threshold: t.number,
created_by: t.string,
false_positives: t.array(t.string),
@@ -40,6 +54,7 @@ export const NewRuleSchema = t.intersection([
saved_id: t.string,
tags: t.array(t.string),
threat: t.array(t.unknown),
+ throttle: t.union([t.string, t.null]),
to: t.string,
updated_by: t.string,
note: t.string,
@@ -54,9 +69,15 @@ export interface AddRulesProps {
signal: AbortSignal;
}
-const MetaRule = t.type({
- from: t.string,
-});
+const MetaRule = t.intersection([
+ t.type({
+ from: t.string,
+ }),
+ t.partial({
+ throttle: t.string,
+ kibanaSiemAppUrl: t.string,
+ }),
+]);
export const RuleSchema = t.intersection([
t.type({
@@ -81,6 +102,8 @@ export const RuleSchema = t.intersection([
threat: t.array(t.unknown),
updated_at: t.string,
updated_by: t.string,
+ actions: t.array(action),
+ throttle: t.union([t.string, t.null]),
}),
t.partial({
anomaly_threshold: t.number,
diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule.test.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule.test.tsx
index e0bf2c4907370..ab09f796ad49b 100644
--- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule.test.tsx
@@ -31,6 +31,7 @@ describe('useRule', () => {
expect(result.current).toEqual([
false,
{
+ actions: [],
created_at: 'mm/dd/yyyyTHH:MM:sssz',
created_by: 'mockUser',
description: 'some desc',
@@ -59,6 +60,7 @@ describe('useRule', () => {
severity: 'high',
tags: ['APM'],
threat: [],
+ throttle: null,
to: 'now',
type: 'query',
updated_at: 'mm/dd/yyyyTHH:MM:sssz',
diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.test.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.test.tsx
index 242d715e20f77..5d13b57f862bc 100644
--- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.test.tsx
@@ -58,6 +58,7 @@ describe('useRules', () => {
{
data: [
{
+ actions: [],
created_at: '2020-02-14T19:49:28.178Z',
created_by: 'elastic',
description:
@@ -82,6 +83,7 @@ describe('useRules', () => {
severity: 'high',
tags: ['Elastic', 'Endpoint'],
threat: [],
+ throttle: null,
to: 'now',
type: 'query',
updated_at: '2020-02-14T19:49:28.320Z',
@@ -89,6 +91,7 @@ describe('useRules', () => {
version: 1,
},
{
+ actions: [],
created_at: '2020-02-14T19:49:28.189Z',
created_by: 'elastic',
description:
@@ -113,6 +116,7 @@ describe('useRules', () => {
severity: 'medium',
tags: ['Elastic', 'Endpoint'],
threat: [],
+ throttle: null,
to: 'now',
type: 'query',
updated_at: '2020-02-14T19:49:28.326Z',
diff --git a/x-pack/legacy/plugins/siem/public/containers/timeline/all/api.ts b/x-pack/legacy/plugins/siem/public/containers/timeline/all/api.ts
index edda2e30ea400..0479851fc5b55 100644
--- a/x-pack/legacy/plugins/siem/public/containers/timeline/all/api.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/timeline/all/api.ts
@@ -4,9 +4,27 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { ImportRulesProps, ImportRulesResponse } from '../../detection_engine/rules';
import { KibanaServices } from '../../../lib/kibana';
+import { TIMELINE_IMPORT_URL, TIMELINE_EXPORT_URL } from '../../../../common/constants';
import { ExportSelectedData } from '../../../components/generic_downloader';
-import { TIMELINE_EXPORT_URL } from '../../../../common/constants';
+
+export const importTimelines = async ({
+ fileToImport,
+ overwrite = false,
+ signal,
+}: ImportRulesProps): Promise => {
+ const formData = new FormData();
+ formData.append('file', fileToImport);
+
+ return KibanaServices.get().http.fetch(`${TIMELINE_IMPORT_URL}`, {
+ method: 'POST',
+ headers: { 'Content-Type': undefined },
+ query: { overwrite },
+ body: formData,
+ signal,
+ });
+};
export const exportSelectedTimeline: ExportSelectedData = async ({
excludeExportDetails = false,
diff --git a/x-pack/legacy/plugins/siem/public/containers/tls/index.gql_query.ts b/x-pack/legacy/plugins/siem/public/containers/tls/index.gql_query.ts
index bbb92282bee83..f513a94d69667 100644
--- a/x-pack/legacy/plugins/siem/public/containers/tls/index.gql_query.ts
+++ b/x-pack/legacy/plugins/siem/public/containers/tls/index.gql_query.ts
@@ -33,10 +33,9 @@ export const tlsQuery = gql`
edges {
node {
_id
- alternativeNames
- commonNames
+ subjects
ja3
- issuerNames
+ issuers
notAfter
}
cursor {
diff --git a/x-pack/legacy/plugins/siem/public/graphql/introspection.json b/x-pack/legacy/plugins/siem/public/graphql/introspection.json
index 9802a5f5bd3bf..5d43024625d0d 100644
--- a/x-pack/legacy/plugins/siem/public/graphql/introspection.json
+++ b/x-pack/legacy/plugins/siem/public/graphql/introspection.json
@@ -9213,22 +9213,6 @@
"isDeprecated": false,
"deprecationReason": null
},
- {
- "name": "alternativeNames",
- "description": "",
- "args": [],
- "type": {
- "kind": "LIST",
- "name": null,
- "ofType": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
{
"name": "notAfter",
"description": "",
@@ -9246,7 +9230,7 @@
"deprecationReason": null
},
{
- "name": "commonNames",
+ "name": "subjects",
"description": "",
"args": [],
"type": {
@@ -9278,7 +9262,7 @@
"deprecationReason": null
},
{
- "name": "issuerNames",
+ "name": "issuers",
"description": "",
"args": [],
"type": {
diff --git a/x-pack/legacy/plugins/siem/public/graphql/types.ts b/x-pack/legacy/plugins/siem/public/graphql/types.ts
index 3528ee6e13a38..a5d1e3fbcba27 100644
--- a/x-pack/legacy/plugins/siem/public/graphql/types.ts
+++ b/x-pack/legacy/plugins/siem/public/graphql/types.ts
@@ -1859,15 +1859,13 @@ export interface TlsNode {
timestamp?: Maybe;
- alternativeNames?: Maybe;
-
notAfter?: Maybe;
- commonNames?: Maybe;
+ subjects?: Maybe;
ja3?: Maybe;
- issuerNames?: Maybe;
+ issuers?: Maybe;
}
export interface UncommonProcessesData {
@@ -5679,13 +5677,11 @@ export namespace GetTlsQuery {
_id: Maybe;
- alternativeNames: Maybe;
-
- commonNames: Maybe;
+ subjects: Maybe;
ja3: Maybe;
- issuerNames: Maybe;
+ issuers: Maybe;
notAfter: Maybe;
};
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx
index 0b3b0daaf4bbc..836595c7c45d9 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/add_comment/index.tsx
@@ -30,72 +30,79 @@ const initialCommentValue: CommentRequest = {
interface AddCommentProps {
caseId: string;
+ onCommentSaving?: () => void;
onCommentPosted: (commentResponse: Comment) => void;
+ showLoading?: boolean;
}
-export const AddComment = React.memo(({ caseId, onCommentPosted }) => {
- const { commentData, isLoading, postComment, resetCommentData } = usePostComment(caseId);
- const { form } = useForm({
- defaultValue: initialCommentValue,
- options: { stripEmptyFields: false },
- schema,
- });
- const { handleCursorChange, handleOnTimelineChange } = useInsertTimeline(
- form,
- 'comment'
- );
+export const AddComment = React.memo(
+ ({ caseId, showLoading = true, onCommentPosted, onCommentSaving }) => {
+ const { commentData, isLoading, postComment, resetCommentData } = usePostComment(caseId);
+ const { form } = useForm({
+ defaultValue: initialCommentValue,
+ options: { stripEmptyFields: false },
+ schema,
+ });
+ const { handleCursorChange, handleOnTimelineChange } = useInsertTimeline(
+ form,
+ 'comment'
+ );
- useEffect(() => {
- if (commentData !== null) {
- onCommentPosted(commentData);
- form.reset();
- resetCommentData();
- }
- }, [commentData]);
+ useEffect(() => {
+ if (commentData !== null) {
+ onCommentPosted(commentData);
+ form.reset();
+ resetCommentData();
+ }
+ }, [commentData]);
- const onSubmit = useCallback(async () => {
- const { isValid, data } = await form.submit();
- if (isValid) {
- await postComment(data);
- }
- }, [form]);
+ const onSubmit = useCallback(async () => {
+ const { isValid, data } = await form.submit();
+ if (isValid) {
+ if (onCommentSaving != null) {
+ onCommentSaving();
+ }
+ await postComment(data);
+ }
+ }, [form]);
- return (
- <>
- {isLoading && }
-
- >
- );
-});
+ return (
+ <>
+ {isLoading && showLoading && }
+
+ >
+ );
+ }
+);
AddComment.displayName = 'AddComment';
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx
index 48fbb4e74c407..d4ec32dfd070b 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx
@@ -18,12 +18,13 @@ export const useGetCasesMockState: UseGetCasesState = {
id: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15',
createdAt: '2020-02-13T19:44:23.627Z',
createdBy: { username: 'elastic' },
- commentIds: [],
comments: [],
description: 'Security banana Issue',
+ externalService: null,
status: 'open',
tags: ['defacement'],
title: 'Another horrible breach',
+ totalComment: 0,
updatedAt: null,
updatedBy: null,
version: 'WzQ3LDFd',
@@ -34,12 +35,13 @@ export const useGetCasesMockState: UseGetCasesState = {
id: '362a5c10-4e99-11ea-9290-35d05cb55c15',
createdAt: '2020-02-13T19:44:13.328Z',
createdBy: { username: 'elastic' },
- commentIds: [],
comments: [],
description: 'Security banana Issue',
+ externalService: null,
status: 'open',
tags: ['phishing'],
title: 'Bad email',
+ totalComment: 0,
updatedAt: null,
updatedBy: null,
version: 'WzQ3LDFd',
@@ -50,12 +52,13 @@ export const useGetCasesMockState: UseGetCasesState = {
id: '34f8b9e0-4e99-11ea-9290-35d05cb55c15',
createdAt: '2020-02-13T19:44:11.328Z',
createdBy: { username: 'elastic' },
- commentIds: [],
comments: [],
description: 'Security banana Issue',
+ externalService: null,
status: 'open',
tags: ['phishing'],
title: 'Bad email',
+ totalComment: 0,
updatedAt: null,
updatedBy: null,
version: 'WzQ3LDFd',
@@ -66,14 +69,15 @@ export const useGetCasesMockState: UseGetCasesState = {
id: '31890e90-4e99-11ea-9290-35d05cb55c15',
createdAt: '2020-02-13T19:44:05.563Z',
createdBy: { username: 'elastic' },
- commentIds: [],
comments: [],
description: 'Security banana Issue',
+ externalService: null,
status: 'closed',
tags: ['phishing'],
title: 'Uh oh',
- updatedAt: '2020-02-13T19:44:13.328Z',
- updatedBy: { username: 'elastic' },
+ totalComment: 0,
+ updatedAt: null,
+ updatedBy: null,
version: 'WzQ3LDFd',
},
{
@@ -82,12 +86,13 @@ export const useGetCasesMockState: UseGetCasesState = {
id: '2f5b3210-4e99-11ea-9290-35d05cb55c15',
createdAt: '2020-02-13T19:44:01.901Z',
createdBy: { username: 'elastic' },
- commentIds: [],
comments: [],
description: 'Security banana Issue',
+ externalService: null,
status: 'open',
tags: ['phishing'],
title: 'Uh oh',
+ totalComment: 0,
updatedAt: null,
updatedBy: null,
version: 'WzQ3LDFd',
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx
index b9e1113c486ad..32a29483e9c75 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx
@@ -35,6 +35,7 @@ const Spacer = styled.span`
const renderStringField = (field: string, dataTestSubj: string) =>
field != null ? {field} : getEmptyTagValue();
+
export const getCasesColumns = (
actions: Array>,
filterStatus: string
@@ -108,11 +109,11 @@ export const getCasesColumns = (
},
{
align: 'right',
- field: 'commentIds',
+ field: 'totalComment',
name: i18n.COMMENTS,
sortable: true,
- render: (comments: Case['commentIds']) =>
- renderStringField(`${comments.length}`, `case-table-column-commentCount`),
+ render: (totalComment: Case['totalComment']) =>
+ renderStringField(`${totalComment}`, `case-table-column-commentCount`),
},
filterStatus === 'open'
? {
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx
index 13869c79c45fd..bdcb87b483851 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx
@@ -95,7 +95,9 @@ describe('AllCases', () => {
.find(`a[data-test-subj="case-details-link"]`)
.first()
.prop('href')
- ).toEqual(`#/link-to/case/${useGetCasesMockState.data.cases[0].id}`);
+ ).toEqual(
+ `#/link-to/case/${useGetCasesMockState.data.cases[0].id}?timerange=(global:(linkTo:!(timeline),timerange:(from:0,fromStr:now-24h,kind:relative,to:1,toStr:now)),timeline:(linkTo:!(global),timerange:(from:0,fromStr:now-24h,kind:relative,to:1,toStr:now)))`
+ );
expect(
wrapper
.find(`a[data-test-subj="case-details-link"]`)
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx
index e7e1e624ccba2..cbb9ddae22d04 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx
@@ -26,6 +26,7 @@ import { useGetCases, UpdateCase } from '../../../../containers/case/use_get_cas
import { useGetCasesStatus } from '../../../../containers/case/use_get_cases_status';
import { useDeleteCases } from '../../../../containers/case/use_delete_cases';
import { EuiBasicTableOnChange } from '../../../detection_engine/rules/types';
+import { useGetUrlSearch } from '../../../../components/navigation/use_get_url_search';
import { Panel } from '../../../../components/panel';
import {
UtilityBar,
@@ -35,19 +36,16 @@ import {
UtilityBarText,
} from '../../../../components/utility_bar';
import { getConfigureCasesUrl, getCreateCaseUrl } from '../../../../components/link_to';
-
import { getBulkItems } from '../bulk_actions';
import { CaseHeaderPage } from '../case_header_page';
import { ConfirmDeleteCaseModal } from '../confirm_delete_case';
import { OpenClosedStats } from '../open_closed_stats';
+import { navTabs } from '../../../home/home_navigations';
import { getActions } from './actions';
import { CasesTableFilters } from './table_filters';
import { useUpdateCases } from '../../../../containers/case/use_bulk_update_case';
-const CONFIGURE_CASES_URL = getConfigureCasesUrl();
-const CREATE_CASE_URL = getCreateCaseUrl();
-
const Div = styled.div`
margin-top: ${({ theme }) => theme.eui.paddingSizes.m};
`;
@@ -78,6 +76,8 @@ const getSortField = (field: string): SortFieldCase => {
return SortFieldCase.createdAt;
};
export const AllCases = React.memo(() => {
+ const urlSearch = useGetUrlSearch(navTabs.case);
+
const {
countClosedCases,
countOpenCases,
@@ -276,12 +276,12 @@ export const AllCases = React.memo(() => {
/>
-
+
{i18n.CONFIGURE_CASES_BUTTON}
-
+
{i18n.CREATE_TITLE}
@@ -342,7 +342,12 @@ export const AllCases = React.memo(() => {
titleSize="xs"
body={i18n.NO_CASES_BODY}
actions={
-
+
{i18n.ADD_NEW_CASE}
}
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_status/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_status/index.tsx
index 9dbd71ea3e34c..0420a71fea907 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_status/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_status/index.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { useCallback } from 'react';
+import React from 'react';
import styled, { css } from 'styled-components';
import {
EuiBadge,
@@ -39,7 +39,7 @@ interface CaseStatusProps {
isSelected: boolean;
status: string;
title: string;
- toggleStatusCase: (status: string) => void;
+ toggleStatusCase: (evt: unknown) => void;
value: string | null;
}
const CaseStatusComp: React.FC = ({
@@ -55,51 +55,46 @@ const CaseStatusComp: React.FC = ({
title,
toggleStatusCase,
value,
-}) => {
- const onChange = useCallback(e => toggleStatusCase(e.target.checked ? 'closed' : 'open'), [
- toggleStatusCase,
- ]);
- return (
-
-
-
-
-
- {i18n.STATUS}
-
-
- {status}
-
-
-
-
- {title}
-
-
-
-
-
-
-
-
-
-
-
-
+}) => (
+
+
+
+
-
+ {i18n.STATUS}
+
+
+ {status}
+
+
+
+
+ {title}
+
+
+
-
-
- );
-};
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
export const CaseStatus = React.memo(CaseStatusComp);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx
index e11441eac3a9d..7aadea1a453a7 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx
@@ -13,7 +13,6 @@ export const caseProps: CaseProps = {
closedAt: null,
closedBy: null,
id: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15',
- commentIds: ['a357c6a0-5435-11ea-b427-fb51a1fcb7b8'],
comments: [
{
comment: 'Solve this fast!',
@@ -24,6 +23,8 @@ export const caseProps: CaseProps = {
username: 'smilovic',
email: 'notmyrealemailfool@elastic.co',
},
+ pushedAt: null,
+ pushedBy: null,
updatedAt: '2020-02-20T23:06:33.798Z',
updatedBy: {
username: 'elastic',
@@ -34,9 +35,11 @@ export const caseProps: CaseProps = {
createdAt: '2020-02-13T19:44:23.627Z',
createdBy: { fullName: null, email: 'testemail@elastic.co', username: 'elastic' },
description: 'Security banana Issue',
+ externalService: null,
status: 'open',
tags: ['defacement'],
title: 'Another horrible breach!!',
+ totalComment: 1,
updatedAt: '2020-02-19T15:02:57.995Z',
updatedBy: {
username: 'elastic',
@@ -44,6 +47,7 @@ export const caseProps: CaseProps = {
version: 'WzQ3LDFd',
},
};
+
export const caseClosedProps: CaseProps = {
...caseProps,
initialData: {
@@ -63,3 +67,21 @@ export const data: Case = {
export const dataClosed: Case = {
...caseClosedProps.initialData,
};
+
+export const caseUserActions = [
+ {
+ actionField: ['comment'],
+ action: 'create',
+ actionAt: '2020-03-20T17:10:09.814Z',
+ actionBy: {
+ fullName: 'Steph Milovic',
+ username: 'smilovic',
+ email: 'notmyrealemailfool@elastic.co',
+ },
+ newValue: 'Solve this fast!',
+ oldValue: null,
+ actionId: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15',
+ caseId: '9b833a50-6acd-11ea-8fad-af86b1071bd9',
+ commentId: 'a357c6a0-5435-11ea-b427-fb51a1fcb7b8',
+ },
+];
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx
index 3f4a83d1bff33..18cc33d8a6d4d 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx
@@ -11,11 +11,18 @@ import { mount } from 'enzyme';
import routeData from 'react-router';
/* eslint-enable @kbn/eslint/module_migration */
import { CaseComponent } from './';
-import { caseProps, caseClosedProps, data, dataClosed } from './__mock__';
+import { caseProps, caseClosedProps, data, dataClosed, caseUserActions } from './__mock__';
import { TestProviders } from '../../../../mock';
import { useUpdateCase } from '../../../../containers/case/use_update_case';
+import { useGetCaseUserActions } from '../../../../containers/case/use_get_case_user_actions';
+import { wait } from '../../../../lib/helpers';
+import { usePushToService } from './push_to_service';
jest.mock('../../../../containers/case/use_update_case');
+jest.mock('../../../../containers/case/use_get_case_user_actions');
+jest.mock('./push_to_service');
const useUpdateCaseMock = useUpdateCase as jest.Mock;
+const useGetCaseUserActionsMock = useGetCaseUserActions as jest.Mock;
+const usePushToServiceMock = usePushToService as jest.Mock;
type Action = 'PUSH' | 'POP' | 'REPLACE';
const pop: Action = 'POP';
const location = {
@@ -47,6 +54,7 @@ const mockLocation = {
describe('CaseView ', () => {
const updateCaseProperty = jest.fn();
+ const fetchCaseUserActions = jest.fn();
/* eslint-disable no-console */
// Silence until enzyme fixed to use ReactTestUtils.act()
const originalError = console.error;
@@ -66,13 +74,31 @@ describe('CaseView ', () => {
updateCaseProperty,
};
+ const defaultUseGetCaseUserActions = {
+ caseUserActions,
+ fetchCaseUserActions,
+ firstIndexPushToService: -1,
+ hasDataToPush: false,
+ isLoading: false,
+ isError: false,
+ lastIndexPushToService: -1,
+ participants: [data.createdBy],
+ };
+
+ const defaultUsePushToServiceMock = {
+ pushButton: <>{'Hello Button'}>,
+ pushCallouts: null,
+ };
+
beforeEach(() => {
jest.resetAllMocks();
useUpdateCaseMock.mockImplementation(() => defaultUpdateCaseState);
jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
+ useGetCaseUserActionsMock.mockImplementation(() => defaultUseGetCaseUserActions);
+ usePushToServiceMock.mockImplementation(() => defaultUsePushToServiceMock);
});
- it('should render CaseComponent', () => {
+ it('should render CaseComponent', async () => {
const wrapper = mount(
@@ -80,6 +106,7 @@ describe('CaseView ', () => {
);
+ await wait();
expect(
wrapper
.find(`[data-test-subj="case-view-title"]`)
@@ -119,7 +146,7 @@ describe('CaseView ', () => {
).toEqual(data.description);
});
- it('should show closed indicators in header when case is closed', () => {
+ it('should show closed indicators in header when case is closed', async () => {
useUpdateCaseMock.mockImplementation(() => ({
...defaultUpdateCaseState,
caseData: dataClosed,
@@ -131,6 +158,7 @@ describe('CaseView ', () => {
);
+ await wait();
expect(wrapper.contains(`[data-test-subj="case-view-createdAt"]`)).toBe(false);
expect(
wrapper
@@ -146,7 +174,7 @@ describe('CaseView ', () => {
).toEqual(dataClosed.status);
});
- it('should dispatch update state when button is toggled', () => {
+ it('should dispatch update state when button is toggled', async () => {
const wrapper = mount(
@@ -154,18 +182,19 @@ describe('CaseView ', () => {
);
-
+ await wait();
wrapper
.find('input[data-test-subj="toggle-case-status"]')
.simulate('change', { target: { checked: true } });
expect(updateCaseProperty).toBeCalledWith({
+ fetchCaseUserActions,
updateKey: 'status',
updateValue: 'closed',
});
});
- it('should render comments', () => {
+ it('should render comments', async () => {
const wrapper = mount(
@@ -173,6 +202,7 @@ describe('CaseView ', () => {
);
+ await wait();
expect(
wrapper
.find(
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx
index 0ac3adeb860ff..5c20b53f5fcb9 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx
@@ -4,10 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { useCallback, useMemo } from 'react';
-import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
-
+import {
+ EuiButtonToggle,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiLoadingContent,
+ EuiLoadingSpinner,
+ EuiHorizontalRule,
+} from '@elastic/eui';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
+
import * as i18n from './translations';
import { Case } from '../../../../containers/case/types';
import { getCaseUrl } from '../../../../components/link_to';
@@ -18,12 +25,16 @@ import { useGetCase } from '../../../../containers/case/use_get_case';
import { UserActionTree } from '../user_action_tree';
import { UserList } from '../user_list';
import { useUpdateCase } from '../../../../containers/case/use_update_case';
+import { useGetUrlSearch } from '../../../../components/navigation/use_get_url_search';
import { WrapperPage } from '../../../../components/wrapper_page';
import { getTypedPayload } from '../../../../containers/case/utils';
import { WhitePageWrapper } from '../wrappers';
import { useBasePath } from '../../../../lib/kibana';
import { CaseStatus } from '../case_status';
+import { navTabs } from '../../../home/home_navigations';
import { SpyRoute } from '../../../../utils/route/spy_routes';
+import { useGetCaseUserActions } from '../../../../containers/case/use_get_case_user_actions';
+import { usePushToService } from './push_to_service';
interface Props {
caseId: string;
@@ -37,6 +48,13 @@ const MyEuiFlexGroup = styled(EuiFlexGroup)`
height: 100%;
`;
+const MyEuiHorizontalRule = styled(EuiHorizontalRule)`
+ margin-left: 48px;
+ &.euiHorizontalRule--full {
+ width: calc(100% - 48px);
+ }
+`;
+
export interface CaseProps {
caseId: string;
initialData: Case;
@@ -45,7 +63,22 @@ export interface CaseProps {
export const CaseComponent = React.memo(({ caseId, initialData }) => {
const basePath = window.location.origin + useBasePath();
const caseLink = `${basePath}/app/siem#/case/${caseId}`;
- const { caseData, isLoading, updateKey, updateCaseProperty } = useUpdateCase(caseId, initialData);
+ const search = useGetUrlSearch(navTabs.case);
+
+ const [initLoadingData, setInitLoadingData] = useState(true);
+ const {
+ caseUserActions,
+ fetchCaseUserActions,
+ firstIndexPushToService,
+ hasDataToPush,
+ isLoading: isLoadingUserActions,
+ lastIndexPushToService,
+ participants,
+ } = useGetCaseUserActions(caseId);
+ const { caseData, isLoading, updateKey, updateCase, updateCaseProperty } = useUpdateCase(
+ caseId,
+ initialData
+ );
// Update Fields
const onUpdateField = useCallback(
@@ -55,6 +88,7 @@ export const CaseComponent = React.memo(({ caseId, initialData }) =>
const titleUpdate = getTypedPayload(updateValue);
if (titleUpdate.length > 0) {
updateCaseProperty({
+ fetchCaseUserActions,
updateKey: 'title',
updateValue: titleUpdate,
});
@@ -64,6 +98,7 @@ export const CaseComponent = React.memo(({ caseId, initialData }) =>
const descriptionUpdate = getTypedPayload(updateValue);
if (descriptionUpdate.length > 0) {
updateCaseProperty({
+ fetchCaseUserActions,
updateKey: 'description',
updateValue: descriptionUpdate,
});
@@ -72,6 +107,7 @@ export const CaseComponent = React.memo(({ caseId, initialData }) =>
case 'tags':
const tagsUpdate = getTypedPayload(updateValue);
updateCaseProperty({
+ fetchCaseUserActions,
updateKey: 'tags',
updateValue: tagsUpdate,
});
@@ -80,6 +116,7 @@ export const CaseComponent = React.memo(({ caseId, initialData }) =>
const statusUpdate = getTypedPayload(updateValue);
if (caseData.status !== updateValue) {
updateCaseProperty({
+ fetchCaseUserActions,
updateKey: 'status',
updateValue: statusUpdate,
});
@@ -88,12 +125,29 @@ export const CaseComponent = React.memo(({ caseId, initialData }) =>
return null;
}
},
- [caseData.status]
+ [fetchCaseUserActions, updateCaseProperty, caseData.status]
);
+ const handleUpdateCase = useCallback(
+ (newCase: Case) => {
+ updateCase(newCase);
+ fetchCaseUserActions(newCase.id);
+ },
+ [updateCase, fetchCaseUserActions]
+ );
+
+ const { pushButton, pushCallouts } = usePushToService({
+ caseId: caseData.id,
+ caseStatus: caseData.status,
+ isNew: caseUserActions.filter(cua => cua.action === 'push-to-service').length === 0,
+ updateCase: handleUpdateCase,
+ });
+
const onSubmitTags = useCallback(newTags => onUpdateField('tags', newTags), [onUpdateField]);
const onSubmitTitle = useCallback(newTitle => onUpdateField('title', newTitle), [onUpdateField]);
- const toggleStatusCase = useCallback(status => onUpdateField('status', status), [onUpdateField]);
-
+ const toggleStatusCase = useCallback(
+ e => onUpdateField('status', e.target.checked ? 'closed' : 'open'),
+ [onUpdateField]
+ );
const spyState = useMemo(() => ({ caseTitle: caseData.title }), [caseData.title]);
const caseStatusData = useMemo(
@@ -111,7 +165,7 @@ export const CaseComponent = React.memo(({ caseId, initialData }) =>
}
: {
'data-test-subj': 'case-view-closedAt',
- value: caseData.closedAt,
+ value: caseData.closedAt ?? '',
title: i18n.CASE_CLOSED,
buttonLabel: i18n.REOPEN_CASE,
status: caseData.status,
@@ -126,14 +180,21 @@ export const CaseComponent = React.memo(({ caseId, initialData }) =>
subject: i18n.EMAIL_SUBJECT(caseData.title),
body: i18n.EMAIL_BODY(caseLink),
}),
- [caseData.title]
+ [caseLink, caseData.title]
);
+
+ useEffect(() => {
+ if (initLoadingData && !isLoadingUserActions) {
+ setInitLoadingData(false);
+ }
+ }, [initLoadingData, isLoadingUserActions]);
+
return (
<>
(({ caseId, initialData }) =>
+ {pushCallouts != null && pushCallouts}
-
+ {initLoadingData && }
+ {!initLoadingData && (
+ <>
+
+
+
+
+
+
+ {hasDataToPush && {pushButton}}
+
+ >
+ )}
+
void;
+}
+
+interface Connector {
+ connectorId: string;
+ connectorName: string;
+}
+
+interface ReturnUsePushToService {
+ pushButton: JSX.Element;
+ pushCallouts: JSX.Element | null;
+}
+
+export const usePushToService = ({
+ caseId,
+ caseStatus,
+ updateCase,
+ isNew,
+}: UsePushToService): ReturnUsePushToService => {
+ const urlSearch = useGetUrlSearch(navTabs.case);
+ const [connector, setConnector] = useState(null);
+
+ const { isLoading, postPushToService } = usePostPushToService();
+
+ const handleSetConnector = useCallback((connectorId: string, connectorName?: string) => {
+ setConnector({ connectorId, connectorName: connectorName ?? '' });
+ }, []);
+
+ const { loading: loadingCaseConfigure } = useCaseConfigure({
+ setConnector: handleSetConnector,
+ });
+
+ const { isLoading: loadingLicense, actionLicense } = useGetActionLicense();
+
+ const handlePushToService = useCallback(() => {
+ if (connector != null) {
+ postPushToService({
+ caseId,
+ ...connector,
+ updateCase,
+ });
+ }
+ }, [caseId, connector, postPushToService, updateCase]);
+
+ const errorsMsg = useMemo(() => {
+ let errors: Array<{ title: string; description: JSX.Element }> = [];
+ if (actionLicense != null && !actionLicense.enabledInLicense) {
+ errors = [
+ ...errors,
+ {
+ title: i18n.PUSH_DISABLE_BY_LICENSE_TITLE,
+ description: (
+
+ {i18n.LINK_CLOUD_DEPLOYMENT}
+
+ ),
+ }}
+ />
+ ),
+ },
+ ];
+ }
+ if (connector == null && !loadingCaseConfigure && !loadingLicense) {
+ errors = [
+ ...errors,
+ {
+ title: i18n.PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE,
+ description: (
+
+ {i18n.LINK_CONNECTOR_CONFIGURE}
+
+ ),
+ }}
+ />
+ ),
+ },
+ ];
+ }
+ if (caseStatus === 'closed') {
+ errors = [
+ ...errors,
+ {
+ title: i18n.PUSH_DISABLE_BECAUSE_CASE_CLOSED_TITLE,
+ description: (
+
+ ),
+ },
+ ];
+ }
+ if (actionLicense != null && !actionLicense.enabledInConfig) {
+ errors = [
+ ...errors,
+ {
+ title: i18n.PUSH_DISABLE_BY_KIBANA_CONFIG_TITLE,
+ description: (
+
+ {'coming soon...'}
+
+ ),
+ }}
+ />
+ ),
+ },
+ ];
+ }
+ return errors;
+ }, [actionLicense, caseStatus, connector, loadingCaseConfigure, loadingLicense, urlSearch]);
+
+ const pushToServiceButton = useMemo(
+ () => (
+ 0}
+ isLoading={isLoading}
+ >
+ {isNew ? i18n.PUSH_SERVICENOW : i18n.UPDATE_PUSH_SERVICENOW}
+
+ ),
+ [isNew, handlePushToService, isLoading, loadingLicense, loadingCaseConfigure, errorsMsg]
+ );
+
+ const objToReturn = useMemo(
+ () => ({
+ pushButton:
+ errorsMsg.length > 0 ? (
+ {errorsMsg[0].description}
}
+ >
+ {pushToServiceButton}
+
+ ) : (
+ <>{pushToServiceButton}>
+ ),
+ pushCallouts: errorsMsg.length > 0 ? : null,
+ }),
+ [errorsMsg, pushToServiceButton]
+ );
+ return objToReturn;
+};
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts
index e5fa3bff51f85..beba80ccd934c 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/translations.ts
@@ -18,17 +18,40 @@ export const SHOWING_CASES = (actionDate: string, actionName: string, userName:
defaultMessage: '{userName} {actionName} on {actionDate}',
});
-export const ADDED_DESCRIPTION = i18n.translate(
- 'xpack.siem.case.caseView.actionLabel.addDescription',
+export const ADDED_FIELD = i18n.translate('xpack.siem.case.caseView.actionLabel.addedField', {
+ defaultMessage: 'added',
+});
+
+export const CHANGED_FIELD = i18n.translate('xpack.siem.case.caseView.actionLabel.changededField', {
+ defaultMessage: 'changed',
+});
+
+export const EDITED_FIELD = i18n.translate('xpack.siem.case.caseView.actionLabel.editedField', {
+ defaultMessage: 'edited',
+});
+
+export const REMOVED_FIELD = i18n.translate('xpack.siem.case.caseView.actionLabel.removedField', {
+ defaultMessage: 'removed',
+});
+
+export const PUSHED_NEW_INCIDENT = i18n.translate(
+ 'xpack.siem.case.caseView.actionLabel.pushedNewIncident',
{
- defaultMessage: 'added description',
+ defaultMessage: 'pushed as new incident',
+ }
+);
+
+export const UPDATE_INCIDENT = i18n.translate(
+ 'xpack.siem.case.caseView.actionLabel.updateIncident',
+ {
+ defaultMessage: 'updated incident',
}
);
-export const EDITED_DESCRIPTION = i18n.translate(
- 'xpack.siem.case.caseView.actionLabel.editDescription',
+export const ADDED_DESCRIPTION = i18n.translate(
+ 'xpack.siem.case.caseView.actionLabel.addDescription',
{
- defaultMessage: 'edited description',
+ defaultMessage: 'added description',
}
);
@@ -52,6 +75,14 @@ export const STATUS = i18n.translate('xpack.siem.case.caseView.statusLabel', {
defaultMessage: 'Status',
});
+export const CASE = i18n.translate('xpack.siem.case.caseView.case', {
+ defaultMessage: 'case',
+});
+
+export const COMMENT = i18n.translate('xpack.siem.case.caseView.comment', {
+ defaultMessage: 'comment',
+});
+
export const CASE_OPENED = i18n.translate('xpack.siem.case.caseView.caseOpened', {
defaultMessage: 'Case opened',
});
@@ -71,3 +102,56 @@ export const EMAIL_BODY = (caseUrl: string) =>
values: { caseUrl },
defaultMessage: 'Case reference: {caseUrl}',
});
+
+export const PUSH_SERVICENOW = i18n.translate('xpack.siem.case.caseView.pushAsServicenowIncident', {
+ defaultMessage: 'Push as ServiceNow incident',
+});
+
+export const UPDATE_PUSH_SERVICENOW = i18n.translate(
+ 'xpack.siem.case.caseView.updatePushAsServicenowIncident',
+ {
+ defaultMessage: 'Update ServiceNow incident',
+ }
+);
+
+export const PUSH_DISABLE_BY_NO_CASE_CONFIG_TITLE = i18n.translate(
+ 'xpack.siem.case.caseView.pushToServiceDisableByNoCaseConfigTitle',
+ {
+ defaultMessage: 'Configure external connector',
+ }
+);
+
+export const PUSH_DISABLE_BECAUSE_CASE_CLOSED_TITLE = i18n.translate(
+ 'xpack.siem.case.caseView.pushToServiceDisableBecauseCaseClosedTitle',
+ {
+ defaultMessage: 'Reopen the case',
+ }
+);
+
+export const PUSH_DISABLE_BY_KIBANA_CONFIG_TITLE = i18n.translate(
+ 'xpack.siem.case.caseView.pushToServiceDisableByConfigTitle',
+ {
+ defaultMessage: 'Enable ServiceNow in Kibana configuration file',
+ }
+);
+
+export const PUSH_DISABLE_BY_LICENSE_TITLE = i18n.translate(
+ 'xpack.siem.case.caseView.pushToServiceDisableByLicenseTitle',
+ {
+ defaultMessage: 'Upgrade to Elastic Platinum',
+ }
+);
+
+export const LINK_CLOUD_DEPLOYMENT = i18n.translate(
+ 'xpack.siem.case.caseView.cloudDeploymentLink',
+ {
+ defaultMessage: 'cloud deployment',
+ }
+);
+
+export const LINK_CONNECTOR_CONFIGURE = i18n.translate(
+ 'xpack.siem.case.caseView.connectorConfigureLink',
+ {
+ defaultMessage: 'connector',
+ }
+);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx
index c8ef6e32595d0..5f99ec362cd5e 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx
@@ -28,7 +28,8 @@ import {
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { ActionConnectorTableItem } from '../../../../../../../../plugins/triggers_actions_ui/public/types';
-
+import { getCaseUrl } from '../../../../components/link_to';
+import { useGetUrlSearch } from '../../../../components/navigation/use_get_url_search';
import {
ClosureType,
CasesConfigurationMapping,
@@ -38,11 +39,9 @@ import { Connectors } from '../configure_cases/connectors';
import { ClosureOptions } from '../configure_cases/closure_options';
import { Mapping } from '../configure_cases/mapping';
import { SectionWrapper } from '../wrappers';
+import { navTabs } from '../../../../pages/home/home_navigations';
import { configureCasesReducer, State } from './reducer';
import * as i18n from './translations';
-import { getCaseUrl } from '../../../../components/link_to';
-
-const CASE_URL = getCaseUrl();
const FormWrapper = styled.div`
${({ theme }) => css`
@@ -73,6 +72,7 @@ const actionTypes: ActionType[] = [
];
const ConfigureCasesComponent: React.FC = () => {
+ const search = useGetUrlSearch(navTabs.case);
const { http, triggers_actions_ui, notifications, application } = useKibana().services;
const [connectorIsValid, setConnectorIsValid] = useState(true);
@@ -113,7 +113,7 @@ const ConfigureCasesComponent: React.FC = () => {
}, []);
const { loading: loadingCaseConfigure, persistLoading, persistCaseConfigure } = useCaseConfigure({
- setConnectorId,
+ setConnector: setConnectorId,
setClosureType,
});
const { loading: isLoadingConnectors, connectors, refetchConnectors } = useConnectors();
@@ -128,9 +128,13 @@ const ConfigureCasesComponent: React.FC = () => {
// TO DO give a warning/error to user when field are not mapped so they have chance to do it
() => {
setActionBarVisible(false);
- persistCaseConfigure({ connectorId, closureType });
+ persistCaseConfigure({
+ connectorId,
+ connectorName: connectors.find(c => c.id === connectorId)?.name ?? '',
+ closureType,
+ });
},
- [connectorId, closureType, mapping]
+ [connectorId, connectors, closureType, mapping]
);
const onChangeConnector = useCallback((newConnectorId: string) => {
@@ -231,7 +235,7 @@ const ConfigureCasesComponent: React.FC = () => {
isDisabled={isLoadingAny}
isLoading={persistLoading}
aria-label="Cancel"
- href={CASE_URL}
+ href={getCaseUrl(search)}
>
{i18n.CANCEL}
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/errors_push_service_callout/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/errors_push_service_callout/index.tsx
new file mode 100644
index 0000000000000..15b50e4b4cd8d
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/errors_push_service_callout/index.tsx
@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { EuiCallOut, EuiButton, EuiDescriptionList, EuiSpacer } from '@elastic/eui';
+import React, { memo, useCallback, useState } from 'react';
+
+import * as i18n from './translations';
+
+interface ErrorsPushServiceCallOut {
+ errors: Array<{ title: string; description: JSX.Element }>;
+}
+
+const ErrorsPushServiceCallOutComponent = ({ errors }: ErrorsPushServiceCallOut) => {
+ const [showCallOut, setShowCallOut] = useState(true);
+ const handleCallOut = useCallback(() => setShowCallOut(false), [setShowCallOut]);
+
+ return showCallOut ? (
+ <>
+
+
+
+ {i18n.DISMISS_CALLOUT}
+
+
+
+ >
+ ) : null;
+};
+
+export const ErrorsPushServiceCallOut = memo(ErrorsPushServiceCallOutComponent);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/errors_push_service_callout/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/errors_push_service_callout/translations.ts
new file mode 100644
index 0000000000000..57712e720f6d0
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/errors_push_service_callout/translations.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const ERROR_PUSH_SERVICE_CALLOUT_TITLE = i18n.translate(
+ 'xpack.siem.case.errorsPushServiceCallOutTitle',
+ {
+ defaultMessage: 'To send cases to external systems, you need to:',
+ }
+);
+
+export const DISMISS_CALLOUT = i18n.translate(
+ 'xpack.siem.case.dismissErrorsPushServiceCallOutTitle',
+ {
+ defaultMessage: 'Dismiss',
+ }
+);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.tsx
new file mode 100644
index 0000000000000..008f4d7048f56
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/helpers.tsx
@@ -0,0 +1,75 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { EuiFlexGroup, EuiFlexItem, EuiBadge, EuiLink } from '@elastic/eui';
+import React from 'react';
+
+import { CaseFullExternalService } from '../../../../../../../../plugins/case/common/api';
+import { CaseUserActions } from '../../../../containers/case/types';
+import * as i18n from '../case_view/translations';
+
+interface LabelTitle {
+ action: CaseUserActions;
+ field: string;
+ firstIndexPushToService: number;
+ index: number;
+}
+
+export const getLabelTitle = ({ action, field, firstIndexPushToService, index }: LabelTitle) => {
+ if (field === 'tags') {
+ return getTagsLabelTitle(action);
+ } else if (field === 'title' && action.action === 'update') {
+ return `${i18n.CHANGED_FIELD.toLowerCase()} ${i18n.CASE_NAME.toLowerCase()} ${i18n.TO} "${
+ action.newValue
+ }"`;
+ } else if (field === 'description' && action.action === 'update') {
+ return `${i18n.EDITED_FIELD} ${i18n.DESCRIPTION.toLowerCase()}`;
+ } else if (field === 'status' && action.action === 'update') {
+ return `${
+ action.newValue === 'open' ? i18n.REOPENED_CASE.toLowerCase() : i18n.CLOSED_CASE.toLowerCase()
+ } ${i18n.CASE}`;
+ } else if (field === 'comment' && action.action === 'update') {
+ return `${i18n.EDITED_FIELD} ${i18n.COMMENT.toLowerCase()}`;
+ } else if (field === 'pushed' && action.action === 'push-to-service' && action.newValue != null) {
+ return getPushedServiceLabelTitle(action, firstIndexPushToService, index);
+ }
+ return '';
+};
+
+const getTagsLabelTitle = (action: CaseUserActions) => (
+
+
+ {action.action === 'add' && i18n.ADDED_FIELD}
+ {action.action === 'delete' && i18n.REMOVED_FIELD} {i18n.TAGS.toLowerCase()}
+
+ {action.newValue != null &&
+ action.newValue.split(',').map(tag => (
+
+ {tag}
+
+ ))}
+
+);
+
+const getPushedServiceLabelTitle = (
+ action: CaseUserActions,
+ firstIndexPushToService: number,
+ index: number
+) => {
+ const pushedVal = JSON.parse(action.newValue ?? '') as CaseFullExternalService;
+ return (
+
+
+ {firstIndexPushToService === index ? i18n.PUSHED_NEW_INCIDENT : i18n.UPDATE_INCIDENT}
+
+
+
+ {pushedVal?.connector_name} {pushedVal?.external_title}
+
+
+
+ );
+};
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx
index 6a3d319561353..8b77186f76f77 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_action_tree/index.tsx
@@ -4,27 +4,54 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { useCallback, useMemo, useState } from 'react';
+import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
+import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react';
+import { useParams } from 'react-router-dom';
+import styled from 'styled-components';
+
import * as i18n from '../case_view/translations';
-import { Case } from '../../../../containers/case/types';
+import { Case, CaseUserActions, Comment } from '../../../../containers/case/types';
import { useUpdateComment } from '../../../../containers/case/use_update_comment';
+import { useCurrentUser } from '../../../../lib/kibana';
+import { AddComment } from '../add_comment';
+import { getLabelTitle } from './helpers';
import { UserActionItem } from './user_action_item';
import { UserActionMarkdown } from './user_action_markdown';
-import { AddComment } from '../add_comment';
-import { useCurrentUser } from '../../../../lib/kibana';
export interface UserActionTreeProps {
data: Case;
+ caseUserActions: CaseUserActions[];
+ fetchUserActions: () => void;
+ firstIndexPushToService: number;
isLoadingDescription: boolean;
+ isLoadingUserActions: boolean;
+ lastIndexPushToService: number;
onUpdateField: (updateKey: keyof Case, updateValue: string | string[]) => void;
}
-const DescriptionId = 'description';
-const NewId = 'newComment';
+const MyEuiFlexGroup = styled(EuiFlexGroup)`
+ margin-bottom: 8px;
+`;
+
+const DESCRIPTION_ID = 'description';
+const NEW_ID = 'newComment';
export const UserActionTree = React.memo(
- ({ data: caseData, onUpdateField, isLoadingDescription }: UserActionTreeProps) => {
+ ({
+ data: caseData,
+ caseUserActions,
+ fetchUserActions,
+ firstIndexPushToService,
+ isLoadingDescription,
+ isLoadingUserActions,
+ lastIndexPushToService,
+ onUpdateField,
+ }: UserActionTreeProps) => {
+ const { commentId } = useParams();
+ const handlerTimeoutId = useRef(0);
+ const [initLoading, setInitLoading] = useState(true);
+ const [selectedOutlineCommentId, setSelectedOutlineCommentId] = useState('');
const { comments, isLoadingIds, updateComment, addPostedComment } = useUpdateComment(
caseData.comments
);
@@ -45,20 +72,54 @@ export const UserActionTree = React.memo(
const handleSaveComment = useCallback(
(id: string, content: string) => {
handleManageMarkdownEditId(id);
- updateComment(caseData.id, id, content);
+ updateComment({
+ caseId: caseData.id,
+ commentId: id,
+ commentUpdate: content,
+ fetchUserActions,
+ });
},
[handleManageMarkdownEditId, updateComment]
);
+ const handleOutlineComment = useCallback(
+ (id: string) => {
+ const moveToTarget = document.getElementById(`${id}-permLink`);
+ if (moveToTarget != null) {
+ const yOffset = -60;
+ const y = moveToTarget.getBoundingClientRect().top + window.pageYOffset + yOffset;
+ window.scrollTo({
+ top: y,
+ behavior: 'smooth',
+ });
+ }
+ window.clearTimeout(handlerTimeoutId.current);
+ setSelectedOutlineCommentId(id);
+ handlerTimeoutId.current = window.setTimeout(() => {
+ setSelectedOutlineCommentId('');
+ window.clearTimeout(handlerTimeoutId.current);
+ }, 2400);
+ },
+ [handlerTimeoutId.current]
+ );
+
+ const handleUpdate = useCallback(
+ (comment: Comment) => {
+ addPostedComment(comment);
+ fetchUserActions();
+ },
+ [addPostedComment, fetchUserActions]
+ );
+
const MarkdownDescription = useMemo(
() => (
{
- handleManageMarkdownEditId(DescriptionId);
- onUpdateField(DescriptionId, content);
+ handleManageMarkdownEditId(DESCRIPTION_ID);
+ onUpdateField(DESCRIPTION_ID, content);
}}
onChangeEditable={handleManageMarkdownEditId}
/>
@@ -67,55 +128,123 @@ export const UserActionTree = React.memo(
);
const MarkdownNewComment = useMemo(
- () => ,
- [caseData.id]
+ () => (
+
+ ),
+ [caseData.id, handleUpdate]
);
+ useEffect(() => {
+ if (initLoading && !isLoadingUserActions && isLoadingIds.length === 0) {
+ setInitLoading(false);
+ if (commentId != null) {
+ handleOutlineComment(commentId);
+ }
+ }
+ }, [commentId, initLoading, isLoadingUserActions, isLoadingIds]);
+
return (
<>
{i18n.ADDED_DESCRIPTION}>}
fullName={caseData.createdBy.fullName ?? caseData.createdBy.username}
markdown={MarkdownDescription}
- onEdit={handleManageMarkdownEditId.bind(null, DescriptionId)}
+ onEdit={handleManageMarkdownEditId.bind(null, DESCRIPTION_ID)}
userName={caseData.createdBy.username}
/>
- {comments.map(comment => (
-
+
+ {caseUserActions.map((action, index) => {
+ if (action.commentId != null && action.action === 'create') {
+ const comment = comments.find(c => c.id === action.commentId);
+ if (comment != null) {
+ return (
+