diff --git a/.ci/Jenkinsfile_flaky b/.ci/Jenkinsfile_flaky
index 8ad02b7162b6a..669395564db44 100644
--- a/.ci/Jenkinsfile_flaky
+++ b/.ci/Jenkinsfile_flaky
@@ -3,10 +3,13 @@
library 'kibana-pipeline-library'
kibanaLibrary.load()
-// Looks like 'oss:ciGroup:1' or 'oss:firefoxSmoke'
-def JOB_PARTS = params.CI_GROUP.split(':')
+def CI_GROUP_PARAM = params.CI_GROUP
+
+// Looks like 'oss:ciGroup:1', 'oss:firefoxSmoke', or 'all:serverMocha'
+def JOB_PARTS = CI_GROUP_PARAM.split(':')
def IS_XPACK = JOB_PARTS[0] == 'xpack'
def JOB = JOB_PARTS[1]
+def NEED_BUILD = JOB != 'serverMocha'
def CI_GROUP = JOB_PARTS.size() > 2 ? JOB_PARTS[2] : ''
def EXECUTIONS = params.NUMBER_EXECUTIONS.toInteger()
def AGENT_COUNT = getAgentCount(EXECUTIONS)
@@ -31,13 +34,15 @@ stage("Kibana Pipeline") {
print "Agent ${agentNumberInside} - ${agentExecutions} executions"
kibanaPipeline.withWorkers('flaky-test-runner', {
- if (!IS_XPACK) {
- kibanaPipeline.buildOss()
- if (CI_GROUP == '1') {
- runbld("./test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh", "Build kbn tp sample panel action for ciGroup1")
+ if (NEED_BUILD) {
+ if (!IS_XPACK) {
+ kibanaPipeline.buildOss()
+ if (CI_GROUP == '1') {
+ runbld("./test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh", "Build kbn tp sample panel action for ciGroup1")
+ }
+ } else {
+ kibanaPipeline.buildXpack()
}
- } else {
- kibanaPipeline.buildXpack()
}
}, getWorkerMap(agentNumberInside, agentExecutions, worker, workerFailures))()
}
@@ -61,7 +66,17 @@ stage("Kibana Pipeline") {
def getWorkerFromParams(isXpack, job, ciGroup) {
if (!isXpack) {
- if (job == 'firefoxSmoke') {
+ if (job == 'serverMocha') {
+ return kibanaPipeline.getPostBuildWorker('serverMocha', {
+ kibanaPipeline.bash(
+ """
+ source src/dev/ci_setup/setup_env.sh
+ node scripts/mocha
+ """,
+ "run `node scripts/mocha`"
+ )
+ })
+ } else if (job == 'firefoxSmoke') {
return kibanaPipeline.getPostBuildWorker('firefoxSmoke', { runbld('./test/scripts/jenkins_firefox_smoke.sh', 'Execute kibana-firefoxSmoke') })
} else if(job == 'visualRegression') {
return kibanaPipeline.getPostBuildWorker('visualRegression', { runbld('./test/scripts/jenkins_visual_regression.sh', 'Execute kibana-visualRegression') })
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 4e2abd5a3db1c..e208dc73c7b4b 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -61,6 +61,7 @@
/config/kibana.yml @elastic/kibana-platform
/x-pack/plugins/features/ @elastic/kibana-platform
/x-pack/plugins/licensing/ @elastic/kibana-platform
+/packages/kbn-config-schema/ @elastic/kibana-platform
# Security
/x-pack/legacy/plugins/security/ @elastic/kibana-security
diff --git a/docs/development/core/server/kibana-plugin-server.irouter.delete.md b/docs/development/core/server/kibana-plugin-server.irouter.delete.md
index 9124b4a1b21c4..5202e0cfd5ebb 100644
--- a/docs/development/core/server/kibana-plugin-server.irouter.delete.md
+++ b/docs/development/core/server/kibana-plugin-server.irouter.delete.md
@@ -9,5 +9,5 @@ Register a route handler for `DELETE` request.
Signature:
```typescript
-delete:
(route: RouteConfig
, handler: RequestHandler
) => void;
+delete: RouteRegistrar;
```
diff --git a/docs/development/core/server/kibana-plugin-server.irouter.get.md b/docs/development/core/server/kibana-plugin-server.irouter.get.md
index 0291906c6fc6b..32552a49cb999 100644
--- a/docs/development/core/server/kibana-plugin-server.irouter.get.md
+++ b/docs/development/core/server/kibana-plugin-server.irouter.get.md
@@ -9,5 +9,5 @@ Register a route handler for `GET` request.
Signature:
```typescript
-get:
(route: RouteConfig
, handler: RequestHandler
) => void;
+get: RouteRegistrar;
```
diff --git a/docs/development/core/server/kibana-plugin-server.irouter.handlelegacyerrors.md b/docs/development/core/server/kibana-plugin-server.irouter.handlelegacyerrors.md
new file mode 100644
index 0000000000000..2367420068064
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.irouter.handlelegacyerrors.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IRouter](./kibana-plugin-server.irouter.md) > [handleLegacyErrors](./kibana-plugin-server.irouter.handlelegacyerrors.md)
+
+## IRouter.handleLegacyErrors property
+
+Wrap a router handler to catch and converts legacy boom errors to proper custom errors.
+
+Signature:
+
+```typescript
+handleLegacyErrors:
(handler: RequestHandler
) => RequestHandler
;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.irouter.md b/docs/development/core/server/kibana-plugin-server.irouter.md
index bbffe1e42f229..b5d3c893d745d 100644
--- a/docs/development/core/server/kibana-plugin-server.irouter.md
+++ b/docs/development/core/server/kibana-plugin-server.irouter.md
@@ -16,9 +16,10 @@ export interface IRouter
| Property | Type | Description |
| --- | --- | --- |
-| [delete](./kibana-plugin-server.irouter.delete.md) | <P extends ObjectType, Q extends ObjectType, B extends ObjectType>(route: RouteConfig<P, Q, B>, handler: RequestHandler<P, Q, B>) => void
| Register a route handler for DELETE
request. |
-| [get](./kibana-plugin-server.irouter.get.md) | <P extends ObjectType, Q extends ObjectType, B extends ObjectType>(route: RouteConfig<P, Q, B>, handler: RequestHandler<P, Q, B>) => void
| Register a route handler for GET
request. |
-| [post](./kibana-plugin-server.irouter.post.md) | <P extends ObjectType, Q extends ObjectType, B extends ObjectType>(route: RouteConfig<P, Q, B>, handler: RequestHandler<P, Q, B>) => void
| Register a route handler for POST
request. |
-| [put](./kibana-plugin-server.irouter.put.md) | <P extends ObjectType, Q extends ObjectType, B extends ObjectType>(route: RouteConfig<P, Q, B>, handler: RequestHandler<P, Q, B>) => void
| Register a route handler for PUT
request. |
+| [delete](./kibana-plugin-server.irouter.delete.md) | RouteRegistrar
| Register a route handler for DELETE
request. |
+| [get](./kibana-plugin-server.irouter.get.md) | RouteRegistrar
| Register a route handler for GET
request. |
+| [handleLegacyErrors](./kibana-plugin-server.irouter.handlelegacyerrors.md) | <P extends ObjectType, Q extends ObjectType, B extends ObjectType>(handler: RequestHandler<P, Q, B>) => RequestHandler<P, Q, B>
| Wrap a router handler to catch and converts legacy boom errors to proper custom errors. |
+| [post](./kibana-plugin-server.irouter.post.md) | RouteRegistrar
| Register a route handler for POST
request. |
+| [put](./kibana-plugin-server.irouter.put.md) | RouteRegistrar
| Register a route handler for PUT
request. |
| [routerPath](./kibana-plugin-server.irouter.routerpath.md) | string
| Resulted path |
diff --git a/docs/development/core/server/kibana-plugin-server.irouter.post.md b/docs/development/core/server/kibana-plugin-server.irouter.post.md
index e97a32e433ce9..cd655c9ce0dc8 100644
--- a/docs/development/core/server/kibana-plugin-server.irouter.post.md
+++ b/docs/development/core/server/kibana-plugin-server.irouter.post.md
@@ -9,5 +9,5 @@ Register a route handler for `POST` request.
Signature:
```typescript
-post:
(route: RouteConfig
, handler: RequestHandler
) => void;
+post: RouteRegistrar;
```
diff --git a/docs/development/core/server/kibana-plugin-server.irouter.put.md b/docs/development/core/server/kibana-plugin-server.irouter.put.md
index 25db91e389939..e553d4b79dd2b 100644
--- a/docs/development/core/server/kibana-plugin-server.irouter.put.md
+++ b/docs/development/core/server/kibana-plugin-server.irouter.put.md
@@ -9,5 +9,5 @@ Register a route handler for `PUT` request.
Signature:
```typescript
-put:
(route: RouteConfig
, handler: RequestHandler
) => void;
+put: RouteRegistrar;
```
diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md
index b7054dde95fc1..13e0ea3645f26 100644
--- a/docs/development/core/server/kibana-plugin-server.md
+++ b/docs/development/core/server/kibana-plugin-server.md
@@ -171,6 +171,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [ResponseErrorAttributes](./kibana-plugin-server.responseerrorattributes.md) | Additional data to provide error details. |
| [ResponseHeaders](./kibana-plugin-server.responseheaders.md) | Http response headers to set. |
| [RouteMethod](./kibana-plugin-server.routemethod.md) | The set of common HTTP methods supported by Kibana routing. |
+| [RouteRegistrar](./kibana-plugin-server.routeregistrar.md) | Handler to declare a route. |
| [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | Type definition for a Saved Object attribute value |
| [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) |
| [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError()
helpers exposed at SavedObjectsErrorHelpers
should be used to understand and manage error responses from the SavedObjectsClient
.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type
or doing substring checks on error.body.error.reason
, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index
setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) |
diff --git a/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.md b/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.md
index 36d803ddea618..248726e26f393 100644
--- a/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.md
+++ b/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.md
@@ -16,6 +16,5 @@ export interface PluginsServiceSetup
| Property | Type | Description |
| --- | --- | --- |
| [contracts](./kibana-plugin-server.pluginsservicesetup.contracts.md) | Map<PluginName, unknown>
| |
-| [uiPluginConfigs](./kibana-plugin-server.pluginsservicesetup.uipluginconfigs.md) | Map<PluginName, Observable<unknown>>
| |
-| [uiPlugins](./kibana-plugin-server.pluginsservicesetup.uiplugins.md) | {
public: Map<PluginName, DiscoveredPlugin>;
internal: Map<PluginName, DiscoveredPluginInternal>;
}
| |
+| [uiPlugins](./kibana-plugin-server.pluginsservicesetup.uiplugins.md) | {
internal: Map<PluginName, InternalPluginInfo>;
public: Map<PluginName, DiscoveredPlugin>;
browserConfigs: Map<PluginName, Observable<unknown>>;
}
| |
diff --git a/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.uipluginconfigs.md b/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.uipluginconfigs.md
deleted file mode 100644
index 4bd57b873043e..0000000000000
--- a/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.uipluginconfigs.md
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) > [uiPluginConfigs](./kibana-plugin-server.pluginsservicesetup.uipluginconfigs.md)
-
-## PluginsServiceSetup.uiPluginConfigs property
-
-Signature:
-
-```typescript
-uiPluginConfigs: Map>;
-```
diff --git a/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.uiplugins.md b/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.uiplugins.md
index fa286dfb59092..7c47304cb9bf6 100644
--- a/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.uiplugins.md
+++ b/docs/development/core/server/kibana-plugin-server.pluginsservicesetup.uiplugins.md
@@ -8,7 +8,8 @@
```typescript
uiPlugins: {
+ internal: Map;
public: Map;
- internal: Map;
+ browserConfigs: Map>;
};
```
diff --git a/docs/development/core/server/kibana-plugin-server.routeregistrar.md b/docs/development/core/server/kibana-plugin-server.routeregistrar.md
new file mode 100644
index 0000000000000..535927dc73743
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.routeregistrar.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteRegistrar](./kibana-plugin-server.routeregistrar.md)
+
+## RouteRegistrar type
+
+Handler to declare a route.
+
+Signature:
+
+```typescript
+export declare type RouteRegistrar = (route: RouteConfig
, handler: RequestHandler
) => void;
+```
diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc
index b852d38c05dc9..a2c05e4d87325 100644
--- a/docs/settings/security-settings.asciidoc
+++ b/docs/settings/security-settings.asciidoc
@@ -20,7 +20,7 @@ are enabled.
Do not set this to `false`; it disables the login form, user and role management
screens, and authorization using <>. To disable
{security-features} entirely, see
-{ref}/security-settings.html[{es} security settings].
+{ref}/security-settings.html[{es} security settings].
`xpack.security.audit.enabled`::
Set to `true` to enable audit logging for security events. By default, it is set
@@ -40,7 +40,7 @@ An arbitrary string of 32 characters or more that is used to encrypt credentials
in a cookie. It is crucial that this key is not exposed to users of {kib}. By
default, a value is automatically generated in memory. If you use that default
behavior, all sessions are invalidated when {kib} restarts.
-In addition, high-availability deployments of {kib} will behave unexpectedly
+In addition, high-availability deployments of {kib} will behave unexpectedly
if this setting isn't the same for all instances of {kib}.
`xpack.security.secureCookies`::
@@ -49,7 +49,16 @@ is set to `true` if `server.ssl.certificate` and `server.ssl.key` are set. Set
this to `true` if SSL is configured outside of {kib} (for example, you are
routing requests through a load balancer or proxy).
-`xpack.security.sessionTimeout`::
+`xpack.security.session.idleTimeout`::
Sets the session duration (in milliseconds). By default, sessions stay active
-until the browser is closed. When this is set to an explicit timeout, closing the
-browser still requires the user to log back in to {kib}.
+until the browser is closed. When this is set to an explicit idle timeout, closing
+the browser still requires the user to log back in to {kib}.
+
+`xpack.security.session.lifespan`::
+Sets the maximum duration (in milliseconds), also known as "absolute timeout". By
+default, a session can be renewed indefinitely. When this value is set, a session
+will end once its lifespan is exceeded, even if the user is not idle. NOTE: if
+`idleTimeout` is not set, this setting will still cause sessions to expire.
+
+`xpack.security.loginAssistanceMessage`::
+Adds a message to the login screen. Useful for displaying information about maintenance windows, links to corporate sign up pages etc.
diff --git a/docs/user/security/authentication/index.asciidoc b/docs/user/security/authentication/index.asciidoc
index c2b1adc5e1b92..e6b70fa059fc2 100644
--- a/docs/user/security/authentication/index.asciidoc
+++ b/docs/user/security/authentication/index.asciidoc
@@ -188,9 +188,10 @@ The following sections apply both to <> and <>
Once the user logs in to {kib} Single Sign-On, either using SAML or OpenID Connect, {es} issues access and refresh tokens
that {kib} encrypts and stores them in its own session cookie. This way, the user isn't redirected to the Identity Provider
-for every request that requires authentication. It also means that the {kib} session depends on the `xpack.security.sessionTimeout`
-setting and the user is automatically logged out if the session expires. An access token that is stored in the session cookie
-can expire, in which case {kib} will automatically renew it with a one-time-use refresh token and store it in the same cookie.
+for every request that requires authentication. It also means that the {kib} session depends on the <> settings, and the user is automatically logged
+out if the session expires. An access token that is stored in the session cookie can expire, in which case {kib} will
+automatically renew it with a one-time-use refresh token and store it in the same cookie.
{kib} can only determine if an access token has expired if it receives a request that requires authentication. If both access
and refresh tokens have already expired (for example, after 24 hours of inactivity), {kib} initiates a new "handshake" and
diff --git a/docs/user/security/securing-kibana.asciidoc b/docs/user/security/securing-kibana.asciidoc
index 1c74bd98642a7..2fbc6ba4f1ee6 100644
--- a/docs/user/security/securing-kibana.asciidoc
+++ b/docs/user/security/securing-kibana.asciidoc
@@ -56,16 +56,31 @@ xpack.security.encryptionKey: "something_at_least_32_characters"
For more information, see <>.
--
-. Optional: Change the default session duration. By default, sessions stay
-active until the browser is closed. To change the duration, set the
-`xpack.security.sessionTimeout` property in the `kibana.yml` configuration file.
-The timeout is specified in milliseconds. For example, set the timeout to 600000
-to expire sessions after 10 minutes:
+. Optional: Set a timeout to expire idle sessions. By default, a session stays
+active until the browser is closed. To define a sliding session expiration, set
+the `xpack.security.session.idleTimeout` property in the `kibana.yml`
+configuration file. The idle timeout is specified in milliseconds. For example,
+set the idle timeout to 600000 to expire idle sessions after 10 minutes:
+
--
[source,yaml]
--------------------------------------------------------------------------------
-xpack.security.sessionTimeout: 600000
+xpack.security.session.idleTimeout: 600000
+--------------------------------------------------------------------------------
+--
+
+. Optional: Change the maximum session duration or "lifespan" -- also known as
+the "absolute timeout". By default, a session stays active until the browser is
+closed. If an idle timeout is defined, a session can still be extended
+indefinitely. To define a maximum session lifespan, set the
+`xpack.security.session.lifespan` property in the `kibana.yml` configuration
+file. The lifespan is specified in milliseconds. For example, set the lifespan
+to 28800000 to expire sessions after 8 hours:
++
+--
+[source,yaml]
+--------------------------------------------------------------------------------
+xpack.security.session.lifespan: 28800000
--------------------------------------------------------------------------------
--
diff --git a/packages/kbn-config-schema/README.md b/packages/kbn-config-schema/README.md
new file mode 100644
index 0000000000000..8ba2c43b5e1fe
--- /dev/null
+++ b/packages/kbn-config-schema/README.md
@@ -0,0 +1,511 @@
+# `@kbn/config-schema` — The Kibana config validation library
+
+`@kbn/config-schema` is a TypeScript library inspired by Joi and designed to allow run-time validation of the
+Kibana configuration entries providing developers with a fully typed model of the validated data.
+
+## Table of Contents
+
+- [Why `@kbn/config-schema`?](#why-kbnconfig-schema)
+- [Schema building blocks](#schema-building-blocks)
+ - [Basic types](#basic-types)
+ - [`schema.string()`](#schemastring)
+ - [`schema.number()`](#schemanumber)
+ - [`schema.boolean()`](#schemaboolean)
+ - [`schema.literal()`](#schemaliteral)
+ - [Composite types](#composite-types)
+ - [`schema.arrayOf()`](#schemaarrayof)
+ - [`schema.object()`](#schemaobject)
+ - [`schema.recordOf()`](#schemarecordof)
+ - [`schema.mapOf()`](#schemamapof)
+ - [Advanced types](#advanced-types)
+ - [`schema.oneOf()`](#schemaoneof)
+ - [`schema.any()`](#schemaany)
+ - [`schema.maybe()`](#schemamaybe)
+ - [`schema.nullable()`](#schemanullable)
+ - [`schema.never()`](#schemanever)
+ - [`schema.uri()`](#schemauri)
+ - [`schema.byteSize()`](#schemabytesize)
+ - [`schema.duration()`](#schemaduration)
+ - [`schema.conditional()`](#schemaconditional)
+ - [References](#references)
+ - [`schema.contextRef()`](#schemacontextref)
+ - [`schema.siblingRef()`](#schemasiblingref)
+- [Custom validation](#custom-validation)
+- [Default values](#default-values)
+
+## Why `@kbn/config-schema`?
+
+Validation of externally supplied data is very important for Kibana. Especially if this data is used to configure how it operates.
+
+There are a number of reasons why we decided to roll our own solution for the configuration validation:
+
+* **Limited API surface** - having a future rich library is awesome, but it's a really hard task to audit such library and make sure everything is sane and secure enough. As everyone knows complexity is the enemy of security and hence we'd like to have a full control over what exactly we expose and commit to maintain.
+* **Custom error messages** - detailed validation error messages are a great help to developers, but at the same time they can contain information that's way too sensitive to expose to everyone. We'd like to control these messages and make them only as detailed as really needed. For example, we don't want validation error messages to contain the passwords for internal users to show-up in the logs. These logs are commonly ingested into Elasticsearch, and accessible to a large number of users which shouldn't have access to the internal user's password.
+* **Type information** - having run-time guarantees is great, but additionally having compile-time guarantees is even better. We'd like to provide developers with a fully typed model of the validated data so that it's harder to misuse it _after_ validation.
+* **Upgradability** - no matter how well a validation library is implemented, it will have bugs and may need to be improved at some point anyway. Some external libraries are very well supported, some aren't or won't be in the future. It's always a risk to depend on an external party with their own release cadence when you need to quickly fix a security vulnerability in a patch version. We'd like to have a better control over lifecycle of such an important piece of our codebase.
+
+## Schema building blocks
+
+The schema is composed of one or more primitives depending on the shape of the data you'd like to validate.
+
+```typescript
+const simpleStringSchema = schema.string();
+const moreComplexObjectSchema = schema.object({ name: schema.string() });
+```
+
+Every schema instance has a `validate` method that is used to perform a validation of the data according to the schema. This method accepts three arguments:
+
+* `data: any` - **required**, data to be validated with the schema
+* `context: Record` - **optional**, object whose properties can be referenced by the [context references](#schemacontextref)
+* `namespace: string` - **optional**, arbitrary string that is used to prefix every error message thrown during validation
+
+```typescript
+const valueSchema = schema.object({
+ isEnabled: schema.boolean(),
+ env: schema.string({ defaultValue: schema.contextRef('envName') }),
+});
+
+expect(valueSchema.validate({ isEnabled: true, env: 'prod' })).toEqual({
+ isEnabled: true,
+ env: 'prod',
+});
+
+// Use default value for `env` from context via reference
+expect(valueSchema.validate({ isEnabled: true }, { envName: 'staging' })).toEqual({
+ isEnabled: true,
+ env: 'staging',
+});
+
+// Fail because of type mismatch
+expect(() =>
+ valueSchema.validate({ isEnabled: 'non-bool' }, { envName: 'staging' })
+).toThrowError(
+ '[isEnabled]: expected value of type [boolean] but got [string]'
+);
+
+// Fail because of type mismatch and prefix error with a custom namespace
+expect(() =>
+ valueSchema.validate({ isEnabled: 'non-bool' }, { envName: 'staging' }, 'configuration')
+).toThrowError(
+ '[configuration.isEnabled]: expected value of type [boolean] but got [string]'
+);
+```
+
+__Notes:__
+* `validate` method throws as soon as the first schema violation is encountered, no further validation is performed.
+* when you retrieve configuration within a Kibana plugin `validate` function is called by the Core automatically providing appropriate namespace and context variables (environment name, package info etc.).
+
+### Basic types
+
+#### `schema.string()`
+
+Validates input data as a string.
+
+__Output type:__ `string`
+
+__Options:__
+ * `defaultValue: string | Reference | (() => string)` - defines a default value, see [Default values](#default-values) section for more details.
+ * `validate: (value: string) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details.
+ * `minLength: number` - defines a minimum length the string should have.
+ * `maxLength: number` - defines a maximum length the string should have.
+ * `hostname: boolean` - indicates whether the string should be validated as a valid hostname (per [RFC 1123](https://tools.ietf.org/html/rfc1123)).
+
+__Usage:__
+```typescript
+const valueSchema = schema.string({ maxLength: 10 });
+```
+
+__Notes:__
+* By default `schema.string()` allows empty strings, to prevent that use non-zero value for `minLength` option.
+* To validate a string using a regular expression use a custom validator function, see [Custom validation](#custom-validation) section for more details.
+
+#### `schema.number()`
+
+Validates input data as a number.
+
+__Output type:__ `number`
+
+__Options:__
+ * `defaultValue: number | Reference | (() => number)` - defines a default value, see [Default values](#default-values) section for more details.
+ * `validate: (value: number) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details.
+ * `min: number` - defines a minimum value the number should have.
+ * `max: number` - defines a maximum value the number should have.
+
+__Usage:__
+```typescript
+const valueSchema = schema.number({ max: 10 });
+```
+
+__Notes:__
+* The `schema.number()` also supports a string as input if it can be safely coerced into number.
+
+#### `schema.boolean()`
+
+Validates input data as a boolean.
+
+__Output type:__ `boolean`
+
+__Options:__
+ * `defaultValue: boolean | Reference | (() => boolean)` - defines a default value, see [Default values](#default-values) section for more details.
+ * `validate: (value: boolean) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details.
+
+__Usage:__
+```typescript
+const valueSchema = schema.boolean({ defaultValue: false });
+```
+
+#### `schema.literal()`
+
+Validates input data as a [string](https://www.typescriptlang.org/docs/handbook/advanced-types.html#string-literal-types), [numeric](https://www.typescriptlang.org/docs/handbook/advanced-types.html#numeric-literal-types) or boolean literal.
+
+__Output type:__ `string`, `number` or `boolean` literals
+
+__Options:__
+ * `defaultValue: TLiteral | Reference | (() => TLiteral)` - defines a default value, see [Default values](#default-values) section for more details.
+ * `validate: (value: TLiteral) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details.
+
+__Usage:__
+```typescript
+const valueSchema = [
+ schema.literal('stringLiteral'),
+ schema.literal(100500),
+ schema.literal(false),
+];
+```
+
+### Composite types
+
+#### `schema.arrayOf()`
+
+Validates input data as a homogeneous array with the values being validated against predefined schema.
+
+__Output type:__ `TValue[]`
+
+__Options:__
+ * `defaultValue: TValue[] | Reference | (() => TValue[])` - defines a default value, see [Default values](#default-values) section for more details.
+ * `validate: (value: TValue[]) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details.
+ * `minSize: number` - defines a minimum size the array should have.
+ * `maxSize: number` - defines a maximum size the array should have.
+
+__Usage:__
+```typescript
+const valueSchema = schema.arrayOf(schema.number());
+```
+
+#### `schema.object()`
+
+Validates input data as an object with a predefined set of properties.
+
+__Output type:__ `{ [K in keyof TProps]: TypeOf } as TObject`
+
+__Options:__
+ * `defaultValue: TObject | Reference | (() => TObject)` - defines a default value, see [Default values](#default-values) section for more details.
+ * `validate: (value: TObject) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details.
+ * `allowUnknowns: boolean` - indicates whether unknown object properties should be allowed. It's `false` by default.
+
+__Usage:__
+```typescript
+const valueSchema = schema.object({
+ isEnabled: schema.boolean({ defaultValue: false }),
+ name: schema.string({ minLength: 10 }),
+});
+```
+
+__Notes:__
+* Using `allowUnknowns` is discouraged and should only be used in exceptional circumstances. Consider using `schema.recordOf()` instead.
+* Currently `schema.object()` always has a default value of `{}`, but this may change in the near future. Try to not rely on this behaviour and specify default value explicitly or use `schema.maybe()` if the value is optional.
+
+#### `schema.recordOf()`
+
+Validates input data as an object with the keys and values being validated against predefined schema.
+
+__Output type:__ `Record`
+
+__Options:__
+ * `defaultValue: Record | Reference> | (() => Record)` - defines a default value, see [Default values](#default-values) section for more details.
+ * `validate: (value: Record) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details.
+
+__Usage:__
+```typescript
+const valueSchema = schema.recordOf(schema.string(), schema.number());
+```
+
+__Notes:__
+* You can use a union of literal types as a record's key schema to restrict record to a specific set of keys, e.g. `schema.oneOf([schema.literal('isEnabled'), schema.literal('name')])`.
+
+#### `schema.mapOf()`
+
+Validates input data as a map with the keys and values being validated against the predefined schema.
+
+__Output type:__ `Map`
+
+__Options:__
+ * `defaultValue: Map | Reference> | (() => Map)` - defines a default value, see [Default values](#default-values) section for more details.
+ * `validate: (value: Map) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details.
+
+__Usage:__
+```typescript
+const valueSchema = schema.mapOf(schema.string(), schema.number());
+```
+
+### Advanced types
+
+#### `schema.oneOf()`
+
+Allows a list of alternative schemas to validate input data against.
+
+__Output type:__ `TValue1 | TValue2 | TValue3 | ..... as TUnion`
+
+__Options:__
+ * `defaultValue: TUnion | Reference | (() => TUnion)` - defines a default value, see [Default values](#default-values) section for more details.
+ * `validate: (value: TUnion) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details.
+
+__Usage:__
+```typescript
+const valueSchema = schema.oneOf([schema.literal('∞'), schema.number()]);
+```
+
+__Notes:__
+* Since the result data type is a type union you should use various TypeScript type guards to get the exact type.
+
+#### `schema.any()`
+
+Indicates that input data shouldn't be validated and returned as is.
+
+__Output type:__ `any`
+
+__Options:__
+ * `defaultValue: any | Reference | (() => any)` - defines a default value, see [Default values](#default-values) section for more details.
+ * `validate: (value: any) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details.
+
+__Usage:__
+```typescript
+const valueSchema = schema.any();
+```
+
+__Notes:__
+* `schema.any()` is essentially an escape hatch for the case when your data can __really__ have any type and should be avoided at all costs.
+
+#### `schema.maybe()`
+
+Indicates that input data is optional and may not be present.
+
+__Output type:__ `T | undefined`
+
+__Usage:__
+```typescript
+const valueSchema = schema.maybe(schema.string());
+```
+
+__Notes:__
+* Don't use `schema.maybe()` if a nested type defines a default value.
+
+#### `schema.nullable()`
+
+Indicates that input data is optional and defaults to `null` if it's not present.
+
+__Output type:__ `T | null`
+
+__Usage:__
+```typescript
+const valueSchema = schema.nullable(schema.string());
+```
+
+__Notes:__
+* `schema.nullable()` also treats explicitly specified `null` as a valid input.
+
+#### `schema.never()`
+
+Indicates that input data is forbidden.
+
+__Output type:__ `never`
+
+__Usage:__
+```typescript
+const valueSchema = schema.never();
+```
+
+__Notes:__
+* `schema.never()` has a very limited application and usually used within [conditional schemas](#schemaconditional) to fully or partially forbid input data.
+
+#### `schema.uri()`
+
+Validates input data as a proper URI string (per [RFC 3986](https://tools.ietf.org/html/rfc3986)).
+
+__Output type:__ `string`
+
+__Options:__
+ * `defaultValue: string | Reference | (() => string)` - defines a default value, see [Default values](#default-values) section for more details.
+ * `validate: (value: string) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details.
+ * `scheme: string | string[]` - limits allowed URI schemes to the one(s) defined here.
+
+__Usage:__
+```typescript
+const valueSchema = schema.uri({ scheme: 'https' });
+```
+
+__Notes:__
+* Prefer using `schema.uri()` for all URI validations even though it may be possible to replicate it with a custom validator for `schema.string()`.
+
+#### `schema.byteSize()`
+
+Validates input data as a proper digital data size.
+
+__Output type:__ `ByteSizeValue`
+
+__Options:__
+ * `defaultValue: ByteSizeValue | string | number | Reference | (() => ByteSizeValue | string | number)` - defines a default value, see [Default values](#default-values) section for more details.
+ * `validate: (value: ByteSizeValue | string | number) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details.
+ * `min: ByteSizeValue | string | number` - defines a minimum value the size should have.
+ * `max: ByteSizeValue | string | number` - defines a maximum value the size should have.
+
+__Usage:__
+```typescript
+const valueSchema = schema.byteSize({ min: '3kb' });
+```
+
+__Notes:__
+* The string value for `schema.byteSize()` and its options supports the following prefixes: `b`, `kb`, `mb`, `gb` and `tb`.
+* The number value is treated as a number of bytes and hence should be a positive integer, e.g. `100` is equal to `'100b'`.
+* Currently you cannot specify zero bytes with a string format and should use number `0` instead.
+
+#### `schema.duration()`
+
+Validates input data as a proper [duration](https://momentjs.com/docs/#/durations/).
+
+__Output type:__ `moment.Duration`
+
+__Options:__
+ * `defaultValue: moment.Duration | string | number | Reference | (() => moment.Duration | string | number)` - defines a default value, see [Default values](#default-values) section for more details.
+ * `validate: (value: moment.Duration | string | number) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details.
+
+__Usage:__
+```typescript
+const valueSchema = schema.duration({ defaultValue: '70ms' });
+```
+
+__Notes:__
+* The string value for `schema.duration()` supports the following prefixes: `ms`, `s`, `m`, `h`, `d`, `w`, `M` and `Y`.
+* The number value is treated as a number of milliseconds and hence should be a positive integer, e.g. `100` is equal to `'100ms'`.
+
+#### `schema.conditional()`
+
+Allows a specified condition that is evaluated _at the validation time_ and results in either one or another input validation schema.
+
+The first argument is always a [reference](#references) while the second one can be:
+* another reference, in this cases both references are "dereferenced" and compared
+* schema, in this case the schema is used to validate "dereferenced" value of the first reference
+* value, in this case "dereferenced" value of the first reference is compared to that value
+
+The third argument is a schema that should be used if the result of the aforementioned comparison evaluates to `true`, otherwise `schema.conditional()` should fallback
+to the schema provided as the fourth argument.
+
+__Output type:__ `TTrueResult | TFalseResult`
+
+__Options:__
+ * `defaultValue: TTrueResult | TFalseResult | Reference | (() => TTrueResult | TFalseResult` - defines a default value, see [Default values](#default-values) section for more details.
+ * `validate: (value: TTrueResult | TFalseResult) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details.
+
+__Usage:__
+```typescript
+const valueSchema = schema.object({
+ key: schema.oneOf([schema.literal('number'), schema.literal('string')]),
+ value: schema.conditional(schema.siblingRef('key'), 'number', schema.number(), schema.string()),
+});
+```
+
+__Notes:__
+* Conditional schemas may be hard to read and understand and hence should be used only sparingly.
+
+### References
+
+#### `schema.contextRef()`
+
+Defines a reference to the value specified through the validation context. Context reference is only used as part of a [conditional schema](#schemaconditional) or as a default value for any other schema.
+
+__Output type:__ `TReferenceValue`
+
+__Usage:__
+```typescript
+const valueSchema = schema.object({
+ env: schema.string({ defaultValue: schema.contextRef('envName') }),
+});
+valueSchema.validate({}, { envName: 'dev' });
+```
+
+__Notes:__
+* The `@kbn/config-schema` neither validates nor coerces the "dereferenced" value and the developer is responsible for making sure that it has the appropriate type.
+* The root context that Kibana provides during config validation includes lots of useful properties like `environment name` that can be used to provide a strict schema for production and more relaxed one for development.
+
+#### `schema.siblingRef()`
+
+Defines a reference to the value of the sibling key. Sibling references are only used a part of [conditional schema](#schemaconditional) or as a default value for any other schema.
+
+__Output type:__ `TReferenceValue`
+
+__Usage:__
+```typescript
+const valueSchema = schema.object({
+ node: schema.object({ tag: schema.string() }),
+ env: schema.string({ defaultValue: schema.siblingRef('node.tag') }),
+});
+```
+
+__Notes:__
+* The `@kbn/config-schema` neither validates nor coerces the "dereferenced" value and the developer is responsible for making sure that it has the appropriate type.
+
+## Custom validation
+
+Using built-in schema primitives may not be enough in some scenarios or sometimes the attempt to model complex schemas with built-in primitives only may result in unreadable code.
+For these cases `@kbn/config-schema` provides a way to specify a custom validation function for almost any schema building block through the `validate` option.
+
+For example `@kbn/config-schema` doesn't have a dedicated primitive for the `RegExp` based validation currently, but you can easily do that with a custom `validate` function:
+
+```typescript
+const valueSchema = schema.string({
+ minLength: 3,
+ validate(value) {
+ if (!/^[a-z0-9_-]+$/.test(value)) {
+ return `must be lower case, a-z, 0-9, '_', and '-' are allowed`;
+ }
+ },
+});
+
+// ...or if you use that construct a lot...
+
+const regexSchema = (regex: RegExp) => schema.string({
+ validate: value => regex.test(value) ? undefined : `must match "${regex.toString()}"`,
+});
+const valueSchema = regexSchema(/^[a-z0-9_-]+$/);
+```
+
+Custom validation function is run _only after_ all built-in validations passed. It should either return a `string` as an error message
+to denote the failed validation or not return anything at all (`void`) otherwise. Please also note that `validate` function is synchronous.
+
+Another use case for custom validation functions is when the schema depends on some run-time data:
+
+```typescript
+const gesSchema = randomRunTimeSeed => schema.string({
+ validate: value => value !== randomRunTimeSeed ? 'value is not allowed' : undefined
+});
+
+const schema = gesSchema('some-random-run-time-data');
+```
+
+## Default values
+
+If you have an optional config field that you can have a default value for you may want to consider using dedicated `defaultValue` option to not
+deal with "defined or undefined"-like checks all over the place in your code. You have three options to provide a default value for almost any schema primitive:
+
+* plain value that's known at the compile time
+* [reference](#references) to a value that will be "dereferenced" at the validation time
+* function that is invoked at the validation time and returns a plain value
+
+```typescript
+const valueSchemaWithPlainValueDefault = schema.string({ defaultValue: 'n/a' });
+const valueSchemaWithReferencedValueDefault = schema.string({ defaultValue: schema.contextRef('env') });
+const valueSchemaWithFunctionEvaluatedDefault = schema.string({ defaultValue: () => Math.random().toString() });
+```
+
+__Notes:__
+* `@kbn/config-schema` neither validates nor coerces default value and developer is responsible for making sure that it has the appropriate type.
diff --git a/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js b/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js
index b43dcd80b4462..aa6611f3b6738 100644
--- a/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js
+++ b/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js
@@ -70,11 +70,21 @@ describe(`running the plugin-generator via 'node scripts/generate_plugin.js plug
describe(`then running`, () => {
it(`'yarn test:browser' should exit 0`, async () => {
- await execa('yarn', ['test:browser'], { cwd: generatedPath });
+ await execa('yarn', ['test:browser'], {
+ cwd: generatedPath,
+ env: {
+ DISABLE_JUNIT_REPORTER: '1',
+ },
+ });
});
it(`'yarn test:server' should exit 0`, async () => {
- await execa('yarn', ['test:server'], { cwd: generatedPath });
+ await execa('yarn', ['test:server'], {
+ cwd: generatedPath,
+ env: {
+ DISABLE_JUNIT_REPORTER: '1',
+ },
+ });
});
it(`'yarn build' should exit 0`, async () => {
diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts
index 52672d5f039fb..4530b61423620 100644
--- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts
+++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts
@@ -143,7 +143,7 @@ export const schema = Joi.object()
junit: Joi.object()
.keys({
- enabled: Joi.boolean().default(!!process.env.CI),
+ enabled: Joi.boolean().default(!!process.env.CI && !process.env.DISABLE_JUNIT_REPORTER),
reportName: Joi.string(),
})
.default(),
diff --git a/packages/kbn-test/src/mocha/auto_junit_reporter.js b/packages/kbn-test/src/mocha/auto_junit_reporter.js
index 50b589fbc57a5..b6e79616e1cde 100644
--- a/packages/kbn-test/src/mocha/auto_junit_reporter.js
+++ b/packages/kbn-test/src/mocha/auto_junit_reporter.js
@@ -29,7 +29,7 @@ export function createAutoJUnitReporter(junitReportOptions) {
new MochaSpecReporter(runner, options);
// in CI we also setup the JUnit reporter
- if (process.env.CI) {
+ if (process.env.CI && !process.env.DISABLE_JUNIT_REPORTER) {
setupJUnitReportGeneration(runner, junitReportOptions);
}
}
diff --git a/packages/kbn-test/src/mocha/run_mocha_cli.js b/packages/kbn-test/src/mocha/run_mocha_cli.js
index 7a90108472721..77f40aded1d7f 100644
--- a/packages/kbn-test/src/mocha/run_mocha_cli.js
+++ b/packages/kbn-test/src/mocha/run_mocha_cli.js
@@ -63,7 +63,16 @@ export function runMochaCli() {
if (!opts._.length) {
globby
.sync(
- ['src/**/__tests__/**/*.js', 'packages/**/__tests__/**/*.js', 'tasks/**/__tests__/**/*.js'],
+ [
+ 'src/**/__tests__/**/*.js',
+ 'packages/**/__tests__/**/*.js',
+ 'tasks/**/__tests__/**/*.js',
+ 'x-pack/common/**/__tests__/**/*.js',
+ 'x-pack/server/**/__tests__/**/*.js',
+ `x-pack/legacy/plugins/*/__tests__/**/*.js`,
+ `x-pack/legacy/plugins/*/common/**/__tests__/**/*.js`,
+ `x-pack/legacy/plugins/*/**/server/**/__tests__/**/*.js`,
+ ],
{
cwd: REPO_ROOT,
onlyFiles: true,
diff --git a/scripts/functional_tests.js b/scripts/functional_tests.js
index 9f4e678c6adf5..b65cd3835cc0a 100644
--- a/scripts/functional_tests.js
+++ b/scripts/functional_tests.js
@@ -22,6 +22,6 @@ require('@kbn/test').runTestsCli([
require.resolve('../test/functional/config.js'),
require.resolve('../test/api_integration/config.js'),
require.resolve('../test/plugin_functional/config.js'),
- require.resolve('../test/interpreter_functional/config.js'),
+ require.resolve('../test/interpreter_functional/config.ts'),
require.resolve('../test/ui_capabilities/newsfeed_err/config.ts'),
]);
diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md
index 22c96110742e0..c5e04c3cfb53a 100644
--- a/src/core/MIGRATION.md
+++ b/src/core/MIGRATION.md
@@ -1167,7 +1167,7 @@ import { setup, start } from '../core_plugins/visualizations/public/legacy';
| Legacy Platform | New Platform | Notes |
| ------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `import 'ui/apply_filters'` | `import { ApplyFiltersPopover } from '../data/public'` | Directive is deprecated. |
+| `import 'ui/apply_filters'` | `import { applyFiltersPopover } from '../data/public'` | Directive is deprecated. |
| `import 'ui/filter_bar'` | `import { FilterBar } from '../data/public'` | Directive is deprecated. |
| `import 'ui/query_bar'` | `import { QueryBarInput } from '../data/public'` | Directives are deprecated. |
| `import 'ui/search_bar'` | `import { SearchBar } from '../data/public'` | Directive is deprecated. |
diff --git a/src/core/MIGRATION_EXAMPLES.md b/src/core/MIGRATION_EXAMPLES.md
index 9eed3a59acaa6..ccf14879baa37 100644
--- a/src/core/MIGRATION_EXAMPLES.md
+++ b/src/core/MIGRATION_EXAMPLES.md
@@ -298,6 +298,35 @@ class Plugin {
}
}
```
+If your plugin still relies on throwing Boom errors from routes, you can use the `router.handleLegacyErrors`
+as a temporary solution until error migration is complete:
+```ts
+// legacy/plugins/demoplugin/server/plugin.ts
+import { schema } from '@kbn/config-schema';
+import { CoreSetup } from 'src/core/server';
+
+export interface DemoPluginsSetup {};
+
+class Plugin {
+ public setup(core: CoreSetup, pluginSetup: DemoPluginSetup) {
+ const router = core.http.createRouter();
+ router.post(
+ {
+ path: '/api/demoplugin/search',
+ validate: {
+ body: schema.object({
+ field1: schema.string(),
+ }),
+ }
+ },
+ router.wrapErrors((context, req, res) => {
+ throw Boom.notFound('not there'); // will be converted into proper New Platform error
+ })
+ )
+ }
+}
+```
+
#### 4. New Platform plugin
As the final step we delete the shim and move all our code into a New Platform
diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts
index 00c9aedc42cfb..e9a2571382edc 100644
--- a/src/core/server/http/http_service.mock.ts
+++ b/src/core/server/http/http_service.mock.ts
@@ -45,6 +45,7 @@ const createRouterMock = (): jest.Mocked => ({
put: jest.fn(),
delete: jest.fn(),
getRoutes: jest.fn(),
+ handleLegacyErrors: jest.fn().mockImplementation(handler => handler),
});
const createSetupContractMock = () => {
diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts
index 895c899b008bb..bed76201bb4f9 100644
--- a/src/core/server/http/index.ts
+++ b/src/core/server/http/index.ts
@@ -45,6 +45,7 @@ export {
IRouter,
RouteMethod,
RouteConfigOptions,
+ RouteRegistrar,
} from './router';
export { BasePathProxyServer } from './base_path_proxy_server';
export { OnPreAuthHandler, OnPreAuthToolkit } from './lifecycle/on_pre_auth';
diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts
index 70d7ae00f917e..481d8e1bbf49b 100644
--- a/src/core/server/http/integration_tests/router.test.ts
+++ b/src/core/server/http/integration_tests/router.test.ts
@@ -164,6 +164,53 @@ describe('Handler', () => {
});
});
+describe('handleLegacyErrors', () => {
+ it('properly convert Boom errors', async () => {
+ const { server: innerServer, createRouter } = await server.setup(setupDeps);
+ const router = createRouter('/');
+
+ router.get(
+ { path: '/', validate: false },
+ router.handleLegacyErrors((context, req, res) => {
+ throw Boom.notFound();
+ })
+ );
+ await server.start();
+
+ const result = await supertest(innerServer.listener)
+ .get('/')
+ .expect(404);
+
+ expect(result.body.message).toBe('Not Found');
+ });
+
+ it('returns default error when non-Boom errors are thrown', async () => {
+ const { server: innerServer, createRouter } = await server.setup(setupDeps);
+ const router = createRouter('/');
+
+ router.get(
+ {
+ path: '/',
+ validate: false,
+ },
+ router.handleLegacyErrors((context, req, res) => {
+ throw new Error('Unexpected');
+ })
+ );
+ await server.start();
+
+ const result = await supertest(innerServer.listener)
+ .get('/')
+ .expect(500);
+
+ expect(result.body).toEqual({
+ error: 'Internal Server Error',
+ message: 'An internal server error occurred.',
+ statusCode: 500,
+ });
+ });
+});
+
describe('Response factory', () => {
describe('Success', () => {
it('supports answering with json object', async () => {
diff --git a/src/core/server/http/router/error_wrapper.test.ts b/src/core/server/http/router/error_wrapper.test.ts
new file mode 100644
index 0000000000000..aa20b49dc9c91
--- /dev/null
+++ b/src/core/server/http/router/error_wrapper.test.ts
@@ -0,0 +1,80 @@
+/*
+ * 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 Boom from 'boom';
+import { KibanaResponse, KibanaResponseFactory, kibanaResponseFactory } from './response';
+import { wrapErrors } from './error_wrapper';
+import { KibanaRequest, RequestHandler, RequestHandlerContext } from 'kibana/server';
+
+const createHandler = (handler: () => any): RequestHandler => () => {
+ return handler();
+};
+
+describe('wrapErrors', () => {
+ let context: RequestHandlerContext;
+ let request: KibanaRequest;
+ let response: KibanaResponseFactory;
+
+ beforeEach(() => {
+ context = {} as any;
+ request = {} as any;
+ response = kibanaResponseFactory;
+ });
+
+ it('should pass-though call parameters to the handler', async () => {
+ const handler = jest.fn();
+ const wrapped = wrapErrors(handler);
+ await wrapped(context, request, response);
+ expect(handler).toHaveBeenCalledWith(context, request, response);
+ });
+
+ it('should pass-though result from the handler', async () => {
+ const handler = createHandler(() => {
+ return 'handler-response';
+ });
+ const wrapped = wrapErrors(handler);
+ const result = await wrapped(context, request, response);
+ expect(result).toBe('handler-response');
+ });
+
+ it('should intercept and convert thrown Boom errors', async () => {
+ const handler = createHandler(() => {
+ throw Boom.notFound('not there');
+ });
+ const wrapped = wrapErrors(handler);
+ const result = await wrapped(context, request, response);
+ expect(result).toBeInstanceOf(KibanaResponse);
+ expect(result.status).toBe(404);
+ expect(result.payload).toEqual({
+ error: 'Not Found',
+ message: 'not there',
+ statusCode: 404,
+ });
+ });
+
+ it('should re-throw non-Boom errors', async () => {
+ const handler = createHandler(() => {
+ throw new Error('something went bad');
+ });
+ const wrapped = wrapErrors(handler);
+ await expect(wrapped(context, request, response)).rejects.toMatchInlineSnapshot(
+ `[Error: something went bad]`
+ );
+ });
+});
diff --git a/src/core/server/http/router/error_wrapper.ts b/src/core/server/http/router/error_wrapper.ts
new file mode 100644
index 0000000000000..706a9fe3b8887
--- /dev/null
+++ b/src/core/server/http/router/error_wrapper.ts
@@ -0,0 +1,48 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import Boom from 'boom';
+import { ObjectType, TypeOf } from '@kbn/config-schema';
+import { KibanaRequest } from './request';
+import { KibanaResponseFactory } from './response';
+import { RequestHandler } from './router';
+import { RequestHandlerContext } from '../../../server';
+
+export const wrapErrors = (
+ handler: RequestHandler
+): RequestHandler
=> {
+ return async (
+ context: RequestHandlerContext,
+ request: KibanaRequest, TypeOf, TypeOf>,
+ response: KibanaResponseFactory
+ ) => {
+ try {
+ return await handler(context, request, response);
+ } catch (e) {
+ if (Boom.isBoom(e)) {
+ return response.customError({
+ body: e.output.payload,
+ statusCode: e.output.statusCode,
+ headers: e.output.headers,
+ });
+ }
+ throw e;
+ }
+ };
+};
diff --git a/src/core/server/http/router/index.ts b/src/core/server/http/router/index.ts
index 56ed9ca11edc1..f07ad3cfe85c0 100644
--- a/src/core/server/http/router/index.ts
+++ b/src/core/server/http/router/index.ts
@@ -18,7 +18,7 @@
*/
export { Headers, filterHeaders, ResponseHeaders, KnownHeaders } from './headers';
-export { Router, RequestHandler, IRouter } from './router';
+export { Router, RequestHandler, IRouter, RouteRegistrar } from './router';
export {
KibanaRequest,
KibanaRequestRoute,
diff --git a/src/core/server/http/router/router.ts b/src/core/server/http/router/router.ts
index 6b7e2e3ad14cd..a13eae51a19a6 100644
--- a/src/core/server/http/router/router.ts
+++ b/src/core/server/http/router/router.ts
@@ -27,6 +27,7 @@ import { KibanaResponseFactory, kibanaResponseFactory, IKibanaResponse } from '.
import { RouteConfig, RouteConfigOptions, RouteMethod, RouteSchemas } from './route';
import { HapiResponseAdapter } from './response_adapter';
import { RequestHandlerContext } from '../../../server';
+import { wrapErrors } from './error_wrapper';
interface RouterRoute {
method: RouteMethod;
@@ -35,6 +36,15 @@ interface RouterRoute {
handler: (req: Request, responseToolkit: ResponseToolkit) => Promise>;
}
+/**
+ * Handler to declare a route.
+ * @public
+ */
+export type RouteRegistrar = (
+ route: RouteConfig
,
+ handler: RequestHandler
+) => void;
+
/**
* Registers route handlers for specified resource path and method.
* See {@link RouteConfig} and {@link RequestHandler} for more information about arguments to route registrations.
@@ -52,40 +62,36 @@ export interface IRouter {
* @param route {@link RouteConfig} - a route configuration.
* @param handler {@link RequestHandler} - a function to call to respond to an incoming request
*/
- get:
(
- route: RouteConfig
,
- handler: RequestHandler
- ) => void;
+ get: RouteRegistrar;
/**
* Register a route handler for `POST` request.
* @param route {@link RouteConfig} - a route configuration.
* @param handler {@link RequestHandler} - a function to call to respond to an incoming request
*/
- post:
(
- route: RouteConfig
,
- handler: RequestHandler
- ) => void;
+ post: RouteRegistrar;
/**
* Register a route handler for `PUT` request.
* @param route {@link RouteConfig} - a route configuration.
* @param handler {@link RequestHandler} - a function to call to respond to an incoming request
*/
- put:
(
- route: RouteConfig
,
- handler: RequestHandler
- ) => void;
+ put: RouteRegistrar;
/**
* Register a route handler for `DELETE` request.
* @param route {@link RouteConfig} - a route configuration.
* @param handler {@link RequestHandler} - a function to call to respond to an incoming request
*/
- delete:
(
- route: RouteConfig
,
+ delete: RouteRegistrar;
+
+ /**
+ * Wrap a router handler to catch and converts legacy boom errors to proper custom errors.
+ * @param handler {@link RequestHandler} - a route handler to wrap
+ */
+ handleLegacyErrors:
(
handler: RequestHandler
- ) => void;
+ ) => RequestHandler
;
/**
* Returns all routes registered with the this router.
@@ -188,6 +194,12 @@ export class Router implements IRouter {
return [...this.routes];
}
+ public handleLegacyErrors
(
+ handler: RequestHandler
+ ): RequestHandler
{
+ return wrapErrors(handler);
+ }
+
private async handle
({
routeSchemas,
request,
diff --git a/src/core/server/index.ts b/src/core/server/index.ts
index 265612d3fc4a9..b53f04d601ff4 100644
--- a/src/core/server/index.ts
+++ b/src/core/server/index.ts
@@ -114,6 +114,7 @@ export {
IRouter,
RouteMethod,
RouteConfigOptions,
+ RouteRegistrar,
SessionStorage,
SessionStorageCookieOptions,
SessionCookieValidationResult,
diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts
index 1240518422e2f..030caa8324521 100644
--- a/src/core/server/legacy/legacy_service.test.ts
+++ b/src/core/server/legacy/legacy_service.test.ts
@@ -41,7 +41,7 @@ import { configServiceMock } from '../config/config_service.mock';
import { BasePathProxyServer } from '../http';
import { loggingServiceMock } from '../logging/logging_service.mock';
-import { DiscoveredPlugin, DiscoveredPluginInternal } from '../plugins';
+import { DiscoveredPlugin } from '../plugins';
import { KibanaMigrator } from '../saved_objects/migrations';
import { ISavedObjectsClientProvider } from '../saved_objects';
@@ -84,9 +84,9 @@ beforeEach(() => {
contracts: new Map([['plugin-id', 'plugin-value']]),
uiPlugins: {
public: new Map([['plugin-id', {} as DiscoveredPlugin]]),
- internal: new Map([['plugin-id', {} as DiscoveredPluginInternal]]),
+ internal: new Map([['plugin-id', { entryPointPath: 'path/to/plugin/public' }]]),
+ browserConfigs: new Map(),
},
- uiPluginConfigs: new Map(),
},
},
plugins: { 'plugin-id': 'plugin-value' },
diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts
index e86e6cde6e927..99963ad9ce3e8 100644
--- a/src/core/server/legacy/legacy_service.ts
+++ b/src/core/server/legacy/legacy_service.ts
@@ -278,7 +278,6 @@ export class LegacyService implements CoreService {
hapiServer: setupDeps.core.http.server,
kibanaMigrator: startDeps.core.savedObjects.migrator,
uiPlugins: setupDeps.core.plugins.uiPlugins,
- uiPluginConfigs: setupDeps.core.plugins.uiPluginConfigs,
elasticsearch: setupDeps.core.elasticsearch,
uiSettings: setupDeps.core.uiSettings,
savedObjectsClientProvider: startDeps.core.savedObjects.clientProvider,
diff --git a/src/core/server/plugins/plugins_service.mock.ts b/src/core/server/plugins/plugins_service.mock.ts
index e3be8fbb98309..8d3c6a8c909a2 100644
--- a/src/core/server/plugins/plugins_service.mock.ts
+++ b/src/core/server/plugins/plugins_service.mock.ts
@@ -30,10 +30,10 @@ const createServiceMock = () => {
mocked.setup.mockResolvedValue({
contracts: new Map(),
uiPlugins: {
- public: new Map(),
+ browserConfigs: new Map(),
internal: new Map(),
+ public: new Map(),
},
- uiPluginConfigs: new Map(),
});
mocked.start.mockResolvedValue({ contracts: new Map() });
return mocked;
diff --git a/src/core/server/plugins/plugins_service.test.ts b/src/core/server/plugins/plugins_service.test.ts
index da6d1d5a010e7..7e55faa43360e 100644
--- a/src/core/server/plugins/plugins_service.test.ts
+++ b/src/core/server/plugins/plugins_service.test.ts
@@ -33,11 +33,12 @@ import { PluginsService } from './plugins_service';
import { PluginsSystem } from './plugins_system';
import { config } from './plugins_config';
import { take } from 'rxjs/operators';
-import { DiscoveredPluginInternal } from './types';
+import { DiscoveredPlugin } from './types';
const MockPluginsSystem: jest.Mock = PluginsSystem as any;
let pluginsService: PluginsService;
+let config$: BehaviorSubject;
let configService: ConfigService;
let coreId: symbol;
let env: Env;
@@ -107,11 +108,10 @@ describe('PluginsService', () => {
coreId = Symbol('core');
env = Env.createDefault(getEnvOptions());
- configService = new ConfigService(
- new BehaviorSubject(new ObjectToConfigAdapter({ plugins: { initialize: true } })),
- env,
- logger
+ config$ = new BehaviorSubject(
+ new ObjectToConfigAdapter({ plugins: { initialize: true } })
);
+ configService = new ConfigService(config$, env, logger);
await configService.setSchema(config.path, config.schema);
pluginsService = new PluginsService({ coreId, env, logger, configService });
@@ -198,7 +198,7 @@ describe('PluginsService', () => {
.mockImplementation(path => Promise.resolve(!path.includes('disabled')));
mockPluginSystem.setupPlugins.mockResolvedValue(new Map());
- mockPluginSystem.uiPlugins.mockReturnValue({ public: new Map(), internal: new Map() });
+ mockPluginSystem.uiPlugins.mockReturnValue(new Map());
mockDiscover.mockReturnValue({
error$: from([]),
@@ -390,11 +390,10 @@ describe('PluginsService', () => {
});
describe('#generateUiPluginsConfigs()', () => {
- const pluginToDiscoveredEntry = (plugin: PluginWrapper): [string, DiscoveredPluginInternal] => [
+ const pluginToDiscoveredEntry = (plugin: PluginWrapper): [string, DiscoveredPlugin] => [
plugin.name,
{
id: plugin.name,
- path: plugin.path,
configPath: plugin.manifest.configPath,
requiredPlugins: [],
optionalPlugins: [],
@@ -427,15 +426,14 @@ describe('PluginsService', () => {
error$: from([]),
plugin$: from([plugin]),
});
- mockPluginSystem.uiPlugins.mockReturnValue({
- public: new Map([pluginToDiscoveredEntry(plugin)]),
- internal: new Map([pluginToDiscoveredEntry(plugin)]),
- });
+ mockPluginSystem.uiPlugins.mockReturnValue(new Map([pluginToDiscoveredEntry(plugin)]));
await pluginsService.discover();
- const { uiPluginConfigs } = await pluginsService.setup(setupDeps);
+ const {
+ uiPlugins: { browserConfigs },
+ } = await pluginsService.setup(setupDeps);
- const uiConfig$ = uiPluginConfigs.get('plugin-with-expose');
+ const uiConfig$ = browserConfigs.get('plugin-with-expose');
expect(uiConfig$).toBeDefined();
const uiConfig = await uiConfig$!.pipe(take(1)).toPromise();
@@ -468,15 +466,55 @@ describe('PluginsService', () => {
error$: from([]),
plugin$: from([plugin]),
});
- mockPluginSystem.uiPlugins.mockReturnValue({
- public: new Map([pluginToDiscoveredEntry(plugin)]),
- internal: new Map([pluginToDiscoveredEntry(plugin)]),
- });
+ mockPluginSystem.uiPlugins.mockReturnValue(new Map([pluginToDiscoveredEntry(plugin)]));
await pluginsService.discover();
- const { uiPluginConfigs } = await pluginsService.setup(setupDeps);
+ const {
+ uiPlugins: { browserConfigs },
+ } = await pluginsService.setup(setupDeps);
- expect([...uiPluginConfigs.entries()]).toHaveLength(0);
+ expect([...browserConfigs.entries()]).toHaveLength(0);
+ });
+ });
+
+ describe('#setup()', () => {
+ describe('uiPlugins.internal', () => {
+ it('includes disabled plugins', async () => {
+ mockDiscover.mockReturnValue({
+ error$: from([]),
+ plugin$: from([
+ createPlugin('plugin-1', {
+ path: 'path-1',
+ version: 'some-version',
+ configPath: 'plugin1',
+ }),
+ createPlugin('plugin-2', {
+ path: 'path-2',
+ version: 'some-version',
+ configPath: 'plugin2',
+ }),
+ ]),
+ });
+
+ mockPluginSystem.uiPlugins.mockReturnValue(new Map());
+
+ config$.next(
+ new ObjectToConfigAdapter({ plugins: { initialize: true }, plugin1: { enabled: false } })
+ );
+
+ await pluginsService.discover();
+ const { uiPlugins } = await pluginsService.setup({} as any);
+ expect(uiPlugins.internal).toMatchInlineSnapshot(`
+ Map {
+ "plugin-1" => Object {
+ "entryPointPath": "path-1/public",
+ },
+ "plugin-2" => Object {
+ "entryPointPath": "path-2/public",
+ },
+ }
+ `);
+ });
});
});
diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts
index 79c9489a8b4c0..4c73c2a304dc4 100644
--- a/src/core/server/plugins/plugins_service.ts
+++ b/src/core/server/plugins/plugins_service.ts
@@ -25,12 +25,7 @@ import { CoreContext } from '../core_context';
import { Logger } from '../logging';
import { discover, PluginDiscoveryError, PluginDiscoveryErrorType } from './discovery';
import { PluginWrapper } from './plugin';
-import {
- DiscoveredPlugin,
- DiscoveredPluginInternal,
- PluginConfigDescriptor,
- PluginName,
-} from './types';
+import { DiscoveredPlugin, PluginConfigDescriptor, PluginName, InternalPluginInfo } from './types';
import { PluginsConfig, PluginsConfigType } from './plugins_config';
import { PluginsSystem } from './plugins_system';
import { InternalCoreSetup } from '../internal_types';
@@ -41,10 +36,22 @@ import { pick } from '../../utils';
export interface PluginsServiceSetup {
contracts: Map;
uiPlugins: {
+ /**
+ * Paths to all discovered ui plugin entrypoints on the filesystem, even if
+ * disabled.
+ */
+ internal: Map;
+
+ /**
+ * Information needed by client-side to load plugins and wire dependencies.
+ */
public: Map;
- internal: Map;
+
+ /**
+ * Configuration for plugins to be exposed to the client-side.
+ */
+ browserConfigs: Map>;
};
- uiPluginConfigs: Map>;
}
/** @public */
@@ -65,6 +72,7 @@ export class PluginsService implements CoreService;
private readonly pluginConfigDescriptors = new Map();
+ private readonly uiPluginInternalInfo = new Map();
constructor(private readonly coreContext: CoreContext) {
this.log = coreContext.logger.get('plugins-service');
@@ -103,8 +111,11 @@ export class PluginsService implements CoreService {
expect(thirdPluginToRun.setup).toHaveBeenCalledTimes(1);
});
-test('`uiPlugins` returns empty Maps before plugins are added', async () => {
- expect(pluginsSystem.uiPlugins()).toMatchInlineSnapshot(`
- Object {
- "internal": Map {},
- "public": Map {},
- }
- `);
+test('`uiPlugins` returns empty Map before plugins are added', async () => {
+ expect(pluginsSystem.uiPlugins()).toMatchInlineSnapshot(`Map {}`);
});
test('`uiPlugins` returns ordered Maps of all plugin manifests', async () => {
@@ -351,7 +346,7 @@ test('`uiPlugins` returns ordered Maps of all plugin manifests', async () => {
pluginsSystem.addPlugin(plugin);
});
- expect([...pluginsSystem.uiPlugins().internal.keys()]).toMatchInlineSnapshot(`
+ expect([...pluginsSystem.uiPlugins().keys()]).toMatchInlineSnapshot(`
Array [
"order-0",
"order-1",
@@ -380,7 +375,7 @@ test('`uiPlugins` returns only ui plugin dependencies', async () => {
pluginsSystem.addPlugin(plugin);
});
- const plugin = pluginsSystem.uiPlugins().internal.get('ui-plugin')!;
+ const plugin = pluginsSystem.uiPlugins().get('ui-plugin')!;
expect(plugin.requiredPlugins).toEqual(['req-ui']);
expect(plugin.optionalPlugins).toEqual(['opt-ui']);
});
diff --git a/src/core/server/plugins/plugins_system.ts b/src/core/server/plugins/plugins_system.ts
index 34acb66d4e931..f437b51e5b07a 100644
--- a/src/core/server/plugins/plugins_system.ts
+++ b/src/core/server/plugins/plugins_system.ts
@@ -17,12 +17,10 @@
* under the License.
*/
-import { pick } from 'lodash';
-
import { CoreContext } from '../core_context';
import { Logger } from '../logging';
import { PluginWrapper } from './plugin';
-import { DiscoveredPlugin, DiscoveredPluginInternal, PluginName, PluginOpaqueId } from './types';
+import { DiscoveredPlugin, PluginName, PluginOpaqueId } from './types';
import { createPluginSetupContext, createPluginStartContext } from './plugin_context';
import { PluginsServiceSetupDeps, PluginsServiceStartDeps } from './plugins_service';
@@ -158,33 +156,22 @@ export class PluginsSystem {
const uiPluginNames = [...this.getTopologicallySortedPluginNames().keys()].filter(
pluginName => this.plugins.get(pluginName)!.includesUiPlugin
);
- const internal = new Map(
+ const publicPlugins = new Map(
uiPluginNames.map(pluginName => {
const plugin = this.plugins.get(pluginName)!;
return [
pluginName,
{
id: pluginName,
- path: plugin.path,
configPath: plugin.manifest.configPath,
requiredPlugins: plugin.manifest.requiredPlugins.filter(p => uiPluginNames.includes(p)),
optionalPlugins: plugin.manifest.optionalPlugins.filter(p => uiPluginNames.includes(p)),
},
- ] as [PluginName, DiscoveredPluginInternal];
+ ];
})
);
- const publicPlugins = new Map(
- [...internal.entries()].map(
- ([pluginName, plugin]) =>
- [
- pluginName,
- pick(plugin, ['id', 'configPath', 'requiredPlugins', 'optionalPlugins']),
- ] as [PluginName, DiscoveredPlugin]
- )
- );
-
- return { public: publicPlugins, internal };
+ return publicPlugins;
}
/**
diff --git a/src/core/server/plugins/types.ts b/src/core/server/plugins/types.ts
index 17704ce687b92..fd487d9fe00aa 100644
--- a/src/core/server/plugins/types.ts
+++ b/src/core/server/plugins/types.ts
@@ -169,15 +169,14 @@ export interface DiscoveredPlugin {
}
/**
- * An extended `DiscoveredPlugin` that exposes more sensitive information. Should never
- * be exposed to client-side code.
* @internal
*/
-export interface DiscoveredPluginInternal extends DiscoveredPlugin {
+export interface InternalPluginInfo {
/**
- * Path on the filesystem where plugin was loaded from.
+ * Path to the client-side entrypoint file to be used to build the client-side
+ * bundle for a plugin.
*/
- readonly path: string;
+ readonly entryPointPath: string;
}
/**
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index 7cc9e24b56dbe..3bbcb85fea9e5 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -714,14 +714,15 @@ export interface IndexSettingsDeprecationInfo {
// @public
export interface IRouter {
- delete: (route: RouteConfig
, handler: RequestHandler
) => void;
- get:
(route: RouteConfig
, handler: RequestHandler
) => void;
+ delete: RouteRegistrar;
+ get: RouteRegistrar;
// Warning: (ae-forgotten-export) The symbol "RouterRoute" needs to be exported by the entry point index.d.ts
//
// @internal
getRoutes: () => RouterRoute[];
- post:
(route: RouteConfig
, handler: RequestHandler
) => void;
- put:
(route: RouteConfig
, handler: RequestHandler
) => void;
+ handleLegacyErrors:
(handler: RequestHandler
) => RequestHandler
;
+ post: RouteRegistrar;
+ put: RouteRegistrar;
routerPath: string;
}
@@ -1014,11 +1015,10 @@ export interface PluginsServiceSetup {
// (undocumented)
contracts: Map;
// (undocumented)
- uiPluginConfigs: Map>;
- // (undocumented)
uiPlugins: {
+ internal: Map;
public: Map;
- internal: Map;
+ browserConfigs: Map>;
};
}
@@ -1100,6 +1100,9 @@ export interface RouteConfigOptions {
// @public
export type RouteMethod = 'get' | 'post' | 'put' | 'delete';
+// @public
+export type RouteRegistrar = (route: RouteConfig
, handler: RequestHandler
) => void;
+
// @public (undocumented)
export interface SavedObject {
attributes: T;
@@ -1634,6 +1637,6 @@ export interface UserProvidedValues {
// Warnings were encountered during analysis:
//
// src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts
-// src/core/server/plugins/plugins_service.ts:45:5 - (ae-forgotten-export) The symbol "DiscoveredPluginInternal" needs to be exported by the entry point index.d.ts
+// src/core/server/plugins/plugins_service.ts:43:5 - (ae-forgotten-export) The symbol "InternalPluginInfo" needs to be exported by the entry point index.d.ts
```
diff --git a/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js b/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js
index 5c0462ce86fa9..b0e38b6481457 100644
--- a/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js
+++ b/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js
@@ -66,10 +66,38 @@ export const CleanClientModulesOnDLLTask = {
// side code entries that were provided
const serverDependencies = await getDependencies(baseDir, serverEntries);
+ // This fulfill a particular exceptional case where
+ // we need to keep loading a file from a node_module
+ // only used in the front-end like we do when using the file-loader
+ // in https://github.com/elastic/kibana/blob/master/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js
+ //
+ // manual list of exception modules
+ const manualExceptionModules = [
+ 'mapbox-gl'
+ ];
+
+ // consider the top modules as exceptions as the entry points
+ // to look for other exceptions dependent on that one
+ const manualExceptionEntries = [
+ ...manualExceptionModules.map(module => `${baseDir}/node_modules/${module}`)
+ ];
+
+ // dependencies for declared exception modules
+ const manualExceptionModulesDependencies = await getDependencies(baseDir, [
+ ...manualExceptionEntries
+ ]);
+
+ // final list of manual exceptions to add
+ const manualExceptions = [
+ ...manualExceptionModules,
+ ...manualExceptionModulesDependencies
+ ];
+
// Consider this as our whiteList for the modules we can't delete
const whiteListedModules = [
...serverDependencies,
- ...kbnWebpackLoaders
+ ...kbnWebpackLoaders,
+ ...manualExceptions
];
// Resolve the client vendors dll manifest path
diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker
index 6609b905b81ec..0c8faf47411d4 100755
--- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker
+++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker
@@ -180,6 +180,9 @@ kibana_vars=(
xpack.security.encryptionKey
xpack.security.secureCookies
xpack.security.sessionTimeout
+ xpack.security.session.idleTimeout
+ xpack.security.session.lifespan
+ xpack.security.loginAssistanceMessage
telemetry.enabled
telemetry.sendUsageFrom
)
diff --git a/src/dev/jest/junit_reporter.js b/src/dev/jest/junit_reporter.js
index 30501965bf1e7..7f51326ee46bb 100644
--- a/src/dev/jest/junit_reporter.js
+++ b/src/dev/jest/junit_reporter.js
@@ -45,7 +45,7 @@ export default class JestJUnitReporter {
* @return {undefined}
*/
onRunComplete(contexts, results) {
- if (!process.env.CI || !results.testResults.length) {
+ if (!process.env.CI || process.env.DISABLE_JUNIT_REPORTER || !results.testResults.length) {
return;
}
diff --git a/src/fixtures/stubbed_search_source.js b/src/fixtures/stubbed_search_source.js
index 3a36b97e6757e..da741a1aa4774 100644
--- a/src/fixtures/stubbed_search_source.js
+++ b/src/fixtures/stubbed_search_source.js
@@ -60,9 +60,6 @@ export default function stubSearchSource(Private, $q, Promise) {
onRequestStart(fn) {
this._requestStartHandlers.push(fn);
},
- requestIsStarting(req) {
- return Promise.map(this._requestStartHandlers, fn => fn(req));
- },
requestIsStopped() {}
};
diff --git a/src/legacy/core_plugins/console/public/quarantined/_app.scss b/src/legacy/core_plugins/console/public/quarantined/_app.scss
index 1e13b6b483981..b19fd438f8ee3 100644
--- a/src/legacy/core_plugins/console/public/quarantined/_app.scss
+++ b/src/legacy/core_plugins/console/public/quarantined/_app.scss
@@ -1,5 +1,8 @@
// TODO: Move all of the styles here (should be modularised by, e.g., CSS-in-JS or CSS modules).
+@import '@elastic/eui/src/components/header/variables';
+
#consoleRoot {
+ height: calc(100vh - calc(#{$euiHeaderChildSize} * 2));
display: flex;
flex: 1 1 auto;
// Make sure the editor actions don't create scrollbars on this container
diff --git a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts
index 39ec1f78b65f0..946b3997a9712 100644
--- a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts
+++ b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts
@@ -29,10 +29,10 @@ import {
esFilters,
FilterManager,
TimefilterContract,
+ applyFiltersPopover,
changeTimeFilter,
extractTimeFilter,
} from '../../../../../../plugins/data/public';
-import { applyFiltersPopover } from '../apply_filters/apply_filters_popover';
import { IndexPatternsStart } from '../../index_patterns';
export const GLOBAL_APPLY_FILTER_ACTION = 'GLOBAL_APPLY_FILTER_ACTION';
diff --git a/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filters_popover.tsx b/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filters_popover.tsx
deleted file mode 100644
index 41f757e726c40..0000000000000
--- a/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filters_popover.tsx
+++ /dev/null
@@ -1,74 +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 { EuiModal, EuiOverlayMask } from '@elastic/eui';
-import React, { Component } from 'react';
-import { ApplyFiltersPopoverContent } from './apply_filter_popover_content';
-import { IndexPattern } from '../../index_patterns/index_patterns';
-import { esFilters } from '../../../../../../plugins/data/public';
-
-interface Props {
- filters: esFilters.Filter[];
- onCancel: () => void;
- onSubmit: (filters: esFilters.Filter[]) => void;
- indexPatterns: IndexPattern[];
-}
-
-interface State {
- isFilterSelected: boolean[];
-}
-
-export class ApplyFiltersPopover extends Component {
- public render() {
- if (!this.props.filters || this.props.filters.length === 0) {
- return '';
- }
-
- return (
-
-
-
-
-
- );
- }
-}
-
-type cancelFunction = () => void;
-type submitFunction = (filters: esFilters.Filter[]) => void;
-export const applyFiltersPopover = (
- filters: esFilters.Filter[],
- indexPatterns: IndexPattern[],
- onCancel: cancelFunction,
- onSubmit: submitFunction
-) => {
- return (
-
- );
-};
diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts
index b33aef75e6756..1349187779061 100644
--- a/src/legacy/core_plugins/data/public/index.ts
+++ b/src/legacy/core_plugins/data/public/index.ts
@@ -29,7 +29,6 @@ export function plugin() {
/** @public types */
export { DataSetup, DataStart };
-export { ApplyFiltersPopover } from './filter';
export {
Field,
FieldType,
@@ -48,7 +47,6 @@ export {
CONTAINS_SPACES,
getFromSavedObject,
getRoutes,
- IndexPatternSelect,
validateIndexPattern,
ILLEGAL_CHARACTERS,
INDEX_PATTERN_ILLEGAL_CHARACTERS,
diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.mock.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.mock.ts
index 5dcf4005ef4e8..db1ece78e7b4d 100644
--- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.mock.ts
+++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.mock.ts
@@ -33,7 +33,6 @@ const createSetupContractMock = () => {
flattenHitWrapper: jest.fn().mockImplementation(flattenHitWrapper),
formatHitProvider: jest.fn(),
indexPatterns: jest.fn() as any,
- IndexPatternSelect: jest.fn(),
__LEGACY: {
// For BWC we must temporarily export the class implementation of Field,
// which is only used externally by the Index Pattern UI.
diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts
index f97246bc5a9bf..381cd491f0210 100644
--- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts
+++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts
@@ -25,7 +25,6 @@ import {
} from 'src/core/public';
import { FieldFormatsStart } from '../../../../../plugins/data/public';
import { Field, FieldList, FieldListInterface, FieldType } from './fields';
-import { createIndexPatternSelect } from './components';
import { setNotifications, setFieldFormats } from './services';
import {
@@ -79,7 +78,6 @@ export class IndexPatternsService {
return {
...this.setupApi,
indexPatterns: new IndexPatterns(uiSettings, savedObjectsClient, http),
- IndexPatternSelect: createIndexPatternSelect(savedObjectsClient),
};
}
@@ -91,7 +89,6 @@ export class IndexPatternsService {
// static code
/** @public */
-export { IndexPatternSelect } from './components';
export {
CONTAINS_SPACES,
getFromSavedObject,
@@ -120,4 +117,4 @@ export type IndexPatternsStart = ReturnType;
export { IndexPattern, IndexPatterns, StaticIndexPattern, Field, FieldType, FieldListInterface };
/** @public */
-export { getIndexPatternTitle, findIndexPatternByTitle } from './utils';
+export { findIndexPatternByTitle } from './utils';
diff --git a/src/legacy/core_plugins/data/public/index_patterns/utils.ts b/src/legacy/core_plugins/data/public/index_patterns/utils.ts
index 8542c1dcce24d..8c2878a3ff9ba 100644
--- a/src/legacy/core_plugins/data/public/index_patterns/utils.ts
+++ b/src/legacy/core_plugins/data/public/index_patterns/utils.ts
@@ -71,19 +71,6 @@ export async function findIndexPatternByTitle(
);
}
-export async function getIndexPatternTitle(
- client: SavedObjectsClientContract,
- indexPatternId: string
-): Promise> {
- const savedObject = (await client.get('index-pattern', indexPatternId)) as SimpleSavedObject;
-
- if (savedObject.error) {
- throw new Error(`Unable to get index-pattern title: ${savedObject.error.message}`);
- }
-
- return savedObject.attributes.title;
-}
-
function indexPatternContainsSpaces(indexPattern: string): boolean {
return indexPattern.includes(' ');
}
diff --git a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts
index db2a803ea1c61..7165de026920d 100644
--- a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts
+++ b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts
@@ -29,7 +29,12 @@ import {
ExpressionFunction,
KibanaDatatableColumn,
} from 'src/plugins/expressions/public';
-import { SearchSource } from '../../../../../ui/public/courier/search_source';
+import {
+ SearchSource,
+ SearchSourceContract,
+ getRequestInspectorStats,
+ getResponseInspectorStats,
+} from '../../../../../ui/public/courier';
// @ts-ignore
import {
FilterBarQueryFilterProvider,
@@ -37,10 +42,6 @@ import {
} from '../../../../../ui/public/filter_manager/query_filter';
import { buildTabularInspectorData } from '../../../../../ui/public/inspector/build_tabular_inspector_data';
-import {
- getRequestInspectorStats,
- getResponseInspectorStats,
-} from '../../../../../ui/public/courier/utils/courier_inspector_utils';
import { calculateObjectHash } from '../../../../../ui/public/vis/lib/calculate_object_hash';
import { getTime } from '../../../../../ui/public/timefilter';
// @ts-ignore
@@ -50,7 +51,7 @@ import { PersistedState } from '../../../../../ui/public/persisted_state';
import { Adapters } from '../../../../../../plugins/inspector/public';
export interface RequestHandlerParams {
- searchSource: SearchSource;
+ searchSource: SearchSourceContract;
aggs: AggConfigs;
timeRange?: TimeRange;
query?: Query;
@@ -119,7 +120,7 @@ const handleCourierRequest = async ({
return aggs.toDsl(metricsAtAllLevels);
});
- requestSearchSource.onRequestStart((paramSearchSource: SearchSource, options: any) => {
+ requestSearchSource.onRequestStart((paramSearchSource, options) => {
return aggs.onSearchRequestStart(paramSearchSource, options);
});
diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.js
index 27f37421b0e25..45981adf9af45 100644
--- a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.js
+++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.js
@@ -17,8 +17,27 @@
* under the License.
*/
-jest.mock('ui/new_platform');
-jest.mock('ui/index_patterns');
+jest.mock('../../../../../core_plugins/data/public/legacy', () => ({
+ indexPatterns: {
+ indexPatterns: {
+ get: jest.fn(),
+ }
+ }
+}));
+
+jest.mock('ui/new_platform', () => ({
+ npStart: {
+ plugins: {
+ data: {
+ ui: {
+ IndexPatternSelect: () => {
+ return
;
+ }
+ }
+ }
+ },
+ },
+}));
import React from 'react';
import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers';
diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.js
index 663a36ab69f46..c48123f3db714 100644
--- a/src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.js
+++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.js
@@ -20,12 +20,13 @@
import PropTypes from 'prop-types';
import React from 'react';
import { injectI18n } from '@kbn/i18n/react';
-import { IndexPatternSelect } from 'ui/index_patterns';
-
import {
EuiFormRow,
} from '@elastic/eui';
+import { npStart } from 'ui/new_platform';
+const { IndexPatternSelect } = npStart.plugins.data.ui;
+
function IndexPatternSelectFormRowUi(props) {
const {
controlIndex,
diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js
index ea029af9e4890..b37e8af0895fe 100644
--- a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js
+++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js
@@ -17,12 +17,24 @@
* under the License.
*/
-jest.mock('ui/new_platform');
-jest.mock('ui/index_patterns');
+jest.mock('ui/new_platform', () => ({
+ npStart: {
+ plugins: {
+ data: {
+ ui: {
+ IndexPatternSelect: () => {
+ return
;
+ }
+ }
+ }
+ },
+ },
+}));
import React from 'react';
import sinon from 'sinon';
import { shallow } from 'enzyme';
+
import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers';
import { findTestSubject } from '@elastic/eui/lib/test';
import { getIndexPatternMock } from './__tests__/get_index_pattern_mock';
diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.js
index 5a698d65286ac..8d601f5a727d1 100644
--- a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.js
+++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.js
@@ -17,19 +17,30 @@
* under the License.
*/
-jest.mock('ui/new_platform');
-jest.mock('ui/index_patterns');
import React from 'react';
import sinon from 'sinon';
import { shallow } from 'enzyme';
import { mountWithIntl } from 'test_utils/enzyme_helpers';
+
+jest.mock('ui/new_platform', () => ({
+ npStart: {
+ plugins: {
+ data: {
+ ui: {
+ IndexPatternSelect: () => {
+ return
;
+ }
+ }
+ }
+ },
+ },
+}));
+
import { findTestSubject } from '@elastic/eui/lib/test';
import { getIndexPatternMock } from './__tests__/get_index_pattern_mock';
-import {
- RangeControlEditor,
-} from './range_control_editor';
+import { RangeControlEditor } from './range_control_editor';
const controlParams = {
id: '1',
diff --git a/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.js b/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.js
index 61a3d4084ab8f..2ab4131957c32 100644
--- a/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.js
+++ b/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.js
@@ -19,9 +19,9 @@
import { timefilter } from 'ui/timefilter';
export function createSearchSource(SearchSource, initialState, indexPattern, aggs, useTimeFilter, filters = []) {
- const searchSource = new SearchSource(initialState);
+ const searchSource = initialState ? new SearchSource(initialState) : new SearchSource();
// Do not not inherit from rootSearchSource to avoid picking up time and globals
- searchSource.setParent(false);
+ searchSource.setParent(undefined);
searchSource.setField('filter', () => {
const activeFilters = [...filters];
if (useTimeFilter) {
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/application.ts
index 57391223fa147..9c50adeeefccb 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/application.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/application.ts
@@ -48,7 +48,7 @@ import {
// @ts-ignore
import { initDashboardApp } from './legacy_app';
import { DataStart } from '../../../data/public';
-import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public';
+import { IEmbeddableStart } from '../../../../../plugins/embeddable/public';
import { NavigationStart } from '../../../navigation/public';
import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public';
import { SharePluginStart } from '../../../../../plugins/share/public';
@@ -68,7 +68,7 @@ export interface RenderDeps {
chrome: ChromeStart;
addBasePath: (path: string) => string;
savedQueryService: DataStart['search']['services']['savedQueryService'];
- embeddables: ReturnType;
+ embeddables: IEmbeddableStart;
localStorage: Storage;
share: SharePluginStart;
}
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts
index deb291deb0d5a..609bd717f3c48 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts
@@ -29,7 +29,7 @@ import { i18n } from '@kbn/i18n';
import { RenderDeps } from './application';
import { DataStart } from '../../../data/public';
import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public';
-import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public';
+import { IEmbeddableStart } from '../../../../../plugins/embeddable/public';
import { Storage } from '../../../../../plugins/kibana_utils/public';
import { NavigationStart } from '../../../navigation/public';
import { DashboardConstants } from './dashboard_constants';
@@ -49,7 +49,7 @@ export interface LegacyAngularInjectedDependencies {
export interface DashboardPluginStartDependencies {
data: DataStart;
npData: NpDataStart;
- embeddables: ReturnType;
+ embeddables: IEmbeddableStart;
navigation: NavigationStart;
share: SharePluginStart;
}
@@ -67,7 +67,7 @@ export class DashboardPlugin implements Plugin {
dataStart: DataStart;
npDataStart: NpDataStart;
savedObjectsClient: SavedObjectsClientContract;
- embeddables: ReturnType;
+ embeddables: IEmbeddableStart;
navigation: NavigationStart;
share: SharePluginStart;
} | null = null;
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts
index 5b24aa13f4f77..4c417ed2954d3 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.d.ts
@@ -17,8 +17,8 @@
* under the License.
*/
-import { SearchSource } from 'ui/courier';
import { SavedObject } from 'ui/saved_objects/saved_object';
+import { SearchSourceContract } from '../../../../../ui/public/courier';
import { esFilters, Query, RefreshInterval } from '../../../../../../plugins/data/public';
export interface SavedObjectDashboard extends SavedObject {
@@ -34,7 +34,7 @@ export interface SavedObjectDashboard extends SavedObject {
// TODO: write a migration to rid of this, it's only around for bwc.
uiStateJSON?: string;
lastSavedTitle: string;
- searchSource: SearchSource;
+ searchSource: SearchSourceContract;
destroy: () => void;
refreshInterval?: RefreshInterval;
getQuery(): Query;
diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js
index 46e66177b516a..4eb68c1bf50bc 100644
--- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js
+++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js
@@ -58,7 +58,7 @@ describe('context app', function () {
.then(() => {
const setParentSpy = searchSourceStub.setParent;
expect(setParentSpy.calledOnce).to.be(true);
- expect(setParentSpy.firstCall.args[0]).to.eql(false);
+ expect(setParentSpy.firstCall.args[0]).to.be(undefined);
});
});
diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js
index 2bf3da42e24e5..ea6a8c092e242 100644
--- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js
+++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js
@@ -196,7 +196,7 @@ describe('context app', function () {
)
.then(() => {
const setParentSpy = searchSourceStub.setParent;
- expect(setParentSpy.alwaysCalledWith(false)).to.be(true);
+ expect(setParentSpy.alwaysCalledWith(undefined)).to.be(true);
expect(setParentSpy.called).to.be(true);
});
});
diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js
index b8bec40f2859c..486c8ed9b410e 100644
--- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js
+++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js
@@ -199,7 +199,7 @@ describe('context app', function () {
)
.then(() => {
const setParentSpy = searchSourceStub.setParent;
- expect(setParentSpy.alwaysCalledWith(false)).to.be(true);
+ expect(setParentSpy.alwaysCalledWith(undefined)).to.be(true);
expect(setParentSpy.called).to.be(true);
});
});
diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js
index 62bbc6166662f..8c4cce810ca13 100644
--- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js
+++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js
@@ -30,7 +30,7 @@ export function fetchAnchorProvider(indexPatterns) {
) {
const indexPattern = await indexPatterns.get(indexPatternId);
const searchSource = new SearchSource()
- .setParent(false)
+ .setParent(undefined)
.setField('index', indexPattern)
.setField('version', true)
.setField('size', 1)
diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts
index 3314bbbf189c4..68ccf56594e72 100644
--- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts
@@ -17,8 +17,9 @@
* under the License.
*/
+import { SortDirection } from '../../../../../../../ui/public/courier';
import { IndexPatterns, IndexPattern, getServices } from '../../../kibana_services';
-import { reverseSortDir, SortDirection } from './utils/sorting';
+import { reverseSortDir } from './utils/sorting';
import { extractNanos, convertIsoToMillis } from './utils/date_conversion';
import { fetchHitsInInterval } from './utils/fetch_hits_in_interval';
import { generateIntervals } from './utils/generate_intervals';
@@ -114,7 +115,7 @@ function fetchContextProvider(indexPatterns: IndexPatterns) {
async function createSearchSource(indexPattern: IndexPattern, filters: esFilters.Filter[]) {
return new SearchSource()
- .setParent(false)
+ .setParent(undefined)
.setField('index', indexPattern)
.setField('filter', filters);
}
diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts
index eeae2aa2c5d0a..33f4454c18d40 100644
--- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts
@@ -16,7 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { reverseSortDir, SortDirection } from '../sorting';
+import { reverseSortDir } from '../sorting';
+import { SortDirection } from '../../../../../../../../../ui/public/courier';
+
+jest.mock('ui/new_platform');
describe('function reverseSortDir', function() {
test('reverse a given sort direction', function() {
diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/fetch_hits_in_interval.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/fetch_hits_in_interval.ts
index 2810e5d9d7e66..19c2ee2cdfe10 100644
--- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/fetch_hits_in_interval.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/fetch_hits_in_interval.ts
@@ -16,12 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { SearchSource } from '../../../../kibana_services';
+import {
+ EsQuerySortValue,
+ SortDirection,
+ SearchSourceContract,
+} from '../../../../../../../../ui/public/courier';
import { convertTimeValueToIso } from './date_conversion';
-import { SortDirection } from './sorting';
import { EsHitRecordList } from '../context';
import { IntervalValue } from './generate_intervals';
-import { EsQuerySort } from './get_es_query_sort';
import { EsQuerySearchAfter } from './get_es_query_search_after';
interface RangeQuery {
@@ -38,9 +40,9 @@ interface RangeQuery {
* and filters set.
*/
export async function fetchHitsInInterval(
- searchSource: SearchSource,
+ searchSource: SearchSourceContract,
timeField: string,
- sort: EsQuerySort,
+ sort: [EsQuerySortValue, EsQuerySortValue],
sortDir: SortDirection,
interval: IntervalValue[],
searchAfter: EsQuerySearchAfter,
diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/generate_intervals.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/generate_intervals.ts
index a50764fe542b1..cb4878239ff92 100644
--- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/generate_intervals.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/generate_intervals.ts
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { SortDirection } from './sorting';
+import { SortDirection } from '../../../../../../../../ui/public/courier';
export type IntervalValue = number | null;
diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/get_es_query_sort.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/get_es_query_sort.ts
index c9f9b9b939f3d..39c69112e58cb 100644
--- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/get_es_query_sort.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/get_es_query_sort.ts
@@ -16,11 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { SortDirection } from './sorting';
-type EsQuerySortValue = Record;
-
-export type EsQuerySort = [EsQuerySortValue, EsQuerySortValue];
+import { EsQuerySortValue, SortDirection } from '../../../../../../../../ui/public/courier/types';
/**
* Returns `EsQuerySort` which is used to sort records in the ES query
@@ -33,6 +30,6 @@ export function getEsQuerySort(
timeField: string,
tieBreakerField: string,
sortDir: SortDirection
-): EsQuerySort {
+): [EsQuerySortValue, EsQuerySortValue] {
return [{ [timeField]: sortDir }, { [tieBreakerField]: sortDir }];
}
diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts
index 4a0f531845f46..47385aecb1937 100644
--- a/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts
@@ -17,13 +17,9 @@
* under the License.
*/
+import { SortDirection } from '../../../../../../../../ui/public/courier';
import { IndexPattern } from '../../../../kibana_services';
-export enum SortDirection {
- asc = 'asc',
- desc = 'desc',
-}
-
/**
* The list of field names that are allowed for sorting, but not included in
* index pattern fields.
diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts
index ef79cda476e51..9fee0cfc3ea00 100644
--- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts
@@ -22,6 +22,7 @@ import { Subscription } from 'rxjs';
import { i18n } from '@kbn/i18n';
import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public';
import { npStart } from 'ui/new_platform';
+import { SearchSourceContract } from '../../../../../ui/public/courier';
import {
esFilters,
TimeRange,
@@ -51,7 +52,6 @@ import {
getServices,
IndexPattern,
RequestAdapter,
- SearchSource,
} from '../kibana_services';
import { SEARCH_EMBEDDABLE_TYPE } from './constants';
@@ -92,7 +92,7 @@ export class SearchEmbeddable extends Embeddable
private inspectorAdaptors: Adapters;
private searchScope?: SearchScope;
private panelTitle: string = '';
- private filtersSearchSource?: SearchSource;
+ private filtersSearchSource?: SearchSourceContract;
private searchInstance?: JQLite;
private autoRefreshFetchSubscription?: Subscription;
private subscription?: Subscription;
@@ -194,13 +194,11 @@ export class SearchEmbeddable extends Embeddable
searchScope.inspectorAdapters = this.inspectorAdaptors;
const { searchSource } = this.savedSearch;
- const indexPattern = (searchScope.indexPattern = searchSource.getField('index'));
+ const indexPattern = (searchScope.indexPattern = searchSource.getField('index'))!;
const timeRangeSearchSource = searchSource.create();
timeRangeSearchSource.setField('filter', () => {
- if (!this.searchScope || !this.input.timeRange) {
- return;
- }
+ if (!this.searchScope || !this.input.timeRange) return;
return getTime(indexPattern, this.input.timeRange);
});
@@ -241,7 +239,7 @@ export class SearchEmbeddable extends Embeddable
};
searchScope.filter = async (field, value, operator) => {
- let filters = generateFilters(this.filterManager, field, value, operator, indexPattern.id);
+ let filters = generateFilters(this.filterManager, field, value, operator, indexPattern.id!);
filters = filters.map(filter => ({
...filter,
$state: { store: esFilters.FilterStateStore.APP_STATE },
diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts
index 1939cc7060621..ebea646a09889 100644
--- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts
@@ -84,6 +84,7 @@ export class SearchEmbeddableFactory extends EmbeddableFactory<
const queryFilter = Private(getServices().FilterBarQueryFilterProvider);
try {
const savedObject = await searchLoader.get(savedObjectId);
+ const indexPattern = savedObject.searchSource.getField('index');
return new SearchEmbeddable(
{
savedSearch: savedObject,
@@ -92,7 +93,7 @@ export class SearchEmbeddableFactory extends EmbeddableFactory<
editUrl,
queryFilter,
editable: getServices().capabilities.discover.save as boolean,
- indexPatterns: _.compact([savedObject.searchSource.getField('index')]),
+ indexPatterns: indexPattern ? [indexPattern] : [],
},
input,
this.executeTriggerActions,
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 02b08d7fa4b61..fc5f34fab7564 100644
--- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts
@@ -28,7 +28,6 @@ import angular from 'angular'; // just used in embeddables and discover controll
import uiRoutes from 'ui/routes';
// @ts-ignore
import { uiModules } from 'ui/modules';
-import { SearchSource } from 'ui/courier';
// @ts-ignore
import { StateProvider } from 'ui/state_management/state';
// @ts-ignore
@@ -43,6 +42,7 @@ import { wrapInI18nContext } from 'ui/i18n';
import { docTitle } from 'ui/doc_title';
// @ts-ignore
import * as docViewsRegistry from 'ui/registry/doc_views';
+import { SearchSource } from '../../../../ui/public/courier';
const services = {
// new plattform
@@ -87,9 +87,10 @@ export { callAfterBindingsWorkaround } from 'ui/compat';
export {
getRequestInspectorStats,
getResponseInspectorStats,
-} from 'ui/courier/utils/courier_inspector_utils';
-// @ts-ignore
-export { hasSearchStategyForIndexPattern, isDefaultTypeIndexPattern } from 'ui/courier';
+ hasSearchStategyForIndexPattern,
+ isDefaultTypeIndexPattern,
+ SearchSource,
+} from '../../../../ui/public/courier';
// @ts-ignore
export { intervalOptions } from 'ui/agg_types/buckets/_interval_options';
// @ts-ignore
@@ -115,7 +116,6 @@ export { unhashUrl } from 'ui/state_management/state_hashing';
// EXPORT types
export { Vis } from 'ui/vis';
export { StaticIndexPattern, IndexPatterns, IndexPattern, FieldType } from 'ui/index_patterns';
-export { SearchSource } from 'ui/courier';
export { ElasticSearchHit } from 'ui/registry/doc_views_types';
export { DocViewRenderProps, DocViewRenderFn } from 'ui/registry/doc_views';
export { Adapters } from 'ui/inspector/types';
diff --git a/src/legacy/core_plugins/kibana/public/discover/plugin.ts b/src/legacy/core_plugins/kibana/public/discover/plugin.ts
index 873c429bf705d..7c2fb4f118915 100644
--- a/src/legacy/core_plugins/kibana/public/discover/plugin.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/plugin.ts
@@ -21,10 +21,7 @@ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/p
import { IUiActionsStart } from 'src/plugins/ui_actions/public';
import { registerFeature } from './helpers/register_feature';
import './kibana_services';
-import {
- Start as EmbeddableStart,
- Setup as EmbeddableSetup,
-} from '../../../../../plugins/embeddable/public';
+import { IEmbeddableStart, IEmbeddableSetup } from '../../../../../plugins/embeddable/public';
/**
* These are the interfaces with your public contracts. You should export these
@@ -35,11 +32,11 @@ export type DiscoverSetup = void;
export type DiscoverStart = void;
interface DiscoverSetupPlugins {
uiActions: IUiActionsStart;
- embeddable: EmbeddableSetup;
+ embeddable: IEmbeddableSetup;
}
interface DiscoverStartPlugins {
uiActions: IUiActionsStart;
- embeddable: EmbeddableStart;
+ embeddable: IEmbeddableStart;
}
export class DiscoverPlugin implements Plugin {
diff --git a/src/legacy/core_plugins/kibana/public/discover/types.d.ts b/src/legacy/core_plugins/kibana/public/discover/types.d.ts
index 7d8740243ec02..6cdd802fa2800 100644
--- a/src/legacy/core_plugins/kibana/public/discover/types.d.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/types.d.ts
@@ -17,13 +17,14 @@
* under the License.
*/
-import { SearchSource } from './kibana_services';
+import { SearchSourceContract } from '../../../../ui/public/courier';
import { SortOrder } from './angular/doc_table/components/table_header/helpers';
+export { SortOrder } from './angular/doc_table/components/table_header/helpers';
export interface SavedSearch {
readonly id: string;
title: string;
- searchSource: SearchSource;
+ searchSource: SearchSourceContract;
description?: string;
columns: string[];
sort: SortOrder[];
diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/activemq.svg b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/activemq.svg
new file mode 100644
index 0000000000000..20694ba6e62c7
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/activemq.svg
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts
index 924f72594ad34..a2b46dab1ef33 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts
@@ -25,12 +25,12 @@ import * as Rx from 'rxjs';
import { buildPipeline } from 'ui/visualize/loader/pipeline_helpers';
import { SavedObject } from 'ui/saved_objects/saved_object';
import { Vis } from 'ui/vis';
-import { SearchSource } from 'ui/courier';
import { queryGeohashBounds } from 'ui/visualize/loader/utils';
import { getTableAggs } from 'ui/visualize/loader/pipeline_helpers/utilities';
import { AppState } from 'ui/state_management/app_state';
import { npStart } from 'ui/new_platform';
import { IExpressionLoaderParams } from 'src/plugins/expressions/public';
+import { SearchSourceContract } from '../../../../../ui/public/courier';
import { VISUALIZE_EMBEDDABLE_TYPE } from './constants';
import {
TimeRange,
@@ -53,7 +53,7 @@ const getKeys = (o: T): Array => Object.keys(o) as Array<
export interface VisSavedObject extends SavedObject {
vis: Vis;
description?: string;
- searchSource: SearchSource;
+ searchSource: SearchSourceContract;
title: string;
uiStateJSON?: string;
destroy: () => void;
diff --git a/src/legacy/core_plugins/kibana/server/tutorials/activemq_metrics/index.js b/src/legacy/core_plugins/kibana/server/tutorials/activemq_metrics/index.js
new file mode 100644
index 0000000000000..b76a9ee7c4dbe
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/server/tutorials/activemq_metrics/index.js
@@ -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 { i18n } from '@kbn/i18n';
+import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category';
+import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions';
+
+export function activemqMetricsSpecProvider(context) {
+ const moduleName = 'activemq';
+ return {
+ id: 'activemqMetrics',
+ name: i18n.translate('kbn.server.tutorials.activemqMetrics.nameTitle', {
+ defaultMessage: 'ActiveMQ metrics',
+ }),
+ category: TUTORIAL_CATEGORY.METRICS,
+ shortDescription: i18n.translate('kbn.server.tutorials.activemqMetrics.shortDescription', {
+ defaultMessage: 'Fetch monitoring metrics from ActiveMQ instances.',
+ }),
+ longDescription: i18n.translate('kbn.server.tutorials.activemqMetrics.longDescription', {
+ defaultMessage: 'The `activemq` Metricbeat module fetches monitoring metrics from ActiveMQ instances \
+[Learn more]({learnMoreLink}).',
+ values: {
+ learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-activemq.html',
+ },
+ }),
+ euiIconType: '/plugins/kibana/home/tutorial_resources/logos/activemq.svg',
+ isBeta: true,
+ artifacts: {
+ application: {
+ label: i18n.translate('kbn.server.tutorials.corednsMetrics.artifacts.application.label', {
+ defaultMessage: 'Discover',
+ }),
+ path: '/app/kibana#/discover'
+ },
+ dashboards: [],
+ exportedFields: {
+ documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-activemq.html'
+ }
+ },
+ completionTimeMinutes: 10,
+ onPrem: onPremInstructions(moduleName, null, null, null, context),
+ elasticCloud: cloudInstructions(moduleName),
+ onPremElasticCloud: onPremCloudInstructions(moduleName)
+ };
+}
diff --git a/src/legacy/core_plugins/kibana/server/tutorials/register.js b/src/legacy/core_plugins/kibana/server/tutorials/register.js
index 2d1aaa92b1e26..f36909e59f39b 100644
--- a/src/legacy/core_plugins/kibana/server/tutorials/register.js
+++ b/src/legacy/core_plugins/kibana/server/tutorials/register.js
@@ -80,6 +80,7 @@ import { consulMetricsSpecProvider } from './consul_metrics';
import { cockroachdbMetricsSpecProvider } from './cockroachdb_metrics';
import { traefikMetricsSpecProvider } from './traefik_metrics';
import { awsLogsSpecProvider } from './aws_logs';
+import { activemqMetricsSpecProvider } from './activemq_metrics';
export function registerTutorials(server) {
server.newPlatform.setup.plugins.home.tutorials.registerTutorial(systemLogsSpecProvider);
@@ -146,4 +147,5 @@ export function registerTutorials(server) {
server.newPlatform.setup.plugins.home.tutorials.registerTutorial(cockroachdbMetricsSpecProvider);
server.newPlatform.setup.plugins.home.tutorials.registerTutorial(traefikMetricsSpecProvider);
server.newPlatform.setup.plugins.home.tutorials.registerTutorial(awsLogsSpecProvider);
+ server.newPlatform.setup.plugins.home.tutorials.registerTutorial(activemqMetricsSpecProvider);
}
diff --git a/src/legacy/core_plugins/status_page/index.js b/src/legacy/core_plugins/status_page/index.js
index 34de58048b887..9f0ad632fd5b1 100644
--- a/src/legacy/core_plugins/status_page/index.js
+++ b/src/legacy/core_plugins/status_page/index.js
@@ -26,6 +26,11 @@ export default function (kibana) {
hidden: true,
url: '/status',
},
+ injectDefaultVars(server) {
+ return {
+ isStatusPageAnonymous: server.config().get('status.allowAnonymous'),
+ };
+ }
}
});
}
diff --git a/src/legacy/server/config/__tests__/deprecation_warnings.js b/src/legacy/server/config/__tests__/deprecation_warnings.js
index 3cebc730e66de..0915f7de25b45 100644
--- a/src/legacy/server/config/__tests__/deprecation_warnings.js
+++ b/src/legacy/server/config/__tests__/deprecation_warnings.js
@@ -25,8 +25,7 @@ const RUN_KBN_SERVER_STARTUP = require.resolve('./fixtures/run_kbn_server_startu
const SETUP_NODE_ENV = require.resolve('../../../../setup_node_env');
const SECOND = 1000;
-// FLAKY: https://github.com/elastic/kibana/issues/51479
-describe.skip('config/deprecation warnings', function () {
+describe('config/deprecation warnings', function () {
this.timeout(15 * SECOND);
let stdio = '';
@@ -52,9 +51,9 @@ describe.skip('config/deprecation warnings', function () {
}
});
- // Either time out in 10 seconds, or resolve once the line is in our buffer
+ // Either time out in 60 seconds, or resolve once the line is in our buffer
return Promise.race([
- new Promise((resolve) => setTimeout(resolve, 10000)),
+ new Promise((resolve) => setTimeout(resolve, 60000)),
new Promise((resolve, reject) => {
proc.stdout.on('data', (chunk) => {
stdio += chunk.toString('utf8');
diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts
index 7399f2d08508f..9cc4e30d4252d 100644
--- a/src/legacy/server/kbn_server.d.ts
+++ b/src/legacy/server/kbn_server.d.ts
@@ -107,7 +107,6 @@ export default class KbnServer {
__internals: {
hapiServer: LegacyServiceSetupDeps['core']['http']['server'];
uiPlugins: LegacyServiceSetupDeps['core']['plugins']['uiPlugins'];
- uiPluginConfigs: LegacyServiceSetupDeps['core']['plugins']['uiPluginConfigs'];
elasticsearch: LegacyServiceSetupDeps['core']['elasticsearch'];
uiSettings: LegacyServiceSetupDeps['core']['uiSettings'];
kibanaMigrator: LegacyServiceStartDeps['core']['savedObjects']['migrator'];
diff --git a/src/legacy/ui/public/agg_types/agg_config.ts b/src/legacy/ui/public/agg_types/agg_config.ts
index de1a6059774e7..d4ef203721456 100644
--- a/src/legacy/ui/public/agg_types/agg_config.ts
+++ b/src/legacy/ui/public/agg_types/agg_config.ts
@@ -27,6 +27,7 @@
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import { npStart } from 'ui/new_platform';
+import { SearchSourceContract, FetchOptions } from '../courier/types';
import { AggType } from './agg_type';
import { FieldParamType } from './param_types/field';
import { AggGroupNames } from '../vis/editors/default/agg_groups';
@@ -233,10 +234,10 @@ export class AggConfig {
/**
* Hook for pre-flight logic, see AggType#onSearchRequestStart
* @param {Courier.SearchSource} searchSource
- * @param {Courier.SearchRequest} searchRequest
+ * @param {Courier.FetchOptions} options
* @return {Promise}
*/
- onSearchRequestStart(searchSource: any, options: any) {
+ onSearchRequestStart(searchSource: SearchSourceContract, options?: FetchOptions) {
if (!this.type) {
return Promise.resolve();
}
diff --git a/src/legacy/ui/public/agg_types/agg_configs.ts b/src/legacy/ui/public/agg_types/agg_configs.ts
index 7c0245f30a1fd..2f6951891f84d 100644
--- a/src/legacy/ui/public/agg_types/agg_configs.ts
+++ b/src/legacy/ui/public/agg_types/agg_configs.ts
@@ -33,6 +33,7 @@ import { Schema } from '../vis/editors/default/schemas';
import { AggConfig, AggConfigOptions } from './agg_config';
import { AggGroupNames } from '../vis/editors/default/agg_groups';
import { IndexPattern } from '../../../core_plugins/data/public';
+import { SearchSourceContract, FetchOptions } from '../courier/types';
function removeParentAggs(obj: any) {
for (const prop in obj) {
@@ -301,7 +302,7 @@ export class AggConfigs {
return _.find(reqAgg.getResponseAggs(), { id });
}
- onSearchRequestStart(searchSource: any, options: any) {
+ onSearchRequestStart(searchSource: SearchSourceContract, options?: FetchOptions) {
return Promise.all(
// @ts-ignore
this.getRequestAggs().map((agg: AggConfig) => agg.onSearchRequestStart(searchSource, options))
diff --git a/src/legacy/ui/public/agg_types/buckets/terms.ts b/src/legacy/ui/public/agg_types/buckets/terms.ts
index 89e33784fb5fb..6ce0b9ce38ad3 100644
--- a/src/legacy/ui/public/agg_types/buckets/terms.ts
+++ b/src/legacy/ui/public/agg_types/buckets/terms.ts
@@ -19,16 +19,12 @@
import chrome from 'ui/chrome';
import { noop } from 'lodash';
-import { SearchSource } from 'ui/courier';
import { i18n } from '@kbn/i18n';
+import { SearchSource, getRequestInspectorStats, getResponseInspectorStats } from '../../courier';
import { BucketAggType, BucketAggParam } from './_bucket_agg_type';
import { BUCKET_TYPES } from './bucket_agg_types';
import { AggConfigOptions } from '../agg_config';
import { IBucketAggConfig } from './_bucket_agg_type';
-import {
- getRequestInspectorStats,
- getResponseInspectorStats,
-} from '../../courier/utils/courier_inspector_utils';
import { createFilterTerms } from './create_filter/terms';
import { wrapWithInlineComp } from './inline_comp_wrapper';
import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exclude_format';
diff --git a/src/legacy/ui/public/agg_types/param_types/base.ts b/src/legacy/ui/public/agg_types/param_types/base.ts
index bc8ed5d485bd4..61ef73fb62e8a 100644
--- a/src/legacy/ui/public/agg_types/param_types/base.ts
+++ b/src/legacy/ui/public/agg_types/param_types/base.ts
@@ -20,7 +20,7 @@
import { AggParam } from '../';
import { AggConfigs } from '../agg_configs';
import { AggConfig } from '../../vis';
-import { SearchSource } from '../../courier';
+import { SearchSourceContract, FetchOptions } from '../../courier/types';
export class BaseParamType implements AggParam {
name: string;
@@ -55,8 +55,8 @@ export class BaseParamType implements AggParam {
*/
modifyAggConfigOnSearchRequestStart: (
aggConfig: AggConfig,
- searchSource?: SearchSource,
- options?: any
+ searchSource?: SearchSourceContract,
+ options?: FetchOptions
) => void;
constructor(config: Record) {
diff --git a/src/legacy/ui/public/courier/fetch/call_client.test.js b/src/legacy/ui/public/courier/fetch/call_client.test.ts
similarity index 64%
rename from src/legacy/ui/public/courier/fetch/call_client.test.js
rename to src/legacy/ui/public/courier/fetch/call_client.test.ts
index 463d6c59e479e..74c87d77dd4fd 100644
--- a/src/legacy/ui/public/courier/fetch/call_client.test.js
+++ b/src/legacy/ui/public/courier/fetch/call_client.test.ts
@@ -19,61 +19,64 @@
import { callClient } from './call_client';
import { handleResponse } from './handle_response';
+import { FetchHandlers, SearchRequest, SearchStrategySearchParams } from '../types';
const mockResponses = [{}, {}];
const mockAbortFns = [jest.fn(), jest.fn()];
const mockSearchFns = [
- jest.fn(({ searchRequests }) => ({
+ jest.fn(({ searchRequests }: SearchStrategySearchParams) => ({
searching: Promise.resolve(Array(searchRequests.length).fill(mockResponses[0])),
- abort: mockAbortFns[0]
+ abort: mockAbortFns[0],
})),
- jest.fn(({ searchRequests }) => ({
+ jest.fn(({ searchRequests }: SearchStrategySearchParams) => ({
searching: Promise.resolve(Array(searchRequests.length).fill(mockResponses[1])),
- abort: mockAbortFns[1]
- }))
+ abort: mockAbortFns[1],
+ })),
];
const mockSearchStrategies = mockSearchFns.map((search, i) => ({ search, id: i }));
jest.mock('./handle_response', () => ({
- handleResponse: jest.fn((request, response) => response)
+ handleResponse: jest.fn((request, response) => response),
}));
jest.mock('../search_strategy', () => ({
- getSearchStrategyForSearchRequest: request => mockSearchStrategies[request._searchStrategyId],
- getSearchStrategyById: id => mockSearchStrategies[id]
+ getSearchStrategyForSearchRequest: (request: SearchRequest) =>
+ mockSearchStrategies[request._searchStrategyId],
+ getSearchStrategyById: (id: number) => mockSearchStrategies[id],
}));
describe('callClient', () => {
beforeEach(() => {
- handleResponse.mockClear();
+ (handleResponse as jest.Mock).mockClear();
mockAbortFns.forEach(fn => fn.mockClear());
mockSearchFns.forEach(fn => fn.mockClear());
});
test('Executes each search strategy with its group of matching requests', () => {
- const searchRequests = [{
- _searchStrategyId: 0
- }, {
- _searchStrategyId: 1
- }, {
- _searchStrategyId: 0
- }, {
- _searchStrategyId: 1
- }];
-
- callClient(searchRequests);
+ const searchRequests = [
+ { _searchStrategyId: 0 },
+ { _searchStrategyId: 1 },
+ { _searchStrategyId: 0 },
+ { _searchStrategyId: 1 },
+ ];
+
+ callClient(searchRequests, [], {} as FetchHandlers);
expect(mockSearchFns[0]).toBeCalled();
- expect(mockSearchFns[0].mock.calls[0][0].searchRequests).toEqual([searchRequests[0], searchRequests[2]]);
+ expect(mockSearchFns[0].mock.calls[0][0].searchRequests).toEqual([
+ searchRequests[0],
+ searchRequests[2],
+ ]);
expect(mockSearchFns[1]).toBeCalled();
- expect(mockSearchFns[1].mock.calls[0][0].searchRequests).toEqual([searchRequests[1], searchRequests[3]]);
+ expect(mockSearchFns[1].mock.calls[0][0].searchRequests).toEqual([
+ searchRequests[1],
+ searchRequests[3],
+ ]);
});
test('Passes the additional arguments it is given to the search strategy', () => {
- const searchRequests = [{
- _searchStrategyId: 0
- }];
- const args = { es: {}, config: {}, esShardTimeout: 0 };
+ const searchRequests = [{ _searchStrategyId: 0 }];
+ const args = { es: {}, config: {}, esShardTimeout: 0 } as FetchHandlers;
callClient(searchRequests, [], args);
@@ -82,25 +85,17 @@ describe('callClient', () => {
});
test('Returns the responses in the original order', async () => {
- const searchRequests = [{
- _searchStrategyId: 1
- }, {
- _searchStrategyId: 0
- }];
+ const searchRequests = [{ _searchStrategyId: 1 }, { _searchStrategyId: 0 }];
- const responses = await Promise.all(callClient(searchRequests));
+ const responses = await Promise.all(callClient(searchRequests, [], {} as FetchHandlers));
expect(responses).toEqual([mockResponses[1], mockResponses[0]]);
});
test('Calls handleResponse with each request and response', async () => {
- const searchRequests = [{
- _searchStrategyId: 0
- }, {
- _searchStrategyId: 1
- }];
+ const searchRequests = [{ _searchStrategyId: 0 }, { _searchStrategyId: 1 }];
- const responses = callClient(searchRequests);
+ const responses = callClient(searchRequests, [], {} as FetchHandlers);
await Promise.all(responses);
expect(handleResponse).toBeCalledTimes(2);
@@ -109,17 +104,15 @@ describe('callClient', () => {
});
test('If passed an abortSignal, calls abort on the strategy if the signal is aborted', () => {
- const searchRequests = [{
- _searchStrategyId: 0
- }, {
- _searchStrategyId: 1
- }];
+ const searchRequests = [{ _searchStrategyId: 0 }, { _searchStrategyId: 1 }];
const abortController = new AbortController();
- const requestOptions = [{
- abortSignal: abortController.signal
- }];
+ const requestOptions = [
+ {
+ abortSignal: abortController.signal,
+ },
+ ];
- callClient(searchRequests, requestOptions);
+ callClient(searchRequests, requestOptions, {} as FetchHandlers);
abortController.abort();
expect(mockAbortFns[0]).toBeCalled();
diff --git a/src/legacy/ui/public/courier/fetch/call_client.js b/src/legacy/ui/public/courier/fetch/call_client.ts
similarity index 70%
rename from src/legacy/ui/public/courier/fetch/call_client.js
rename to src/legacy/ui/public/courier/fetch/call_client.ts
index 971ae4c49a604..43da27f941e4e 100644
--- a/src/legacy/ui/public/courier/fetch/call_client.js
+++ b/src/legacy/ui/public/courier/fetch/call_client.ts
@@ -20,11 +20,20 @@
import { groupBy } from 'lodash';
import { getSearchStrategyForSearchRequest, getSearchStrategyById } from '../search_strategy';
import { handleResponse } from './handle_response';
+import { FetchOptions, FetchHandlers } from './types';
+import { SearchRequest } from '../types';
-export function callClient(searchRequests, requestsOptions = [], { es, config, esShardTimeout } = {}) {
+export function callClient(
+ searchRequests: SearchRequest[],
+ requestsOptions: FetchOptions[] = [],
+ { es, config, esShardTimeout }: FetchHandlers
+) {
// Correlate the options with the request that they're associated with
- const requestOptionEntries = searchRequests.map((request, i) => [request, requestsOptions[i]]);
- const requestOptionsMap = new Map(requestOptionEntries);
+ const requestOptionEntries: Array<[
+ SearchRequest,
+ FetchOptions
+ ]> = searchRequests.map((request, i) => [request, requestsOptions[i]]);
+ const requestOptionsMap = new Map(requestOptionEntries);
// Group the requests by the strategy used to search that specific request
const searchStrategyMap = groupBy(searchRequests, (request, i) => {
@@ -39,15 +48,22 @@ export function callClient(searchRequests, requestsOptions = [], { es, config, e
Object.keys(searchStrategyMap).forEach(searchStrategyId => {
const searchStrategy = getSearchStrategyById(searchStrategyId);
const requests = searchStrategyMap[searchStrategyId];
- const { searching, abort } = searchStrategy.search({ searchRequests: requests, es, config, esShardTimeout });
+
+ // There's no way `searchStrategy` could be undefined here because if we didn't get a matching strategy for this ID
+ // then an error would have been thrown above
+ const { searching, abort } = searchStrategy!.search({
+ searchRequests: requests,
+ es,
+ config,
+ esShardTimeout,
+ });
+
requests.forEach((request, i) => {
const response = searching.then(results => handleResponse(request, results[i]));
- const { abortSignal } = requestOptionsMap.get(request) || {};
+ const { abortSignal = null } = requestOptionsMap.get(request) || {};
if (abortSignal) abortSignal.addEventListener('abort', abort);
requestResponseMap.set(request, response);
});
}, []);
return searchRequests.map(request => requestResponseMap.get(request));
}
-
-
diff --git a/src/legacy/ui/public/courier/fetch/components/shard_failure_types.ts b/src/legacy/ui/public/courier/fetch/components/shard_failure_types.ts
index de32b9d7b3087..22fc20233cc87 100644
--- a/src/legacy/ui/public/courier/fetch/components/shard_failure_types.ts
+++ b/src/legacy/ui/public/courier/fetch/components/shard_failure_types.ts
@@ -24,6 +24,7 @@ export interface Request {
sort: unknown;
stored_fields: string[];
}
+
export interface ResponseWithShardFailure {
_shards: {
failed: number;
diff --git a/src/legacy/ui/public/courier/fetch/errors.ts b/src/legacy/ui/public/courier/fetch/errors.ts
index aba554a795258..a2ac013915b4b 100644
--- a/src/legacy/ui/public/courier/fetch/errors.ts
+++ b/src/legacy/ui/public/courier/fetch/errors.ts
@@ -17,17 +17,18 @@
* under the License.
*/
+import { SearchError } from '../../courier';
import { KbnError } from '../../../../../plugins/kibana_utils/public';
+import { SearchResponse } from '../types';
/**
* Request Failure - When an entire multi request fails
* @param {Error} err - the Error that came back
* @param {Object} resp - optional HTTP response
*/
export class RequestFailure extends KbnError {
- public resp: any;
- constructor(err: any, resp?: any) {
- err = err || false;
- super(`Request to Elasticsearch failed: ${JSON.stringify(resp || err.message)}`);
+ public resp: SearchResponse;
+ constructor(err: SearchError | null = null, resp?: SearchResponse) {
+ super(`Request to Elasticsearch failed: ${JSON.stringify(resp || err?.message)}`);
this.resp = resp;
}
diff --git a/src/legacy/ui/public/courier/fetch/fetch_soon.js b/src/legacy/ui/public/courier/fetch/fetch_soon.js
deleted file mode 100644
index ef02beddcb59a..0000000000000
--- a/src/legacy/ui/public/courier/fetch/fetch_soon.js
+++ /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 { callClient } from './call_client';
-
-/**
- * This function introduces a slight delay in the request process to allow multiple requests to queue
- * up (e.g. when a dashboard is loading).
- */
-export async function fetchSoon(request, options, { es, config, esShardTimeout }) {
- const delay = config.get('courier:batchSearches') ? 50 : 0;
- return delayedFetch(request, options, { es, config, esShardTimeout }, delay);
-}
-
-/**
- * Delays executing a function for a given amount of time, and returns a promise that resolves
- * with the result.
- * @param fn The function to invoke
- * @param ms The number of milliseconds to wait
- * @return Promise A promise that resolves with the result of executing the function
- */
-function delay(fn, ms) {
- return new Promise(resolve => {
- setTimeout(() => resolve(fn()), ms);
- });
-}
-
-// The current batch/queue of requests to fetch
-let requestsToFetch = [];
-let requestOptions = [];
-
-// The in-progress fetch (if there is one)
-let fetchInProgress = null;
-
-/**
- * Delay fetching for a given amount of time, while batching up the requests to be fetched.
- * Returns a promise that resolves with the response for the given request.
- * @param request The request to fetch
- * @param ms The number of milliseconds to wait (and batch requests)
- * @return Promise The response for the given request
- */
-async function delayedFetch(request, options, { es, config, esShardTimeout }, ms) {
- const i = requestsToFetch.length;
- requestsToFetch = [...requestsToFetch, request];
- requestOptions = [...requestOptions, options];
- const responses = await (fetchInProgress = fetchInProgress || delay(() => {
- const response = callClient(requestsToFetch, requestOptions, { es, config, esShardTimeout });
- requestsToFetch = [];
- requestOptions = [];
- fetchInProgress = null;
- return response;
- }, ms));
- return responses[i];
-}
diff --git a/src/legacy/ui/public/courier/fetch/fetch_soon.test.js b/src/legacy/ui/public/courier/fetch/fetch_soon.test.ts
similarity index 63%
rename from src/legacy/ui/public/courier/fetch/fetch_soon.test.js
rename to src/legacy/ui/public/courier/fetch/fetch_soon.test.ts
index 824a4ab7e12e3..e753c526b748d 100644
--- a/src/legacy/ui/public/courier/fetch/fetch_soon.test.js
+++ b/src/legacy/ui/public/courier/fetch/fetch_soon.test.ts
@@ -19,47 +19,53 @@
import { fetchSoon } from './fetch_soon';
import { callClient } from './call_client';
-
-function getMockConfig(config) {
- const entries = Object.entries(config);
- return new Map(entries);
+import { UiSettingsClientContract } from '../../../../../core/public';
+import { FetchHandlers, FetchOptions } from './types';
+import { SearchRequest, SearchResponse } from '../types';
+
+function getConfigStub(config: any = {}) {
+ return {
+ get: key => config[key],
+ } as UiSettingsClientContract;
}
-const mockResponses = {
- 'foo': {},
- 'bar': {},
- 'baz': {},
+const mockResponses: Record = {
+ foo: {},
+ bar: {},
+ baz: {},
};
jest.useFakeTimers();
jest.mock('./call_client', () => ({
- callClient: jest.fn(requests => {
+ callClient: jest.fn((requests: SearchRequest[]) => {
// Allow a request object to specify which mockResponse it wants to receive (_mockResponseId)
// in addition to how long to simulate waiting before returning a response (_waitMs)
const responses = requests.map(request => {
- const waitMs = requests.reduce((total, request) => request._waitMs || 0, 0);
+ const waitMs = requests.reduce((total, { _waitMs }) => total + _waitMs || 0, 0);
return new Promise(resolve => {
- resolve(mockResponses[request._mockResponseId]);
- }, waitMs);
+ setTimeout(() => {
+ resolve(mockResponses[request._mockResponseId]);
+ }, waitMs);
+ });
});
return Promise.resolve(responses);
- })
+ }),
}));
describe('fetchSoon', () => {
beforeEach(() => {
- callClient.mockClear();
+ (callClient as jest.Mock).mockClear();
});
test('should delay by 0ms if config is set to not batch searches', () => {
- const config = getMockConfig({
- 'courier:batchSearches': false
+ const config = getConfigStub({
+ 'courier:batchSearches': false,
});
const request = {};
const options = {};
- fetchSoon(request, options, { config });
+ fetchSoon(request, options, { config } as FetchHandlers);
expect(callClient).not.toBeCalled();
jest.advanceTimersByTime(0);
@@ -67,13 +73,13 @@ describe('fetchSoon', () => {
});
test('should delay by 50ms if config is set to batch searches', () => {
- const config = getMockConfig({
- 'courier:batchSearches': true
+ const config = getConfigStub({
+ 'courier:batchSearches': true,
});
const request = {};
const options = {};
- fetchSoon(request, options, { config });
+ fetchSoon(request, options, { config } as FetchHandlers);
expect(callClient).not.toBeCalled();
jest.advanceTimersByTime(0);
@@ -83,30 +89,30 @@ describe('fetchSoon', () => {
});
test('should send a batch of requests to callClient', () => {
- const config = getMockConfig({
- 'courier:batchSearches': true
+ const config = getConfigStub({
+ 'courier:batchSearches': true,
});
const requests = [{ foo: 1 }, { foo: 2 }];
const options = [{ bar: 1 }, { bar: 2 }];
requests.forEach((request, i) => {
- fetchSoon(request, options[i], { config });
+ fetchSoon(request, options[i] as FetchOptions, { config } as FetchHandlers);
});
jest.advanceTimersByTime(50);
expect(callClient).toBeCalledTimes(1);
- expect(callClient.mock.calls[0][0]).toEqual(requests);
- expect(callClient.mock.calls[0][1]).toEqual(options);
+ expect((callClient as jest.Mock).mock.calls[0][0]).toEqual(requests);
+ expect((callClient as jest.Mock).mock.calls[0][1]).toEqual(options);
});
test('should return the response to the corresponding call for multiple batched requests', async () => {
- const config = getMockConfig({
- 'courier:batchSearches': true
+ const config = getConfigStub({
+ 'courier:batchSearches': true,
});
const requests = [{ _mockResponseId: 'foo' }, { _mockResponseId: 'bar' }];
const promises = requests.map(request => {
- return fetchSoon(request, {}, { config });
+ return fetchSoon(request, {}, { config } as FetchHandlers);
});
jest.advanceTimersByTime(50);
const results = await Promise.all(promises);
@@ -115,26 +121,26 @@ describe('fetchSoon', () => {
});
test('should wait for the previous batch to start before starting a new batch', () => {
- const config = getMockConfig({
- 'courier:batchSearches': true
+ const config = getConfigStub({
+ 'courier:batchSearches': true,
});
const firstBatch = [{ foo: 1 }, { foo: 2 }];
const secondBatch = [{ bar: 1 }, { bar: 2 }];
firstBatch.forEach(request => {
- fetchSoon(request, {}, { config });
+ fetchSoon(request, {}, { config } as FetchHandlers);
});
jest.advanceTimersByTime(50);
secondBatch.forEach(request => {
- fetchSoon(request, {}, { config });
+ fetchSoon(request, {}, { config } as FetchHandlers);
});
expect(callClient).toBeCalledTimes(1);
- expect(callClient.mock.calls[0][0]).toEqual(firstBatch);
+ expect((callClient as jest.Mock).mock.calls[0][0]).toEqual(firstBatch);
jest.advanceTimersByTime(50);
expect(callClient).toBeCalledTimes(2);
- expect(callClient.mock.calls[1][0]).toEqual(secondBatch);
+ expect((callClient as jest.Mock).mock.calls[1][0]).toEqual(secondBatch);
});
});
diff --git a/src/legacy/ui/public/courier/fetch/fetch_soon.ts b/src/legacy/ui/public/courier/fetch/fetch_soon.ts
new file mode 100644
index 0000000000000..75de85e02a1a2
--- /dev/null
+++ b/src/legacy/ui/public/courier/fetch/fetch_soon.ts
@@ -0,0 +1,83 @@
+/*
+ * 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 { callClient } from './call_client';
+import { FetchHandlers, FetchOptions } from './types';
+import { SearchRequest, SearchResponse } from '../types';
+
+/**
+ * This function introduces a slight delay in the request process to allow multiple requests to queue
+ * up (e.g. when a dashboard is loading).
+ */
+export async function fetchSoon(
+ request: SearchRequest,
+ options: FetchOptions,
+ { es, config, esShardTimeout }: FetchHandlers
+) {
+ const msToDelay = config.get('courier:batchSearches') ? 50 : 0;
+ return delayedFetch(request, options, { es, config, esShardTimeout }, msToDelay);
+}
+
+/**
+ * Delays executing a function for a given amount of time, and returns a promise that resolves
+ * with the result.
+ * @param fn The function to invoke
+ * @param ms The number of milliseconds to wait
+ * @return Promise A promise that resolves with the result of executing the function
+ */
+function delay(fn: Function, ms: number) {
+ return new Promise(resolve => {
+ setTimeout(() => resolve(fn()), ms);
+ });
+}
+
+// The current batch/queue of requests to fetch
+let requestsToFetch: SearchRequest[] = [];
+let requestOptions: FetchOptions[] = [];
+
+// The in-progress fetch (if there is one)
+let fetchInProgress: Promise | null = null;
+
+/**
+ * Delay fetching for a given amount of time, while batching up the requests to be fetched.
+ * Returns a promise that resolves with the response for the given request.
+ * @param request The request to fetch
+ * @param ms The number of milliseconds to wait (and batch requests)
+ * @return Promise The response for the given request
+ */
+async function delayedFetch(
+ request: SearchRequest,
+ options: FetchOptions,
+ { es, config, esShardTimeout }: FetchHandlers,
+ ms: number
+) {
+ const i = requestsToFetch.length;
+ requestsToFetch = [...requestsToFetch, request];
+ requestOptions = [...requestOptions, options];
+ const responses = await (fetchInProgress =
+ fetchInProgress ||
+ delay(() => {
+ const response = callClient(requestsToFetch, requestOptions, { es, config, esShardTimeout });
+ requestsToFetch = [];
+ requestOptions = [];
+ fetchInProgress = null;
+ return response;
+ }, ms));
+ return responses[i];
+}
diff --git a/src/legacy/ui/public/courier/fetch/get_search_params.test.js b/src/legacy/ui/public/courier/fetch/get_search_params.test.ts
similarity index 96%
rename from src/legacy/ui/public/courier/fetch/get_search_params.test.js
rename to src/legacy/ui/public/courier/fetch/get_search_params.test.ts
index 380d1da963ddf..d6f3d33099599 100644
--- a/src/legacy/ui/public/courier/fetch/get_search_params.test.js
+++ b/src/legacy/ui/public/courier/fetch/get_search_params.test.ts
@@ -18,11 +18,12 @@
*/
import { getMSearchParams, getSearchParams } from './get_search_params';
+import { UiSettingsClientContract } from '../../../../../core/public';
-function getConfigStub(config = {}) {
+function getConfigStub(config: any = {}) {
return {
- get: key => config[key]
- };
+ get: key => config[key],
+ } as UiSettingsClientContract;
}
describe('getMSearchParams', () => {
diff --git a/src/legacy/ui/public/courier/fetch/get_search_params.js b/src/legacy/ui/public/courier/fetch/get_search_params.ts
similarity index 73%
rename from src/legacy/ui/public/courier/fetch/get_search_params.js
rename to src/legacy/ui/public/courier/fetch/get_search_params.ts
index dd55201ba5540..6b8da07ca93d4 100644
--- a/src/legacy/ui/public/courier/fetch/get_search_params.js
+++ b/src/legacy/ui/public/courier/fetch/get_search_params.ts
@@ -17,9 +17,11 @@
* under the License.
*/
+import { UiSettingsClientContract } from '../../../../../core/public';
+
const sessionId = Date.now();
-export function getMSearchParams(config) {
+export function getMSearchParams(config: UiSettingsClientContract) {
return {
rest_total_hits_as_int: true,
ignore_throttled: getIgnoreThrottled(config),
@@ -27,7 +29,7 @@ export function getMSearchParams(config) {
};
}
-export function getSearchParams(config, esShardTimeout) {
+export function getSearchParams(config: UiSettingsClientContract, esShardTimeout: number = 0) {
return {
rest_total_hits_as_int: true,
ignore_unavailable: true,
@@ -38,21 +40,23 @@ export function getSearchParams(config, esShardTimeout) {
};
}
-export function getIgnoreThrottled(config) {
+export function getIgnoreThrottled(config: UiSettingsClientContract) {
return !config.get('search:includeFrozen');
}
-export function getMaxConcurrentShardRequests(config) {
+export function getMaxConcurrentShardRequests(config: UiSettingsClientContract) {
const maxConcurrentShardRequests = config.get('courier:maxConcurrentShardRequests');
return maxConcurrentShardRequests > 0 ? maxConcurrentShardRequests : undefined;
}
-export function getPreference(config) {
+export function getPreference(config: UiSettingsClientContract) {
const setRequestPreference = config.get('courier:setRequestPreference');
if (setRequestPreference === 'sessionId') return sessionId;
- return setRequestPreference === 'custom' ? config.get('courier:customRequestPreference') : undefined;
+ return setRequestPreference === 'custom'
+ ? config.get('courier:customRequestPreference')
+ : undefined;
}
-export function getTimeout(esShardTimeout) {
+export function getTimeout(esShardTimeout: number) {
return esShardTimeout > 0 ? `${esShardTimeout}ms` : undefined;
}
diff --git a/src/legacy/ui/public/courier/fetch/handle_response.test.js b/src/legacy/ui/public/courier/fetch/handle_response.test.ts
similarity index 78%
rename from src/legacy/ui/public/courier/fetch/handle_response.test.js
rename to src/legacy/ui/public/courier/fetch/handle_response.test.ts
index 0836832e6c05a..0163aca777161 100644
--- a/src/legacy/ui/public/courier/fetch/handle_response.test.js
+++ b/src/legacy/ui/public/courier/fetch/handle_response.test.ts
@@ -23,46 +23,50 @@ import { toastNotifications } from '../../notify/toasts';
jest.mock('../../notify/toasts', () => {
return {
toastNotifications: {
- addWarning: jest.fn()
- }
+ addWarning: jest.fn(),
+ },
};
});
jest.mock('@kbn/i18n', () => {
return {
i18n: {
- translate: (id, { defaultMessage }) => defaultMessage
- }
+ translate: (id: string, { defaultMessage }: { defaultMessage: string }) => defaultMessage,
+ },
};
});
describe('handleResponse', () => {
beforeEach(() => {
- toastNotifications.addWarning.mockReset();
+ (toastNotifications.addWarning as jest.Mock).mockReset();
});
test('should notify if timed out', () => {
const request = { body: {} };
const response = {
- timed_out: true
+ timed_out: true,
};
const result = handleResponse(request, response);
expect(result).toBe(response);
expect(toastNotifications.addWarning).toBeCalled();
- expect(toastNotifications.addWarning.mock.calls[0][0].title).toMatch('request timed out');
+ expect((toastNotifications.addWarning as jest.Mock).mock.calls[0][0].title).toMatch(
+ 'request timed out'
+ );
});
test('should notify if shards failed', () => {
const request = { body: {} };
const response = {
_shards: {
- failed: true
- }
+ failed: true,
+ },
};
const result = handleResponse(request, response);
expect(result).toBe(response);
expect(toastNotifications.addWarning).toBeCalled();
- expect(toastNotifications.addWarning.mock.calls[0][0].title).toMatch('shards failed');
+ expect((toastNotifications.addWarning as jest.Mock).mock.calls[0][0].title).toMatch(
+ 'shards failed'
+ );
});
test('returns the response', () => {
diff --git a/src/legacy/ui/public/courier/fetch/handle_response.js b/src/legacy/ui/public/courier/fetch/handle_response.tsx
similarity index 71%
rename from src/legacy/ui/public/courier/fetch/handle_response.js
rename to src/legacy/ui/public/courier/fetch/handle_response.tsx
index fb2797369d78f..d7f2263268f8c 100644
--- a/src/legacy/ui/public/courier/fetch/handle_response.js
+++ b/src/legacy/ui/public/courier/fetch/handle_response.tsx
@@ -17,14 +17,16 @@
* under the License.
*/
-
import React from 'react';
-import { toastNotifications } from '../../notify/toasts';
import { i18n } from '@kbn/i18n';
import { EuiSpacer } from '@elastic/eui';
+import { toastNotifications } from '../../notify/toasts';
import { ShardFailureOpenModalButton } from './components/shard_failure_open_modal_button';
+import { Request, ResponseWithShardFailure } from './components/shard_failure_types';
+import { SearchRequest, SearchResponse } from '../types';
+import { toMountPoint } from '../../../../../plugins/kibana_react/public';
-export function handleResponse(request, response) {
+export function handleResponse(request: SearchRequest, response: SearchResponse) {
if (response.timed_out) {
toastNotifications.addWarning({
title: i18n.translate('common.ui.courier.fetch.requestTimedOutNotificationMessage', {
@@ -41,26 +43,26 @@ export function handleResponse(request, response) {
shardsTotal: response._shards.total,
},
});
- const description = i18n.translate('common.ui.courier.fetch.shardsFailedNotificationDescription', {
- defaultMessage: 'The data you are seeing might be incomplete or wrong.',
- });
+ const description = i18n.translate(
+ 'common.ui.courier.fetch.shardsFailedNotificationDescription',
+ {
+ defaultMessage: 'The data you are seeing might be incomplete or wrong.',
+ }
+ );
- const text = (
+ const text = toMountPoint(
<>
{description}
-
+
>
);
- toastNotifications.addWarning({
- title,
- text,
- });
+ toastNotifications.addWarning({ title, text });
}
return response;
diff --git a/src/legacy/ui/public/courier/fetch/index.js b/src/legacy/ui/public/courier/fetch/index.ts
similarity index 100%
rename from src/legacy/ui/public/courier/fetch/index.js
rename to src/legacy/ui/public/courier/fetch/index.ts
diff --git a/src/legacy/ui/public/courier/fetch/types.ts b/src/legacy/ui/public/courier/fetch/types.ts
new file mode 100644
index 0000000000000..e341e1ab35c5c
--- /dev/null
+++ b/src/legacy/ui/public/courier/fetch/types.ts
@@ -0,0 +1,41 @@
+/*
+ * 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 { UiSettingsClientContract } from '../../../../../core/public';
+import { SearchRequest, SearchResponse } from '../types';
+
+export interface ApiCaller {
+ search: (searchRequest: SearchRequest) => ApiCallerResponse;
+ msearch: (searchRequest: SearchRequest) => ApiCallerResponse;
+}
+
+export interface ApiCallerResponse extends Promise {
+ abort: () => void;
+}
+
+export interface FetchOptions {
+ abortSignal?: AbortSignal;
+ searchStrategyId?: string;
+}
+
+export interface FetchHandlers {
+ es: ApiCaller;
+ config: UiSettingsClientContract;
+ esShardTimeout: number;
+}
diff --git a/src/legacy/ui/public/courier/index.d.ts b/src/legacy/ui/public/courier/index.ts
similarity index 94%
rename from src/legacy/ui/public/courier/index.d.ts
rename to src/legacy/ui/public/courier/index.ts
index 93556c2666c9a..3c16926d2aba7 100644
--- a/src/legacy/ui/public/courier/index.d.ts
+++ b/src/legacy/ui/public/courier/index.ts
@@ -17,6 +17,8 @@
* under the License.
*/
+export * from './fetch';
export * from './search_source';
export * from './search_strategy';
export * from './utils/courier_inspector_utils';
+export * from './types';
diff --git a/src/legacy/ui/public/courier/search_poll/search_poll.js b/src/legacy/ui/public/courier/search_poll/search_poll.js
deleted file mode 100644
index f00c2a32e0ec6..0000000000000
--- a/src/legacy/ui/public/courier/search_poll/search_poll.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _ from 'lodash';
-
-import { timefilter } from 'ui/timefilter';
-
-export class SearchPoll {
- constructor() {
- this._isPolling = false;
- this._intervalInMs = undefined;
- this._timerId = null;
- }
-
- setIntervalInMs = intervalInMs => {
- this._intervalInMs = _.parseInt(intervalInMs);
- };
-
- resume = () => {
- this._isPolling = true;
- this.resetTimer();
- };
-
- pause = () => {
- this._isPolling = false;
- this.clearTimer();
- };
-
- resetTimer = () => {
- // Cancel the pending search and schedule a new one.
- this.clearTimer();
-
- if (this._isPolling) {
- this._timerId = setTimeout(this._search, this._intervalInMs);
- }
- };
-
- clearTimer = () => {
- // Cancel the pending search, if there is one.
- if (this._timerId) {
- clearTimeout(this._timerId);
- this._timerId = null;
- }
- };
-
- _search = () => {
- // Schedule another search.
- this.resetTimer();
-
- timefilter.notifyShouldFetch();
- };
-}
diff --git a/src/legacy/ui/public/courier/search_source/__tests__/normalize_sort_request.js b/src/legacy/ui/public/courier/search_source/__tests__/normalize_sort_request.js
deleted file mode 100644
index 279e389dec114..0000000000000
--- a/src/legacy/ui/public/courier/search_source/__tests__/normalize_sort_request.js
+++ /dev/null
@@ -1,124 +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 '../../../private';
-import ngMock from 'ng_mock';
-import expect from '@kbn/expect';
-import { normalizeSortRequest } from '../_normalize_sort_request';
-import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
-import _ from 'lodash';
-
-describe('SearchSource#normalizeSortRequest', function () {
- let indexPattern;
- let normalizedSort;
- const defaultSortOptions = { unmapped_type: 'boolean' };
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(ngMock.inject(function (Private) {
- indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
-
- normalizedSort = [{
- someField: {
- order: 'desc',
- unmapped_type: 'boolean'
- }
- }];
- }));
-
- it('should return an array', function () {
- const sortable = { someField: 'desc' };
- const result = normalizeSortRequest(sortable, indexPattern, defaultSortOptions);
- expect(result).to.be.an(Array);
- expect(result).to.eql(normalizedSort);
- // ensure object passed in is not mutated
- expect(result[0]).to.not.be.equal(sortable);
- expect(sortable).to.eql({ someField: 'desc' });
- });
-
- it('should make plain string sort into the more verbose format', function () {
- const result = normalizeSortRequest([{ someField: 'desc' }], indexPattern, defaultSortOptions);
- expect(result).to.eql(normalizedSort);
- });
-
- it('should append default sort options', function () {
- const sortState = [{
- someField: {
- order: 'desc',
- unmapped_type: 'boolean'
- }
- }];
- const result = normalizeSortRequest(sortState, indexPattern, defaultSortOptions);
- expect(result).to.eql(normalizedSort);
- });
-
- it('should enable script based sorting', function () {
- const fieldName = 'script string';
- const direction = 'desc';
- const indexField = indexPattern.fields.getByName(fieldName);
-
- const sortState = {};
- sortState[fieldName] = direction;
- normalizedSort = {
- _script: {
- script: {
- source: indexField.script,
- lang: indexField.lang
- },
- type: indexField.type,
- order: direction
- }
- };
-
- let result = normalizeSortRequest(sortState, indexPattern, defaultSortOptions);
- expect(result).to.eql([normalizedSort]);
-
- sortState[fieldName] = { order: direction };
- result = normalizeSortRequest([sortState], indexPattern, defaultSortOptions);
- expect(result).to.eql([normalizedSort]);
- });
-
- it('should use script based sorting only on sortable types', function () {
- const fieldName = 'script murmur3';
- const direction = 'asc';
-
- const sortState = {};
- sortState[fieldName] = direction;
- normalizedSort = {};
- normalizedSort[fieldName] = {
- order: direction,
- unmapped_type: 'boolean'
- };
- const result = normalizeSortRequest([sortState], indexPattern, defaultSortOptions);
-
- expect(result).to.eql([normalizedSort]);
- });
-
- it('should remove unmapped_type parameter from _score sorting', function () {
- const sortable = { _score: 'desc' };
- const expected = [{
- _score: {
- order: 'desc'
- }
- }];
-
- const result = normalizeSortRequest(sortable, indexPattern, defaultSortOptions);
- expect(_.isEqual(result, expected)).to.be.ok();
-
- });
-});
diff --git a/src/legacy/ui/public/courier/search_source/_normalize_sort_request.js b/src/legacy/ui/public/courier/search_source/_normalize_sort_request.js
deleted file mode 100644
index 3e5d7a1374115..0000000000000
--- a/src/legacy/ui/public/courier/search_source/_normalize_sort_request.js
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _ from 'lodash';
-
-/**
- * Decorate queries with default parameters
- * @param {query} query object
- * @returns {object}
- */
-export function normalizeSortRequest(sortObject, indexPattern, defaultSortOptions) {
- // [].concat({}) -> [{}], [].concat([{}]) -> [{}]
- return [].concat(sortObject).map(function (sortable) {
- return normalize(sortable, indexPattern, defaultSortOptions);
- });
-}
-
-/*
- Normalize the sort description to the more verbose format:
- { someField: "desc" } into { someField: { "order": "desc"}}
- */
-function normalize(sortable, indexPattern, defaultSortOptions) {
- const normalized = {};
- let sortField = _.keys(sortable)[0];
- let sortValue = sortable[sortField];
- const indexField = indexPattern.fields.getByName(sortField);
-
- if (indexField && indexField.scripted && indexField.sortable) {
- let direction;
- if (_.isString(sortValue)) direction = sortValue;
- if (_.isObject(sortValue) && sortValue.order) direction = sortValue.order;
-
- sortField = '_script';
- sortValue = {
- script: {
- source: indexField.script,
- lang: indexField.lang
- },
- type: castSortType(indexField.type),
- order: direction
- };
- } else {
- if (_.isString(sortValue)) {
- sortValue = { order: sortValue };
- }
- sortValue = _.defaults({}, sortValue, defaultSortOptions);
-
- if (sortField === '_score') {
- delete sortValue.unmapped_type;
- }
- }
-
- normalized[sortField] = sortValue;
- return normalized;
-}
-
-// The ES API only supports sort scripts of type 'number' and 'string'
-function castSortType(type) {
- const typeCastings = {
- number: 'number',
- string: 'string',
- date: 'number',
- boolean: 'string'
- };
-
- const castedType = typeCastings[type];
- if (!castedType) {
- throw new Error(`Unsupported script sort type: ${type}`);
- }
-
- return castedType;
-}
diff --git a/src/legacy/ui/public/courier/search_source/filter_docvalue_fields.test.js b/src/legacy/ui/public/courier/search_source/filter_docvalue_fields.test.ts
similarity index 88%
rename from src/legacy/ui/public/courier/search_source/filter_docvalue_fields.test.js
rename to src/legacy/ui/public/courier/search_source/filter_docvalue_fields.test.ts
index b220361e33b3b..522117fe22804 100644
--- a/src/legacy/ui/public/courier/search_source/filter_docvalue_fields.test.js
+++ b/src/legacy/ui/public/courier/search_source/filter_docvalue_fields.test.ts
@@ -23,11 +23,8 @@ test('Should exclude docvalue_fields that are not contained in fields', () => {
const docvalueFields = [
'my_ip_field',
{ field: 'my_keyword_field' },
- { field: 'my_date_field', 'format': 'epoch_millis' }
+ { field: 'my_date_field', format: 'epoch_millis' },
];
const out = filterDocvalueFields(docvalueFields, ['my_ip_field', 'my_keyword_field']);
- expect(out).toEqual([
- 'my_ip_field',
- { field: 'my_keyword_field' },
- ]);
+ expect(out).toEqual(['my_ip_field', { field: 'my_keyword_field' }]);
});
diff --git a/src/legacy/ui/public/courier/search_source/filter_docvalue_fields.js b/src/legacy/ui/public/courier/search_source/filter_docvalue_fields.ts
similarity index 84%
rename from src/legacy/ui/public/courier/search_source/filter_docvalue_fields.js
rename to src/legacy/ui/public/courier/search_source/filter_docvalue_fields.ts
index cd726709b4b5c..917d26f0decd1 100644
--- a/src/legacy/ui/public/courier/search_source/filter_docvalue_fields.js
+++ b/src/legacy/ui/public/courier/search_source/filter_docvalue_fields.ts
@@ -17,7 +17,15 @@
* under the License.
*/
-export function filterDocvalueFields(docvalueFields, fields) {
+interface DocvalueField {
+ field: string;
+ [key: string]: unknown;
+}
+
+export function filterDocvalueFields(
+ docvalueFields: Array,
+ fields: string[]
+) {
return docvalueFields.filter(docValue => {
const docvalueFieldName = typeof docValue === 'string' ? docValue : docValue.field;
return fields.includes(docvalueFieldName);
diff --git a/src/legacy/ui/public/courier/search_source/index.d.ts b/src/legacy/ui/public/courier/search_source/index.ts
similarity index 94%
rename from src/legacy/ui/public/courier/search_source/index.d.ts
rename to src/legacy/ui/public/courier/search_source/index.ts
index dcae7b3d2ff05..72170adc2b129 100644
--- a/src/legacy/ui/public/courier/search_source/index.d.ts
+++ b/src/legacy/ui/public/courier/search_source/index.ts
@@ -17,4 +17,4 @@
* under the License.
*/
-export { SearchSource } from './search_source';
+export * from './search_source';
diff --git a/src/legacy/ui/public/courier/search_source/mocks.ts b/src/legacy/ui/public/courier/search_source/mocks.ts
index bf546c1b9e7c2..2b83f379b4f09 100644
--- a/src/legacy/ui/public/courier/search_source/mocks.ts
+++ b/src/legacy/ui/public/courier/search_source/mocks.ts
@@ -36,21 +36,22 @@
* under the License.
*/
-export const searchSourceMock = {
+import { SearchSourceContract } from './search_source';
+
+export const searchSourceMock: MockedKeys = {
setPreferredSearchStrategyId: jest.fn(),
- getPreferredSearchStrategyId: jest.fn(),
- setFields: jest.fn(),
- setField: jest.fn(),
+ setFields: jest.fn().mockReturnThis(),
+ setField: jest.fn().mockReturnThis(),
getId: jest.fn(),
getFields: jest.fn(),
getField: jest.fn(),
getOwnField: jest.fn(),
- create: jest.fn(),
- createCopy: jest.fn(),
- createChild: jest.fn(),
+ create: jest.fn().mockReturnThis(),
+ createCopy: jest.fn().mockReturnThis(),
+ createChild: jest.fn().mockReturnThis(),
setParent: jest.fn(),
- getParent: jest.fn(),
- fetch: jest.fn(),
+ getParent: jest.fn().mockReturnThis(),
+ fetch: jest.fn().mockResolvedValue({}),
onRequestStart: jest.fn(),
getSearchRequestBody: jest.fn(),
destroy: jest.fn(),
diff --git a/src/legacy/ui/public/courier/search_source/normalize_sort_request.test.ts b/src/legacy/ui/public/courier/search_source/normalize_sort_request.test.ts
new file mode 100644
index 0000000000000..d27b01eb5cf7c
--- /dev/null
+++ b/src/legacy/ui/public/courier/search_source/normalize_sort_request.test.ts
@@ -0,0 +1,142 @@
+/*
+ * 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 { normalizeSortRequest } from './normalize_sort_request';
+import { SortDirection } from './types';
+import { IndexPattern } from '../../../../core_plugins/data/public/index_patterns';
+
+jest.mock('ui/new_platform');
+
+describe('SearchSource#normalizeSortRequest', function() {
+ const scriptedField = {
+ name: 'script string',
+ type: 'number',
+ scripted: true,
+ sortable: true,
+ script: 'foo',
+ lang: 'painless',
+ };
+ const murmurScriptedField = {
+ ...scriptedField,
+ sortable: false,
+ name: 'murmur script',
+ type: 'murmur3',
+ };
+ const indexPattern = {
+ fields: [scriptedField, murmurScriptedField],
+ } as IndexPattern;
+
+ it('should return an array', function() {
+ const sortable = { someField: SortDirection.desc };
+ const result = normalizeSortRequest(sortable, indexPattern);
+ expect(result).toEqual([
+ {
+ someField: {
+ order: SortDirection.desc,
+ },
+ },
+ ]);
+ // ensure object passed in is not mutated
+ expect(result[0]).not.toBe(sortable);
+ expect(sortable).toEqual({ someField: SortDirection.desc });
+ });
+
+ it('should make plain string sort into the more verbose format', function() {
+ const result = normalizeSortRequest([{ someField: SortDirection.desc }], indexPattern);
+ expect(result).toEqual([
+ {
+ someField: {
+ order: SortDirection.desc,
+ },
+ },
+ ]);
+ });
+
+ it('should append default sort options', function() {
+ const defaultSortOptions = {
+ unmapped_type: 'boolean',
+ };
+ const result = normalizeSortRequest(
+ [{ someField: SortDirection.desc }],
+ indexPattern,
+ defaultSortOptions
+ );
+ expect(result).toEqual([
+ {
+ someField: {
+ order: SortDirection.desc,
+ ...defaultSortOptions,
+ },
+ },
+ ]);
+ });
+
+ it('should enable script based sorting', function() {
+ const result = normalizeSortRequest(
+ {
+ [scriptedField.name]: SortDirection.desc,
+ },
+ indexPattern
+ );
+ expect(result).toEqual([
+ {
+ _script: {
+ script: {
+ source: scriptedField.script,
+ lang: scriptedField.lang,
+ },
+ type: scriptedField.type,
+ order: SortDirection.desc,
+ },
+ },
+ ]);
+ });
+
+ it('should use script based sorting only on sortable types', function() {
+ const result = normalizeSortRequest(
+ [
+ {
+ [murmurScriptedField.name]: SortDirection.asc,
+ },
+ ],
+ indexPattern
+ );
+
+ expect(result).toEqual([
+ {
+ [murmurScriptedField.name]: {
+ order: SortDirection.asc,
+ },
+ },
+ ]);
+ });
+
+ it('should remove unmapped_type parameter from _score sorting', function() {
+ const result = normalizeSortRequest({ _score: SortDirection.desc }, indexPattern, {
+ unmapped_type: 'boolean',
+ });
+ expect(result).toEqual([
+ {
+ _score: {
+ order: SortDirection.desc,
+ },
+ },
+ ]);
+ });
+});
diff --git a/src/legacy/ui/public/courier/search_source/normalize_sort_request.ts b/src/legacy/ui/public/courier/search_source/normalize_sort_request.ts
new file mode 100644
index 0000000000000..0f8fc8076caa0
--- /dev/null
+++ b/src/legacy/ui/public/courier/search_source/normalize_sort_request.ts
@@ -0,0 +1,78 @@
+/*
+ * 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 { IndexPattern } from '../../../../core_plugins/data/public';
+import { EsQuerySortValue, SortOptions } from './types';
+
+export function normalizeSortRequest(
+ sortObject: EsQuerySortValue | EsQuerySortValue[],
+ indexPattern: IndexPattern | string | undefined,
+ defaultSortOptions: SortOptions = {}
+) {
+ const sortArray: EsQuerySortValue[] = Array.isArray(sortObject) ? sortObject : [sortObject];
+ return sortArray.map(function(sortable) {
+ return normalize(sortable, indexPattern, defaultSortOptions);
+ });
+}
+
+/**
+ * Normalize the sort description to the more verbose format (e.g. { someField: "desc" } into
+ * { someField: { "order": "desc"}}), and convert sorts on scripted fields into the proper script
+ * for Elasticsearch. Mix in the default options according to the advanced settings.
+ */
+function normalize(
+ sortable: EsQuerySortValue,
+ indexPattern: IndexPattern | string | undefined,
+ defaultSortOptions: any
+) {
+ const [[sortField, sortOrder]] = Object.entries(sortable);
+ const order = typeof sortOrder === 'object' ? sortOrder : { order: sortOrder };
+
+ if (indexPattern && typeof indexPattern !== 'string') {
+ const indexField = indexPattern.fields.find(({ name }) => name === sortField);
+ if (indexField && indexField.scripted && indexField.sortable) {
+ return {
+ _script: {
+ script: {
+ source: indexField.script,
+ lang: indexField.lang,
+ },
+ type: castSortType(indexField.type),
+ ...order,
+ },
+ };
+ }
+ }
+
+ // Don't include unmapped_type for _score field
+ const { unmapped_type, ...otherSortOptions } = defaultSortOptions;
+ return {
+ [sortField]: { ...order, ...(sortField === '_score' ? otherSortOptions : defaultSortOptions) },
+ };
+}
+
+// The ES API only supports sort scripts of type 'number' and 'string'
+function castSortType(type: string) {
+ if (['number', 'string'].includes(type)) {
+ return 'number';
+ } else if (['string', 'boolean'].includes(type)) {
+ return 'string';
+ }
+ throw new Error(`Unsupported script sort type: ${type}`);
+}
diff --git a/src/legacy/ui/public/courier/search_source/search_source.d.ts b/src/legacy/ui/public/courier/search_source/search_source.d.ts
deleted file mode 100644
index 674e7ace0594c..0000000000000
--- a/src/legacy/ui/public/courier/search_source/search_source.d.ts
+++ /dev/null
@@ -1,39 +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 declare class SearchSource {
- setPreferredSearchStrategyId: (searchStrategyId: string) => void;
- getPreferredSearchStrategyId: () => string;
- setFields: (newFields: any) => SearchSource;
- setField: (field: string, value: any) => SearchSource;
- getId: () => string;
- getFields: () => any;
- getField: (field: string) => any;
- getOwnField: () => any;
- create: () => SearchSource;
- createCopy: () => SearchSource;
- createChild: (options?: any) => SearchSource;
- setParent: (parent: SearchSource | boolean) => SearchSource;
- getParent: () => SearchSource | undefined;
- fetch: (options?: any) => Promise;
- onRequestStart: (handler: (searchSource: SearchSource, options: any) => void) => void;
- getSearchRequestBody: () => any;
- destroy: () => void;
- history: any[];
-}
diff --git a/src/legacy/ui/public/courier/search_source/search_source.js b/src/legacy/ui/public/courier/search_source/search_source.js
deleted file mode 100644
index bc69e862fea48..0000000000000
--- a/src/legacy/ui/public/courier/search_source/search_source.js
+++ /dev/null
@@ -1,540 +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.
- */
-
-/**
- * @name SearchSource
- *
- * @description A promise-based stream of search results that can inherit from other search sources.
- *
- * Because filters/queries in Kibana have different levels of persistence and come from different
- * places, it is important to keep track of where filters come from for when they are saved back to
- * the savedObject store in the Kibana index. To do this, we create trees of searchSource objects
- * that can have associated query parameters (index, query, filter, etc) which can also inherit from
- * other searchSource objects.
- *
- * At query time, all of the searchSource objects that have subscribers are "flattened", at which
- * point the query params from the searchSource are collected while traversing up the inheritance
- * chain. At each link in the chain a decision about how to merge the query params is made until a
- * single set of query parameters is created for each active searchSource (a searchSource with
- * subscribers).
- *
- * That set of query parameters is then sent to elasticsearch. This is how the filter hierarchy
- * works in Kibana.
- *
- * Visualize, starting from a new search:
- *
- * - the `savedVis.searchSource` is set as the `appSearchSource`.
- * - The `savedVis.searchSource` would normally inherit from the `appSearchSource`, but now it is
- * upgraded to inherit from the `rootSearchSource`.
- * - Any interaction with the visualization will still apply filters to the `appSearchSource`, so
- * they will be stored directly on the `savedVis.searchSource`.
- * - Any interaction with the time filter will be written to the `rootSearchSource`, so those
- * filters will not be saved by the `savedVis`.
- * - When the `savedVis` is saved to elasticsearch, it takes with it all the filters that are
- * defined on it directly, but none of the ones that it inherits from other places.
- *
- * Visualize, starting from an existing search:
- *
- * - The `savedVis` loads the `savedSearch` on which it is built.
- * - The `savedVis.searchSource` is set to inherit from the `saveSearch.searchSource` and set as
- * the `appSearchSource`.
- * - The `savedSearch.searchSource`, is set to inherit from the `rootSearchSource`.
- * - Then the `savedVis` is written to elasticsearch it will be flattened and only include the
- * filters created in the visualize application and will reconnect the filters from the
- * `savedSearch` at runtime to prevent losing the relationship
- *
- * Dashboard search sources:
- *
- * - Each panel in a dashboard has a search source.
- * - The `savedDashboard` also has a searchsource, and it is set as the `appSearchSource`.
- * - Each panel's search source inherits from the `appSearchSource`, meaning that they inherit from
- * the dashboard search source.
- * - When a filter is added to the search box, or via a visualization, it is written to the
- * `appSearchSource`.
- */
-
-import _ from 'lodash';
-import angular from 'angular';
-
-import { normalizeSortRequest } from './_normalize_sort_request';
-
-import { fetchSoon } from '../fetch';
-import { fieldWildcardFilter } from '../../field_wildcard';
-import { getHighlightRequest, esQuery } from '../../../../../plugins/data/public';
-import { npSetup } from 'ui/new_platform';
-import chrome from '../../chrome';
-import { RequestFailure } from '../fetch/errors';
-import { filterDocvalueFields } from './filter_docvalue_fields';
-
-const FIELDS = [
- 'type',
- 'query',
- 'filter',
- 'sort',
- 'highlight',
- 'highlightAll',
- 'aggs',
- 'from',
- 'searchAfter',
- 'size',
- 'source',
- 'version',
- 'fields',
- 'index',
-];
-
-function parseInitialFields(initialFields) {
- if (!initialFields) {
- return {};
- }
-
- return typeof initialFields === 'string' ?
- JSON.parse(initialFields)
- : _.cloneDeep(initialFields);
-}
-
-function isIndexPattern(val) {
- return Boolean(val && typeof val.title === 'string');
-}
-
-const esShardTimeout = npSetup.core.injectedMetadata.getInjectedVar('esShardTimeout');
-const config = npSetup.core.uiSettings;
-const getConfig = (...args) => config.get(...args);
-const forIp = Symbol('for which index pattern?');
-
-export class SearchSource {
- constructor(initialFields) {
- this._id = _.uniqueId('data_source');
-
- this._searchStrategyId = undefined;
- this._fields = parseInitialFields(initialFields);
- this._parent = undefined;
-
- this.history = [];
- this._requestStartHandlers = [];
- this._inheritOptions = {};
- }
-
- /*****
- * PUBLIC API
- *****/
-
- setPreferredSearchStrategyId(searchStrategyId) {
- this._searchStrategyId = searchStrategyId;
- }
-
- getPreferredSearchStrategyId() {
- return this._searchStrategyId;
- }
-
- setFields(newFields) {
- this._fields = newFields;
- return this;
- }
-
- setField(field, value) {
- if (!FIELDS.includes(field)) {
- throw new Error(`Can't set field '${field}' on SearchSource. Acceptable fields are: ${FIELDS.join(', ')}.`);
- }
-
- if (field === 'index') {
- const fields = this._fields;
-
- const hasSource = fields.source;
- const sourceCameFromIp = hasSource && fields.source.hasOwnProperty(forIp);
- const sourceIsForOurIp = sourceCameFromIp && fields.source[forIp] === fields.index;
- if (sourceIsForOurIp) {
- delete fields.source;
- }
-
- if (value === null || value === undefined) {
- delete fields.index;
- return this;
- }
-
- if (!isIndexPattern(value)) {
- throw new TypeError('expected indexPattern to be an IndexPattern duck.');
- }
-
- fields[field] = value;
- if (!fields.source) {
- // imply source filtering based on the index pattern, but allow overriding
- // it by simply setting another field for "source". When index is changed
- fields.source = function () {
- return value.getSourceFiltering();
- };
- fields.source[forIp] = value;
- }
-
- return this;
- }
-
- if (value == null) {
- delete this._fields[field];
- return this;
- }
-
- this._fields[field] = value;
- return this;
- }
-
- getId() {
- return this._id;
- }
-
- getFields() {
- return _.clone(this._fields);
- }
-
- /**
- * Get fields from the fields
- */
- getField(field) {
- if (!FIELDS.includes(field)) {
- throw new Error(`Can't get field '${field}' from SearchSource. Acceptable fields are: ${FIELDS.join(', ')}.`);
- }
-
- let searchSource = this;
-
- while (searchSource) {
- const value = searchSource._fields[field];
- if (value !== void 0) {
- return value;
- }
-
- searchSource = searchSource.getParent();
- }
- }
-
- /**
- * Get the field from our own fields, don't traverse up the chain
- */
- getOwnField(field) {
- if (!FIELDS.includes(field)) {
- throw new Error(`Can't get field '${field}' from SearchSource. Acceptable fields are: ${FIELDS.join(', ')}.`);
- }
-
- const value = this._fields[field];
- if (value !== void 0) {
- return value;
- }
- }
-
- create() {
- return new SearchSource();
- }
-
- createCopy() {
- const json = angular.toJson(this._fields);
- const newSearchSource = new SearchSource(json);
- // when serializing the internal fields we lose the internal classes used in the index
- // pattern, so we have to set it again to workaround this behavior
- newSearchSource.setField('index', this.getField('index'));
- newSearchSource.setParent(this.getParent());
- return newSearchSource;
- }
-
- createChild(options = {}) {
- const childSearchSource = new SearchSource();
- childSearchSource.setParent(this, options);
- return childSearchSource;
- }
-
- /**
- * Set a searchSource that this source should inherit from
- * @param {SearchSource} searchSource - the parent searchSource
- * @return {this} - chainable
- */
- setParent(parent, options = {}) {
- this._parent = parent;
- this._inheritOptions = options;
- return this;
- }
-
- /**
- * Get the parent of this SearchSource
- * @return {undefined|searchSource}
- */
- getParent() {
- return this._parent || undefined;
- }
-
- /**
- * Fetch this source and reject the returned Promise on error
- *
- * @async
- */
- async fetch(options) {
- const $injector = await chrome.dangerouslyGetActiveInjector();
- const es = $injector.get('es');
-
- await this.requestIsStarting(options);
-
- const searchRequest = await this._flatten();
- this.history = [searchRequest];
-
- const response = await fetchSoon(searchRequest, {
- ...(this._searchStrategyId && { searchStrategyId: this._searchStrategyId }),
- ...options,
- }, { es, config, esShardTimeout });
-
- if (response.error) {
- throw new RequestFailure(null, response);
- }
-
- return response;
- }
-
- /**
- * Add a handler that will be notified whenever requests start
- * @param {Function} handler
- * @return {undefined}
- */
- onRequestStart(handler) {
- this._requestStartHandlers.push(handler);
- }
-
- /**
- * Called by requests of this search source when they are started
- * @param {Courier.Request} request
- * @param options
- * @return {Promise}
- */
- requestIsStarting(options) {
- const handlers = [...this._requestStartHandlers];
- // If callparentStartHandlers has been set to true, we also call all
- // handlers of parent search sources.
- if (this._inheritOptions.callParentStartHandlers) {
- let searchSource = this.getParent();
- while (searchSource) {
- handlers.push(...searchSource._requestStartHandlers);
- searchSource = searchSource.getParent();
- }
- }
-
- return Promise.all(handlers.map(fn => fn(this, options)));
- }
-
- async getSearchRequestBody() {
- const searchRequest = await this._flatten();
- return searchRequest.body;
- }
-
- /**
- * Completely destroy the SearchSource.
- * @return {undefined}
- */
- destroy() {
- this._requestStartHandlers.length = 0;
- }
-
- /******
- * PRIVATE APIS
- ******/
-
- /**
- * Used to merge properties into the data within ._flatten().
- * The data is passed in and modified by the function
- *
- * @param {object} data - the current merged data
- * @param {*} val - the value at `key`
- * @param {*} key - The key of `val`
- * @return {undefined}
- */
- _mergeProp(data, val, key) {
- if (typeof val === 'function') {
- const source = this;
- return Promise.resolve(val(this))
- .then(function (newVal) {
- return source._mergeProp(data, newVal, key);
- });
- }
-
- if (val == null || !key || !_.isString(key)) return;
-
- switch (key) {
- case 'filter':
- const filters = Array.isArray(val) ? val : [val];
- data.filters = [...(data.filters || []), ...filters];
- return;
- case 'index':
- case 'type':
- case 'id':
- case 'highlightAll':
- if (key && data[key] == null) {
- data[key] = val;
- }
- return;
- case 'searchAfter':
- key = 'search_after';
- addToBody();
- break;
- case 'source':
- key = '_source';
- addToBody();
- break;
- case 'sort':
- val = normalizeSortRequest(val, this.getField('index'), config.get('sort:options'));
- addToBody();
- break;
- case 'query':
- data.query = (data.query || []).concat(val);
- break;
- case 'fields':
- data[key] = _.uniq([...(data[key] || []), ...val]);
- break;
- default:
- addToBody();
- }
-
- /**
- * Add the key and val to the body of the request
- */
- function addToBody() {
- data.body = data.body || {};
- // ignore if we already have a value
- if (data.body[key] == null) {
- data.body[key] = val;
- }
- }
- }
-
- /**
- * Walk the inheritance chain of a source and return it's
- * flat representation (taking into account merging rules)
- * @returns {Promise}
- * @resolved {Object|null} - the flat data of the SearchSource
- */
- _flatten() {
- // the merged data of this dataSource and it's ancestors
- const flatData = {};
-
- // function used to write each property from each data object in the chain to flat data
- const root = this;
-
- // start the chain at this source
- let current = this;
-
- // call the ittr and return it's promise
- return (function ittr() {
- // iterate the _fields object (not array) and
- // pass each key:value pair to source._mergeProp. if _mergeProp
- // returns a promise, then wait for it to complete and call _mergeProp again
- return Promise.all(_.map(current._fields, function ittr(value, key) {
- if (value instanceof Promise) {
- return value.then(function (value) {
- return ittr(value, key);
- });
- }
-
- const prom = root._mergeProp(flatData, value, key);
- return prom instanceof Promise ? prom : null;
- }))
- .then(function () {
- // move to this sources parent
- const parent = current.getParent();
- // keep calling until we reach the top parent
- if (parent) {
- current = parent;
- return ittr();
- }
- });
- }())
- .then(function () {
- // This is down here to prevent the circular dependency
- flatData.body = flatData.body || {};
-
- const computedFields = flatData.index.getComputedFields();
-
- flatData.body.stored_fields = computedFields.storedFields;
- flatData.body.script_fields = flatData.body.script_fields || {};
- _.extend(flatData.body.script_fields, computedFields.scriptFields);
-
- const defaultDocValueFields = computedFields.docvalueFields ? computedFields.docvalueFields : [];
- flatData.body.docvalue_fields = flatData.body.docvalue_fields || defaultDocValueFields;
-
- if (flatData.body._source) {
- // exclude source fields for this index pattern specified by the user
- const filter = fieldWildcardFilter(flatData.body._source.excludes, config.get('metaFields'));
- flatData.body.docvalue_fields = flatData.body.docvalue_fields.filter(
- docvalueField => filter(docvalueField.field)
- );
- }
-
- // if we only want to search for certain fields
- const fields = flatData.fields;
- if (fields) {
- // filter out the docvalue_fields, and script_fields to only include those that we are concerned with
- flatData.body.docvalue_fields = filterDocvalueFields(flatData.body.docvalue_fields, fields);
- flatData.body.script_fields = _.pick(flatData.body.script_fields, fields);
-
- // request the remaining fields from both stored_fields and _source
- const remainingFields = _.difference(fields, _.keys(flatData.body.script_fields));
- flatData.body.stored_fields = remainingFields;
- _.set(flatData.body, '_source.includes', remainingFields);
- }
-
- const esQueryConfigs = esQuery.getEsQueryConfig(config);
- flatData.body.query = esQuery.buildEsQuery(flatData.index, flatData.query, flatData.filters, esQueryConfigs);
-
- if (flatData.highlightAll != null) {
- if (flatData.highlightAll && flatData.body.query) {
- flatData.body.highlight = getHighlightRequest(flatData.body.query, getConfig);
- }
- delete flatData.highlightAll;
- }
-
- /**
- * Translate a filter into a query to support es 3+
- * @param {Object} filter - The filter to translate
- * @return {Object} the query version of that filter
- */
- const translateToQuery = function (filter) {
- if (!filter) return;
-
- if (filter.query) {
- return filter.query;
- }
-
- return filter;
- };
-
- // re-write filters within filter aggregations
- (function recurse(aggBranch) {
- if (!aggBranch) return;
- Object.keys(aggBranch).forEach(function (id) {
- const agg = aggBranch[id];
-
- if (agg.filters) {
- // translate filters aggregations
- const filters = agg.filters.filters;
-
- Object.keys(filters).forEach(function (filterId) {
- filters[filterId] = translateToQuery(filters[filterId]);
- });
- }
-
- recurse(agg.aggs || agg.aggregations);
- });
- }(flatData.body.aggs || flatData.body.aggregations));
-
- return flatData;
- });
- }
-}
diff --git a/src/legacy/ui/public/courier/search_source/search_source.test.js b/src/legacy/ui/public/courier/search_source/search_source.test.js
deleted file mode 100644
index 800f4e4308671..0000000000000
--- a/src/legacy/ui/public/courier/search_source/search_source.test.js
+++ /dev/null
@@ -1,193 +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 { SearchSource } from '../search_source';
-
-jest.mock('ui/new_platform', () => ({
- npSetup: {
- core: {
- injectedMetadata: {
- getInjectedVar: () => 0,
- }
- }
- }
-}));
-
-jest.mock('../fetch', () => ({
- fetchSoon: jest.fn(),
-}));
-
-const indexPattern = { title: 'foo' };
-const indexPattern2 = { title: 'foo' };
-
-describe('SearchSource', function () {
- describe('#setField()', function () {
- it('sets the value for the property', function () {
- const searchSource = new SearchSource();
- searchSource.setField('aggs', 5);
- expect(searchSource.getField('aggs')).toBe(5);
- });
-
- it('throws an error if the property is not accepted', function () {
- const searchSource = new SearchSource();
- expect(() => searchSource.setField('index', 5)).toThrow();
- });
- });
-
- describe('#getField()', function () {
- it('gets the value for the property', function () {
- const searchSource = new SearchSource();
- searchSource.setField('aggs', 5);
- expect(searchSource.getField('aggs')).toBe(5);
- });
-
- it('throws an error if the property is not accepted', function () {
- const searchSource = new SearchSource();
- expect(() => searchSource.getField('unacceptablePropName')).toThrow();
- });
- });
-
- describe(`#setField('index')`, function () {
- describe('auto-sourceFiltering', function () {
- describe('new index pattern assigned', function () {
- it('generates a searchSource filter', function () {
- const searchSource = new SearchSource();
- expect(searchSource.getField('index')).toBe(undefined);
- expect(searchSource.getField('source')).toBe(undefined);
- searchSource.setField('index', indexPattern);
- expect(searchSource.getField('index')).toBe(indexPattern);
- expect(typeof searchSource.getField('source')).toBe('function');
- });
-
- it('removes created searchSource filter on removal', function () {
- const searchSource = new SearchSource();
- searchSource.setField('index', indexPattern);
- searchSource.setField('index', null);
- expect(searchSource.getField('index')).toBe(undefined);
- expect(searchSource.getField('source')).toBe(undefined);
- });
- });
-
- describe('new index pattern assigned over another', function () {
- it('replaces searchSource filter with new', function () {
- const searchSource = new SearchSource();
- searchSource.setField('index', indexPattern);
- const searchSourceFilter1 = searchSource.getField('source');
- searchSource.setField('index', indexPattern2);
- expect(searchSource.getField('index')).toBe(indexPattern2);
- expect(typeof searchSource.getField('source')).toBe('function');
- expect(searchSource.getField('source')).not.toBe(searchSourceFilter1);
- });
-
- it('removes created searchSource filter on removal', function () {
- const searchSource = new SearchSource();
- searchSource.setField('index', indexPattern);
- searchSource.setField('index', indexPattern2);
- searchSource.setField('index', null);
- expect(searchSource.getField('index')).toBe(undefined);
- expect(searchSource.getField('source')).toBe(undefined);
- });
- });
-
- describe('ip assigned before custom searchSource filter', function () {
- it('custom searchSource filter becomes new searchSource', function () {
- const searchSource = new SearchSource();
- const football = {};
- searchSource.setField('index', indexPattern);
- expect(typeof searchSource.getField('source')).toBe('function');
- searchSource.setField('source', football);
- expect(searchSource.getField('index')).toBe(indexPattern);
- expect(searchSource.getField('source')).toBe(football);
- });
-
- it('custom searchSource stays after removal', function () {
- const searchSource = new SearchSource();
- const football = {};
- searchSource.setField('index', indexPattern);
- searchSource.setField('source', football);
- searchSource.setField('index', null);
- expect(searchSource.getField('index')).toBe(undefined);
- expect(searchSource.getField('source')).toBe(football);
- });
- });
-
- describe('ip assigned after custom searchSource filter', function () {
- it('leaves the custom filter in place', function () {
- const searchSource = new SearchSource();
- const football = {};
- searchSource.setField('source', football);
- searchSource.setField('index', indexPattern);
- expect(searchSource.getField('index')).toBe(indexPattern);
- expect(searchSource.getField('source')).toBe(football);
- });
-
- it('custom searchSource stays after removal', function () {
- const searchSource = new SearchSource();
- const football = {};
- searchSource.setField('source', football);
- searchSource.setField('index', indexPattern);
- searchSource.setField('index', null);
- expect(searchSource.getField('index')).toBe(undefined);
- expect(searchSource.getField('source')).toBe(football);
- });
- });
- });
- });
-
- describe('#onRequestStart()', () => {
- it('should be called when starting a request', () => {
- const searchSource = new SearchSource();
- const fn = jest.fn();
- searchSource.onRequestStart(fn);
- const options = {};
- searchSource.requestIsStarting(options);
- expect(fn).toBeCalledWith(searchSource, options);
- });
-
- it('should not be called on parent searchSource', () => {
- const parent = new SearchSource();
- const searchSource = new SearchSource().setParent(parent);
-
- const fn = jest.fn();
- searchSource.onRequestStart(fn);
- const parentFn = jest.fn();
- parent.onRequestStart(parentFn);
- const options = {};
- searchSource.requestIsStarting(options);
-
- expect(fn).toBeCalledWith(searchSource, options);
- expect(parentFn).not.toBeCalled();
- });
-
- it('should be called on parent searchSource if callParentStartHandlers is true', () => {
- const parent = new SearchSource();
- const searchSource = new SearchSource().setParent(parent, { callParentStartHandlers: true });
-
- const fn = jest.fn();
- searchSource.onRequestStart(fn);
- const parentFn = jest.fn();
- parent.onRequestStart(parentFn);
- const options = {};
- searchSource.requestIsStarting(options);
-
- expect(fn).toBeCalledWith(searchSource, options);
- expect(parentFn).toBeCalledWith(searchSource, options);
- });
- });
-});
diff --git a/src/legacy/ui/public/courier/search_source/search_source.test.ts b/src/legacy/ui/public/courier/search_source/search_source.test.ts
new file mode 100644
index 0000000000000..ddd3717f55e29
--- /dev/null
+++ b/src/legacy/ui/public/courier/search_source/search_source.test.ts
@@ -0,0 +1,156 @@
+/*
+ * 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 { SearchSource } from '../search_source';
+import { IndexPattern } from '../../../../core_plugins/data/public';
+
+jest.mock('ui/new_platform');
+
+jest.mock('../fetch', () => ({
+ fetchSoon: jest.fn().mockResolvedValue({}),
+}));
+
+jest.mock('../../chrome', () => ({
+ dangerouslyGetActiveInjector: () => ({
+ get: jest.fn(),
+ }),
+}));
+
+const getComputedFields = () => ({
+ storedFields: [],
+ scriptFields: [],
+ docvalueFields: [],
+});
+const mockSource = { excludes: ['foo-*'] };
+const mockSource2 = { excludes: ['bar-*'] };
+const indexPattern = ({
+ title: 'foo',
+ getComputedFields,
+ getSourceFiltering: () => mockSource,
+} as unknown) as IndexPattern;
+const indexPattern2 = ({
+ title: 'foo',
+ getComputedFields,
+ getSourceFiltering: () => mockSource2,
+} as unknown) as IndexPattern;
+
+describe('SearchSource', function() {
+ describe('#setField()', function() {
+ it('sets the value for the property', function() {
+ const searchSource = new SearchSource();
+ searchSource.setField('aggs', 5);
+ expect(searchSource.getField('aggs')).toBe(5);
+ });
+ });
+
+ describe('#getField()', function() {
+ it('gets the value for the property', function() {
+ const searchSource = new SearchSource();
+ searchSource.setField('aggs', 5);
+ expect(searchSource.getField('aggs')).toBe(5);
+ });
+ });
+
+ describe(`#setField('index')`, function() {
+ describe('auto-sourceFiltering', function() {
+ describe('new index pattern assigned', function() {
+ it('generates a searchSource filter', async function() {
+ const searchSource = new SearchSource();
+ expect(searchSource.getField('index')).toBe(undefined);
+ expect(searchSource.getField('source')).toBe(undefined);
+ searchSource.setField('index', indexPattern);
+ expect(searchSource.getField('index')).toBe(indexPattern);
+ const request = await searchSource.getSearchRequestBody();
+ expect(request._source).toBe(mockSource);
+ });
+
+ it('removes created searchSource filter on removal', async function() {
+ const searchSource = new SearchSource();
+ searchSource.setField('index', indexPattern);
+ searchSource.setField('index', undefined);
+ const request = await searchSource.getSearchRequestBody();
+ expect(request._source).toBe(undefined);
+ });
+ });
+
+ describe('new index pattern assigned over another', function() {
+ it('replaces searchSource filter with new', async function() {
+ const searchSource = new SearchSource();
+ searchSource.setField('index', indexPattern);
+ searchSource.setField('index', indexPattern2);
+ expect(searchSource.getField('index')).toBe(indexPattern2);
+ const request = await searchSource.getSearchRequestBody();
+ expect(request._source).toBe(mockSource2);
+ });
+
+ it('removes created searchSource filter on removal', async function() {
+ const searchSource = new SearchSource();
+ searchSource.setField('index', indexPattern);
+ searchSource.setField('index', indexPattern2);
+ searchSource.setField('index', undefined);
+ const request = await searchSource.getSearchRequestBody();
+ expect(request._source).toBe(undefined);
+ });
+ });
+ });
+ });
+
+ describe('#onRequestStart()', () => {
+ it('should be called when starting a request', async () => {
+ const searchSource = new SearchSource({ index: indexPattern });
+ const fn = jest.fn();
+ searchSource.onRequestStart(fn);
+ const options = {};
+ await searchSource.fetch(options);
+ expect(fn).toBeCalledWith(searchSource, options);
+ });
+
+ it('should not be called on parent searchSource', async () => {
+ const parent = new SearchSource();
+ const searchSource = new SearchSource({ index: indexPattern });
+
+ const fn = jest.fn();
+ searchSource.onRequestStart(fn);
+ const parentFn = jest.fn();
+ parent.onRequestStart(parentFn);
+ const options = {};
+ await searchSource.fetch(options);
+
+ expect(fn).toBeCalledWith(searchSource, options);
+ expect(parentFn).not.toBeCalled();
+ });
+
+ it('should be called on parent searchSource if callParentStartHandlers is true', async () => {
+ const parent = new SearchSource();
+ const searchSource = new SearchSource({ index: indexPattern }).setParent(parent, {
+ callParentStartHandlers: true,
+ });
+
+ const fn = jest.fn();
+ searchSource.onRequestStart(fn);
+ const parentFn = jest.fn();
+ parent.onRequestStart(parentFn);
+ const options = {};
+ await searchSource.fetch(options);
+
+ expect(fn).toBeCalledWith(searchSource, options);
+ expect(parentFn).toBeCalledWith(searchSource, options);
+ });
+ });
+});
diff --git a/src/legacy/ui/public/courier/search_source/search_source.ts b/src/legacy/ui/public/courier/search_source/search_source.ts
new file mode 100644
index 0000000000000..e862bb1118a74
--- /dev/null
+++ b/src/legacy/ui/public/courier/search_source/search_source.ts
@@ -0,0 +1,410 @@
+/*
+ * 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.
+ */
+
+/**
+ * @name SearchSource
+ *
+ * @description A promise-based stream of search results that can inherit from other search sources.
+ *
+ * Because filters/queries in Kibana have different levels of persistence and come from different
+ * places, it is important to keep track of where filters come from for when they are saved back to
+ * the savedObject store in the Kibana index. To do this, we create trees of searchSource objects
+ * that can have associated query parameters (index, query, filter, etc) which can also inherit from
+ * other searchSource objects.
+ *
+ * At query time, all of the searchSource objects that have subscribers are "flattened", at which
+ * point the query params from the searchSource are collected while traversing up the inheritance
+ * chain. At each link in the chain a decision about how to merge the query params is made until a
+ * single set of query parameters is created for each active searchSource (a searchSource with
+ * subscribers).
+ *
+ * That set of query parameters is then sent to elasticsearch. This is how the filter hierarchy
+ * works in Kibana.
+ *
+ * Visualize, starting from a new search:
+ *
+ * - the `savedVis.searchSource` is set as the `appSearchSource`.
+ * - The `savedVis.searchSource` would normally inherit from the `appSearchSource`, but now it is
+ * upgraded to inherit from the `rootSearchSource`.
+ * - Any interaction with the visualization will still apply filters to the `appSearchSource`, so
+ * they will be stored directly on the `savedVis.searchSource`.
+ * - Any interaction with the time filter will be written to the `rootSearchSource`, so those
+ * filters will not be saved by the `savedVis`.
+ * - When the `savedVis` is saved to elasticsearch, it takes with it all the filters that are
+ * defined on it directly, but none of the ones that it inherits from other places.
+ *
+ * Visualize, starting from an existing search:
+ *
+ * - The `savedVis` loads the `savedSearch` on which it is built.
+ * - The `savedVis.searchSource` is set to inherit from the `saveSearch.searchSource` and set as
+ * the `appSearchSource`.
+ * - The `savedSearch.searchSource`, is set to inherit from the `rootSearchSource`.
+ * - Then the `savedVis` is written to elasticsearch it will be flattened and only include the
+ * filters created in the visualize application and will reconnect the filters from the
+ * `savedSearch` at runtime to prevent losing the relationship
+ *
+ * Dashboard search sources:
+ *
+ * - Each panel in a dashboard has a search source.
+ * - The `savedDashboard` also has a searchsource, and it is set as the `appSearchSource`.
+ * - Each panel's search source inherits from the `appSearchSource`, meaning that they inherit from
+ * the dashboard search source.
+ * - When a filter is added to the search box, or via a visualization, it is written to the
+ * `appSearchSource`.
+ */
+
+import _ from 'lodash';
+import { npSetup } from 'ui/new_platform';
+import { normalizeSortRequest } from './normalize_sort_request';
+import { fetchSoon } from '../fetch';
+import { fieldWildcardFilter } from '../../field_wildcard';
+import { getHighlightRequest, esFilters, esQuery } from '../../../../../plugins/data/public';
+import chrome from '../../chrome';
+import { RequestFailure } from '../fetch/errors';
+import { filterDocvalueFields } from './filter_docvalue_fields';
+import { SearchSourceOptions, SearchSourceFields, SearchRequest } from './types';
+import { FetchOptions, ApiCaller } from '../fetch/types';
+
+const esShardTimeout = npSetup.core.injectedMetadata.getInjectedVar('esShardTimeout') as number;
+const config = npSetup.core.uiSettings;
+
+export type SearchSourceContract = Pick;
+
+export class SearchSource {
+ private id: string = _.uniqueId('data_source');
+ private searchStrategyId?: string;
+ private parent?: SearchSource;
+ private requestStartHandlers: Array<
+ (searchSource: SearchSourceContract, options?: FetchOptions) => Promise
+ > = [];
+ private inheritOptions: SearchSourceOptions = {};
+ public history: SearchRequest[] = [];
+
+ constructor(private fields: SearchSourceFields = {}) {}
+
+ /** ***
+ * PUBLIC API
+ *****/
+
+ setPreferredSearchStrategyId(searchStrategyId: string) {
+ this.searchStrategyId = searchStrategyId;
+ }
+
+ setFields(newFields: SearchSourceFields) {
+ this.fields = newFields;
+ return this;
+ }
+
+ setField(field: K, value: SearchSourceFields[K]) {
+ if (value == null) {
+ delete this.fields[field];
+ } else {
+ this.fields[field] = value;
+ }
+ return this;
+ }
+
+ getId() {
+ return this.id;
+ }
+
+ getFields() {
+ return { ...this.fields };
+ }
+
+ /**
+ * Get fields from the fields
+ */
+ getField(field: K, recurse = true): SearchSourceFields[K] {
+ if (!recurse || this.fields[field] !== void 0) {
+ return this.fields[field];
+ }
+ const parent = this.getParent();
+ return parent && parent.getField(field);
+ }
+
+ /**
+ * Get the field from our own fields, don't traverse up the chain
+ */
+ getOwnField(field: K): SearchSourceFields[K] {
+ return this.getField(field, false);
+ }
+
+ create() {
+ return new SearchSource();
+ }
+
+ createCopy() {
+ const newSearchSource = new SearchSource();
+ newSearchSource.setFields({ ...this.fields });
+ // when serializing the internal fields we lose the internal classes used in the index
+ // pattern, so we have to set it again to workaround this behavior
+ newSearchSource.setField('index', this.getField('index'));
+ newSearchSource.setParent(this.getParent());
+ return newSearchSource;
+ }
+
+ createChild(options = {}) {
+ const childSearchSource = new SearchSource();
+ childSearchSource.setParent(this, options);
+ return childSearchSource;
+ }
+
+ /**
+ * Set a searchSource that this source should inherit from
+ * @param {SearchSource} parent - the parent searchSource
+ * @param {SearchSourceOptions} options - the inherit options
+ * @return {this} - chainable
+ */
+ setParent(parent?: SearchSourceContract, options: SearchSourceOptions = {}) {
+ this.parent = parent as SearchSource;
+ this.inheritOptions = options;
+ return this;
+ }
+
+ /**
+ * Get the parent of this SearchSource
+ * @return {undefined|searchSource}
+ */
+ getParent() {
+ return this.parent;
+ }
+
+ /**
+ * Fetch this source and reject the returned Promise on error
+ *
+ * @async
+ */
+ async fetch(options: FetchOptions = {}) {
+ const $injector = await chrome.dangerouslyGetActiveInjector();
+ const es = $injector.get('es') as ApiCaller;
+
+ await this.requestIsStarting(options);
+
+ const searchRequest = await this.flatten();
+ this.history = [searchRequest];
+
+ const response = await fetchSoon(
+ searchRequest,
+ {
+ ...(this.searchStrategyId && { searchStrategyId: this.searchStrategyId }),
+ ...options,
+ },
+ { es, config, esShardTimeout }
+ );
+
+ if (response.error) {
+ throw new RequestFailure(null, response);
+ }
+
+ return response;
+ }
+
+ /**
+ * Add a handler that will be notified whenever requests start
+ * @param {Function} handler
+ * @return {undefined}
+ */
+ onRequestStart(
+ handler: (searchSource: SearchSourceContract, options?: FetchOptions) => Promise
+ ) {
+ this.requestStartHandlers.push(handler);
+ }
+
+ async getSearchRequestBody() {
+ const searchRequest = await this.flatten();
+ return searchRequest.body;
+ }
+
+ /**
+ * Completely destroy the SearchSource.
+ * @return {undefined}
+ */
+ destroy() {
+ this.requestStartHandlers.length = 0;
+ }
+
+ /** ****
+ * PRIVATE APIS
+ ******/
+
+ /**
+ * Called by requests of this search source when they are started
+ * @param {Courier.Request} request
+ * @param options
+ * @return {Promise}
+ */
+ private requestIsStarting(options: FetchOptions = {}) {
+ const handlers = [...this.requestStartHandlers];
+ // If callParentStartHandlers has been set to true, we also call all
+ // handlers of parent search sources.
+ if (this.inheritOptions.callParentStartHandlers) {
+ let searchSource = this.getParent();
+ while (searchSource) {
+ handlers.push(...searchSource.requestStartHandlers);
+ searchSource = searchSource.getParent();
+ }
+ }
+
+ return Promise.all(handlers.map(fn => fn(this, options)));
+ }
+
+ /**
+ * Used to merge properties into the data within ._flatten().
+ * The data is passed in and modified by the function
+ *
+ * @param {object} data - the current merged data
+ * @param {*} val - the value at `key`
+ * @param {*} key - The key of `val`
+ * @return {undefined}
+ */
+ private mergeProp(
+ data: SearchRequest,
+ val: SearchSourceFields[K],
+ key: K
+ ) {
+ val = typeof val === 'function' ? val(this) : val;
+ if (val == null || !key) return;
+
+ const addToRoot = (rootKey: string, value: any) => {
+ data[rootKey] = value;
+ };
+
+ /**
+ * Add the key and val to the body of the request
+ */
+ const addToBody = (bodyKey: string, value: any) => {
+ // ignore if we already have a value
+ if (data.body[bodyKey] == null) {
+ data.body[bodyKey] = value;
+ }
+ };
+
+ switch (key) {
+ case 'filter':
+ return addToRoot('filters', (data.filters || []).concat(val));
+ case 'query':
+ return addToRoot(key, (data[key] || []).concat(val));
+ case 'fields':
+ const fields = _.uniq((data[key] || []).concat(val));
+ return addToRoot(key, fields);
+ case 'index':
+ case 'type':
+ case 'highlightAll':
+ return key && data[key] == null && addToRoot(key, val);
+ case 'searchAfter':
+ return addToBody('search_after', val);
+ case 'source':
+ return addToBody('_source', val);
+ case 'sort':
+ const sort = normalizeSortRequest(val, this.getField('index'), config.get('sort:options'));
+ return addToBody(key, sort);
+ default:
+ return addToBody(key, val);
+ }
+ }
+
+ /**
+ * Walk the inheritance chain of a source and return its
+ * flat representation (taking into account merging rules)
+ * @returns {Promise}
+ * @resolved {Object|null} - the flat data of the SearchSource
+ */
+ private mergeProps(root = this, searchRequest: SearchRequest = { body: {} }) {
+ Object.entries(this.fields).forEach(([key, value]) => {
+ this.mergeProp(searchRequest, value, key as keyof SearchSourceFields);
+ });
+ if (this.parent) {
+ this.parent.mergeProps(root, searchRequest);
+ }
+ return searchRequest;
+ }
+
+ private flatten() {
+ const searchRequest = this.mergeProps();
+
+ searchRequest.body = searchRequest.body || {};
+ const { body, index, fields, query, filters, highlightAll } = searchRequest;
+
+ const computedFields = index ? index.getComputedFields() : {};
+
+ body.stored_fields = computedFields.storedFields;
+ body.script_fields = body.script_fields || {};
+ _.extend(body.script_fields, computedFields.scriptFields);
+
+ const defaultDocValueFields = computedFields.docvalueFields
+ ? computedFields.docvalueFields
+ : [];
+ body.docvalue_fields = body.docvalue_fields || defaultDocValueFields;
+
+ if (!body.hasOwnProperty('_source') && index) {
+ body._source = index.getSourceFiltering();
+ }
+
+ if (body._source) {
+ // exclude source fields for this index pattern specified by the user
+ const filter = fieldWildcardFilter(body._source.excludes, config.get('metaFields'));
+ body.docvalue_fields = body.docvalue_fields.filter((docvalueField: any) =>
+ filter(docvalueField.field)
+ );
+ }
+
+ // if we only want to search for certain fields
+ if (fields) {
+ // filter out the docvalue_fields, and script_fields to only include those that we are concerned with
+ body.docvalue_fields = filterDocvalueFields(body.docvalue_fields, fields);
+ body.script_fields = _.pick(body.script_fields, fields);
+
+ // request the remaining fields from both stored_fields and _source
+ const remainingFields = _.difference(fields, _.keys(body.script_fields));
+ body.stored_fields = remainingFields;
+ _.set(body, '_source.includes', remainingFields);
+ }
+
+ const esQueryConfigs = esQuery.getEsQueryConfig(config);
+ body.query = esQuery.buildEsQuery(index, query, filters, esQueryConfigs);
+
+ if (highlightAll && body.query) {
+ body.highlight = getHighlightRequest(body.query, config.get('doc_table:highlight'));
+ delete searchRequest.highlightAll;
+ }
+
+ const translateToQuery = (filter: esFilters.Filter) => filter && (filter.query || filter);
+
+ // re-write filters within filter aggregations
+ (function recurse(aggBranch) {
+ if (!aggBranch) return;
+ Object.keys(aggBranch).forEach(function(id) {
+ const agg = aggBranch[id];
+
+ if (agg.filters) {
+ // translate filters aggregations
+ const { filters: aggFilters } = agg.filters;
+ Object.keys(aggFilters).forEach(filterId => {
+ aggFilters[filterId] = translateToQuery(aggFilters[filterId]);
+ });
+ }
+
+ recurse(agg.aggs || agg.aggregations);
+ });
+ })(body.aggs || body.aggregations);
+
+ return searchRequest;
+ }
+}
diff --git a/src/legacy/ui/public/courier/search_source/types.ts b/src/legacy/ui/public/courier/search_source/types.ts
new file mode 100644
index 0000000000000..293f3d49596c3
--- /dev/null
+++ b/src/legacy/ui/public/courier/search_source/types.ts
@@ -0,0 +1,106 @@
+/*
+ * 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 { NameList } from 'elasticsearch';
+import { esFilters, Query } from '../../../../../plugins/data/public';
+import { IndexPattern } from '../../../../core_plugins/data/public/index_patterns';
+
+export type EsQuerySearchAfter = [string | number, string | number];
+
+export enum SortDirection {
+ asc = 'asc',
+ desc = 'desc',
+}
+
+export type EsQuerySortValue = Record;
+
+export interface SearchSourceFields {
+ type?: string;
+ query?: Query;
+ filter?:
+ | esFilters.Filter[]
+ | esFilters.Filter
+ | (() => esFilters.Filter[] | esFilters.Filter | undefined);
+ sort?: EsQuerySortValue | EsQuerySortValue[];
+ highlight?: any;
+ highlightAll?: boolean;
+ aggs?: any;
+ from?: number;
+ size?: number;
+ source?: NameList;
+ version?: boolean;
+ fields?: NameList;
+ index?: IndexPattern;
+ searchAfter?: EsQuerySearchAfter;
+}
+
+export interface SearchSourceOptions {
+ callParentStartHandlers?: boolean;
+}
+
+export { SearchSourceContract } from './search_source';
+
+export interface SortOptions {
+ mode?: 'min' | 'max' | 'sum' | 'avg' | 'median';
+ type?: 'double' | 'long' | 'date' | 'date_nanos';
+ nested?: object;
+ unmapped_type?: string;
+ distance_type?: 'arc' | 'plane';
+ unit?: string;
+ ignore_unmapped?: boolean;
+ _script?: object;
+}
+
+export interface Request {
+ docvalue_fields: string[];
+ _source: unknown;
+ query: unknown;
+ script_fields: unknown;
+ sort: unknown;
+ stored_fields: string[];
+}
+
+export interface ResponseWithShardFailure {
+ _shards: {
+ failed: number;
+ failures: ShardFailure[];
+ skipped: number;
+ successful: number;
+ total: number;
+ };
+}
+
+export interface ShardFailure {
+ index: string;
+ node: string;
+ reason: {
+ caused_by: {
+ reason: string;
+ type: string;
+ };
+ reason: string;
+ lang?: string;
+ script?: string;
+ script_stack?: string[];
+ type: string;
+ };
+ shard: number;
+}
+
+export type SearchRequest = any;
+export type SearchResponse = any;
diff --git a/src/legacy/ui/public/courier/search_strategy/default_search_strategy.test.js b/src/legacy/ui/public/courier/search_strategy/default_search_strategy.test.ts
similarity index 67%
rename from src/legacy/ui/public/courier/search_strategy/default_search_strategy.test.js
rename to src/legacy/ui/public/courier/search_strategy/default_search_strategy.test.ts
index a1ea53e8b5b47..29921fc7a11d3 100644
--- a/src/legacy/ui/public/courier/search_strategy/default_search_strategy.test.js
+++ b/src/legacy/ui/public/courier/search_strategy/default_search_strategy.test.ts
@@ -18,26 +18,28 @@
*/
import { defaultSearchStrategy } from './default_search_strategy';
+import { UiSettingsClientContract } from '../../../../../core/public';
+import { SearchStrategySearchParams } from './types';
const { search } = defaultSearchStrategy;
-function getConfigStub(config = {}) {
+function getConfigStub(config: any = {}) {
return {
- get: key => config[key]
- };
+ get: key => config[key],
+ } as UiSettingsClientContract;
}
-const msearchMockResponse = Promise.resolve([]);
+const msearchMockResponse: any = Promise.resolve([]);
msearchMockResponse.abort = jest.fn();
const msearchMock = jest.fn().mockReturnValue(msearchMockResponse);
-const searchMockResponse = Promise.resolve([]);
+const searchMockResponse: any = Promise.resolve([]);
searchMockResponse.abort = jest.fn();
const searchMock = jest.fn().mockReturnValue(searchMockResponse);
-describe('defaultSearchStrategy', function () {
- describe('search', function () {
- let searchArgs;
+describe('defaultSearchStrategy', function() {
+ describe('search', function() {
+ let searchArgs: MockedKeys>;
beforeEach(() => {
msearchMockResponse.abort.mockClear();
@@ -47,9 +49,12 @@ describe('defaultSearchStrategy', function () {
searchMock.mockClear();
searchArgs = {
- searchRequests: [{
- index: { title: 'foo' }
- }],
+ searchRequests: [
+ {
+ index: { title: 'foo' },
+ },
+ ],
+ esShardTimeout: 0,
es: {
msearch: msearchMock,
search: searchMock,
@@ -58,48 +63,48 @@ describe('defaultSearchStrategy', function () {
});
test('does not send max_concurrent_shard_requests by default', async () => {
- searchArgs.config = getConfigStub({ 'courier:batchSearches': true });
- await search(searchArgs);
+ const config = getConfigStub({ 'courier:batchSearches': true });
+ await search({ ...searchArgs, config });
expect(searchArgs.es.msearch.mock.calls[0][0].max_concurrent_shard_requests).toBe(undefined);
});
test('allows configuration of max_concurrent_shard_requests', async () => {
- searchArgs.config = getConfigStub({
+ const config = getConfigStub({
'courier:batchSearches': true,
'courier:maxConcurrentShardRequests': 42,
});
- await search(searchArgs);
+ await search({ ...searchArgs, config });
expect(searchArgs.es.msearch.mock.calls[0][0].max_concurrent_shard_requests).toBe(42);
});
test('should set rest_total_hits_as_int to true on a request', async () => {
- searchArgs.config = getConfigStub({ 'courier:batchSearches': true });
- await search(searchArgs);
+ const config = getConfigStub({ 'courier:batchSearches': true });
+ await search({ ...searchArgs, config });
expect(searchArgs.es.msearch.mock.calls[0][0]).toHaveProperty('rest_total_hits_as_int', true);
});
test('should set ignore_throttled=false when including frozen indices', async () => {
- searchArgs.config = getConfigStub({
+ const config = getConfigStub({
'courier:batchSearches': true,
'search:includeFrozen': true,
});
- await search(searchArgs);
+ await search({ ...searchArgs, config });
expect(searchArgs.es.msearch.mock.calls[0][0]).toHaveProperty('ignore_throttled', false);
});
test('should properly call abort with msearch', () => {
- searchArgs.config = getConfigStub({
- 'courier:batchSearches': true
+ const config = getConfigStub({
+ 'courier:batchSearches': true,
});
- search(searchArgs).abort();
+ search({ ...searchArgs, config }).abort();
expect(msearchMockResponse.abort).toHaveBeenCalled();
});
test('should properly abort with search', async () => {
- searchArgs.config = getConfigStub({
- 'courier:batchSearches': false
+ const config = getConfigStub({
+ 'courier:batchSearches': false,
});
- search(searchArgs).abort();
+ search({ ...searchArgs, config }).abort();
expect(searchMockResponse.abort).toHaveBeenCalled();
});
});
diff --git a/src/legacy/ui/public/courier/search_strategy/default_search_strategy.js b/src/legacy/ui/public/courier/search_strategy/default_search_strategy.ts
similarity index 76%
rename from src/legacy/ui/public/courier/search_strategy/default_search_strategy.js
rename to src/legacy/ui/public/courier/search_strategy/default_search_strategy.ts
index 42a9b64136454..5be4fef076655 100644
--- a/src/legacy/ui/public/courier/search_strategy/default_search_strategy.js
+++ b/src/legacy/ui/public/courier/search_strategy/default_search_strategy.ts
@@ -17,37 +17,39 @@
* under the License.
*/
+import { SearchStrategyProvider, SearchStrategySearchParams } from './types';
import { addSearchStrategy } from './search_strategy_registry';
import { isDefaultTypeIndexPattern } from './is_default_type_index_pattern';
-import { getSearchParams, getMSearchParams, getPreference, getTimeout } from '../fetch/get_search_params';
+import {
+ getSearchParams,
+ getMSearchParams,
+ getPreference,
+ getTimeout,
+} from '../fetch/get_search_params';
-export const defaultSearchStrategy = {
+export const defaultSearchStrategy: SearchStrategyProvider = {
id: 'default',
search: params => {
return params.config.get('courier:batchSearches') ? msearch(params) : search(params);
},
- isViable: (indexPattern) => {
- if (!indexPattern) {
- return false;
- }
-
- return isDefaultTypeIndexPattern(indexPattern);
+ isViable: indexPattern => {
+ return indexPattern && isDefaultTypeIndexPattern(indexPattern);
},
};
-function msearch({ searchRequests, es, config, esShardTimeout }) {
+function msearch({ searchRequests, es, config, esShardTimeout }: SearchStrategySearchParams) {
const inlineRequests = searchRequests.map(({ index, body, search_type: searchType }) => {
const inlineHeader = {
index: index.title || index,
search_type: searchType,
ignore_unavailable: true,
- preference: getPreference(config)
+ preference: getPreference(config),
};
const inlineBody = {
...body,
- timeout: getTimeout(esShardTimeout)
+ timeout: getTimeout(esShardTimeout),
};
return `${JSON.stringify(inlineHeader)}\n${JSON.stringify(inlineBody)}`;
});
@@ -58,11 +60,11 @@ function msearch({ searchRequests, es, config, esShardTimeout }) {
});
return {
searching: searching.then(({ responses }) => responses),
- abort: searching.abort
+ abort: searching.abort,
};
}
-function search({ searchRequests, es, config, esShardTimeout }) {
+function search({ searchRequests, es, config, esShardTimeout }: SearchStrategySearchParams) {
const abortController = new AbortController();
const searchParams = getSearchParams(config, esShardTimeout);
const promises = searchRequests.map(({ index, body }) => {
diff --git a/src/legacy/ui/public/courier/search_strategy/index.js b/src/legacy/ui/public/courier/search_strategy/index.ts
similarity index 100%
rename from src/legacy/ui/public/courier/search_strategy/index.js
rename to src/legacy/ui/public/courier/search_strategy/index.ts
diff --git a/src/legacy/ui/public/courier/search_strategy/is_default_type_index_pattern.js b/src/legacy/ui/public/courier/search_strategy/is_default_type_index_pattern.ts
similarity index 85%
rename from src/legacy/ui/public/courier/search_strategy/is_default_type_index_pattern.js
rename to src/legacy/ui/public/courier/search_strategy/is_default_type_index_pattern.ts
index 94c85c0e13ec7..3785ce6341078 100644
--- a/src/legacy/ui/public/courier/search_strategy/is_default_type_index_pattern.js
+++ b/src/legacy/ui/public/courier/search_strategy/is_default_type_index_pattern.ts
@@ -17,7 +17,9 @@
* under the License.
*/
-export const isDefaultTypeIndexPattern = indexPattern => {
+import { IndexPattern } from '../../../../core_plugins/data/public';
+
+export const isDefaultTypeIndexPattern = (indexPattern: IndexPattern) => {
// Default index patterns don't have `type` defined.
return !indexPattern.type;
};
diff --git a/src/legacy/ui/public/courier/search_strategy/no_op_search_strategy.js b/src/legacy/ui/public/courier/search_strategy/no_op_search_strategy.ts
similarity index 79%
rename from src/legacy/ui/public/courier/search_strategy/no_op_search_strategy.js
rename to src/legacy/ui/public/courier/search_strategy/no_op_search_strategy.ts
index c4499cc870d56..24c3876cfcc05 100644
--- a/src/legacy/ui/public/courier/search_strategy/no_op_search_strategy.js
+++ b/src/legacy/ui/public/courier/search_strategy/no_op_search_strategy.ts
@@ -17,21 +17,25 @@
* under the License.
*/
-import { SearchError } from './search_error';
import { i18n } from '@kbn/i18n';
+import { SearchError } from './search_error';
+import { SearchStrategyProvider } from './types';
-export const noOpSearchStrategy = {
+export const noOpSearchStrategy: SearchStrategyProvider = {
id: 'noOp',
- search: async () => {
+ search: () => {
const searchError = new SearchError({
status: '418', // "I'm a teapot" error
title: i18n.translate('common.ui.courier.noSearchStrategyRegisteredErrorMessageTitle', {
defaultMessage: 'No search strategy registered',
}),
- message: i18n.translate('common.ui.courier.noSearchStrategyRegisteredErrorMessageDescription', {
- defaultMessage: `Couldn't find a search strategy for the search request`,
- }),
+ message: i18n.translate(
+ 'common.ui.courier.noSearchStrategyRegisteredErrorMessageDescription',
+ {
+ defaultMessage: `Couldn't find a search strategy for the search request`,
+ }
+ ),
type: 'NO_OP_SEARCH_STRATEGY',
path: '',
});
@@ -39,7 +43,6 @@ export const noOpSearchStrategy = {
return {
searching: Promise.reject(searchError),
abort: () => {},
- failedSearchRequests: [],
};
},
diff --git a/src/legacy/ui/public/courier/search_strategy/search_error.d.ts b/src/legacy/ui/public/courier/search_strategy/search_error.d.ts
deleted file mode 100644
index bf49853957c75..0000000000000
--- a/src/legacy/ui/public/courier/search_strategy/search_error.d.ts
+++ /dev/null
@@ -1,21 +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 type SearchError = any;
-export type getSearchErrorType = any;
diff --git a/src/legacy/ui/public/courier/search_strategy/search_error.js b/src/legacy/ui/public/courier/search_strategy/search_error.ts
similarity index 76%
rename from src/legacy/ui/public/courier/search_strategy/search_error.js
rename to src/legacy/ui/public/courier/search_strategy/search_error.ts
index 9c35d11a6abf4..d4042fb17499c 100644
--- a/src/legacy/ui/public/courier/search_strategy/search_error.js
+++ b/src/legacy/ui/public/courier/search_strategy/search_error.ts
@@ -17,8 +17,23 @@
* under the License.
*/
+interface SearchErrorOptions {
+ status: string;
+ title: string;
+ message: string;
+ path: string;
+ type: string;
+}
+
export class SearchError extends Error {
- constructor({ status, title, message, path, type }) {
+ public name: string;
+ public status: string;
+ public title: string;
+ public message: string;
+ public path: string;
+ public type: string;
+
+ constructor({ status, title, message, path, type }: SearchErrorOptions) {
super(message);
this.name = 'SearchError';
this.status = status;
@@ -39,9 +54,9 @@ export class SearchError extends Error {
}
}
-export function getSearchErrorType({ message }) {
+export function getSearchErrorType({ message }: Pick) {
const msg = message.toLowerCase();
- if(msg.indexOf('unsupported query') > -1) {
+ if (msg.indexOf('unsupported query') > -1) {
return 'UNSUPPORTED_QUERY';
}
}
diff --git a/src/legacy/ui/public/courier/search_strategy/search_strategy_registry.test.js b/src/legacy/ui/public/courier/search_strategy/search_strategy_registry.test.ts
similarity index 58%
rename from src/legacy/ui/public/courier/search_strategy/search_strategy_registry.test.js
rename to src/legacy/ui/public/courier/search_strategy/search_strategy_registry.test.ts
index 362d303eb6203..ae2ed6128c8ea 100644
--- a/src/legacy/ui/public/courier/search_strategy/search_strategy_registry.test.js
+++ b/src/legacy/ui/public/courier/search_strategy/search_strategy_registry.test.ts
@@ -17,6 +17,7 @@
* under the License.
*/
+import { IndexPattern } from '../../../../core_plugins/data/public';
import { noOpSearchStrategy } from './no_op_search_strategy';
import {
searchStrategies,
@@ -24,16 +25,28 @@ import {
getSearchStrategyByViability,
getSearchStrategyById,
getSearchStrategyForSearchRequest,
- hasSearchStategyForIndexPattern
+ hasSearchStategyForIndexPattern,
} from './search_strategy_registry';
-
-const mockSearchStrategies = [{
- id: 0,
- isViable: index => index === 0
-}, {
- id: 1,
- isViable: index => index === 1
-}];
+import { SearchStrategyProvider } from './types';
+
+const mockSearchStrategies: SearchStrategyProvider[] = [
+ {
+ id: '0',
+ isViable: (index: IndexPattern) => index.id === '0',
+ search: () => ({
+ searching: Promise.resolve([]),
+ abort: () => void 0,
+ }),
+ },
+ {
+ id: '1',
+ isViable: (index: IndexPattern) => index.id === '1',
+ search: () => ({
+ searching: Promise.resolve([]),
+ abort: () => void 0,
+ }),
+ },
+];
describe('Search strategy registry', () => {
beforeEach(() => {
@@ -59,12 +72,16 @@ describe('Search strategy registry', () => {
});
it('returns the viable strategy', () => {
- expect(getSearchStrategyByViability(0)).toBe(mockSearchStrategies[0]);
- expect(getSearchStrategyByViability(1)).toBe(mockSearchStrategies[1]);
+ expect(getSearchStrategyByViability({ id: '0' } as IndexPattern)).toBe(
+ mockSearchStrategies[0]
+ );
+ expect(getSearchStrategyByViability({ id: '1' } as IndexPattern)).toBe(
+ mockSearchStrategies[1]
+ );
});
it('returns undefined if there is no viable strategy', () => {
- expect(getSearchStrategyByViability(-1)).toBe(undefined);
+ expect(getSearchStrategyByViability({ id: '-1' } as IndexPattern)).toBe(undefined);
});
});
@@ -74,12 +91,16 @@ describe('Search strategy registry', () => {
});
it('returns the strategy by ID', () => {
- expect(getSearchStrategyById(0)).toBe(mockSearchStrategies[0]);
- expect(getSearchStrategyById(1)).toBe(mockSearchStrategies[1]);
+ expect(getSearchStrategyById('0')).toBe(mockSearchStrategies[0]);
+ expect(getSearchStrategyById('1')).toBe(mockSearchStrategies[1]);
});
it('returns undefined if there is no strategy with that ID', () => {
- expect(getSearchStrategyById(-1)).toBe(undefined);
+ expect(getSearchStrategyById('-1')).toBe(undefined);
+ });
+
+ it('returns the noOp search strategy if passed that ID', () => {
+ expect(getSearchStrategyById('noOp')).toBe(noOpSearchStrategy);
});
});
@@ -89,15 +110,29 @@ describe('Search strategy registry', () => {
});
it('returns the strategy by ID if provided', () => {
- expect(getSearchStrategyForSearchRequest({}, { searchStrategyId: 1 })).toBe(mockSearchStrategies[1]);
+ expect(getSearchStrategyForSearchRequest({}, { searchStrategyId: '1' })).toBe(
+ mockSearchStrategies[1]
+ );
+ });
+
+ it('throws if there is no strategy by provided ID', () => {
+ expect(() =>
+ getSearchStrategyForSearchRequest({}, { searchStrategyId: '-1' })
+ ).toThrowErrorMatchingInlineSnapshot(`"No strategy with ID -1"`);
});
it('returns the strategy by viability if there is one', () => {
- expect(getSearchStrategyForSearchRequest({ index: 1 })).toBe(mockSearchStrategies[1]);
+ expect(
+ getSearchStrategyForSearchRequest({
+ index: {
+ id: '1',
+ },
+ })
+ ).toBe(mockSearchStrategies[1]);
});
it('returns the no op strategy if there is no viable strategy', () => {
- expect(getSearchStrategyForSearchRequest({ index: 3 })).toBe(noOpSearchStrategy);
+ expect(getSearchStrategyForSearchRequest({ index: '3' })).toBe(noOpSearchStrategy);
});
});
@@ -107,8 +142,8 @@ describe('Search strategy registry', () => {
});
it('returns whether there is a search strategy for this index pattern', () => {
- expect(hasSearchStategyForIndexPattern(0)).toBe(true);
- expect(hasSearchStategyForIndexPattern(-1)).toBe(false);
+ expect(hasSearchStategyForIndexPattern({ id: '0' } as IndexPattern)).toBe(true);
+ expect(hasSearchStategyForIndexPattern({ id: '-1' } as IndexPattern)).toBe(false);
});
});
});
diff --git a/src/legacy/ui/public/courier/search_strategy/search_strategy_registry.js b/src/legacy/ui/public/courier/search_strategy/search_strategy_registry.ts
similarity index 64%
rename from src/legacy/ui/public/courier/search_strategy/search_strategy_registry.js
rename to src/legacy/ui/public/courier/search_strategy/search_strategy_registry.ts
index e67d39ea27aa6..9ef007f97531e 100644
--- a/src/legacy/ui/public/courier/search_strategy/search_strategy_registry.js
+++ b/src/legacy/ui/public/courier/search_strategy/search_strategy_registry.ts
@@ -17,11 +17,14 @@
* under the License.
*/
+import { IndexPattern } from '../../../../core_plugins/data/public';
+import { SearchStrategyProvider } from './types';
import { noOpSearchStrategy } from './no_op_search_strategy';
+import { SearchResponse } from '../types';
-export const searchStrategies = [];
+export const searchStrategies: SearchStrategyProvider[] = [];
-export const addSearchStrategy = searchStrategy => {
+export const addSearchStrategy = (searchStrategy: SearchStrategyProvider) => {
if (searchStrategies.includes(searchStrategy)) {
return;
}
@@ -29,22 +32,27 @@ export const addSearchStrategy = searchStrategy => {
searchStrategies.push(searchStrategy);
};
-export const getSearchStrategyByViability = indexPattern => {
+export const getSearchStrategyByViability = (indexPattern: IndexPattern) => {
return searchStrategies.find(searchStrategy => {
return searchStrategy.isViable(indexPattern);
});
};
-export const getSearchStrategyById = searchStrategyId => {
- return searchStrategies.find(searchStrategy => {
+export const getSearchStrategyById = (searchStrategyId: string) => {
+ return [...searchStrategies, noOpSearchStrategy].find(searchStrategy => {
return searchStrategy.id === searchStrategyId;
});
};
-export const getSearchStrategyForSearchRequest = (searchRequest, { searchStrategyId } = {}) => {
+export const getSearchStrategyForSearchRequest = (
+ searchRequest: SearchResponse,
+ { searchStrategyId }: { searchStrategyId?: string } = {}
+) => {
// Allow the searchSource to declare the correct strategy with which to execute its searches.
if (searchStrategyId != null) {
- return getSearchStrategyById(searchStrategyId);
+ const strategy = getSearchStrategyById(searchStrategyId);
+ if (!strategy) throw Error(`No strategy with ID ${searchStrategyId}`);
+ return strategy;
}
// Otherwise try to match it to a strategy.
@@ -58,6 +66,6 @@ export const getSearchStrategyForSearchRequest = (searchRequest, { searchStrateg
return noOpSearchStrategy;
};
-export const hasSearchStategyForIndexPattern = indexPattern => {
+export const hasSearchStategyForIndexPattern = (indexPattern: IndexPattern) => {
return Boolean(getSearchStrategyByViability(indexPattern));
};
diff --git a/src/legacy/ui/public/courier/search_strategy/types.ts b/src/legacy/ui/public/courier/search_strategy/types.ts
new file mode 100644
index 0000000000000..1542f9824a5b1
--- /dev/null
+++ b/src/legacy/ui/public/courier/search_strategy/types.ts
@@ -0,0 +1,37 @@
+/*
+ * 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 { IndexPattern } from '../../../../core_plugins/data/public';
+import { FetchHandlers } from '../fetch/types';
+import { SearchRequest, SearchResponse } from '../types';
+
+export interface SearchStrategyProvider {
+ id: string;
+ search: (params: SearchStrategySearchParams) => SearchStrategyResponse;
+ isViable: (indexPattern: IndexPattern) => boolean;
+}
+
+export interface SearchStrategyResponse {
+ searching: Promise;
+ abort: () => void;
+}
+
+export interface SearchStrategySearchParams extends FetchHandlers {
+ searchRequests: SearchRequest[];
+}
diff --git a/src/legacy/core_plugins/data/public/filter/index.tsx b/src/legacy/ui/public/courier/types.ts
similarity index 84%
rename from src/legacy/core_plugins/data/public/filter/index.tsx
rename to src/legacy/ui/public/courier/types.ts
index e48a18fc53a76..23d74ce6a57da 100644
--- a/src/legacy/core_plugins/data/public/filter/index.tsx
+++ b/src/legacy/ui/public/courier/types.ts
@@ -17,4 +17,7 @@
* under the License.
*/
-export { ApplyFiltersPopover } from './apply_filters';
+export * from './fetch/types';
+export * from './search_source/types';
+export * from './search_strategy/types';
+export * from './utils/types';
diff --git a/src/legacy/ui/public/courier/utils/courier_inspector_utils.js b/src/legacy/ui/public/courier/utils/courier_inspector_utils.ts
similarity index 78%
rename from src/legacy/ui/public/courier/utils/courier_inspector_utils.js
rename to src/legacy/ui/public/courier/utils/courier_inspector_utils.ts
index 0e53f92bd9dcb..2c47fae4cce37 100644
--- a/src/legacy/ui/public/courier/utils/courier_inspector_utils.js
+++ b/src/legacy/ui/public/courier/utils/courier_inspector_utils.ts
@@ -25,51 +25,57 @@
*/
import { i18n } from '@kbn/i18n';
+import { SearchResponse } from 'elasticsearch';
+import { SearchSourceContract, RequestInspectorStats } from '../types';
-function getRequestInspectorStats(searchSource) {
- const stats = {};
+function getRequestInspectorStats(searchSource: SearchSourceContract) {
+ const stats: RequestInspectorStats = {};
const index = searchSource.getField('index');
if (index) {
stats.indexPattern = {
label: i18n.translate('common.ui.courier.indexPatternLabel', {
- defaultMessage: 'Index pattern'
+ defaultMessage: 'Index pattern',
}),
value: index.title,
description: i18n.translate('common.ui.courier.indexPatternDescription', {
- defaultMessage: 'The index pattern that connected to the Elasticsearch indices.'
+ defaultMessage: 'The index pattern that connected to the Elasticsearch indices.',
}),
};
stats.indexPatternId = {
label: i18n.translate('common.ui.courier.indexPatternIdLabel', {
- defaultMessage: 'Index pattern ID'
+ defaultMessage: 'Index pattern ID',
}),
- value: index.id,
+ value: index.id!,
description: i18n.translate('common.ui.courier.indexPatternIdDescription', {
defaultMessage: 'The ID in the {kibanaIndexPattern} index.',
- values: { kibanaIndexPattern: '.kibana' }
+ values: { kibanaIndexPattern: '.kibana' },
}),
};
}
return stats;
}
-function getResponseInspectorStats(searchSource, resp) {
+function getResponseInspectorStats(
+ searchSource: SearchSourceContract,
+ resp: SearchResponse
+) {
const lastRequest = searchSource.history && searchSource.history[searchSource.history.length - 1];
- const stats = {};
+ const stats: RequestInspectorStats = {};
if (resp && resp.took) {
stats.queryTime = {
label: i18n.translate('common.ui.courier.queryTimeLabel', {
- defaultMessage: 'Query time'
+ defaultMessage: 'Query time',
}),
value: i18n.translate('common.ui.courier.queryTimeValue', {
defaultMessage: '{queryTime}ms',
values: { queryTime: resp.took },
}),
description: i18n.translate('common.ui.courier.queryTimeDescription', {
- defaultMessage: 'The time it took to process the query. ' +
- 'Does not include the time to send the request or parse it in the browser.'
+ defaultMessage:
+ 'The time it took to process the query. ' +
+ 'Does not include the time to send the request or parse it in the browser.',
}),
};
}
@@ -77,21 +83,21 @@ function getResponseInspectorStats(searchSource, resp) {
if (resp && resp.hits) {
stats.hitsTotal = {
label: i18n.translate('common.ui.courier.hitsTotalLabel', {
- defaultMessage: 'Hits (total)'
+ defaultMessage: 'Hits (total)',
}),
value: `${resp.hits.total}`,
description: i18n.translate('common.ui.courier.hitsTotalDescription', {
- defaultMessage: 'The number of documents that match the query.'
+ defaultMessage: 'The number of documents that match the query.',
}),
};
stats.hits = {
label: i18n.translate('common.ui.courier.hitsLabel', {
- defaultMessage: 'Hits'
+ defaultMessage: 'Hits',
}),
value: `${resp.hits.hits.length}`,
description: i18n.translate('common.ui.courier.hitsDescription', {
- defaultMessage: 'The number of documents returned by the query.'
+ defaultMessage: 'The number of documents returned by the query.',
}),
};
}
@@ -99,15 +105,16 @@ function getResponseInspectorStats(searchSource, resp) {
if (lastRequest && (lastRequest.ms === 0 || lastRequest.ms)) {
stats.requestTime = {
label: i18n.translate('common.ui.courier.requestTimeLabel', {
- defaultMessage: 'Request time'
+ defaultMessage: 'Request time',
}),
value: i18n.translate('common.ui.courier.requestTimeValue', {
defaultMessage: '{requestTime}ms',
values: { requestTime: lastRequest.ms },
}),
description: i18n.translate('common.ui.courier.requestTimeDescription', {
- defaultMessage: 'The time of the request from the browser to Elasticsearch and back. ' +
- 'Does not include the time the requested waited in the queue.'
+ defaultMessage:
+ 'The time of the request from the browser to Elasticsearch and back. ' +
+ 'Does not include the time the requested waited in the queue.',
}),
};
}
diff --git a/src/legacy/ui/public/courier/utils/types.ts b/src/legacy/ui/public/courier/utils/types.ts
new file mode 100644
index 0000000000000..305f27a86b398
--- /dev/null
+++ b/src/legacy/ui/public/courier/utils/types.ts
@@ -0,0 +1,33 @@
+/*
+ * 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 interface InspectorStat {
+ label: string;
+ value: string;
+ description: string;
+}
+
+export interface RequestInspectorStats {
+ indexPattern?: InspectorStat;
+ indexPatternId?: InspectorStat;
+ queryTime?: InspectorStat;
+ hitsTotal?: InspectorStat;
+ hits?: InspectorStat;
+ requestTime?: InspectorStat;
+}
diff --git a/src/legacy/ui/public/field_wildcard/__tests__/field_wildcard.js b/src/legacy/ui/public/field_wildcard/__tests__/field_wildcard.js
deleted file mode 100644
index a15c602b7ba83..0000000000000
--- a/src/legacy/ui/public/field_wildcard/__tests__/field_wildcard.js
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import expect from '@kbn/expect';
-import ngMock from 'ng_mock';
-
-import { fieldWildcardFilter, makeRegEx } from '../../field_wildcard';
-
-describe('fieldWildcard', function () {
- const metaFields = ['_id', '_type', '_source'];
-
- beforeEach(ngMock.module('kibana'));
-
- describe('makeRegEx', function () {
- it('matches * in any position', function () {
- expect('aaaaaabbbbbbbcccccc').to.match(makeRegEx('*a*b*c*'));
- expect('a1234').to.match(makeRegEx('*1234'));
- expect('1234a').to.match(makeRegEx('1234*'));
- expect('12a34').to.match(makeRegEx('12a34'));
- });
-
- it('properly escapes regexp control characters', function () {
- expect('account[user_id]').to.match(makeRegEx('account[*]'));
- });
-
- it('properly limits matches without wildcards', function () {
- expect('username').to.match(makeRegEx('*name'));
- expect('username').to.match(makeRegEx('user*'));
- expect('username').to.match(makeRegEx('username'));
- expect('username').to.not.match(makeRegEx('user'));
- expect('username').to.not.match(makeRegEx('name'));
- expect('username').to.not.match(makeRegEx('erna'));
- });
- });
-
- describe('filter', function () {
- it('filters nothing when given undefined', function () {
- const filter = fieldWildcardFilter();
- const original = [
- 'foo',
- 'bar',
- 'baz',
- 1234
- ];
-
- expect(original.filter(filter)).to.eql(original);
- });
-
- it('filters nothing when given an empty array', function () {
- const filter = fieldWildcardFilter([], metaFields);
- const original = [
- 'foo',
- 'bar',
- 'baz',
- 1234
- ];
-
- expect(original.filter(filter)).to.eql(original);
- });
-
- it('does not filter metaFields', function () {
- const filter = fieldWildcardFilter([ '_*' ], metaFields);
-
- const original = [
- '_id',
- '_type',
- '_typefake'
- ];
-
- expect(original.filter(filter)).to.eql(['_id', '_type']);
- });
-
- it('filters values that match the globs', function () {
- const filter = fieldWildcardFilter([
- 'f*',
- '*4'
- ], metaFields);
-
- const original = [
- 'foo',
- 'bar',
- 'baz',
- 1234
- ];
-
- expect(original.filter(filter)).to.eql(['bar', 'baz']);
- });
-
- it('handles weird values okay', function () {
- const filter = fieldWildcardFilter([
- 'f*',
- '*4',
- 'undefined'
- ], metaFields);
-
- const original = [
- 'foo',
- null,
- 'bar',
- undefined,
- {},
- [],
- 'baz',
- 1234
- ];
-
- expect(original.filter(filter)).to.eql([null, 'bar', {}, [], 'baz']);
- });
- });
-});
diff --git a/src/legacy/ui/public/field_wildcard/field_wildcard.test.ts b/src/legacy/ui/public/field_wildcard/field_wildcard.test.ts
new file mode 100644
index 0000000000000..9f7523866fdc1
--- /dev/null
+++ b/src/legacy/ui/public/field_wildcard/field_wildcard.test.ts
@@ -0,0 +1,86 @@
+/*
+ * 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 { fieldWildcardFilter, makeRegEx } from './field_wildcard';
+
+describe('fieldWildcard', () => {
+ const metaFields = ['_id', '_type', '_source'];
+
+ describe('makeRegEx', function() {
+ it('matches * in any position', function() {
+ expect('aaaaaabbbbbbbcccccc').toMatch(makeRegEx('*a*b*c*'));
+ expect('a1234').toMatch(makeRegEx('*1234'));
+ expect('1234a').toMatch(makeRegEx('1234*'));
+ expect('12a34').toMatch(makeRegEx('12a34'));
+ });
+
+ it('properly escapes regexp control characters', function() {
+ expect('account[user_id]').toMatch(makeRegEx('account[*]'));
+ });
+
+ it('properly limits matches without wildcards', function() {
+ expect('username').toMatch(makeRegEx('*name'));
+ expect('username').toMatch(makeRegEx('user*'));
+ expect('username').toMatch(makeRegEx('username'));
+ expect('username').not.toMatch(makeRegEx('user'));
+ expect('username').not.toMatch(makeRegEx('name'));
+ expect('username').not.toMatch(makeRegEx('erna'));
+ });
+ });
+
+ describe('filter', function() {
+ it('filters nothing when given undefined', function() {
+ const filter = fieldWildcardFilter();
+ const original = ['foo', 'bar', 'baz', 1234];
+
+ expect(original.filter(val => filter(val))).toEqual(original);
+ });
+
+ it('filters nothing when given an empty array', function() {
+ const filter = fieldWildcardFilter([], metaFields);
+ const original = ['foo', 'bar', 'baz', 1234];
+
+ expect(original.filter(filter)).toEqual(original);
+ });
+
+ it('does not filter metaFields', function() {
+ const filter = fieldWildcardFilter(['_*'], metaFields);
+
+ const original = ['_id', '_type', '_typefake'];
+
+ expect(original.filter(filter)).toEqual(['_id', '_type']);
+ });
+
+ it('filters values that match the globs', function() {
+ const filter = fieldWildcardFilter(['f*', '*4'], metaFields);
+
+ const original = ['foo', 'bar', 'baz', 1234];
+
+ expect(original.filter(filter)).toEqual(['bar', 'baz']);
+ });
+
+ it('handles weird values okay', function() {
+ const filter = fieldWildcardFilter(['f*', '*4', 'undefined'], metaFields);
+
+ const original = ['foo', null, 'bar', undefined, {}, [], 'baz', 1234];
+
+ expect(original.filter(filter)).toEqual([null, 'bar', {}, [], 'baz']);
+ });
+ });
+});
diff --git a/src/legacy/ui/public/field_wildcard/field_wildcard.js b/src/legacy/ui/public/field_wildcard/field_wildcard.ts
similarity index 70%
rename from src/legacy/ui/public/field_wildcard/field_wildcard.js
rename to src/legacy/ui/public/field_wildcard/field_wildcard.ts
index 656641b20a98c..5437086ddd6f4 100644
--- a/src/legacy/ui/public/field_wildcard/field_wildcard.js
+++ b/src/legacy/ui/public/field_wildcard/field_wildcard.ts
@@ -19,25 +19,29 @@
import { escapeRegExp, memoize } from 'lodash';
-export const makeRegEx = memoize(function makeRegEx(glob) {
- return new RegExp('^' + glob.split('*').map(escapeRegExp).join('.*') + '$');
+export const makeRegEx = memoize(function makeRegEx(glob: string) {
+ const globRegex = glob
+ .split('*')
+ .map(escapeRegExp)
+ .join('.*');
+ return new RegExp(`^${globRegex}$`);
});
// Note that this will return an essentially noop function if globs is undefined.
-export function fieldWildcardMatcher(globs = [], metaFields) {
- return function matcher(val) {
+export function fieldWildcardMatcher(globs: string[] = [], metaFields: unknown[] = []) {
+ return function matcher(val: unknown) {
// do not test metaFields or keyword
if (metaFields.indexOf(val) !== -1) {
return false;
}
- return globs.some(p => makeRegEx(p).test(val));
+ return globs.some(p => makeRegEx(p).test(`${val}`));
};
}
// Note that this will return an essentially noop function if globs is undefined.
-export function fieldWildcardFilter(globs = [], metaFields = []) {
+export function fieldWildcardFilter(globs: string[] = [], metaFields: string[] = []) {
const matcher = fieldWildcardMatcher(globs, metaFields);
- return function filter(val) {
+ return function filter(val: unknown) {
return !matcher(val);
};
}
diff --git a/src/legacy/ui/public/field_wildcard/index.js b/src/legacy/ui/public/field_wildcard/index.ts
similarity index 100%
rename from src/legacy/ui/public/field_wildcard/index.js
rename to src/legacy/ui/public/field_wildcard/index.ts
diff --git a/src/legacy/ui/public/index_patterns/__mocks__/index.ts b/src/legacy/ui/public/index_patterns/__mocks__/index.ts
index f51ae86b5c9a7..145045a90ade8 100644
--- a/src/legacy/ui/public/index_patterns/__mocks__/index.ts
+++ b/src/legacy/ui/public/index_patterns/__mocks__/index.ts
@@ -35,7 +35,6 @@ export {
CONTAINS_SPACES,
getFromSavedObject,
getRoutes,
- IndexPatternSelect,
validateIndexPattern,
ILLEGAL_CHARACTERS,
INDEX_PATTERN_ILLEGAL_CHARACTERS,
diff --git a/src/legacy/ui/public/index_patterns/index.ts b/src/legacy/ui/public/index_patterns/index.ts
index 690a9cffaa138..d0ff0aaa8c72c 100644
--- a/src/legacy/ui/public/index_patterns/index.ts
+++ b/src/legacy/ui/public/index_patterns/index.ts
@@ -30,7 +30,6 @@ export const {
FieldList, // only used in Discover and StubIndexPattern
flattenHitWrapper,
formatHitProvider,
- IndexPatternSelect, // only used in x-pack/plugin/maps and input control vis
} = data.indexPatterns;
// static code
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 773d4283cad88..ff89ef69d53ca 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
@@ -26,6 +26,10 @@ const mockObservable = () => {
};
};
+const mockComponent = () => {
+ return null;
+};
+
export const mockUiSettings = {
get: (item) => {
return mockUiSettings[item];
@@ -139,6 +143,9 @@ export const npStart = {
getProvider: sinon.fake(),
},
getSuggestions: sinon.fake(),
+ ui: {
+ IndexPatternSelect: mockComponent,
+ },
query: {
filterManager: {
getFetches$: sinon.fake(),
diff --git a/src/legacy/ui/public/new_platform/new_platform.test.ts b/src/legacy/ui/public/new_platform/new_platform.test.ts
index cbdaccd65f94b..e5d5cd0a87776 100644
--- a/src/legacy/ui/public/new_platform/new_platform.test.ts
+++ b/src/legacy/ui/public/new_platform/new_platform.test.ts
@@ -18,13 +18,15 @@
*/
import { setRootControllerMock } from './new_platform.test.mocks';
-import { legacyAppRegister, __reset__ } from './new_platform';
+import { legacyAppRegister, __reset__, __setup__ } from './new_platform';
+import { coreMock } from '../../../../core/public/mocks';
describe('ui/new_platform', () => {
describe('legacyAppRegister', () => {
beforeEach(() => {
setRootControllerMock.mockReset();
__reset__();
+ __setup__(coreMock.createSetup({ basePath: '/test/base/path' }) as any, {} as any);
});
const registerApp = () => {
@@ -59,7 +61,7 @@ describe('ui/new_platform', () => {
controller(scopeMock, elementMock);
expect(mountMock).toHaveBeenCalledWith(expect.any(Object), {
element: elementMock[0],
- appBasePath: '',
+ appBasePath: '/test/base/path/app/test',
});
});
diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts
index acf1191852dc8..c0b2d6d913257 100644
--- a/src/legacy/ui/public/new_platform/new_platform.ts
+++ b/src/legacy/ui/public/new_platform/new_platform.ts
@@ -19,7 +19,7 @@
import { IScope } from 'angular';
import { IUiActionsStart, IUiActionsSetup } from 'src/plugins/ui_actions/public';
-import { Start as EmbeddableStart, Setup as EmbeddableSetup } from 'src/plugins/embeddable/public';
+import { IEmbeddableStart, IEmbeddableSetup } from 'src/plugins/embeddable/public';
import { LegacyCoreSetup, LegacyCoreStart, App } from '../../../../core/public';
import { Plugin as DataPlugin } from '../../../../plugins/data/public';
import { Plugin as ExpressionsPlugin } from '../../../../plugins/expressions/public';
@@ -35,7 +35,7 @@ import { SharePluginSetup, SharePluginStart } from '../../../../plugins/share/pu
export interface PluginsSetup {
data: ReturnType;
- embeddable: EmbeddableSetup;
+ embeddable: IEmbeddableSetup;
expressions: ReturnType;
home: HomePublicPluginSetup;
inspector: InspectorSetup;
@@ -47,7 +47,7 @@ export interface PluginsSetup {
export interface PluginsStart {
data: ReturnType;
- embeddable: EmbeddableStart;
+ embeddable: IEmbeddableStart;
eui_utils: EuiUtilsStart;
expressions: ReturnType;
home: HomePublicPluginStart;
@@ -111,7 +111,10 @@ export const legacyAppRegister = (app: App) => {
// Root controller cannot return a Promise so use an internal async function and call it immediately
(async () => {
- const unmount = await app.mount({ core: npStart.core }, { element, appBasePath: '' });
+ const unmount = await app.mount(
+ { core: npStart.core },
+ { element, appBasePath: npSetup.core.http.basePath.prepend(`/app/${app.id}`) }
+ );
$scope.$on('$destroy', () => {
unmount();
});
diff --git a/src/legacy/ui/public/promises/defer.ts b/src/legacy/ui/public/promises/defer.ts
index 8ef97c0b3ebcc..3d435f2ba8dfd 100644
--- a/src/legacy/ui/public/promises/defer.ts
+++ b/src/legacy/ui/public/promises/defer.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-interface Defer {
+export interface Defer {
promise: Promise;
resolve(value: T): void;
reject(reason: Error): void;
diff --git a/src/legacy/ui/public/visualize/components/visualization_requesterror.tsx b/src/legacy/ui/public/visualize/components/visualization_requesterror.tsx
index ebbe886b3650b..19cbbf9cea04c 100644
--- a/src/legacy/ui/public/visualize/components/visualization_requesterror.tsx
+++ b/src/legacy/ui/public/visualize/components/visualization_requesterror.tsx
@@ -19,7 +19,7 @@
import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui';
import React from 'react';
-import { SearchError } from 'ui/courier';
+import { SearchError } from '../../courier';
import { dispatchRenderComplete } from '../../../../../plugins/kibana_utils/public';
interface VisualizationRequestErrorProps {
@@ -32,7 +32,7 @@ export class VisualizationRequestError extends React.Component
diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.ts
index 70e0c1f1382fa..608a8b9ce8aa7 100644
--- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.ts
+++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.ts
@@ -28,7 +28,7 @@ import {
} from './build_pipeline';
import { Vis, VisState } from 'ui/vis';
import { AggConfig } from 'ui/agg_types/agg_config';
-import { searchSourceMock } from 'ui/courier/search_source/mocks';
+import { searchSourceMock } from '../../../courier/search_source/mocks';
jest.mock('ui/new_platform');
jest.mock('ui/agg_types/buckets/date_histogram', () => ({
diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts
index 21b13abea440e..ca9540b4d3737 100644
--- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts
+++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts
@@ -20,11 +20,11 @@
import { cloneDeep, get } from 'lodash';
// @ts-ignore
import { setBounds } from 'ui/agg_types';
-import { SearchSource } from 'ui/courier';
import { AggConfig, Vis, VisParams, VisState } from 'ui/vis';
import { isDateHistogramBucketAggConfig } from 'ui/agg_types/buckets/date_histogram';
import moment from 'moment';
import { SerializedFieldFormat } from 'src/plugins/expressions/public';
+import { SearchSourceContract } from '../../../courier/types';
import { createFormat } from './utilities';
interface SchemaConfigParams {
@@ -462,7 +462,7 @@ export const buildVislibDimensions = async (
// take a Vis object and decorate it with the necessary params (dimensions, bucket, metric, etc)
export const getVisParams = async (
vis: Vis,
- params: { searchSource: SearchSource; timeRange?: any; abortSignal?: AbortSignal }
+ params: { searchSource: SearchSourceContract; timeRange?: any; abortSignal?: AbortSignal }
) => {
const schemas = getSchemas(vis, params.timeRange);
let visConfig = cloneDeep(vis.params);
@@ -479,7 +479,10 @@ export const getVisParams = async (
export const buildPipeline = async (
vis: Vis,
- params: { searchSource: SearchSource; timeRange?: any }
+ params: {
+ searchSource: SearchSourceContract;
+ timeRange?: any;
+ }
) => {
const { searchSource } = params;
const { indexPattern } = vis;
diff --git a/src/legacy/ui/public/visualize/loader/utils/query_geohash_bounds.ts b/src/legacy/ui/public/visualize/loader/utils/query_geohash_bounds.ts
index 36759551a1723..a9203415321fa 100644
--- a/src/legacy/ui/public/visualize/loader/utils/query_geohash_bounds.ts
+++ b/src/legacy/ui/public/visualize/loader/utils/query_geohash_bounds.ts
@@ -24,13 +24,13 @@ import { toastNotifications } from 'ui/notify';
import { AggConfig } from 'ui/vis';
import { timefilter } from 'ui/timefilter';
import { Vis } from '../../../vis';
+import { SearchSource, SearchSourceContract } from '../../../courier';
import { esFilters, Query } from '../../../../../../plugins/data/public';
-import { SearchSource } from '../../../courier';
interface QueryGeohashBoundsParams {
filters?: esFilters.Filter[];
query?: Query;
- searchSource?: SearchSource;
+ searchSource?: SearchSourceContract;
}
/**
diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js
index c0885cd5d3d13..763167c6b5ccf 100644
--- a/src/legacy/ui/ui_render/ui_render_mixin.js
+++ b/src/legacy/ui/ui_render/ui_render_mixin.js
@@ -234,7 +234,7 @@ export function uiRenderMixin(kbnServer, server, config) {
// Get the list of new platform plugins.
// Convert the Map into an array of objects so it is JSON serializable and order is preserved.
- const uiPluginConfigs = kbnServer.newPlatform.__internals.uiPluginConfigs;
+ const uiPluginConfigs = kbnServer.newPlatform.__internals.uiPlugins.browserConfigs;
const uiPlugins = await Promise.all([
...kbnServer.newPlatform.__internals.uiPlugins.public.entries(),
].map(async ([id, plugin]) => {
diff --git a/src/optimize/base_optimizer.js b/src/optimize/base_optimizer.js
index 28eb448d12d82..2eaf4c1d6e882 100644
--- a/src/optimize/base_optimizer.js
+++ b/src/optimize/base_optimizer.js
@@ -61,7 +61,7 @@ export default class BaseOptimizer {
constructor(opts) {
this.logWithMetadata = opts.logWithMetadata || (() => null);
this.uiBundles = opts.uiBundles;
- this.discoveredPlugins = opts.discoveredPlugins;
+ this.newPlatformPluginInfo = opts.newPlatformPluginInfo;
this.profile = opts.profile || false;
this.workers = opts.workers;
@@ -551,9 +551,9 @@ export default class BaseOptimizer {
_getDiscoveredPluginEntryPoints() {
// New platform plugin entry points
- return [...this.discoveredPlugins.entries()]
- .reduce((entryPoints, [pluginId, plugin]) => {
- entryPoints[`plugin/${pluginId}`] = `${plugin.path}/public`;
+ return [...this.newPlatformPluginInfo.entries()]
+ .reduce((entryPoints, [pluginId, pluginInfo]) => {
+ entryPoints[`plugin/${pluginId}`] = pluginInfo.entryPointPath;
return entryPoints;
}, {});
}
diff --git a/src/optimize/index.js b/src/optimize/index.js
index 9789e7abc2f9d..0960f9ecb10b6 100644
--- a/src/optimize/index.js
+++ b/src/optimize/index.js
@@ -66,7 +66,7 @@ export default async (kbnServer, server, config) => {
const optimizer = new FsOptimizer({
logWithMetadata: (tags, message, metadata) => server.logWithMetadata(tags, message, metadata),
uiBundles,
- discoveredPlugins: newPlatform.__internals.uiPlugins.internal,
+ newPlatformPluginInfo: newPlatform.__internals.uiPlugins.internal,
profile: config.get('optimize.profile'),
sourceMaps: config.get('optimize.sourceMaps'),
workers: config.get('optimize.workers'),
diff --git a/src/optimize/watch/optmzr_role.js b/src/optimize/watch/optmzr_role.js
index 16be840b3ca0e..9fbeceb578615 100644
--- a/src/optimize/watch/optmzr_role.js
+++ b/src/optimize/watch/optmzr_role.js
@@ -30,7 +30,7 @@ export default async (kbnServer, kibanaHapiServer, config) => {
const watchOptimizer = new WatchOptimizer({
logWithMetadata,
uiBundles: kbnServer.uiBundles,
- discoveredPlugins: kbnServer.newPlatform.__internals.uiPlugins.internal,
+ newPlatformPluginInfo: kbnServer.newPlatform.__internals.uiPlugins.internal,
profile: config.get('optimize.profile'),
sourceMaps: config.get('optimize.sourceMaps'),
workers: config.get('optimize.workers'),
diff --git a/src/plugins/dashboard_embeddable_container/public/actions/open_replace_panel_flyout.tsx b/src/plugins/dashboard_embeddable_container/public/actions/open_replace_panel_flyout.tsx
index b30733760bbdf..f15d538703e21 100644
--- a/src/plugins/dashboard_embeddable_container/public/actions/open_replace_panel_flyout.tsx
+++ b/src/plugins/dashboard_embeddable_container/public/actions/open_replace_panel_flyout.tsx
@@ -24,7 +24,7 @@ import {
IEmbeddable,
EmbeddableInput,
EmbeddableOutput,
- Start as EmbeddableStart,
+ IEmbeddableStart,
IContainer,
} from '../embeddable_plugin';
@@ -34,7 +34,7 @@ export async function openReplacePanelFlyout(options: {
savedObjectFinder: React.ComponentType;
notifications: CoreStart['notifications'];
panelToRemove: IEmbeddable;
- getEmbeddableFactories: EmbeddableStart['getEmbeddableFactories'];
+ getEmbeddableFactories: IEmbeddableStart['getEmbeddableFactories'];
}) {
const {
embeddable,
diff --git a/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_action.tsx b/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_action.tsx
index f6d2fcbcd57fd..78ce6bdc4c58f 100644
--- a/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_action.tsx
+++ b/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_action.tsx
@@ -19,7 +19,7 @@
import { i18n } from '@kbn/i18n';
import { CoreStart } from '../../../../core/public';
-import { IEmbeddable, ViewMode, Start as EmbeddableStart } from '../embeddable_plugin';
+import { IEmbeddable, ViewMode, IEmbeddableStart } from '../embeddable_plugin';
import { DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '../embeddable';
import { IAction, IncompatibleActionError } from '../ui_actions_plugin';
import { openReplacePanelFlyout } from './open_replace_panel_flyout';
@@ -43,7 +43,7 @@ export class ReplacePanelAction implements IAction {
private core: CoreStart,
private savedobjectfinder: React.ComponentType,
private notifications: CoreStart['notifications'],
- private getEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']
+ private getEmbeddableFactories: IEmbeddableStart['getEmbeddableFactories']
) {}
public getDisplayName({ embeddable }: ActionContext) {
diff --git a/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_flyout.tsx b/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_flyout.tsx
index 36efd0bcba676..36313353e3c33 100644
--- a/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_flyout.tsx
+++ b/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_flyout.tsx
@@ -20,15 +20,10 @@
import { i18n } from '@kbn/i18n';
import React from 'react';
import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui';
+import { GetEmbeddableFactories } from 'src/plugins/embeddable/public';
import { DashboardPanelState } from '../embeddable';
import { NotificationsStart, Toast } from '../../../../core/public';
-import {
- IContainer,
- IEmbeddable,
- EmbeddableInput,
- EmbeddableOutput,
- Start as EmbeddableStart,
-} from '../embeddable_plugin';
+import { IContainer, IEmbeddable, EmbeddableInput, EmbeddableOutput } from '../embeddable_plugin';
interface Props {
container: IContainer;
@@ -36,7 +31,7 @@ interface Props {
onClose: () => void;
notifications: NotificationsStart;
panelToRemove: IEmbeddable;
- getEmbeddableFactories: EmbeddableStart['getEmbeddableFactories'];
+ getEmbeddableFactories: GetEmbeddableFactories;
}
export class ReplacePanelFlyout extends React.Component {
diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx
index 6cefd11c912f1..684aa93779bc1 100644
--- a/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx
+++ b/src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_container.tsx
@@ -30,7 +30,7 @@ import {
ViewMode,
EmbeddableFactory,
IEmbeddable,
- Start as EmbeddableStartContract,
+ IEmbeddableStart,
} from '../embeddable_plugin';
import { DASHBOARD_CONTAINER_TYPE } from './dashboard_constants';
import { createPanelState } from './panel';
@@ -77,7 +77,7 @@ export interface DashboardContainerOptions {
application: CoreStart['application'];
overlays: CoreStart['overlays'];
notifications: CoreStart['notifications'];
- embeddable: EmbeddableStartContract;
+ embeddable: IEmbeddableStart;
inspector: InspectorStartContract;
SavedObjectFinder: React.ComponentType;
ExitFullScreenButton: React.ComponentType;
diff --git a/src/plugins/dashboard_embeddable_container/public/plugin.tsx b/src/plugins/dashboard_embeddable_container/public/plugin.tsx
index dbb5a06da9cd9..79cc9b6980545 100644
--- a/src/plugins/dashboard_embeddable_container/public/plugin.tsx
+++ b/src/plugins/dashboard_embeddable_container/public/plugin.tsx
@@ -22,7 +22,7 @@
import * as React from 'react';
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public';
import { IUiActionsSetup, IUiActionsStart } from '../../../plugins/ui_actions/public';
-import { CONTEXT_MENU_TRIGGER, Plugin as EmbeddablePlugin } from './embeddable_plugin';
+import { CONTEXT_MENU_TRIGGER, IEmbeddableSetup, IEmbeddableStart } from './embeddable_plugin';
import { ExpandPanelAction, ReplacePanelAction } from '.';
import { DashboardContainerFactory } from './embeddable/dashboard_container_factory';
import { Start as InspectorStartContract } from '../../../plugins/inspector/public';
@@ -34,12 +34,12 @@ import {
} from '../../../plugins/kibana_react/public';
interface SetupDependencies {
- embeddable: ReturnType;
+ embeddable: IEmbeddableSetup;
uiActions: IUiActionsSetup;
}
interface StartDependencies {
- embeddable: ReturnType;
+ embeddable: IEmbeddableStart;
inspector: InspectorStartContract;
uiActions: IUiActionsStart;
}
diff --git a/src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts b/src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts
index 6a5c7bdf8eea3..6e03c665290ae 100644
--- a/src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts
+++ b/src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts
@@ -49,4 +49,11 @@ describe('filterMatchesIndex', () => {
expect(filterMatchesIndex(filter, indexPattern)).toBe(false);
});
+
+ it('should return true if the filter has meta without a key', () => {
+ const filter = { meta: { index: 'foo' } } as Filter;
+ const indexPattern = { id: 'foo', fields: [{ name: 'bar' }] } as IIndexPattern;
+
+ expect(filterMatchesIndex(filter, indexPattern)).toBe(true);
+ });
});
diff --git a/src/plugins/data/common/es_query/es_query/filter_matches_index.ts b/src/plugins/data/common/es_query/es_query/filter_matches_index.ts
index 496aab3ea585f..9b68f5088c447 100644
--- a/src/plugins/data/common/es_query/es_query/filter_matches_index.ts
+++ b/src/plugins/data/common/es_query/es_query/filter_matches_index.ts
@@ -26,7 +26,7 @@ import { Filter } from '../filters';
* change.
*/
export function filterMatchesIndex(filter: Filter, indexPattern: IIndexPattern | null) {
- if (!filter.meta || !indexPattern) {
+ if (!filter.meta?.key || !indexPattern) {
return true;
}
return indexPattern.fields.some((field: IFieldType) => field.name === filter.meta.key);
diff --git a/src/plugins/data/common/es_query/filters/range_filter.ts b/src/plugins/data/common/es_query/filters/range_filter.ts
index fa07b3e611fa7..3d819bd145fa6 100644
--- a/src/plugins/data/common/es_query/filters/range_filter.ts
+++ b/src/plugins/data/common/es_query/filters/range_filter.ts
@@ -63,18 +63,22 @@ export type RangeFilterMeta = FilterMeta & {
formattedValue?: string;
};
-export type RangeFilter = Filter & {
- meta: RangeFilterMeta;
- script?: {
- script: {
- params: any;
- lang: string;
- source: any;
+export interface EsRangeFilter {
+ range: { [key: string]: RangeFilterParams };
+}
+
+export type RangeFilter = Filter &
+ EsRangeFilter & {
+ meta: RangeFilterMeta;
+ script?: {
+ script: {
+ params: any;
+ lang: string;
+ source: any;
+ };
};
+ match_all?: any;
};
- match_all?: any;
- range: { [key: string]: RangeFilterParams };
-};
export const isRangeFilter = (filter: any): filter is RangeFilter => filter && filter.range;
diff --git a/src/plugins/data/common/field_formats/utils/highlight/highlight_request.test.ts b/src/plugins/data/common/field_formats/utils/highlight/highlight_request.test.ts
index 5312f1be6c26c..8788d4b690aba 100644
--- a/src/plugins/data/common/field_formats/utils/highlight/highlight_request.test.ts
+++ b/src/plugins/data/common/field_formats/utils/highlight/highlight_request.test.ts
@@ -20,36 +20,19 @@
import { getHighlightRequest } from './highlight_request';
describe('getHighlightRequest', () => {
- let configMock: Record;
- const getConfig = (key: string) => configMock[key];
const queryStringQuery = { query_string: { query: 'foo' } };
- beforeEach(function() {
- configMock = {};
- configMock['doc_table:highlight'] = true;
- });
-
test('should be a function', () => {
expect(getHighlightRequest).toBeInstanceOf(Function);
});
test('should not modify the original query', () => {
- getHighlightRequest(queryStringQuery, getConfig);
+ getHighlightRequest(queryStringQuery, true);
expect(queryStringQuery.query_string).not.toHaveProperty('highlight');
});
test('should return undefined if highlighting is turned off', () => {
- configMock['doc_table:highlight'] = false;
- const request = getHighlightRequest(queryStringQuery, getConfig);
- expect(request).toBe(undefined);
- });
-
- test('should enable/disable highlighting if config is changed', () => {
- let request = getHighlightRequest(queryStringQuery, getConfig);
- expect(request).not.toBe(undefined);
-
- configMock['doc_table:highlight'] = false;
- request = getHighlightRequest(queryStringQuery, getConfig);
+ const request = getHighlightRequest(queryStringQuery, false);
expect(request).toBe(undefined);
});
});
diff --git a/src/plugins/data/common/field_formats/utils/highlight/highlight_request.ts b/src/plugins/data/common/field_formats/utils/highlight/highlight_request.ts
index 199a73e692e39..8012ab59c33ba 100644
--- a/src/plugins/data/common/field_formats/utils/highlight/highlight_request.ts
+++ b/src/plugins/data/common/field_formats/utils/highlight/highlight_request.ts
@@ -21,8 +21,8 @@ import { highlightTags } from './highlight_tags';
const FRAGMENT_SIZE = Math.pow(2, 31) - 1; // Max allowed value for fragment_size (limit of a java int)
-export function getHighlightRequest(query: any, getConfig: Function) {
- if (!getConfig('doc_table:highlight')) return;
+export function getHighlightRequest(query: any, shouldHighlight: boolean) {
+ if (!shouldHighlight) return;
return {
pre_tags: [highlightTags.pre],
diff --git a/src/plugins/data/public/index_patterns/lib/get_index_pattern_title.ts b/src/plugins/data/public/index_patterns/lib/get_index_pattern_title.ts
new file mode 100644
index 0000000000000..777a12c7e2884
--- /dev/null
+++ b/src/plugins/data/public/index_patterns/lib/get_index_pattern_title.ts
@@ -0,0 +1,33 @@
+/*
+ * 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 { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public';
+
+export async function getIndexPatternTitle(
+ client: SavedObjectsClientContract,
+ indexPatternId: string
+): Promise> {
+ const savedObject = (await client.get('index-pattern', indexPatternId)) as SimpleSavedObject;
+
+ if (savedObject.error) {
+ throw new Error(`Unable to get index-pattern title: ${savedObject.error.message}`);
+ }
+
+ return savedObject.attributes.title;
+}
diff --git a/src/legacy/ui/public/courier/search_strategy/index.d.ts b/src/plugins/data/public/index_patterns/lib/index.ts
similarity index 92%
rename from src/legacy/ui/public/courier/search_strategy/index.d.ts
rename to src/plugins/data/public/index_patterns/lib/index.ts
index dc98484655d00..d1c229513aa33 100644
--- a/src/legacy/ui/public/courier/search_strategy/index.d.ts
+++ b/src/plugins/data/public/index_patterns/lib/index.ts
@@ -17,4 +17,4 @@
* under the License.
*/
-export { SearchError, getSearchErrorType } from './search_error';
+export { getIndexPatternTitle } from './get_index_pattern_title';
diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts
index ff5c96c2d89ed..ceb57b4a3a564 100644
--- a/src/plugins/data/public/mocks.ts
+++ b/src/plugins/data/public/mocks.ts
@@ -66,6 +66,9 @@ const createStartContract = (): Start => {
search: { search: jest.fn() },
fieldFormats: fieldFormatsMock as FieldFormatsStart,
query: queryStartMock,
+ ui: {
+ IndexPatternSelect: jest.fn(),
+ },
};
return startContract;
};
diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts
index 3aa9cd9a0bcb4..d8c45b6786c0c 100644
--- a/src/plugins/data/public/plugin.ts
+++ b/src/plugins/data/public/plugin.ts
@@ -25,6 +25,7 @@ import { getSuggestionsProvider } from './suggestions_provider';
import { SearchService } from './search/search_service';
import { FieldFormatsService } from './field_formats_provider';
import { QueryService } from './query';
+import { createIndexPatternSelect } from './ui/index_pattern_select';
export class DataPublicPlugin implements Plugin {
private readonly autocomplete = new AutocompleteProviderRegister();
@@ -59,6 +60,9 @@ export class DataPublicPlugin implements Plugin {
describe('getTime', () => {
@@ -43,8 +43,8 @@ describe('get_time', () => {
],
} as any,
{ from: 'now-60y', to: 'now' }
- ) as Filter;
- expect(filter.range.date).toEqual({
+ );
+ expect(filter!.range.date).toEqual({
gte: '1940-02-01T00:00:00.000Z',
lte: '2000-02-01T00:00:00.000Z',
format: 'strict_date_optional_time',
diff --git a/src/plugins/data/public/query/timefilter/get_time.ts b/src/plugins/data/public/query/timefilter/get_time.ts
index 41ad1a49af0ff..d3fbc17734f81 100644
--- a/src/plugins/data/public/query/timefilter/get_time.ts
+++ b/src/plugins/data/public/query/timefilter/get_time.ts
@@ -21,22 +21,13 @@ import dateMath from '@elastic/datemath';
import { TimeRange } from '../../../common';
// TODO: remove this
-import { IndexPattern, Field } from '../../../../../legacy/core_plugins/data/public/index_patterns';
+import { IndexPattern, Field } from '../../../../../legacy/core_plugins/data/public';
+import { esFilters } from '../../../common';
interface CalculateBoundsOptions {
forceNow?: Date;
}
-interface RangeFilter {
- gte?: string | number;
- lte?: string | number;
- format: string;
-}
-
-export interface Filter {
- range: { [s: string]: RangeFilter };
-}
-
export function calculateBounds(timeRange: TimeRange, options: CalculateBoundsOptions = {}) {
return {
min: dateMath.parse(timeRange.from, { forceNow: options.forceNow }),
@@ -45,10 +36,10 @@ export function calculateBounds(timeRange: TimeRange, options: CalculateBoundsOp
}
export function getTime(
- indexPattern: IndexPattern,
+ indexPattern: IndexPattern | undefined,
timeRange: TimeRange,
forceNow?: Date
-): Filter | undefined {
+) {
if (!indexPattern) {
// in CI, we sometimes seem to fail here.
return;
@@ -66,17 +57,13 @@ export function getTime(
if (!bounds) {
return;
}
- const filter: Filter = {
- range: { [timefield.name]: { format: 'strict_date_optional_time' } },
- };
-
- if (bounds.min) {
- filter.range[timefield.name].gte = bounds.min.toISOString();
- }
-
- if (bounds.max) {
- filter.range[timefield.name].lte = bounds.max.toISOString();
- }
-
- return filter;
+ return esFilters.buildRangeFilter(
+ timefield,
+ {
+ ...(bounds.min && { gte: bounds.min.toISOString() }),
+ ...(bounds.max && { lte: bounds.max.toISOString() }),
+ format: 'strict_date_optional_time',
+ },
+ indexPattern
+ );
}
diff --git a/src/plugins/data/public/search/i_search.ts b/src/plugins/data/public/search/i_search.ts
index 0e256b960ffa3..a39ef3e3e7571 100644
--- a/src/plugins/data/public/search/i_search.ts
+++ b/src/plugins/data/public/search/i_search.ts
@@ -49,11 +49,11 @@ export interface IResponseTypesMap {
export type ISearchGeneric = (
request: IRequestTypesMap[T],
- options: ISearchOptions,
+ options?: ISearchOptions,
strategy?: T
) => Observable;
export type ISearch = (
request: IRequestTypesMap[T],
- options: ISearchOptions
+ options?: ISearchOptions
) => Observable;
diff --git a/src/plugins/data/public/search/sync_search_strategy.ts b/src/plugins/data/public/search/sync_search_strategy.ts
index c412bbb3b104a..3885a97a98571 100644
--- a/src/plugins/data/public/search/sync_search_strategy.ts
+++ b/src/plugins/data/public/search/sync_search_strategy.ts
@@ -34,7 +34,7 @@ export const syncSearchStrategyProvider: TSearchStrategyProvider {
const search: ISearch = (
request: ISyncSearchRequest,
- options: ISearchOptions
+ options: ISearchOptions = {}
) => {
const response: Promise = context.core.http.fetch(
`/internal/search/${request.serverStrategy}`,
diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts
index a59e7f3de3588..c0c96372f9f59 100644
--- a/src/plugins/data/public/types.ts
+++ b/src/plugins/data/public/types.ts
@@ -24,6 +24,7 @@ import { FieldFormatsSetup, FieldFormatsStart } from './field_formats_provider';
import { ISearchSetup, ISearchStart } from './search';
import { IGetSuggestions } from './suggestions_provider/types';
import { QuerySetup, QueryStart } from './query';
+import { IndexPatternSelectProps } from './ui/index_pattern_select';
export interface DataPublicPluginSetup {
autocomplete: AutocompletePublicPluginSetup;
@@ -38,6 +39,9 @@ export interface DataPublicPluginStart {
search: ISearchStart;
fieldFormats: FieldFormatsStart;
query: QueryStart;
+ ui: {
+ IndexPatternSelect: React.ComponentType;
+ };
}
export * from './autocomplete_provider/types';
diff --git a/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx b/src/plugins/data/public/ui/apply_filters/apply_filter_popover_content.tsx
similarity index 94%
rename from src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx
rename to src/plugins/data/public/ui/apply_filters/apply_filter_popover_content.tsx
index 954cbca8f054b..affbb8acecb20 100644
--- a/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx
+++ b/src/plugins/data/public/ui/apply_filters/apply_filter_popover_content.tsx
@@ -30,17 +30,12 @@ import {
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Component } from 'react';
-import { IndexPattern } from '../../index_patterns';
-import {
- mapAndFlattenFilters,
- esFilters,
- utils,
- FilterLabel,
-} from '../../../../../../plugins/data/public';
+import { mapAndFlattenFilters, esFilters, utils, IIndexPattern } from '../..';
+import { FilterLabel } from '../filter_bar';
interface Props {
filters: esFilters.Filter[];
- indexPatterns: IndexPattern[];
+ indexPatterns: IIndexPattern[];
onCancel: () => void;
onSubmit: (filters: esFilters.Filter[]) => void;
}
diff --git a/src/plugins/data/public/ui/apply_filters/apply_filters_popover.tsx b/src/plugins/data/public/ui/apply_filters/apply_filters_popover.tsx
new file mode 100644
index 0000000000000..71a042adffa39
--- /dev/null
+++ b/src/plugins/data/public/ui/apply_filters/apply_filters_popover.tsx
@@ -0,0 +1,41 @@
+/*
+ * 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 { ApplyFiltersPopoverContent } from './apply_filter_popover_content';
+import { IIndexPattern, esFilters } from '../..';
+
+type CancelFnType = () => void;
+type SubmitFnType = (filters: esFilters.Filter[]) => void;
+
+export const applyFiltersPopover = (
+ filters: esFilters.Filter[],
+ indexPatterns: IIndexPattern[],
+ onCancel: CancelFnType,
+ onSubmit: SubmitFnType
+) => {
+ return (
+
+ );
+};
diff --git a/src/legacy/core_plugins/data/public/filter/apply_filters/index.ts b/src/plugins/data/public/ui/apply_filters/index.ts
similarity index 92%
rename from src/legacy/core_plugins/data/public/filter/apply_filters/index.ts
rename to src/plugins/data/public/ui/apply_filters/index.ts
index 6b64230ed6a0c..93c1245e1ffb0 100644
--- a/src/legacy/core_plugins/data/public/filter/apply_filters/index.ts
+++ b/src/plugins/data/public/ui/apply_filters/index.ts
@@ -17,4 +17,4 @@
* under the License.
*/
-export { ApplyFiltersPopover } from './apply_filters_popover';
+export { applyFiltersPopover } from './apply_filters_popover';
diff --git a/src/plugins/data/public/ui/index.ts b/src/plugins/data/public/ui/index.ts
index d0aaf2f6aac1c..cb7c92b00ea3a 100644
--- a/src/plugins/data/public/ui/index.ts
+++ b/src/plugins/data/public/ui/index.ts
@@ -17,4 +17,6 @@
* under the License.
*/
-export * from './filter_bar';
+export { IndexPatternSelect } from './index_pattern_select';
+export { FilterBar } from './filter_bar';
+export { applyFiltersPopover } from './apply_filters';
diff --git a/src/legacy/core_plugins/data/public/index_patterns/components/index.ts b/src/plugins/data/public/ui/index_pattern_select/index.ts
similarity index 100%
rename from src/legacy/core_plugins/data/public/index_patterns/components/index.ts
rename to src/plugins/data/public/ui/index_pattern_select/index.ts
diff --git a/src/legacy/core_plugins/data/public/index_patterns/components/index_pattern_select.tsx b/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx
similarity index 97%
rename from src/legacy/core_plugins/data/public/index_patterns/components/index_pattern_select.tsx
rename to src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx
index 77692d7bcaa0d..f868e4b1f7504 100644
--- a/src/legacy/core_plugins/data/public/index_patterns/components/index_pattern_select.tsx
+++ b/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx
@@ -21,10 +21,10 @@ import _ from 'lodash';
import React, { Component } from 'react';
import { EuiComboBox } from '@elastic/eui';
-import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../../core/public';
-import { getIndexPatternTitle } from '../utils';
+import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public';
+import { getIndexPatternTitle } from '../../index_patterns/lib';
-interface IndexPatternSelectProps {
+export interface IndexPatternSelectProps {
onChange: (opt: any) => void;
indexPatternId: string;
placeholder: string;
diff --git a/src/plugins/data/server/search/create_api.test.ts b/src/plugins/data/server/search/create_api.test.ts
index 32570a05031f6..cc13269e1aa21 100644
--- a/src/plugins/data/server/search/create_api.test.ts
+++ b/src/plugins/data/server/search/create_api.test.ts
@@ -55,7 +55,7 @@ describe('createApi', () => {
});
it('should throw if no provider is found for the given name', () => {
- expect(api.search({}, 'noneByThisName')).rejects.toThrowErrorMatchingInlineSnapshot(
+ expect(api.search({}, {}, 'noneByThisName')).rejects.toThrowErrorMatchingInlineSnapshot(
`"No strategy found for noneByThisName"`
);
});
diff --git a/src/plugins/data/server/search/create_api.ts b/src/plugins/data/server/search/create_api.ts
index 4c13dd9e1137c..2a874869526d7 100644
--- a/src/plugins/data/server/search/create_api.ts
+++ b/src/plugins/data/server/search/create_api.ts
@@ -30,7 +30,7 @@ export function createApi({
caller: APICaller;
}) {
const api: IRouteHandlerSearchContext = {
- search: async (request, strategyName) => {
+ search: async (request, options, strategyName) => {
const name = strategyName ? strategyName : DEFAULT_SEARCH_STRATEGY;
const strategyProvider = searchStrategies[name];
if (!strategyProvider) {
diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts
index 619a28df839bd..7b725a47aa13b 100644
--- a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts
+++ b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts
@@ -66,7 +66,7 @@ describe('ES search strategy', () => {
expect(spy).toBeCalled();
});
- it('calls the API caller with the params', () => {
+ it('calls the API caller with the params with defaults', () => {
const params = { index: 'logstash-*' };
const esSearch = esSearchStrategyProvider(
{
@@ -80,7 +80,31 @@ describe('ES search strategy', () => {
expect(mockApiCaller).toBeCalled();
expect(mockApiCaller.mock.calls[0][0]).toBe('search');
- expect(mockApiCaller.mock.calls[0][1]).toEqual(params);
+ expect(mockApiCaller.mock.calls[0][1]).toEqual({
+ ...params,
+ ignoreUnavailable: true,
+ restTotalHitsAsInt: true,
+ });
+ });
+
+ it('calls the API caller with overridden defaults', () => {
+ const params = { index: 'logstash-*', ignoreUnavailable: false };
+ const esSearch = esSearchStrategyProvider(
+ {
+ core: mockCoreSetup,
+ },
+ mockApiCaller,
+ mockSearch
+ );
+
+ esSearch.search({ params });
+
+ expect(mockApiCaller).toBeCalled();
+ expect(mockApiCaller.mock.calls[0][0]).toBe('search');
+ expect(mockApiCaller.mock.calls[0][1]).toEqual({
+ ...params,
+ restTotalHitsAsInt: true,
+ });
});
it('returns total, loaded, and raw response', async () => {
diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts
index 31f4fc15a0989..c5fc1d9d3a11c 100644
--- a/src/plugins/data/server/search/es_search/es_search_strategy.ts
+++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts
@@ -18,7 +18,7 @@
*/
import { APICaller } from 'kibana/server';
import { SearchResponse } from 'elasticsearch';
-import { IEsSearchRequest, ES_SEARCH_STRATEGY } from '../../../common/search';
+import { ES_SEARCH_STRATEGY } from '../../../common/search';
import { ISearchStrategy, TSearchStrategyProvider } from '../i_search_strategy';
import { ISearchContext } from '..';
@@ -27,16 +27,17 @@ export const esSearchStrategyProvider: TSearchStrategyProvider => {
return {
- search: async (request: IEsSearchRequest) => {
+ search: async (request, options) => {
+ const params = {
+ ignoreUnavailable: true, // Don't fail if the index/indices don't exist
+ restTotalHitsAsInt: true, // Get the number of hits as an int rather than a range
+ ...request.params,
+ };
if (request.debug) {
// eslint-disable-next-line
- console.log(JSON.stringify(request, null, 2));
+ console.log(JSON.stringify(params, null, 2));
}
- const esSearchResponse = (await caller('search', {
- ...request.params,
- // TODO: could do something like this here?
- // ...getCurrentSearchParams(context),
- })) as SearchResponse;
+ const esSearchResponse = (await caller('search', params, options)) as SearchResponse;
// The above query will either complete or timeout and throw an error.
// There is no progress indication on this api.
diff --git a/src/plugins/data/server/search/i_search.ts b/src/plugins/data/server/search/i_search.ts
index fabcb98ceea72..0a35734574153 100644
--- a/src/plugins/data/server/search/i_search.ts
+++ b/src/plugins/data/server/search/i_search.ts
@@ -22,6 +22,10 @@ import { TStrategyTypes } from './strategy_types';
import { ES_SEARCH_STRATEGY, IEsSearchResponse } from '../../common/search/es_search';
import { IEsSearchRequest } from './es_search';
+export interface ISearchOptions {
+ signal?: AbortSignal;
+}
+
export interface IRequestTypesMap {
[ES_SEARCH_STRATEGY]: IEsSearchRequest;
[key: string]: IKibanaSearchRequest;
@@ -34,9 +38,11 @@ export interface IResponseTypesMap {
export type ISearchGeneric = (
request: IRequestTypesMap[T],
+ options?: ISearchOptions,
strategy?: T
) => Promise;
export type ISearch = (
- request: IRequestTypesMap[T]
+ request: IRequestTypesMap[T],
+ options?: ISearchOptions
) => Promise;
diff --git a/src/plugins/data/server/search/routes.test.ts b/src/plugins/data/server/search/routes.test.ts
index ebdcf48f608b9..a2394d88f3931 100644
--- a/src/plugins/data/server/search/routes.test.ts
+++ b/src/plugins/data/server/search/routes.test.ts
@@ -60,7 +60,7 @@ describe('Search service', () => {
expect(mockSearch).toBeCalled();
expect(mockSearch.mock.calls[0][0]).toStrictEqual(mockBody);
- expect(mockSearch.mock.calls[0][1]).toBe(mockParams.strategy);
+ expect(mockSearch.mock.calls[0][2]).toBe(mockParams.strategy);
expect(mockResponse.ok).toBeCalled();
expect(mockResponse.ok.mock.calls[0][0]).toEqual({ body: 'yay' });
});
@@ -92,7 +92,7 @@ describe('Search service', () => {
expect(mockSearch).toBeCalled();
expect(mockSearch.mock.calls[0][0]).toStrictEqual(mockBody);
- expect(mockSearch.mock.calls[0][1]).toBe(mockParams.strategy);
+ expect(mockSearch.mock.calls[0][2]).toBe(mockParams.strategy);
expect(mockResponse.internalError).toBeCalled();
expect(mockResponse.internalError.mock.calls[0][0]).toEqual({ body: 'oh no' });
});
diff --git a/src/plugins/data/server/search/routes.ts b/src/plugins/data/server/search/routes.ts
index 6cb6c28c76014..eaa72548e08ee 100644
--- a/src/plugins/data/server/search/routes.ts
+++ b/src/plugins/data/server/search/routes.ts
@@ -36,7 +36,7 @@ export function registerSearchRoute(router: IRouter): void {
const searchRequest = request.body;
const strategy = request.params.strategy;
try {
- const response = await context.search!.search(searchRequest, strategy);
+ const response = await context.search!.search(searchRequest, {}, strategy);
return res.ok({ body: response });
} catch (err) {
return res.internalError({ body: err });
diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts
index 4edb51300dfaf..3409a72326121 100644
--- a/src/plugins/data/server/search/search_service.ts
+++ b/src/plugins/data/server/search/search_service.ts
@@ -77,7 +77,7 @@ export class SearchService implements Plugin {
caller,
searchStrategies: this.searchStrategies,
});
- return searchAPI.search(request, strategyName);
+ return searchAPI.search(request, {}, strategyName);
},
},
};
diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts
index 33855b07df7a1..ea2bd910b0624 100644
--- a/src/plugins/embeddable/public/index.ts
+++ b/src/plugins/embeddable/public/index.ts
@@ -61,5 +61,4 @@ export function plugin(initializerContext: PluginInitializerContext) {
return new EmbeddablePublicPlugin(initializerContext);
}
-export { EmbeddablePublicPlugin as Plugin };
-export * from './plugin';
+export { IEmbeddableSetup, IEmbeddableStart } from './plugin';
diff --git a/src/plugins/embeddable/public/mocks.ts b/src/plugins/embeddable/public/mocks.ts
index ef1517bb7f1d5..fd299bc626fb9 100644
--- a/src/plugins/embeddable/public/mocks.ts
+++ b/src/plugins/embeddable/public/mocks.ts
@@ -17,14 +17,15 @@
* under the License.
*/
-import { Plugin } from '.';
+import { IEmbeddableStart, IEmbeddableSetup } from '.';
+import { EmbeddablePublicPlugin } from './plugin';
import { coreMock } from '../../../core/public/mocks';
// eslint-disable-next-line
import { uiActionsPluginMock } from '../../ui_actions/public/mocks';
-export type Setup = jest.Mocked>;
-export type Start = jest.Mocked>;
+export type Setup = jest.Mocked;
+export type Start = jest.Mocked;
const createSetupContract = (): Setup => {
const setupContract: Setup = {
@@ -43,7 +44,7 @@ const createStartContract = (): Start => {
};
const createInstance = () => {
- const plugin = new Plugin({} as any);
+ const plugin = new EmbeddablePublicPlugin({} as any);
const setup = plugin.setup(coreMock.createSetup(), {
uiActions: uiActionsPluginMock.createSetupContract(),
});
diff --git a/src/plugins/embeddable/public/plugin.ts b/src/plugins/embeddable/public/plugin.ts
index 458c8bfeb8762..df1f4e5080031 100644
--- a/src/plugins/embeddable/public/plugin.ts
+++ b/src/plugins/embeddable/public/plugin.ts
@@ -27,7 +27,13 @@ export interface IEmbeddableSetupDependencies {
uiActions: IUiActionsSetup;
}
-export class EmbeddablePublicPlugin implements Plugin {
+export interface IEmbeddableSetup {
+ registerEmbeddableFactory: EmbeddableApi['registerEmbeddableFactory'];
+}
+
+export type IEmbeddableStart = EmbeddableApi;
+
+export class EmbeddablePublicPlugin implements Plugin {
private readonly embeddableFactories: EmbeddableFactoryRegistry = new Map();
private api!: EmbeddableApi;
@@ -52,6 +58,3 @@ export class EmbeddablePublicPlugin implements Plugin {
public stop() {}
}
-
-export type Setup = ReturnType;
-export type Start = ReturnType;
diff --git a/src/plugins/embeddable/public/tests/test_plugin.ts b/src/plugins/embeddable/public/tests/test_plugin.ts
index 5b50bddefcdb7..6d1e15137480a 100644
--- a/src/plugins/embeddable/public/tests/test_plugin.ts
+++ b/src/plugins/embeddable/public/tests/test_plugin.ts
@@ -21,14 +21,14 @@ import { CoreSetup, CoreStart } from 'src/core/public';
// eslint-disable-next-line
import { uiActionsTestPlugin } from 'src/plugins/ui_actions/public/tests';
import { IUiActionsApi } from 'src/plugins/ui_actions/public';
-import { EmbeddablePublicPlugin } from '../plugin';
+import { EmbeddablePublicPlugin, IEmbeddableSetup, IEmbeddableStart } from '../plugin';
export interface TestPluginReturn {
plugin: EmbeddablePublicPlugin;
coreSetup: CoreSetup;
coreStart: CoreStart;
- setup: ReturnType;
- doStart: (anotherCoreStart?: CoreStart) => ReturnType;
+ setup: IEmbeddableSetup;
+ doStart: (anotherCoreStart?: CoreStart) => IEmbeddableStart;
uiActions: IUiActionsApi;
}
diff --git a/src/plugins/expressions/public/index.ts b/src/plugins/expressions/public/index.ts
index a14aaae98fc34..6dc88fd23f29a 100644
--- a/src/plugins/expressions/public/index.ts
+++ b/src/plugins/expressions/public/index.ts
@@ -20,10 +20,6 @@
import { PluginInitializerContext } from '../../../core/public';
import { ExpressionsPublicPlugin } from './plugin';
-export function plugin(initializerContext: PluginInitializerContext) {
- return new ExpressionsPublicPlugin(initializerContext);
-}
-
export { ExpressionsPublicPlugin as Plugin };
export * from './plugin';
@@ -31,3 +27,10 @@ export * from './types';
export * from '../common';
export { interpreterProvider, ExpressionInterpret } from './interpreter_provider';
export { ExpressionRenderer, ExpressionRendererProps } from './expression_renderer';
+export { ExpressionDataHandler } from './execute';
+
+export { RenderResult, ExpressionRenderHandler } from './render';
+
+export function plugin(initializerContext: PluginInitializerContext) {
+ return new ExpressionsPublicPlugin(initializerContext);
+}
diff --git a/src/plugins/expressions/public/render.ts b/src/plugins/expressions/public/render.ts
index 364d5f587bb6f..3c7008806e779 100644
--- a/src/plugins/expressions/public/render.ts
+++ b/src/plugins/expressions/public/render.ts
@@ -30,15 +30,17 @@ interface RenderError {
export type IExpressionRendererExtraHandlers = Record;
+export type RenderResult = RenderId | RenderError;
+
export class ExpressionRenderHandler {
- render$: Observable;
+ render$: Observable;
update$: Observable;
events$: Observable;
private element: HTMLElement;
private destroyFn?: any;
private renderCount: number = 0;
- private renderSubject: Rx.BehaviorSubject;
+ private renderSubject: Rx.BehaviorSubject;
private eventsSubject: Rx.Subject;
private updateSubject: Rx.Subject;
private handlers: IInterpreterRenderHandlers;
@@ -49,11 +51,11 @@ export class ExpressionRenderHandler {
this.eventsSubject = new Rx.Subject();
this.events$ = this.eventsSubject.asObservable().pipe(share());
- this.renderSubject = new Rx.BehaviorSubject(null as RenderId | RenderError | null);
+ this.renderSubject = new Rx.BehaviorSubject(null as RenderResult | null);
this.render$ = this.renderSubject.asObservable().pipe(
share(),
filter(_ => _ !== null)
- ) as Observable;
+ ) as Observable;
this.updateSubject = new Rx.Subject();
this.update$ = this.updateSubject.asObservable().pipe(share());
diff --git a/src/plugins/status_page/kibana.json b/src/plugins/status_page/kibana.json
new file mode 100644
index 0000000000000..edebf8cb12239
--- /dev/null
+++ b/src/plugins/status_page/kibana.json
@@ -0,0 +1,6 @@
+{
+ "id": "status_page",
+ "version": "kibana",
+ "server": false,
+ "ui": true
+}
diff --git a/src/plugins/status_page/public/index.ts b/src/plugins/status_page/public/index.ts
new file mode 100644
index 0000000000000..db1f05cac076f
--- /dev/null
+++ b/src/plugins/status_page/public/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { PluginInitializer } from 'kibana/public';
+import { StatusPagePlugin, StatusPagePluginSetup, StatusPagePluginStart } from './plugin';
+
+export const plugin: PluginInitializer = () =>
+ new StatusPagePlugin();
diff --git a/src/plugins/status_page/public/plugin.ts b/src/plugins/status_page/public/plugin.ts
new file mode 100644
index 0000000000000..d072fd4a67c30
--- /dev/null
+++ b/src/plugins/status_page/public/plugin.ts
@@ -0,0 +1,39 @@
+/*
+ * 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 { Plugin, CoreSetup } from 'kibana/public';
+
+export class StatusPagePlugin implements Plugin {
+ public setup(core: CoreSetup) {
+ const isStatusPageAnonymous = core.injectedMetadata.getInjectedVar(
+ 'isStatusPageAnonymous'
+ ) as boolean;
+
+ if (isStatusPageAnonymous) {
+ core.http.anonymousPaths.register('/status');
+ }
+ }
+
+ public start() {}
+
+ public stop() {}
+}
+
+export type StatusPagePluginSetup = ReturnType;
+export type StatusPagePluginStart = ReturnType;
diff --git a/tasks/config/karma.js b/tasks/config/karma.js
index 16947a97a3d14..25723677390bd 100644
--- a/tasks/config/karma.js
+++ b/tasks/config/karma.js
@@ -34,6 +34,19 @@ module.exports = function (grunt) {
return 'Chrome';
}
+ function pickReporters() {
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ if (process.env.CI && process.env.DISABLE_JUNIT_REPORTER) {
+ return ['dots'];
+ }
+
+ if (process.env.CI) {
+ return ['dots', 'junit'];
+ }
+
+ return ['progress'];
+ }
+
const config = {
options: {
// base path that will be used to resolve all patterns (eg. files, exclude)
@@ -63,14 +76,13 @@ module.exports = function (grunt) {
},
},
- // available reporters: https://npmjs.org/browse/keyword/karma-reporter
- reporters: process.env.CI ? ['dots', 'junit'] : ['progress'],
+ reporters: pickReporters(),
junitReporter: {
outputFile: resolve(ROOT, 'target/junit', process.env.JOB || '.', `TEST-${process.env.JOB ? process.env.JOB + '-' : ''}karma.xml`),
useBrowserName: false,
- nameFormatter: (browser, result) => [...result.suite, result.description].join(' '),
- classNameFormatter: (browser, result) => {
+ nameFormatter: (_, result) => [...result.suite, result.description].join(' '),
+ classNameFormatter: (_, result) => {
const rootSuite = result.suite[0] || result.description;
return `Browser Unit Tests.${rootSuite.replace(/\./g, '·')}`;
},
diff --git a/tasks/config/run.js b/tasks/config/run.js
index ea5a4b01dc8a5..e4071c8b7d0ab 100644
--- a/tasks/config/run.js
+++ b/tasks/config/run.js
@@ -254,7 +254,7 @@ module.exports = function (grunt) {
cmd: NODE,
args: [
'scripts/functional_tests',
- '--config', 'test/interpreter_functional/config.js',
+ '--config', 'test/interpreter_functional/config.ts',
'--bail',
'--debug',
'--kibana-install-dir', KIBANA_INSTALL_DIR,
diff --git a/test/functional/services/browser.ts b/test/functional/services/browser.ts
index a8ce4270d4205..ab686f4d5ffec 100644
--- a/test/functional/services/browser.ts
+++ b/test/functional/services/browser.ts
@@ -470,7 +470,10 @@ export async function BrowserProvider({ getService }: FtrProviderContext) {
);
}
- public async executeAsync(fn: string | ((...args: any[]) => R), ...args: any[]): Promise {
+ public async executeAsync(
+ fn: string | ((...args: any[]) => Promise),
+ ...args: any[]
+ ): Promise {
return await driver.executeAsyncScript(
fn,
...cloneDeep(args, arg => {
diff --git a/test/interpreter_functional/README.md b/test/interpreter_functional/README.md
index 336bfe3405a01..73df0ce4c9f04 100644
--- a/test/interpreter_functional/README.md
+++ b/test/interpreter_functional/README.md
@@ -3,7 +3,7 @@
This folder contains interpreter functional tests.
Add new test suites into the `test_suites` folder and reference them from the
-`config.js` file. These test suites work the same as regular functional test.
+`config.ts` file. These test suites work the same as regular functional test.
## Run the test
@@ -11,17 +11,17 @@ To run these tests during development you can use the following commands:
```
# Start the test server (can continue running)
-node scripts/functional_tests_server.js --config test/interpreter_functional/config.js
+node scripts/functional_tests_server.js --config test/interpreter_functional/config.ts
# Start a test run
-node scripts/functional_test_runner.js --config test/interpreter_functional/config.js
+node scripts/functional_test_runner.js --config test/interpreter_functional/config.ts
```
# Writing tests
-Look into test_suites/run_pipeline/basic.js for examples
+Look into test_suites/run_pipeline/basic.ts for examples
to update baseline screenshots and snapshots run with:
```
-node scripts/functional_test_runner.js --config test/interpreter_functional/config.js --updateBaselines
+node scripts/functional_test_runner.js --config test/interpreter_functional/config.ts --updateBaselines
```
\ No newline at end of file
diff --git a/test/interpreter_functional/config.js b/test/interpreter_functional/config.ts
similarity index 76%
rename from test/interpreter_functional/config.js
rename to test/interpreter_functional/config.ts
index e8700262e273a..0fe7df4d50715 100644
--- a/test/interpreter_functional/config.js
+++ b/test/interpreter_functional/config.ts
@@ -19,25 +19,26 @@
import path from 'path';
import fs from 'fs';
+import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
-export default async function ({ readConfigFile }) {
+export default async function({ readConfigFile }: FtrConfigProviderContext) {
const functionalConfig = await readConfigFile(require.resolve('../functional/config'));
// Find all folders in ./plugins since we treat all them as plugin folder
const allFiles = fs.readdirSync(path.resolve(__dirname, 'plugins'));
- const plugins = allFiles.filter(file => fs.statSync(path.resolve(__dirname, 'plugins', file)).isDirectory());
+ const plugins = allFiles.filter(file =>
+ fs.statSync(path.resolve(__dirname, 'plugins', file)).isDirectory()
+ );
return {
- testFiles: [
- require.resolve('./test_suites/run_pipeline'),
- ],
+ testFiles: [require.resolve('./test_suites/run_pipeline')],
services: functionalConfig.get('services'),
pageObjects: functionalConfig.get('pageObjects'),
servers: functionalConfig.get('servers'),
esTestCluster: functionalConfig.get('esTestCluster'),
apps: functionalConfig.get('apps'),
esArchiver: {
- directory: path.resolve(__dirname, '../es_archives')
+ directory: path.resolve(__dirname, '../es_archives'),
},
snapshots: {
directory: path.resolve(__dirname, 'snapshots'),
@@ -49,7 +50,9 @@ export default async function ({ readConfigFile }) {
...functionalConfig.get('kbnTestServer'),
serverArgs: [
...functionalConfig.get('kbnTestServer.serverArgs'),
- ...plugins.map(pluginDir => `--plugin-path=${path.resolve(__dirname, 'plugins', pluginDir)}`),
+ ...plugins.map(
+ pluginDir => `--plugin-path=${path.resolve(__dirname, 'plugins', pluginDir)}`
+ ),
],
},
};
diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/index.js b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/index.ts
similarity index 68%
rename from test/interpreter_functional/plugins/kbn_tp_run_pipeline/index.js
rename to test/interpreter_functional/plugins/kbn_tp_run_pipeline/index.ts
index 95d6a555ebcf0..1d5564ec06e4e 100644
--- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/index.js
+++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/index.ts
@@ -16,24 +16,34 @@
* specific language governing permissions and limitations
* under the License.
*/
+import { Legacy } from 'kibana';
+import {
+ ArrayOrItem,
+ LegacyPluginApi,
+ LegacyPluginSpec,
+ LegacyPluginOptions,
+} from 'src/legacy/plugin_discovery/types';
-export default function (kibana) {
- return new kibana.Plugin({
+// eslint-disable-next-line import/no-default-export
+export default function(kibana: LegacyPluginApi): ArrayOrItem {
+ const pluginSpec: Partial = {
+ id: 'kbn_tp_run_pipeline',
uiExports: {
app: {
title: 'Run Pipeline',
description: 'This is a sample plugin to test running pipeline expressions',
- main: 'plugins/kbn_tp_run_pipeline/app',
- }
+ main: 'plugins/kbn_tp_run_pipeline/legacy',
+ },
},
- init(server) {
+ init(server: Legacy.Server) {
// The following lines copy over some configuration variables from Kibana
// to this plugin. This will be needed when embedding visualizations, so that e.g.
// region map is able to get its configuration.
server.injectUiAppVars('kbn_tp_run_pipeline', async () => {
- return await server.getInjectedUiAppVars('kibana');
+ return server.getInjectedUiAppVars('kibana');
});
- }
- });
+ },
+ };
+ return new kibana.Plugin(pluginSpec);
}
diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js
deleted file mode 100644
index e9ab2a4169915..0000000000000
--- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js
+++ /dev/null
@@ -1,76 +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 { render, unmountComponentAtNode } from 'react-dom';
-
-import { uiModules } from 'ui/modules';
-import chrome from 'ui/chrome';
-
-import { RequestAdapter, DataAdapter } from 'ui/inspector/adapters';
-import { registries } from 'plugins/interpreter/registries';
-import { npStart } from 'ui/new_platform';
-
-// This is required so some default styles and required scripts/Angular modules are loaded,
-// or the timezone setting is correctly applied.
-import 'ui/autoload/all';
-
-// These are all the required uiExports you need to import in case you want to embed visualizations.
-import 'uiExports/visTypes';
-import 'uiExports/visResponseHandlers';
-import 'uiExports/visRequestHandlers';
-import 'uiExports/visEditorTypes';
-import 'uiExports/visualize';
-import 'uiExports/savedObjectTypes';
-import 'uiExports/search';
-import 'uiExports/interpreter';
-
-import { Main } from './components/main';
-
-const app = uiModules.get('apps/kbnRunPipelinePlugin', ['kibana']);
-
-app.config($locationProvider => {
- $locationProvider.html5Mode({
- enabled: false,
- requireBase: false,
- rewriteLinks: false,
- });
-});
-app.config(stateManagementConfigProvider =>
- stateManagementConfigProvider.disable()
-);
-
-function RootController($scope, $element) {
- const domNode = $element[0];
-
- // render react to DOM
- render( , domNode);
-
- // unmount react on controller destroy
- $scope.$on('$destroy', () => {
- unmountComponentAtNode(domNode);
- });
-}
-
-chrome.setRootController('kbnRunPipelinePlugin', RootController);
diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/components/main.js b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/components/main.js
deleted file mode 100644
index 3e19d3a4d78ec..0000000000000
--- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/components/main.js
+++ /dev/null
@@ -1,91 +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 {
- EuiPage,
- EuiPageBody,
- EuiPageContent,
- EuiPageContentHeader,
-} from '@elastic/eui';
-import { first } from 'rxjs/operators';
-
-class Main extends React.Component {
- chartDiv = React.createRef();
-
- constructor(props) {
- super(props);
-
- this.state = {
- expression: '',
- };
-
- window.runPipeline = async (expression, context = {}, initialContext = {}) => {
- this.setState({ expression });
- const adapters = {
- requests: new props.RequestAdapter(),
- data: new props.DataAdapter(),
- };
- return await props.expressions.execute(expression, {
- inspectorAdapters: adapters,
- context,
- searchContext: initialContext,
- }).getData();
- };
-
- let lastRenderHandler;
- window.renderPipelineResponse = async (context = {}) => {
- if (lastRenderHandler) {
- lastRenderHandler.destroy();
- }
-
- lastRenderHandler = props.expressions.render(this.chartDiv, context);
- const renderResult = await lastRenderHandler.render$.pipe(first()).toPromise();
-
- if (typeof renderResult === 'object' && renderResult.type === 'error') {
- return this.setState({ expression: 'Render error!\n\n' + JSON.stringify(renderResult.error) });
- }
- };
- }
-
-
- render() {
- const pStyle = {
- display: 'flex',
- width: '100%',
- height: '300px'
- };
-
- return (
-
-
-
-
- runPipeline tests are running ...
-
- this.chartDiv = ref} style={pStyle}/>
-
{this.state.expression}
-
-
-
- );
- }
-}
-
-export { Main };
diff --git a/src/legacy/ui/public/courier/search_source/index.js b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/index.ts
similarity index 94%
rename from src/legacy/ui/public/courier/search_source/index.js
rename to test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/index.ts
index dcae7b3d2ff05..c4cc7175d6157 100644
--- a/src/legacy/ui/public/courier/search_source/index.js
+++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/index.ts
@@ -17,4 +17,4 @@
* under the License.
*/
-export { SearchSource } from './search_source';
+export * from './np_ready';
diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/legacy.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/legacy.ts
new file mode 100644
index 0000000000000..39ce2b3077c96
--- /dev/null
+++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/legacy.ts
@@ -0,0 +1,41 @@
+/*
+ * 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 { PluginInitializerContext } from 'src/core/public';
+import { npSetup, npStart } from 'ui/new_platform';
+
+import { plugin } from './np_ready';
+
+// This is required so some default styles and required scripts/Angular modules are loaded,
+// or the timezone setting is correctly applied.
+import 'ui/autoload/all';
+// Used to run esaggs queries
+import 'uiExports/fieldFormats';
+import 'uiExports/search';
+import 'uiExports/visRequestHandlers';
+import 'uiExports/visResponseHandlers';
+// Used for kibana_context function
+
+import 'uiExports/savedObjectTypes';
+import 'uiExports/interpreter';
+
+const pluginInstance = plugin({} as PluginInitializerContext);
+
+export const setup = pluginInstance.setup(npSetup.core, npSetup.plugins);
+export const start = pluginInstance.start(npStart.core, npStart.plugins);
diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/app.tsx b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/app.tsx
new file mode 100644
index 0000000000000..f47a7c3a256f0
--- /dev/null
+++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/app.tsx
@@ -0,0 +1,28 @@
+/*
+ * 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 { render, unmountComponentAtNode } from 'react-dom';
+import { AppMountContext, AppMountParameters } from 'kibana/public';
+import { Main } from './components/main';
+
+export const renderApp = (context: AppMountContext, { element }: AppMountParameters) => {
+ render(
, element);
+ return () => unmountComponentAtNode(element);
+};
diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/components/main.tsx b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/components/main.tsx
new file mode 100644
index 0000000000000..c091765619a19
--- /dev/null
+++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/components/main.tsx
@@ -0,0 +1,122 @@
+/*
+ * 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 { EuiPage, EuiPageBody, EuiPageContent, EuiPageContentHeader } from '@elastic/eui';
+import { first } from 'rxjs/operators';
+import {
+ RequestAdapter,
+ DataAdapter,
+} from '../../../../../../../../src/plugins/inspector/public/adapters';
+import {
+ Adapters,
+ Context,
+ ExpressionRenderHandler,
+ ExpressionDataHandler,
+ RenderResult,
+} from '../../types';
+import { getExpressions } from '../../services';
+
+declare global {
+ interface Window {
+ runPipeline: (
+ expressions: string,
+ context?: Context,
+ initialContext?: Context
+ ) => ReturnType
;
+ renderPipelineResponse: (context?: Context) => Promise;
+ }
+}
+
+interface State {
+ expression: string;
+}
+
+class Main extends React.Component<{}, State> {
+ chartRef = React.createRef();
+
+ constructor(props: {}) {
+ super(props);
+
+ this.state = {
+ expression: '',
+ };
+
+ window.runPipeline = async (
+ expression: string,
+ context: Context = {},
+ initialContext: Context = {}
+ ) => {
+ this.setState({ expression });
+ const adapters: Adapters = {
+ requests: new RequestAdapter(),
+ data: new DataAdapter(),
+ };
+ return getExpressions()
+ .execute(expression, {
+ inspectorAdapters: adapters,
+ context,
+ // TODO: naming / typing is confusing and doesn't match here
+ // searchContext is also a way to set initialContext and Context can't be set to SearchContext
+ searchContext: initialContext as any,
+ })
+ .getData();
+ };
+
+ let lastRenderHandler: ExpressionRenderHandler;
+ window.renderPipelineResponse = async (context = {}) => {
+ if (lastRenderHandler) {
+ lastRenderHandler.destroy();
+ }
+
+ lastRenderHandler = getExpressions().render(this.chartRef.current!, context);
+ const renderResult = await lastRenderHandler.render$.pipe(first()).toPromise();
+
+ if (typeof renderResult === 'object' && renderResult.type === 'error') {
+ this.setState({
+ expression: 'Render error!\n\n' + JSON.stringify(renderResult.error),
+ });
+ }
+
+ return renderResult;
+ };
+ }
+
+ render() {
+ const pStyle = {
+ display: 'flex',
+ width: '100%',
+ height: '300px',
+ };
+
+ return (
+
+
+
+ runPipeline tests are running ...
+
+ {this.state.expression}
+
+
+
+ );
+ }
+}
+
+export { Main };
diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/index.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/index.ts
new file mode 100644
index 0000000000000..d7a764b581c01
--- /dev/null
+++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/index.ts
@@ -0,0 +1,28 @@
+/*
+ * 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 { PluginInitializer, PluginInitializerContext } from 'src/core/public';
+import { Plugin, StartDeps } from './plugin';
+export { StartDeps };
+
+export const plugin: PluginInitializer = (
+ initializerContext: PluginInitializerContext
+) => {
+ return new Plugin(initializerContext);
+};
diff --git a/src/legacy/ui/public/courier/utils/courier_inspector_utils.d.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/plugin.ts
similarity index 53%
rename from src/legacy/ui/public/courier/utils/courier_inspector_utils.d.ts
rename to test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/plugin.ts
index 7f638d357a9e1..348ba215930b0 100644
--- a/src/legacy/ui/public/courier/utils/courier_inspector_utils.d.ts
+++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/plugin.ts
@@ -17,36 +17,29 @@
* under the License.
*/
-import { SearchSource } from 'ui/courier';
+import { CoreSetup, CoreStart, PluginInitializerContext } from 'src/core/public';
+import { ExpressionsStart } from './types';
+import { setExpressions } from './services';
-interface InspectorStat {
- label: string;
- value: string;
- description: string;
+export interface StartDeps {
+ expressions: ExpressionsStart;
}
-interface RequestInspectorStats {
- indexPattern: InspectorStat;
- indexPatternId: InspectorStat;
-}
+export class Plugin {
+ constructor(initializerContext: PluginInitializerContext) {}
-interface ResponseInspectorStats {
- queryTime: InspectorStat;
- hitsTotal: InspectorStat;
- hits: InspectorStat;
- requestTime: InspectorStat;
-}
+ public setup({ application }: CoreSetup) {
+ application.register({
+ id: 'kbn_tp_run_pipeline',
+ title: 'Run Pipeline',
+ async mount(context, params) {
+ const { renderApp } = await import('./app/app');
+ return renderApp(context, params);
+ },
+ });
+ }
-interface Response {
- took: number;
- hits: {
- total: number;
- hits: any[];
- };
+ public start(start: CoreStart, { expressions }: StartDeps) {
+ setExpressions(expressions);
+ }
}
-
-export function getRequestInspectorStats(searchSource: SearchSource): RequestInspectorStats;
-export function getResponseInspectorStats(
- searchSource: SearchSource,
- resp: Response
-): ResponseInspectorStats;
diff --git a/src/legacy/ui/public/courier/index.js b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/services.ts
similarity index 77%
rename from src/legacy/ui/public/courier/index.js
rename to test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/services.ts
index 5647af3d0d645..657d8d5150c3a 100644
--- a/src/legacy/ui/public/courier/index.js
+++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/services.ts
@@ -17,12 +17,7 @@
* under the License.
*/
-export { SearchSource } from './search_source';
+import { createGetterSetter } from '../../../../../../src/plugins/kibana_utils/public/core';
+import { ExpressionsStart } from './types';
-export {
- addSearchStrategy,
- hasSearchStategyForIndexPattern,
- isDefaultTypeIndexPattern,
- SearchError,
- getSearchErrorType,
-} from './search_strategy';
+export const [getExpressions, setExpressions] = createGetterSetter('Expressions');
diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/types.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/types.ts
new file mode 100644
index 0000000000000..082bb47d80066
--- /dev/null
+++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/types.ts
@@ -0,0 +1,37 @@
+/*
+ * 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 {
+ ExpressionsStart,
+ Context,
+ ExpressionRenderHandler,
+ ExpressionDataHandler,
+ RenderResult,
+} from 'src/plugins/expressions/public';
+
+import { Adapters } from 'src/plugins/inspector/public';
+
+export {
+ ExpressionsStart,
+ Context,
+ ExpressionRenderHandler,
+ ExpressionDataHandler,
+ RenderResult,
+ Adapters,
+};
diff --git a/test/interpreter_functional/test_suites/run_pipeline/basic.js b/test/interpreter_functional/test_suites/run_pipeline/basic.ts
similarity index 69%
rename from test/interpreter_functional/test_suites/run_pipeline/basic.js
rename to test/interpreter_functional/test_suites/run_pipeline/basic.ts
index 893a79956093c..77853b0bcd6a4 100644
--- a/test/interpreter_functional/test_suites/run_pipeline/basic.js
+++ b/test/interpreter_functional/test_suites/run_pipeline/basic.ts
@@ -18,13 +18,16 @@
*/
import expect from '@kbn/expect';
-import { expectExpressionProvider } from './helpers';
+import { ExpectExpression, expectExpressionProvider } from './helpers';
+import { FtrProviderContext } from '../../../functional/ftr_provider_context';
-// this file showcases how to use testing utilities defined in helpers.js together with the kbn_tp_run_pipeline
+// this file showcases how to use testing utilities defined in helpers.ts together with the kbn_tp_run_pipeline
// test plugin to write autmated tests for interprete
-export default function ({ getService, updateBaselines }) {
-
- let expectExpression;
+export default function({
+ getService,
+ updateBaselines,
+}: FtrProviderContext & { updateBaselines: boolean }) {
+ let expectExpression: ExpectExpression;
describe('basic visualize loader pipeline expression tests', () => {
before(() => {
expectExpression = expectExpressionProvider({ getService, updateBaselines });
@@ -39,7 +42,12 @@ export default function ({ getService, updateBaselines }) {
});
it('correctly sets timeRange', async () => {
- const result = await expectExpression('correctly_sets_timerange', 'kibana', {}, { timeRange: 'test' }).getResponse();
+ const result = await expectExpression(
+ 'correctly_sets_timerange',
+ 'kibana',
+ {},
+ { timeRange: 'test' }
+ ).getResponse();
expect(result).to.have.property('timeRange', 'test');
});
});
@@ -60,30 +68,32 @@ export default function ({ getService, updateBaselines }) {
// we can also do snapshot comparison of result of our expression
// to update the snapshots run the tests with --updateBaselines
- it ('runs the expression and compares final output', async () => {
+ it('runs the expression and compares final output', async () => {
await expectExpression('final_output_test', expression).toMatchSnapshot();
});
// its also possible to check snapshot at every step of expression (after execution of each function)
- it ('runs the expression and compares output at every step', async () => {
+ it('runs the expression and compares output at every step', async () => {
await expectExpression('step_output_test', expression).steps.toMatchSnapshot();
});
// and we can do screenshot comparison of the rendered output of expression (if expression returns renderable)
- it ('runs the expression and compares screenshots', async () => {
+ it('runs the expression and compares screenshots', async () => {
await expectExpression('final_screenshot_test', expression).toMatchScreenshot();
});
// it is also possible to combine different checks
- it ('runs the expression and combines different checks', async () => {
- await (await expectExpression('combined_test', expression).steps.toMatchSnapshot()).toMatchScreenshot();
+ it('runs the expression and combines different checks', async () => {
+ await (
+ await expectExpression('combined_test', expression).steps.toMatchSnapshot()
+ ).toMatchScreenshot();
});
});
// if we want to do multiple different tests using the same data, or reusing a part of expression its
// possible to retrieve the intermediate result and reuse it in later expressions
describe('reusing partial results', () => {
- it ('does some screenshot comparisons', async () => {
+ it('does some screenshot comparisons', async () => {
const expression = `kibana | kibana_context | esaggs index='logstash-*' aggConfigs='[
{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},
{"id":"2","enabled":true,"type":"terms","schema":"segment","params":
@@ -93,17 +103,20 @@ export default function ({ getService, updateBaselines }) {
const context = await expectExpression('partial_test', expression).getResponse();
// we reuse that response to render 3 different charts and compare screenshots with baselines
- const tagCloudExpr =
- `tagcloud metric={visdimension 1 format="number"} bucket={visdimension 0}`;
- await (await expectExpression('partial_test_1', tagCloudExpr, context).toMatchSnapshot()).toMatchScreenshot();
+ const tagCloudExpr = `tagcloud metric={visdimension 1 format="number"} bucket={visdimension 0}`;
+ await (
+ await expectExpression('partial_test_1', tagCloudExpr, context).toMatchSnapshot()
+ ).toMatchScreenshot();
- const metricExpr =
- `metricVis metric={visdimension 1 format="number"} bucket={visdimension 0}`;
- await (await expectExpression('partial_test_2', metricExpr, context).toMatchSnapshot()).toMatchScreenshot();
+ const metricExpr = `metricVis metric={visdimension 1 format="number"} bucket={visdimension 0}`;
+ await (
+ await expectExpression('partial_test_2', metricExpr, context).toMatchSnapshot()
+ ).toMatchScreenshot();
- const regionMapExpr =
- `regionmap visConfig='{"metric":{"accessor":1,"format":{"id":"number"}},"bucket":{"accessor":0}}'`;
- await (await expectExpression('partial_test_3', regionMapExpr, context).toMatchSnapshot()).toMatchScreenshot();
+ const regionMapExpr = `regionmap visConfig='{"metric":{"accessor":1,"format":{"id":"number"}},"bucket":{"accessor":0}}'`;
+ await (
+ await expectExpression('partial_test_3', regionMapExpr, context).toMatchSnapshot()
+ ).toMatchScreenshot();
});
});
});
diff --git a/test/interpreter_functional/test_suites/run_pipeline/helpers.js b/test/interpreter_functional/test_suites/run_pipeline/helpers.ts
similarity index 55%
rename from test/interpreter_functional/test_suites/run_pipeline/helpers.js
rename to test/interpreter_functional/test_suites/run_pipeline/helpers.ts
index 4df86d3418f1f..e1ec18fae5e3a 100644
--- a/test/interpreter_functional/test_suites/run_pipeline/helpers.js
+++ b/test/interpreter_functional/test_suites/run_pipeline/helpers.ts
@@ -18,14 +18,45 @@
*/
import expect from '@kbn/expect';
+import { FtrProviderContext } from '../../../functional/ftr_provider_context';
+import {
+ ExpressionDataHandler,
+ RenderResult,
+ Context,
+} from '../../plugins/kbn_tp_run_pipeline/public/np_ready/types';
+
+type UnWrapPromise = T extends Promise ? U : T;
+export type ExpressionResult = UnWrapPromise>;
+
+export type ExpectExpression = (
+ name: string,
+ expression: string,
+ context?: Context,
+ initialContext?: Context
+) => ExpectExpressionHandler;
+
+export interface ExpectExpressionHandler {
+ toReturn: (expectedResult: ExpressionResult) => Promise;
+ getResponse: () => Promise;
+ runExpression: (step?: string, stepContext?: Context) => Promise;
+ steps: {
+ toMatchSnapshot: () => Promise;
+ };
+ toMatchSnapshot: () => Promise;
+ toMatchScreenshot: () => Promise;
+}
// helper for testing interpreter expressions
-export const expectExpressionProvider = ({ getService, updateBaselines }) => {
+export function expectExpressionProvider({
+ getService,
+ updateBaselines,
+}: Pick & { updateBaselines: boolean }): ExpectExpression {
const browser = getService('browser');
const screenshot = getService('screenshots');
const snapshots = getService('snapshots');
const log = getService('log');
const testSubjects = getService('testSubjects');
+
/**
* returns a handler object to test a given expression
* @name: name of the test
@@ -34,20 +65,25 @@ export const expectExpressionProvider = ({ getService, updateBaselines }) => {
* @initialContext: initialContext provided to the expression
* @returns handler object
*/
- return (name, expression, context = {}, initialContext = {}) => {
+ return (
+ name: string,
+ expression: string,
+ context: Context = {},
+ initialContext: Context = {}
+ ): ExpectExpressionHandler => {
log.debug(`executing expression ${expression}`);
const steps = expression.split('|'); // todo: we should actually use interpreter parser and get the ast
- let responsePromise;
+ let responsePromise: Promise;
- const handler = {
+ const handler: ExpectExpressionHandler = {
/**
* checks if provided object matches expression result
* @param result: expected expression result
* @returns {Promise}
*/
- toReturn: async result => {
+ toReturn: async (expectedResult: ExpressionResult) => {
const pipelineResponse = await handler.getResponse();
- expect(pipelineResponse).to.eql(result);
+ expect(pipelineResponse).to.eql(expectedResult);
},
/**
* returns expression response
@@ -63,16 +99,31 @@ export const expectExpressionProvider = ({ getService, updateBaselines }) => {
* @param stepContext: context to provide to expression
* @returns {Promise<*>} result of running expression
*/
- runExpression: async (step, stepContext) => {
+ runExpression: async (
+ step: string = expression,
+ stepContext: Context = context
+ ): Promise => {
log.debug(`running expression ${step || expression}`);
- const promise = browser.executeAsync((expression, context, initialContext, done) => {
- if (!context) context = {};
- if (!context.type) context.type = 'null';
- window.runPipeline(expression, context, initialContext).then(result => {
- done(result);
- });
- }, step || expression, stepContext || context, initialContext);
- return await promise;
+ return browser.executeAsync(
+ (
+ _expression: string,
+ _currentContext: Context & { type: string },
+ _initialContext: Context,
+ done: (expressionResult: ExpressionResult) => void
+ ) => {
+ if (!_currentContext) _currentContext = { type: 'null' };
+ if (!_currentContext.type) _currentContext.type = 'null';
+ return window
+ .runPipeline(_expression, _currentContext, _initialContext)
+ .then(expressionResult => {
+ done(expressionResult);
+ return expressionResult;
+ });
+ },
+ step,
+ stepContext,
+ initialContext
+ );
},
steps: {
/**
@@ -80,17 +131,19 @@ export const expectExpressionProvider = ({ getService, updateBaselines }) => {
* @returns {Promise}
*/
toMatchSnapshot: async () => {
- let lastResponse;
+ let lastResponse: ExpressionResult;
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
- lastResponse = await handler.runExpression(step, lastResponse);
- const diff = await snapshots.compareAgainstBaseline(name + i, toSerializable(lastResponse), updateBaselines);
+ lastResponse = await handler.runExpression(step, lastResponse!);
+ const diff = await snapshots.compareAgainstBaseline(
+ name + i,
+ toSerializable(lastResponse!),
+ updateBaselines
+ );
expect(diff).to.be.lessThan(0.05);
}
if (!responsePromise) {
- responsePromise = new Promise(resolve => {
- resolve(lastResponse);
- });
+ responsePromise = Promise.resolve(lastResponse!);
}
return handler;
},
@@ -101,7 +154,11 @@ export const expectExpressionProvider = ({ getService, updateBaselines }) => {
*/
toMatchSnapshot: async () => {
const pipelineResponse = await handler.getResponse();
- await snapshots.compareAgainstBaseline(name, toSerializable(pipelineResponse), updateBaselines);
+ await snapshots.compareAgainstBaseline(
+ name,
+ toSerializable(pipelineResponse),
+ updateBaselines
+ );
return handler;
},
/**
@@ -111,24 +168,31 @@ export const expectExpressionProvider = ({ getService, updateBaselines }) => {
toMatchScreenshot: async () => {
const pipelineResponse = await handler.getResponse();
log.debug('starting to render');
- const result = await browser.executeAsync((context, done) => {
- window.renderPipelineResponse(context).then(result => {
- done(result);
- });
- }, pipelineResponse);
+ const result = await browser.executeAsync(
+ (_context: ExpressionResult, done: (renderResult: RenderResult) => void) =>
+ window.renderPipelineResponse(_context).then(renderResult => {
+ done(renderResult);
+ return renderResult;
+ }),
+ pipelineResponse
+ );
log.debug('response of rendering: ', result);
const chartEl = await testSubjects.find('pluginChart');
- const percentDifference = await screenshot.compareAgainstBaseline(name, updateBaselines, chartEl);
+ const percentDifference = await screenshot.compareAgainstBaseline(
+ name,
+ updateBaselines,
+ chartEl
+ );
expect(percentDifference).to.be.lessThan(0.1);
return handler;
- }
+ },
};
return handler;
};
- function toSerializable(response) {
+ function toSerializable(response: ExpressionResult) {
if (response.error) {
// in case of error, pass through only message to the snapshot
// as error could be expected and stack trace shouldn't be part of the snapshot
@@ -136,4 +200,4 @@ export const expectExpressionProvider = ({ getService, updateBaselines }) => {
}
return response;
}
-};
+}
diff --git a/test/interpreter_functional/test_suites/run_pipeline/index.js b/test/interpreter_functional/test_suites/run_pipeline/index.ts
similarity index 82%
rename from test/interpreter_functional/test_suites/run_pipeline/index.js
rename to test/interpreter_functional/test_suites/run_pipeline/index.ts
index 3c1ce2314f55f..031a0e3576ccc 100644
--- a/test/interpreter_functional/test_suites/run_pipeline/index.js
+++ b/test/interpreter_functional/test_suites/run_pipeline/index.ts
@@ -17,7 +17,9 @@
* under the License.
*/
-export default function ({ getService, getPageObjects, loadTestFile }) {
+import { FtrProviderContext } from '../../../functional/ftr_provider_context';
+
+export default function({ getService, getPageObjects, loadTestFile }: FtrProviderContext) {
const browser = getService('browser');
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
@@ -25,13 +27,16 @@ export default function ({ getService, getPageObjects, loadTestFile }) {
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common', 'header']);
- describe('runPipeline', function () {
+ describe('runPipeline', function() {
this.tags(['skipFirefox']);
before(async () => {
await esArchiver.loadIfNeeded('../functional/fixtures/es_archiver/logstash_functional');
await esArchiver.load('../functional/fixtures/es_archiver/visualize_embedding');
- await kibanaServer.uiSettings.replace({ 'dateFormat:tz': 'Australia/North', 'defaultIndex': 'logstash-*' });
+ await kibanaServer.uiSettings.replace({
+ 'dateFormat:tz': 'Australia/North',
+ defaultIndex: 'logstash-*',
+ });
await browser.setWindowSize(1300, 900);
await PageObjects.common.navigateToApp('settings');
await appsMenu.clickLink('Run Pipeline');
diff --git a/test/interpreter_functional/test_suites/run_pipeline/metric.js b/test/interpreter_functional/test_suites/run_pipeline/metric.ts
similarity index 64%
rename from test/interpreter_functional/test_suites/run_pipeline/metric.js
rename to test/interpreter_functional/test_suites/run_pipeline/metric.ts
index 78d571b3583be..c238bedfa28ce 100644
--- a/test/interpreter_functional/test_suites/run_pipeline/metric.js
+++ b/test/interpreter_functional/test_suites/run_pipeline/metric.ts
@@ -17,18 +17,21 @@
* under the License.
*/
-import { expectExpressionProvider } from './helpers';
+import { ExpectExpression, expectExpressionProvider, ExpressionResult } from './helpers';
+import { FtrProviderContext } from '../../../functional/ftr_provider_context';
-export default function ({ getService, updateBaselines }) {
-
- let expectExpression;
+export default function({
+ getService,
+ updateBaselines,
+}: FtrProviderContext & { updateBaselines: boolean }) {
+ let expectExpression: ExpectExpression;
describe('metricVis pipeline expression tests', () => {
before(() => {
expectExpression = expectExpressionProvider({ getService, updateBaselines });
});
describe('correctly renders metric', () => {
- let dataContext;
+ let dataContext: ExpressionResult;
before(async () => {
const expression = `kibana | kibana_context | esaggs index='logstash-*' aggConfigs='[
{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},
@@ -44,27 +47,46 @@ export default function ({ getService, updateBaselines }) {
it('with invalid data', async () => {
const expression = 'metricVis metric={visdimension 0}';
- await (await expectExpression('metric_invalid_data', expression).toMatchSnapshot()).toMatchScreenshot();
+ await (
+ await expectExpression('metric_invalid_data', expression).toMatchSnapshot()
+ ).toMatchScreenshot();
});
it('with single metric data', async () => {
const expression = 'metricVis metric={visdimension 0}';
- await (await expectExpression('metric_single_metric_data', expression, dataContext).toMatchSnapshot()).toMatchScreenshot();
+ await (
+ await expectExpression(
+ 'metric_single_metric_data',
+ expression,
+ dataContext
+ ).toMatchSnapshot()
+ ).toMatchScreenshot();
});
it('with multiple metric data', async () => {
const expression = 'metricVis metric={visdimension 0} metric={visdimension 1}';
- await (await expectExpression('metric_multi_metric_data', expression, dataContext).toMatchSnapshot()).toMatchScreenshot();
+ await (
+ await expectExpression(
+ 'metric_multi_metric_data',
+ expression,
+ dataContext
+ ).toMatchSnapshot()
+ ).toMatchScreenshot();
});
it('with metric and bucket data', async () => {
const expression = 'metricVis metric={visdimension 0} bucket={visdimension 2}';
- await (await expectExpression('metric_all_data', expression, dataContext).toMatchSnapshot()).toMatchScreenshot();
+ await (
+ await expectExpression('metric_all_data', expression, dataContext).toMatchSnapshot()
+ ).toMatchScreenshot();
});
it('with percentage option', async () => {
- const expression = 'metricVis metric={visdimension 0} percentage=true colorRange={range from=0 to=1000}';
- await (await expectExpression('metric_percentage', expression, dataContext).toMatchSnapshot()).toMatchScreenshot();
+ const expression =
+ 'metricVis metric={visdimension 0} percentage=true colorRange={range from=0 to=1000}';
+ await (
+ await expectExpression('metric_percentage', expression, dataContext).toMatchSnapshot()
+ ).toMatchScreenshot();
});
});
});
diff --git a/test/interpreter_functional/test_suites/run_pipeline/tag_cloud.js b/test/interpreter_functional/test_suites/run_pipeline/tag_cloud.ts
similarity index 61%
rename from test/interpreter_functional/test_suites/run_pipeline/tag_cloud.js
rename to test/interpreter_functional/test_suites/run_pipeline/tag_cloud.ts
index 7c0e2d7190703..2451df4db6310 100644
--- a/test/interpreter_functional/test_suites/run_pipeline/tag_cloud.js
+++ b/test/interpreter_functional/test_suites/run_pipeline/tag_cloud.ts
@@ -17,18 +17,21 @@
* under the License.
*/
-import { expectExpressionProvider } from './helpers';
+import { ExpectExpression, expectExpressionProvider, ExpressionResult } from './helpers';
+import { FtrProviderContext } from '../../../functional/ftr_provider_context';
-export default function ({ getService, updateBaselines }) {
-
- let expectExpression;
+export default function({
+ getService,
+ updateBaselines,
+}: FtrProviderContext & { updateBaselines: boolean }) {
+ let expectExpression: ExpectExpression;
describe('tag cloud pipeline expression tests', () => {
before(() => {
expectExpression = expectExpressionProvider({ getService, updateBaselines });
});
describe('correctly renders tagcloud', () => {
- let dataContext;
+ let dataContext: ExpressionResult;
before(async () => {
const expression = `kibana | kibana_context | esaggs index='logstash-*' aggConfigs='[
{"id":"1","enabled":true,"type":"count","schema":"metric","params":{}},
@@ -41,27 +44,39 @@ export default function ({ getService, updateBaselines }) {
it('with invalid data', async () => {
const expression = 'tagcloud metric={visdimension 0}';
- await (await expectExpression('tagcloud_invalid_data', expression).toMatchSnapshot()).toMatchScreenshot();
+ await (
+ await expectExpression('tagcloud_invalid_data', expression).toMatchSnapshot()
+ ).toMatchScreenshot();
});
it('with just metric data', async () => {
const expression = 'tagcloud metric={visdimension 0}';
- await (await expectExpression('tagcloud_metric_data', expression, dataContext).toMatchSnapshot()).toMatchScreenshot();
+ await (
+ await expectExpression('tagcloud_metric_data', expression, dataContext).toMatchSnapshot()
+ ).toMatchScreenshot();
});
it('with metric and bucket data', async () => {
const expression = 'tagcloud metric={visdimension 0} bucket={visdimension 1}';
- await (await expectExpression('tagcloud_all_data', expression, dataContext).toMatchSnapshot()).toMatchScreenshot();
+ await (
+ await expectExpression('tagcloud_all_data', expression, dataContext).toMatchSnapshot()
+ ).toMatchScreenshot();
});
it('with font size options', async () => {
- const expression = 'tagcloud metric={visdimension 0} bucket={visdimension 1} minFontSize=20 maxFontSize=40';
- await (await expectExpression('tagcloud_fontsize', expression, dataContext).toMatchSnapshot()).toMatchScreenshot();
+ const expression =
+ 'tagcloud metric={visdimension 0} bucket={visdimension 1} minFontSize=20 maxFontSize=40';
+ await (
+ await expectExpression('tagcloud_fontsize', expression, dataContext).toMatchSnapshot()
+ ).toMatchScreenshot();
});
it('with scale and orientation options', async () => {
- const expression = 'tagcloud metric={visdimension 0} bucket={visdimension 1} scale="log" orientation="multiple"';
- await (await expectExpression('tagcloud_options', expression, dataContext).toMatchSnapshot()).toMatchScreenshot();
+ const expression =
+ 'tagcloud metric={visdimension 0} bucket={visdimension 1} scale="log" orientation="multiple"';
+ await (
+ await expectExpression('tagcloud_options', expression, dataContext).toMatchSnapshot()
+ ).toMatchScreenshot();
});
});
});
diff --git a/test/plugin_functional/plugins/demo_search/server/constants.ts b/test/plugin_functional/plugins/demo_search/server/constants.ts
deleted file mode 100644
index 11c258a21d5a8..0000000000000
--- a/test/plugin_functional/plugins/demo_search/server/constants.ts
+++ /dev/null
@@ -1,20 +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 FAKE_PROGRESS_STRATEGY = 'FAKE_PROGRESS_STRATEGY';
diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx
index f03b3c4a1e0a5..6b82a67b9fcda 100644
--- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx
+++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx
@@ -27,7 +27,7 @@ import {
Setup as InspectorSetupContract,
} from '../../../../../../../src/plugins/inspector/public';
-import { Plugin as EmbeddablePlugin, CONTEXT_MENU_TRIGGER } from './embeddable_api';
+import { CONTEXT_MENU_TRIGGER } from './embeddable_api';
const REACT_ROOT_ID = 'embeddableExplorerRoot';
@@ -38,9 +38,13 @@ import {
ContactCardEmbeddableFactory,
} from './embeddable_api';
import { App } from './app';
+import {
+ IEmbeddableStart,
+ IEmbeddableSetup,
+} from '.../../../../../../../src/plugins/embeddable/public';
export interface SetupDependencies {
- embeddable: ReturnType;
+ embeddable: IEmbeddableSetup;
inspector: InspectorSetupContract;
__LEGACY: {
SavedObjectFinder: React.ComponentType;
@@ -49,7 +53,7 @@ export interface SetupDependencies {
}
interface StartDependencies {
- embeddable: ReturnType;
+ embeddable: IEmbeddableStart;
uiActions: IUiActionsStart;
inspector: InspectorStartContract;
__LEGACY: {
diff --git a/test/scripts/jenkins_xpack.sh b/test/scripts/jenkins_xpack.sh
index 1a7a1c973102c..27f73c0b6e20d 100755
--- a/test/scripts/jenkins_xpack.sh
+++ b/test/scripts/jenkins_xpack.sh
@@ -6,7 +6,7 @@ export TEST_BROWSER_HEADLESS=1
echo " -> Running mocha tests"
cd "$XPACK_DIR"
-checks-reporter-with-killswitch "X-Pack Mocha" yarn test
+checks-reporter-with-killswitch "X-Pack Karma Tests" yarn test:browser
echo ""
echo ""
diff --git a/x-pack/gulpfile.js b/x-pack/gulpfile.js
index 74e24692f59f6..d3f93c29e3df8 100644
--- a/x-pack/gulpfile.js
+++ b/x-pack/gulpfile.js
@@ -8,7 +8,7 @@ require('../src/setup_node_env');
const { buildTask } = require('./tasks/build');
const { devTask } = require('./tasks/dev');
-const { testTask, testBrowserTask, testBrowserDevTask, testServerTask } = require('./tasks/test');
+const { testTask, testBrowserTask, testBrowserDevTask } = require('./tasks/test');
const { prepareTask } = require('./tasks/prepare');
// export the tasks that are runnable from the CLI
@@ -17,7 +17,6 @@ module.exports = {
dev: devTask,
prepare: prepareTask,
test: testTask,
- testserver: testServerTask,
testbrowser: testBrowserTask,
'testbrowser-dev': testBrowserDevTask,
};
diff --git a/x-pack/legacy/plugins/apm/public/components/app/Home/Home.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/Home/Home.test.tsx
index 035015c82a0ac..7a23c9f7de842 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/Home/Home.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/app/Home/Home.test.tsx
@@ -8,7 +8,6 @@ import { shallow } from 'enzyme';
import React from 'react';
import { Home } from '../Home';
-jest.mock('ui/index_patterns');
jest.mock('ui/new_platform');
describe('Home component', () => {
diff --git a/x-pack/legacy/plugins/apm/public/components/app/Main/__test__/UpdateBreadcrumbs.test.js b/x-pack/legacy/plugins/apm/public/components/app/Main/__test__/UpdateBreadcrumbs.test.js
index 8ddf48e79f911..41fb12be284ad 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/Main/__test__/UpdateBreadcrumbs.test.js
+++ b/x-pack/legacy/plugins/apm/public/components/app/Main/__test__/UpdateBreadcrumbs.test.js
@@ -10,7 +10,6 @@ import { MemoryRouter } from 'react-router-dom';
import { UpdateBreadcrumbs } from '../UpdateBreadcrumbs';
import * as kibanaCore from '../../../../../../observability/public/context/kibana_core';
-jest.mock('ui/index_patterns');
jest.mock('ui/new_platform');
const coreMock = {
diff --git a/x-pack/legacy/plugins/apm/public/components/app/Main/useUpdateBadgeEffect.ts b/x-pack/legacy/plugins/apm/public/components/app/Main/useUpdateBadgeEffect.ts
deleted file mode 100644
index bb9f581129c5e..0000000000000
--- a/x-pack/legacy/plugins/apm/public/components/app/Main/useUpdateBadgeEffect.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { i18n } from '@kbn/i18n';
-import { useEffect } from 'react';
-import { capabilities } from 'ui/capabilities';
-import { useKibanaCore } from '../../../../../observability/public';
-
-export const useUpdateBadgeEffect = () => {
- const { chrome } = useKibanaCore();
-
- useEffect(() => {
- const uiCapabilities = capabilities.get();
- chrome.setBadge(
- !uiCapabilities.apm.save
- ? {
- text: i18n.translate('xpack.apm.header.badge.readOnly.text', {
- defaultMessage: 'Read only'
- }),
- tooltip: i18n.translate('xpack.apm.header.badge.readOnly.tooltip', {
- defaultMessage: 'Unable to save'
- }),
- iconType: 'glasses'
- }
- : undefined
- );
- }, [chrome]);
-};
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/DatePicker/__test__/DatePicker.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/DatePicker/__test__/DatePicker.test.tsx
index 881e5975fc81f..05094c59712a9 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/DatePicker/__test__/DatePicker.test.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/DatePicker/__test__/DatePicker.test.tsx
@@ -18,8 +18,6 @@ import { mount } from 'enzyme';
import { EuiSuperDatePicker } from '@elastic/eui';
import { MemoryRouter } from 'react-router-dom';
-jest.mock('ui/kfetch');
-
const mockHistoryPush = jest.spyOn(history, 'push');
const mockRefreshTimeRange = jest.fn();
const MockUrlParamsProvider: React.FC<{
diff --git a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx
index 66946e5b447f9..52be4d4fba774 100644
--- a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx
@@ -7,8 +7,6 @@
import React, { useState } from 'react';
import { uniqueId, startsWith } from 'lodash';
import styled from 'styled-components';
-import { npStart } from 'ui/new_platform';
-import { StaticIndexPattern } from 'ui/index_patterns';
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
import { i18n } from '@kbn/i18n';
import { fromQuery, toQuery } from '../Links/url_helpers';
@@ -18,25 +16,24 @@ import { getBoolFilter } from './get_bool_filter';
import { useLocation } from '../../../hooks/useLocation';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { history } from '../../../utils/history';
-import { AutocompleteSuggestion } from '../../../../../../../../src/plugins/data/public';
+import {
+ AutocompleteSuggestion,
+ AutocompleteProvider,
+ IIndexPattern
+} from '../../../../../../../../src/plugins/data/public';
import { useDynamicIndexPattern } from '../../../hooks/useDynamicIndexPattern';
+import { usePlugins } from '../../../new-platform/plugin';
const Container = styled.div`
margin-bottom: 10px;
`;
-const getAutocompleteProvider = (language: string) =>
- npStart.plugins.data.autocomplete.getProvider(language);
-
interface State {
suggestions: AutocompleteSuggestion[];
isLoadingSuggestions: boolean;
}
-function convertKueryToEsQuery(
- kuery: string,
- indexPattern: StaticIndexPattern
-) {
+function convertKueryToEsQuery(kuery: string, indexPattern: IIndexPattern) {
const ast = fromKueryExpression(kuery);
return toElasticsearchQuery(ast, indexPattern);
}
@@ -44,10 +41,10 @@ function convertKueryToEsQuery(
function getSuggestions(
query: string,
selectionStart: number,
- indexPattern: StaticIndexPattern,
- boolFilter: unknown
+ indexPattern: IIndexPattern,
+ boolFilter: unknown,
+ autocompleteProvider?: AutocompleteProvider
) {
- const autocompleteProvider = getAutocompleteProvider('kuery');
if (!autocompleteProvider) {
return [];
}
@@ -74,6 +71,8 @@ export function KueryBar() {
});
const { urlParams } = useUrlParams();
const location = useLocation();
+ const { data } = usePlugins();
+ const autocompleteProvider = data.autocomplete.getProvider('kuery');
let currentRequestCheck;
@@ -108,7 +107,8 @@ export function KueryBar() {
inputValue,
selectionStart,
indexPattern,
- boolFilter
+ boolFilter,
+ autocompleteProvider
)
)
.filter(suggestion => !startsWith(suggestion.text, 'span.'))
diff --git a/x-pack/legacy/plugins/apm/public/index.tsx b/x-pack/legacy/plugins/apm/public/index.tsx
index 8fd3cb0893dea..db14e1c520020 100644
--- a/x-pack/legacy/plugins/apm/public/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/index.tsx
@@ -6,43 +6,18 @@
import { npStart } from 'ui/new_platform';
import 'react-vis/dist/style.css';
+import { PluginInitializerContext } from 'kibana/public';
import 'ui/autoload/all';
import chrome from 'ui/chrome';
-import { i18n } from '@kbn/i18n';
-import url from 'url';
-
-// @ts-ignore
-import { uiModules } from 'ui/modules';
import { plugin } from './new-platform';
import { REACT_APP_ROOT_ID } from './new-platform/plugin';
import './style/global_overrides.css';
import template from './templates/index.html';
-const { core } = npStart;
-
-// render APM feedback link in global help menu
-core.chrome.setHelpExtension({
- appName: i18n.translate('xpack.apm.feedbackMenu.appName', {
- defaultMessage: 'APM'
- }),
- links: [
- {
- linkType: 'discuss',
- href: 'https://discuss.elastic.co/c/apm'
- },
- {
- linkType: 'custom',
- href: url.format({
- pathname: core.http.basePath.prepend('/app/kibana'),
- hash: '/management/elasticsearch/upgrade_assistant'
- }),
- content: i18n.translate('xpack.apm.helpMenu.upgradeAssistantLink', {
- defaultMessage: 'Upgrade assistant'
- })
- }
- ]
-});
+const { core, plugins } = npStart;
+// This will be moved to core.application.register when the new platform
+// migration is complete.
// @ts-ignore
chrome.setRootTemplate(template);
@@ -57,5 +32,5 @@ const checkForRoot = () => {
});
};
checkForRoot().then(() => {
- plugin().start(core);
+ plugin({} as PluginInitializerContext).start(core, plugins);
});
diff --git a/x-pack/legacy/plugins/apm/public/new-platform/index.tsx b/x-pack/legacy/plugins/apm/public/new-platform/index.tsx
index cb4cc2a845a4c..9dce4bcdd828c 100644
--- a/x-pack/legacy/plugins/apm/public/new-platform/index.tsx
+++ b/x-pack/legacy/plugins/apm/public/new-platform/index.tsx
@@ -4,8 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Plugin } from './plugin';
+import { PluginInitializer } from '../../../../../../src/core/public';
+import { ApmPlugin, ApmPluginSetup, ApmPluginStart } from './plugin';
-export function plugin() {
- return new Plugin();
-}
+export const plugin: PluginInitializer<
+ ApmPluginSetup,
+ ApmPluginStart
+> = _core => new ApmPlugin();
diff --git a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx
index ac4aca4c795b7..b5986610d3048 100644
--- a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx
+++ b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx
@@ -4,11 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
+import React, { useContext, createContext } from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, Switch } from 'react-router-dom';
import styled from 'styled-components';
-import { LegacyCoreStart } from 'src/core/public';
+import {
+ CoreStart,
+ LegacyCoreStart,
+ Plugin,
+ CoreSetup
+} from '../../../../../../src/core/public';
+import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public';
import { KibanaCoreContextProvider } from '../../../observability/public';
import { history } from '../utils/history';
import { LocationProvider } from '../context/LocationContext';
@@ -19,9 +25,10 @@ import { LicenseProvider } from '../context/LicenseContext';
import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs';
import { routes } from '../components/app/Main/route_config';
import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange';
-import { useUpdateBadgeEffect } from '../components/app/Main/useUpdateBadgeEffect';
import { MatchedRouteProvider } from '../context/MatchedRouteContext';
import { createStaticIndexPattern } from '../services/rest/index_pattern';
+import { setHelpExtension } from './setHelpExtension';
+import { setReadonlyBadge } from './updateBadge';
export const REACT_APP_ROOT_ID = 'react-apm-root';
@@ -31,41 +38,70 @@ const MainContainer = styled.main`
`;
const App = () => {
- useUpdateBadgeEffect();
-
return (
-
-
-
-
-
-
-
-
- {routes.map((route, i) => (
-
- ))}
-
-
-
-
-
-
+
+
+
+
+ {routes.map((route, i) => (
+
+ ))}
+
+
);
};
-export class Plugin {
- public start(core: LegacyCoreStart) {
- const { i18n } = core;
+export type ApmPluginSetup = void;
+export type ApmPluginStart = void;
+export type ApmPluginSetupDeps = {}; // eslint-disable-line @typescript-eslint/consistent-type-definitions
+
+export interface ApmPluginStartDeps {
+ data: DataPublicPluginStart;
+}
+
+const PluginsContext = createContext({} as ApmPluginStartDeps);
+
+export function usePlugins() {
+ return useContext(PluginsContext);
+}
+
+export class ApmPlugin
+ implements
+ Plugin<
+ ApmPluginSetup,
+ ApmPluginStart,
+ ApmPluginSetupDeps,
+ ApmPluginStartDeps
+ > {
+ // Take the DOM element as the constructor, so we can mount the app.
+ public setup(_core: CoreSetup, _plugins: ApmPluginSetupDeps) {}
+
+ public start(core: CoreStart, plugins: ApmPluginStartDeps) {
+ const i18nCore = core.i18n;
+
+ // render APM feedback link in global help menu
+ setHelpExtension(core);
+ setReadonlyBadge(core);
+
ReactDOM.render(
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
,
document.getElementById(REACT_APP_ROOT_ID)
);
@@ -76,4 +112,6 @@ export class Plugin {
console.log('Error fetching static index pattern', e);
});
}
+
+ public stop() {}
}
diff --git a/x-pack/legacy/plugins/apm/public/new-platform/setHelpExtension.ts b/x-pack/legacy/plugins/apm/public/new-platform/setHelpExtension.ts
new file mode 100644
index 0000000000000..1a3394651b2ff
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/new-platform/setHelpExtension.ts
@@ -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 url from 'url';
+import { i18n } from '@kbn/i18n';
+import { CoreStart } from 'kibana/public';
+
+export function setHelpExtension({ chrome, http }: CoreStart) {
+ chrome.setHelpExtension({
+ appName: i18n.translate('xpack.apm.feedbackMenu.appName', {
+ defaultMessage: 'APM'
+ }),
+ links: [
+ {
+ linkType: 'discuss',
+ href: 'https://discuss.elastic.co/c/apm'
+ },
+ {
+ linkType: 'custom',
+ href: url.format({
+ pathname: http.basePath.prepend('/app/kibana'),
+ hash: '/management/elasticsearch/upgrade_assistant'
+ }),
+ content: i18n.translate('xpack.apm.helpMenu.upgradeAssistantLink', {
+ defaultMessage: 'Upgrade assistant'
+ })
+ }
+ ]
+ });
+}
diff --git a/x-pack/legacy/plugins/apm/public/new-platform/updateBadge.ts b/x-pack/legacy/plugins/apm/public/new-platform/updateBadge.ts
new file mode 100644
index 0000000000000..b3e29bb891c23
--- /dev/null
+++ b/x-pack/legacy/plugins/apm/public/new-platform/updateBadge.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { CoreStart } from 'kibana/public';
+
+export function setReadonlyBadge({ application, chrome }: CoreStart) {
+ const canSave = application.capabilities.apm.save;
+ const { setBadge } = chrome;
+
+ setBadge(
+ !canSave
+ ? {
+ text: i18n.translate('xpack.apm.header.badge.readOnly.text', {
+ defaultMessage: 'Read only'
+ }),
+ tooltip: i18n.translate('xpack.apm.header.badge.readOnly.tooltip', {
+ defaultMessage: 'Unable to save'
+ }),
+ iconType: 'glasses'
+ }
+ : undefined
+ );
+}
diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts
index 8f19f4baed7ee..a09cdbf91ec6e 100644
--- a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts
@@ -6,7 +6,6 @@
import moment from 'moment';
import { KibanaRequest } from 'src/core/server';
-import { StaticIndexPattern } from 'ui/index_patterns';
import { IIndexPattern } from 'src/plugins/data/common';
import { APMConfig } from '../../../../../../plugins/apm/server';
import {
@@ -22,7 +21,7 @@ import { ProcessorEvent } from '../../../common/processor_event';
import { getDynamicIndexPattern } from '../index_pattern/get_dynamic_index_pattern';
function decodeUiFilters(
- indexPattern: StaticIndexPattern | undefined,
+ indexPattern: IIndexPattern | undefined,
uiFiltersEncoded?: string
) {
if (!uiFiltersEncoded || !indexPattern) {
diff --git a/x-pack/legacy/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts b/x-pack/legacy/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts
index f113e645ed95f..9eb99b7c21e75 100644
--- a/x-pack/legacy/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { StaticIndexPattern } from 'ui/index_patterns';
import { APICaller } from 'src/core/server';
import LRU from 'lru-cache';
import {
@@ -51,7 +50,7 @@ export const getDynamicIndexPattern = async ({
pattern: patternIndices
});
- const indexPattern: StaticIndexPattern = {
+ const indexPattern: IIndexPattern = {
fields,
title: indexPatternTitle
};
diff --git a/x-pack/legacy/plugins/grokdebugger/server/models/grokdebugger_request/__tests__/grokdebugger_request.js b/x-pack/legacy/plugins/grokdebugger/server/models/grokdebugger_request/__tests__/grokdebugger_request.js
index a87999873e40f..616aefaf73f62 100644
--- a/x-pack/legacy/plugins/grokdebugger/server/models/grokdebugger_request/__tests__/grokdebugger_request.js
+++ b/x-pack/legacy/plugins/grokdebugger/server/models/grokdebugger_request/__tests__/grokdebugger_request.js
@@ -7,7 +7,8 @@
import expect from '@kbn/expect';
import { GrokdebuggerRequest } from '../grokdebugger_request';
-describe('grokdebugger_request', () => {
+// FAILING: https://github.com/elastic/kibana/issues/51372
+describe.skip('grokdebugger_request', () => {
describe('GrokdebuggerRequest', () => {
const downstreamRequest = {
diff --git a/x-pack/legacy/plugins/infra/types/eui.d.ts b/x-pack/legacy/plugins/infra/types/eui.d.ts
index 2907830ff882f..7cf0a91e88c1f 100644
--- a/x-pack/legacy/plugins/infra/types/eui.d.ts
+++ b/x-pack/legacy/plugins/infra/types/eui.d.ts
@@ -68,6 +68,7 @@ declare module '@elastic/eui' {
rowProps?: any;
cellProps?: any;
responsive?: boolean;
+ itemIdToExpandedRowMap?: any;
};
export const EuiInMemoryTable: React.FC;
}
diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.tsx
index 354a5186db4c1..f7399255b2001 100644
--- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.tsx
+++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.tsx
@@ -15,8 +15,8 @@ import {
ExpressionsStart,
} from '../../../../../../src/plugins/expressions/public';
import {
- Setup as EmbeddableSetup,
- Start as EmbeddableStart,
+ IEmbeddableSetup,
+ IEmbeddableStart,
} from '../../../../../../src/plugins/embeddable/public';
import {
setup as dataSetup,
@@ -36,13 +36,13 @@ import { getActiveDatasourceIdFromDoc } from './editor_frame/state_management';
export interface EditorFrameSetupPlugins {
data: typeof dataSetup;
- embeddable: EmbeddableSetup;
+ embeddable: IEmbeddableSetup;
expressions: ExpressionsSetup;
}
export interface EditorFrameStartPlugins {
data: typeof dataStart;
- embeddable: EmbeddableStart;
+ embeddable: IEmbeddableStart;
expressions: ExpressionsStart;
chrome: Chrome;
}
diff --git a/x-pack/legacy/plugins/maps/common/constants.js b/x-pack/legacy/plugins/maps/common/constants.js
index 691c679e5290b..3b2f887e13c87 100644
--- a/x-pack/legacy/plugins/maps/common/constants.js
+++ b/x-pack/legacy/plugins/maps/common/constants.js
@@ -68,7 +68,7 @@ export const ZOOM_PRECISION = 2;
export const ES_SIZE_LIMIT = 10000;
export const FEATURE_ID_PROPERTY_NAME = '__kbn__feature_id__';
-export const FEATURE_VISIBLE_PROPERTY_NAME = '__kbn__isvisible__';
+export const FEATURE_VISIBLE_PROPERTY_NAME = '__kbn_isvisibleduetojoin__';
export const MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER = '_';
diff --git a/x-pack/legacy/plugins/maps/public/angular/map.html b/x-pack/legacy/plugins/maps/public/angular/map.html
index 90d4ddbeb0092..2f34ffa660d6e 100644
--- a/x-pack/legacy/plugins/maps/public/angular/map.html
+++ b/x-pack/legacy/plugins/maps/public/angular/map.html
@@ -1,4 +1,5 @@
+
diff --git a/x-pack/legacy/plugins/maps/public/angular/map_controller.js b/x-pack/legacy/plugins/maps/public/angular/map_controller.js
index 41c618d68a68e..b9354dd0a0ddd 100644
--- a/x-pack/legacy/plugins/maps/public/angular/map_controller.js
+++ b/x-pack/legacy/plugins/maps/public/angular/map_controller.js
@@ -66,6 +66,7 @@ const app = uiModules.get(MAP_APP_PATH, []);
app.controller('GisMapController', ($scope, $route, kbnUrl, localStorage, AppState, globalState) => {
const { filterManager } = npStart.plugins.data.query;
const savedMap = $route.current.locals.map;
+ $scope.screenTitle = savedMap.title;
let unsubscribe;
let initialLayerListConfig;
const $state = new AppState();
diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js
index 44629d16e6fb3..01c323d73f19e 100644
--- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js
+++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js
@@ -16,7 +16,6 @@ import {
EuiFormHelpText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { IndexPatternSelect } from 'ui/index_patterns';
import { SingleFieldSelect } from '../../../../components/single_field_select';
import { FormattedMessage } from '@kbn/i18n/react';
import { getTermsFields } from '../../../../index_pattern_util';
@@ -25,6 +24,9 @@ import {
indexPatternService,
} from '../../../../kibana_services';
+import { npStart } from 'ui/new_platform';
+const { IndexPatternSelect } = npStart.plugins.data.ui;
+
export class JoinExpression extends Component {
state = {
diff --git a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js b/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js
index ef2819f1f372c..4b04251edd94a 100644
--- a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js
+++ b/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js
@@ -11,7 +11,6 @@ import {
DECIMAL_DEGREES_PRECISION,
ES_GEO_FIELD_TYPE,
ES_SPATIAL_RELATIONS,
- FEATURE_ID_PROPERTY_NAME,
GEO_JSON_TYPE,
POLYGON_COORDINATES_EXTERIOR_INDEX,
LON_INDEX,
@@ -81,12 +80,10 @@ export function hitsToGeoJson(hits, flattenHit, geoFieldName, geoFieldType) {
features.push({
type: 'Feature',
geometry: tmpGeometriesAccumulator[j],
- properties: {
- ...properties,
- // _id is not unique across Kibana index pattern. Multiple ES indices could have _id collisions
- // Need to prefix with _index to guarantee uniqueness
- [FEATURE_ID_PROPERTY_NAME]: `${properties._index}:${properties._id}:${j}`
- }
+ // _id is not unique across Kibana index pattern. Multiple ES indices could have _id collisions
+ // Need to prefix with _index to guarantee uniqueness
+ id: `${properties._index}:${properties._id}:${j}`,
+ properties,
});
}
}
diff --git a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.test.js b/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.test.js
index 0b84b4c32f4ac..45aa2af15eb9d 100644
--- a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.test.js
+++ b/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.test.js
@@ -74,8 +74,8 @@ describe('hitsToGeoJson', () => {
coordinates: [100, 20],
type: 'Point',
},
+ id: 'index1:doc1:0',
properties: {
- __kbn__feature_id__: 'index1:doc1:0',
_id: 'doc1',
_index: 'index1',
},
@@ -139,8 +139,8 @@ describe('hitsToGeoJson', () => {
coordinates: [100, 20],
type: 'Point',
},
+ id: 'index1:doc1:0',
properties: {
- __kbn__feature_id__: 'index1:doc1:0',
_id: 'doc1',
_index: 'index1',
myField: 8
@@ -152,8 +152,8 @@ describe('hitsToGeoJson', () => {
coordinates: [110, 30],
type: 'Point',
},
+ id: 'index1:doc1:1',
properties: {
- __kbn__feature_id__: 'index1:doc1:1',
_id: 'doc1',
_index: 'index1',
myField: 8
diff --git a/x-pack/legacy/plugins/maps/public/kibana_services.js b/x-pack/legacy/plugins/maps/public/kibana_services.js
index 12fab24d1f8d6..7169014542710 100644
--- a/x-pack/legacy/plugins/maps/public/kibana_services.js
+++ b/x-pack/legacy/plugins/maps/public/kibana_services.js
@@ -4,13 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { getRequestInspectorStats, getResponseInspectorStats } from 'ui/courier/utils/courier_inspector_utils';
+import { getRequestInspectorStats, getResponseInspectorStats } from '../../../../../src/legacy/ui/public/courier';
export { xpackInfo } from 'plugins/xpack_main/services/xpack_info';
import { start as data } from '../../../../../src/legacy/core_plugins/data/public/legacy';
import { esFilters } from '../../../../../src/plugins/data/public';
export const SPATIAL_FILTER_TYPE = esFilters.FILTERS.SPATIAL_FILTER;
-export { SearchSource } from 'ui/courier';
+export { SearchSource } from '../../../../../src/legacy/ui/public/courier';
export const indexPatternService = data.indexPatterns.indexPatterns;
export async function fetchSearchSourceAndRecordWithInspector({
diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.js b/x-pack/legacy/plugins/maps/public/layers/layer.js
index 72a89046ed2f5..1c2f33df66bf8 100644
--- a/x-pack/legacy/plugins/maps/public/layers/layer.js
+++ b/x-pack/legacy/plugins/maps/public/layers/layer.js
@@ -6,8 +6,6 @@
import _ from 'lodash';
import React from 'react';
import { EuiIcon, EuiLoadingSpinner } from '@elastic/eui';
-import turf from 'turf';
-import turfBooleanContains from '@turf/boolean-contains';
import { DataRequest } from './util/data_request';
import {
MAX_ZOOM,
@@ -19,9 +17,6 @@ import uuid from 'uuid/v4';
import { copyPersistentState } from '../reducers/util';
import { i18n } from '@kbn/i18n';
-const SOURCE_UPDATE_REQUIRED = true;
-const NO_SOURCE_UPDATE_REQUIRED = false;
-
export class AbstractLayer {
constructor({ layerDescriptor, source }) {
@@ -316,42 +311,7 @@ export class AbstractLayer {
throw new Error('Should implement AbstractLayer#syncLayerWithMB');
}
- updateDueToExtent(source, prevMeta = {}, nextMeta = {}) {
- const extentAware = source.isFilterByMapBounds();
- if (!extentAware) {
- return NO_SOURCE_UPDATE_REQUIRED;
- }
- const { buffer: previousBuffer } = prevMeta;
- const { buffer: newBuffer } = nextMeta;
-
- if (!previousBuffer) {
- return SOURCE_UPDATE_REQUIRED;
- }
-
- if (_.isEqual(previousBuffer, newBuffer)) {
- return NO_SOURCE_UPDATE_REQUIRED;
- }
-
- const previousBufferGeometry = turf.bboxPolygon([
- previousBuffer.minLon,
- previousBuffer.minLat,
- previousBuffer.maxLon,
- previousBuffer.maxLat
- ]);
- const newBufferGeometry = turf.bboxPolygon([
- newBuffer.minLon,
- newBuffer.minLat,
- newBuffer.maxLon,
- newBuffer.maxLat
- ]);
- const doesPreviousBufferContainNewBuffer = turfBooleanContains(previousBufferGeometry, newBufferGeometry);
-
- const isTrimmed = _.get(prevMeta, 'areResultsTrimmed', false);
- return doesPreviousBufferContainNewBuffer && !isTrimmed
- ? NO_SOURCE_UPDATE_REQUIRED
- : SOURCE_UPDATE_REQUIRED;
- }
getLayerTypeIconName() {
throw new Error('should implement Layer#getLayerTypeIconName');
@@ -407,4 +367,3 @@ export class AbstractLayer {
}
}
-
diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.test.js b/x-pack/legacy/plugins/maps/public/layers/layer.test.js
deleted file mode 100644
index 98be0855cd4b7..0000000000000
--- a/x-pack/legacy/plugins/maps/public/layers/layer.test.js
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { AbstractLayer } from './layer';
-
-describe('layer', () => {
- const layer = new AbstractLayer({ layerDescriptor: {} });
-
- describe('updateDueToExtent', () => {
-
- it('should be false when the source is not extent aware', async () => {
- const sourceMock = {
- isFilterByMapBounds: () => { return false; }
- };
- const updateDueToExtent = layer.updateDueToExtent(sourceMock);
- expect(updateDueToExtent).toBe(false);
- });
-
- it('should be false when buffers are the same', async () => {
- const sourceMock = {
- isFilterByMapBounds: () => { return true; }
- };
- const oldBuffer = {
- maxLat: 12.5,
- maxLon: 102.5,
- minLat: 2.5,
- minLon: 92.5,
- };
- const newBuffer = {
- maxLat: 12.5,
- maxLon: 102.5,
- minLat: 2.5,
- minLon: 92.5,
- };
- const updateDueToExtent = layer.updateDueToExtent(sourceMock, { buffer: oldBuffer }, { buffer: newBuffer });
- expect(updateDueToExtent).toBe(false);
- });
-
- it('should be false when the new buffer is contained in the old buffer', async () => {
- const sourceMock = {
- isFilterByMapBounds: () => { return true; }
- };
- const oldBuffer = {
- maxLat: 12.5,
- maxLon: 102.5,
- minLat: 2.5,
- minLon: 92.5,
- };
- const newBuffer = {
- maxLat: 10,
- maxLon: 100,
- minLat: 5,
- minLon: 95,
- };
- const updateDueToExtent = layer.updateDueToExtent(sourceMock, { buffer: oldBuffer }, { buffer: newBuffer });
- expect(updateDueToExtent).toBe(false);
- });
-
- it('should be true when the new buffer is contained in the old buffer and the past results were truncated', async () => {
- const sourceMock = {
- isFilterByMapBounds: () => { return true; }
- };
- const oldBuffer = {
- maxLat: 12.5,
- maxLon: 102.5,
- minLat: 2.5,
- minLon: 92.5,
- };
- const newBuffer = {
- maxLat: 10,
- maxLon: 100,
- minLat: 5,
- minLon: 95,
- };
- const updateDueToExtent = layer.updateDueToExtent(
- sourceMock,
- { buffer: oldBuffer, areResultsTrimmed: true },
- { buffer: newBuffer });
- expect(updateDueToExtent).toBe(true);
- });
-
- it('should be true when meta has no old buffer', async () => {
- const sourceMock = {
- isFilterByMapBounds: () => { return true; }
- };
- const updateDueToExtent = layer.updateDueToExtent(sourceMock);
- expect(updateDueToExtent).toBe(true);
- });
-
- it('should be true when the new buffer is not contained in the old buffer', async () => {
- const sourceMock = {
- isFilterByMapBounds: () => { return true; }
- };
- const oldBuffer = {
- maxLat: 12.5,
- maxLon: 102.5,
- minLat: 2.5,
- minLon: 92.5,
- };
- const newBuffer = {
- maxLat: 7.5,
- maxLon: 92.5,
- minLat: -2.5,
- minLon: 82.5,
- };
- const updateDueToExtent = layer.updateDueToExtent(sourceMock, { buffer: oldBuffer }, { buffer: newBuffer });
- expect(updateDueToExtent).toBe(true);
- });
- });
-});
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js
index 11a02d58a9198..920253d15eaee 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js
@@ -10,7 +10,6 @@ import {
ES_GEO_FIELD_TYPE,
GEOJSON_FILE,
ES_SIZE_LIMIT,
- FEATURE_ID_PROPERTY_NAME
} from '../../../../common/constants';
import { ClientFileCreateSourceEditor } from './create_client_file_source_editor';
import { ESSearchSource } from '../es_search_source';
@@ -137,21 +136,8 @@ export class GeojsonFileSource extends AbstractVectorSource {
}
async getGeoJsonWithMeta() {
- const copiedPropsFeatures = this._descriptor.__featureCollection.features.map((feature, index) => {
- const properties = feature.properties ? { ...feature.properties } : {};
- properties[FEATURE_ID_PROPERTY_NAME] = index;
- return {
- type: 'Feature',
- geometry: feature.geometry,
- properties,
- };
- });
-
return {
- data: {
- type: 'FeatureCollection',
- features: copiedPropsFeatures
- },
+ data: this._descriptor.__featureCollection,
meta: {}
};
}
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js
index b2e04f56e5718..fcd52683b70ff 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js
@@ -7,7 +7,7 @@
import { AbstractVectorSource } from '../vector_source';
import { VECTOR_SHAPE_TYPES } from '../vector_feature_types';
import React from 'react';
-import { EMS_FILE, FEATURE_ID_PROPERTY_NAME, FIELD_ORIGIN } from '../../../../common/constants';
+import { EMS_FILE, FIELD_ORIGIN } from '../../../../common/constants';
import { getEMSClient } from '../../../meta';
import { EMSFileCreateSourceEditor } from './create_source_editor';
import { i18n } from '@kbn/i18n';
@@ -94,7 +94,7 @@ export class EMSFileSource extends AbstractVectorSource {
return field.type === 'id';
});
featureCollection.features.forEach((feature, index) => {
- feature.properties[FEATURE_ID_PROPERTY_NAME] = emsIdField
+ feature.id = emsIdField
? feature.properties[emsIdField.id]
: index;
});
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js
index c83f12ce992ff..d26bfd8bbeacb 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js
@@ -6,7 +6,7 @@
import { RENDER_AS } from './render_as';
import { getTileBoundingBox } from './geo_tile_utils';
-import { EMPTY_FEATURE_COLLECTION, FEATURE_ID_PROPERTY_NAME } from '../../../../common/constants';
+import { EMPTY_FEATURE_COLLECTION } from '../../../../common/constants';
export function convertToGeoJson({ table, renderAs }) {
@@ -34,9 +34,7 @@ export function convertToGeoJson({ table, renderAs }) {
return;
}
- const properties = {
- [FEATURE_ID_PROPERTY_NAME]: gridKey
- };
+ const properties = {};
metricColumns.forEach(metricColumn => {
properties[metricColumn.aggConfig.id] = row[metricColumn.id];
});
@@ -49,6 +47,7 @@ export function convertToGeoJson({ table, renderAs }) {
geocentroidColumn,
renderAs,
}),
+ id: gridKey,
properties
});
});
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js
index 395b6ac5cc431..3d02b075b3b81 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js
@@ -8,7 +8,6 @@ import _ from 'lodash';
import React, { Fragment, Component } from 'react';
import PropTypes from 'prop-types';
-import { IndexPatternSelect } from 'ui/index_patterns';
import { SingleFieldSelect } from '../../../components/single_field_select';
import { RENDER_AS } from './render_as';
import { indexPatternService } from '../../../kibana_services';
@@ -22,6 +21,9 @@ import {
} from '@elastic/eui';
import { ES_GEO_FIELD_TYPE } from '../../../../common/constants';
+import { npStart } from 'ui/new_platform';
+const { IndexPatternSelect } = npStart.plugins.data.ui;
+
function filterGeoField({ type }) {
return [ES_GEO_FIELD_TYPE.GEO_POINT].includes(type);
}
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js
index c334776e6c4e8..ae9435dc42c69 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js
@@ -6,8 +6,6 @@
import _ from 'lodash';
-import { FEATURE_ID_PROPERTY_NAME } from '../../../../common/constants';
-
const LAT_INDEX = 0;
const LON_INDEX = 1;
@@ -47,10 +45,10 @@ export function convertToLines(esResponse) {
type: 'LineString',
coordinates: [[sourceCentroid.location.lon, sourceCentroid.location.lat], dest]
},
+ id: `${dest.join()},${key}`,
properties: {
- [FEATURE_ID_PROPERTY_NAME]: `${dest.join()},${key}`,
...rest
- }
+ },
});
}
}
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js
index 9f9789374274a..897ded43be28b 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js
@@ -8,7 +8,6 @@ import _ from 'lodash';
import React, { Fragment, Component } from 'react';
import PropTypes from 'prop-types';
-import { IndexPatternSelect } from 'ui/index_patterns';
import { SingleFieldSelect } from '../../../components/single_field_select';
import { indexPatternService } from '../../../kibana_services';
import { i18n } from '@kbn/i18n';
@@ -20,6 +19,8 @@ import {
} from '@elastic/eui';
import { ES_GEO_FIELD_TYPE } from '../../../../common/constants';
+import { npStart } from 'ui/new_platform';
+const { IndexPatternSelect } = npStart.plugins.data.ui;
const GEO_FIELD_TYPES = [ES_GEO_FIELD_TYPE.GEO_POINT];
function filterGeoField({ type }) {
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js
index 61300ed209c1f..a6ba31366d504 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js
@@ -9,7 +9,6 @@ import React, { Fragment, Component } from 'react';
import PropTypes from 'prop-types';
import { EuiFormRow, EuiSpacer, EuiSwitch, EuiCallOut } from '@elastic/eui';
-import { IndexPatternSelect } from 'ui/index_patterns';
import { SingleFieldSelect } from '../../../components/single_field_select';
import { indexPatternService } from '../../../kibana_services';
import { NoIndexPatternCallout } from '../../../components/no_index_pattern_callout';
@@ -19,6 +18,9 @@ import { kfetch } from 'ui/kfetch';
import { ES_GEO_FIELD_TYPE, GIS_API_PATH, ES_SIZE_LIMIT } from '../../../../common/constants';
import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants';
+import { npStart } from 'ui/new_platform';
+const { IndexPatternSelect } = npStart.plugins.data.ui;
+
function filterGeoField(field) {
return [ES_GEO_FIELD_TYPE.GEO_POINT, ES_GEO_FIELD_TYPE.GEO_SHAPE].includes(field.type);
}
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js
index ffccb18a69192..e29887edcf7d9 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js
@@ -10,7 +10,7 @@ import { CreateSourceEditor } from './create_source_editor';
import { getKibanaRegionList } from '../../../meta';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
-import { FEATURE_ID_PROPERTY_NAME, FIELD_ORIGIN } from '../../../../common/constants';
+import { FIELD_ORIGIN } from '../../../../common/constants';
import { KibanaRegionField } from '../../fields/kibana_region_field';
export class KibanaRegionmapSource extends AbstractVectorSource {
@@ -91,9 +91,6 @@ export class KibanaRegionmapSource extends AbstractVectorSource {
featureCollectionPath: vectorFileMeta.meta.feature_collection_path,
fetchUrl: vectorFileMeta.url
});
- featureCollection.features.forEach((feature, index) => {
- feature.properties[FEATURE_ID_PROPERTY_NAME] = index;
- });
return {
data: featureCollection
};
diff --git a/x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.js b/x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.js
new file mode 100644
index 0000000000000..2c0d08f86cfc0
--- /dev/null
+++ b/x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.js
@@ -0,0 +1,57 @@
+/*
+ * 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 _ from 'lodash';
+import { FEATURE_ID_PROPERTY_NAME } from '../../../common/constants';
+
+let idCounter = 0;
+
+function generateNumericalId() {
+ const newId = idCounter < Number.MAX_SAFE_INTEGER ? idCounter : 0;
+ idCounter = newId + 1;
+ return newId;
+}
+
+export function assignFeatureIds(featureCollection) {
+
+ // wrt https://github.com/elastic/kibana/issues/39317
+ // In constrained resource environments, mapbox-gl may throw a stackoverflow error due to hitting the browser's recursion limit. This crashes Kibana.
+ // This error is thrown in mapbox-gl's quicksort implementation, when it is sorting all the features by id.
+ // This is a work-around to avoid hitting such a worst-case
+ // This was tested as a suitable work-around for mapbox-gl 0.54
+ // The core issue itself is likely related to https://github.com/mapbox/mapbox-gl-js/issues/6086
+
+ // This only shuffles the id-assignment, _not_ the features in the collection
+ // The reason for this is that we do not want to modify the feature-ordering, which is the responsiblity of the VectorSource#.
+ const ids = [];
+ for (let i = 0; i < featureCollection.features.length; i++) {
+ const id = generateNumericalId();
+ ids.push(id);
+ }
+
+ const randomizedIds = _.shuffle(ids);
+ const features = [];
+ for (let i = 0; i < featureCollection.features.length; i++) {
+ const numericId = randomizedIds[i];
+ const feature = featureCollection.features[i];
+ features.push({
+ type: 'Feature',
+ geometry: feature.geometry, // do not copy geometry, this object can be massive
+ properties: {
+ // preserve feature id provided by source so features can be referenced across fetches
+ [FEATURE_ID_PROPERTY_NAME]: feature.id == null ? numericId : feature.id,
+ // create new object for properties so original is not polluted with kibana internal props
+ ...feature.properties,
+ },
+ id: numericId, // Mapbox feature state id, must be integer
+ });
+ }
+
+ return {
+ type: 'FeatureCollection',
+ features
+ };
+}
diff --git a/x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.test.js b/x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.test.js
new file mode 100644
index 0000000000000..0678070f568a2
--- /dev/null
+++ b/x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.test.js
@@ -0,0 +1,83 @@
+/*
+ * 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 { assignFeatureIds } from './assign_feature_ids';
+import { FEATURE_ID_PROPERTY_NAME } from '../../../common/constants';
+
+const featureId = 'myFeature1';
+
+test('should provide unique id when feature.id is not provided', () => {
+ const featureCollection = {
+ features: [
+ {
+ properties: {}
+ },
+ {
+ properties: {}
+ },
+ ]
+ };
+
+ const updatedFeatureCollection = assignFeatureIds(featureCollection);
+ const feature1 = updatedFeatureCollection.features[0];
+ const feature2 = updatedFeatureCollection.features[1];
+ expect(typeof feature1.id).toBe('number');
+ expect(typeof feature2.id).toBe('number');
+ expect(feature1.id).toBe(feature1.properties[FEATURE_ID_PROPERTY_NAME]);
+ expect(feature1.id).not.toBe(feature2.id);
+});
+
+test('should preserve feature id when provided', () => {
+ const featureCollection = {
+ features: [
+ {
+ id: featureId,
+ properties: {}
+ }
+ ]
+ };
+
+ const updatedFeatureCollection = assignFeatureIds(featureCollection);
+ const feature1 = updatedFeatureCollection.features[0];
+ expect(typeof feature1.id).toBe('number');
+ expect(feature1.id).not.toBe(feature1.properties[FEATURE_ID_PROPERTY_NAME]);
+ expect(feature1.properties[FEATURE_ID_PROPERTY_NAME]).toBe(featureId);
+});
+
+test('should preserve feature id for falsy value', () => {
+ const featureCollection = {
+ features: [
+ {
+ id: 0,
+ properties: {}
+ }
+ ]
+ };
+
+ const updatedFeatureCollection = assignFeatureIds(featureCollection);
+ const feature1 = updatedFeatureCollection.features[0];
+ expect(typeof feature1.id).toBe('number');
+ expect(feature1.id).not.toBe(feature1.properties[FEATURE_ID_PROPERTY_NAME]);
+ expect(feature1.properties[FEATURE_ID_PROPERTY_NAME]).toBe(0);
+});
+
+test('should not modify original feature properties', () => {
+ const featureProperties = {};
+ const featureCollection = {
+ features: [
+ {
+ id: featureId,
+ properties: featureProperties
+ }
+ ]
+ };
+
+ const updatedFeatureCollection = assignFeatureIds(featureCollection);
+ const feature1 = updatedFeatureCollection.features[0];
+ expect(feature1.properties[FEATURE_ID_PROPERTY_NAME]).toBe(featureId);
+ expect(featureProperties).not.toHaveProperty(FEATURE_ID_PROPERTY_NAME);
+});
+
diff --git a/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.js b/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.js
new file mode 100644
index 0000000000000..610c704b34ec6
--- /dev/null
+++ b/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.js
@@ -0,0 +1,130 @@
+/*
+ * 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 _ from 'lodash';
+import turf from 'turf';
+import turfBooleanContains from '@turf/boolean-contains';
+import { isRefreshOnlyQuery } from './is_refresh_only_query';
+
+const SOURCE_UPDATE_REQUIRED = true;
+const NO_SOURCE_UPDATE_REQUIRED = false;
+
+export function updateDueToExtent(source, prevMeta = {}, nextMeta = {}) {
+ const extentAware = source.isFilterByMapBounds();
+ if (!extentAware) {
+ return NO_SOURCE_UPDATE_REQUIRED;
+ }
+
+ const { buffer: previousBuffer } = prevMeta;
+ const { buffer: newBuffer } = nextMeta;
+
+ if (!previousBuffer) {
+ return SOURCE_UPDATE_REQUIRED;
+ }
+
+ if (_.isEqual(previousBuffer, newBuffer)) {
+ return NO_SOURCE_UPDATE_REQUIRED;
+ }
+
+ const previousBufferGeometry = turf.bboxPolygon([
+ previousBuffer.minLon,
+ previousBuffer.minLat,
+ previousBuffer.maxLon,
+ previousBuffer.maxLat
+ ]);
+ const newBufferGeometry = turf.bboxPolygon([
+ newBuffer.minLon,
+ newBuffer.minLat,
+ newBuffer.maxLon,
+ newBuffer.maxLat
+ ]);
+ const doesPreviousBufferContainNewBuffer = turfBooleanContains(previousBufferGeometry, newBufferGeometry);
+
+ const isTrimmed = _.get(prevMeta, 'areResultsTrimmed', false);
+ return doesPreviousBufferContainNewBuffer && !isTrimmed
+ ? NO_SOURCE_UPDATE_REQUIRED
+ : SOURCE_UPDATE_REQUIRED;
+}
+
+export async function canSkipSourceUpdate({ source, prevDataRequest, nextMeta }) {
+
+ const timeAware = await source.isTimeAware();
+ const refreshTimerAware = await source.isRefreshTimerAware();
+ const extentAware = source.isFilterByMapBounds();
+ const isFieldAware = source.isFieldAware();
+ const isQueryAware = source.isQueryAware();
+ const isGeoGridPrecisionAware = source.isGeoGridPrecisionAware();
+
+ if (
+ !timeAware &&
+ !refreshTimerAware &&
+ !extentAware &&
+ !isFieldAware &&
+ !isQueryAware &&
+ !isGeoGridPrecisionAware
+ ) {
+ return (prevDataRequest && prevDataRequest.hasDataOrRequestInProgress());
+ }
+
+ if (!prevDataRequest) {
+ return false;
+ }
+ const prevMeta = prevDataRequest.getMeta();
+ if (!prevMeta) {
+ return false;
+ }
+
+ let updateDueToTime = false;
+ if (timeAware) {
+ updateDueToTime = !_.isEqual(prevMeta.timeFilters, nextMeta.timeFilters);
+ }
+
+ let updateDueToRefreshTimer = false;
+ if (refreshTimerAware && nextMeta.refreshTimerLastTriggeredAt) {
+ updateDueToRefreshTimer = !_.isEqual(prevMeta.refreshTimerLastTriggeredAt, nextMeta.refreshTimerLastTriggeredAt);
+ }
+
+ let updateDueToFields = false;
+ if (isFieldAware) {
+ updateDueToFields = !_.isEqual(prevMeta.fieldNames, nextMeta.fieldNames);
+ }
+
+ let updateDueToQuery = false;
+ let updateDueToFilters = false;
+ let updateDueToSourceQuery = false;
+ let updateDueToApplyGlobalQuery = false;
+ if (isQueryAware) {
+ updateDueToApplyGlobalQuery = prevMeta.applyGlobalQuery !== nextMeta.applyGlobalQuery;
+ updateDueToSourceQuery = !_.isEqual(prevMeta.sourceQuery, nextMeta.sourceQuery);
+ if (nextMeta.applyGlobalQuery) {
+ updateDueToQuery = !_.isEqual(prevMeta.query, nextMeta.query);
+ updateDueToFilters = !_.isEqual(prevMeta.filters, nextMeta.filters);
+ } else {
+ // Global filters and query are not applied to layer search request so no re-fetch required.
+ // Exception is "Refresh" query.
+ updateDueToQuery = isRefreshOnlyQuery(prevMeta.query, nextMeta.query);
+ }
+ }
+
+ let updateDueToPrecisionChange = false;
+ if (isGeoGridPrecisionAware) {
+ updateDueToPrecisionChange = !_.isEqual(prevMeta.geogridPrecision, nextMeta.geogridPrecision);
+ }
+
+ const updateDueToExtentChange = updateDueToExtent(source, prevMeta, nextMeta);
+
+ const updateDueToSourceMetaChange = !_.isEqual(prevMeta.sourceMeta, nextMeta.sourceMeta);
+
+ return !updateDueToTime
+ && !updateDueToRefreshTimer
+ && !updateDueToExtentChange
+ && !updateDueToFields
+ && !updateDueToQuery
+ && !updateDueToFilters
+ && !updateDueToSourceQuery
+ && !updateDueToApplyGlobalQuery
+ && !updateDueToPrecisionChange
+ && !updateDueToSourceMetaChange;
+}
diff --git a/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.test.js b/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.test.js
new file mode 100644
index 0000000000000..77359a6def48f
--- /dev/null
+++ b/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.test.js
@@ -0,0 +1,287 @@
+/*
+ * 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 { canSkipSourceUpdate, updateDueToExtent } from './can_skip_fetch';
+import { DataRequest } from './data_request';
+
+describe('updateDueToExtent', () => {
+
+ it('should be false when the source is not extent aware', async () => {
+ const sourceMock = {
+ isFilterByMapBounds: () => { return false; }
+ };
+ expect(updateDueToExtent(sourceMock)).toBe(false);
+ });
+
+ describe('source is extent aware', () => {
+ const sourceMock = {
+ isFilterByMapBounds: () => { return true; }
+ };
+
+ it('should be false when buffers are the same', async () => {
+ const oldBuffer = {
+ maxLat: 12.5,
+ maxLon: 102.5,
+ minLat: 2.5,
+ minLon: 92.5,
+ };
+ const newBuffer = {
+ maxLat: 12.5,
+ maxLon: 102.5,
+ minLat: 2.5,
+ minLon: 92.5,
+ };
+ expect(updateDueToExtent(sourceMock, { buffer: oldBuffer }, { buffer: newBuffer }))
+ .toBe(false);
+ });
+
+ it('should be false when the new buffer is contained in the old buffer', async () => {
+ const oldBuffer = {
+ maxLat: 12.5,
+ maxLon: 102.5,
+ minLat: 2.5,
+ minLon: 92.5,
+ };
+ const newBuffer = {
+ maxLat: 10,
+ maxLon: 100,
+ minLat: 5,
+ minLon: 95,
+ };
+ expect(updateDueToExtent(sourceMock, { buffer: oldBuffer }, { buffer: newBuffer })).toBe(false);
+ });
+
+ it('should be true when the new buffer is contained in the old buffer and the past results were truncated', async () => {
+ const oldBuffer = {
+ maxLat: 12.5,
+ maxLon: 102.5,
+ minLat: 2.5,
+ minLon: 92.5,
+ };
+ const newBuffer = {
+ maxLat: 10,
+ maxLon: 100,
+ minLat: 5,
+ minLon: 95,
+ };
+ expect(updateDueToExtent(
+ sourceMock,
+ { buffer: oldBuffer, areResultsTrimmed: true },
+ { buffer: newBuffer }
+ )).toBe(true);
+ });
+
+ it('should be true when meta has no old buffer', async () => {
+ expect(updateDueToExtent(sourceMock)).toBe(true);
+ });
+
+ it('should be true when the new buffer is not contained in the old buffer', async () => {
+ const oldBuffer = {
+ maxLat: 12.5,
+ maxLon: 102.5,
+ minLat: 2.5,
+ minLon: 92.5,
+ };
+ const newBuffer = {
+ maxLat: 7.5,
+ maxLon: 92.5,
+ minLat: -2.5,
+ minLon: 82.5,
+ };
+ expect(updateDueToExtent(sourceMock, { buffer: oldBuffer }, { buffer: newBuffer })).toBe(true);
+ });
+ });
+});
+
+describe('canSkipSourceUpdate', () => {
+ const SOURCE_DATA_REQUEST_ID = 'foo';
+
+ describe('isQueryAware', () => {
+
+ const queryAwareSourceMock = {
+ isTimeAware: () => { return false; },
+ isRefreshTimerAware: () => { return false; },
+ isFilterByMapBounds: () => { return false; },
+ isFieldAware: () => { return false; },
+ isQueryAware: () => { return true; },
+ isGeoGridPrecisionAware: () => { return false; },
+ };
+ const prevFilters = [];
+ const prevQuery = {
+ language: 'kuery',
+ query: 'machine.os.keyword : "win 7"',
+ queryLastTriggeredAt: '2019-04-25T20:53:22.331Z'
+ };
+
+ describe('applyGlobalQuery is false', () => {
+
+ const prevApplyGlobalQuery = false;
+
+ const prevDataRequest = new DataRequest({
+ dataId: SOURCE_DATA_REQUEST_ID,
+ dataMeta: {
+ applyGlobalQuery: prevApplyGlobalQuery,
+ filters: prevFilters,
+ query: prevQuery,
+ }
+ });
+
+ it('can skip update when filter changes', async () => {
+ const nextMeta = {
+ applyGlobalQuery: prevApplyGlobalQuery,
+ filters: [prevQuery],
+ query: prevQuery,
+ };
+
+ const canSkipUpdate = await canSkipSourceUpdate({
+ source: queryAwareSourceMock,
+ prevDataRequest,
+ nextMeta,
+ });
+
+ expect(canSkipUpdate).toBe(true);
+ });
+
+ it('can skip update when query changes', async () => {
+ const nextMeta = {
+ applyGlobalQuery: prevApplyGlobalQuery,
+ filters: prevFilters,
+ query: {
+ ...prevQuery,
+ query: 'a new query string',
+ }
+ };
+
+ const canSkipUpdate = await canSkipSourceUpdate({
+ source: queryAwareSourceMock,
+ prevDataRequest,
+ nextMeta
+ });
+
+ expect(canSkipUpdate).toBe(true);
+ });
+
+ it('can not skip update when query is refreshed', async () => {
+ const nextMeta = {
+ applyGlobalQuery: prevApplyGlobalQuery,
+ filters: prevFilters,
+ query: {
+ ...prevQuery,
+ queryLastTriggeredAt: 'sometime layer when Refresh button is clicked'
+ }
+ };
+
+ const canSkipUpdate = await canSkipSourceUpdate({
+ source: queryAwareSourceMock,
+ prevDataRequest,
+ nextMeta
+ });
+
+ expect(canSkipUpdate).toBe(false);
+ });
+
+ it('can not skip update when applyGlobalQuery changes', async () => {
+ const nextMeta = {
+ applyGlobalQuery: !prevApplyGlobalQuery,
+ filters: prevFilters,
+ query: prevQuery
+ };
+
+ const canSkipUpdate = await canSkipSourceUpdate({
+ source: queryAwareSourceMock,
+ prevDataRequest,
+ nextMeta
+ });
+
+ expect(canSkipUpdate).toBe(false);
+ });
+ });
+
+ describe('applyGlobalQuery is true', () => {
+
+ const prevApplyGlobalQuery = true;
+
+ const prevDataRequest = new DataRequest({
+ dataId: SOURCE_DATA_REQUEST_ID,
+ dataMeta: {
+ applyGlobalQuery: prevApplyGlobalQuery,
+ filters: prevFilters,
+ query: prevQuery,
+ }
+ });
+
+ it('can not skip update when filter changes', async () => {
+ const nextMeta = {
+ applyGlobalQuery: prevApplyGlobalQuery,
+ filters: [prevQuery],
+ query: prevQuery,
+ };
+
+ const canSkipUpdate = await canSkipSourceUpdate({
+ source: queryAwareSourceMock,
+ prevDataRequest,
+ nextMeta
+ });
+
+ expect(canSkipUpdate).toBe(false);
+ });
+
+ it('can not skip update when query changes', async () => {
+ const nextMeta = {
+ applyGlobalQuery: prevApplyGlobalQuery,
+ filters: prevFilters,
+ query: {
+ ...prevQuery,
+ query: 'a new query string',
+ }
+ };
+
+ const canSkipUpdate = await canSkipSourceUpdate({
+ source: queryAwareSourceMock,
+ prevDataRequest,
+ nextMeta
+ });
+
+ expect(canSkipUpdate).toBe(false);
+ });
+
+ it('can not skip update when query is refreshed', async () => {
+ const nextMeta = {
+ applyGlobalQuery: prevApplyGlobalQuery,
+ filters: prevFilters,
+ query: {
+ ...prevQuery,
+ queryLastTriggeredAt: 'sometime layer when Refresh button is clicked'
+ }
+ };
+
+ const canSkipUpdate = await canSkipSourceUpdate({
+ source: queryAwareSourceMock,
+ prevDataRequest,
+ nextMeta
+ });
+
+ expect(canSkipUpdate).toBe(false);
+ });
+
+ it('can not skip update when applyGlobalQuery changes', async () => {
+ const nextMeta = {
+ applyGlobalQuery: !prevApplyGlobalQuery,
+ filters: prevFilters,
+ query: prevQuery
+ };
+
+ const canSkipUpdate = await canSkipSourceUpdate({
+ source: queryAwareSourceMock,
+ prevDataRequest,
+ nextMeta
+ });
+
+ expect(canSkipUpdate).toBe(false);
+ });
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/maps/public/layers/util/mb_filter_expressions.js b/x-pack/legacy/plugins/maps/public/layers/util/mb_filter_expressions.js
new file mode 100644
index 0000000000000..393c290d69668
--- /dev/null
+++ b/x-pack/legacy/plugins/maps/public/layers/util/mb_filter_expressions.js
@@ -0,0 +1,62 @@
+/*
+ * 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 { GEO_JSON_TYPE, FEATURE_VISIBLE_PROPERTY_NAME } from '../../../common/constants';
+
+const VISIBILITY_FILTER_CLAUSE = ['all',
+ [
+ '==',
+ ['get', FEATURE_VISIBLE_PROPERTY_NAME],
+ true
+ ]
+];
+
+const CLOSED_SHAPE_MB_FILTER = [
+ 'any',
+ ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON],
+ ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON]
+];
+
+const VISIBLE_CLOSED_SHAPE_MB_FILTER = [
+ ...VISIBILITY_FILTER_CLAUSE,
+ CLOSED_SHAPE_MB_FILTER,
+];
+
+const ALL_SHAPE_MB_FILTER = [
+ 'any',
+ ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON],
+ ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON],
+ ['==', ['geometry-type'], GEO_JSON_TYPE.LINE_STRING],
+ ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_LINE_STRING]
+];
+
+const VISIBLE_ALL_SHAPE_MB_FILTER = [
+ ...VISIBILITY_FILTER_CLAUSE,
+ ALL_SHAPE_MB_FILTER,
+];
+
+const POINT_MB_FILTER = [
+ 'any',
+ ['==', ['geometry-type'], GEO_JSON_TYPE.POINT],
+ ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POINT]
+];
+
+const VISIBLE_POINT_MB_FILTER = [
+ ...VISIBILITY_FILTER_CLAUSE,
+ POINT_MB_FILTER,
+];
+
+export function getFillFilterExpression(hasJoins) {
+ return hasJoins ? VISIBLE_CLOSED_SHAPE_MB_FILTER : CLOSED_SHAPE_MB_FILTER;
+}
+
+export function getLineFilterExpression(hasJoins) {
+ return hasJoins ? VISIBLE_ALL_SHAPE_MB_FILTER : ALL_SHAPE_MB_FILTER;
+}
+
+export function getPointFilterExpression(hasJoins) {
+ return hasJoins ? VISIBLE_POINT_MB_FILTER : POINT_MB_FILTER;
+}
diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js
index 362c7bfd72540..57126bb7681b8 100644
--- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js
+++ b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js
@@ -10,7 +10,6 @@ import { AbstractLayer } from './layer';
import { VectorStyle } from './styles/vector/vector_style';
import { InnerJoin } from './joins/inner_join';
import {
- GEO_JSON_TYPE,
FEATURE_ID_PROPERTY_NAME,
SOURCE_DATA_ID_ORIGIN,
FEATURE_VISIBLE_PROPERTY_NAME,
@@ -19,55 +18,16 @@ import {
} from '../../common/constants';
import _ from 'lodash';
import { JoinTooltipProperty } from './tooltips/join_tooltip_property';
-import { isRefreshOnlyQuery } from './util/is_refresh_only_query';
import { EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { DataRequestAbortError } from './util/data_request';
-
-const VISIBILITY_FILTER_CLAUSE = ['all',
- [
- '==',
- ['get', FEATURE_VISIBLE_PROPERTY_NAME],
- true
- ]
-];
-
-const FILL_LAYER_MB_FILTER = [
- ...VISIBILITY_FILTER_CLAUSE,
- [
- 'any',
- ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON],
- ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON]
- ]
-];
-
-const LINE_LAYER_MB_FILTER = [...VISIBILITY_FILTER_CLAUSE,
- [
- 'any',
- ['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON],
- ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON],
- ['==', ['geometry-type'], GEO_JSON_TYPE.LINE_STRING],
- ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_LINE_STRING]
- ]
-];
-
-const POINT_LAYER_MB_FILTER = [...VISIBILITY_FILTER_CLAUSE,
- [
- 'any',
- ['==', ['geometry-type'], GEO_JSON_TYPE.POINT],
- ['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POINT]
- ]
-];
-
-
-let idCounter = 0;
-
-function generateNumericalId() {
- const newId = idCounter < Number.MAX_SAFE_INTEGER ? idCounter : 0;
- idCounter = newId + 1;
- return newId;
-}
-
+import { canSkipSourceUpdate } from './util/can_skip_fetch';
+import { assignFeatureIds } from './util/assign_feature_ids';
+import {
+ getFillFilterExpression,
+ getLineFilterExpression,
+ getPointFilterExpression,
+} from './util/mb_filter_expressions';
export class VectorLayer extends AbstractLayer {
@@ -116,6 +76,10 @@ export class VectorLayer extends AbstractLayer {
});
}
+ _hasJoins() {
+ return this.getValidJoins().length > 0;
+ }
+
isDataLoaded() {
const sourceDataRequest = this.getSourceDataRequest();
if (!sourceDataRequest || !sourceDataRequest.hasData()) {
@@ -265,109 +229,31 @@ export class VectorLayer extends AbstractLayer {
return this._dataRequests.find(dataRequest => dataRequest.getDataId() === sourceDataId);
}
- async _canSkipSourceUpdate(source, sourceDataId, nextMeta) {
-
- const timeAware = await source.isTimeAware();
- const refreshTimerAware = await source.isRefreshTimerAware();
- const extentAware = source.isFilterByMapBounds();
- const isFieldAware = source.isFieldAware();
- const isQueryAware = source.isQueryAware();
- const isGeoGridPrecisionAware = source.isGeoGridPrecisionAware();
-
- if (
- !timeAware &&
- !refreshTimerAware &&
- !extentAware &&
- !isFieldAware &&
- !isQueryAware &&
- !isGeoGridPrecisionAware
- ) {
- const sourceDataRequest = this._findDataRequestForSource(sourceDataId);
- return (sourceDataRequest && sourceDataRequest.hasDataOrRequestInProgress());
- }
-
- const sourceDataRequest = this._findDataRequestForSource(sourceDataId);
- if (!sourceDataRequest) {
- return false;
- }
- const prevMeta = sourceDataRequest.getMeta();
- if (!prevMeta) {
- return false;
- }
-
- let updateDueToTime = false;
- if (timeAware) {
- updateDueToTime = !_.isEqual(prevMeta.timeFilters, nextMeta.timeFilters);
- }
-
- let updateDueToRefreshTimer = false;
- if (refreshTimerAware && nextMeta.refreshTimerLastTriggeredAt) {
- updateDueToRefreshTimer = !_.isEqual(prevMeta.refreshTimerLastTriggeredAt, nextMeta.refreshTimerLastTriggeredAt);
- }
-
- let updateDueToFields = false;
- if (isFieldAware) {
- updateDueToFields = !_.isEqual(prevMeta.fieldNames, nextMeta.fieldNames);
- }
-
- let updateDueToQuery = false;
- let updateDueToFilters = false;
- let updateDueToSourceQuery = false;
- let updateDueToApplyGlobalQuery = false;
- if (isQueryAware) {
- updateDueToApplyGlobalQuery = prevMeta.applyGlobalQuery !== nextMeta.applyGlobalQuery;
- updateDueToSourceQuery = !_.isEqual(prevMeta.sourceQuery, nextMeta.sourceQuery);
- if (nextMeta.applyGlobalQuery) {
- updateDueToQuery = !_.isEqual(prevMeta.query, nextMeta.query);
- updateDueToFilters = !_.isEqual(prevMeta.filters, nextMeta.filters);
- } else {
- // Global filters and query are not applied to layer search request so no re-fetch required.
- // Exception is "Refresh" query.
- updateDueToQuery = isRefreshOnlyQuery(prevMeta.query, nextMeta.query);
- }
- }
-
- let updateDueToPrecisionChange = false;
- if (isGeoGridPrecisionAware) {
- updateDueToPrecisionChange = !_.isEqual(prevMeta.geogridPrecision, nextMeta.geogridPrecision);
- }
- const updateDueToExtentChange = this.updateDueToExtent(source, prevMeta, nextMeta);
-
- const updateDueToSourceMetaChange = !_.isEqual(prevMeta.sourceMeta, nextMeta.sourceMeta);
-
- return !updateDueToTime
- && !updateDueToRefreshTimer
- && !updateDueToExtentChange
- && !updateDueToFields
- && !updateDueToQuery
- && !updateDueToFilters
- && !updateDueToSourceQuery
- && !updateDueToApplyGlobalQuery
- && !updateDueToPrecisionChange
- && !updateDueToSourceMetaChange;
- }
async _syncJoin({ join, startLoading, stopLoading, onLoadError, registerCancelCallback, dataFilters }) {
const joinSource = join.getRightJoinSource();
const sourceDataId = join.getSourceId();
const requestToken = Symbol(`layer-join-refresh:${this.getId()} - ${sourceDataId}`);
-
const searchFilters = {
...dataFilters,
fieldNames: joinSource.getFieldNames(),
sourceQuery: joinSource.getWhereQuery(),
applyGlobalQuery: joinSource.getApplyGlobalQuery(),
};
- const canSkip = await this._canSkipSourceUpdate(joinSource, sourceDataId, searchFilters);
- if (canSkip) {
- const sourceDataRequest = this._findDataRequestForSource(sourceDataId);
- const propertiesMap = sourceDataRequest ? sourceDataRequest.getData() : null;
+ const prevDataRequest = this._findDataRequestForSource(sourceDataId);
+
+ const canSkipFetch = await canSkipSourceUpdate({
+ source: joinSource,
+ prevDataRequest,
+ nextMeta: searchFilters,
+ });
+ if (canSkipFetch) {
return {
dataHasChanged: false,
join: join,
- propertiesMap: propertiesMap
+ propertiesMap: prevDataRequest.getData()
};
}
@@ -466,27 +352,32 @@ export class VectorLayer extends AbstractLayer {
const requestToken = Symbol(`layer-source-refresh:${this.getId()} - source`);
const searchFilters = this._getSearchFilters(dataFilters);
- const canSkip = await this._canSkipSourceUpdate(this._source, SOURCE_DATA_ID_ORIGIN, searchFilters);
- if (canSkip) {
- const sourceDataRequest = this.getSourceDataRequest();
+ const prevDataRequest = this.getSourceDataRequest();
+
+ const canSkipFetch = await canSkipSourceUpdate({
+ source: this._source,
+ prevDataRequest,
+ nextMeta: searchFilters,
+ });
+ if (canSkipFetch) {
return {
refreshed: false,
- featureCollection: sourceDataRequest.getData()
+ featureCollection: prevDataRequest.getData()
};
}
try {
startLoading(SOURCE_DATA_ID_ORIGIN, requestToken, searchFilters);
const layerName = await this.getDisplayName();
- const { data: featureCollection, meta } =
+ const { data: sourceFeatureCollection, meta } =
await this._source.getGeoJsonWithMeta(layerName, searchFilters,
registerCancelCallback.bind(null, requestToken)
);
- this._assignIdsToFeatures(featureCollection);
- stopLoading(SOURCE_DATA_ID_ORIGIN, requestToken, featureCollection, meta);
+ const layerFeatureCollection = assignFeatureIds(sourceFeatureCollection);
+ stopLoading(SOURCE_DATA_ID_ORIGIN, requestToken, layerFeatureCollection, meta);
return {
refreshed: true,
- featureCollection: featureCollection
+ featureCollection: layerFeatureCollection
};
} catch (error) {
if (!(error instanceof DataRequestAbortError)) {
@@ -498,44 +389,21 @@ export class VectorLayer extends AbstractLayer {
}
}
- _assignIdsToFeatures(featureCollection) {
-
- //wrt https://github.com/elastic/kibana/issues/39317
- //In constrained resource environments, mapbox-gl may throw a stackoverflow error due to hitting the browser's recursion limit. This crashes Kibana.
- //This error is thrown in mapbox-gl's quicksort implementation, when it is sorting all the features by id.
- //This is a work-around to avoid hitting such a worst-case
- //This was tested as a suitable work-around for mapbox-gl 0.54
- //The core issue itself is likely related to https://github.com/mapbox/mapbox-gl-js/issues/6086
-
- //This only shuffles the id-assignment, _not_ the features in the collection
- //The reason for this is that we do not want to modify the feature-ordering, which is the responsiblity of the VectorSource#.
- const ids = [];
- for (let i = 0; i < featureCollection.features.length; i++) {
- const id = generateNumericalId();
- ids.push(id);
- }
-
- const randomizedIds = _.shuffle(ids);
- for (let i = 0; i < featureCollection.features.length; i++) {
- const id = randomizedIds[i];
- const feature = featureCollection.features[i];
- feature.id = id; // Mapbox feature state id, must be integer
- }
- }
-
async syncData(syncContext) {
if (!this.isVisible() || !this.showAtZoomLevel(syncContext.dataFilters.zoom)) {
return;
}
const sourceResult = await this._syncSource(syncContext);
- if (!sourceResult.featureCollection || !sourceResult.featureCollection.features.length) {
+ if (
+ !sourceResult.featureCollection ||
+ !sourceResult.featureCollection.features.length ||
+ !this._hasJoins()) {
return;
}
const joinStates = await this._syncJoins(syncContext);
await this._performInnerJoins(sourceResult, joinStates, syncContext.updateSourceData);
-
}
_getSourceFeatureCollection() {
@@ -605,7 +473,11 @@ export class VectorLayer extends AbstractLayer {
source: sourceId,
paint: {}
});
- mbMap.setFilter(pointLayerId, POINT_LAYER_MB_FILTER);
+ }
+
+ const filterExpr = getPointFilterExpression(this._hasJoins());
+ if (filterExpr !== mbMap.getFilter(pointLayerId)) {
+ mbMap.setFilter(pointLayerId, filterExpr);
}
this._style.setMBPaintPropertiesForPoints({
@@ -626,7 +498,11 @@ export class VectorLayer extends AbstractLayer {
type: 'symbol',
source: sourceId,
});
- mbMap.setFilter(symbolLayerId, POINT_LAYER_MB_FILTER);
+ }
+
+ const filterExpr = getPointFilterExpression(this._hasJoins());
+ if (filterExpr !== mbMap.getFilter(symbolLayerId)) {
+ mbMap.setFilter(symbolLayerId, filterExpr);
}
this._style.setMBSymbolPropertiesForPoints({
@@ -640,6 +516,7 @@ export class VectorLayer extends AbstractLayer {
const sourceId = this.getId();
const fillLayerId = this._getMbPolygonLayerId();
const lineLayerId = this._getMbLineLayerId();
+ const hasJoins = this._hasJoins();
if (!mbMap.getLayer(fillLayerId)) {
mbMap.addLayer({
id: fillLayerId,
@@ -647,7 +524,6 @@ export class VectorLayer extends AbstractLayer {
source: sourceId,
paint: {}
});
- mbMap.setFilter(fillLayerId, FILL_LAYER_MB_FILTER);
}
if (!mbMap.getLayer(lineLayerId)) {
mbMap.addLayer({
@@ -656,7 +532,6 @@ export class VectorLayer extends AbstractLayer {
source: sourceId,
paint: {}
});
- mbMap.setFilter(lineLayerId, LINE_LAYER_MB_FILTER);
}
this._style.setMBPaintProperties({
alpha: this.getAlpha(),
@@ -666,9 +541,18 @@ export class VectorLayer extends AbstractLayer {
});
this.syncVisibilityWithMb(mbMap, fillLayerId);
+ mbMap.setLayerZoomRange(fillLayerId, this._descriptor.minZoom, this._descriptor.maxZoom);
+ const fillFilterExpr = getFillFilterExpression(hasJoins);
+ if (fillFilterExpr !== mbMap.getFilter(fillLayerId)) {
+ mbMap.setFilter(fillLayerId, fillFilterExpr);
+ }
+
this.syncVisibilityWithMb(mbMap, lineLayerId);
mbMap.setLayerZoomRange(lineLayerId, this._descriptor.minZoom, this._descriptor.maxZoom);
- mbMap.setLayerZoomRange(fillLayerId, this._descriptor.minZoom, this._descriptor.maxZoom);
+ const lineFilterExpr = getLineFilterExpression(hasJoins);
+ if (lineFilterExpr !== mbMap.getFilter(lineLayerId)) {
+ mbMap.setFilter(lineLayerId, lineFilterExpr);
+ }
}
_syncStylePropertiesWithMb(mbMap) {
diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.test.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.test.js
deleted file mode 100644
index 0a07582c57856..0000000000000
--- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.test.js
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-jest.mock('./joins/inner_join', () => ({
- InnerJoin: Object
-}));
-
-jest.mock('./tooltips/join_tooltip_property', () => ({
- JoinTooltipProperty: Object
-}));
-
-import { VectorLayer } from './vector_layer';
-
-describe('_canSkipSourceUpdate', () => {
- const SOURCE_DATA_REQUEST_ID = 'foo';
-
- describe('isQueryAware', () => {
-
- const queryAwareSourceMock = {
- isTimeAware: () => { return false; },
- isRefreshTimerAware: () => { return false; },
- isFilterByMapBounds: () => { return false; },
- isFieldAware: () => { return false; },
- isQueryAware: () => { return true; },
- isGeoGridPrecisionAware: () => { return false; },
- };
- const prevFilters = [];
- const prevQuery = {
- language: 'kuery',
- query: 'machine.os.keyword : "win 7"',
- queryLastTriggeredAt: '2019-04-25T20:53:22.331Z'
- };
-
- describe('applyGlobalQuery is false', () => {
-
- const prevApplyGlobalQuery = false;
-
- const vectorLayer = new VectorLayer({
- layerDescriptor: {
- __dataRequests: [
- {
- dataId: SOURCE_DATA_REQUEST_ID,
- dataMeta: {
- applyGlobalQuery: prevApplyGlobalQuery,
- filters: prevFilters,
- query: prevQuery,
- }
- }
- ]
- }
- });
-
- it('can skip update when filter changes', async () => {
- const searchFilters = {
- applyGlobalQuery: prevApplyGlobalQuery,
- filters: [prevQuery],
- query: prevQuery,
- };
-
- const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters);
-
- expect(canSkipUpdate).toBe(true);
- });
-
- it('can skip update when query changes', async () => {
- const searchFilters = {
- applyGlobalQuery: prevApplyGlobalQuery,
- filters: prevFilters,
- query: {
- ...prevQuery,
- query: 'a new query string',
- }
- };
-
- const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters);
-
- expect(canSkipUpdate).toBe(true);
- });
-
- it('can not skip update when query is refreshed', async () => {
- const searchFilters = {
- applyGlobalQuery: prevApplyGlobalQuery,
- filters: prevFilters,
- query: {
- ...prevQuery,
- queryLastTriggeredAt: 'sometime layer when Refresh button is clicked'
- }
- };
-
- const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters);
-
- expect(canSkipUpdate).toBe(false);
- });
-
- it('can not skip update when applyGlobalQuery changes', async () => {
- const searchFilters = {
- applyGlobalQuery: !prevApplyGlobalQuery,
- filters: prevFilters,
- query: prevQuery
- };
-
- const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters);
-
- expect(canSkipUpdate).toBe(false);
- });
- });
-
- describe('applyGlobalQuery is true', () => {
-
- const prevApplyGlobalQuery = true;
-
- const vectorLayer = new VectorLayer({
- layerDescriptor: {
- __dataRequests: [
- {
- dataId: SOURCE_DATA_REQUEST_ID,
- dataMeta: {
- applyGlobalQuery: prevApplyGlobalQuery,
- filters: prevFilters,
- query: prevQuery,
- }
- }
- ]
- }
- });
-
- it('can not skip update when filter changes', async () => {
- const searchFilters = {
- applyGlobalQuery: prevApplyGlobalQuery,
- filters: [prevQuery],
- query: prevQuery,
- };
-
- const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters);
-
- expect(canSkipUpdate).toBe(false);
- });
-
- it('can not skip update when query changes', async () => {
- const searchFilters = {
- applyGlobalQuery: prevApplyGlobalQuery,
- filters: prevFilters,
- query: {
- ...prevQuery,
- query: 'a new query string',
- }
- };
-
- const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters);
-
- expect(canSkipUpdate).toBe(false);
- });
-
- it('can not skip update when query is refreshed', async () => {
- const searchFilters = {
- applyGlobalQuery: prevApplyGlobalQuery,
- filters: prevFilters,
- query: {
- ...prevQuery,
- queryLastTriggeredAt: 'sometime layer when Refresh button is clicked'
- }
- };
-
- const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters);
-
- expect(canSkipUpdate).toBe(false);
- });
-
- it('can not skip update when applyGlobalQuery changes', async () => {
- const searchFilters = {
- applyGlobalQuery: !prevApplyGlobalQuery,
- filters: prevFilters,
- query: prevQuery
- };
-
- const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters);
-
- expect(canSkipUpdate).toBe(false);
- });
- });
- });
-});
diff --git a/x-pack/legacy/plugins/ml/public/application/contexts/kibana/__mocks__/saved_search.ts b/x-pack/legacy/plugins/ml/public/application/contexts/kibana/__mocks__/saved_search.ts
index 2e442c5c61b1e..2bff760ed3711 100644
--- a/x-pack/legacy/plugins/ml/public/application/contexts/kibana/__mocks__/saved_search.ts
+++ b/x-pack/legacy/plugins/ml/public/application/contexts/kibana/__mocks__/saved_search.ts
@@ -5,11 +5,12 @@
*/
import { searchSourceMock } from '../../../../../../../../../src/legacy/ui/public/courier/search_source/mocks';
+import { SearchSourceContract } from '../../../../../../../../../src/legacy/ui/public/courier';
export const savedSearchMock = {
id: 'the-saved-search-id',
title: 'the-saved-search-title',
- searchSource: searchSourceMock,
+ searchSource: searchSourceMock as SearchSourceContract,
columns: [],
sort: [],
destroy: () => {},
diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/page.tsx b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/page.tsx
index 1caa068620618..642b4c5649a13 100644
--- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/page.tsx
+++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/page.tsx
@@ -176,7 +176,7 @@ export const Page: FC = () => {
const searchSource = currentSavedSearch.searchSource;
const query = searchSource.getField('query');
if (query !== undefined) {
- const queryLanguage = query.language;
+ const queryLanguage = query.language as SEARCH_QUERY_LANGUAGE;
const qryString = query.query;
let qry;
if (queryLanguage === SEARCH_QUERY_LANGUAGE.KUERY) {
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts
index 0e88b291e76fc..455fac9b532d6 100644
--- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts
+++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts
@@ -7,7 +7,7 @@
import { IndexPattern } from 'ui/index_patterns';
import { SavedSearch } from 'src/legacy/core_plugins/kibana/public/discover/types';
import { KibanaConfigTypeFix } from '../../../contexts/kibana';
-import { esQuery, IIndexPattern } from '../../../../../../../../../src/plugins/data/public';
+import { esQuery, Query, IIndexPattern } from '../../../../../../../../../src/plugins/data/public';
export interface SearchItems {
indexPattern: IIndexPattern;
@@ -28,7 +28,7 @@ export function createSearchItems(
// a lucene query_string.
// Using a blank query will cause match_all:{} to be used
// when passed through luceneStringToDsl
- let query = {
+ let query: Query = {
query: '',
language: 'lucene',
};
@@ -45,12 +45,12 @@ export function createSearchItems(
if (indexPattern.id === undefined && savedSearch.id !== undefined) {
const searchSource = savedSearch.searchSource;
- indexPattern = searchSource.getField('index');
+ indexPattern = searchSource.getField('index')!;
- query = searchSource.getField('query');
+ query = searchSource.getField('query')!;
const fs = searchSource.getField('filter');
- const filters = fs.length ? fs : [];
+ const filters = Array.isArray(fs) ? fs : [];
const esQueryConfigs = esQuery.getEsQueryConfig(kibanaConfig);
combinedQuery = esQuery.buildEsQuery(indexPattern, [query], filters, esQueryConfigs);
diff --git a/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts b/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts
index a614be547abde..aeec71462308e 100644
--- a/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts
+++ b/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts
@@ -38,7 +38,7 @@ export function loadNewJobCapabilities(
// saved search is being used
// load the index pattern from the saved search
const savedSearch = await savedSearches.get(savedSearchId);
- const indexPattern = savedSearch.searchSource.getField('index');
+ const indexPattern = savedSearch.searchSource.getField('index')!;
await newJobCapsService.initializeFromIndexPattern(indexPattern);
resolve(newJobCapsService.newJobCaps);
} else {
diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_all_stats.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_all_stats.js
index c1425de20d146..7b300939bd470 100644
--- a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_all_stats.js
+++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_all_stats.js
@@ -8,7 +8,8 @@ import expect from '@kbn/expect';
import sinon from 'sinon';
import { addStackStats, getAllStats, handleAllStats } from '../get_all_stats';
-describe('get_all_stats', () => {
+// FAILING: https://github.com/elastic/kibana/issues/51371
+describe.skip('get_all_stats', () => {
const size = 123;
const start = 0;
const end = 1;
diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_cluster_uuids.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_cluster_uuids.js
index e3153670ac58f..a0072e52fc7f7 100644
--- a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_cluster_uuids.js
+++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_cluster_uuids.js
@@ -8,7 +8,8 @@ import expect from '@kbn/expect';
import sinon from 'sinon';
import { getClusterUuids, fetchClusterUuids, handleClusterUuidsResponse } from '../get_cluster_uuids';
-describe('get_cluster_uuids', () => {
+// FAILING: https://github.com/elastic/kibana/issues/51371
+describe.skip('get_cluster_uuids', () => {
const callWith = sinon.stub();
const size = 123;
const server = {
diff --git a/x-pack/legacy/plugins/reporting/common/get_absolute_url.test.ts b/x-pack/legacy/plugins/reporting/common/get_absolute_url.test.ts
index 9bad3b2b08002..cb792fbd6ae03 100644
--- a/x-pack/legacy/plugins/reporting/common/get_absolute_url.test.ts
+++ b/x-pack/legacy/plugins/reporting/common/get_absolute_url.test.ts
@@ -4,88 +4,80 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { createMockServer } from '../test_helpers/create_mock_server';
import { getAbsoluteUrlFactory } from './get_absolute_url';
-test(`by default it builds url using information from server.info.protocol and the server.config`, () => {
- const mockServer = createMockServer('');
- const getAbsoluteUrl = getAbsoluteUrlFactory(mockServer);
+const defaultOptions = {
+ defaultBasePath: 'sbp',
+ protocol: 'http:',
+ hostname: 'localhost',
+ port: 5601,
+};
+
+test(`by default it builds urls using information from server.info.protocol and the server.config`, () => {
+ const getAbsoluteUrl = getAbsoluteUrlFactory(defaultOptions);
const absoluteUrl = getAbsoluteUrl();
expect(absoluteUrl).toBe(`http://localhost:5601/sbp/app/kibana`);
});
test(`uses kibanaServer.protocol if specified`, () => {
- const settings = {
- 'xpack.reporting.kibanaServer.protocol': 'https',
- };
- const mockServer = createMockServer({ settings });
+ const getAbsoluteUrl = getAbsoluteUrlFactory({
+ ...defaultOptions,
+ protocol: 'https:',
+ });
- const getAbsoluteUrl = getAbsoluteUrlFactory(mockServer);
const absoluteUrl = getAbsoluteUrl();
expect(absoluteUrl).toBe(`https://localhost:5601/sbp/app/kibana`);
});
test(`uses kibanaServer.hostname if specified`, () => {
- const settings = {
- 'xpack.reporting.kibanaServer.hostname': 'something-else',
- };
- const mockServer = createMockServer({ settings });
+ const getAbsoluteUrl = getAbsoluteUrlFactory({
+ ...defaultOptions,
+ hostname: 'something-else',
+ });
- const getAbsoluteUrl = getAbsoluteUrlFactory(mockServer);
const absoluteUrl = getAbsoluteUrl();
expect(absoluteUrl).toBe(`http://something-else:5601/sbp/app/kibana`);
});
test(`uses kibanaServer.port if specified`, () => {
- const settings = {
- 'xpack.reporting.kibanaServer.port': 8008,
- };
- const mockServer = createMockServer({ settings });
+ const getAbsoluteUrl = getAbsoluteUrlFactory({
+ ...defaultOptions,
+ port: 8008,
+ });
- const getAbsoluteUrl = getAbsoluteUrlFactory(mockServer);
const absoluteUrl = getAbsoluteUrl();
expect(absoluteUrl).toBe(`http://localhost:8008/sbp/app/kibana`);
});
test(`uses the provided hash`, () => {
- const mockServer = createMockServer('');
-
- const getAbsoluteUrl = getAbsoluteUrlFactory(mockServer);
+ const getAbsoluteUrl = getAbsoluteUrlFactory(defaultOptions);
const hash = '/hash';
const absoluteUrl = getAbsoluteUrl({ hash });
expect(absoluteUrl).toBe(`http://localhost:5601/sbp/app/kibana#${hash}`);
});
test(`uses the provided hash with queryString`, () => {
- const mockServer = createMockServer('');
-
- const getAbsoluteUrl = getAbsoluteUrlFactory(mockServer);
+ const getAbsoluteUrl = getAbsoluteUrlFactory(defaultOptions);
const hash = '/hash?querystring';
const absoluteUrl = getAbsoluteUrl({ hash });
expect(absoluteUrl).toBe(`http://localhost:5601/sbp/app/kibana#${hash}`);
});
test(`uses the provided basePath`, () => {
- const mockServer = createMockServer('');
-
- const getAbsoluteUrl = getAbsoluteUrlFactory(mockServer);
+ const getAbsoluteUrl = getAbsoluteUrlFactory(defaultOptions);
const absoluteUrl = getAbsoluteUrl({ basePath: '/s/marketing' });
expect(absoluteUrl).toBe(`http://localhost:5601/s/marketing/app/kibana`);
});
test(`uses the path`, () => {
- const mockServer = createMockServer('');
-
- const getAbsoluteUrl = getAbsoluteUrlFactory(mockServer);
+ const getAbsoluteUrl = getAbsoluteUrlFactory(defaultOptions);
const path = '/app/canvas';
const absoluteUrl = getAbsoluteUrl({ path });
expect(absoluteUrl).toBe(`http://localhost:5601/sbp${path}`);
});
test(`uses the search`, () => {
- const mockServer = createMockServer('');
-
- const getAbsoluteUrl = getAbsoluteUrlFactory(mockServer);
+ const getAbsoluteUrl = getAbsoluteUrlFactory(defaultOptions);
const search = '_t=123456789';
const absoluteUrl = getAbsoluteUrl({ search });
expect(absoluteUrl).toBe(`http://localhost:5601/sbp/app/kibana?${search}`);
diff --git a/x-pack/legacy/plugins/reporting/common/get_absolute_url.ts b/x-pack/legacy/plugins/reporting/common/get_absolute_url.ts
index 1d34189abcb24..0e350cb1ec011 100644
--- a/x-pack/legacy/plugins/reporting/common/get_absolute_url.ts
+++ b/x-pack/legacy/plugins/reporting/common/get_absolute_url.ts
@@ -5,24 +5,27 @@
*/
import url from 'url';
-import { ServerFacade } from '../types';
-
-export function getAbsoluteUrlFactory(server: ServerFacade) {
- const config = server.config();
+import { AbsoluteURLFactoryOptions } from '../types';
+export const getAbsoluteUrlFactory = ({
+ protocol,
+ hostname,
+ port,
+ defaultBasePath,
+}: AbsoluteURLFactoryOptions) => {
return function getAbsoluteUrl({
- basePath = config.get('server.basePath'),
+ basePath = defaultBasePath,
hash = '',
path = '/app/kibana',
search = '',
} = {}) {
return url.format({
- protocol: config.get('xpack.reporting.kibanaServer.protocol') || server.info.protocol,
- hostname: config.get('xpack.reporting.kibanaServer.hostname') || config.get('server.host'),
- port: config.get('xpack.reporting.kibanaServer.port') || config.get('server.port'),
+ protocol,
+ hostname,
+ port,
pathname: basePath + path,
hash,
search,
});
};
-}
+};
diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts
index 3b82852073421..2b66c77067ed2 100644
--- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts
+++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts
@@ -28,7 +28,14 @@ export async function getFullUrls({
job: JobDocPayloadPNG | JobDocPayloadPDF;
server: ServerFacade;
}) {
- const getAbsoluteUrl = getAbsoluteUrlFactory(server);
+ const config = server.config();
+
+ const getAbsoluteUrl = getAbsoluteUrlFactory({
+ defaultBasePath: config.get('server.basePath'),
+ protocol: config.get('xpack.reporting.kibanaServer.protocol') || server.info.protocol,
+ hostname: config.get('xpack.reporting.kibanaServer.hostname') || config.get('server.host'),
+ port: config.get('xpack.reporting.kibanaServer.port') || config.get('server.port'),
+ });
// PDF and PNG job params put in the url differently
let relativeUrls: string[] = [];
diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/helpers/cancellation_token.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/helpers/cancellation_token.js
index 195a0d4fdbec4..6d638e50af476 100644
--- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/helpers/cancellation_token.js
+++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/helpers/cancellation_token.js
@@ -8,7 +8,8 @@ import expect from '@kbn/expect';
import sinon from 'sinon';
import { CancellationToken } from '../../../../../common/cancellation_token';
-describe('CancellationToken', function () {
+// FAILING: https://github.com/elastic/kibana/issues/51373
+describe.skip('CancellationToken', function () {
let cancellationToken;
beforeEach(function () {
cancellationToken = new CancellationToken();
diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js
index 84549d0680ff3..b2e87482b73a1 100644
--- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js
+++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js
@@ -26,6 +26,7 @@ const defaultWorkerOptions = {
intervalErrorMultiplier: 10
};
+
describe('Worker class', function () {
// some of these tests might be a little slow, give them a little extra time
this.timeout(10000);
@@ -1068,7 +1069,8 @@ describe('Format Job Object', () => {
});
});
-describe('Get Doc Path from ES Response', () => {
+// FAILING: https://github.com/elastic/kibana/issues/51372
+describe.skip('Get Doc Path from ES Response', () => {
it('returns a formatted string after response of an update', function () {
const responseMock = {
_index: 'foo',
diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/__tests__/validate_config.js b/x-pack/legacy/plugins/reporting/server/lib/validate/__tests__/validate_config.js
index 9a74ba63b8e31..8b5d6f4591ff5 100644
--- a/x-pack/legacy/plugins/reporting/server/lib/validate/__tests__/validate_config.js
+++ b/x-pack/legacy/plugins/reporting/server/lib/validate/__tests__/validate_config.js
@@ -8,7 +8,8 @@ import expect from '@kbn/expect';
import sinon from 'sinon';
import { validateConfig } from '../validate_config';
-describe('Reporting: Validate config', () => {
+// FAILING: https://github.com/elastic/kibana/issues/51373
+describe.skip('Reporting: Validate config', () => {
const logger = {
warning: sinon.spy(),
};
diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.js b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.js
index 10ff9f477f424..59317ac46773b 100644
--- a/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.js
+++ b/x-pack/legacy/plugins/reporting/server/routes/lib/authorized_user_pre_routing.js
@@ -6,11 +6,10 @@
import boom from 'boom';
import { getUserFactory } from '../../lib/get_user';
-import { oncePerServer } from '../../lib/once_per_server';
const superuserRole = 'superuser';
-function authorizedUserPreRoutingFn(server) {
+export const authorizedUserPreRoutingFactory = function authorizedUserPreRoutingFn(server) {
const getUser = getUserFactory(server);
const config = server.config();
@@ -40,6 +39,4 @@ function authorizedUserPreRoutingFn(server) {
return user;
};
-}
-
-export const authorizedUserPreRoutingFactory = oncePerServer(authorizedUserPreRoutingFn);
+};
diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.js b/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.js
index ad91e5a654a4e..92973e3d0b422 100644
--- a/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.js
+++ b/x-pack/legacy/plugins/reporting/server/routes/lib/reporting_feature_pre_routing.js
@@ -5,9 +5,8 @@
*/
import Boom from 'boom';
-import { oncePerServer } from '../../lib/once_per_server';
-function reportingFeaturePreRoutingFn(server) {
+export const reportingFeaturePreRoutingFactory = function reportingFeaturePreRoutingFn(server) {
const xpackMainPlugin = server.plugins.xpack_main;
const pluginId = 'reporting';
@@ -24,6 +23,4 @@ function reportingFeaturePreRoutingFn(server) {
}
};
};
-}
-
-export const reportingFeaturePreRoutingFactory = oncePerServer(reportingFeaturePreRoutingFn);
+};
diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts
index 7d05811ef4aa6..e8fb015426f51 100644
--- a/x-pack/legacy/plugins/reporting/types.d.ts
+++ b/x-pack/legacy/plugins/reporting/types.d.ts
@@ -326,3 +326,10 @@ export { CancellationToken } from './common/cancellation_token';
// Prefer to import this type using: `import { LevelLogger } from 'relative/path/server/lib';`
export { LevelLogger as Logger } from './server/lib/level_logger';
+
+export interface AbsoluteURLFactoryOptions {
+ defaultBasePath: string;
+ protocol: string;
+ hostname: string;
+ port: string | number;
+}
diff --git a/x-pack/legacy/plugins/rollup/public/search/register.js b/x-pack/legacy/plugins/rollup/public/search/register.js
index 917ee872254f5..f7f1c681b63ca 100644
--- a/x-pack/legacy/plugins/rollup/public/search/register.js
+++ b/x-pack/legacy/plugins/rollup/public/search/register.js
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { addSearchStrategy } from 'ui/courier';
+import { addSearchStrategy } from '../../../../../../src/legacy/ui/public/courier';
import { rollupSearchStrategy } from './rollup_search_strategy';
export function initSearch() {
diff --git a/x-pack/legacy/plugins/rollup/public/search/rollup_search_strategy.js b/x-pack/legacy/plugins/rollup/public/search/rollup_search_strategy.js
index ab24a37a2ecec..28f08ba1ab952 100644
--- a/x-pack/legacy/plugins/rollup/public/search/rollup_search_strategy.js
+++ b/x-pack/legacy/plugins/rollup/public/search/rollup_search_strategy.js
@@ -5,7 +5,7 @@
*/
import { kfetch } from 'ui/kfetch';
-import { SearchError, getSearchErrorType } from 'ui/courier';
+import { SearchError, getSearchErrorType } from '../../../../../../src/legacy/ui/public/courier';
function serializeFetchParams(searchRequests) {
return JSON.stringify(searchRequests.map(searchRequestWithFetchParams => {
diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/empty_tree_placeholder.tsx b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/empty_tree_placeholder.tsx
index bf27620dcac18..d709a8feb48bd 100644
--- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/empty_tree_placeholder.tsx
+++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/empty_tree_placeholder.tsx
@@ -15,13 +15,12 @@ export const EmptyTreePlaceHolder = () => {
{/* TODO: translations */}
{i18n.translate('xpack.searchProfiler.emptyProfileTreeTitle', {
- defaultMessage: 'Nothing to see here yet.',
+ defaultMessage: 'No queries to profile',
})}
{i18n.translate('xpack.searchProfiler.emptyProfileTreeDescription', {
- defaultMessage:
- 'Enter a query and press the "Profile" button or provide profile data in the editor.',
+ defaultMessage: 'Enter a query, click Profile, and see the results here.',
})}
diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/profile_loading_placeholder.tsx b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/profile_loading_placeholder.tsx
index fb09c6cddf70a..a7db54b670a84 100644
--- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/profile_loading_placeholder.tsx
+++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/profile_loading_placeholder.tsx
@@ -13,7 +13,7 @@ export const ProfileLoadingPlaceholder = () => {
{i18n.translate('xpack.searchProfiler.profilingLoaderText', {
- defaultMessage: 'Profiling...',
+ defaultMessage: 'Loading query profiles...',
})}
diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/main.tsx b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/main.tsx
index 7f5d223949e61..63ae5c7583625 100644
--- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/main.tsx
+++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/main.tsx
@@ -93,7 +93,7 @@ export const Main = () => {
return (
<>
-
+
{renderLicenseWarning()}
diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.ts
index dac9dab9bd092..615511786afd1 100644
--- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.ts
+++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.ts
@@ -12,7 +12,6 @@ import { OnHighlightChangeArgs } from '../components/profile_tree';
import { ShardSerialized, Targets } from '../types';
export type Action =
- | { type: 'setPristine'; value: boolean }
| { type: 'setProfiling'; value: boolean }
| { type: 'setHighlightDetails'; value: OnHighlightChangeArgs | null }
| { type: 'setActiveTab'; value: Targets | null }
@@ -20,12 +19,8 @@ export type Action =
export const reducer: Reducer = (state, action) =>
produce(state, draft => {
- if (action.type === 'setPristine') {
- draft.pristine = action.value;
- return;
- }
-
if (action.type === 'setProfiling') {
+ draft.pristine = false;
draft.profiling = action.value;
if (draft.profiling) {
draft.currentResponse = null;
diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/store.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/store.ts
index 7b5a1ce93583d..7008854a16285 100644
--- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/store.ts
+++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/store.ts
@@ -18,7 +18,7 @@ export interface State {
export const initialState: State = {
profiling: false,
- pristine: false,
+ pristine: true,
highlightDetails: null,
activeTab: null,
currentResponse: null,
diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss
index a72d079354f89..d36a587b9257f 100644
--- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss
+++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss
@@ -10,12 +10,6 @@
@import 'containers/main';
@import 'containers/profile_query_editor';
-#searchProfilerAppRoot {
- height: 100%;
- display: flex;
- flex: 1 1 auto;
-}
-
.prfDevTool__licenseWarning {
&__container {
max-width: 1000px;
@@ -55,19 +49,10 @@
}
}
-.prfDevTool {
- height: calc(100vh - #{$euiHeaderChildSize});
+.appRoot {
+ height: calc(100vh - calc(#{$euiHeaderChildSize} * 2));
overflow: hidden;
-
- .devApp__container {
- height: 100%;
- overflow: hidden;
- flex-shrink: 1;
- }
-
- &__container {
- overflow: hidden;
- }
+ flex-shrink: 1;
}
.prfDevTool__detail {
diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_profile_tree.scss b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_profile_tree.scss
index cc4d334f58fd3..c7dc4a305acb2 100644
--- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_profile_tree.scss
+++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_profile_tree.scss
@@ -5,10 +5,6 @@ $badgeSize: $euiSize * 5.5;
.prfDevTool__profileTree {
- &__container {
- height: 100%;
- }
-
&__shardDetails--dim small {
color: $euiColorDarkShade;
}
diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js
index c098e3e67a6d9..60374d562f96c 100644
--- a/x-pack/legacy/plugins/security/index.js
+++ b/x-pack/legacy/plugins/security/index.js
@@ -29,8 +29,12 @@ export const security = (kibana) => new kibana.Plugin({
enabled: Joi.boolean().default(true),
cookieName: Joi.any().description('This key is handled in the new platform security plugin ONLY'),
encryptionKey: Joi.any().description('This key is handled in the new platform security plugin ONLY'),
- sessionTimeout: Joi.any().description('This key is handled in the new platform security plugin ONLY'),
+ session: Joi.object({
+ idleTimeout: Joi.any().description('This key is handled in the new platform security plugin ONLY'),
+ lifespan: Joi.any().description('This key is handled in the new platform security plugin ONLY'),
+ }).default(),
secureCookies: Joi.any().description('This key is handled in the new platform security plugin ONLY'),
+ loginAssistanceMessage: Joi.string().default(),
authorization: Joi.object({
legacyFallback: Joi.object({
enabled: Joi.boolean().default(true) // deprecated
@@ -43,9 +47,10 @@ export const security = (kibana) => new kibana.Plugin({
}).default();
},
- deprecations: function ({ unused }) {
+ deprecations: function ({ rename, unused }) {
return [
unused('authorization.legacyFallback.enabled'),
+ rename('sessionTimeout', 'session.idleTimeout'),
];
},
@@ -88,7 +93,11 @@ export const security = (kibana) => new kibana.Plugin({
return {
secureCookies: securityPlugin.__legacyCompat.config.secureCookies,
- sessionTimeout: securityPlugin.__legacyCompat.config.sessionTimeout,
+ session: {
+ tenant: server.newPlatform.setup.core.http.basePath.serverBasePath,
+ idleTimeout: securityPlugin.__legacyCompat.config.session.idleTimeout,
+ lifespan: securityPlugin.__legacyCompat.config.session.lifespan,
+ },
enableSpaceAwarePrivileges: server.config().get('xpack.spaces.enabled'),
};
},
@@ -147,7 +156,9 @@ export const security = (kibana) => new kibana.Plugin({
server.injectUiAppVars('login', () => {
const { showLogin, allowLogin, layout = 'form' } = securityPlugin.__legacyCompat.license.getFeatures();
+ const { loginAssistanceMessage } = securityPlugin.__legacyCompat.config;
return {
+ loginAssistanceMessage,
loginState: {
showLogin,
allowLogin,
diff --git a/x-pack/legacy/plugins/security/public/hacks/on_session_timeout.js b/x-pack/legacy/plugins/security/public/hacks/on_session_timeout.js
index 81b14ee7d8bf4..d9fb450779411 100644
--- a/x-pack/legacy/plugins/security/public/hacks/on_session_timeout.js
+++ b/x-pack/legacy/plugins/security/public/hacks/on_session_timeout.js
@@ -7,28 +7,20 @@
import _ from 'lodash';
import { uiModules } from 'ui/modules';
import { isSystemApiRequest } from 'ui/system_api';
-import { Path } from 'plugins/xpack_main/services/path';
import { npSetup } from 'ui/new_platform';
-/**
- * Client session timeout is decreased by this number so that Kibana server
- * can still access session content during logout request to properly clean
- * user session up (invalidate access tokens, redirect to logout portal etc.).
- * @type {number}
- */
-
const module = uiModules.get('security', []);
module.config(($httpProvider) => {
$httpProvider.interceptors.push((
$q,
) => {
- const isUnauthenticated = Path.isUnauthenticated();
+ const isAnonymous = npSetup.core.http.anonymousPaths.isAnonymous(window.location.pathname);
function interceptorFactory(responseHandler) {
return function interceptor(response) {
- if (!isUnauthenticated && !isSystemApiRequest(response.config)) {
- npSetup.plugins.security.sessionTimeout.extend();
+ if (!isAnonymous && !isSystemApiRequest(response.config)) {
+ npSetup.plugins.security.sessionTimeout.extend(response.config.url);
}
return responseHandler(response);
};
diff --git a/x-pack/legacy/plugins/security/public/views/logged_out/logged_out.tsx b/x-pack/legacy/plugins/security/public/views/logged_out/logged_out.tsx
index 369b531e8ddf8..dbeb68875c1a9 100644
--- a/x-pack/legacy/plugins/security/public/views/logged_out/logged_out.tsx
+++ b/x-pack/legacy/plugins/security/public/views/logged_out/logged_out.tsx
@@ -31,7 +31,7 @@ chrome
}
>
-
+
,
diff --git a/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/__snapshots__/basic_login_form.test.tsx.snap b/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/__snapshots__/basic_login_form.test.tsx.snap
index 3b3024024a9cf..a08c454e569e6 100644
--- a/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/__snapshots__/basic_login_form.test.tsx.snap
+++ b/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/__snapshots__/basic_login_form.test.tsx.snap
@@ -2,6 +2,20 @@
exports[`BasicLoginForm renders as expected 1`] = `
+
+
+
`;
+exports[`LoginPage disabled form states renders as expected when loginAssistanceMessage is set 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
exports[`LoginPage disabled form states renders as expected when secure cookies are required but not present 1`] = `
{
loginState: createLoginState(),
isSecureConnection: false,
requiresSecureConnection: true,
+ loginAssistanceMessage: '',
};
expect(shallow(
)).toMatchSnapshot();
@@ -61,6 +62,7 @@ describe('LoginPage', () => {
}),
isSecureConnection: false,
requiresSecureConnection: false,
+ loginAssistanceMessage: '',
};
expect(shallow(
)).toMatchSnapshot();
@@ -76,6 +78,7 @@ describe('LoginPage', () => {
}),
isSecureConnection: false,
requiresSecureConnection: false,
+ loginAssistanceMessage: '',
};
expect(shallow(
)).toMatchSnapshot();
@@ -91,6 +94,21 @@ describe('LoginPage', () => {
}),
isSecureConnection: false,
requiresSecureConnection: false,
+ loginAssistanceMessage: '',
+ };
+
+ expect(shallow(
)).toMatchSnapshot();
+ });
+
+ it('renders as expected when loginAssistanceMessage is set', () => {
+ const props = {
+ http: createMockHttp(),
+ window: {},
+ next: '',
+ loginState: createLoginState(),
+ isSecureConnection: false,
+ requiresSecureConnection: false,
+ loginAssistanceMessage: 'This is an *important* message',
};
expect(shallow(
)).toMatchSnapshot();
@@ -106,6 +124,7 @@ describe('LoginPage', () => {
loginState: createLoginState(),
isSecureConnection: false,
requiresSecureConnection: false,
+ loginAssistanceMessage: '',
};
expect(shallow(
)).toMatchSnapshot();
diff --git a/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.tsx b/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.tsx
index 82dd0e679a5ee..e7e56947ca58f 100644
--- a/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.tsx
+++ b/x-pack/legacy/plugins/security/public/views/login/components/login_page/login_page.tsx
@@ -31,6 +31,7 @@ interface Props {
loginState: LoginState;
isSecureConnection: boolean;
requiresSecureConnection: boolean;
+ loginAssistanceMessage: string;
}
export class LoginPage extends Component
{
diff --git a/x-pack/legacy/plugins/security/public/views/login/login.tsx b/x-pack/legacy/plugins/security/public/views/login/login.tsx
index 8b452e4c4fdf5..d9daf2d1f4d0d 100644
--- a/x-pack/legacy/plugins/security/public/views/login/login.tsx
+++ b/x-pack/legacy/plugins/security/public/views/login/login.tsx
@@ -39,7 +39,8 @@ interface AnyObject {
$http: AnyObject,
$window: AnyObject,
secureCookies: boolean,
- loginState: LoginState
+ loginState: LoginState,
+ loginAssistanceMessage: string
) => {
const basePath = chrome.getBasePath();
const next = parseNext($window.location.href, basePath);
@@ -59,6 +60,7 @@ interface AnyObject {
loginState={loginState}
isSecureConnection={isSecure}
requiresSecureConnection={secureCookies}
+ loginAssistanceMessage={loginAssistanceMessage}
next={next}
/>
,
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx
index 282ce4eea160c..91f5f048adc6d 100644
--- a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx
+++ b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx
@@ -374,7 +374,7 @@ class EditUserPageUI extends Component {
-
+
{isNewUser ? (
{
values={{ userName: user.username }}
/>
)}
-
+
{reserved && (
diff --git a/x-pack/legacy/plugins/siem/common/constants.ts b/x-pack/legacy/plugins/siem/common/constants.ts
index f01493cec869e..e5d1fc83dac26 100644
--- a/x-pack/legacy/plugins/siem/common/constants.ts
+++ b/x-pack/legacy/plugins/siem/common/constants.ts
@@ -17,6 +17,8 @@ export const DEFAULT_SIEM_TIME_RANGE = 'siem:timeDefaults';
export const DEFAULT_SIEM_REFRESH_INTERVAL = 'siem:refreshIntervalDefaults';
export const DEFAULT_SIGNALS_INDEX_KEY = 'siem:defaultSignalsIndex';
export const DEFAULT_SIGNALS_INDEX = '.siem-signals';
+export const DEFAULT_MAX_SIGNALS = 100;
+export const DEFAULT_SEARCH_AFTER_PAGE_SIZE = 100;
export const DEFAULT_ANOMALY_SCORE = 'siem:defaultAnomalyScore';
export const DEFAULT_MAX_TABLE_QUERY_SIZE = 10000;
export const DEFAULT_SCALE_DATE_FORMAT = 'dateFormat:scaled';
diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/logout/index.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/logout/index.ts
index 132242606d88c..7a6c7f71bc98c 100644
--- a/x-pack/legacy/plugins/siem/cypress/integration/lib/logout/index.ts
+++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/logout/index.ts
@@ -4,9 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { LOGOUT } from '../urls';
-
export const logout = (): null => {
- cy.visit(`${Cypress.config().baseUrl}${LOGOUT}`);
+ cy.request({
+ method: 'GET',
+ url: `${Cypress.config().baseUrl}/logout`,
+ }).then(response => {
+ expect(response.status).to.eq(200);
+ });
return null;
};
diff --git a/x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_signals.js b/x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_rules.js
similarity index 70%
rename from x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_signals.js
rename to x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_rules.js
index 597d93a44210b..3e1c5f51ebb5c 100644
--- a/x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_signals.js
+++ b/x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_rules.js
@@ -11,22 +11,22 @@ const path = require('path');
/*
* This script is used to parse a set of saved searches on a file system
- * and output signal data compatible json files.
+ * and output rule data compatible json files.
* Example:
- * node saved_query_to_signals.js ${HOME}/saved_searches ${HOME}/saved_signals
+ * node saved_query_to_rules.js ${HOME}/saved_searches ${HOME}/saved_rules
*
- * After editing any changes in the files of ${HOME}/saved_signals/*.json
- * you can then post the signals with a CURL post script such as:
+ * After editing any changes in the files of ${HOME}/saved_rules/*.json
+ * you can then post the rules with a CURL post script such as:
*
- * ./post_signal.sh ${HOME}/saved_signals/*.json
+ * ./post_rule.sh ${HOME}/saved_rules/*.json
*
* Note: This script is recursive and but does not preserve folder structure
- * when it outputs the saved signals.
+ * when it outputs the saved rules.
*/
-// Defaults of the outputted signals since the saved KQL searches do not have
+// Defaults of the outputted rules since the saved KQL searches do not have
// this type of information. You usually will want to make any hand edits after
-// doing a search to KQL conversion before posting it as a signal or checking it
+// doing a search to KQL conversion before posting it as a rule or checking it
// into another repository.
const INTERVAL = '5m';
const SEVERITY = 'low';
@@ -34,9 +34,17 @@ const TYPE = 'query';
const FROM = 'now-6m';
const TO = 'now';
const IMMUTABLE = true;
-const INDEX = ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'];
-const OUTPUT_INDEX = process.env.SIGNALS_INDEX || '.siem-signals';
const RISK_SCORE = 50;
+const ENABLED = false;
+let allRules = '';
+const allRulesNdJson = 'all_rules.ndjson';
+
+// For converting, if you want to use these instead of rely on the defaults then
+// comment these in and use them for the script. Otherwise this is commented out
+// so we can utilize the defaults of input and output which are based on saved objects
+// of siem:defaultIndex and siem:defaultSignalsIndex
+// const INDEX = ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'];
+// const OUTPUT_INDEX = process.env.SIGNALS_INDEX || '.siem-signals';
const walk = dir => {
const list = fs.readdirSync(dir);
@@ -66,7 +74,7 @@ const cleanupFileName = file => {
async function main() {
if (process.argv.length !== 4) {
throw new Error(
- 'usage: saved_query_to_signals [input directory with saved searches] [output directory]'
+ 'usage: saved_query_to_rules [input directory with saved searches] [output directory]'
);
}
@@ -124,7 +132,6 @@ async function main() {
risk_score: RISK_SCORE,
description: description || title,
immutable: IMMUTABLE,
- index: INDEX,
interval: INTERVAL,
name: title,
severity: SEVERITY,
@@ -134,16 +141,22 @@ async function main() {
query,
language,
filters: filter,
- output_index: OUTPUT_INDEX,
+ enabled: ENABLED,
+ // comment these in if you want to use these for input output, otherwise
+ // with these two commented out, we will use the default saved objects from spaces.
+ // index: INDEX,
+ // output_index: OUTPUT_INDEX,
};
fs.writeFileSync(
`${outputDir}/${fileToWrite}.json`,
JSON.stringify(outputMessage, null, 2)
);
+ allRules += `${JSON.stringify(outputMessage)}\n`;
}
}
);
+ fs.writeFileSync(`${outputDir}/${allRulesNdJson}`, allRules);
}
if (require.main === module) {
diff --git a/x-pack/legacy/plugins/siem/server/kibana.index.ts b/x-pack/legacy/plugins/siem/server/kibana.index.ts
index c79b2651c11cb..2f1530a777042 100644
--- a/x-pack/legacy/plugins/siem/server/kibana.index.ts
+++ b/x-pack/legacy/plugins/siem/server/kibana.index.ts
@@ -15,13 +15,13 @@ import {
timelineSavedObjectType,
} from './saved_objects';
-import { signalsAlertType } from './lib/detection_engine/alerts/signals_alert_type';
+import { rulesAlertType } from './lib/detection_engine/alerts/rules_alert_type';
import { isAlertExecutor } from './lib/detection_engine/alerts/types';
-import { createSignalsRoute } from './lib/detection_engine/routes/create_signals_route';
-import { readSignalsRoute } from './lib/detection_engine/routes/read_signals_route';
-import { findSignalsRoute } from './lib/detection_engine/routes/find_signals_route';
-import { deleteSignalsRoute } from './lib/detection_engine/routes/delete_signals_route';
-import { updateSignalsRoute } from './lib/detection_engine/routes/update_signals_route';
+import { createRulesRoute } from './lib/detection_engine/routes/create_rules_route';
+import { readRulesRoute } from './lib/detection_engine/routes/read_rules_route';
+import { findRulesRoute } from './lib/detection_engine/routes/find_rules_route';
+import { deleteRulesRoute } from './lib/detection_engine/routes/delete_rules_route';
+import { updateRulesRoute } from './lib/detection_engine/routes/update_rules_route';
import { ServerFacade } from './types';
const APP_ID = 'siem';
@@ -32,7 +32,8 @@ export const initServerWithKibana = (
mode: EnvironmentMode
) => {
if (kbnServer.plugins.alerting != null) {
- const type = signalsAlertType({ logger });
+ const version = kbnServer.config().get('pkg.version');
+ const type = rulesAlertType({ logger, version });
if (isAlertExecutor(type)) {
kbnServer.plugins.alerting.setup.registerType(type);
}
@@ -48,13 +49,13 @@ export const initServerWithKibana = (
kbnServer.config().has('xpack.alerting.enabled') === true
) {
logger.info(
- 'Detected feature flags for actions and alerting and enabling signals API endpoints'
+ 'Detected feature flags for actions and alerting and enabling detection engine API endpoints'
);
- createSignalsRoute(kbnServer);
- readSignalsRoute(kbnServer);
- updateSignalsRoute(kbnServer);
- deleteSignalsRoute(kbnServer);
- findSignalsRoute(kbnServer);
+ createRulesRoute(kbnServer);
+ readRulesRoute(kbnServer);
+ updateRulesRoute(kbnServer);
+ deleteRulesRoute(kbnServer);
+ findRulesRoute(kbnServer);
}
const xpackMainPlugin = kbnServer.plugins.xpack_main;
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md b/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md
index 0a0439a9ace1b..4b1dbf62d0dd4 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/README.md
@@ -24,10 +24,10 @@ xpack.alerting.enabled: true
xpack.actions.enabled: true
```
-Start Kibana and you will see these messages indicating signals is activated like so:
+Start Kibana and you will see these messages indicating detection engine is activated like so:
```sh
-server log [11:39:05.561] [info][siem] Detected feature flags for actions and alerting and enabling signals API endpoints
+server log [11:39:05.561] [info][siem] Detected feature flags for actions and alerting and enabling detection engine API endpoints
```
If you see crashes like this:
@@ -98,10 +98,17 @@ server log [22:05:22.277] [info][status][plugin:alerting@8.0.0] Status chan
server log [22:05:22.270] [info][status][plugin:actions@8.0.0] Status changed from uninitialized to green - Ready
```
-You should also see the SIEM detect the feature flags and start the API endpoints for signals
+You should also see the SIEM detect the feature flags and start the API endpoints for detection engine
```sh
-server log [11:39:05.561] [info][siem] Detected feature flags for actions and alerting and enabling signals API endpoints
+server log [11:39:05.561] [info][siem] Detected feature flags for actions and alerting and enabling detection engine API endpoints
+```
+
+Go into your SIEM Advanced settings and underneath the setting of `siem:defaultSignalsIndex`, set that to the same
+value as you did with the environment variable of SIGNALS_INDEX, which should be `.siem-signals-${your user id}`
+
+```
+.siem-signals-${your user id}
```
Open a terminal and go into the scripts folder `cd kibana/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts` and run:
@@ -118,16 +125,16 @@ which will:
- Delete any existing alert tasks you have
- Delete any existing signal mapping you might have had.
- Add the latest signal index and its mappings using your settings from `SIGNALS_INDEX` environment variable.
-- Posts the sample signal from `signals/root_or_admin_1.json` by replacing its `output_index` with your `SIGNALS_INDEX` environment variable
-- The sample signal checks for root or admin every 5 minutes and reports that as a signal if it is a positive hit
+- Posts the sample rule from `rules/root_or_admin_1.json` by replacing its `output_index` with your `SIGNALS_INDEX` environment variable
+- The sample rule checks for root or admin every 5 minutes and reports that as a signal if it is a positive hit
Now you can run
```sh
-./find_signals.sh
+./find_rules.sh
```
-You should see the new signals created like so:
+You should see the new rules created like so:
```sh
{
@@ -177,7 +184,7 @@ Every 5 minutes if you get positive hits you will see messages on info like so:
server log [09:54:59.013] [info][plugins][siem] Total signals found from signal rule "id: a556065c-0656-4ba1-ad64-a77ca9d2013b", "ruleId: rule-1": 10000
```
-Signals are space aware and default to the "default" space for these scripts if you do not export
+Rules are space aware and default to the "default" space for these scripts if you do not export
the variable of SPACE_URL. For example, if you want to post rules to the space `test-space` you would
set your SPACE_URL to be:
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts
index 0a70a7342b2dd..079d3658461fa 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts
@@ -4,12 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { SignalSourceHit, SignalSearchResponse, AlertTypeParams } from '../types';
+import { SignalSourceHit, SignalSearchResponse, RuleTypeParams } from '../types';
-export const sampleSignalAlertParams = (
+export const sampleRuleAlertParams = (
maxSignals: number | undefined,
riskScore?: number | undefined
-): AlertTypeParams => ({
+): RuleTypeParams => ({
ruleId: 'rule-1',
description: 'Detecting root and admin users',
falsePositives: [],
@@ -30,33 +30,43 @@ export const sampleSignalAlertParams = (
filters: undefined,
savedId: undefined,
meta: undefined,
- size: 1000,
});
-export const sampleDocNoSortId: SignalSourceHit = {
+export const sampleDocNoSortId = (someUuid: string): SignalSourceHit => ({
_index: 'myFakeSignalIndex',
_type: 'doc',
_score: 100,
_version: 1,
- _id: 'someFakeId',
+ _id: someUuid,
_source: {
someKey: 'someValue',
'@timestamp': 'someTimeStamp',
},
-};
+});
+
+export const sampleDocNoSortIdNoVersion = (someUuid: string): SignalSourceHit => ({
+ _index: 'myFakeSignalIndex',
+ _type: 'doc',
+ _score: 100,
+ _id: someUuid,
+ _source: {
+ someKey: 'someValue',
+ '@timestamp': 'someTimeStamp',
+ },
+});
-export const sampleDocWithSortId: SignalSourceHit = {
+export const sampleDocWithSortId = (someUuid: string): SignalSourceHit => ({
_index: 'myFakeSignalIndex',
_type: 'doc',
_score: 100,
_version: 1,
- _id: 'someFakeId',
+ _id: someUuid,
_source: {
someKey: 'someValue',
'@timestamp': 'someTimeStamp',
},
sort: ['1234567891111'],
-};
+});
export const sampleEmptyDocSearchResults: SignalSearchResponse = {
took: 10,
@@ -74,7 +84,61 @@ export const sampleEmptyDocSearchResults: SignalSearchResponse = {
},
};
-export const sampleDocSearchResultsNoSortId: SignalSearchResponse = {
+export const sampleBulkCreateDuplicateResult = {
+ took: 60,
+ errors: true,
+ items: [
+ {
+ create: {
+ _index: 'test',
+ _type: '_doc',
+ _id: '4',
+ _version: 1,
+ result: 'created',
+ _shards: {
+ total: 2,
+ successful: 1,
+ failed: 0,
+ },
+ _seq_no: 1,
+ _primary_term: 1,
+ status: 201,
+ },
+ },
+ {
+ create: {
+ _index: 'test',
+ _type: '_doc',
+ _id: '4',
+ status: 409,
+ error: {
+ type: 'version_conflict_engine_exception',
+ reason: '[4]: version conflict, document already exists (current version [1])',
+ index_uuid: 'cXmq4Rt3RGGswDTTwZFzvA',
+ shard: '0',
+ index: 'test',
+ },
+ },
+ },
+ {
+ create: {
+ _index: 'test',
+ _type: '_doc',
+ _id: '4',
+ status: 409,
+ error: {
+ type: 'version_conflict_engine_exception',
+ reason: '[4]: version conflict, document already exists (current version [1])',
+ index_uuid: 'cXmq4Rt3RGGswDTTwZFzvA',
+ shard: '0',
+ index: 'test',
+ },
+ },
+ },
+ ],
+};
+
+export const sampleDocSearchResultsNoSortId = (someUuid: string): SignalSearchResponse => ({
took: 10,
timed_out: false,
_shards: {
@@ -88,13 +152,35 @@ export const sampleDocSearchResultsNoSortId: SignalSearchResponse = {
max_score: 100,
hits: [
{
- ...sampleDocNoSortId,
+ ...sampleDocNoSortId(someUuid),
},
],
},
-};
+});
+
+export const sampleDocSearchResultsNoSortIdNoVersion = (
+ someUuid: string
+): SignalSearchResponse => ({
+ took: 10,
+ timed_out: false,
+ _shards: {
+ total: 10,
+ successful: 10,
+ failed: 0,
+ skipped: 0,
+ },
+ hits: {
+ total: 100,
+ max_score: 100,
+ hits: [
+ {
+ ...sampleDocNoSortIdNoVersion(someUuid),
+ },
+ ],
+ },
+});
-export const sampleDocSearchResultsNoSortIdNoHits: SignalSearchResponse = {
+export const sampleDocSearchResultsNoSortIdNoHits = (someUuid: string): SignalSearchResponse => ({
took: 10,
timed_out: false,
_shards: {
@@ -108,13 +194,17 @@ export const sampleDocSearchResultsNoSortIdNoHits: SignalSearchResponse = {
max_score: 100,
hits: [
{
- ...sampleDocNoSortId,
+ ...sampleDocNoSortId(someUuid),
},
],
},
-};
+});
-export const repeatedSearchResultsWithSortId = (repeat: number) => ({
+export const repeatedSearchResultsWithSortId = (
+ total: number,
+ pageSize: number,
+ guids: string[]
+) => ({
took: 10,
timed_out: false,
_shards: {
@@ -124,15 +214,15 @@ export const repeatedSearchResultsWithSortId = (repeat: number) => ({
skipped: 0,
},
hits: {
- total: repeat,
+ total,
max_score: 100,
- hits: Array.from({ length: repeat }).map(x => ({
- ...sampleDocWithSortId,
+ hits: Array.from({ length: pageSize }).map((x, index) => ({
+ ...sampleDocWithSortId(guids[index]),
})),
},
});
-export const sampleDocSearchResultsWithSortId: SignalSearchResponse = {
+export const sampleDocSearchResultsWithSortId = (someUuid: string): SignalSearchResponse => ({
took: 10,
timed_out: false,
_shards: {
@@ -146,10 +236,10 @@ export const sampleDocSearchResultsWithSortId: SignalSearchResponse = {
max_score: 100,
hits: [
{
- ...sampleDocWithSortId,
+ ...sampleDocWithSortId(someUuid),
},
],
},
-};
+});
-export const sampleSignalId = '04128c15-0d1b-4716-a4c5-46997ac7f3bd';
+export const sampleRuleGuid = '04128c15-0d1b-4716-a4c5-46997ac7f3bd';
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_signals.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts
similarity index 92%
rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_signals.ts
rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts
index 420f995431423..7c66714484383 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_signals.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_rules.ts
@@ -5,9 +5,9 @@
*/
import { SIGNALS_ID } from '../../../../common/constants';
-import { SignalParams } from './types';
+import { RuleParams } from './types';
-export const createSignals = async ({
+export const createRules = async ({
alertsClient,
actionsClient, // TODO: Use this actionsClient once we have actions such as email, etc...
description,
@@ -29,12 +29,11 @@ export const createSignals = async ({
outputIndex,
name,
severity,
- size,
tags,
to,
type,
references,
-}: SignalParams) => {
+}: RuleParams) => {
return alertsClient.create({
data: {
name,
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/delete_signals.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/delete_rules.ts
similarity index 65%
rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/delete_signals.ts
rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/delete_rules.ts
index d89895772f1ef..c3ca1d79424cf 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/delete_signals.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/delete_rules.ts
@@ -4,27 +4,27 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { readSignals } from './read_signals';
-import { DeleteSignalParams } from './types';
+import { readRules } from './read_rules';
+import { DeleteRuleParams } from './types';
-export const deleteSignals = async ({
+export const deleteRules = async ({
alertsClient,
actionsClient, // TODO: Use this when we have actions such as email, etc...
id,
ruleId,
-}: DeleteSignalParams) => {
- const signal = await readSignals({ alertsClient, id, ruleId });
- if (signal == null) {
+}: DeleteRuleParams) => {
+ const rule = await readRules({ alertsClient, id, ruleId });
+ if (rule == null) {
return null;
}
if (ruleId != null) {
- await alertsClient.delete({ id: signal.id });
- return signal;
+ await alertsClient.delete({ id: rule.id });
+ return rule;
} else if (id != null) {
try {
await alertsClient.delete({ id });
- return signal;
+ return rule;
} catch (err) {
if (err.output.statusCode === 404) {
return null;
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_signals.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_rules.test.ts
similarity index 90%
rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_signals.test.ts
rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_rules.test.ts
index 7873781fb05c4..23f031b22a9dd 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_signals.test.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_rules.test.ts
@@ -4,10 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { getFilter } from './find_signals';
+import { getFilter } from './find_rules';
import { SIGNALS_ID } from '../../../../common/constants';
-describe('find_signals', () => {
+describe('find_rules', () => {
test('it returns a full filter with an AND if sent down', () => {
expect(getFilter('alert.attributes.enabled: true')).toEqual(
`alert.attributes.alertTypeId: ${SIGNALS_ID} AND alert.attributes.enabled: true`
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_signals.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_rules.ts
similarity index 88%
rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_signals.ts
rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_rules.ts
index 63e6a069c0cfe..c1058bd353e8c 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_signals.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/find_rules.ts
@@ -5,7 +5,7 @@
*/
import { SIGNALS_ID } from '../../../../common/constants';
-import { FindSignalParams } from './types';
+import { FindRuleParams } from './types';
export const getFilter = (filter: string | null | undefined) => {
if (filter == null) {
@@ -15,7 +15,7 @@ export const getFilter = (filter: string | null | undefined) => {
}
};
-export const findSignals = async ({
+export const findRules = async ({
alertsClient,
perPage,
page,
@@ -23,7 +23,7 @@ export const findSignals = async ({
filter,
sortField,
sortOrder,
-}: FindSignalParams) => {
+}: FindRuleParams) => {
return alertsClient.find({
options: {
fields,
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts
index 1aa22ea024cc8..5d3b47ecebfd5 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.ts
@@ -5,7 +5,7 @@
*/
import { AlertServices } from '../../../../../alerting/server/types';
-import { SignalAlertParams, PartialFilter } from './types';
+import { RuleAlertParams, PartialFilter } from './types';
import { assertUnreachable } from '../../../utils/build_query';
import {
Query,
@@ -41,7 +41,7 @@ export const getQueryFilter = (
};
interface GetFilterArgs {
- type: SignalAlertParams['type'];
+ type: RuleAlertParams['type'];
filter: Record | undefined | null;
filters: PartialFilter[] | undefined | null;
language: string | undefined | null;
@@ -86,7 +86,7 @@ export const getFilter = async ({
if (query != null && language != null && index != null) {
return getQueryFilter(query, language, filters || [], index);
} else {
- // user did not give any additional fall back mechanism for generating a signal
+ // user did not give any additional fall back mechanism for generating a rule
// rethrow error for activity monitoring
throw err;
}
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_input_output_index.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_input_output_index.test.ts
new file mode 100644
index 0000000000000..07eb7c885b443
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_input_output_index.test.ts
@@ -0,0 +1,286 @@
+/*
+ * 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 { savedObjectsClientMock } from 'src/core/server/mocks';
+import {
+ DEFAULT_SIGNALS_INDEX_KEY,
+ DEFAULT_INDEX_KEY,
+ DEFAULT_SIGNALS_INDEX,
+} from '../../../../common/constants';
+import { AlertServices } from '../../../../../alerting/server/types';
+import { getInputOutputIndex, getOutputIndex, getInputIndex } from './get_input_output_index';
+import { defaultIndexPattern } from '../../../../default_index_pattern';
+
+describe('get_input_output_index', () => {
+ let savedObjectsClient = savedObjectsClientMock.create();
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {},
+ }));
+ let servicesMock: AlertServices = {
+ savedObjectsClient,
+ callCluster: jest.fn(),
+ alertInstanceFactory: jest.fn(),
+ };
+
+ beforeAll(() => {
+ jest.resetAllMocks();
+ });
+
+ afterAll(() => {
+ jest.resetAllMocks();
+ });
+
+ beforeEach(() => {
+ savedObjectsClient = savedObjectsClientMock.create();
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {},
+ }));
+ servicesMock = {
+ savedObjectsClient,
+ callCluster: jest.fn(),
+ alertInstanceFactory: jest.fn(),
+ };
+ });
+
+ describe('getInputOutputIndex', () => {
+ test('Returns inputIndex as is if inputIndex and outputIndex are both passed in', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {},
+ }));
+ const { inputIndex } = await getInputOutputIndex(
+ servicesMock,
+ '8.0.0',
+ ['test-input-index-1'],
+ 'test-output-index'
+ );
+ expect(inputIndex).toEqual(['test-input-index-1']);
+ });
+
+ test('Returns outputIndex as is if inputIndex and outputIndex are both passed in', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {},
+ }));
+ const { outputIndex } = await getInputOutputIndex(
+ servicesMock,
+ '8.0.0',
+ ['test-input-index-1'],
+ 'test-output-index'
+ );
+ expect(outputIndex).toEqual('test-output-index');
+ });
+
+ test('Returns inputIndex as is if inputIndex is defined but outputIndex is null', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {},
+ }));
+ const { inputIndex } = await getInputOutputIndex(
+ servicesMock,
+ '8.0.0',
+ ['test-input-index-1'],
+ null
+ );
+ expect(inputIndex).toEqual(['test-input-index-1']);
+ });
+
+ test('Returns outputIndex as is if inputIndex is null but outputIndex is defined', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {},
+ }));
+ const { outputIndex } = await getInputOutputIndex(
+ servicesMock,
+ '8.0.0',
+ null,
+ 'test-output-index'
+ );
+ expect(outputIndex).toEqual('test-output-index');
+ });
+
+ test('Returns a saved object outputIndex if both passed in are undefined', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {
+ [DEFAULT_SIGNALS_INDEX_KEY]: '.signals-test-index',
+ },
+ }));
+ const { outputIndex } = await getInputOutputIndex(
+ servicesMock,
+ '8.0.0',
+ undefined,
+ undefined
+ );
+ expect(outputIndex).toEqual('.signals-test-index');
+ });
+
+ test('Returns a saved object outputIndex if passed in outputIndex is undefined', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {
+ [DEFAULT_SIGNALS_INDEX_KEY]: '.signals-test-index',
+ },
+ }));
+ const { outputIndex } = await getInputOutputIndex(
+ servicesMock,
+ '8.0.0',
+ ['test-input-index-1'],
+ undefined
+ );
+ expect(outputIndex).toEqual('.signals-test-index');
+ });
+
+ test('Returns a saved object outputIndex default from constants if both passed in input and configuration are null', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {
+ [DEFAULT_SIGNALS_INDEX_KEY]: null,
+ },
+ }));
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {},
+ }));
+ const { outputIndex } = await getInputOutputIndex(servicesMock, '8.0.0', null, null);
+ expect(outputIndex).toEqual(DEFAULT_SIGNALS_INDEX);
+ });
+
+ test('Returns a saved object outputIndex default from constants if both passed in input and configuration are missing', async () => {
+ const { outputIndex } = await getInputOutputIndex(
+ servicesMock,
+ '8.0.0',
+ undefined,
+ undefined
+ );
+ expect(outputIndex).toEqual(DEFAULT_SIGNALS_INDEX);
+ });
+
+ test('Returns a saved object inputIndex if passed in inputIndex and outputIndex are undefined', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {
+ [DEFAULT_INDEX_KEY]: ['configured-index-1', 'configured-index-2'],
+ },
+ }));
+ const { inputIndex } = await getInputOutputIndex(servicesMock, '8.0.0', undefined, undefined);
+ expect(inputIndex).toEqual(['configured-index-1', 'configured-index-2']);
+ });
+
+ test('Returns a saved object inputIndex if passed in inputIndex is undefined', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {
+ [DEFAULT_INDEX_KEY]: ['configured-index-1', 'configured-index-2'],
+ },
+ }));
+ const { inputIndex } = await getInputOutputIndex(
+ servicesMock,
+ '8.0.0',
+ undefined,
+ 'output-index-1'
+ );
+ expect(inputIndex).toEqual(['configured-index-1', 'configured-index-2']);
+ });
+
+ test('Returns a saved object inputIndex default from constants if both passed in inputIndex and configuration is null', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {
+ [DEFAULT_INDEX_KEY]: null,
+ },
+ }));
+ const { inputIndex } = await getInputOutputIndex(servicesMock, '8.0.0', null, null);
+ expect(inputIndex).toEqual(defaultIndexPattern);
+ });
+
+ test('Returns a saved object inputIndex default from constants if both passed in inputIndex and configuration attributes is missing', async () => {
+ const { inputIndex } = await getInputOutputIndex(servicesMock, '8.0.0', undefined, undefined);
+ expect(inputIndex).toEqual(defaultIndexPattern);
+ });
+ });
+
+ describe('getOutputIndex', () => {
+ test('test output index is returned when passed in as is', async () => {
+ const mockConfiguration = await savedObjectsClient.get('config', '8.0.0');
+ const outputIndex = getOutputIndex('output-index-1', mockConfiguration);
+ expect(outputIndex).toEqual('output-index-1');
+ });
+
+ test('configured output index is returned when output index is null', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {
+ [DEFAULT_SIGNALS_INDEX_KEY]: '.siem-test-signals',
+ },
+ }));
+ const mockConfiguration = await savedObjectsClient.get('config', '8.0.0');
+ const outputIndex = getOutputIndex(null, mockConfiguration);
+ expect(outputIndex).toEqual('.siem-test-signals');
+ });
+
+ test('output index from constants is returned when output index is null and so is the configuration', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {
+ [DEFAULT_SIGNALS_INDEX_KEY]: null,
+ },
+ }));
+ const mockConfiguration = await savedObjectsClient.get('config', '8.0.0');
+ const outputIndex = getOutputIndex(null, mockConfiguration);
+ expect(outputIndex).toEqual(DEFAULT_SIGNALS_INDEX);
+ });
+
+ test('output index from constants is returned when output index is null and configuration is missing', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {},
+ }));
+ const mockConfiguration = await savedObjectsClient.get('config', '8.0.0');
+ const outputIndex = getOutputIndex(null, mockConfiguration);
+ expect(outputIndex).toEqual(DEFAULT_SIGNALS_INDEX);
+ });
+
+ test('output index from constants is returned when output index is null and attributes is missing', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({}));
+ const mockConfiguration = await savedObjectsClient.get('config', '8.0.0');
+ const outputIndex = getOutputIndex(null, mockConfiguration);
+ expect(outputIndex).toEqual(DEFAULT_SIGNALS_INDEX);
+ });
+ });
+
+ describe('getInputIndex', () => {
+ test('test input index is returned when passed in as is', async () => {
+ const mockConfiguration = await savedObjectsClient.get('config', '8.0.0');
+ const inputIndex = getInputIndex(['input-index-1'], mockConfiguration);
+ expect(inputIndex).toEqual(['input-index-1']);
+ });
+
+ test('configured input index is returned when input index is null', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {
+ [DEFAULT_INDEX_KEY]: ['input-index-1', 'input-index-2'],
+ },
+ }));
+ const mockConfiguration = await savedObjectsClient.get('config', '8.0.0');
+ const inputIndex = getInputIndex(null, mockConfiguration);
+ expect(inputIndex).toEqual(['input-index-1', 'input-index-2']);
+ });
+
+ test('input index from constants is returned when input index is null and so is the configuration', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {
+ [DEFAULT_INDEX_KEY]: null,
+ },
+ }));
+ const mockConfiguration = await savedObjectsClient.get('config', '8.0.0');
+ const inputIndex = getInputIndex(null, mockConfiguration);
+ expect(inputIndex).toEqual(defaultIndexPattern);
+ });
+
+ test('input index from constants is returned when input index is null and configuration is missing', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({
+ attributes: {},
+ }));
+ const mockConfiguration = await savedObjectsClient.get('config', '8.0.0');
+ const inputIndex = getInputIndex(null, mockConfiguration);
+ expect(inputIndex).toEqual(defaultIndexPattern);
+ });
+
+ test('input index from constants is returned when input index is null and attributes is missing', async () => {
+ savedObjectsClient.get = jest.fn().mockImplementation(() => ({}));
+ const mockConfiguration = await savedObjectsClient.get('config', '8.0.0');
+ const inputIndex = getInputIndex(null, mockConfiguration);
+ expect(inputIndex).toEqual(defaultIndexPattern);
+ });
+ });
+});
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_input_output_index.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_input_output_index.ts
new file mode 100644
index 0000000000000..567ab27976d8d
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_input_output_index.ts
@@ -0,0 +1,72 @@
+/*
+ * 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 { SavedObject, SavedObjectAttributes } from 'src/core/server';
+import { defaultIndexPattern } from '../../../../default_index_pattern';
+import { AlertServices } from '../../../../../alerting/server/types';
+import {
+ DEFAULT_INDEX_KEY,
+ DEFAULT_SIGNALS_INDEX_KEY,
+ DEFAULT_SIGNALS_INDEX,
+} from '../../../../common/constants';
+
+interface IndexObjectAttributes extends SavedObjectAttributes {
+ [DEFAULT_INDEX_KEY]: string[];
+ [DEFAULT_SIGNALS_INDEX_KEY]: string;
+}
+
+export const getInputIndex = (
+ inputIndex: string[] | undefined | null,
+ configuration: SavedObject
+): string[] => {
+ if (inputIndex != null) {
+ return inputIndex;
+ } else {
+ if (configuration.attributes != null && configuration.attributes[DEFAULT_INDEX_KEY] != null) {
+ return configuration.attributes[DEFAULT_INDEX_KEY];
+ } else {
+ return defaultIndexPattern;
+ }
+ }
+};
+
+export const getOutputIndex = (
+ outputIndex: string | undefined | null,
+ configuration: SavedObject
+): string => {
+ if (outputIndex != null) {
+ return outputIndex;
+ } else {
+ if (
+ configuration.attributes != null &&
+ configuration.attributes[DEFAULT_SIGNALS_INDEX_KEY] != null
+ ) {
+ return configuration.attributes[DEFAULT_SIGNALS_INDEX_KEY];
+ } else {
+ return DEFAULT_SIGNALS_INDEX;
+ }
+ }
+};
+
+export const getInputOutputIndex = async (
+ services: AlertServices,
+ version: string,
+ inputIndex: string[] | null | undefined,
+ outputIndex: string | null | undefined
+): Promise<{
+ inputIndex: string[];
+ outputIndex: string;
+}> => {
+ if (inputIndex != null && outputIndex != null) {
+ return { inputIndex, outputIndex };
+ } else {
+ const configuration = await services.savedObjectsClient.get('config', version);
+ return {
+ inputIndex: getInputIndex(inputIndex, configuration),
+ outputIndex: getOutputIndex(outputIndex, configuration),
+ };
+ }
+};
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_signals.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_rules.test.ts
similarity index 82%
rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_signals.test.ts
rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_rules.test.ts
index 39d1fac8f7a09..b3d7ab1322775 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_signals.test.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_rules.test.ts
@@ -5,7 +5,7 @@
*/
import { alertsClientMock } from '../../../../../alerting/server/alerts_client.mock';
-import { readSignals, readSignalByRuleId, findSignalInArrayByRuleId } from './read_signals';
+import { readRules, readRuleByRuleId, findRuleInArrayByRuleId } from './read_rules';
import { AlertsClient } from '../../../../../alerting';
import {
getResult,
@@ -14,19 +14,19 @@ import {
} from '../routes/__mocks__/request_responses';
import { SIGNALS_ID } from '../../../../common/constants';
-describe('read_signals', () => {
- describe('readSignals', () => {
+describe('read_rules', () => {
+ describe('readRules', () => {
test('should return the output from alertsClient if id is set but ruleId is undefined', async () => {
const alertsClient = alertsClientMock.create();
alertsClient.get.mockResolvedValue(getResult());
const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient;
- const signal = await readSignals({
+ const rule = await readRules({
alertsClient: unsafeCast,
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
ruleId: undefined,
});
- expect(signal).toEqual(getResult());
+ expect(rule).toEqual(getResult());
});
test('should return the output from alertsClient if id is set but ruleId is null', async () => {
@@ -34,12 +34,12 @@ describe('read_signals', () => {
alertsClient.get.mockResolvedValue(getResult());
const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient;
- const signal = await readSignals({
+ const rule = await readRules({
alertsClient: unsafeCast,
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
ruleId: null,
});
- expect(signal).toEqual(getResult());
+ expect(rule).toEqual(getResult());
});
test('should return the output from alertsClient if id is undefined but ruleId is set', async () => {
@@ -48,12 +48,12 @@ describe('read_signals', () => {
alertsClient.find.mockResolvedValue(getFindResultWithSingleHit());
const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient;
- const signal = await readSignals({
+ const rule = await readRules({
alertsClient: unsafeCast,
id: undefined,
ruleId: 'rule-1',
});
- expect(signal).toEqual(getResult());
+ expect(rule).toEqual(getResult());
});
test('should return the output from alertsClient if id is null but ruleId is set', async () => {
@@ -62,12 +62,12 @@ describe('read_signals', () => {
alertsClient.find.mockResolvedValue(getFindResultWithSingleHit());
const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient;
- const signal = await readSignals({
+ const rule = await readRules({
alertsClient: unsafeCast,
id: null,
ruleId: 'rule-1',
});
- expect(signal).toEqual(getResult());
+ expect(rule).toEqual(getResult());
});
test('should return null if id and ruleId are null', async () => {
@@ -76,12 +76,12 @@ describe('read_signals', () => {
alertsClient.find.mockResolvedValue(getFindResultWithSingleHit());
const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient;
- const signal = await readSignals({
+ const rule = await readRules({
alertsClient: unsafeCast,
id: null,
ruleId: null,
});
- expect(signal).toEqual(null);
+ expect(rule).toEqual(null);
});
test('should return null if id and ruleId are undefined', async () => {
@@ -90,27 +90,27 @@ describe('read_signals', () => {
alertsClient.find.mockResolvedValue(getFindResultWithSingleHit());
const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient;
- const signal = await readSignals({
+ const rule = await readRules({
alertsClient: unsafeCast,
id: undefined,
ruleId: undefined,
});
- expect(signal).toEqual(null);
+ expect(rule).toEqual(null);
});
});
- describe('readSignalByRuleId', () => {
+ describe('readRuleByRuleId', () => {
test('should return a single value if the rule id matches', async () => {
const alertsClient = alertsClientMock.create();
alertsClient.get.mockResolvedValue(getResult());
alertsClient.find.mockResolvedValue(getFindResultWithSingleHit());
const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient;
- const signal = await readSignalByRuleId({
+ const rule = await readRuleByRuleId({
alertsClient: unsafeCast,
ruleId: 'rule-1',
});
- expect(signal).toEqual(getResult());
+ expect(rule).toEqual(getResult());
});
test('should not return a single value if the rule id does not match', async () => {
@@ -119,11 +119,11 @@ describe('read_signals', () => {
alertsClient.find.mockResolvedValue(getFindResultWithSingleHit());
const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient;
- const signal = await readSignalByRuleId({
+ const rule = await readRuleByRuleId({
alertsClient: unsafeCast,
ruleId: 'rule-that-should-not-match-anything',
});
- expect(signal).toEqual(null);
+ expect(rule).toEqual(null);
});
test('should return a single value of rule-1 with multiple values', async () => {
@@ -140,11 +140,11 @@ describe('read_signals', () => {
alertsClient.find.mockResolvedValue(getFindResultWithMultiHits([result1, result2]));
const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient;
- const signal = await readSignalByRuleId({
+ const rule = await readRuleByRuleId({
alertsClient: unsafeCast,
ruleId: 'rule-1',
});
- expect(signal).toEqual(result1);
+ expect(rule).toEqual(result1);
});
test('should return a single value of rule-2 with multiple values', async () => {
@@ -161,11 +161,11 @@ describe('read_signals', () => {
alertsClient.find.mockResolvedValue(getFindResultWithMultiHits([result1, result2]));
const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient;
- const signal = await readSignalByRuleId({
+ const rule = await readRuleByRuleId({
alertsClient: unsafeCast,
ruleId: 'rule-2',
});
- expect(signal).toEqual(result2);
+ expect(rule).toEqual(result2);
});
test('should return null for a made up value with multiple values', async () => {
@@ -182,57 +182,57 @@ describe('read_signals', () => {
alertsClient.find.mockResolvedValue(getFindResultWithMultiHits([result1, result2]));
const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient;
- const signal = await readSignalByRuleId({
+ const rule = await readRuleByRuleId({
alertsClient: unsafeCast,
ruleId: 'rule-that-should-not-match-anything',
});
- expect(signal).toEqual(null);
+ expect(rule).toEqual(null);
});
});
- describe('findSignalInArrayByRuleId', () => {
+ describe('findRuleInArrayByRuleId', () => {
test('returns null if the objects are not of a signal rule type', () => {
- const signal = findSignalInArrayByRuleId(
+ const rule = findRuleInArrayByRuleId(
[
{ alertTypeId: 'made up 1', params: { ruleId: '123' } },
{ alertTypeId: 'made up 2', params: { ruleId: '456' } },
],
'123'
);
- expect(signal).toEqual(null);
+ expect(rule).toEqual(null);
});
test('returns correct type if the objects are of a signal rule type', () => {
- const signal = findSignalInArrayByRuleId(
+ const rule = findRuleInArrayByRuleId(
[
{ alertTypeId: SIGNALS_ID, params: { ruleId: '123' } },
{ alertTypeId: 'made up 2', params: { ruleId: '456' } },
],
'123'
);
- expect(signal).toEqual({ alertTypeId: 'siem.signals', params: { ruleId: '123' } });
+ expect(rule).toEqual({ alertTypeId: 'siem.signals', params: { ruleId: '123' } });
});
test('returns second correct type if the objects are of a signal rule type', () => {
- const signal = findSignalInArrayByRuleId(
+ const rule = findRuleInArrayByRuleId(
[
{ alertTypeId: SIGNALS_ID, params: { ruleId: '123' } },
{ alertTypeId: SIGNALS_ID, params: { ruleId: '456' } },
],
'456'
);
- expect(signal).toEqual({ alertTypeId: 'siem.signals', params: { ruleId: '456' } });
+ expect(rule).toEqual({ alertTypeId: 'siem.signals', params: { ruleId: '456' } });
});
test('returns null with correct types but data does not exist', () => {
- const signal = findSignalInArrayByRuleId(
+ const rule = findRuleInArrayByRuleId(
[
{ alertTypeId: SIGNALS_ID, params: { ruleId: '123' } },
{ alertTypeId: SIGNALS_ID, params: { ruleId: '456' } },
],
'892'
);
- expect(signal).toEqual(null);
+ expect(rule).toEqual(null);
});
});
});
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_signals.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_rules.ts
similarity index 50%
rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_signals.ts
rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_rules.ts
index 3c49112aaf50b..5c33526329016 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_signals.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/read_rules.ts
@@ -4,20 +4,20 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { findSignals } from './find_signals';
-import { SignalAlertType, isAlertTypeArray, ReadSignalParams, ReadSignalByRuleId } from './types';
+import { findRules } from './find_rules';
+import { RuleAlertType, isAlertTypeArray, ReadRuleParams, ReadRuleByRuleId } from './types';
-export const findSignalInArrayByRuleId = (
+export const findRuleInArrayByRuleId = (
objects: object[],
ruleId: string
-): SignalAlertType | null => {
+): RuleAlertType | null => {
if (isAlertTypeArray(objects)) {
- const signals: SignalAlertType[] = objects;
- const signal: SignalAlertType[] = signals.filter(datum => {
+ const rules: RuleAlertType[] = objects;
+ const rule: RuleAlertType[] = rules.filter(datum => {
return datum.params.ruleId === ruleId;
});
- if (signal.length !== 0) {
- return signal[0];
+ if (rule.length !== 0) {
+ return rule[0];
} else {
return null;
}
@@ -26,32 +26,32 @@ export const findSignalInArrayByRuleId = (
}
};
-// This an extremely slow and inefficient way of getting a signal by its id.
-// I have to manually query every single record since the Signal Params are
+// This an extremely slow and inefficient way of getting a rule by its id.
+// I have to manually query every single record since the rule Params are
// not indexed and I cannot push in my own _id when I create an alert at the moment.
// TODO: Once we can directly push in the _id, then we should no longer need this way.
// TODO: This is meant to be _very_ temporary.
-export const readSignalByRuleId = async ({
+export const readRuleByRuleId = async ({
alertsClient,
ruleId,
-}: ReadSignalByRuleId): Promise => {
- const firstSignals = await findSignals({ alertsClient, page: 1 });
- const firstSignal = findSignalInArrayByRuleId(firstSignals.data, ruleId);
- if (firstSignal != null) {
- return firstSignal;
+}: ReadRuleByRuleId): Promise => {
+ const firstRules = await findRules({ alertsClient, page: 1 });
+ const firstRule = findRuleInArrayByRuleId(firstRules.data, ruleId);
+ if (firstRule != null) {
+ return firstRule;
} else {
- const totalPages = Math.ceil(firstSignals.total / firstSignals.perPage);
+ const totalPages = Math.ceil(firstRules.total / firstRules.perPage);
return Array(totalPages)
.fill({})
.map((_, page) => {
// page index never starts at zero. It always has to be 1 or greater
- return findSignals({ alertsClient, page: page + 1 });
+ return findRules({ alertsClient, page: page + 1 });
})
- .reduce>(async (accum, findSignal) => {
- const signals = await findSignal;
- const signal = findSignalInArrayByRuleId(signals.data, ruleId);
- if (signal != null) {
- return signal;
+ .reduce>(async (accum, findRule) => {
+ const rules = await findRule;
+ const rule = findRuleInArrayByRuleId(rules.data, ruleId);
+ if (rule != null) {
+ return rule;
} else {
return accum;
}
@@ -59,7 +59,7 @@ export const readSignalByRuleId = async ({
}
};
-export const readSignals = async ({ alertsClient, id, ruleId }: ReadSignalParams) => {
+export const readRules = async ({ alertsClient, id, ruleId }: ReadRuleParams) => {
if (id != null) {
try {
const output = await alertsClient.get({ id });
@@ -73,7 +73,7 @@ export const readSignals = async ({ alertsClient, id, ruleId }: ReadSignalParams
}
}
} else if (ruleId != null) {
- return readSignalByRuleId({ alertsClient, ruleId });
+ return readRuleByRuleId({ alertsClient, ruleId });
} else {
// should never get here, and yet here we are.
return null;
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/signals_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts
similarity index 71%
rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/signals_alert_type.ts
rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts
index 8308bca68e9af..91d7d18a4945c 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/signals_alert_type.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/rules_alert_type.ts
@@ -6,14 +6,25 @@
import { schema } from '@kbn/config-schema';
import { Logger } from 'src/core/server';
-import { SIGNALS_ID } from '../../../../common/constants';
+import {
+ SIGNALS_ID,
+ DEFAULT_MAX_SIGNALS,
+ DEFAULT_SEARCH_AFTER_PAGE_SIZE,
+} from '../../../../common/constants';
import { buildEventsSearchQuery } from './build_events_query';
-import { searchAfterAndBulkIndex } from './utils';
-import { SignalAlertTypeDefinition } from './types';
+import { searchAfterAndBulkCreate } from './utils';
+import { RuleAlertTypeDefinition } from './types';
import { getFilter } from './get_filter';
+import { getInputOutputIndex } from './get_input_output_index';
-export const signalsAlertType = ({ logger }: { logger: Logger }): SignalAlertTypeDefinition => {
+export const rulesAlertType = ({
+ logger,
+ version,
+}: {
+ logger: Logger;
+ version: string;
+}): RuleAlertTypeDefinition => {
return {
id: SIGNALS_ID,
name: 'SIEM Signals',
@@ -26,21 +37,20 @@ export const signalsAlertType = ({ logger }: { logger: Logger }): SignalAlertTyp
filter: schema.nullable(schema.object({}, { allowUnknowns: true })),
ruleId: schema.string(),
immutable: schema.boolean({ defaultValue: false }),
- index: schema.arrayOf(schema.string()),
+ index: schema.nullable(schema.arrayOf(schema.string())),
language: schema.nullable(schema.string()),
- outputIndex: schema.string(),
+ outputIndex: schema.nullable(schema.string()),
savedId: schema.nullable(schema.string()),
meta: schema.nullable(schema.object({}, { allowUnknowns: true })),
query: schema.nullable(schema.string()),
filters: schema.nullable(schema.arrayOf(schema.object({}, { allowUnknowns: true }))),
- maxSignals: schema.number({ defaultValue: 10000 }),
+ maxSignals: schema.number({ defaultValue: DEFAULT_MAX_SIGNALS }),
riskScore: schema.number(),
severity: schema.string(),
tags: schema.arrayOf(schema.string(), { defaultValue: [] }),
to: schema.string(),
type: schema.string(),
references: schema.arrayOf(schema.string(), { defaultValue: [] }),
- size: schema.maybe(schema.number()),
}),
},
async executor({ alertId, services, params }) {
@@ -56,7 +66,6 @@ export const signalsAlertType = ({ logger }: { logger: Logger }): SignalAlertTyp
query,
to,
type,
- size,
} = params;
// TODO: Remove this hard extraction of name once this is fixed: https://github.com/elastic/kibana/issues/50522
@@ -68,8 +77,18 @@ export const signalsAlertType = ({ logger }: { logger: Logger }): SignalAlertTyp
const interval: string = savedObject.attributes.interval;
const enabled: boolean = savedObject.attributes.enabled;
- const searchAfterSize = size ? size : 1000;
+ // set searchAfter page size to be the lesser of default page size or maxSignals.
+ const searchAfterSize =
+ DEFAULT_SEARCH_AFTER_PAGE_SIZE <= params.maxSignals
+ ? DEFAULT_SEARCH_AFTER_PAGE_SIZE
+ : params.maxSignals;
+ const { inputIndex, outputIndex: signalsIndex } = await getInputOutputIndex(
+ services,
+ version,
+ index,
+ outputIndex
+ );
const esFilter = await getFilter({
type,
filter,
@@ -78,11 +97,11 @@ export const signalsAlertType = ({ logger }: { logger: Logger }): SignalAlertTyp
query,
savedId,
services,
- index,
+ index: inputIndex,
});
const noReIndex = buildEventsSearchQuery({
- index,
+ index: inputIndex,
from,
to,
filter: esFilter,
@@ -98,22 +117,27 @@ export const signalsAlertType = ({ logger }: { logger: Logger }): SignalAlertTyp
const noReIndexResult = await services.callCluster('search', noReIndex);
if (noReIndexResult.hits.total.value !== 0) {
logger.info(
- `Total signals found from signal rule "id: ${alertId}", "ruleId: ${ruleId}": ${noReIndexResult.hits.total.value}`
+ `Found ${
+ noReIndexResult.hits.total.value
+ } signals from the indexes of "${inputIndex.join(
+ ', '
+ )}" using signal rule "id: ${alertId}", "ruleId: ${ruleId}", pushing signals to index ${signalsIndex}`
);
}
- const bulkIndexResult = await searchAfterAndBulkIndex({
+ const bulkIndexResult = await searchAfterAndBulkCreate({
someResult: noReIndexResult,
- signalParams: params,
+ ruleParams: params,
services,
logger,
id: alertId,
- signalsIndex: outputIndex,
+ signalsIndex,
name,
createdBy,
updatedBy,
interval,
enabled,
+ pageSize: searchAfterSize,
});
if (bulkIndexResult) {
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts
index 9c6e1f99c672b..28431b8165266 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts
@@ -21,7 +21,7 @@ import { esFilters } from '../../../../../../../../src/plugins/data/server';
export type PartialFilter = Partial;
-export interface SignalAlertParams {
+export interface RuleAlertParams {
description: string;
enabled: boolean;
falsePositives: string[];
@@ -42,37 +42,36 @@ export interface SignalAlertParams {
savedId: string | undefined | null;
meta: Record | undefined | null;
severity: string;
- size: number | undefined | null;
tags: string[];
to: string;
type: 'filter' | 'query' | 'saved_query';
}
-export type SignalAlertParamsRest = Omit<
- SignalAlertParams,
+export type RuleAlertParamsRest = Omit<
+ RuleAlertParams,
'ruleId' | 'falsePositives' | 'maxSignals' | 'savedId' | 'riskScore' | 'outputIndex'
> & {
- rule_id: SignalAlertParams['ruleId'];
- false_positives: SignalAlertParams['falsePositives'];
- saved_id: SignalAlertParams['savedId'];
- max_signals: SignalAlertParams['maxSignals'];
- risk_score: SignalAlertParams['riskScore'];
- output_index: SignalAlertParams['outputIndex'];
+ rule_id: RuleAlertParams['ruleId'];
+ false_positives: RuleAlertParams['falsePositives'];
+ saved_id: RuleAlertParams['savedId'];
+ max_signals: RuleAlertParams['maxSignals'];
+ risk_score: RuleAlertParams['riskScore'];
+ output_index: RuleAlertParams['outputIndex'];
};
-export type OutputSignalAlertRest = SignalAlertParamsRest & {
+export type OutputRuleAlertRest = RuleAlertParamsRest & {
id: string;
created_by: string | undefined | null;
updated_by: string | undefined | null;
};
-export type OutputSignalES = OutputSignalAlertRest & {
+export type OutputRuleES = OutputRuleAlertRest & {
status: 'open' | 'closed';
};
-export type UpdateSignalAlertParamsRest = Partial & {
+export type UpdateRuleAlertParamsRest = Partial & {
id: string | undefined;
- rule_id: SignalAlertParams['ruleId'] | undefined;
+ rule_id: RuleAlertParams['ruleId'] | undefined;
};
export interface FindParamsRest {
@@ -89,18 +88,18 @@ export interface Clients {
actionsClient: ActionsClient;
}
-export type SignalParams = SignalAlertParams & Clients;
+export type RuleParams = RuleAlertParams & Clients;
-export type UpdateSignalParams = Partial & {
+export type UpdateRuleParams = Partial & {
id: string | undefined | null;
} & Clients;
-export type DeleteSignalParams = Clients & {
+export type DeleteRuleParams = Clients & {
id: string | undefined;
ruleId: string | undefined | null;
};
-export interface FindSignalsRequest extends Omit {
+export interface FindRulesRequest extends Omit {
query: {
per_page: number;
page: number;
@@ -112,7 +111,7 @@ export interface FindSignalsRequest extends Omit {
};
}
-export interface FindSignalParams {
+export interface FindRuleParams {
alertsClient: AlertsClient;
perPage?: number;
page?: number;
@@ -122,34 +121,34 @@ export interface FindSignalParams {
sortOrder?: 'asc' | 'desc';
}
-export interface ReadSignalParams {
+export interface ReadRuleParams {
alertsClient: AlertsClient;
id?: string | undefined | null;
ruleId?: string | undefined | null;
}
-export interface ReadSignalByRuleId {
+export interface ReadRuleByRuleId {
alertsClient: AlertsClient;
ruleId: string;
}
-export type AlertTypeParams = Omit;
+export type RuleTypeParams = Omit;
-export type SignalAlertType = Alert & {
+export type RuleAlertType = Alert & {
id: string;
- params: AlertTypeParams;
+ params: RuleTypeParams;
};
-export interface SignalsRequest extends RequestFacade {
- payload: SignalAlertParamsRest;
+export interface RulesRequest extends RequestFacade {
+ payload: RuleAlertParamsRest;
}
-export interface UpdateSignalsRequest extends RequestFacade {
- payload: UpdateSignalAlertParamsRest;
+export interface UpdateRulesRequest extends RequestFacade {
+ payload: UpdateRuleAlertParamsRest;
}
-export type SignalExecutorOptions = Omit & {
- params: SignalAlertParams & {
+export type RuleExecutorOptions = Omit & {
+ params: RuleAlertParams & {
scrollSize: number;
scrollLock: string;
};
@@ -173,7 +172,46 @@ export interface SignalSource {
export interface BulkResponse {
took: number;
errors: boolean;
- items: unknown[];
+ items: [
+ {
+ create: {
+ _index: string;
+ _type?: string;
+ _id: string;
+ _version: number;
+ result?: string;
+ _shards?: {
+ total: number;
+ successful: number;
+ failed: number;
+ };
+ _seq_no?: number;
+ _primary_term?: number;
+ status: number;
+ error?: {
+ type: string;
+ reason: string;
+ index_uuid?: string;
+ shard: string;
+ index: string;
+ };
+ };
+ }
+ ];
+}
+
+export interface MGetResponse {
+ docs: GetResponse[];
+}
+export interface GetResponse {
+ _index: string;
+ _type: string;
+ _id: string;
+ _version: number;
+ _seq_no: number;
+ _primary_term: number;
+ found: boolean;
+ _source: SearchTypes;
}
export type SignalSearchResponse = SearchResponse;
@@ -183,24 +221,24 @@ export type QueryRequest = Omit & {
query: { id: string | undefined; rule_id: string | undefined };
};
-// This returns true because by default a SignalAlertTypeDefinition is an AlertType
+// This returns true because by default a RuleAlertTypeDefinition is an AlertType
// since we are only increasing the strictness of params.
-export const isAlertExecutor = (obj: SignalAlertTypeDefinition): obj is AlertType => {
+export const isAlertExecutor = (obj: RuleAlertTypeDefinition): obj is AlertType => {
return true;
};
-export type SignalAlertTypeDefinition = Omit & {
- executor: ({ services, params, state }: SignalExecutorOptions) => Promise;
+export type RuleAlertTypeDefinition = Omit & {
+ executor: ({ services, params, state }: RuleExecutorOptions) => Promise;
};
-export const isAlertTypes = (obj: unknown[]): obj is SignalAlertType[] => {
- return obj.every(signal => isAlertType(signal));
+export const isAlertTypes = (obj: unknown[]): obj is RuleAlertType[] => {
+ return obj.every(rule => isAlertType(rule));
};
-export const isAlertType = (obj: unknown): obj is SignalAlertType => {
+export const isAlertType = (obj: unknown): obj is RuleAlertType => {
return get('alertTypeId', obj) === SIGNALS_ID;
};
-export const isAlertTypeArray = (objArray: unknown[]): objArray is SignalAlertType[] => {
+export const isAlertTypeArray = (objArray: unknown[]): objArray is RuleAlertType[] => {
return objArray.length === 0 || isAlertType(objArray[0]);
};
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_signals.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.test.ts
similarity index 80%
rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_signals.test.ts
rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.test.ts
index 39f7951a8eab9..1022fea93200f 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_signals.test.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.test.ts
@@ -4,21 +4,21 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { calculateInterval, calculateName } from './update_signals';
+import { calculateInterval, calculateName } from './update_rules';
-describe('update_signals', () => {
+describe('update_rules', () => {
describe('#calculateInterval', () => {
- test('given a undefined interval, it returns the signalInterval ', () => {
+ test('given a undefined interval, it returns the ruleInterval ', () => {
const interval = calculateInterval(undefined, '10m');
expect(interval).toEqual('10m');
});
- test('given a undefined signalInterval, it returns a undefined interval ', () => {
+ test('given a undefined ruleInterval, it returns a undefined interval ', () => {
const interval = calculateInterval('10m', undefined);
expect(interval).toEqual('10m');
});
- test('given both an undefined signalInterval and a undefined interval, it returns 5m', () => {
+ test('given both an undefined ruleInterval and a undefined interval, it returns 5m', () => {
const interval = calculateInterval(undefined, undefined);
expect(interval).toEqual('5m');
});
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_signals.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts
similarity index 66%
rename from x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_signals.ts
rename to x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts
index a38fd7756afa1..81360d7824230 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_signals.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/update_rules.ts
@@ -6,17 +6,17 @@
import { defaults } from 'lodash/fp';
import { AlertAction } from '../../../../../alerting/server/types';
-import { readSignals } from './read_signals';
-import { UpdateSignalParams } from './types';
+import { readRules } from './read_rules';
+import { UpdateRuleParams } from './types';
export const calculateInterval = (
interval: string | undefined,
- signalInterval: string | undefined
+ ruleInterval: string | undefined
): string => {
if (interval != null) {
return interval;
- } else if (signalInterval != null) {
- return signalInterval;
+ } else if (ruleInterval != null) {
+ return ruleInterval;
} else {
return '5m';
}
@@ -35,13 +35,13 @@ export const calculateName = ({
return originalName;
} else {
// You really should never get to this point. This is a fail safe way to send back
- // the name of "untitled" just in case a signal rule name became null or undefined at
+ // the name of "untitled" just in case a rule name became null or undefined at
// some point since TypeScript allows it.
return 'untitled';
}
};
-export const updateSignal = async ({
+export const updateRules = async ({
alertsClient,
actionsClient, // TODO: Use this whenever we add feature support for different action types
description,
@@ -68,17 +68,17 @@ export const updateSignal = async ({
to,
type,
references,
-}: UpdateSignalParams) => {
- const signal = await readSignals({ alertsClient, ruleId, id });
- if (signal == null) {
+}: UpdateRuleParams) => {
+ const rule = await readRules({ alertsClient, ruleId, id });
+ if (rule == null) {
return null;
}
- // TODO: Remove this as cast as soon as signal.actions TypeScript bug is fixed
+ // TODO: Remove this as cast as soon as rule.actions TypeScript bug is fixed
// where it is trying to return AlertAction[] or RawAlertAction[]
- const actions = (signal.actions as AlertAction[] | undefined) || [];
+ const actions = (rule.actions as AlertAction[] | undefined) || [];
- const params = signal.params || {};
+ const params = rule.params || {};
const nextParams = defaults(
{
@@ -107,18 +107,18 @@ export const updateSignal = async ({
}
);
- if (signal.enabled && !enabled) {
- await alertsClient.disable({ id: signal.id });
- } else if (!signal.enabled && enabled) {
- await alertsClient.enable({ id: signal.id });
+ if (rule.enabled && !enabled) {
+ await alertsClient.disable({ id: rule.id });
+ } else if (!rule.enabled && enabled) {
+ await alertsClient.enable({ id: rule.id });
}
return alertsClient.update({
- id: signal.id,
+ id: rule.id,
data: {
tags: [],
- name: calculateName({ updatedName: name, originalName: signal.name }),
- interval: calculateInterval(interval, signal.interval),
+ name: calculateName({ updatedName: name, originalName: rule.name }),
+ interval: calculateInterval(interval, rule.interval),
actions,
params: nextParams,
},
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts
index bc147fa1dae07..19c8d5ccc87ca 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts
@@ -3,24 +3,29 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+
+import uuid from 'uuid';
import { savedObjectsClientMock } from 'src/core/server/mocks';
import { Logger } from '../../../../../../../../src/core/server';
import {
buildBulkBody,
- singleBulkIndex,
+ generateId,
+ singleBulkCreate,
singleSearchAfter,
- searchAfterAndBulkIndex,
+ searchAfterAndBulkCreate,
} from './utils';
import {
sampleDocNoSortId,
- sampleSignalAlertParams,
+ sampleRuleAlertParams,
sampleDocSearchResultsNoSortId,
sampleDocSearchResultsNoSortIdNoHits,
+ sampleDocSearchResultsNoSortIdNoVersion,
sampleDocSearchResultsWithSortId,
sampleEmptyDocSearchResults,
repeatedSearchResultsWithSortId,
- sampleSignalId,
+ sampleBulkCreateDuplicateResult,
+ sampleRuleGuid,
} from './__mocks__/es_results';
import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants';
@@ -46,11 +51,12 @@ describe('utils', () => {
});
describe('buildBulkBody', () => {
test('if bulk body builds well-defined body', () => {
- const sampleParams = sampleSignalAlertParams(undefined);
+ const fakeUuid = uuid.v4();
+ const sampleParams = sampleRuleAlertParams(undefined);
const fakeSignalSourceHit = buildBulkBody({
- doc: sampleDocNoSortId,
- signalParams: sampleParams,
- id: sampleSignalId,
+ doc: sampleDocNoSortId(fakeUuid),
+ ruleParams: sampleParams,
+ id: sampleRuleGuid,
name: 'rule-name',
createdBy: 'elastic',
updatedBy: 'elastic',
@@ -59,11 +65,13 @@ describe('utils', () => {
});
// Timestamp will potentially always be different so remove it for the test
delete fakeSignalSourceHit['@timestamp'];
+ if (fakeSignalSourceHit.signal.parent) {
+ delete fakeSignalSourceHit.signal.parent.id;
+ }
expect(fakeSignalSourceHit).toEqual({
someKey: 'someValue',
signal: {
parent: {
- id: 'someFakeId',
type: 'event',
index: 'myFakeSignalIndex',
depth: 1,
@@ -88,7 +96,6 @@ describe('utils', () => {
severity: 'high',
tags: ['some fake tag'],
type: 'query',
- size: 1000,
status: 'open',
to: 'now',
enabled: true,
@@ -99,9 +106,115 @@ describe('utils', () => {
});
});
});
- describe('singleBulkIndex', () => {
- test('create successful bulk index', async () => {
- const sampleParams = sampleSignalAlertParams(undefined);
+ describe('singleBulkCreate', () => {
+ describe('create signal id gereateId', () => {
+ test('two docs with same index, id, and version should have same id', () => {
+ const findex = 'myfakeindex';
+ const fid = 'somefakeid';
+ const version = '1';
+ const ruleId = 'rule-1';
+ // 'myfakeindexsomefakeid1rule-1'
+ const generatedHash = '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679';
+ const firstHash = generateId(findex, fid, version, ruleId);
+ const secondHash = generateId(findex, fid, version, ruleId);
+ expect(firstHash).toEqual(generatedHash);
+ expect(secondHash).toEqual(generatedHash);
+ expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field
+ expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512);
+ });
+ test('two docs with different index, id, and version should have different id', () => {
+ const findex = 'myfakeindex';
+ const findex2 = 'mysecondfakeindex';
+ const fid = 'somefakeid';
+ const version = '1';
+ const ruleId = 'rule-1';
+ // 'myfakeindexsomefakeid1rule-1'
+ const firstGeneratedHash =
+ '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679';
+ // 'mysecondfakeindexsomefakeid1rule-1'
+ const secondGeneratedHash =
+ 'a852941273f805ffe9006e574601acc8ae1148d6c0b3f7f8c4785cba8f6b768a';
+ const firstHash = generateId(findex, fid, version, ruleId);
+ const secondHash = generateId(findex2, fid, version, ruleId);
+ expect(firstHash).toEqual(firstGeneratedHash);
+ expect(secondHash).toEqual(secondGeneratedHash);
+ expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field
+ expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512);
+ expect(firstHash).not.toEqual(secondHash);
+ });
+ test('two docs with same index, different id, and same version should have different id', () => {
+ const findex = 'myfakeindex';
+ const fid = 'somefakeid';
+ const fid2 = 'somefakeid2';
+ const version = '1';
+ const ruleId = 'rule-1';
+ // 'myfakeindexsomefakeid1rule-1'
+ const firstGeneratedHash =
+ '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679';
+ // 'myfakeindexsomefakeid21rule-1'
+ const secondGeneratedHash =
+ '7d33faea18159fd010c4b79890620e8b12cdc88ec1d370149d0e5552ce860255';
+ const firstHash = generateId(findex, fid, version, ruleId);
+ const secondHash = generateId(findex, fid2, version, ruleId);
+ expect(firstHash).toEqual(firstGeneratedHash);
+ expect(secondHash).toEqual(secondGeneratedHash);
+ expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field
+ expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512);
+ expect(firstHash).not.toEqual(secondHash);
+ });
+ test('two docs with same index, same id, and different version should have different id', () => {
+ const findex = 'myfakeindex';
+ const fid = 'somefakeid';
+ const version = '1';
+ const version2 = '2';
+ const ruleId = 'rule-1';
+ // 'myfakeindexsomefakeid1rule-1'
+ const firstGeneratedHash =
+ '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679';
+ // myfakeindexsomefakeid2rule-1'
+ const secondGeneratedHash =
+ 'f016f3071fa9df9221d2fb2ba92389d4d388a4347c6ec7a4012c01cb1c640a40';
+ const firstHash = generateId(findex, fid, version, ruleId);
+ const secondHash = generateId(findex, fid, version2, ruleId);
+ expect(firstHash).toEqual(firstGeneratedHash);
+ expect(secondHash).toEqual(secondGeneratedHash);
+ expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field
+ expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512);
+ expect(firstHash).not.toEqual(secondHash);
+ });
+ test('Ensure generated id is less than 512 bytes, even for really really long strings', () => {
+ const longIndexName =
+ 'myfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindexmyfakeindex';
+ const fid = 'somefakeid';
+ const version = '1';
+ const ruleId = 'rule-1';
+ const firstHash = generateId(longIndexName, fid, version, ruleId);
+ expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field
+ });
+ test('two docs with same index, same id, same version number, and different rule ids should have different id', () => {
+ const findex = 'myfakeindex';
+ const fid = 'somefakeid';
+ const version = '1';
+ const ruleId = 'rule-1';
+ const ruleId2 = 'rule-2';
+ // 'myfakeindexsomefakeid1rule-1'
+ const firstGeneratedHash =
+ '342404d620be4344d6d90dd0461d1d1848aec457944d5c5f40cc0cbfedb36679';
+ // myfakeindexsomefakeid1rule-2'
+ const secondGeneratedHash =
+ '1eb04f997086f8b3b143d4d9b18ac178c4a7423f71a5dad9ba8b9e92603c6863';
+ const firstHash = generateId(findex, fid, version, ruleId);
+ const secondHash = generateId(findex, fid, version, ruleId2);
+ expect(firstHash).toEqual(firstGeneratedHash);
+ expect(secondHash).toEqual(secondGeneratedHash);
+ expect(Buffer.byteLength(firstHash, 'utf8')).toBeLessThan(512); // 512 bytes is maximum size of _id field
+ expect(Buffer.byteLength(secondHash, 'utf8')).toBeLessThan(512);
+ expect(firstHash).not.toEqual(secondHash);
+ });
+ });
+ test('create successful bulk create', async () => {
+ const fakeUuid = uuid.v4();
+ const sampleParams = sampleRuleAlertParams(undefined);
const sampleSearchResult = sampleDocSearchResultsNoSortId;
mockService.callCluster.mockReturnValueOnce({
took: 100,
@@ -112,12 +225,12 @@ describe('utils', () => {
},
],
});
- const successfulSingleBulkIndex = await singleBulkIndex({
- someResult: sampleSearchResult,
- signalParams: sampleParams,
+ const successfulsingleBulkCreate = await singleBulkCreate({
+ someResult: sampleSearchResult(fakeUuid),
+ ruleParams: sampleParams,
services: mockService,
logger: mockLogger,
- id: sampleSignalId,
+ id: sampleRuleGuid,
signalsIndex: DEFAULT_SIGNALS_INDEX,
name: 'rule-name',
createdBy: 'elastic',
@@ -125,18 +238,46 @@ describe('utils', () => {
interval: '5m',
enabled: true,
});
- expect(successfulSingleBulkIndex).toEqual(true);
+ expect(successfulsingleBulkCreate).toEqual(true);
});
- test('create unsuccessful bulk index due to empty search results', async () => {
- const sampleParams = sampleSignalAlertParams(undefined);
+ test('create successful bulk create with docs with no versioning', async () => {
+ const fakeUuid = uuid.v4();
+ const sampleParams = sampleRuleAlertParams(undefined);
+ const sampleSearchResult = sampleDocSearchResultsNoSortIdNoVersion;
+ mockService.callCluster.mockReturnValueOnce({
+ took: 100,
+ errors: false,
+ items: [
+ {
+ fakeItemValue: 'fakeItemKey',
+ },
+ ],
+ });
+ const successfulsingleBulkCreate = await singleBulkCreate({
+ someResult: sampleSearchResult(fakeUuid),
+ ruleParams: sampleParams,
+ services: mockService,
+ logger: mockLogger,
+ id: sampleRuleGuid,
+ signalsIndex: DEFAULT_SIGNALS_INDEX,
+ name: 'rule-name',
+ createdBy: 'elastic',
+ updatedBy: 'elastic',
+ interval: '5m',
+ enabled: true,
+ });
+ expect(successfulsingleBulkCreate).toEqual(true);
+ });
+ test('create unsuccessful bulk create due to empty search results', async () => {
+ const sampleParams = sampleRuleAlertParams(undefined);
const sampleSearchResult = sampleEmptyDocSearchResults;
mockService.callCluster.mockReturnValue(false);
- const successfulSingleBulkIndex = await singleBulkIndex({
+ const successfulsingleBulkCreate = await singleBulkCreate({
someResult: sampleSearchResult,
- signalParams: sampleParams,
+ ruleParams: sampleParams,
services: mockService,
logger: mockLogger,
- id: sampleSignalId,
+ id: sampleRuleGuid,
signalsIndex: DEFAULT_SIGNALS_INDEX,
name: 'rule-name',
createdBy: 'elastic',
@@ -144,22 +285,19 @@ describe('utils', () => {
interval: '5m',
enabled: true,
});
- expect(successfulSingleBulkIndex).toEqual(true);
+ expect(successfulsingleBulkCreate).toEqual(true);
});
- test('create unsuccessful bulk index due to bulk index errors', async () => {
- // need a sample search result, sample signal params, mock service, mock logger
- const sampleParams = sampleSignalAlertParams(undefined);
+ test('create successful bulk create when bulk create has errors', async () => {
+ const fakeUuid = uuid.v4();
+ const sampleParams = sampleRuleAlertParams(undefined);
const sampleSearchResult = sampleDocSearchResultsNoSortId;
- mockService.callCluster.mockReturnValue({
- took: 100,
- errors: true,
- });
- const successfulSingleBulkIndex = await singleBulkIndex({
- someResult: sampleSearchResult,
- signalParams: sampleParams,
+ mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult);
+ const successfulsingleBulkCreate = await singleBulkCreate({
+ someResult: sampleSearchResult(fakeUuid),
+ ruleParams: sampleParams,
services: mockService,
logger: mockLogger,
- id: sampleSignalId,
+ id: sampleRuleGuid,
signalsIndex: DEFAULT_SIGNALS_INDEX,
name: 'rule-name',
createdBy: 'elastic',
@@ -168,72 +306,77 @@ describe('utils', () => {
enabled: true,
});
expect(mockLogger.error).toHaveBeenCalled();
- expect(successfulSingleBulkIndex).toEqual(false);
+ expect(successfulsingleBulkCreate).toEqual(true);
});
});
describe('singleSearchAfter', () => {
test('if singleSearchAfter works without a given sort id', async () => {
let searchAfterSortId;
- const sampleParams = sampleSignalAlertParams(undefined);
+ const sampleParams = sampleRuleAlertParams(undefined);
mockService.callCluster.mockReturnValue(sampleDocSearchResultsNoSortId);
await expect(
singleSearchAfter({
searchAfterSortId,
- signalParams: sampleParams,
+ ruleParams: sampleParams,
services: mockService,
logger: mockLogger,
+ pageSize: 1,
})
).rejects.toThrow('Attempted to search after with empty sort id');
});
test('if singleSearchAfter works with a given sort id', async () => {
const searchAfterSortId = '1234567891111';
- const sampleParams = sampleSignalAlertParams(undefined);
+ const sampleParams = sampleRuleAlertParams(undefined);
mockService.callCluster.mockReturnValue(sampleDocSearchResultsWithSortId);
const searchAfterResult = await singleSearchAfter({
searchAfterSortId,
- signalParams: sampleParams,
+ ruleParams: sampleParams,
services: mockService,
logger: mockLogger,
+ pageSize: 1,
});
expect(searchAfterResult).toEqual(sampleDocSearchResultsWithSortId);
});
test('if singleSearchAfter throws error', async () => {
const searchAfterSortId = '1234567891111';
- const sampleParams = sampleSignalAlertParams(undefined);
+ const sampleParams = sampleRuleAlertParams(undefined);
mockService.callCluster.mockImplementation(async () => {
throw Error('Fake Error');
});
await expect(
singleSearchAfter({
searchAfterSortId,
- signalParams: sampleParams,
+ ruleParams: sampleParams,
services: mockService,
logger: mockLogger,
+ pageSize: 1,
})
).rejects.toThrow('Fake Error');
});
});
- describe('searchAfterAndBulkIndex', () => {
+ describe('searchAfterAndBulkCreate', () => {
test('if successful with empty search results', async () => {
- const sampleParams = sampleSignalAlertParams(undefined);
- const result = await searchAfterAndBulkIndex({
+ const sampleParams = sampleRuleAlertParams(undefined);
+ const result = await searchAfterAndBulkCreate({
someResult: sampleEmptyDocSearchResults,
- signalParams: sampleParams,
+ ruleParams: sampleParams,
services: mockService,
logger: mockLogger,
- id: sampleSignalId,
+ id: sampleRuleGuid,
signalsIndex: DEFAULT_SIGNALS_INDEX,
name: 'rule-name',
createdBy: 'elastic',
updatedBy: 'elastic',
interval: '5m',
enabled: true,
+ pageSize: 1,
});
expect(mockService.callCluster).toHaveBeenCalledTimes(0);
expect(result).toEqual(true);
});
test('if successful iteration of while loop with maxDocs', async () => {
- const sampleParams = sampleSignalAlertParams(10);
+ const sampleParams = sampleRuleAlertParams(30);
+ const someGuids = Array.from({ length: 13 }).map(x => uuid.v4());
mockService.callCluster
.mockReturnValueOnce({
took: 100,
@@ -244,7 +387,7 @@ describe('utils', () => {
},
],
})
- .mockReturnValueOnce(repeatedSearchResultsWithSortId(4))
+ .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(0, 3)))
.mockReturnValueOnce({
took: 100,
errors: false,
@@ -254,7 +397,7 @@ describe('utils', () => {
},
],
})
- .mockReturnValueOnce(repeatedSearchResultsWithSortId(4))
+ .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(3, 6)))
.mockReturnValueOnce({
took: 100,
errors: false,
@@ -264,47 +407,47 @@ describe('utils', () => {
},
],
});
- const result = await searchAfterAndBulkIndex({
- someResult: repeatedSearchResultsWithSortId(4),
- signalParams: sampleParams,
+ const result = await searchAfterAndBulkCreate({
+ someResult: repeatedSearchResultsWithSortId(3, 1, someGuids.slice(6, 9)),
+ ruleParams: sampleParams,
services: mockService,
logger: mockLogger,
- id: sampleSignalId,
+ id: sampleRuleGuid,
signalsIndex: DEFAULT_SIGNALS_INDEX,
name: 'rule-name',
createdBy: 'elastic',
updatedBy: 'elastic',
interval: '5m',
enabled: true,
+ pageSize: 1,
});
expect(mockService.callCluster).toHaveBeenCalledTimes(5);
expect(result).toEqual(true);
});
- test('if unsuccessful first bulk index', async () => {
- const sampleParams = sampleSignalAlertParams(10);
- mockService.callCluster.mockReturnValue({
- took: 100,
- errors: true, // will cause singleBulkIndex to return false
- });
- const result = await searchAfterAndBulkIndex({
- someResult: repeatedSearchResultsWithSortId(4),
- signalParams: sampleParams,
+ test('if unsuccessful first bulk create', async () => {
+ const someGuids = Array.from({ length: 4 }).map(x => uuid.v4());
+ const sampleParams = sampleRuleAlertParams(10);
+ mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult);
+ const result = await searchAfterAndBulkCreate({
+ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids),
+ ruleParams: sampleParams,
services: mockService,
logger: mockLogger,
- id: sampleSignalId,
+ id: sampleRuleGuid,
signalsIndex: DEFAULT_SIGNALS_INDEX,
name: 'rule-name',
createdBy: 'elastic',
updatedBy: 'elastic',
interval: '5m',
enabled: true,
+ pageSize: 1,
});
expect(mockLogger.error).toHaveBeenCalled();
expect(result).toEqual(false);
});
- test('if unsuccessful iteration of searchAfterAndBulkIndex due to empty sort ids', async () => {
- const sampleParams = sampleSignalAlertParams(undefined);
-
+ test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids', async () => {
+ const sampleParams = sampleRuleAlertParams(undefined);
+ const someUuid = uuid.v4();
mockService.callCluster.mockReturnValueOnce({
took: 100,
errors: false,
@@ -314,24 +457,26 @@ describe('utils', () => {
},
],
});
- const result = await searchAfterAndBulkIndex({
- someResult: sampleDocSearchResultsNoSortId,
- signalParams: sampleParams,
+ const result = await searchAfterAndBulkCreate({
+ someResult: sampleDocSearchResultsNoSortId(someUuid),
+ ruleParams: sampleParams,
services: mockService,
logger: mockLogger,
- id: sampleSignalId,
+ id: sampleRuleGuid,
signalsIndex: DEFAULT_SIGNALS_INDEX,
name: 'rule-name',
createdBy: 'elastic',
updatedBy: 'elastic',
interval: '5m',
enabled: true,
+ pageSize: 1,
});
expect(mockLogger.error).toHaveBeenCalled();
expect(result).toEqual(false);
});
- test('if unsuccessful iteration of searchAfterAndBulkIndex due to empty sort ids and 0 total hits', async () => {
- const sampleParams = sampleSignalAlertParams(undefined);
+ test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids and 0 total hits', async () => {
+ const sampleParams = sampleRuleAlertParams(undefined);
+ const someUuid = uuid.v4();
mockService.callCluster.mockReturnValueOnce({
took: 100,
errors: false,
@@ -341,23 +486,26 @@ describe('utils', () => {
},
],
});
- const result = await searchAfterAndBulkIndex({
- someResult: sampleDocSearchResultsNoSortIdNoHits,
- signalParams: sampleParams,
+ const result = await searchAfterAndBulkCreate({
+ someResult: sampleDocSearchResultsNoSortIdNoHits(someUuid),
+ ruleParams: sampleParams,
services: mockService,
logger: mockLogger,
- id: sampleSignalId,
+ id: sampleRuleGuid,
signalsIndex: DEFAULT_SIGNALS_INDEX,
name: 'rule-name',
createdBy: 'elastic',
updatedBy: 'elastic',
interval: '5m',
enabled: true,
+ pageSize: 1,
});
expect(result).toEqual(true);
});
test('if successful iteration of while loop with maxDocs and search after returns results with no sort ids', async () => {
- const sampleParams = sampleSignalAlertParams(10);
+ const sampleParams = sampleRuleAlertParams(10);
+ const oneGuid = uuid.v4();
+ const someGuids = Array.from({ length: 4 }).map(x => uuid.v4());
mockService.callCluster
.mockReturnValueOnce({
took: 100,
@@ -368,24 +516,26 @@ describe('utils', () => {
},
],
})
- .mockReturnValueOnce(sampleDocSearchResultsNoSortId);
- const result = await searchAfterAndBulkIndex({
- someResult: repeatedSearchResultsWithSortId(4),
- signalParams: sampleParams,
+ .mockReturnValueOnce(sampleDocSearchResultsNoSortId(oneGuid));
+ const result = await searchAfterAndBulkCreate({
+ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids),
+ ruleParams: sampleParams,
services: mockService,
logger: mockLogger,
- id: sampleSignalId,
+ id: sampleRuleGuid,
signalsIndex: DEFAULT_SIGNALS_INDEX,
name: 'rule-name',
createdBy: 'elastic',
updatedBy: 'elastic',
interval: '5m',
enabled: true,
+ pageSize: 1,
});
expect(result).toEqual(true);
});
test('if successful iteration of while loop with maxDocs and search after returns empty results with no sort ids', async () => {
- const sampleParams = sampleSignalAlertParams(10);
+ const sampleParams = sampleRuleAlertParams(10);
+ const someGuids = Array.from({ length: 4 }).map(x => uuid.v4());
mockService.callCluster
.mockReturnValueOnce({
took: 100,
@@ -397,53 +547,25 @@ describe('utils', () => {
],
})
.mockReturnValueOnce(sampleEmptyDocSearchResults);
- const result = await searchAfterAndBulkIndex({
- someResult: repeatedSearchResultsWithSortId(4),
- signalParams: sampleParams,
- services: mockService,
- logger: mockLogger,
- id: sampleSignalId,
- signalsIndex: DEFAULT_SIGNALS_INDEX,
- name: 'rule-name',
- createdBy: 'elastic',
- updatedBy: 'elastic',
- interval: '5m',
- enabled: true,
- });
- expect(result).toEqual(true);
- });
- test('if logs error when iteration is unsuccessful when bulk index results in a failure', async () => {
- const sampleParams = sampleSignalAlertParams(5);
- mockService.callCluster
- .mockReturnValueOnce({
- // first bulk insert
- took: 100,
- errors: false,
- items: [
- {
- fakeItemValue: 'fakeItemKey',
- },
- ],
- })
- .mockReturnValueOnce(sampleDocSearchResultsWithSortId); // get some more docs
- const result = await searchAfterAndBulkIndex({
- someResult: repeatedSearchResultsWithSortId(4),
- signalParams: sampleParams,
+ const result = await searchAfterAndBulkCreate({
+ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids),
+ ruleParams: sampleParams,
services: mockService,
logger: mockLogger,
- id: sampleSignalId,
+ id: sampleRuleGuid,
signalsIndex: DEFAULT_SIGNALS_INDEX,
name: 'rule-name',
createdBy: 'elastic',
updatedBy: 'elastic',
interval: '5m',
enabled: true,
+ pageSize: 1,
});
- expect(mockLogger.error).toHaveBeenCalled();
expect(result).toEqual(true);
});
test('if returns false when singleSearchAfter throws an exception', async () => {
- const sampleParams = sampleSignalAlertParams(10);
+ const sampleParams = sampleRuleAlertParams(10);
+ const someGuids = Array.from({ length: 4 }).map(x => uuid.v4());
mockService.callCluster
.mockReturnValueOnce({
took: 100,
@@ -454,19 +576,22 @@ describe('utils', () => {
},
],
})
- .mockRejectedValueOnce(Error('Fake Error'));
- const result = await searchAfterAndBulkIndex({
- someResult: repeatedSearchResultsWithSortId(4),
- signalParams: sampleParams,
+ .mockImplementation(() => {
+ throw Error('Fake Error');
+ });
+ const result = await searchAfterAndBulkCreate({
+ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids),
+ ruleParams: sampleParams,
services: mockService,
logger: mockLogger,
- id: sampleSignalId,
+ id: sampleRuleGuid,
signalsIndex: DEFAULT_SIGNALS_INDEX,
name: 'rule-name',
createdBy: 'elastic',
updatedBy: 'elastic',
interval: '5m',
enabled: true,
+ pageSize: 1,
});
expect(result).toEqual(false);
});
diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts
index 25934dc9aa356..ba3f310c886ce 100644
--- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts
+++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts
@@ -3,6 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+import { createHash } from 'crypto';
import { performance } from 'perf_hooks';
import { pickBy } from 'lodash/fp';
import { SignalHit, Signal } from '../../types';
@@ -12,13 +13,13 @@ import {
SignalSourceHit,
SignalSearchResponse,
BulkResponse,
- AlertTypeParams,
- OutputSignalES,
+ RuleTypeParams,
+ OutputRuleES,
} from './types';
import { buildEventsSearchQuery } from './build_events_query';
interface BuildRuleParams {
- signalParams: AlertTypeParams;
+ ruleParams: RuleTypeParams;
name: string;
id: string;
enabled: boolean;
@@ -28,47 +29,46 @@ interface BuildRuleParams {
}
export const buildRule = ({
- signalParams,
+ ruleParams,
name,
id,
enabled,
createdBy,
updatedBy,
interval,
-}: BuildRuleParams): Partial => {
- return pickBy((value: unknown) => value != null, {
+}: BuildRuleParams): Partial => {
+ return pickBy((value: unknown) => value != null, {
id,
status: 'open',
- rule_id: signalParams.ruleId,
- false_positives: signalParams.falsePositives,
- saved_id: signalParams.savedId,
- meta: signalParams.meta,
- max_signals: signalParams.maxSignals,
- risk_score: signalParams.riskScore,
- output_index: signalParams.outputIndex,
- description: signalParams.description,
- filter: signalParams.filter,
- from: signalParams.from,
- immutable: signalParams.immutable,
- index: signalParams.index,
+ rule_id: ruleParams.ruleId,
+ false_positives: ruleParams.falsePositives,
+ saved_id: ruleParams.savedId,
+ meta: ruleParams.meta,
+ max_signals: ruleParams.maxSignals,
+ risk_score: ruleParams.riskScore,
+ output_index: ruleParams.outputIndex,
+ description: ruleParams.description,
+ filter: ruleParams.filter,
+ from: ruleParams.from,
+ immutable: ruleParams.immutable,
+ index: ruleParams.index,
interval,
- language: signalParams.language,
+ language: ruleParams.language,
name,
- query: signalParams.query,
- references: signalParams.references,
- severity: signalParams.severity,
- tags: signalParams.tags,
- type: signalParams.type,
- size: signalParams.size,
- to: signalParams.to,
+ query: ruleParams.query,
+ references: ruleParams.references,
+ severity: ruleParams.severity,
+ tags: ruleParams.tags,
+ type: ruleParams.type,
+ to: ruleParams.to,
enabled,
- filters: signalParams.filters,
+ filters: ruleParams.filters,
created_by: createdBy,
updated_by: updatedBy,
});
};
-export const buildSignal = (doc: SignalSourceHit, rule: Partial): Signal => {
+export const buildSignal = (doc: SignalSourceHit, rule: Partial