diff --git a/.sass-lint.yml b/.sass-lint.yml index 89735342a2d6f..9b31f3fae6d16 100644 --- a/.sass-lint.yml +++ b/.sass-lint.yml @@ -4,7 +4,6 @@ files: - 'src/legacy/core_plugins/timelion/**/*.s+(a|c)ss' - 'src/legacy/core_plugins/vis_type_vislib/**/*.s+(a|c)ss' - 'src/legacy/core_plugins/vis_type_xy/**/*.s+(a|c)ss' - - 'x-pack/legacy/plugins/rollup/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/security/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/canvas/**/*.s+(a|c)ss' - 'x-pack/plugins/triggers_actions_ui/**/*.s+(a|c)ss' diff --git a/Jenkinsfile b/Jenkinsfile index 6646ee15ba1c2..98cc6994f229b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -11,7 +11,7 @@ kibanaPipeline(timeoutMinutes: 135, checkPrChanges: true) { 'kibana-intake-agent': workers.intake('kibana-intake', './test/scripts/jenkins_unit.sh'), 'x-pack-intake-agent': workers.intake('x-pack-intake', './test/scripts/jenkins_xpack.sh'), 'kibana-oss-agent': workers.functional('kibana-oss-tests', { kibanaPipeline.buildOss() }, [ - // 'oss-firefoxSmoke': kibanaPipeline.functionalTestProcess('kibana-firefoxSmoke', './test/scripts/jenkins_firefox_smoke.sh'), + 'oss-firefoxSmoke': kibanaPipeline.functionalTestProcess('kibana-firefoxSmoke', './test/scripts/jenkins_firefox_smoke.sh'), 'oss-ciGroup1': kibanaPipeline.ossCiGroupProcess(1), 'oss-ciGroup2': kibanaPipeline.ossCiGroupProcess(2), 'oss-ciGroup3': kibanaPipeline.ossCiGroupProcess(3), @@ -28,7 +28,7 @@ kibanaPipeline(timeoutMinutes: 135, checkPrChanges: true) { // 'oss-visualRegression': kibanaPipeline.functionalTestProcess('visualRegression', './test/scripts/jenkins_visual_regression.sh'), ]), 'kibana-xpack-agent': workers.functional('kibana-xpack-tests', { kibanaPipeline.buildXpack() }, [ - // 'xpack-firefoxSmoke': kibanaPipeline.functionalTestProcess('xpack-firefoxSmoke', './test/scripts/jenkins_xpack_firefox_smoke.sh'), + 'xpack-firefoxSmoke': kibanaPipeline.functionalTestProcess('xpack-firefoxSmoke', './test/scripts/jenkins_xpack_firefox_smoke.sh'), 'xpack-ciGroup1': kibanaPipeline.xpackCiGroupProcess(1), 'xpack-ciGroup2': kibanaPipeline.xpackCiGroupProcess(2), 'xpack-ciGroup3': kibanaPipeline.xpackCiGroupProcess(3), diff --git a/docs/developer/core/development-functional-tests.asciidoc b/docs/developer/core/development-functional-tests.asciidoc index 51b5273851ce7..2b091d9abb9fc 100644 --- a/docs/developer/core/development-functional-tests.asciidoc +++ b/docs/developer/core/development-functional-tests.asciidoc @@ -154,16 +154,16 @@ A test suite is a collection of tests defined by calling `describe()`, and then Use tags in `describe()` function to group functional tests. Tags include: * `ciGroup{id}` - Assigns test suite to a specific CI worker * `skipCloud` and `skipFirefox` - Excludes test suite from running on Cloud or Firefox -* `smoke` - Groups tests that run on Chrome and Firefox +* `includeFirefox` - Groups tests that run on Chrome and Firefox **Cross-browser testing**::: -On CI, all the functional tests are executed in Chrome by default. To also run a suite against Firefox, assign the `smoke` tag: +On CI, all the functional tests are executed in Chrome by default. To also run a suite against Firefox, assign the `includeFirefox` tag: ["source","js"] ----------- // on CI test suite will be run twice: in Chrome and Firefox describe('My Cross-browser Test Suite', function () { - this.tags('smoke'); + this.tags('includeFirefox'); it('My First Test'); } diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 5450e84417f89..a91a5bec988b7 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -263,13 +263,14 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectAttribute](./kibana-plugin-core-server.savedobjectattribute.md) | Type definition for a Saved Object attribute value | | [SavedObjectAttributeSingle](./kibana-plugin-core-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-core-server.savedobjectattribute.md) | | [SavedObjectMigrationFn](./kibana-plugin-core-server.savedobjectmigrationfn.md) | A migration function for a [saved object type](./kibana-plugin-core-server.savedobjectstype.md) used to migrate it to a given version | -| [SavedObjectSanitizedDoc](./kibana-plugin-core-server.savedobjectsanitizeddoc.md) | | -| [SavedObjectsClientContract](./kibana-plugin-core-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-core-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-core-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) | +| [SavedObjectSanitizedDoc](./kibana-plugin-core-server.savedobjectsanitizeddoc.md) | Describes Saved Object documents that have passed through the migration framework and are guaranteed to have a references root property. | +| [SavedObjectsClientContract](./kibana-plugin-core-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-core-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.See [SavedObjectsClient](./kibana-plugin-core-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) | | [SavedObjectsClientFactory](./kibana-plugin-core-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. | | [SavedObjectsClientFactoryProvider](./kibana-plugin-core-server.savedobjectsclientfactoryprovider.md) | Provider to invoke to retrieve a [SavedObjectsClientFactory](./kibana-plugin-core-server.savedobjectsclientfactory.md). | | [SavedObjectsClientWrapperFactory](./kibana-plugin-core-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | | [SavedObjectsFieldMapping](./kibana-plugin-core-server.savedobjectsfieldmapping.md) | Describe a [saved object type mapping](./kibana-plugin-core-server.savedobjectstypemappingdefinition.md) field.Please refer to [elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html) For the mapping documentation | | [SavedObjectsNamespaceType](./kibana-plugin-core-server.savedobjectsnamespacetype.md) | The namespace type dictates how a saved object can be interacted in relation to namespaces. Each type is mutually exclusive: \* single (default): this type of saved object is namespace-isolated, e.g., it exists in only one namespace. \* multiple: this type of saved object is shareable, e.g., it can exist in one or more namespaces. \* agnostic: this type of saved object is global.Note: do not write logic that uses this value directly; instead, use the appropriate accessors in the [type registry](./kibana-plugin-core-server.savedobjecttyperegistry.md). | +| [SavedObjectUnsanitizedDoc](./kibana-plugin-core-server.savedobjectunsanitizeddoc.md) | Describes Saved Object documents from Kibana < 7.0.0 which don't have a references root property defined. This type should only be used in migrations. | | [ScopeableRequest](./kibana-plugin-core-server.scopeablerequest.md) | A user credentials container. It accommodates the necessary auth credentials to impersonate the current user.See [KibanaRequest](./kibana-plugin-core-server.kibanarequest.md). | | [ServiceStatusLevel](./kibana-plugin-core-server.servicestatuslevel.md) | A convenience type that represents the union of each value in [ServiceStatusLevels](./kibana-plugin-core-server.servicestatuslevels.md). | | [SharedGlobalConfig](./kibana-plugin-core-server.sharedglobalconfig.md) | | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsanitizeddoc.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsanitizeddoc.md index 47feb50e9a827..6d4e252fe7532 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsanitizeddoc.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsanitizeddoc.md @@ -4,6 +4,7 @@ ## SavedObjectSanitizedDoc type +Describes Saved Object documents that have passed through the migration framework and are guaranteed to have a `references` root property. Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientcontract.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientcontract.md index c39df0655f1d4..610356a733126 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientcontract.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientcontract.md @@ -30,10 +30,6 @@ At the time of writing we are in the process of transitioning away from the oper 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 index - -Unlike 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-core-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.createesautocreateindexerror.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.createesautocreateindexerror.md deleted file mode 100644 index 6350afacee2ba..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.createesautocreateindexerror.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) > [createEsAutoCreateIndexError](./kibana-plugin-core-server.savedobjectserrorhelpers.createesautocreateindexerror.md) - -## SavedObjectsErrorHelpers.createEsAutoCreateIndexError() method - -Signature: - -```typescript -static createEsAutoCreateIndexError(): DecoratedError; -``` -Returns: - -`DecoratedError` - diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.isesautocreateindexerror.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.isesautocreateindexerror.md deleted file mode 100644 index bdffff5c1365b..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.isesautocreateindexerror.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) > [isEsAutoCreateIndexError](./kibana-plugin-core-server.savedobjectserrorhelpers.isesautocreateindexerror.md) - -## SavedObjectsErrorHelpers.isEsAutoCreateIndexError() method - -Signature: - -```typescript -static isEsAutoCreateIndexError(error: Error | DecoratedError): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| error | Error | DecoratedError | | - -Returns: - -`boolean` - diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.md index 250b9d3899670..7874be311d52c 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.md @@ -17,7 +17,6 @@ export declare class SavedObjectsErrorHelpers | --- | --- | --- | | [createBadRequestError(reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.createbadrequesterror.md) | static | | | [createConflictError(type, id)](./kibana-plugin-core-server.savedobjectserrorhelpers.createconflicterror.md) | static | | -| [createEsAutoCreateIndexError()](./kibana-plugin-core-server.savedobjectserrorhelpers.createesautocreateindexerror.md) | static | | | [createGenericNotFoundError(type, id)](./kibana-plugin-core-server.savedobjectserrorhelpers.creategenericnotfounderror.md) | static | | | [createInvalidVersionError(versionInput)](./kibana-plugin-core-server.savedobjectserrorhelpers.createinvalidversionerror.md) | static | | | [createUnsupportedTypeError(type)](./kibana-plugin-core-server.savedobjectserrorhelpers.createunsupportedtypeerror.md) | static | | @@ -31,7 +30,6 @@ export declare class SavedObjectsErrorHelpers | [decorateRequestEntityTooLargeError(error, reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.decoraterequestentitytoolargeerror.md) | static | | | [isBadRequestError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isbadrequesterror.md) | static | | | [isConflictError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isconflicterror.md) | static | | -| [isEsAutoCreateIndexError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isesautocreateindexerror.md) | static | | | [isEsCannotExecuteScriptError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isescannotexecutescripterror.md) | static | | | [isEsUnavailableError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isesunavailableerror.md) | static | | | [isForbiddenError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isforbiddenerror.md) | static | | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectunsanitizeddoc.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectunsanitizeddoc.md new file mode 100644 index 0000000000000..be51400addbbc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectunsanitizeddoc.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectUnsanitizedDoc](./kibana-plugin-core-server.savedobjectunsanitizeddoc.md) + +## SavedObjectUnsanitizedDoc type + +Describes Saved Object documents from Kibana < 7.0.0 which don't have a `references` root property defined. This type should only be used in migrations. + +Signature: + +```typescript +export declare type SavedObjectUnsanitizedDoc = SavedObjectDoc & Partial; +``` diff --git a/docs/logs/images/logs-action-menu.png b/docs/logs/images/logs-action-menu.png new file mode 100644 index 0000000000000..f1c79b6fa88d1 Binary files /dev/null and b/docs/logs/images/logs-action-menu.png differ diff --git a/docs/logs/images/logs-view-in-context.png b/docs/logs/images/logs-view-in-context.png new file mode 100644 index 0000000000000..09a9e89fc3042 Binary files /dev/null and b/docs/logs/images/logs-view-in-context.png differ diff --git a/docs/logs/using.asciidoc b/docs/logs/using.asciidoc index 96e7c5fa4d312..4f5992f945c7a 100644 --- a/docs/logs/using.asciidoc +++ b/docs/logs/using.asciidoc @@ -69,12 +69,19 @@ To highlight a word or phrase in the logs stream, click *Highlights* and enter y [float] [[logs-event-inspector]] === Inspect a log event -To inspect a log event, hover over it, then click the *View details* icon image:logs/images/logs-view-event.png[View event icon] beside the event. -This opens the *Log event document details* fly-out that shows the fields associated with the log event. +To inspect a log event, hover over it, then click the *View actions for line* icon image:logs/images/logs-action-menu.png[View actions for line icon]. On the menu that opens, select *View details*. This opens the *Log event document details* fly-out that shows the fields associated with the log event. To quickly filter the logs stream by one of the field values, in the log event details, click the *View event with filter* icon image:logs/images/logs-view-event-with-filter.png[View event icon] beside the field. This automatically adds a search filter to the logs stream to filter the entries by this field and value. +[float] +[[log-view-in-context]] +=== View log line in context +To view a certain line in its context (for example, with other log lines from the same file, or the same cloud container), hover over it, then click the *View actions for line* image:logs/images/logs-action-menu.png[View actions for line icon]. On the menu that opens, select *View in context*. This opens the *View log in context* modal, that shows the log line in its context. + +[role="screenshot"] +image::logs/images/logs-view-in-context.png[View a log line in context] + [float] [[view-log-anomalies]] === View log anomalies diff --git a/docs/user/alerting/action-types.asciidoc b/docs/user/alerting/action-types.asciidoc index 49e7bd1d77743..8794c389d72bc 100644 --- a/docs/user/alerting/action-types.asciidoc +++ b/docs/user/alerting/action-types.asciidoc @@ -41,12 +41,14 @@ see https://www.elastic.co/subscriptions[the subscription page]. [float] [[create-connectors]] -=== Connectors +=== Preconfigured connectors and action types You can create connectors for actions in <> or via the action API. For out-of-the-box and standardized connectors, you can <> before {kib} starts. +Action type with only preconfigured connectors could be specified as a <>. + include::action-types/email.asciidoc[] include::action-types/index.asciidoc[] include::action-types/pagerduty.asciidoc[] @@ -54,3 +56,4 @@ include::action-types/server-log.asciidoc[] include::action-types/slack.asciidoc[] include::action-types/webhook.asciidoc[] include::pre-configured-connectors.asciidoc[] +include::pre-configured-action-types.asciidoc[] diff --git a/docs/user/alerting/images/pre-configured-action-type-alert-form.png b/docs/user/alerting/images/pre-configured-action-type-alert-form.png new file mode 100644 index 0000000000000..e12bad468009a Binary files /dev/null and b/docs/user/alerting/images/pre-configured-action-type-alert-form.png differ diff --git a/docs/user/alerting/images/pre-configured-action-type-managing.png b/docs/user/alerting/images/pre-configured-action-type-managing.png new file mode 100644 index 0000000000000..95fe1c6aa0958 Binary files /dev/null and b/docs/user/alerting/images/pre-configured-action-type-managing.png differ diff --git a/docs/user/alerting/images/pre-configured-action-type-select-type.png b/docs/user/alerting/images/pre-configured-action-type-select-type.png new file mode 100644 index 0000000000000..5f555f851cd81 Binary files /dev/null and b/docs/user/alerting/images/pre-configured-action-type-select-type.png differ diff --git a/docs/user/alerting/pre-configured-action-types.asciidoc b/docs/user/alerting/pre-configured-action-types.asciidoc new file mode 100644 index 0000000000000..780a2119037b1 --- /dev/null +++ b/docs/user/alerting/pre-configured-action-types.asciidoc @@ -0,0 +1,61 @@ +[role="xpack"] +[[pre-configured-action-types]] + +== Preconfigured action types + +A preconfigure an action type has all the information it needs prior to startup. +A preconfigured action type offers the following capabilities: + +- Requires no setup. Configuration and credentials needed to execute an +action are predefined. +- Has only <>. +- Connectors of the preconfigured action type cannot be edited or deleted. + +[float] +[[preconfigured-action-type-example]] +=== Creating a preconfigured action + +In the `kibana.yml` file: + +. Exclude the action type from `xpack.actions.enabledActionTypes`. +. Add all its connectors. + +The following example shows a valid configuration of preconfigured action type with one out-of-the box connector. + +```js + xpack.actions.enabledActionTypes: ['.slack', '.email', '.index'] <1> + xpack.actions.preconfigured: <2> + - id: 'my-server-log' + actionTypeId: .server-log + name: 'Server log #xyz' +``` + +<1> `enabledActionTypes` should exclude preconfigured action type to prevent creating and deleting connectors. +<2> `preconfigured` is the setting for defining the list of available connectors for the preconfigured action type. + +[float] +[[pre-configured-action-type-alert-form]] +=== Attaching a preconfigured action to an alert + +To attach an action to an alert, +select from a list of available action types, and +then select the *Server log* type. This action type was configured previously. + +[role="screenshot"] +image::images/pre-configured-action-type-alert-form.png[Create alert with selected Server log action type] + +[float] +[[managing-pre-configured-action-types]] +=== Managing preconfigured actions + +Connectors with preconfigured actions appear in the connector list, regardless of which space the user is in. +They are tagged as “preconfigured” and cannot be deleted. + +[role="screenshot"] +image::images/pre-configured-action-type-managing.png[Connectors managing tab with pre-cofigured] + +Clicking *Create connector* shows the list of available action types. +Preconfigured action types are not included because you can't create a connector with a preconfigured action type. + +[role="screenshot"] +image::images/pre-configured-action-type-select-type.png[Pre-configured connector create menu] diff --git a/package.json b/package.json index 956ad9ee4f71d..bfea8c7423f9c 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "**/@types/hapi": "^17.0.18", "**/@types/angular": "^1.6.56", "**/@types/hoist-non-react-statics": "^3.3.1", + "**/@types/chai": "^4.2.11", "**/typescript": "3.7.2", "**/graphql-toolkit/lodash": "^4.17.13", "**/hoist-non-react-statics": "^3.3.2", @@ -120,7 +121,7 @@ "@babel/core": "^7.9.0", "@babel/register": "^7.9.0", "@elastic/apm-rum": "^5.1.1", - "@elastic/charts": "18.4.1", + "@elastic/charts": "18.4.2", "@elastic/datemath": "5.0.3", "@elastic/ems-client": "7.8.0", "@elastic/eui": "22.3.0", @@ -441,7 +442,7 @@ "gulp-babel": "^8.0.0", "gulp-sourcemaps": "2.6.5", "has-ansi": "^3.0.0", - "iedriver": "^3.14.1", + "iedriver": "^3.14.2", "intl-messageformat-parser": "^1.4.0", "is-path-inside": "^2.1.0", "istanbul-instrumenter-loader": "3.0.1", @@ -450,13 +451,13 @@ "jest-raw-loader": "^1.0.1", "jimp": "^0.9.6", "json5": "^1.0.1", - "karma": "3.1.4", + "karma": "5.0.2", "karma-chrome-launcher": "2.2.0", "karma-coverage": "1.1.2", "karma-firefox-launcher": "1.1.0", "karma-ie-launcher": "1.0.0", "karma-junit-reporter": "1.2.0", - "karma-mocha": "1.3.0", + "karma-mocha": "2.0.0", "karma-safari-launcher": "1.0.0", "license-checker": "^16.0.0", "listr": "^0.14.1", diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 6a2d02ee778dd..b921956642728 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -79178,7 +79178,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(704); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; }); -/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(927); +/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(939); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); /* @@ -79363,9 +79363,9 @@ const pAll = __webpack_require__(706); const arrify = __webpack_require__(708); const globby = __webpack_require__(709); const isGlob = __webpack_require__(603); -const cpFile = __webpack_require__(912); -const junk = __webpack_require__(924); -const CpyError = __webpack_require__(925); +const cpFile = __webpack_require__(924); +const junk = __webpack_require__(936); +const CpyError = __webpack_require__(937); const defaultOptions = { ignoreJunk: true @@ -79615,8 +79615,8 @@ const fs = __webpack_require__(23); const arrayUnion = __webpack_require__(710); const glob = __webpack_require__(712); const fastGlob = __webpack_require__(717); -const dirGlob = __webpack_require__(905); -const gitignore = __webpack_require__(908); +const dirGlob = __webpack_require__(917); +const gitignore = __webpack_require__(920); const DEFAULT_FILTER = () => false; @@ -81449,11 +81449,11 @@ module.exports.generateTasks = pkg.generateTasks; Object.defineProperty(exports, "__esModule", { value: true }); var optionsManager = __webpack_require__(719); var taskManager = __webpack_require__(720); -var reader_async_1 = __webpack_require__(876); -var reader_stream_1 = __webpack_require__(900); -var reader_sync_1 = __webpack_require__(901); -var arrayUtils = __webpack_require__(903); -var streamUtils = __webpack_require__(904); +var reader_async_1 = __webpack_require__(888); +var reader_stream_1 = __webpack_require__(912); +var reader_sync_1 = __webpack_require__(913); +var arrayUtils = __webpack_require__(915); +var streamUtils = __webpack_require__(916); /** * Synchronous API. */ @@ -82085,17 +82085,17 @@ module.exports = function isGlob(str, options) { var util = __webpack_require__(29); var braces = __webpack_require__(727); -var toRegex = __webpack_require__(829); -var extend = __webpack_require__(837); +var toRegex = __webpack_require__(840); +var extend = __webpack_require__(848); /** * Local dependencies */ -var compilers = __webpack_require__(840); -var parsers = __webpack_require__(872); -var cache = __webpack_require__(873); -var utils = __webpack_require__(874); +var compilers = __webpack_require__(851); +var parsers = __webpack_require__(884); +var cache = __webpack_require__(885); +var utils = __webpack_require__(886); var MAX_LENGTH = 1024 * 64; /** @@ -82968,17 +82968,17 @@ module.exports = micromatch; */ var toRegex = __webpack_require__(728); -var unique = __webpack_require__(740); +var unique = __webpack_require__(742); var extend = __webpack_require__(737); /** * Local dependencies */ -var compilers = __webpack_require__(741); -var parsers = __webpack_require__(756); -var Braces = __webpack_require__(766); -var utils = __webpack_require__(742); +var compilers = __webpack_require__(743); +var parsers = __webpack_require__(760); +var Braces = __webpack_require__(770); +var utils = __webpack_require__(744); var MAX_LENGTH = 1024 * 64; var cache = {}; @@ -84132,7 +84132,7 @@ module.exports = function isExtendable(val) { "use strict"; -var extend = __webpack_require__(737); +var extend = __webpack_require__(740); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -84202,6 +84202,66 @@ module.exports = toRegex; /* 740 */ /***/ (function(module, exports, __webpack_require__) { +"use strict"; + + +var isObject = __webpack_require__(741); + +module.exports = function extend(o/*, objects*/) { + if (!isObject(o)) { o = {}; } + + var len = arguments.length; + for (var i = 1; i < len; i++) { + var obj = arguments[i]; + + if (isObject(obj)) { + assign(o, obj); + } + } + return o; +}; + +function assign(a, b) { + for (var key in b) { + if (hasOwn(b, key)) { + a[key] = b[key]; + } + } +} + +/** + * Returns true if the given `key` is an own property of `obj`. + */ + +function hasOwn(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); +} + + +/***/ }), +/* 741 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/*! + * is-extendable + * + * Copyright (c) 2015, Jon Schlinkert. + * Licensed under the MIT License. + */ + + + +module.exports = function isExtendable(val) { + return typeof val !== 'undefined' && val !== null + && (typeof val === 'object' || typeof val === 'function'); +}; + + +/***/ }), +/* 742 */ +/***/ (function(module, exports, __webpack_require__) { + "use strict"; /*! * array-unique @@ -84249,13 +84309,13 @@ module.exports.immutable = function uniqueImmutable(arr) { /***/ }), -/* 741 */ +/* 743 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(742); +var utils = __webpack_require__(744); module.exports = function(braces, options) { braces.compiler @@ -84538,13 +84598,13 @@ function hasQueue(node) { /***/ }), -/* 742 */ +/* 744 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var splitString = __webpack_require__(743); +var splitString = __webpack_require__(745); var utils = module.exports; /** @@ -84552,11 +84612,11 @@ var utils = module.exports; */ utils.extend = __webpack_require__(737); -utils.flatten = __webpack_require__(749); -utils.isObject = __webpack_require__(747); -utils.fillRange = __webpack_require__(750); -utils.repeat = __webpack_require__(755); -utils.unique = __webpack_require__(740); +utils.flatten = __webpack_require__(751); +utils.isObject = __webpack_require__(749); +utils.fillRange = __webpack_require__(752); +utils.repeat = __webpack_require__(759); +utils.unique = __webpack_require__(742); utils.define = function(obj, key, val) { Object.defineProperty(obj, key, { @@ -84888,7 +84948,7 @@ utils.escapeRegex = function(str) { /***/ }), -/* 743 */ +/* 745 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84901,7 +84961,7 @@ utils.escapeRegex = function(str) { -var extend = __webpack_require__(744); +var extend = __webpack_require__(746); module.exports = function(str, options, fn) { if (typeof str !== 'string') { @@ -85066,14 +85126,14 @@ function keepEscaping(opts, str, idx) { /***/ }), -/* 744 */ +/* 746 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(745); -var assignSymbols = __webpack_require__(748); +var isExtendable = __webpack_require__(747); +var assignSymbols = __webpack_require__(750); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -85133,7 +85193,7 @@ function isEnum(obj, key) { /***/ }), -/* 745 */ +/* 747 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85146,7 +85206,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(746); +var isPlainObject = __webpack_require__(748); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -85154,7 +85214,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 746 */ +/* 748 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85167,7 +85227,7 @@ module.exports = function isExtendable(val) { -var isObject = __webpack_require__(747); +var isObject = __webpack_require__(749); function isObjectObject(o) { return isObject(o) === true @@ -85198,7 +85258,7 @@ module.exports = function isPlainObject(o) { /***/ }), -/* 747 */ +/* 749 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85217,7 +85277,7 @@ module.exports = function isObject(val) { /***/ }), -/* 748 */ +/* 750 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85264,7 +85324,7 @@ module.exports = function(receiver, objects) { /***/ }), -/* 749 */ +/* 751 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85293,7 +85353,7 @@ function flat(arr, res) { /***/ }), -/* 750 */ +/* 752 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85307,10 +85367,10 @@ function flat(arr, res) { var util = __webpack_require__(29); -var isNumber = __webpack_require__(751); -var extend = __webpack_require__(737); -var repeat = __webpack_require__(753); -var toRegex = __webpack_require__(754); +var isNumber = __webpack_require__(753); +var extend = __webpack_require__(755); +var repeat = __webpack_require__(757); +var toRegex = __webpack_require__(758); /** * Return a range of numbers or letters. @@ -85508,7 +85568,7 @@ module.exports = fillRange; /***/ }), -/* 751 */ +/* 753 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85521,7 +85581,7 @@ module.exports = fillRange; -var typeOf = __webpack_require__(752); +var typeOf = __webpack_require__(754); module.exports = function isNumber(num) { var type = typeOf(num); @@ -85537,7 +85597,7 @@ module.exports = function isNumber(num) { /***/ }), -/* 752 */ +/* 754 */ /***/ (function(module, exports, __webpack_require__) { var isBuffer = __webpack_require__(734); @@ -85659,7 +85719,67 @@ module.exports = function kindOf(val) { /***/ }), -/* 753 */ +/* 755 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var isObject = __webpack_require__(756); + +module.exports = function extend(o/*, objects*/) { + if (!isObject(o)) { o = {}; } + + var len = arguments.length; + for (var i = 1; i < len; i++) { + var obj = arguments[i]; + + if (isObject(obj)) { + assign(o, obj); + } + } + return o; +}; + +function assign(a, b) { + for (var key in b) { + if (hasOwn(b, key)) { + a[key] = b[key]; + } + } +} + +/** + * Returns true if the given `key` is an own property of `obj`. + */ + +function hasOwn(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); +} + + +/***/ }), +/* 756 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/*! + * is-extendable + * + * Copyright (c) 2015, Jon Schlinkert. + * Licensed under the MIT License. + */ + + + +module.exports = function isExtendable(val) { + return typeof val !== 'undefined' && val !== null + && (typeof val === 'object' || typeof val === 'function'); +}; + + +/***/ }), +/* 757 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85736,7 +85856,7 @@ function repeat(str, num) { /***/ }), -/* 754 */ +/* 758 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85749,8 +85869,8 @@ function repeat(str, num) { -var repeat = __webpack_require__(753); -var isNumber = __webpack_require__(751); +var repeat = __webpack_require__(757); +var isNumber = __webpack_require__(753); var cache = {}; function toRegexRange(min, max, options) { @@ -86037,7 +86157,7 @@ module.exports = toRegexRange; /***/ }), -/* 755 */ +/* 759 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86062,14 +86182,14 @@ module.exports = function repeat(ele, num) { /***/ }), -/* 756 */ +/* 760 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Node = __webpack_require__(757); -var utils = __webpack_require__(742); +var Node = __webpack_require__(761); +var utils = __webpack_require__(744); /** * Braces parsers @@ -86429,15 +86549,15 @@ function concatNodes(pos, node, parent, options) { /***/ }), -/* 757 */ +/* 761 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(747); -var define = __webpack_require__(758); -var utils = __webpack_require__(765); +var isObject = __webpack_require__(749); +var define = __webpack_require__(762); +var utils = __webpack_require__(769); var ownNames; /** @@ -86928,7 +87048,7 @@ exports = module.exports = Node; /***/ }), -/* 758 */ +/* 762 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86941,7 +87061,7 @@ exports = module.exports = Node; -var isDescriptor = __webpack_require__(759); +var isDescriptor = __webpack_require__(763); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -86966,7 +87086,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 759 */ +/* 763 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86979,9 +87099,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(760); -var isAccessor = __webpack_require__(761); -var isData = __webpack_require__(763); +var typeOf = __webpack_require__(764); +var isAccessor = __webpack_require__(765); +var isData = __webpack_require__(767); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -86995,7 +87115,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 760 */ +/* 764 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -87130,7 +87250,7 @@ function isBuffer(val) { /***/ }), -/* 761 */ +/* 765 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87143,7 +87263,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(762); +var typeOf = __webpack_require__(766); // accessor descriptor properties var accessor = { @@ -87206,7 +87326,7 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 762 */ +/* 766 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -87341,7 +87461,7 @@ function isBuffer(val) { /***/ }), -/* 763 */ +/* 767 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87354,7 +87474,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(764); +var typeOf = __webpack_require__(768); module.exports = function isDataDescriptor(obj, prop) { // data descriptor properties @@ -87397,7 +87517,7 @@ module.exports = function isDataDescriptor(obj, prop) { /***/ }), -/* 764 */ +/* 768 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -87532,13 +87652,13 @@ function isBuffer(val) { /***/ }), -/* 765 */ +/* 769 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(752); +var typeOf = __webpack_require__(754); var utils = module.exports; /** @@ -88558,17 +88678,17 @@ function assert(val, message) { /***/ }), -/* 766 */ +/* 770 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var extend = __webpack_require__(737); -var Snapdragon = __webpack_require__(767); -var compilers = __webpack_require__(741); -var parsers = __webpack_require__(756); -var utils = __webpack_require__(742); +var Snapdragon = __webpack_require__(771); +var compilers = __webpack_require__(743); +var parsers = __webpack_require__(760); +var utils = __webpack_require__(744); /** * Customize Snapdragon parser and renderer @@ -88669,17 +88789,17 @@ module.exports = Braces; /***/ }), -/* 767 */ +/* 771 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Base = __webpack_require__(768); -var define = __webpack_require__(729); -var Compiler = __webpack_require__(797); -var Parser = __webpack_require__(826); -var utils = __webpack_require__(806); +var Base = __webpack_require__(772); +var define = __webpack_require__(798); +var Compiler = __webpack_require__(808); +var Parser = __webpack_require__(837); +var utils = __webpack_require__(817); var regexCache = {}; var cache = {}; @@ -88850,20 +88970,20 @@ module.exports.Parser = Parser; /***/ }), -/* 768 */ +/* 772 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(29); -var define = __webpack_require__(769); -var CacheBase = __webpack_require__(770); -var Emitter = __webpack_require__(771); -var isObject = __webpack_require__(747); -var merge = __webpack_require__(788); -var pascal = __webpack_require__(791); -var cu = __webpack_require__(792); +var define = __webpack_require__(773); +var CacheBase = __webpack_require__(774); +var Emitter = __webpack_require__(775); +var isObject = __webpack_require__(749); +var merge = __webpack_require__(792); +var pascal = __webpack_require__(795); +var cu = __webpack_require__(796); /** * Optionally define a custom `cache` namespace to use. @@ -89292,7 +89412,7 @@ module.exports.namespace = namespace; /***/ }), -/* 769 */ +/* 773 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -89305,7 +89425,7 @@ module.exports.namespace = namespace; -var isDescriptor = __webpack_require__(759); +var isDescriptor = __webpack_require__(763); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -89330,21 +89450,21 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 770 */ +/* 774 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(747); -var Emitter = __webpack_require__(771); -var visit = __webpack_require__(772); -var toPath = __webpack_require__(775); -var union = __webpack_require__(776); -var del = __webpack_require__(780); -var get = __webpack_require__(778); -var has = __webpack_require__(785); -var set = __webpack_require__(779); +var isObject = __webpack_require__(749); +var Emitter = __webpack_require__(775); +var visit = __webpack_require__(776); +var toPath = __webpack_require__(779); +var union = __webpack_require__(780); +var del = __webpack_require__(784); +var get = __webpack_require__(782); +var has = __webpack_require__(789); +var set = __webpack_require__(783); /** * Create a `Cache` constructor that when instantiated will @@ -89598,7 +89718,7 @@ module.exports.namespace = namespace; /***/ }), -/* 771 */ +/* 775 */ /***/ (function(module, exports, __webpack_require__) { @@ -89767,7 +89887,7 @@ Emitter.prototype.hasListeners = function(event){ /***/ }), -/* 772 */ +/* 776 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -89780,8 +89900,8 @@ Emitter.prototype.hasListeners = function(event){ -var visit = __webpack_require__(773); -var mapVisit = __webpack_require__(774); +var visit = __webpack_require__(777); +var mapVisit = __webpack_require__(778); module.exports = function(collection, method, val) { var result; @@ -89804,7 +89924,7 @@ module.exports = function(collection, method, val) { /***/ }), -/* 773 */ +/* 777 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -89817,7 +89937,7 @@ module.exports = function(collection, method, val) { -var isObject = __webpack_require__(747); +var isObject = __webpack_require__(749); module.exports = function visit(thisArg, method, target, val) { if (!isObject(thisArg) && typeof thisArg !== 'function') { @@ -89844,14 +89964,14 @@ module.exports = function visit(thisArg, method, target, val) { /***/ }), -/* 774 */ +/* 778 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(29); -var visit = __webpack_require__(773); +var visit = __webpack_require__(777); /** * Map `visit` over an array of objects. @@ -89888,7 +90008,7 @@ function isObject(val) { /***/ }), -/* 775 */ +/* 779 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -89901,7 +90021,7 @@ function isObject(val) { -var typeOf = __webpack_require__(752); +var typeOf = __webpack_require__(754); module.exports = function toPath(args) { if (typeOf(args) !== 'arguments') { @@ -89928,16 +90048,16 @@ function filter(arr) { /***/ }), -/* 776 */ +/* 780 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(738); -var union = __webpack_require__(777); -var get = __webpack_require__(778); -var set = __webpack_require__(779); +var isObject = __webpack_require__(741); +var union = __webpack_require__(781); +var get = __webpack_require__(782); +var set = __webpack_require__(783); module.exports = function unionValue(obj, prop, value) { if (!isObject(obj)) { @@ -89965,7 +90085,7 @@ function arrayify(val) { /***/ }), -/* 777 */ +/* 781 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90001,7 +90121,7 @@ module.exports = function union(init) { /***/ }), -/* 778 */ +/* 782 */ /***/ (function(module, exports) { /*! @@ -90057,7 +90177,7 @@ function toString(val) { /***/ }), -/* 779 */ +/* 783 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90070,10 +90190,10 @@ function toString(val) { -var split = __webpack_require__(743); -var extend = __webpack_require__(737); -var isPlainObject = __webpack_require__(746); -var isObject = __webpack_require__(738); +var split = __webpack_require__(745); +var extend = __webpack_require__(740); +var isPlainObject = __webpack_require__(748); +var isObject = __webpack_require__(741); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -90119,7 +90239,7 @@ function isValidKey(key) { /***/ }), -/* 780 */ +/* 784 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90132,8 +90252,8 @@ function isValidKey(key) { -var isObject = __webpack_require__(747); -var has = __webpack_require__(781); +var isObject = __webpack_require__(749); +var has = __webpack_require__(785); module.exports = function unset(obj, prop) { if (!isObject(obj)) { @@ -90158,7 +90278,7 @@ module.exports = function unset(obj, prop) { /***/ }), -/* 781 */ +/* 785 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90171,9 +90291,9 @@ module.exports = function unset(obj, prop) { -var isObject = __webpack_require__(782); -var hasValues = __webpack_require__(784); -var get = __webpack_require__(778); +var isObject = __webpack_require__(786); +var hasValues = __webpack_require__(788); +var get = __webpack_require__(782); module.exports = function(obj, prop, noZero) { if (isObject(obj)) { @@ -90184,7 +90304,7 @@ module.exports = function(obj, prop, noZero) { /***/ }), -/* 782 */ +/* 786 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90197,7 +90317,7 @@ module.exports = function(obj, prop, noZero) { -var isArray = __webpack_require__(783); +var isArray = __webpack_require__(787); module.exports = function isObject(val) { return val != null && typeof val === 'object' && isArray(val) === false; @@ -90205,7 +90325,7 @@ module.exports = function isObject(val) { /***/ }), -/* 783 */ +/* 787 */ /***/ (function(module, exports) { var toString = {}.toString; @@ -90216,7 +90336,7 @@ module.exports = Array.isArray || function (arr) { /***/ }), -/* 784 */ +/* 788 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90259,7 +90379,7 @@ module.exports = function hasValue(o, noZero) { /***/ }), -/* 785 */ +/* 789 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90272,9 +90392,9 @@ module.exports = function hasValue(o, noZero) { -var isObject = __webpack_require__(747); -var hasValues = __webpack_require__(786); -var get = __webpack_require__(778); +var isObject = __webpack_require__(749); +var hasValues = __webpack_require__(790); +var get = __webpack_require__(782); module.exports = function(val, prop) { return hasValues(isObject(val) && prop ? get(val, prop) : val); @@ -90282,7 +90402,7 @@ module.exports = function(val, prop) { /***/ }), -/* 786 */ +/* 790 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90295,8 +90415,8 @@ module.exports = function(val, prop) { -var typeOf = __webpack_require__(787); -var isNumber = __webpack_require__(751); +var typeOf = __webpack_require__(791); +var isNumber = __webpack_require__(753); module.exports = function hasValue(val) { // is-number checks for NaN and other edge cases @@ -90349,7 +90469,7 @@ module.exports = function hasValue(val) { /***/ }), -/* 787 */ +/* 791 */ /***/ (function(module, exports, __webpack_require__) { var isBuffer = __webpack_require__(734); @@ -90474,14 +90594,14 @@ module.exports = function kindOf(val) { /***/ }), -/* 788 */ +/* 792 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(789); -var forIn = __webpack_require__(790); +var isExtendable = __webpack_require__(793); +var forIn = __webpack_require__(794); function mixinDeep(target, objects) { var len = arguments.length, i = 0; @@ -90545,7 +90665,7 @@ module.exports = mixinDeep; /***/ }), -/* 789 */ +/* 793 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90558,7 +90678,7 @@ module.exports = mixinDeep; -var isPlainObject = __webpack_require__(746); +var isPlainObject = __webpack_require__(748); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -90566,7 +90686,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 790 */ +/* 794 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90589,7 +90709,7 @@ module.exports = function forIn(obj, fn, thisArg) { /***/ }), -/* 791 */ +/* 795 */ /***/ (function(module, exports) { /*! @@ -90616,14 +90736,14 @@ module.exports = pascalcase; /***/ }), -/* 792 */ +/* 796 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(29); -var utils = __webpack_require__(793); +var utils = __webpack_require__(797); /** * Expose class utils @@ -90988,7 +91108,7 @@ cu.bubble = function(Parent, events) { /***/ }), -/* 793 */ +/* 797 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -91002,10 +91122,10 @@ var utils = {}; * Lazily required module dependencies */ -utils.union = __webpack_require__(777); -utils.define = __webpack_require__(729); -utils.isObj = __webpack_require__(747); -utils.staticExtend = __webpack_require__(794); +utils.union = __webpack_require__(781); +utils.define = __webpack_require__(798); +utils.isObj = __webpack_require__(749); +utils.staticExtend = __webpack_require__(805); /** @@ -91016,36 +91136,638 @@ module.exports = utils; /***/ }), -/* 794 */ +/* 798 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /*! - * static-extend + * define-property * - * Copyright (c) 2016, Jon Schlinkert. + * Copyright (c) 2015, Jon Schlinkert. * Licensed under the MIT License. */ -var copy = __webpack_require__(795); -var define = __webpack_require__(729); -var util = __webpack_require__(29); +var isDescriptor = __webpack_require__(799); -/** - * Returns a function for extending the static properties, - * prototype properties, and descriptors from the `Parent` - * constructor onto `Child` constructors. - * - * ```js - * var extend = require('static-extend'); - * Parent.extend = extend(Parent); - * - * // optionally pass a custom merge function as the second arg - * Parent.extend = extend(Parent, function(Child) { - * Child.prototype.mixin = function(key, val) { - * Child.prototype[key] = val; +module.exports = function defineProperty(obj, prop, val) { + if (typeof obj !== 'object' && typeof obj !== 'function') { + throw new TypeError('expected an object or function.'); + } + + if (typeof prop !== 'string') { + throw new TypeError('expected `prop` to be a string.'); + } + + if (isDescriptor(val) && ('set' in val || 'get' in val)) { + return Object.defineProperty(obj, prop, val); + } + + return Object.defineProperty(obj, prop, { + configurable: true, + enumerable: false, + writable: true, + value: val + }); +}; + + +/***/ }), +/* 799 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/*! + * is-descriptor + * + * Copyright (c) 2015-2017, Jon Schlinkert. + * Released under the MIT License. + */ + + + +var typeOf = __webpack_require__(800); +var isAccessor = __webpack_require__(801); +var isData = __webpack_require__(803); + +module.exports = function isDescriptor(obj, key) { + if (typeOf(obj) !== 'object') { + return false; + } + if ('get' in obj) { + return isAccessor(obj, key); + } + return isData(obj, key); +}; + + +/***/ }), +/* 800 */ +/***/ (function(module, exports) { + +var toString = Object.prototype.toString; + +/** + * Get the native `typeof` a value. + * + * @param {*} `val` + * @return {*} Native javascript type + */ + +module.exports = function kindOf(val) { + var type = typeof val; + + // primitivies + if (type === 'undefined') { + return 'undefined'; + } + if (val === null) { + return 'null'; + } + if (val === true || val === false || val instanceof Boolean) { + return 'boolean'; + } + if (type === 'string' || val instanceof String) { + return 'string'; + } + if (type === 'number' || val instanceof Number) { + return 'number'; + } + + // functions + if (type === 'function' || val instanceof Function) { + if (typeof val.constructor.name !== 'undefined' && val.constructor.name.slice(0, 9) === 'Generator') { + return 'generatorfunction'; + } + return 'function'; + } + + // array + if (typeof Array.isArray !== 'undefined' && Array.isArray(val)) { + return 'array'; + } + + // check for instances of RegExp and Date before calling `toString` + if (val instanceof RegExp) { + return 'regexp'; + } + if (val instanceof Date) { + return 'date'; + } + + // other objects + type = toString.call(val); + + if (type === '[object RegExp]') { + return 'regexp'; + } + if (type === '[object Date]') { + return 'date'; + } + if (type === '[object Arguments]') { + return 'arguments'; + } + if (type === '[object Error]') { + return 'error'; + } + if (type === '[object Promise]') { + return 'promise'; + } + + // buffer + if (isBuffer(val)) { + return 'buffer'; + } + + // es6: Map, WeakMap, Set, WeakSet + if (type === '[object Set]') { + return 'set'; + } + if (type === '[object WeakSet]') { + return 'weakset'; + } + if (type === '[object Map]') { + return 'map'; + } + if (type === '[object WeakMap]') { + return 'weakmap'; + } + if (type === '[object Symbol]') { + return 'symbol'; + } + + if (type === '[object Map Iterator]') { + return 'mapiterator'; + } + if (type === '[object Set Iterator]') { + return 'setiterator'; + } + if (type === '[object String Iterator]') { + return 'stringiterator'; + } + if (type === '[object Array Iterator]') { + return 'arrayiterator'; + } + + // typed arrays + if (type === '[object Int8Array]') { + return 'int8array'; + } + if (type === '[object Uint8Array]') { + return 'uint8array'; + } + if (type === '[object Uint8ClampedArray]') { + return 'uint8clampedarray'; + } + if (type === '[object Int16Array]') { + return 'int16array'; + } + if (type === '[object Uint16Array]') { + return 'uint16array'; + } + if (type === '[object Int32Array]') { + return 'int32array'; + } + if (type === '[object Uint32Array]') { + return 'uint32array'; + } + if (type === '[object Float32Array]') { + return 'float32array'; + } + if (type === '[object Float64Array]') { + return 'float64array'; + } + + // must be a plain object + return 'object'; +}; + +/** + * If you need to support Safari 5-7 (8-10 yr-old browser), + * take a look at https://github.com/feross/is-buffer + */ + +function isBuffer(val) { + return val.constructor + && typeof val.constructor.isBuffer === 'function' + && val.constructor.isBuffer(val); +} + + +/***/ }), +/* 801 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/*! + * is-accessor-descriptor + * + * Copyright (c) 2015, Jon Schlinkert. + * Licensed under the MIT License. + */ + + + +var typeOf = __webpack_require__(802); + +// accessor descriptor properties +var accessor = { + get: 'function', + set: 'function', + configurable: 'boolean', + enumerable: 'boolean' +}; + +function isAccessorDescriptor(obj, prop) { + if (typeof prop === 'string') { + var val = Object.getOwnPropertyDescriptor(obj, prop); + return typeof val !== 'undefined'; + } + + if (typeOf(obj) !== 'object') { + return false; + } + + if (has(obj, 'value') || has(obj, 'writable')) { + return false; + } + + if (!has(obj, 'get') || typeof obj.get !== 'function') { + return false; + } + + // tldr: it's valid to have "set" be undefined + // "set" might be undefined if `Object.getOwnPropertyDescriptor` + // was used to get the value, and only `get` was defined by the user + if (has(obj, 'set') && typeof obj[key] !== 'function' && typeof obj[key] !== 'undefined') { + return false; + } + + for (var key in obj) { + if (!accessor.hasOwnProperty(key)) { + continue; + } + + if (typeOf(obj[key]) === accessor[key]) { + continue; + } + + if (typeof obj[key] !== 'undefined') { + return false; + } + } + return true; +} + +function has(obj, key) { + return {}.hasOwnProperty.call(obj, key); +} + +/** + * Expose `isAccessorDescriptor` + */ + +module.exports = isAccessorDescriptor; + + +/***/ }), +/* 802 */ +/***/ (function(module, exports, __webpack_require__) { + +var isBuffer = __webpack_require__(734); +var toString = Object.prototype.toString; + +/** + * Get the native `typeof` a value. + * + * @param {*} `val` + * @return {*} Native javascript type + */ + +module.exports = function kindOf(val) { + // primitivies + if (typeof val === 'undefined') { + return 'undefined'; + } + if (val === null) { + return 'null'; + } + if (val === true || val === false || val instanceof Boolean) { + return 'boolean'; + } + if (typeof val === 'string' || val instanceof String) { + return 'string'; + } + if (typeof val === 'number' || val instanceof Number) { + return 'number'; + } + + // functions + if (typeof val === 'function' || val instanceof Function) { + return 'function'; + } + + // array + if (typeof Array.isArray !== 'undefined' && Array.isArray(val)) { + return 'array'; + } + + // check for instances of RegExp and Date before calling `toString` + if (val instanceof RegExp) { + return 'regexp'; + } + if (val instanceof Date) { + return 'date'; + } + + // other objects + var type = toString.call(val); + + if (type === '[object RegExp]') { + return 'regexp'; + } + if (type === '[object Date]') { + return 'date'; + } + if (type === '[object Arguments]') { + return 'arguments'; + } + if (type === '[object Error]') { + return 'error'; + } + + // buffer + if (isBuffer(val)) { + return 'buffer'; + } + + // es6: Map, WeakMap, Set, WeakSet + if (type === '[object Set]') { + return 'set'; + } + if (type === '[object WeakSet]') { + return 'weakset'; + } + if (type === '[object Map]') { + return 'map'; + } + if (type === '[object WeakMap]') { + return 'weakmap'; + } + if (type === '[object Symbol]') { + return 'symbol'; + } + + // typed arrays + if (type === '[object Int8Array]') { + return 'int8array'; + } + if (type === '[object Uint8Array]') { + return 'uint8array'; + } + if (type === '[object Uint8ClampedArray]') { + return 'uint8clampedarray'; + } + if (type === '[object Int16Array]') { + return 'int16array'; + } + if (type === '[object Uint16Array]') { + return 'uint16array'; + } + if (type === '[object Int32Array]') { + return 'int32array'; + } + if (type === '[object Uint32Array]') { + return 'uint32array'; + } + if (type === '[object Float32Array]') { + return 'float32array'; + } + if (type === '[object Float64Array]') { + return 'float64array'; + } + + // must be a plain object + return 'object'; +}; + + +/***/ }), +/* 803 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/*! + * is-data-descriptor + * + * Copyright (c) 2015, Jon Schlinkert. + * Licensed under the MIT License. + */ + + + +var typeOf = __webpack_require__(804); + +// data descriptor properties +var data = { + configurable: 'boolean', + enumerable: 'boolean', + writable: 'boolean' +}; + +function isDataDescriptor(obj, prop) { + if (typeOf(obj) !== 'object') { + return false; + } + + if (typeof prop === 'string') { + var val = Object.getOwnPropertyDescriptor(obj, prop); + return typeof val !== 'undefined'; + } + + if (!('value' in obj) && !('writable' in obj)) { + return false; + } + + for (var key in obj) { + if (key === 'value') continue; + + if (!data.hasOwnProperty(key)) { + continue; + } + + if (typeOf(obj[key]) === data[key]) { + continue; + } + + if (typeof obj[key] !== 'undefined') { + return false; + } + } + return true; +} + +/** + * Expose `isDataDescriptor` + */ + +module.exports = isDataDescriptor; + + +/***/ }), +/* 804 */ +/***/ (function(module, exports, __webpack_require__) { + +var isBuffer = __webpack_require__(734); +var toString = Object.prototype.toString; + +/** + * Get the native `typeof` a value. + * + * @param {*} `val` + * @return {*} Native javascript type + */ + +module.exports = function kindOf(val) { + // primitivies + if (typeof val === 'undefined') { + return 'undefined'; + } + if (val === null) { + return 'null'; + } + if (val === true || val === false || val instanceof Boolean) { + return 'boolean'; + } + if (typeof val === 'string' || val instanceof String) { + return 'string'; + } + if (typeof val === 'number' || val instanceof Number) { + return 'number'; + } + + // functions + if (typeof val === 'function' || val instanceof Function) { + return 'function'; + } + + // array + if (typeof Array.isArray !== 'undefined' && Array.isArray(val)) { + return 'array'; + } + + // check for instances of RegExp and Date before calling `toString` + if (val instanceof RegExp) { + return 'regexp'; + } + if (val instanceof Date) { + return 'date'; + } + + // other objects + var type = toString.call(val); + + if (type === '[object RegExp]') { + return 'regexp'; + } + if (type === '[object Date]') { + return 'date'; + } + if (type === '[object Arguments]') { + return 'arguments'; + } + if (type === '[object Error]') { + return 'error'; + } + + // buffer + if (isBuffer(val)) { + return 'buffer'; + } + + // es6: Map, WeakMap, Set, WeakSet + if (type === '[object Set]') { + return 'set'; + } + if (type === '[object WeakSet]') { + return 'weakset'; + } + if (type === '[object Map]') { + return 'map'; + } + if (type === '[object WeakMap]') { + return 'weakmap'; + } + if (type === '[object Symbol]') { + return 'symbol'; + } + + // typed arrays + if (type === '[object Int8Array]') { + return 'int8array'; + } + if (type === '[object Uint8Array]') { + return 'uint8array'; + } + if (type === '[object Uint8ClampedArray]') { + return 'uint8clampedarray'; + } + if (type === '[object Int16Array]') { + return 'int16array'; + } + if (type === '[object Uint16Array]') { + return 'uint16array'; + } + if (type === '[object Int32Array]') { + return 'int32array'; + } + if (type === '[object Uint32Array]') { + return 'uint32array'; + } + if (type === '[object Float32Array]') { + return 'float32array'; + } + if (type === '[object Float64Array]') { + return 'float64array'; + } + + // must be a plain object + return 'object'; +}; + + +/***/ }), +/* 805 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/*! + * static-extend + * + * Copyright (c) 2016, Jon Schlinkert. + * Licensed under the MIT License. + */ + + + +var copy = __webpack_require__(806); +var define = __webpack_require__(798); +var util = __webpack_require__(29); + +/** + * Returns a function for extending the static properties, + * prototype properties, and descriptors from the `Parent` + * constructor onto `Child` constructors. + * + * ```js + * var extend = require('static-extend'); + * Parent.extend = extend(Parent); + * + * // optionally pass a custom merge function as the second arg + * Parent.extend = extend(Parent, function(Child) { + * Child.prototype.mixin = function(key, val) { + * Child.prototype[key] = val; * }; * }); * @@ -91113,15 +91835,15 @@ module.exports = extend; /***/ }), -/* 795 */ +/* 806 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(752); -var copyDescriptor = __webpack_require__(796); -var define = __webpack_require__(729); +var typeOf = __webpack_require__(754); +var copyDescriptor = __webpack_require__(807); +var define = __webpack_require__(798); /** * Copy static properties, prototype properties, and descriptors from one object to another. @@ -91294,7 +92016,7 @@ module.exports.has = has; /***/ }), -/* 796 */ +/* 807 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -91382,16 +92104,16 @@ function isObject(val) { /***/ }), -/* 797 */ +/* 808 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(798); -var define = __webpack_require__(729); -var debug = __webpack_require__(800)('snapdragon:compiler'); -var utils = __webpack_require__(806); +var use = __webpack_require__(809); +var define = __webpack_require__(798); +var debug = __webpack_require__(811)('snapdragon:compiler'); +var utils = __webpack_require__(817); /** * Create a new `Compiler` with the given `options`. @@ -91545,7 +92267,7 @@ Compiler.prototype = { // source map support if (opts.sourcemap) { - var sourcemaps = __webpack_require__(825); + var sourcemaps = __webpack_require__(836); sourcemaps(this); this.mapVisit(this.ast.nodes); this.applySourceMaps(); @@ -91566,7 +92288,7 @@ module.exports = Compiler; /***/ }), -/* 798 */ +/* 809 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -91579,7 +92301,7 @@ module.exports = Compiler; -var utils = __webpack_require__(799); +var utils = __webpack_require__(810); module.exports = function base(app, opts) { if (!utils.isObject(app) && typeof app !== 'function') { @@ -91694,7 +92416,7 @@ module.exports = function base(app, opts) { /***/ }), -/* 799 */ +/* 810 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -91708,8 +92430,8 @@ var utils = {}; * Lazily required module dependencies */ -utils.define = __webpack_require__(729); -utils.isObject = __webpack_require__(747); +utils.define = __webpack_require__(798); +utils.isObject = __webpack_require__(749); utils.isString = function(val) { @@ -91724,7 +92446,7 @@ module.exports = utils; /***/ }), -/* 800 */ +/* 811 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -91733,14 +92455,14 @@ module.exports = utils; */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(801); + module.exports = __webpack_require__(812); } else { - module.exports = __webpack_require__(804); + module.exports = __webpack_require__(815); } /***/ }), -/* 801 */ +/* 812 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -91749,7 +92471,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(802); +exports = module.exports = __webpack_require__(813); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -91931,7 +92653,7 @@ function localstorage() { /***/ }), -/* 802 */ +/* 813 */ /***/ (function(module, exports, __webpack_require__) { @@ -91947,7 +92669,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(803); +exports.humanize = __webpack_require__(814); /** * The currently active debug mode names, and names to skip. @@ -92139,7 +92861,7 @@ function coerce(val) { /***/ }), -/* 803 */ +/* 814 */ /***/ (function(module, exports) { /** @@ -92297,7 +93019,7 @@ function plural(ms, n, name) { /***/ }), -/* 804 */ +/* 815 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -92313,7 +93035,7 @@ var util = __webpack_require__(29); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(802); +exports = module.exports = __webpack_require__(813); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -92492,7 +93214,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(805); + var net = __webpack_require__(816); stream = new net.Socket({ fd: fd, readable: false, @@ -92551,13 +93273,13 @@ exports.enable(load()); /***/ }), -/* 805 */ +/* 816 */ /***/ (function(module, exports) { module.exports = require("net"); /***/ }), -/* 806 */ +/* 817 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92567,9 +93289,9 @@ module.exports = require("net"); * Module dependencies */ -exports.extend = __webpack_require__(737); -exports.SourceMap = __webpack_require__(807); -exports.sourceMapResolve = __webpack_require__(818); +exports.extend = __webpack_require__(740); +exports.SourceMap = __webpack_require__(818); +exports.sourceMapResolve = __webpack_require__(829); /** * Convert backslash in the given string to forward slashes @@ -92612,7 +93334,7 @@ exports.last = function(arr, n) { /***/ }), -/* 807 */ +/* 818 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -92620,13 +93342,13 @@ exports.last = function(arr, n) { * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ -exports.SourceMapGenerator = __webpack_require__(808).SourceMapGenerator; -exports.SourceMapConsumer = __webpack_require__(814).SourceMapConsumer; -exports.SourceNode = __webpack_require__(817).SourceNode; +exports.SourceMapGenerator = __webpack_require__(819).SourceMapGenerator; +exports.SourceMapConsumer = __webpack_require__(825).SourceMapConsumer; +exports.SourceNode = __webpack_require__(828).SourceNode; /***/ }), -/* 808 */ +/* 819 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -92636,10 +93358,10 @@ exports.SourceNode = __webpack_require__(817).SourceNode; * http://opensource.org/licenses/BSD-3-Clause */ -var base64VLQ = __webpack_require__(809); -var util = __webpack_require__(811); -var ArraySet = __webpack_require__(812).ArraySet; -var MappingList = __webpack_require__(813).MappingList; +var base64VLQ = __webpack_require__(820); +var util = __webpack_require__(822); +var ArraySet = __webpack_require__(823).ArraySet; +var MappingList = __webpack_require__(824).MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -93048,7 +93770,7 @@ exports.SourceMapGenerator = SourceMapGenerator; /***/ }), -/* 809 */ +/* 820 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -93088,7 +93810,7 @@ exports.SourceMapGenerator = SourceMapGenerator; * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -var base64 = __webpack_require__(810); +var base64 = __webpack_require__(821); // A single base 64 digit can contain 6 bits of data. For the base 64 variable // length quantities we use in the source map spec, the first bit is the sign, @@ -93194,7 +93916,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { /***/ }), -/* 810 */ +/* 821 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -93267,7 +93989,7 @@ exports.decode = function (charCode) { /***/ }), -/* 811 */ +/* 822 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -93690,7 +94412,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate /***/ }), -/* 812 */ +/* 823 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -93700,7 +94422,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(811); +var util = __webpack_require__(822); var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; @@ -93817,7 +94539,7 @@ exports.ArraySet = ArraySet; /***/ }), -/* 813 */ +/* 824 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -93827,7 +94549,7 @@ exports.ArraySet = ArraySet; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(811); +var util = __webpack_require__(822); /** * Determine whether mappingB is after mappingA with respect to generated @@ -93902,7 +94624,7 @@ exports.MappingList = MappingList; /***/ }), -/* 814 */ +/* 825 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -93912,11 +94634,11 @@ exports.MappingList = MappingList; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(811); -var binarySearch = __webpack_require__(815); -var ArraySet = __webpack_require__(812).ArraySet; -var base64VLQ = __webpack_require__(809); -var quickSort = __webpack_require__(816).quickSort; +var util = __webpack_require__(822); +var binarySearch = __webpack_require__(826); +var ArraySet = __webpack_require__(823).ArraySet; +var base64VLQ = __webpack_require__(820); +var quickSort = __webpack_require__(827).quickSort; function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; @@ -94990,7 +95712,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; /***/ }), -/* 815 */ +/* 826 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -95107,7 +95829,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { /***/ }), -/* 816 */ +/* 827 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -95227,7 +95949,7 @@ exports.quickSort = function (ary, comparator) { /***/ }), -/* 817 */ +/* 828 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -95237,8 +95959,8 @@ exports.quickSort = function (ary, comparator) { * http://opensource.org/licenses/BSD-3-Clause */ -var SourceMapGenerator = __webpack_require__(808).SourceMapGenerator; -var util = __webpack_require__(811); +var SourceMapGenerator = __webpack_require__(819).SourceMapGenerator; +var util = __webpack_require__(822); // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other // operating systems these days (capturing the result). @@ -95646,17 +96368,17 @@ exports.SourceNode = SourceNode; /***/ }), -/* 818 */ +/* 829 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014, 2015, 2016, 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var sourceMappingURL = __webpack_require__(819) -var resolveUrl = __webpack_require__(820) -var decodeUriComponent = __webpack_require__(821) -var urix = __webpack_require__(823) -var atob = __webpack_require__(824) +var sourceMappingURL = __webpack_require__(830) +var resolveUrl = __webpack_require__(831) +var decodeUriComponent = __webpack_require__(832) +var urix = __webpack_require__(834) +var atob = __webpack_require__(835) @@ -95954,7 +96676,7 @@ module.exports = { /***/ }), -/* 819 */ +/* 830 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell @@ -96017,7 +96739,7 @@ void (function(root, factory) { /***/ }), -/* 820 */ +/* 831 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -96035,13 +96757,13 @@ module.exports = resolveUrl /***/ }), -/* 821 */ +/* 832 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var decodeUriComponent = __webpack_require__(822) +var decodeUriComponent = __webpack_require__(833) function customDecodeUriComponent(string) { // `decodeUriComponent` turns `+` into ` `, but that's not wanted. @@ -96052,7 +96774,7 @@ module.exports = customDecodeUriComponent /***/ }), -/* 822 */ +/* 833 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -96153,7 +96875,7 @@ module.exports = function (encodedURI) { /***/ }), -/* 823 */ +/* 834 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -96176,7 +96898,7 @@ module.exports = urix /***/ }), -/* 824 */ +/* 835 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -96190,7 +96912,7 @@ module.exports = atob.atob = atob; /***/ }), -/* 825 */ +/* 836 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -96198,8 +96920,8 @@ module.exports = atob.atob = atob; var fs = __webpack_require__(23); var path = __webpack_require__(16); -var define = __webpack_require__(729); -var utils = __webpack_require__(806); +var define = __webpack_require__(798); +var utils = __webpack_require__(817); /** * Expose `mixin()`. @@ -96342,19 +97064,19 @@ exports.comment = function(node) { /***/ }), -/* 826 */ +/* 837 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(798); +var use = __webpack_require__(809); var util = __webpack_require__(29); -var Cache = __webpack_require__(827); -var define = __webpack_require__(729); -var debug = __webpack_require__(800)('snapdragon:parser'); -var Position = __webpack_require__(828); -var utils = __webpack_require__(806); +var Cache = __webpack_require__(838); +var define = __webpack_require__(798); +var debug = __webpack_require__(811)('snapdragon:parser'); +var Position = __webpack_require__(839); +var utils = __webpack_require__(817); /** * Create a new `Parser` with the given `input` and `options`. @@ -96882,7 +97604,7 @@ module.exports = Parser; /***/ }), -/* 827 */ +/* 838 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -96989,13 +97711,13 @@ MapCache.prototype.del = function mapDelete(key) { /***/ }), -/* 828 */ +/* 839 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(729); +var define = __webpack_require__(798); /** * Store position for a node @@ -97010,16 +97732,16 @@ module.exports = function Position(start, parser) { /***/ }), -/* 829 */ +/* 840 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var safe = __webpack_require__(830); -var define = __webpack_require__(836); -var extend = __webpack_require__(837); -var not = __webpack_require__(839); +var safe = __webpack_require__(841); +var define = __webpack_require__(847); +var extend = __webpack_require__(848); +var not = __webpack_require__(850); var MAX_LENGTH = 1024 * 64; /** @@ -97172,10 +97894,10 @@ module.exports.makeRe = makeRe; /***/ }), -/* 830 */ +/* 841 */ /***/ (function(module, exports, __webpack_require__) { -var parse = __webpack_require__(831); +var parse = __webpack_require__(842); var types = parse.types; module.exports = function (re, opts) { @@ -97221,13 +97943,13 @@ function isRegExp (x) { /***/ }), -/* 831 */ +/* 842 */ /***/ (function(module, exports, __webpack_require__) { -var util = __webpack_require__(832); -var types = __webpack_require__(833); -var sets = __webpack_require__(834); -var positions = __webpack_require__(835); +var util = __webpack_require__(843); +var types = __webpack_require__(844); +var sets = __webpack_require__(845); +var positions = __webpack_require__(846); module.exports = function(regexpStr) { @@ -97509,11 +98231,11 @@ module.exports.types = types; /***/ }), -/* 832 */ +/* 843 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(833); -var sets = __webpack_require__(834); +var types = __webpack_require__(844); +var sets = __webpack_require__(845); // All of these are private and only used by randexp. @@ -97626,7 +98348,7 @@ exports.error = function(regexp, msg) { /***/ }), -/* 833 */ +/* 844 */ /***/ (function(module, exports) { module.exports = { @@ -97642,10 +98364,10 @@ module.exports = { /***/ }), -/* 834 */ +/* 845 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(833); +var types = __webpack_require__(844); var INTS = function() { return [{ type: types.RANGE , from: 48, to: 57 }]; @@ -97730,10 +98452,10 @@ exports.anyChar = function() { /***/ }), -/* 835 */ +/* 846 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(833); +var types = __webpack_require__(844); exports.wordBoundary = function() { return { type: types.POSITION, value: 'b' }; @@ -97753,7 +98475,7 @@ exports.end = function() { /***/ }), -/* 836 */ +/* 847 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -97766,8 +98488,8 @@ exports.end = function() { -var isobject = __webpack_require__(747); -var isDescriptor = __webpack_require__(759); +var isobject = __webpack_require__(749); +var isDescriptor = __webpack_require__(763); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -97798,14 +98520,14 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 837 */ +/* 848 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(838); -var assignSymbols = __webpack_require__(748); +var isExtendable = __webpack_require__(849); +var assignSymbols = __webpack_require__(750); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -97865,7 +98587,7 @@ function isEnum(obj, key) { /***/ }), -/* 838 */ +/* 849 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -97878,7 +98600,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(746); +var isPlainObject = __webpack_require__(748); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -97886,14 +98608,14 @@ module.exports = function isExtendable(val) { /***/ }), -/* 839 */ +/* 850 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(837); -var safe = __webpack_require__(830); +var extend = __webpack_require__(848); +var safe = __webpack_require__(841); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -97965,14 +98687,14 @@ module.exports = toRegex; /***/ }), -/* 840 */ +/* 851 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var nanomatch = __webpack_require__(841); -var extglob = __webpack_require__(856); +var nanomatch = __webpack_require__(852); +var extglob = __webpack_require__(868); module.exports = function(snapdragon) { var compilers = snapdragon.compiler.compilers; @@ -98049,7 +98771,7 @@ function escapeExtglobs(compiler) { /***/ }), -/* 841 */ +/* 852 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -98060,17 +98782,17 @@ function escapeExtglobs(compiler) { */ var util = __webpack_require__(29); -var toRegex = __webpack_require__(728); -var extend = __webpack_require__(842); +var toRegex = __webpack_require__(853); +var extend = __webpack_require__(854); /** * Local dependencies */ -var compilers = __webpack_require__(844); -var parsers = __webpack_require__(845); -var cache = __webpack_require__(848); -var utils = __webpack_require__(850); +var compilers = __webpack_require__(856); +var parsers = __webpack_require__(857); +var cache = __webpack_require__(860); +var utils = __webpack_require__(862); var MAX_LENGTH = 1024 * 64; /** @@ -98855,53 +99577,208 @@ function compose(patterns, options, matcher) { }); } -/** - * Memoize a generated regex or function. A unique key is generated - * from the `type` (usually method name), the `pattern`, and - * user-defined options. - */ +/** + * Memoize a generated regex or function. A unique key is generated + * from the `type` (usually method name), the `pattern`, and + * user-defined options. + */ + +function memoize(type, pattern, options, fn) { + var key = utils.createKey(type + '=' + pattern, options); + + if (options && options.cache === false) { + return fn(pattern, options); + } + + if (cache.has(type, key)) { + return cache.get(type, key); + } + + var val = fn(pattern, options); + cache.set(type, key, val); + return val; +} + +/** + * Expose compiler, parser and cache on `nanomatch` + */ + +nanomatch.compilers = compilers; +nanomatch.parsers = parsers; +nanomatch.cache = cache; + +/** + * Expose `nanomatch` + * @type {Function} + */ + +module.exports = nanomatch; + + +/***/ }), +/* 853 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var define = __webpack_require__(798); +var extend = __webpack_require__(740); +var not = __webpack_require__(739); +var MAX_LENGTH = 1024 * 64; + +/** + * Session cache + */ + +var cache = {}; + +/** + * Create a regular expression from the given `pattern` string. + * + * @param {String|RegExp} `pattern` Pattern can be a string or regular expression. + * @param {Object} `options` + * @return {RegExp} + * @api public + */ + +module.exports = function(patterns, options) { + if (!Array.isArray(patterns)) { + return makeRe(patterns, options); + } + return makeRe(patterns.join('|'), options); +}; + +/** + * Create a regular expression from the given `pattern` string. + * + * @param {String|RegExp} `pattern` Pattern can be a string or regular expression. + * @param {Object} `options` + * @return {RegExp} + * @api public + */ + +function makeRe(pattern, options) { + if (pattern instanceof RegExp) { + return pattern; + } + + if (typeof pattern !== 'string') { + throw new TypeError('expected a string'); + } + + if (pattern.length > MAX_LENGTH) { + throw new Error('expected pattern to be less than ' + MAX_LENGTH + ' characters'); + } + + var key = pattern; + // do this before shallow cloning options, it's a lot faster + if (!options || (options && options.cache !== false)) { + key = createKey(pattern, options); + + if (cache.hasOwnProperty(key)) { + return cache[key]; + } + } + + var opts = extend({}, options); + if (opts.contains === true) { + if (opts.negate === true) { + opts.strictNegate = false; + } else { + opts.strict = false; + } + } + + if (opts.strict === false) { + opts.strictOpen = false; + opts.strictClose = false; + } -function memoize(type, pattern, options, fn) { - var key = utils.createKey(type + '=' + pattern, options); + var open = opts.strictOpen !== false ? '^' : ''; + var close = opts.strictClose !== false ? '$' : ''; + var flags = opts.flags || ''; + var regex; - if (options && options.cache === false) { - return fn(pattern, options); + if (opts.nocase === true && !/i/.test(flags)) { + flags += 'i'; } - if (cache.has(type, key)) { - return cache.get(type, key); + try { + if (opts.negate || typeof opts.strictNegate === 'boolean') { + pattern = not.create(pattern, opts); + } + var str = open + '(?:' + pattern + ')' + close; + regex = new RegExp(str, flags); + } catch (err) { + if (opts.strictErrors === true) { + err.key = key; + err.pattern = pattern; + err.originalOptions = options; + err.createdOptions = opts; + throw err; + } + + try { + regex = new RegExp('^' + pattern.replace(/(\W)/g, '\\$1') + '$'); + } catch (err) { + regex = /.^/; //<= match nothing + } } - var val = fn(pattern, options); - cache.set(type, key, val); - return val; + if (opts.cache !== false) { + cacheRegex(regex, key, pattern, opts); + } + return regex; } /** - * Expose compiler, parser and cache on `nanomatch` + * Cache generated regex. This can result in dramatic speed improvements + * and simplify debugging by adding options and pattern to the regex. It can be + * disabled by passing setting `options.cache` to false. */ -nanomatch.compilers = compilers; -nanomatch.parsers = parsers; -nanomatch.cache = cache; +function cacheRegex(regex, key, pattern, options) { + define(regex, 'cached', true); + define(regex, 'pattern', pattern); + define(regex, 'options', options); + define(regex, 'key', key); + cache[key] = regex; +} /** - * Expose `nanomatch` - * @type {Function} + * Create the key to use for memoization. The key is generated + * by iterating over the options and concatenating key-value pairs + * to the pattern string. */ -module.exports = nanomatch; +function createKey(pattern, options) { + if (!options) return pattern; + var key = pattern; + for (var prop in options) { + if (options.hasOwnProperty(prop)) { + key += ';' + prop + '=' + String(options[prop]); + } + } + return key; +} + +/** + * Expose `makeRe` + */ + +module.exports.makeRe = makeRe; /***/ }), -/* 842 */ +/* 854 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(843); -var assignSymbols = __webpack_require__(748); +var isExtendable = __webpack_require__(855); +var assignSymbols = __webpack_require__(750); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -98961,7 +99838,7 @@ function isEnum(obj, key) { /***/ }), -/* 843 */ +/* 855 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -98974,7 +99851,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(746); +var isPlainObject = __webpack_require__(748); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -98982,7 +99859,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 844 */ +/* 856 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -99328,15 +100205,15 @@ module.exports = function(nanomatch, options) { /***/ }), -/* 845 */ +/* 857 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var regexNot = __webpack_require__(739); -var toRegex = __webpack_require__(728); -var isOdd = __webpack_require__(846); +var toRegex = __webpack_require__(853); +var isOdd = __webpack_require__(858); /** * Characters to use in negation regex (we want to "not" match @@ -99722,7 +100599,7 @@ module.exports.not = NOT_REGEX; /***/ }), -/* 846 */ +/* 858 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -99735,7 +100612,7 @@ module.exports.not = NOT_REGEX; -var isNumber = __webpack_require__(847); +var isNumber = __webpack_require__(859); module.exports = function isOdd(i) { if (!isNumber(i)) { @@ -99749,7 +100626,7 @@ module.exports = function isOdd(i) { /***/ }), -/* 847 */ +/* 859 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -99777,14 +100654,14 @@ module.exports = function isNumber(num) { /***/ }), -/* 848 */ +/* 860 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(849))(); +module.exports = new (__webpack_require__(861))(); /***/ }), -/* 849 */ +/* 861 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -99797,7 +100674,7 @@ module.exports = new (__webpack_require__(849))(); -var MapCache = __webpack_require__(827); +var MapCache = __webpack_require__(838); /** * Create a new `FragmentCache` with an optional object to use for `caches`. @@ -99919,7 +100796,7 @@ exports = module.exports = FragmentCache; /***/ }), -/* 850 */ +/* 862 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -99932,14 +100809,14 @@ var path = __webpack_require__(16); * Module dependencies */ -var isWindows = __webpack_require__(851)(); -var Snapdragon = __webpack_require__(767); -utils.define = __webpack_require__(852); -utils.diff = __webpack_require__(853); -utils.extend = __webpack_require__(842); -utils.pick = __webpack_require__(854); -utils.typeOf = __webpack_require__(855); -utils.unique = __webpack_require__(740); +var isWindows = __webpack_require__(863)(); +var Snapdragon = __webpack_require__(771); +utils.define = __webpack_require__(864); +utils.diff = __webpack_require__(865); +utils.extend = __webpack_require__(854); +utils.pick = __webpack_require__(866); +utils.typeOf = __webpack_require__(867); +utils.unique = __webpack_require__(742); /** * Returns true if the given value is effectively an empty string @@ -100305,7 +101182,7 @@ utils.unixify = function(options) { /***/ }), -/* 851 */ +/* 863 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! @@ -100333,7 +101210,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 852 */ +/* 864 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -100346,8 +101223,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ -var isobject = __webpack_require__(747); -var isDescriptor = __webpack_require__(759); +var isobject = __webpack_require__(749); +var isDescriptor = __webpack_require__(763); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -100378,7 +101255,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 853 */ +/* 865 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -100432,7 +101309,7 @@ function diffArray(one, two) { /***/ }), -/* 854 */ +/* 866 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -100445,7 +101322,7 @@ function diffArray(one, two) { -var isObject = __webpack_require__(747); +var isObject = __webpack_require__(749); module.exports = function pick(obj, keys) { if (!isObject(obj) && typeof obj !== 'function') { @@ -100474,7 +101351,7 @@ module.exports = function pick(obj, keys) { /***/ }), -/* 855 */ +/* 867 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -100609,7 +101486,7 @@ function isBuffer(val) { /***/ }), -/* 856 */ +/* 868 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -100619,18 +101496,18 @@ function isBuffer(val) { * Module dependencies */ -var extend = __webpack_require__(737); -var unique = __webpack_require__(740); -var toRegex = __webpack_require__(728); +var extend = __webpack_require__(740); +var unique = __webpack_require__(742); +var toRegex = __webpack_require__(853); /** * Local dependencies */ -var compilers = __webpack_require__(857); -var parsers = __webpack_require__(868); -var Extglob = __webpack_require__(871); -var utils = __webpack_require__(870); +var compilers = __webpack_require__(869); +var parsers = __webpack_require__(880); +var Extglob = __webpack_require__(883); +var utils = __webpack_require__(882); var MAX_LENGTH = 1024 * 64; /** @@ -100947,13 +101824,13 @@ module.exports = extglob; /***/ }), -/* 857 */ +/* 869 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(858); +var brackets = __webpack_require__(870); /** * Extglob compilers @@ -101123,7 +102000,7 @@ module.exports = function(extglob) { /***/ }), -/* 858 */ +/* 870 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -101133,17 +102010,17 @@ module.exports = function(extglob) { * Local dependencies */ -var compilers = __webpack_require__(859); -var parsers = __webpack_require__(861); +var compilers = __webpack_require__(871); +var parsers = __webpack_require__(873); /** * Module dependencies */ -var debug = __webpack_require__(863)('expand-brackets'); -var extend = __webpack_require__(737); -var Snapdragon = __webpack_require__(767); -var toRegex = __webpack_require__(728); +var debug = __webpack_require__(875)('expand-brackets'); +var extend = __webpack_require__(740); +var Snapdragon = __webpack_require__(771); +var toRegex = __webpack_require__(853); /** * Parses the given POSIX character class `pattern` and returns a @@ -101341,13 +102218,13 @@ module.exports = brackets; /***/ }), -/* 859 */ +/* 871 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var posix = __webpack_require__(860); +var posix = __webpack_require__(872); module.exports = function(brackets) { brackets.compiler @@ -101435,7 +102312,7 @@ module.exports = function(brackets) { /***/ }), -/* 860 */ +/* 872 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -101464,14 +102341,14 @@ module.exports = { /***/ }), -/* 861 */ +/* 873 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(862); -var define = __webpack_require__(729); +var utils = __webpack_require__(874); +var define = __webpack_require__(798); /** * Text regex @@ -101690,13 +102567,13 @@ module.exports.TEXT_REGEX = TEXT_REGEX; /***/ }), -/* 862 */ +/* 874 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var toRegex = __webpack_require__(728); +var toRegex = __webpack_require__(853); var regexNot = __webpack_require__(739); var cached; @@ -101731,7 +102608,7 @@ exports.createRegex = function(pattern, include) { /***/ }), -/* 863 */ +/* 875 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -101740,14 +102617,14 @@ exports.createRegex = function(pattern, include) { */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(864); + module.exports = __webpack_require__(876); } else { - module.exports = __webpack_require__(867); + module.exports = __webpack_require__(879); } /***/ }), -/* 864 */ +/* 876 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -101756,7 +102633,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(865); +exports = module.exports = __webpack_require__(877); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -101938,7 +102815,7 @@ function localstorage() { /***/ }), -/* 865 */ +/* 877 */ /***/ (function(module, exports, __webpack_require__) { @@ -101954,7 +102831,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(866); +exports.humanize = __webpack_require__(878); /** * The currently active debug mode names, and names to skip. @@ -102146,7 +103023,7 @@ function coerce(val) { /***/ }), -/* 866 */ +/* 878 */ /***/ (function(module, exports) { /** @@ -102304,7 +103181,7 @@ function plural(ms, n, name) { /***/ }), -/* 867 */ +/* 879 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -102320,7 +103197,7 @@ var util = __webpack_require__(29); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(865); +exports = module.exports = __webpack_require__(877); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -102499,7 +103376,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(805); + var net = __webpack_require__(816); stream = new net.Socket({ fd: fd, readable: false, @@ -102558,15 +103435,15 @@ exports.enable(load()); /***/ }), -/* 868 */ +/* 880 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(858); -var define = __webpack_require__(869); -var utils = __webpack_require__(870); +var brackets = __webpack_require__(870); +var define = __webpack_require__(881); +var utils = __webpack_require__(882); /** * Characters to use in text regex (we want to "not" match @@ -102721,7 +103598,7 @@ module.exports = parsers; /***/ }), -/* 869 */ +/* 881 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102734,7 +103611,7 @@ module.exports = parsers; -var isDescriptor = __webpack_require__(759); +var isDescriptor = __webpack_require__(763); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -102759,14 +103636,14 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 870 */ +/* 882 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var regex = __webpack_require__(739); -var Cache = __webpack_require__(849); +var Cache = __webpack_require__(861); /** * Utils @@ -102835,7 +103712,7 @@ utils.createRegex = function(str) { /***/ }), -/* 871 */ +/* 883 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102845,16 +103722,16 @@ utils.createRegex = function(str) { * Module dependencies */ -var Snapdragon = __webpack_require__(767); -var define = __webpack_require__(869); -var extend = __webpack_require__(737); +var Snapdragon = __webpack_require__(771); +var define = __webpack_require__(881); +var extend = __webpack_require__(740); /** * Local dependencies */ -var compilers = __webpack_require__(857); -var parsers = __webpack_require__(868); +var compilers = __webpack_require__(869); +var parsers = __webpack_require__(880); /** * Customize Snapdragon parser and renderer @@ -102920,16 +103797,16 @@ module.exports = Extglob; /***/ }), -/* 872 */ +/* 884 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extglob = __webpack_require__(856); -var nanomatch = __webpack_require__(841); +var extglob = __webpack_require__(868); +var nanomatch = __webpack_require__(852); var regexNot = __webpack_require__(739); -var toRegex = __webpack_require__(829); +var toRegex = __webpack_require__(840); var not; /** @@ -103010,14 +103887,14 @@ function textRegex(pattern) { /***/ }), -/* 873 */ +/* 885 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(849))(); +module.exports = new (__webpack_require__(861))(); /***/ }), -/* 874 */ +/* 886 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103030,13 +103907,13 @@ var path = __webpack_require__(16); * Module dependencies */ -var Snapdragon = __webpack_require__(767); -utils.define = __webpack_require__(836); -utils.diff = __webpack_require__(853); -utils.extend = __webpack_require__(837); -utils.pick = __webpack_require__(854); -utils.typeOf = __webpack_require__(875); -utils.unique = __webpack_require__(740); +var Snapdragon = __webpack_require__(771); +utils.define = __webpack_require__(847); +utils.diff = __webpack_require__(865); +utils.extend = __webpack_require__(848); +utils.pick = __webpack_require__(866); +utils.typeOf = __webpack_require__(887); +utils.unique = __webpack_require__(742); /** * Returns true if the platform is windows, or `path.sep` is `\\`. @@ -103333,7 +104210,7 @@ utils.unixify = function(options) { /***/ }), -/* 875 */ +/* 887 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -103468,7 +104345,7 @@ function isBuffer(val) { /***/ }), -/* 876 */ +/* 888 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103487,9 +104364,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(877); -var reader_1 = __webpack_require__(890); -var fs_stream_1 = __webpack_require__(894); +var readdir = __webpack_require__(889); +var reader_1 = __webpack_require__(902); +var fs_stream_1 = __webpack_require__(906); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -103550,15 +104427,15 @@ exports.default = ReaderAsync; /***/ }), -/* 877 */ +/* 889 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(878); -const readdirAsync = __webpack_require__(886); -const readdirStream = __webpack_require__(889); +const readdirSync = __webpack_require__(890); +const readdirAsync = __webpack_require__(898); +const readdirStream = __webpack_require__(901); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -103642,7 +104519,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 878 */ +/* 890 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103650,11 +104527,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(879); +const DirectoryReader = __webpack_require__(891); let syncFacade = { - fs: __webpack_require__(884), - forEach: __webpack_require__(885), + fs: __webpack_require__(896), + forEach: __webpack_require__(897), sync: true }; @@ -103683,7 +104560,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 879 */ +/* 891 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103692,9 +104569,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(27).Readable; const EventEmitter = __webpack_require__(379).EventEmitter; const path = __webpack_require__(16); -const normalizeOptions = __webpack_require__(880); -const stat = __webpack_require__(882); -const call = __webpack_require__(883); +const normalizeOptions = __webpack_require__(892); +const stat = __webpack_require__(894); +const call = __webpack_require__(895); /** * Asynchronously reads the contents of a directory and streams the results @@ -104070,14 +104947,14 @@ module.exports = DirectoryReader; /***/ }), -/* 880 */ +/* 892 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const globToRegExp = __webpack_require__(881); +const globToRegExp = __webpack_require__(893); module.exports = normalizeOptions; @@ -104254,7 +105131,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 881 */ +/* 893 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -104391,13 +105268,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 882 */ +/* 894 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(883); +const call = __webpack_require__(895); module.exports = stat; @@ -104472,7 +105349,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 883 */ +/* 895 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104533,14 +105410,14 @@ function callOnce (fn) { /***/ }), -/* 884 */ +/* 896 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const call = __webpack_require__(883); +const call = __webpack_require__(895); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -104604,7 +105481,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 885 */ +/* 897 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104633,7 +105510,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 886 */ +/* 898 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104641,12 +105518,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(887); -const DirectoryReader = __webpack_require__(879); +const maybe = __webpack_require__(899); +const DirectoryReader = __webpack_require__(891); let asyncFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(888), + forEach: __webpack_require__(900), async: true }; @@ -104688,7 +105565,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 887 */ +/* 899 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104715,7 +105592,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 888 */ +/* 900 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104751,7 +105628,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 889 */ +/* 901 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104759,11 +105636,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(879); +const DirectoryReader = __webpack_require__(891); let streamFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(888), + forEach: __webpack_require__(900), async: true }; @@ -104783,16 +105660,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 890 */ +/* 902 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var deep_1 = __webpack_require__(891); -var entry_1 = __webpack_require__(893); -var pathUtil = __webpack_require__(892); +var deep_1 = __webpack_require__(903); +var entry_1 = __webpack_require__(905); +var pathUtil = __webpack_require__(904); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -104858,13 +105735,13 @@ exports.default = Reader; /***/ }), -/* 891 */ +/* 903 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(892); +var pathUtils = __webpack_require__(904); var patternUtils = __webpack_require__(721); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { @@ -104948,7 +105825,7 @@ exports.default = DeepFilter; /***/ }), -/* 892 */ +/* 904 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104979,13 +105856,13 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 893 */ +/* 905 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(892); +var pathUtils = __webpack_require__(904); var patternUtils = __webpack_require__(721); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { @@ -105071,7 +105948,7 @@ exports.default = EntryFilter; /***/ }), -/* 894 */ +/* 906 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105091,8 +105968,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(27); -var fsStat = __webpack_require__(895); -var fs_1 = __webpack_require__(899); +var fsStat = __webpack_require__(907); +var fs_1 = __webpack_require__(911); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -105142,14 +106019,14 @@ exports.default = FileSystemStream; /***/ }), -/* 895 */ +/* 907 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(896); -const statProvider = __webpack_require__(898); +const optionsManager = __webpack_require__(908); +const statProvider = __webpack_require__(910); /** * Asynchronous API. */ @@ -105180,13 +106057,13 @@ exports.statSync = statSync; /***/ }), -/* 896 */ +/* 908 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(897); +const fsAdapter = __webpack_require__(909); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -105199,7 +106076,7 @@ exports.prepare = prepare; /***/ }), -/* 897 */ +/* 909 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105222,7 +106099,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 898 */ +/* 910 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105274,7 +106151,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 899 */ +/* 911 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105305,7 +106182,7 @@ exports.default = FileSystem; /***/ }), -/* 900 */ +/* 912 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105325,9 +106202,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(27); -var readdir = __webpack_require__(877); -var reader_1 = __webpack_require__(890); -var fs_stream_1 = __webpack_require__(894); +var readdir = __webpack_require__(889); +var reader_1 = __webpack_require__(902); +var fs_stream_1 = __webpack_require__(906); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -105395,7 +106272,7 @@ exports.default = ReaderStream; /***/ }), -/* 901 */ +/* 913 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105414,9 +106291,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(877); -var reader_1 = __webpack_require__(890); -var fs_sync_1 = __webpack_require__(902); +var readdir = __webpack_require__(889); +var reader_1 = __webpack_require__(902); +var fs_sync_1 = __webpack_require__(914); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -105476,7 +106353,7 @@ exports.default = ReaderSync; /***/ }), -/* 902 */ +/* 914 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105495,8 +106372,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(895); -var fs_1 = __webpack_require__(899); +var fsStat = __webpack_require__(907); +var fs_1 = __webpack_require__(911); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -105542,7 +106419,7 @@ exports.default = FileSystemSync; /***/ }), -/* 903 */ +/* 915 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105558,7 +106435,7 @@ exports.flatten = flatten; /***/ }), -/* 904 */ +/* 916 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105579,13 +106456,13 @@ exports.merge = merge; /***/ }), -/* 905 */ +/* 917 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const pathType = __webpack_require__(906); +const pathType = __webpack_require__(918); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -105651,13 +106528,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 906 */ +/* 918 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const pify = __webpack_require__(907); +const pify = __webpack_require__(919); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -105700,7 +106577,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 907 */ +/* 919 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105791,7 +106668,7 @@ module.exports = (obj, opts) => { /***/ }), -/* 908 */ +/* 920 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105799,9 +106676,9 @@ module.exports = (obj, opts) => { const fs = __webpack_require__(23); const path = __webpack_require__(16); const fastGlob = __webpack_require__(717); -const gitIgnore = __webpack_require__(909); -const pify = __webpack_require__(910); -const slash = __webpack_require__(911); +const gitIgnore = __webpack_require__(921); +const pify = __webpack_require__(922); +const slash = __webpack_require__(923); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -105899,7 +106776,7 @@ module.exports.sync = options => { /***/ }), -/* 909 */ +/* 921 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -106368,7 +107245,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 910 */ +/* 922 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -106443,7 +107320,7 @@ module.exports = (input, options) => { /***/ }), -/* 911 */ +/* 923 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -106461,17 +107338,17 @@ module.exports = input => { /***/ }), -/* 912 */ +/* 924 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); const {constants: fsConstants} = __webpack_require__(23); -const pEvent = __webpack_require__(913); -const CpFileError = __webpack_require__(916); -const fs = __webpack_require__(920); -const ProgressEmitter = __webpack_require__(923); +const pEvent = __webpack_require__(925); +const CpFileError = __webpack_require__(928); +const fs = __webpack_require__(932); +const ProgressEmitter = __webpack_require__(935); const cpFileAsync = async (source, destination, options, progressEmitter) => { let readError; @@ -106585,12 +107462,12 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 913 */ +/* 925 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pTimeout = __webpack_require__(914); +const pTimeout = __webpack_require__(926); const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator'; @@ -106881,12 +107758,12 @@ module.exports.iterator = (emitter, event, options) => { /***/ }), -/* 914 */ +/* 926 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pFinally = __webpack_require__(915); +const pFinally = __webpack_require__(927); class TimeoutError extends Error { constructor(message) { @@ -106932,7 +107809,7 @@ module.exports.TimeoutError = TimeoutError; /***/ }), -/* 915 */ +/* 927 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -106954,12 +107831,12 @@ module.exports = (promise, onFinally) => { /***/ }), -/* 916 */ +/* 928 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(917); +const NestedError = __webpack_require__(929); class CpFileError extends NestedError { constructor(message, nested) { @@ -106973,10 +107850,10 @@ module.exports = CpFileError; /***/ }), -/* 917 */ +/* 929 */ /***/ (function(module, exports, __webpack_require__) { -var inherits = __webpack_require__(918); +var inherits = __webpack_require__(930); var NestedError = function (message, nested) { this.nested = nested; @@ -107027,7 +107904,7 @@ module.exports = NestedError; /***/ }), -/* 918 */ +/* 930 */ /***/ (function(module, exports, __webpack_require__) { try { @@ -107035,12 +107912,12 @@ try { if (typeof util.inherits !== 'function') throw ''; module.exports = util.inherits; } catch (e) { - module.exports = __webpack_require__(919); + module.exports = __webpack_require__(931); } /***/ }), -/* 919 */ +/* 931 */ /***/ (function(module, exports) { if (typeof Object.create === 'function') { @@ -107069,16 +107946,16 @@ if (typeof Object.create === 'function') { /***/ }), -/* 920 */ +/* 932 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(29); const fs = __webpack_require__(22); -const makeDir = __webpack_require__(921); -const pEvent = __webpack_require__(913); -const CpFileError = __webpack_require__(916); +const makeDir = __webpack_require__(933); +const pEvent = __webpack_require__(925); +const CpFileError = __webpack_require__(928); const stat = promisify(fs.stat); const lstat = promisify(fs.lstat); @@ -107175,7 +108052,7 @@ exports.copyFileSync = (source, destination, flags) => { /***/ }), -/* 921 */ +/* 933 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -107183,7 +108060,7 @@ exports.copyFileSync = (source, destination, flags) => { const fs = __webpack_require__(23); const path = __webpack_require__(16); const {promisify} = __webpack_require__(29); -const semver = __webpack_require__(922); +const semver = __webpack_require__(934); const defaults = { mode: 0o777 & (~process.umask()), @@ -107332,7 +108209,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 922 */ +/* 934 */ /***/ (function(module, exports) { exports = module.exports = SemVer @@ -108934,7 +109811,7 @@ function coerce (version, options) { /***/ }), -/* 923 */ +/* 935 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -108975,7 +109852,7 @@ module.exports = ProgressEmitter; /***/ }), -/* 924 */ +/* 936 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -109021,12 +109898,12 @@ exports.default = module.exports; /***/ }), -/* 925 */ +/* 937 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(926); +const NestedError = __webpack_require__(938); class CpyError extends NestedError { constructor(message, nested) { @@ -109040,7 +109917,7 @@ module.exports = CpyError; /***/ }), -/* 926 */ +/* 938 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(29).inherits; @@ -109096,7 +109973,7 @@ module.exports = NestedError; /***/ }), -/* 927 */ +/* 939 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index 46f55da87575d..a60e2b0449d95 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -9,7 +9,7 @@ "kbn:watch": "node scripts/build --watch" }, "dependencies": { - "@elastic/charts": "18.4.1", + "@elastic/charts": "18.4.2", "@elastic/eui": "22.3.0", "@kbn/i18n": "1.0.0", "abortcontroller-polyfill": "^1.4.0", diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index d26cadecb184a..7958a4f8134d3 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -216,17 +216,7 @@ export class SavedObjectsClient { }), }); - return createRequest - .then(resp => this.createSavedObject(resp)) - .catch((error: object) => { - if (isAutoCreateIndexError(error)) { - window.location.assign( - this.http.basePath.prepend('/app/kibana#/error/action.auto_create_index') - ); - } - - throw error; - }); + return createRequest.then(resp => this.createSavedObject(resp)); }; /** @@ -468,9 +458,3 @@ const renameKeys = , U extends Record ...{ [keysMap[key] || key]: obj[key] }, }; }, {}); - -const isAutoCreateIndexError = (error: any) => { - return ( - error?.res?.status === 503 && error?.body?.attributes?.code === 'ES_AUTO_CREATE_INDEX_ERROR' - ); -}; diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts index 64270c677ff20..3ec478e3ca28d 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts @@ -149,13 +149,13 @@ describe('DocumentMigrator', () => { expect(_.get(migratedDoc, 'attributes.name')).toBe('Mike'); }); - it('migrates meta properties', () => { + it('migrates root properties', () => { const migrator = new DocumentMigrator({ ...testOpts(), typeRegistry: createRegistry({ name: 'acl', migrations: { - '2.3.5': setAttr('acl', 'admins-only,sucka!'), + '2.3.5': setAttr('acl', 'admins-only, sucka!'), }, }), }); @@ -165,13 +165,13 @@ describe('DocumentMigrator', () => { attributes: { name: 'Tyler' }, acl: 'anyone', migrationVersion: {}, - }); + } as SavedObjectUnsanitizedDoc); expect(actual).toEqual({ id: 'me', type: 'user', attributes: { name: 'Tyler' }, migrationVersion: { acl: '2.3.5' }, - acl: 'admins-only,sucka!', + acl: 'admins-only, sucka!', }); }); @@ -241,7 +241,7 @@ describe('DocumentMigrator', () => { type: 'user', attributes: { name: 'Tyler' }, bbb: 'Shazm', - }); + } as SavedObjectUnsanitizedDoc); expect(actual).toEqual({ id: 'me', type: 'user', @@ -405,7 +405,7 @@ describe('DocumentMigrator', () => { attributes: { name: 'Callie' }, dawg: 'Yo', migrationVersion: {}, - }); + } as SavedObjectUnsanitizedDoc); expect(actual).toEqual({ id: 'smelly', type: 'foo', diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.ts b/src/core/server/saved_objects/migrations/core/document_migrator.ts index 0284f513a361c..4ddb2b070d3ac 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.ts @@ -279,7 +279,7 @@ function props(doc: SavedObjectUnsanitizedDoc) { */ function propVersion(doc: SavedObjectUnsanitizedDoc | ActiveMigrations, prop: string) { return ( - (doc[prop] && doc[prop].latestVersion) || + ((doc as any)[prop] && (doc as any)[prop].latestVersion) || (doc.migrationVersion && (doc as any).migrationVersion[prop]) ); } diff --git a/src/core/server/saved_objects/serialization/types.ts b/src/core/server/saved_objects/serialization/types.ts index 7ea61f67e9496..a33e16895078e 100644 --- a/src/core/server/saved_objects/serialization/types.ts +++ b/src/core/server/saved_objects/serialization/types.ts @@ -45,10 +45,7 @@ export interface SavedObjectsRawDocSource { } /** - * A saved object type definition that allows for miscellaneous, unknown - * properties, as current discussions around security, ACLs, etc indicate - * that future props are likely to be added. Migrations support this - * scenario out of the box. + * Saved Object base document */ interface SavedObjectDoc { attributes: any; @@ -59,8 +56,6 @@ interface SavedObjectDoc { migrationVersion?: SavedObjectsMigrationVersion; version?: string; updated_at?: string; - - [rootProp: string]: any; } interface Referencable { @@ -68,14 +63,18 @@ interface Referencable { } /** - * We want to have two types, one that guarantees a "references" attribute - * will exist and one that allows it to be null. Since we're not migrating - * all the saved objects to have a "references" array, we need to support - * the scenarios where it may be missing (ex migrations). + * Describes Saved Object documents from Kibana < 7.0.0 which don't have a + * `references` root property defined. This type should only be used in + * migrations. * * @public */ export type SavedObjectUnsanitizedDoc = SavedObjectDoc & Partial; -/** @public */ +/** + * Describes Saved Object documents that have passed through the migration + * framework and are guaranteed to have a `references` root property. + * + * @public + */ export type SavedObjectSanitizedDoc = SavedObjectDoc & Referencable; diff --git a/src/core/server/saved_objects/service/lib/errors.test.ts b/src/core/server/saved_objects/service/lib/errors.test.ts index 4a43835d795d1..324d19e279212 100644 --- a/src/core/server/saved_objects/service/lib/errors.test.ts +++ b/src/core/server/saved_objects/service/lib/errors.test.ts @@ -403,43 +403,4 @@ describe('savedObjectsClient/errorTypes', () => { }); }); }); - - describe('EsAutoCreateIndex error', () => { - describe('createEsAutoCreateIndexError', () => { - it('does not take an error argument', () => { - const error = new Error(); - // @ts-ignore - expect(SavedObjectsErrorHelpers.createEsAutoCreateIndexError(error)).not.toBe(error); - }); - - it('returns a new Error', () => { - expect(SavedObjectsErrorHelpers.createEsAutoCreateIndexError()).toBeInstanceOf(Error); - }); - - it('makes errors identifiable as EsAutoCreateIndex errors', () => { - expect( - SavedObjectsErrorHelpers.isEsAutoCreateIndexError( - SavedObjectsErrorHelpers.createEsAutoCreateIndexError() - ) - ).toBe(true); - }); - - it('returns a boom error', () => { - const error = SavedObjectsErrorHelpers.createEsAutoCreateIndexError(); - expect(error).toHaveProperty('isBoom', true); - }); - - describe('error.output', () => { - it('uses "Automatic index creation failed" message', () => { - const error = SavedObjectsErrorHelpers.createEsAutoCreateIndexError(); - expect(error.output.payload).toHaveProperty('message', 'Automatic index creation failed'); - }); - - it('sets statusCode to 503', () => { - const error = SavedObjectsErrorHelpers.createEsAutoCreateIndexError(); - expect(error.output).toHaveProperty('statusCode', 503); - }); - }); - }); - }); }); diff --git a/src/core/server/saved_objects/service/lib/errors.ts b/src/core/server/saved_objects/service/lib/errors.ts index 478c6b6d26d53..9614d692741e0 100644 --- a/src/core/server/saved_objects/service/lib/errors.ts +++ b/src/core/server/saved_objects/service/lib/errors.ts @@ -37,8 +37,6 @@ const CODE_CONFLICT = 'SavedObjectsClient/conflict'; const CODE_ES_CANNOT_EXECUTE_SCRIPT = 'SavedObjectsClient/esCannotExecuteScript'; // 503 - Es Unavailable const CODE_ES_UNAVAILABLE = 'SavedObjectsClient/esUnavailable'; -// 503 - Unable to automatically create index because of action.auto_create_index setting -const CODE_ES_AUTO_CREATE_INDEX_ERROR = 'SavedObjectsClient/autoCreateIndex'; // 500 - General Error const CODE_GENERAL_ERROR = 'SavedObjectsClient/generalError'; @@ -180,18 +178,6 @@ export class SavedObjectsErrorHelpers { return isSavedObjectsClientError(error) && error[code] === CODE_ES_UNAVAILABLE; } - public static createEsAutoCreateIndexError() { - const error = Boom.serverUnavailable('Automatic index creation failed'); - error.output.payload.attributes = error.output.payload.attributes || {}; - error.output.payload.attributes.code = 'ES_AUTO_CREATE_INDEX_ERROR'; - - return decorate(error, CODE_ES_AUTO_CREATE_INDEX_ERROR, 503); - } - - public static isEsAutoCreateIndexError(error: Error | DecoratedError) { - return isSavedObjectsClientError(error) && error[code] === CODE_ES_AUTO_CREATE_INDEX_ERROR; - } - public static decorateGeneralError(error: Error, reason?: string) { return decorate(error, CODE_GENERAL_ERROR, 500, reason); } diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 5f17c11792763..bc8ad2cdb0058 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -239,40 +239,31 @@ export class SavedObjectsRepository { } } - try { - const migrated = this._migrator.migrateDocument({ - id, - type, - ...(savedObjectNamespace && { namespace: savedObjectNamespace }), - ...(savedObjectNamespaces && { namespaces: savedObjectNamespaces }), - attributes, - migrationVersion, - updated_at: time, - ...(Array.isArray(references) && { references }), - }); - - const raw = this._serializer.savedObjectToRaw(migrated as SavedObjectSanitizedDoc); + const migrated = this._migrator.migrateDocument({ + id, + type, + ...(savedObjectNamespace && { namespace: savedObjectNamespace }), + ...(savedObjectNamespaces && { namespaces: savedObjectNamespaces }), + attributes, + migrationVersion, + updated_at: time, + ...(Array.isArray(references) && { references }), + }); - const method = id && overwrite ? 'index' : 'create'; - const response = await this._writeToCluster(method, { - id: raw._id, - index: this.getIndexForType(type), - refresh, - body: raw._source, - }); + const raw = this._serializer.savedObjectToRaw(migrated as SavedObjectSanitizedDoc); - return this._rawToSavedObject({ - ...raw, - ...response, - }); - } catch (error) { - if (SavedObjectsErrorHelpers.isNotFoundError(error)) { - // See "503s from missing index" above - throw SavedObjectsErrorHelpers.createEsAutoCreateIndexError(); - } + const method = id && overwrite ? 'index' : 'create'; + const response = await this._writeToCluster(method, { + id: raw._id, + index: this.getIndexForType(type), + refresh, + body: raw._source, + }); - throw error; - } + return this._rawToSavedObject({ + ...raw, + ...response, + }); } /** diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index b50c6dc9a1abf..43b7663491711 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -156,15 +156,6 @@ export type MutatingOperationRefreshSetting = boolean | 'wait_for'; * takes special care to ensure that 404 errors are generic and don't distinguish * between index missing or document missing. * - * ### 503s from missing index - * - * Unlike 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 {@link SavedObjectsClient} * See {@link SavedObjectsErrorHelpers} * diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 6369720ada2c3..a36e746f6d940 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1707,7 +1707,7 @@ export interface SavedObjectsAddToNamespacesOptions extends SavedObjectsBaseOpti // Warning: (ae-forgotten-export) The symbol "SavedObjectDoc" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "Referencable" needs to be exported by the entry point index.d.ts // -// @public (undocumented) +// @public export type SavedObjectSanitizedDoc = SavedObjectDoc & Referencable; // @public (undocumented) @@ -1875,8 +1875,6 @@ export class SavedObjectsErrorHelpers { // (undocumented) static createConflictError(type: string, id: string): DecoratedError; // (undocumented) - static createEsAutoCreateIndexError(): DecoratedError; - // (undocumented) static createGenericNotFoundError(type?: string | null, id?: string | null): DecoratedError; // (undocumented) static createInvalidVersionError(versionInput?: string): DecoratedError; @@ -1903,8 +1901,6 @@ export class SavedObjectsErrorHelpers { // (undocumented) static isConflictError(error: Error | DecoratedError): boolean; // (undocumented) - static isEsAutoCreateIndexError(error: Error | DecoratedError): boolean; - // (undocumented) static isEsCannotExecuteScriptError(error: Error | DecoratedError): boolean; // (undocumented) static isEsUnavailableError(error: Error | DecoratedError): boolean; diff --git a/src/dev/run_check_published_api_changes.ts b/src/dev/run_check_published_api_changes.ts index ba3cd1280f34b..6d5fa04a93951 100644 --- a/src/dev/run_check_published_api_changes.ts +++ b/src/dev/run_check_published_api_changes.ts @@ -250,7 +250,7 @@ async function run( Options: --accept {dim Accepts all changes by updating the API Review files and documentation} --docs {dim Updates the Core API documentation} - --only {dim RegExp that folder names must match, folders: [${folders.join(', ')}]} + --filter {dim RegExp that folder names must match, folders: [${folders.join(', ')}]} --help {dim Show this message} `) ); diff --git a/src/es_archiver/actions/rebuild_all.ts b/src/es_archiver/actions/rebuild_all.ts index 1467a1d0430b7..f35b2ca49c666 100644 --- a/src/es_archiver/actions/rebuild_all.ts +++ b/src/es_archiver/actions/rebuild_all.ts @@ -18,7 +18,7 @@ */ import { resolve, dirname, relative } from 'path'; -import { stat, rename, createReadStream, createWriteStream } from 'fs'; +import { stat, Stats, rename, createReadStream, createWriteStream } from 'fs'; import { Readable, Writable } from 'stream'; import { fromNode } from 'bluebird'; import { ToolingLog } from '@kbn/dev-utils'; @@ -33,7 +33,7 @@ import { } from '../lib'; async function isDirectory(path: string): Promise { - const stats = await fromNode(cb => stat(path, cb)); + const stats: Stats = await fromNode(cb => stat(path, cb)); return stats.isDirectory(); } diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 8465d71e1e998..48d86e3628e49 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -118,22 +118,6 @@ export default function(kibana) { }, ], - savedObjectsManagement: { - url: { - defaultSearchField: 'url', - isImportableAndExportable: true, - getTitle(obj) { - return `/goto/${encodeURIComponent(obj.id)}`; - }, - }, - }, - - savedObjectSchemas: { - 'kql-telemetry': { - isNamespaceAgnostic: true, - }, - }, - injectDefaultVars(server, options) { const mapConfig = server.config().get('map'); const tilemap = mapConfig.tilemap; @@ -158,61 +142,6 @@ export default function(kibana) { uiSettingDefaults: getUiSettingDefaults(), }, - uiCapabilities: async function() { - return { - discover: { - show: true, - createShortUrl: true, - save: true, - saveQuery: true, - }, - visualize: { - show: true, - createShortUrl: true, - delete: true, - save: true, - saveQuery: true, - }, - dashboard: { - createNew: true, - show: true, - showWriteControls: true, - saveQuery: true, - }, - catalogue: { - discover: true, - dashboard: true, - visualize: true, - console: true, - advanced_settings: true, - index_patterns: true, - }, - advancedSettings: { - show: true, - save: true, - }, - indexPatterns: { - save: true, - }, - savedObjectsManagement: { - delete: true, - edit: true, - read: true, - }, - management: { - /* - * Management settings correspond to management section/link ids, and should not be changed - * without also updating those definitions. - */ - kibana: { - settings: true, - index_patterns: true, - objects: true, - }, - }, - }; - }, - preInit: async function(server) { try { // Create the data directory (recursively, if the a parent dir doesn't exist). diff --git a/src/legacy/core_plugins/kibana/mappings.json b/src/legacy/core_plugins/kibana/mappings.json index febdf2cc3d649..e2cbd584dbe1f 100644 --- a/src/legacy/core_plugins/kibana/mappings.json +++ b/src/legacy/core_plugins/kibana/mappings.json @@ -1,41 +1,9 @@ { - "url": { - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "type": "text", - "fields": { - "keyword": { - "type": "keyword", - "ignore_above": 2048 - } - } - } - } - }, "server": { "properties": { "uuid": { "type": "keyword" } } - }, - "kql-telemetry": { - "properties": { - "optInCount": { - "type": "long" - }, - "optOutCount": { - "type": "long" - } - } } } diff --git a/src/legacy/ui/public/_index.scss b/src/legacy/ui/public/_index.scss index b36e62297cc23..f10718ba58c2c 100644 --- a/src/legacy/ui/public/_index.scss +++ b/src/legacy/ui/public/_index.scss @@ -10,7 +10,6 @@ @import './accessibility/index'; @import './directives/index'; -@import './error_auto_create_index/index'; @import './error_url_overflow/index'; @import './exit_full_screen/index'; @import './field_editor/index'; diff --git a/src/legacy/ui/public/error_auto_create_index/_error_auto_create_index.scss b/src/legacy/ui/public/error_auto_create_index/_error_auto_create_index.scss deleted file mode 100644 index ad31aabfc66cd..0000000000000 --- a/src/legacy/ui/public/error_auto_create_index/_error_auto_create_index.scss +++ /dev/null @@ -1,3 +0,0 @@ -.kbnError--auto-create-index { - padding: $euiSizeL; -} diff --git a/src/legacy/ui/public/error_auto_create_index/_index.scss b/src/legacy/ui/public/error_auto_create_index/_index.scss deleted file mode 100644 index 42e672ab322dc..0000000000000 --- a/src/legacy/ui/public/error_auto_create_index/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './error_auto_create_index' diff --git a/src/legacy/ui/public/error_auto_create_index/error_auto_create_index.html b/src/legacy/ui/public/error_auto_create_index/error_auto_create_index.html deleted file mode 100644 index 2af31dda6c345..0000000000000 --- a/src/legacy/ui/public/error_auto_create_index/error_auto_create_index.html +++ /dev/null @@ -1,69 +0,0 @@ -
-

- - -

- -

- -

- -

-
    -
  1. -
  2. -
  3. -
- -
-
- - -
- -
-
-
-
-
diff --git a/src/legacy/ui/public/error_auto_create_index/error_auto_create_index.test.js b/src/legacy/ui/public/error_auto_create_index/error_auto_create_index.test.js deleted file mode 100644 index a8f6318090b1d..0000000000000 --- a/src/legacy/ui/public/error_auto_create_index/error_auto_create_index.test.js +++ /dev/null @@ -1,95 +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. - */ - -// @ts-ignore -import './error_auto_create_index.test.mocks'; -import fetchMock from 'fetch-mock/es5/client'; -import { kfetch } from '../kfetch'; - -import { isAutoCreateIndexError } from './error_auto_create_index'; - -describe('isAutoCreateIndexError correctly handles KFetchError thrown by kfetch', () => { - describe('404', () => { - beforeEach(() => { - fetchMock.post({ - matcher: '*', - response: { - status: 404, - }, - }); - }); - afterEach(() => fetchMock.restore()); - - test('should return false', async () => { - expect.assertions(1); - try { - await kfetch({ method: 'POST', pathname: '/my/path' }); - } catch (kfetchError) { - expect(isAutoCreateIndexError(kfetchError)).toBe(false); - } - }); - }); - - describe('503 error that is not ES_AUTO_CREATE_INDEX_ERROR', () => { - beforeEach(() => { - fetchMock.post({ - matcher: '*', - response: { - status: 503, - }, - }); - }); - afterEach(() => fetchMock.restore()); - - test('should return false', async () => { - expect.assertions(1); - try { - await kfetch({ method: 'POST', pathname: '/my/path' }); - } catch (kfetchError) { - expect(isAutoCreateIndexError(kfetchError)).toBe(false); - } - }); - }); - - describe('503 error that is ES_AUTO_CREATE_INDEX_ERROR', () => { - beforeEach(() => { - fetchMock.post({ - matcher: '*', - response: { - body: { - attributes: { - code: 'ES_AUTO_CREATE_INDEX_ERROR', - }, - }, - status: 503, - }, - }); - }); - afterEach(() => fetchMock.restore()); - - test('should return true', async () => { - expect.assertions(1); - try { - await kfetch({ method: 'POST', pathname: '/my/path' }); - } catch (kfetchError) { - expect(isAutoCreateIndexError(kfetchError)).toBe(true); - } - }); - }); -}); diff --git a/src/plugins/advanced_settings/kibana.json b/src/plugins/advanced_settings/kibana.json index cac9a6daa8df8..e6ca6e797ba45 100644 --- a/src/plugins/advanced_settings/kibana.json +++ b/src/plugins/advanced_settings/kibana.json @@ -1,7 +1,7 @@ { "id": "advancedSettings", "version": "kibana", - "server": false, + "server": true, "ui": true, "requiredPlugins": ["management"] } diff --git a/src/legacy/ui/public/error_auto_create_index/error_auto_create_index.test.mocks.js b/src/plugins/advanced_settings/server/capabilities_provider.ts similarity index 84% rename from src/legacy/ui/public/error_auto_create_index/error_auto_create_index.test.mocks.js rename to src/plugins/advanced_settings/server/capabilities_provider.ts index 1ac30b85c5a85..083d5f3ffced4 100644 --- a/src/legacy/ui/public/error_auto_create_index/error_auto_create_index.test.mocks.js +++ b/src/plugins/advanced_settings/server/capabilities_provider.ts @@ -17,6 +17,9 @@ * under the License. */ -import { setup } from '../../../../test_utils/public/http_test_setup'; - -jest.doMock('ui/new_platform', () => ({ npSetup: { core: setup() } })); +export const capabilitiesProvider = () => ({ + advancedSettings: { + show: true, + save: true, + }, +}); diff --git a/src/plugins/advanced_settings/server/index.ts b/src/plugins/advanced_settings/server/index.ts new file mode 100644 index 0000000000000..ffcf7cd49a8c3 --- /dev/null +++ b/src/plugins/advanced_settings/server/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 { PluginInitializerContext } from 'kibana/server'; +import { AdvancedSettingsServerPlugin } from './plugin'; + +export const plugin = (initContext: PluginInitializerContext) => + new AdvancedSettingsServerPlugin(initContext); diff --git a/src/plugins/advanced_settings/server/plugin.ts b/src/plugins/advanced_settings/server/plugin.ts new file mode 100644 index 0000000000000..4d7bd34259819 --- /dev/null +++ b/src/plugins/advanced_settings/server/plugin.ts @@ -0,0 +1,44 @@ +/* + * 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, CoreSetup, CoreStart, Plugin, Logger } from 'kibana/server'; +import { capabilitiesProvider } from './capabilities_provider'; + +export class AdvancedSettingsServerPlugin implements Plugin { + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(core: CoreSetup) { + this.logger.debug('advancedSettings: Setup'); + + core.capabilities.registerProvider(capabilitiesProvider); + + return {}; + } + + public start(core: CoreStart) { + this.logger.debug('advancedSettings: Started'); + return {}; + } + + public stop() {} +} diff --git a/src/plugins/dashboard/server/capabilities_provider.ts b/src/plugins/dashboard/server/capabilities_provider.ts new file mode 100644 index 0000000000000..0bb53d60c38a5 --- /dev/null +++ b/src/plugins/dashboard/server/capabilities_provider.ts @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const capabilitiesProvider = () => ({ + dashboard: { + createNew: true, + show: true, + showWriteControls: true, + saveQuery: true, + }, +}); diff --git a/src/plugins/dashboard/server/plugin.ts b/src/plugins/dashboard/server/plugin.ts index 5d1b66002e749..ba7bdeeda0133 100644 --- a/src/plugins/dashboard/server/plugin.ts +++ b/src/plugins/dashboard/server/plugin.ts @@ -26,6 +26,7 @@ import { } from '../../../core/server'; import { dashboardSavedObjectType } from './saved_objects'; +import { capabilitiesProvider } from './capabilities_provider'; import { DashboardPluginSetup, DashboardPluginStart } from './types'; @@ -40,6 +41,7 @@ export class DashboardPlugin implements Plugin ({ + indexPatterns: { + save: true, + }, +}); diff --git a/src/plugins/data/server/index_patterns/index_patterns_service.ts b/src/plugins/data/server/index_patterns/index_patterns_service.ts index 58e8fbae9f9e2..3e31f8e8a566d 100644 --- a/src/plugins/data/server/index_patterns/index_patterns_service.ts +++ b/src/plugins/data/server/index_patterns/index_patterns_service.ts @@ -20,10 +20,12 @@ import { CoreSetup, Plugin } from 'kibana/server'; import { registerRoutes } from './routes'; import { indexPatternSavedObjectType } from '../saved_objects'; +import { capabilitiesProvider } from './capabilities_provider'; export class IndexPatternsService implements Plugin { public setup(core: CoreSetup) { core.savedObjects.registerType(indexPatternSavedObjectType); + core.capabilities.registerProvider(capabilitiesProvider); registerRoutes(core.http); } diff --git a/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts b/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts index e45ad796bd9d4..3dfaa9c6d0a98 100644 --- a/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts +++ b/src/plugins/data/server/kql_telemetry/kql_telemetry_service.ts @@ -22,14 +22,16 @@ import { CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server'; import { registerKqlTelemetryRoute } from './route'; import { UsageCollectionSetup } from '../../../usage_collection/server'; import { makeKQLUsageCollector } from './usage_collector'; +import { kqlTelemetry } from '../saved_objects'; export class KqlTelemetryService implements Plugin { constructor(private initializerContext: PluginInitializerContext) {} public setup( - { http, getStartServices }: CoreSetup, + { http, getStartServices, savedObjects }: CoreSetup, { usageCollection }: { usageCollection?: UsageCollectionSetup } ) { + savedObjects.registerType(kqlTelemetry); registerKqlTelemetryRoute( http.createRouter(), getStartServices, diff --git a/src/plugins/data/server/saved_objects/index.ts b/src/plugins/data/server/saved_objects/index.ts index 5d980974474de..4326200141179 100644 --- a/src/plugins/data/server/saved_objects/index.ts +++ b/src/plugins/data/server/saved_objects/index.ts @@ -20,3 +20,4 @@ export { searchSavedObjectType } from './search'; export { querySavedObjectType } from './query'; export { indexPatternSavedObjectType } from './index_patterns'; +export { kqlTelemetry } from './kql_telementry'; diff --git a/src/plugins/data/server/saved_objects/kql_telementry.ts b/src/plugins/data/server/saved_objects/kql_telementry.ts new file mode 100644 index 0000000000000..6539d5eacfde2 --- /dev/null +++ b/src/plugins/data/server/saved_objects/kql_telementry.ts @@ -0,0 +1,35 @@ +/* + * 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 { SavedObjectsType } from 'kibana/server'; + +export const kqlTelemetry: SavedObjectsType = { + name: 'kql-telemetry', + namespaceType: 'agnostic', + hidden: false, + mappings: { + properties: { + optInCount: { + type: 'long', + }, + optOutCount: { + type: 'long', + }, + }, + }, +}; diff --git a/src/plugins/discover/kibana.json b/src/plugins/discover/kibana.json index 91d6358d44c18..2d41293f26369 100644 --- a/src/plugins/discover/kibana.json +++ b/src/plugins/discover/kibana.json @@ -1,6 +1,6 @@ { "id": "discover", "version": "kibana", - "server": false, + "server": true, "ui": true } diff --git a/src/plugins/discover/server/capabilities_provider.ts b/src/plugins/discover/server/capabilities_provider.ts new file mode 100644 index 0000000000000..2e03631894aee --- /dev/null +++ b/src/plugins/discover/server/capabilities_provider.ts @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const capabilitiesProvider = () => ({ + discover: { + show: true, + createShortUrl: true, + save: true, + saveQuery: true, + }, +}); diff --git a/src/plugins/discover/server/index.ts b/src/plugins/discover/server/index.ts new file mode 100644 index 0000000000000..15a948c56148e --- /dev/null +++ b/src/plugins/discover/server/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 { PluginInitializerContext } from 'kibana/server'; +import { DiscoverServerPlugin } from './plugin'; + +export const plugin = (initContext: PluginInitializerContext) => + new DiscoverServerPlugin(initContext); diff --git a/src/plugins/discover/server/plugin.ts b/src/plugins/discover/server/plugin.ts new file mode 100644 index 0000000000000..04502f5fc14e6 --- /dev/null +++ b/src/plugins/discover/server/plugin.ts @@ -0,0 +1,44 @@ +/* + * 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, CoreSetup, CoreStart, Plugin, Logger } from 'kibana/server'; +import { capabilitiesProvider } from './capabilities_provider'; + +export class DiscoverServerPlugin implements Plugin { + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(core: CoreSetup) { + this.logger.debug('discover: Setup'); + + core.capabilities.registerProvider(capabilitiesProvider); + + return {}; + } + + public start(core: CoreStart) { + this.logger.debug('discover: Started'); + return {}; + } + + public stop() {} +} diff --git a/src/plugins/home/server/capabilities_provider.ts b/src/plugins/home/server/capabilities_provider.ts new file mode 100644 index 0000000000000..1c662e2301e16 --- /dev/null +++ b/src/plugins/home/server/capabilities_provider.ts @@ -0,0 +1,29 @@ +/* + * 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 capabilitiesProvider = () => ({ + catalogue: { + discover: true, + dashboard: true, + visualize: true, + console: true, + advanced_settings: true, + index_patterns: true, + }, +}); diff --git a/src/plugins/home/server/plugin.ts b/src/plugins/home/server/plugin.ts index 1f530bc58f0b9..1050c19362ae1 100644 --- a/src/plugins/home/server/plugin.ts +++ b/src/plugins/home/server/plugin.ts @@ -26,6 +26,7 @@ import { SampleDataRegistryStart, } from './services'; import { UsageCollectionSetup } from '../../usage_collection/server'; +import { capabilitiesProvider } from './capabilities_provider'; import { sampleDataTelemetry } from './saved_objects'; interface HomeServerPluginSetupDependencies { @@ -38,6 +39,7 @@ export class HomeServerPlugin implements Plugin [ - { - text: i18n.translate('common.ui.errorAutoCreateIndex.breadcrumbs.errorText', { - defaultMessage: 'Error', - }), +export const capabilitiesProvider = () => ({ + management: { + /* + * Management settings correspond to management section/link ids, and should not be changed + * without also updating those definitions. + */ + kibana: { + settings: true, + index_patterns: true, + objects: true, }, - ], + }, }); - -export function isAutoCreateIndexError(error: object) { - return ( - get(error, 'res.status') === 503 && - get(error, 'body.attributes.code') === 'ES_AUTO_CREATE_INDEX_ERROR' - ); -} - -export function showAutoCreateIndexErrorPage() { - window.location.hash = '/error/action.auto_create_index'; -} diff --git a/src/plugins/management/server/index.ts b/src/plugins/management/server/index.ts new file mode 100644 index 0000000000000..afc7adf8832e1 --- /dev/null +++ b/src/plugins/management/server/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 { PluginInitializerContext } from 'kibana/server'; +import { ManagementServerPlugin } from './plugin'; + +export const plugin = (initContext: PluginInitializerContext) => + new ManagementServerPlugin(initContext); diff --git a/src/plugins/management/server/plugin.ts b/src/plugins/management/server/plugin.ts new file mode 100644 index 0000000000000..f8fda7da9b95a --- /dev/null +++ b/src/plugins/management/server/plugin.ts @@ -0,0 +1,44 @@ +/* + * 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, CoreSetup, CoreStart, Plugin, Logger } from 'kibana/server'; +import { capabilitiesProvider } from './capabilities_provider'; + +export class ManagementServerPlugin implements Plugin { + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(core: CoreSetup) { + this.logger.debug('management: Setup'); + + core.capabilities.registerProvider(capabilitiesProvider); + + return {}; + } + + public start(core: CoreStart) { + this.logger.debug('management: Started'); + return {}; + } + + public stop() {} +} diff --git a/src/plugins/saved_objects_management/server/capabilities_provider.ts b/src/plugins/saved_objects_management/server/capabilities_provider.ts new file mode 100644 index 0000000000000..bd621de4a6195 --- /dev/null +++ b/src/plugins/saved_objects_management/server/capabilities_provider.ts @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const capabilitiesProvider = () => ({ + savedObjectsManagement: { + delete: true, + edit: true, + read: true, + }, +}); diff --git a/src/plugins/saved_objects_management/server/plugin.ts b/src/plugins/saved_objects_management/server/plugin.ts index b72644b500967..4e39b08f5c62c 100644 --- a/src/plugins/saved_objects_management/server/plugin.ts +++ b/src/plugins/saved_objects_management/server/plugin.ts @@ -23,6 +23,7 @@ import { CoreSetup, CoreStart, Logger, Plugin, PluginInitializerContext } from ' import { SavedObjectsManagementPluginSetup, SavedObjectsManagementPluginStart } from './types'; import { SavedObjectsManagement } from './services'; import { registerRoutes } from './routes'; +import { capabilitiesProvider } from './capabilities_provider'; export class SavedObjectsManagementPlugin implements Plugin { @@ -33,13 +34,15 @@ export class SavedObjectsManagementPlugin this.logger = this.context.logger.get(); } - public async setup({ http }: CoreSetup) { + public async setup({ http, capabilities }: CoreSetup) { this.logger.debug('Setting up SavedObjectsManagement plugin'); registerRoutes({ http, managementServicePromise: this.managementService$.pipe(first()).toPromise(), }); + capabilities.registerProvider(capabilitiesProvider); + return {}; } diff --git a/src/plugins/share/server/plugin.ts b/src/plugins/share/server/plugin.ts index bcb681a50652a..0d9f183d13404 100644 --- a/src/plugins/share/server/plugin.ts +++ b/src/plugins/share/server/plugin.ts @@ -19,12 +19,14 @@ import { CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server'; import { createRoutes } from './routes/create_routes'; +import { url } from './saved_objects'; export class SharePlugin implements Plugin { constructor(private readonly initializerContext: PluginInitializerContext) {} public async setup(core: CoreSetup) { createRoutes(core, this.initializerContext.logger.get()); + core.savedObjects.registerType(url); } public start() { diff --git a/src/plugins/share/server/saved_objects/index.ts b/src/plugins/share/server/saved_objects/index.ts new file mode 100644 index 0000000000000..956f031d2f1a5 --- /dev/null +++ b/src/plugins/share/server/saved_objects/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { url } from './url'; diff --git a/src/plugins/share/server/saved_objects/url.ts b/src/plugins/share/server/saved_objects/url.ts new file mode 100644 index 0000000000000..c76c21993a13f --- /dev/null +++ b/src/plugins/share/server/saved_objects/url.ts @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { SavedObjectsType } from 'kibana/server'; + +export const url: SavedObjectsType = { + name: 'url', + namespaceType: 'single', + hidden: false, + management: { + icon: 'link', + defaultSearchField: 'url', + importableAndExportable: true, + getTitle(obj) { + return `/goto/${encodeURIComponent(obj.id)}`; + }, + }, + mappings: { + properties: { + accessCount: { + type: 'long', + }, + accessDate: { + type: 'date', + }, + createDate: { + type: 'date', + }, + url: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + }, + }, + }, + }, + }, +}; diff --git a/src/plugins/visualize/kibana.json b/src/plugins/visualize/kibana.json index a7afa0697a5eb..d536d2f246a6b 100644 --- a/src/plugins/visualize/kibana.json +++ b/src/plugins/visualize/kibana.json @@ -1,7 +1,7 @@ { "id": "visualize", "version": "kibana", - "server": false, + "server": true, "ui": true, "requiredPlugins": [ "data", diff --git a/src/plugins/visualize/server/capabilities_provider.ts b/src/plugins/visualize/server/capabilities_provider.ts new file mode 100644 index 0000000000000..3b09b28f53c77 --- /dev/null +++ b/src/plugins/visualize/server/capabilities_provider.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. + */ + +export const capabilitiesProvider = () => ({ + visualize: { + show: true, + createShortUrl: true, + delete: true, + save: true, + saveQuery: true, + }, +}); diff --git a/src/plugins/visualize/server/index.ts b/src/plugins/visualize/server/index.ts new file mode 100644 index 0000000000000..5cebef71d8d22 --- /dev/null +++ b/src/plugins/visualize/server/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 { PluginInitializerContext } from 'kibana/server'; +import { VisualizeServerPlugin } from './plugin'; + +export const plugin = (initContext: PluginInitializerContext) => + new VisualizeServerPlugin(initContext); diff --git a/src/plugins/visualize/server/plugin.ts b/src/plugins/visualize/server/plugin.ts new file mode 100644 index 0000000000000..7cc57c25f3229 --- /dev/null +++ b/src/plugins/visualize/server/plugin.ts @@ -0,0 +1,44 @@ +/* + * 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, CoreSetup, CoreStart, Plugin, Logger } from 'kibana/server'; +import { capabilitiesProvider } from './capabilities_provider'; + +export class VisualizeServerPlugin implements Plugin { + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(core: CoreSetup) { + this.logger.debug('visualize: Setup'); + + core.capabilities.registerProvider(capabilitiesProvider); + + return {}; + } + + public start(core: CoreStart) { + this.logger.debug('visualize: Started'); + return {}; + } + + public stop() {} +} diff --git a/test/functional/apps/console/_console.ts b/test/functional/apps/console/_console.ts index 456752c0cd6eb..47b8a4a2e9f70 100644 --- a/test/functional/apps/console/_console.ts +++ b/test/functional/apps/console/_console.ts @@ -40,7 +40,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'console']); describe('console app', function describeIndexTests() { - this.tags('smoke'); + this.tags('includeFirefox'); before(async () => { log.debug('navigateTo console'); await PageObjects.common.navigateToApp('console'); diff --git a/test/functional/apps/context/_discover_navigation.js b/test/functional/apps/context/_discover_navigation.js index a56a85546bbcd..9a0130d39bc2f 100644 --- a/test/functional/apps/context/_discover_navigation.js +++ b/test/functional/apps/context/_discover_navigation.js @@ -33,7 +33,6 @@ export default function({ getService, getPageObjects }) { // FLAKY: https://github.com/elastic/kibana/issues/53308 describe.skip('context link in discover', function contextSize() { - this.tags('smoke'); before(async function() { await PageObjects.common.navigateToApp('discover'); await PageObjects.timePicker.setDefaultAbsoluteRange(); diff --git a/test/functional/apps/dashboard/dashboard_filtering.js b/test/functional/apps/dashboard/dashboard_filtering.js index f388993dcaf7d..8846a753f3794 100644 --- a/test/functional/apps/dashboard/dashboard_filtering.js +++ b/test/functional/apps/dashboard/dashboard_filtering.js @@ -38,7 +38,7 @@ export default function({ getService, getPageObjects }) { const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'visualize', 'timePicker']); describe('dashboard filtering', function() { - this.tags('smoke'); + this.tags('includeFirefox'); before(async () => { await esArchiver.load('dashboard/current/kibana'); diff --git a/test/functional/apps/dashboard/dashboard_save.js b/test/functional/apps/dashboard/dashboard_save.js index 2ea1389b89ad4..7ffe951faa398 100644 --- a/test/functional/apps/dashboard/dashboard_save.js +++ b/test/functional/apps/dashboard/dashboard_save.js @@ -24,7 +24,7 @@ export default function({ getPageObjects, getService }) { const listingTable = getService('listingTable'); describe('dashboard save', function describeIndexTests() { - this.tags('smoke'); + this.tags('includeFirefox'); const dashboardName = 'Dashboard Save Test'; const dashboardNameEnterKey = 'Dashboard Save Test with Enter Key'; diff --git a/test/functional/apps/dashboard/panel_controls.js b/test/functional/apps/dashboard/panel_controls.js index 6e24b9f3570a3..279adb22a1cfa 100644 --- a/test/functional/apps/dashboard/panel_controls.js +++ b/test/functional/apps/dashboard/panel_controls.js @@ -43,8 +43,6 @@ export default function({ getService, getPageObjects }) { const dashboardName = 'Dashboard Panel Controls Test'; describe('dashboard panel controls', function viewEditModeTests() { - this.tags('smoke'); - before(async function() { await PageObjects.dashboard.initTests(); await PageObjects.dashboard.preserveCrossAppState(); diff --git a/test/functional/apps/dashboard/time_zones.js b/test/functional/apps/dashboard/time_zones.js index b7698a7d6ac4b..564eb790eb8d1 100644 --- a/test/functional/apps/dashboard/time_zones.js +++ b/test/functional/apps/dashboard/time_zones.js @@ -27,7 +27,7 @@ export default function({ getService, getPageObjects }) { const PageObjects = getPageObjects(['dashboard', 'timePicker', 'settings', 'common']); describe('dashboard time zones', function() { - this.tags('smoke'); + this.tags('includeFirefox'); before(async () => { await esArchiver.load('dashboard/current/kibana'); diff --git a/test/functional/apps/discover/_doc_navigation.js b/test/functional/apps/discover/_doc_navigation.js index 08e0cb0b8d23a..ebce8dcafadb4 100644 --- a/test/functional/apps/discover/_doc_navigation.js +++ b/test/functional/apps/discover/_doc_navigation.js @@ -33,7 +33,6 @@ export default function({ getService, getPageObjects }) { // FLAKY: https://github.com/elastic/kibana/issues/62281 describe.skip('doc link in discover', function contextSize() { - this.tags('smoke'); before(async function() { await esArchiver.loadIfNeeded('logstash_functional'); await PageObjects.common.navigateToApp('discover'); diff --git a/test/functional/apps/discover/_field_data.js b/test/functional/apps/discover/_field_data.js index 62d42f3da5c84..ace9710665f10 100644 --- a/test/functional/apps/discover/_field_data.js +++ b/test/functional/apps/discover/_field_data.js @@ -28,7 +28,7 @@ export default function({ getService, getPageObjects }) { const PageObjects = getPageObjects(['common', 'header', 'discover', 'visualize', 'timePicker']); describe('discover tab', function describeIndexTests() { - this.tags('smoke'); + this.tags('includeFirefox'); before(async function() { await esArchiver.loadIfNeeded('logstash_functional'); await esArchiver.load('discover'); diff --git a/test/functional/apps/getting_started/index.js b/test/functional/apps/getting_started/index.js index b73c16e2583b5..41ee71a753712 100644 --- a/test/functional/apps/getting_started/index.js +++ b/test/functional/apps/getting_started/index.js @@ -21,7 +21,7 @@ export default function({ getService, loadTestFile }) { const browser = getService('browser'); describe('Getting Started ', function() { - this.tags(['ciGroup6', 'smoke']); + this.tags(['ciGroup6']); before(async function() { await browser.setWindowSize(1200, 800); diff --git a/test/functional/apps/home/_home.js b/test/functional/apps/home/_home.js index 6587c2f113b7f..3c56c22c046dd 100644 --- a/test/functional/apps/home/_home.js +++ b/test/functional/apps/home/_home.js @@ -25,7 +25,7 @@ export default function({ getService, getPageObjects }) { const PageObjects = getPageObjects(['common', 'home']); describe('Kibana takes you home', function describeIndexTests() { - this.tags('smoke'); + this.tags('includeFirefox'); it('clicking on kibana logo should take you to home page', async () => { await PageObjects.common.navigateToApp('settings'); diff --git a/test/functional/apps/home/_sample_data.ts b/test/functional/apps/home/_sample_data.ts index 5812b9b96e42a..f46b3390cbcf8 100644 --- a/test/functional/apps/home/_sample_data.ts +++ b/test/functional/apps/home/_sample_data.ts @@ -32,8 +32,6 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'header', 'home', 'dashboard', 'timePicker']); describe('sample data', function describeIndexTests() { - this.tags('smoke'); - before(async () => { await security.testUser.setRoles(['kibana_admin', 'kibana_sample_admin']); await PageObjects.common.navigateToUrl('home', 'tutorial_directory/sampleData'); diff --git a/test/functional/apps/management/_create_index_pattern_wizard.js b/test/functional/apps/management/_create_index_pattern_wizard.js index c8d11d8555d37..65d852b249ea0 100644 --- a/test/functional/apps/management/_create_index_pattern_wizard.js +++ b/test/functional/apps/management/_create_index_pattern_wizard.js @@ -25,8 +25,6 @@ export default function({ getService, getPageObjects }) { const PageObjects = getPageObjects(['settings', 'common']); describe('"Create Index Pattern" wizard', function() { - this.tags('smoke'); - before(async function() { // delete .kibana index and then wait for Kibana to re-create it await kibanaServer.uiSettings.replace({}); diff --git a/test/functional/apps/visualize/_experimental_vis.js b/test/functional/apps/visualize/_experimental_vis.js index c45a95abab86e..22d36d671cb68 100644 --- a/test/functional/apps/visualize/_experimental_vis.js +++ b/test/functional/apps/visualize/_experimental_vis.js @@ -24,8 +24,6 @@ export default ({ getService, getPageObjects }) => { const PageObjects = getPageObjects(['visualize']); describe('experimental visualizations in visualize app ', function() { - this.tags('smoke'); - describe('experimental visualizations', () => { beforeEach(async () => { log.debug('navigateToApp visualize'); diff --git a/test/functional/apps/visualize/_gauge_chart.js b/test/functional/apps/visualize/_gauge_chart.js index 7ebb4548f967b..d7a30f39250f3 100644 --- a/test/functional/apps/visualize/_gauge_chart.js +++ b/test/functional/apps/visualize/_gauge_chart.js @@ -28,8 +28,6 @@ export default function({ getService, getPageObjects }) { // FLAKY: https://github.com/elastic/kibana/issues/45089 describe('gauge chart', function indexPatternCreation() { - this.tags('smoke'); - async function initGaugeVis() { log.debug('navigateToApp visualize'); await PageObjects.visualize.navigateToNewVisualization(); diff --git a/test/functional/apps/visualize/_heatmap_chart.js b/test/functional/apps/visualize/_heatmap_chart.js index cbf7ab8df6831..842599fa1d0a1 100644 --- a/test/functional/apps/visualize/_heatmap_chart.js +++ b/test/functional/apps/visualize/_heatmap_chart.js @@ -25,7 +25,6 @@ export default function({ getService, getPageObjects }) { const PageObjects = getPageObjects(['visualize', 'visEditor', 'visChart', 'timePicker']); describe('heatmap chart', function indexPatternCreation() { - this.tags('smoke'); const vizName1 = 'Visualization HeatmapChart'; before(async function() { diff --git a/test/functional/apps/visualize/_inspector.js b/test/functional/apps/visualize/_inspector.js index d989f8e2539a0..256c0362226e5 100644 --- a/test/functional/apps/visualize/_inspector.js +++ b/test/functional/apps/visualize/_inspector.js @@ -24,7 +24,6 @@ export default function({ getService, getPageObjects }) { const PageObjects = getPageObjects(['visualize', 'visEditor', 'visChart', 'timePicker']); describe('inspector', function describeIndexTests() { - this.tags('smoke'); before(async function() { await PageObjects.visualize.navigateToNewVisualization(); await PageObjects.visualize.clickVerticalBarChart(); diff --git a/test/functional/apps/visualize/_tsvb_chart.ts b/test/functional/apps/visualize/_tsvb_chart.ts index 867db66ac81dc..27a06cc05b45c 100644 --- a/test/functional/apps/visualize/_tsvb_chart.ts +++ b/test/functional/apps/visualize/_tsvb_chart.ts @@ -29,7 +29,6 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['visualize', 'visualBuilder', 'timePicker', 'visChart']); describe('visual builder', function describeIndexTests() { - this.tags('smoke'); beforeEach(async () => { await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']); await PageObjects.visualize.navigateToNewVisualization(); diff --git a/test/functional/apps/visualize/input_control_vis/chained_controls.js b/test/functional/apps/visualize/input_control_vis/chained_controls.js index b56a37218aba5..b5231f3161377 100644 --- a/test/functional/apps/visualize/input_control_vis/chained_controls.js +++ b/test/functional/apps/visualize/input_control_vis/chained_controls.js @@ -27,7 +27,7 @@ export default function({ getService, getPageObjects }) { const comboBox = getService('comboBox'); describe('chained controls', function() { - this.tags('smoke'); + this.tags('includeFirefox'); before(async () => { await PageObjects.common.navigateToApp('visualize'); diff --git a/test/scripts/jenkins_firefox_smoke.sh b/test/scripts/jenkins_firefox_smoke.sh index 0129d4f1bce9f..2bba6e06d76d7 100755 --- a/test/scripts/jenkins_firefox_smoke.sh +++ b/test/scripts/jenkins_firefox_smoke.sh @@ -6,5 +6,5 @@ checks-reporter-with-killswitch "Firefox smoke test" \ node scripts/functional_tests \ --bail --debug \ --kibana-install-dir "$installDir" \ - --include-tag "smoke" \ + --include-tag "includeFirefox" \ --config test/functional/config.firefox.js; diff --git a/test/scripts/jenkins_xpack_firefox_smoke.sh b/test/scripts/jenkins_xpack_firefox_smoke.sh index 5fe8b41cc0010..fdaee76cafa9d 100755 --- a/test/scripts/jenkins_xpack_firefox_smoke.sh +++ b/test/scripts/jenkins_xpack_firefox_smoke.sh @@ -6,5 +6,5 @@ checks-reporter-with-killswitch "X-Pack firefox smoke test" \ node scripts/functional_tests \ --debug --bail \ --kibana-install-dir "$KIBANA_INSTALL_DIR" \ - --include-tag "smoke" \ + --include-tag "includeFirefox" \ --config test/functional/config.firefox.js; diff --git a/x-pack/index.js b/x-pack/index.js index 82a35f1710361..e65f6cf60928e 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -17,7 +17,6 @@ import { spaces } from './legacy/plugins/spaces'; import { canvas } from './legacy/plugins/canvas'; import { infra } from './legacy/plugins/infra'; import { taskManager } from './legacy/plugins/task_manager'; -import { rollup } from './legacy/plugins/rollup'; import { siem } from './legacy/plugins/siem'; import { remoteClusters } from './legacy/plugins/remote_clusters'; import { upgradeAssistant } from './legacy/plugins/upgrade_assistant'; @@ -43,7 +42,6 @@ module.exports = function(kibana) { indexManagement(kibana), infra(kibana), taskManager(kibana), - rollup(kibana), siem(kibana), remoteClusters(kibana), upgradeAssistant(kibana), diff --git a/x-pack/legacy/plugins/ingest_manager/index.ts b/x-pack/legacy/plugins/ingest_manager/index.ts index 47c6478f66471..df9923d9f11ec 100644 --- a/x-pack/legacy/plugins/ingest_manager/index.ts +++ b/x-pack/legacy/plugins/ingest_manager/index.ts @@ -4,43 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ import { resolve } from 'path'; -import { - savedObjectMappings, - OUTPUT_SAVED_OBJECT_TYPE, - AGENT_CONFIG_SAVED_OBJECT_TYPE, - DATASOURCE_SAVED_OBJECT_TYPE, - PACKAGES_SAVED_OBJECT_TYPE, -} from '../../../plugins/ingest_manager/server'; - -// TODO https://github.com/elastic/kibana/issues/46373 -// const INDEX_NAMES = { -// INGEST: '.kibana', -// }; export function ingestManager(kibana: any) { return new kibana.Plugin({ id: 'ingestManager', publicDir: resolve(__dirname, '../../../plugins/ingest_manager/public'), - uiExports: { - savedObjectSchemas: { - [AGENT_CONFIG_SAVED_OBJECT_TYPE]: { - isNamespaceAgnostic: true, - // indexPattern: INDEX_NAMES.INGEST, - }, - [OUTPUT_SAVED_OBJECT_TYPE]: { - isNamespaceAgnostic: true, - // indexPattern: INDEX_NAMES.INGEST, - }, - [DATASOURCE_SAVED_OBJECT_TYPE]: { - isNamespaceAgnostic: true, - // indexPattern: INDEX_NAMES.INGEST, - }, - [PACKAGES_SAVED_OBJECT_TYPE]: { - isNamespaceAgnostic: true, - // indexPattern: INDEX_NAMES.INGEST, - }, - }, - mappings: savedObjectMappings, - }, }); } diff --git a/x-pack/legacy/plugins/rollup/common/index.ts b/x-pack/legacy/plugins/rollup/common/index.ts deleted file mode 100644 index 526af055a3ef6..0000000000000 --- a/x-pack/legacy/plugins/rollup/common/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { LICENSE_TYPE_BASIC, LicenseType } from '../../../common/constants'; - -export const PLUGIN = { - ID: 'rollup', - MINIMUM_LICENSE_REQUIRED: LICENSE_TYPE_BASIC as LicenseType, - getI18nName: (i18n: any): string => { - return i18n.translate('xpack.rollupJobs.appName', { - defaultMessage: 'Rollup jobs', - }); - }, -}; - -export * from '../../../../plugins/rollup/common'; diff --git a/x-pack/legacy/plugins/rollup/index.ts b/x-pack/legacy/plugins/rollup/index.ts deleted file mode 100644 index f33ae7cfee0a2..0000000000000 --- a/x-pack/legacy/plugins/rollup/index.ts +++ /dev/null @@ -1,43 +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 { PluginInitializerContext } from 'src/core/server'; -import { RollupSetup } from '../../../plugins/rollup/server'; -import { PLUGIN } from './common'; -import { plugin } from './server'; - -export function rollup(kibana: any) { - return new kibana.Plugin({ - id: PLUGIN.ID, - configPrefix: 'xpack.rollup', - require: ['kibana', 'elasticsearch', 'xpack_main'], - init(server: any) { - const { core: coreSetup, plugins } = server.newPlatform.setup; - const { usageCollection, visTypeTimeseries, indexManagement } = plugins; - - const rollupSetup = (plugins.rollup as unknown) as RollupSetup; - - const initContext = ({ - config: rollupSetup.__legacy.config, - logger: rollupSetup.__legacy.logger, - } as unknown) as PluginInitializerContext; - - const rollupPluginInstance = plugin(initContext); - - rollupPluginInstance.setup(coreSetup, { - usageCollection, - visTypeTimeseries, - indexManagement, - __LEGACY: { - plugins: { - xpack_main: server.plugins.xpack_main, - rollup: server.plugins[PLUGIN.ID], - }, - }, - }); - }, - }); -} diff --git a/x-pack/legacy/plugins/rollup/kibana.json b/x-pack/legacy/plugins/rollup/kibana.json deleted file mode 100644 index 78458c9218be3..0000000000000 --- a/x-pack/legacy/plugins/rollup/kibana.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "id": "rollup", - "version": "kibana", - "requiredPlugins": [ - "home", - "index_management", - "visTypeTimeseries", - "indexPatternManagement" - ], - "optionalPlugins": [ - "usageCollection" - ], - "server": true, - "ui": false -} diff --git a/x-pack/legacy/plugins/rollup/server/lib/__tests__/fixtures/jobs.js b/x-pack/legacy/plugins/rollup/server/lib/__tests__/fixtures/jobs.js deleted file mode 100644 index eb16b211da3fd..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/__tests__/fixtures/jobs.js +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const jobs = [ - { - "job_id" : "foo1", - "rollup_index" : "foo_rollup", - "index_pattern" : "foo-*", - "fields" : { - "node" : [ - { - "agg" : "terms" - } - ], - "temperature" : [ - { - "agg" : "min" - }, - { - "agg" : "max" - }, - { - "agg" : "sum" - } - ], - "timestamp" : [ - { - "agg" : "date_histogram", - "time_zone" : "UTC", - "interval" : "1h", - "delay": "7d" - } - ], - "voltage" : [ - { - "agg" : "histogram", - "interval": 5 - }, - { - "agg" : "sum" - } - ] - } - }, - { - "job_id" : "foo2", - "rollup_index" : "foo_rollup", - "index_pattern" : "foo-*", - "fields" : { - "host" : [ - { - "agg" : "terms" - } - ], - "timestamp" : [ - { - "agg" : "date_histogram", - "time_zone" : "UTC", - "interval" : "1h", - "delay": "7d" - } - ], - "voltage" : [ - { - "agg" : "histogram", - "interval": 20 - } - ] - } - }, - { - "job_id" : "foo3", - "rollup_index" : "foo_rollup", - "index_pattern" : "foo-*", - "fields" : { - "timestamp" : [ - { - "agg" : "date_histogram", - "time_zone" : "PST", - "interval" : "1h", - "delay": "7d" - } - ], - "voltage" : [ - { - "agg" : "histogram", - "interval": 5 - }, - { - "agg" : "sum" - } - ] - } - } -]; diff --git a/x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/call_with_request_factory.ts b/x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/call_with_request_factory.ts deleted file mode 100644 index 883b3552a7c02..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/call_with_request_factory.ts +++ /dev/null @@ -1,28 +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 { ElasticsearchServiceSetup } from 'kibana/server'; -import { once } from 'lodash'; -import { elasticsearchJsPlugin } from '../../client/elasticsearch_rollup'; - -const callWithRequest = once((elasticsearchService: ElasticsearchServiceSetup) => { - const config = { plugins: [elasticsearchJsPlugin] }; - return elasticsearchService.createClient('rollup', config); -}); - -export const callWithRequestFactory = ( - elasticsearchService: ElasticsearchServiceSetup, - request: any -) => { - return (...args: any[]) => { - return ( - callWithRequest(elasticsearchService) - .asScoped(request) - // @ts-ignore - .callAsCurrentUser(...args) - ); - }; -}; diff --git a/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.test.js b/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.test.js deleted file mode 100644 index b6cea09e0ea3c..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.test.js +++ /dev/null @@ -1,62 +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 expect from '@kbn/expect'; -import { licensePreRoutingFactory } from '.'; -import { - LICENSE_STATUS_VALID, - LICENSE_STATUS_INVALID, -} from '../../../../../common/constants/license_status'; -import { kibanaResponseFactory } from '../../../../../../../src/core/server'; - -describe('licensePreRoutingFactory()', () => { - let mockServer; - let mockLicenseCheckResults; - - beforeEach(() => { - mockServer = { - plugins: { - xpack_main: { - info: { - feature: () => ({ - getLicenseCheckResults: () => mockLicenseCheckResults, - }), - }, - }, - }, - }; - }); - - describe('status is invalid', () => { - beforeEach(() => { - mockLicenseCheckResults = { - status: LICENSE_STATUS_INVALID, - }; - }); - - it('replies with 403', () => { - const routeWithLicenseCheck = licensePreRoutingFactory(mockServer, () => {}); - const stubRequest = {}; - const response = routeWithLicenseCheck({}, stubRequest, kibanaResponseFactory); - expect(response.status).to.be(403); - }); - }); - - describe('status is valid', () => { - beforeEach(() => { - mockLicenseCheckResults = { - status: LICENSE_STATUS_VALID, - }; - }); - - it('replies with nothing', () => { - const routeWithLicenseCheck = licensePreRoutingFactory(mockServer, () => null); - const stubRequest = {}; - const response = routeWithLicenseCheck({}, stubRequest, kibanaResponseFactory); - expect(response).to.be(null); - }); - }); -}); diff --git a/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.ts b/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.ts deleted file mode 100644 index 353510d96a00d..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/license_pre_routing_factory.ts +++ /dev/null @@ -1,43 +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 { - KibanaRequest, - KibanaResponseFactory, - RequestHandler, - RequestHandlerContext, -} from 'src/core/server'; -import { PLUGIN } from '../../../common'; -import { LICENSE_STATUS_VALID } from '../../../../../common/constants/license_status'; -import { ServerShim } from '../../types'; - -export const licensePreRoutingFactory = ( - server: ServerShim, - handler: RequestHandler -): RequestHandler => { - const xpackMainPlugin = server.plugins.xpack_main; - - // License checking and enable/disable logic - return function licensePreRouting( - ctx: RequestHandlerContext, - request: KibanaRequest, - response: KibanaResponseFactory - ) { - const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults(); - const { status } = licenseCheckResults; - - if (status !== LICENSE_STATUS_VALID) { - return response.customError({ - body: { - message: licenseCheckResults.messsage, - }, - statusCode: 403, - }); - } - - return handler(ctx, request, response); - }; -}; diff --git a/x-pack/legacy/plugins/rollup/server/plugin.ts b/x-pack/legacy/plugins/rollup/server/plugin.ts deleted file mode 100644 index 05c22b030fff9..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/plugin.ts +++ /dev/null @@ -1,95 +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 { CoreSetup, Plugin, PluginInitializerContext, Logger } from 'src/core/server'; -import { first } from 'rxjs/operators'; -import { i18n } from '@kbn/i18n'; - -import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { VisTypeTimeseriesSetup } from 'src/plugins/vis_type_timeseries/server'; -import { IndexManagementPluginSetup } from '../../../../plugins/index_management/server'; -import { registerLicenseChecker } from '../../../server/lib/register_license_checker'; -import { PLUGIN } from '../common'; -import { ServerShim, RouteDependencies } from './types'; - -import { - registerIndicesRoute, - registerFieldsForWildcardRoute, - registerSearchRoute, - registerJobsRoute, -} from './routes/api'; - -import { registerRollupUsageCollector } from './collectors'; - -import { rollupDataEnricher } from './rollup_data_enricher'; -import { registerRollupSearchStrategy } from './lib/search_strategies'; - -export class RollupsServerPlugin implements Plugin { - log: Logger; - - constructor(private readonly initializerContext: PluginInitializerContext) { - this.log = initializerContext.logger.get(); - } - - async setup( - { http, elasticsearch: elasticsearchService }: CoreSetup, - { - __LEGACY: serverShim, - usageCollection, - visTypeTimeseries, - indexManagement, - }: { - __LEGACY: ServerShim; - usageCollection?: UsageCollectionSetup; - visTypeTimeseries?: VisTypeTimeseriesSetup; - indexManagement?: IndexManagementPluginSetup; - } - ) { - const elasticsearch = await elasticsearchService.adminClient; - const router = http.createRouter(); - const routeDependencies: RouteDependencies = { - elasticsearch, - elasticsearchService, - router, - }; - - registerLicenseChecker( - serverShim as any, - PLUGIN.ID, - PLUGIN.getI18nName(i18n), - PLUGIN.MINIMUM_LICENSE_REQUIRED - ); - - registerIndicesRoute(routeDependencies, serverShim); - registerFieldsForWildcardRoute(routeDependencies, serverShim); - registerSearchRoute(routeDependencies, serverShim); - registerJobsRoute(routeDependencies, serverShim); - - if (usageCollection) { - this.initializerContext.config.legacy.globalConfig$ - .pipe(first()) - .toPromise() - .then(config => { - registerRollupUsageCollector(usageCollection, config.kibana.index); - }) - .catch(e => { - this.log.warn(`Registering Rollup collector failed: ${e}`); - }); - } - - if (indexManagement && indexManagement.indexDataEnricher) { - indexManagement.indexDataEnricher.add(rollupDataEnricher); - } - - if (visTypeTimeseries) { - const { addSearchStrategy } = visTypeTimeseries; - registerRollupSearchStrategy(routeDependencies, addSearchStrategy); - } - } - - start() {} - - stop() {} -} diff --git a/x-pack/legacy/plugins/rollup/server/routes/api/index.ts b/x-pack/legacy/plugins/rollup/server/routes/api/index.ts deleted file mode 100644 index 146c3e973f9ea..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/routes/api/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { registerIndicesRoute } from './indices'; -export { registerFieldsForWildcardRoute } from './index_patterns'; -export { registerSearchRoute } from './search'; -export { registerJobsRoute } from './jobs'; diff --git a/x-pack/legacy/plugins/rollup/server/routes/api/index_patterns.ts b/x-pack/legacy/plugins/rollup/server/routes/api/index_patterns.ts deleted file mode 100644 index 2516840bd9537..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/routes/api/index_patterns.ts +++ /dev/null @@ -1,131 +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 { schema } from '@kbn/config-schema'; -import { RequestHandler } from 'src/core/server'; - -import { indexBy } from 'lodash'; -import { IndexPatternsFetcher } from '../../shared_imports'; -import { RouteDependencies, ServerShim } from '../../types'; -import { callWithRequestFactory } from '../../lib/call_with_request_factory'; -import { isEsError } from '../../lib/is_es_error'; -import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; -import { getCapabilitiesForRollupIndices } from '../../lib/map_capabilities'; -import { mergeCapabilitiesWithFields, Field } from '../../lib/merge_capabilities_with_fields'; - -const parseMetaFields = (metaFields: string | string[]) => { - let parsedFields: string[] = []; - if (typeof metaFields === 'string') { - parsedFields = JSON.parse(metaFields); - } else { - parsedFields = metaFields; - } - return parsedFields; -}; - -const getFieldsForWildcardRequest = async (context: any, request: any, response: any) => { - const { callAsCurrentUser } = context.core.elasticsearch.dataClient; - const indexPatterns = new IndexPatternsFetcher(callAsCurrentUser); - const { pattern, meta_fields: metaFields } = request.query; - - let parsedFields: string[] = []; - try { - parsedFields = parseMetaFields(metaFields); - } catch (error) { - return response.badRequest({ - body: error, - }); - } - - try { - const fields = await indexPatterns.getFieldsForWildcard({ - pattern, - metaFields: parsedFields, - }); - - return response.ok({ - body: { fields }, - headers: { - 'content-type': 'application/json', - }, - }); - } catch (error) { - return response.notFound(); - } -}; - -/** - * Get list of fields for rollup index pattern, in the format of regular index pattern fields - */ -export function registerFieldsForWildcardRoute(deps: RouteDependencies, legacy: ServerShim) { - const handler: RequestHandler = async (ctx, request, response) => { - const { params, meta_fields: metaFields } = request.query; - - try { - // Make call and use field information from response - const { payload } = await getFieldsForWildcardRequest(ctx, request, response); - const fields = payload.fields; - const parsedParams = JSON.parse(params); - const rollupIndex = parsedParams.rollup_index; - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - const rollupFields: Field[] = []; - const fieldsFromFieldCapsApi: { [key: string]: any } = indexBy(fields, 'name'); - const rollupIndexCapabilities = getCapabilitiesForRollupIndices( - await callWithRequest('rollup.rollupIndexCapabilities', { - indexPattern: rollupIndex, - }) - )[rollupIndex].aggs; - // Keep meta fields - metaFields.forEach( - (field: string) => - fieldsFromFieldCapsApi[field] && rollupFields.push(fieldsFromFieldCapsApi[field]) - ); - const mergedRollupFields = mergeCapabilitiesWithFields( - rollupIndexCapabilities, - fieldsFromFieldCapsApi, - rollupFields - ); - return response.ok({ body: { fields: mergedRollupFields } }); - } catch (err) { - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }; - - deps.router.get( - { - path: '/api/index_patterns/rollup/_fields_for_wildcard', - validate: { - query: schema.object({ - pattern: schema.string(), - meta_fields: schema.arrayOf(schema.string(), { - defaultValue: [], - }), - params: schema.string({ - validate(value) { - try { - const params = JSON.parse(value); - const keys = Object.keys(params); - const { rollup_index: rollupIndex } = params; - - if (!rollupIndex) { - return '[request query.params]: "rollup_index" is required'; - } else if (keys.length > 1) { - const invalidParams = keys.filter(key => key !== 'rollup_index'); - return `[request query.params]: ${invalidParams.join(', ')} is not allowed`; - } - } catch (err) { - return '[request query.params]: expected JSON string'; - } - }, - }), - }), - }, - }, - licensePreRoutingFactory(legacy, handler) - ); -} diff --git a/x-pack/legacy/plugins/rollup/server/routes/api/indices.ts b/x-pack/legacy/plugins/rollup/server/routes/api/indices.ts deleted file mode 100644 index e78f09a71876b..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/routes/api/indices.ts +++ /dev/null @@ -1,175 +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 { schema } from '@kbn/config-schema'; -import { RequestHandler } from 'src/core/server'; -import { callWithRequestFactory } from '../../lib/call_with_request_factory'; -import { isEsError } from '../../lib/is_es_error'; -import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; -import { getCapabilitiesForRollupIndices } from '../../lib/map_capabilities'; -import { API_BASE_PATH } from '../../../common'; -import { RouteDependencies, ServerShim } from '../../types'; - -type NumericField = - | 'long' - | 'integer' - | 'short' - | 'byte' - | 'scaled_float' - | 'double' - | 'float' - | 'half_float'; - -interface FieldCapability { - date?: any; - keyword?: any; - long?: any; - integer?: any; - short?: any; - byte?: any; - double?: any; - float?: any; - half_float?: any; - scaled_float?: any; -} - -interface FieldCapabilities { - fields: FieldCapability[]; -} - -function isNumericField(fieldCapability: FieldCapability) { - const numericTypes = [ - 'long', - 'integer', - 'short', - 'byte', - 'double', - 'float', - 'half_float', - 'scaled_float', - ]; - return numericTypes.some(numericType => fieldCapability[numericType as NumericField] != null); -} - -export function registerIndicesRoute(deps: RouteDependencies, legacy: ServerShim) { - const getIndicesHandler: RequestHandler = async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - - try { - const data = await callWithRequest('rollup.rollupIndexCapabilities', { - indexPattern: '_all', - }); - return response.ok({ body: getCapabilitiesForRollupIndices(data) }); - } catch (err) { - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }; - - const validateIndexPatternHandler: RequestHandler = async ( - ctx, - request, - response - ) => { - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - - try { - const { indexPattern } = request.params; - const [fieldCapabilities, rollupIndexCapabilities]: [ - FieldCapabilities, - { [key: string]: any } - ] = await Promise.all([ - callWithRequest('rollup.fieldCapabilities', { indexPattern }), - callWithRequest('rollup.rollupIndexCapabilities', { indexPattern }), - ]); - - const doesMatchIndices = Object.entries(fieldCapabilities.fields).length !== 0; - const doesMatchRollupIndices = Object.entries(rollupIndexCapabilities).length !== 0; - - const dateFields: string[] = []; - const numericFields: string[] = []; - const keywordFields: string[] = []; - - const fieldCapabilitiesEntries = Object.entries(fieldCapabilities.fields); - - fieldCapabilitiesEntries.forEach( - ([fieldName, fieldCapability]: [string, FieldCapability]) => { - if (fieldCapability.date) { - dateFields.push(fieldName); - return; - } - - if (isNumericField(fieldCapability)) { - numericFields.push(fieldName); - return; - } - - if (fieldCapability.keyword) { - keywordFields.push(fieldName); - } - } - ); - - const body = { - doesMatchIndices, - doesMatchRollupIndices, - dateFields, - numericFields, - keywordFields, - }; - - return response.ok({ body }); - } catch (err) { - // 404s are still valid results. - if (err.statusCode === 404) { - const notFoundBody = { - doesMatchIndices: false, - doesMatchRollupIndices: false, - dateFields: [], - numericFields: [], - keywordFields: [], - }; - return response.ok({ body: notFoundBody }); - } - - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - - return response.internalError({ body: err }); - } - }; - - /** - * Returns a list of all rollup index names - */ - deps.router.get( - { - path: `${API_BASE_PATH}/indices`, - validate: false, - }, - licensePreRoutingFactory(legacy, getIndicesHandler) - ); - - /** - * Returns information on validity of an index pattern for creating a rollup job: - * - Does the index pattern match any indices? - * - Does the index pattern match rollup indices? - * - Which date fields, numeric fields, and keyword fields are available in the matching indices? - */ - deps.router.get( - { - path: `${API_BASE_PATH}/index_pattern_validity/{indexPattern}`, - validate: { - params: schema.object({ - indexPattern: schema.string(), - }), - }, - }, - licensePreRoutingFactory(legacy, validateIndexPatternHandler) - ); -} diff --git a/x-pack/legacy/plugins/rollup/server/routes/api/jobs.ts b/x-pack/legacy/plugins/rollup/server/routes/api/jobs.ts deleted file mode 100644 index e45713e2b807c..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/routes/api/jobs.ts +++ /dev/null @@ -1,178 +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 { schema } from '@kbn/config-schema'; -import { RequestHandler } from 'src/core/server'; -import { callWithRequestFactory } from '../../lib/call_with_request_factory'; -import { isEsError } from '../../lib/is_es_error'; -import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; -import { API_BASE_PATH } from '../../../common'; -import { RouteDependencies, ServerShim } from '../../types'; - -export function registerJobsRoute(deps: RouteDependencies, legacy: ServerShim) { - const getJobsHandler: RequestHandler = async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - - try { - const data = await callWithRequest('rollup.jobs'); - return response.ok({ body: data }); - } catch (err) { - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }; - - const createJobsHandler: RequestHandler = async (ctx, request, response) => { - try { - const { id, ...rest } = request.body.job; - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - // Create job. - await callWithRequest('rollup.createJob', { - id, - body: rest, - }); - // Then request the newly created job. - const results = await callWithRequest('rollup.job', { id }); - return response.ok({ body: results.jobs[0] }); - } catch (err) { - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }; - - const startJobsHandler: RequestHandler = async (ctx, request, response) => { - try { - const { jobIds } = request.body; - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - - const data = await Promise.all( - jobIds.map((id: string) => callWithRequest('rollup.startJob', { id })) - ).then(() => ({ success: true })); - return response.ok({ body: data }); - } catch (err) { - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }; - - const stopJobsHandler: RequestHandler = async (ctx, request, response) => { - try { - const { jobIds } = request.body; - // For our API integration tests we need to wait for the jobs to be stopped - // in order to be able to delete them sequencially. - const { waitForCompletion } = request.query; - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - const stopRollupJob = (id: string) => - callWithRequest('rollup.stopJob', { - id, - waitForCompletion: waitForCompletion === 'true', - }); - const data = await Promise.all(jobIds.map(stopRollupJob)).then(() => ({ success: true })); - return response.ok({ body: data }); - } catch (err) { - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }; - - const deleteJobsHandler: RequestHandler = async (ctx, request, response) => { - try { - const { jobIds } = request.body; - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - const data = await Promise.all( - jobIds.map((id: string) => callWithRequest('rollup.deleteJob', { id })) - ).then(() => ({ success: true })); - return response.ok({ body: data }); - } catch (err) { - // There is an issue opened on ES to handle the following error correctly - // https://github.com/elastic/elasticsearch/issues/42908 - // Until then we'll modify the response here. - if (err.response && err.response.includes('Job must be [STOPPED] before deletion')) { - err.status = 400; - err.statusCode = 400; - err.displayName = 'Bad request'; - err.message = JSON.parse(err.response).task_failures[0].reason.reason; - } - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }; - - deps.router.get( - { - path: `${API_BASE_PATH}/jobs`, - validate: false, - }, - licensePreRoutingFactory(legacy, getJobsHandler) - ); - - deps.router.put( - { - path: `${API_BASE_PATH}/create`, - validate: { - body: schema.object({ - job: schema.object( - { - id: schema.string(), - }, - { unknowns: 'allow' } - ), - }), - }, - }, - licensePreRoutingFactory(legacy, createJobsHandler) - ); - - deps.router.post( - { - path: `${API_BASE_PATH}/start`, - validate: { - body: schema.object({ - jobIds: schema.arrayOf(schema.string()), - }), - query: schema.maybe( - schema.object({ - waitForCompletion: schema.maybe(schema.string()), - }) - ), - }, - }, - licensePreRoutingFactory(legacy, startJobsHandler) - ); - - deps.router.post( - { - path: `${API_BASE_PATH}/stop`, - validate: { - body: schema.object({ - jobIds: schema.arrayOf(schema.string()), - }), - }, - }, - licensePreRoutingFactory(legacy, stopJobsHandler) - ); - - deps.router.post( - { - path: `${API_BASE_PATH}/delete`, - validate: { - body: schema.object({ - jobIds: schema.arrayOf(schema.string()), - }), - }, - }, - licensePreRoutingFactory(legacy, deleteJobsHandler) - ); -} diff --git a/x-pack/legacy/plugins/rollup/server/routes/api/search.ts b/x-pack/legacy/plugins/rollup/server/routes/api/search.ts deleted file mode 100644 index 97999a4b5ce8d..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/routes/api/search.ts +++ /dev/null @@ -1,50 +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 { schema } from '@kbn/config-schema'; -import { RequestHandler } from 'src/core/server'; -import { callWithRequestFactory } from '../../lib/call_with_request_factory'; -import { isEsError } from '../../lib/is_es_error'; -import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; -import { API_BASE_PATH } from '../../../common'; -import { RouteDependencies, ServerShim } from '../../types'; - -export function registerSearchRoute(deps: RouteDependencies, legacy: ServerShim) { - const handler: RequestHandler = async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(deps.elasticsearchService, request); - try { - const requests = request.body.map(({ index, query }: { index: string; query: any }) => - callWithRequest('rollup.search', { - index, - rest_total_hits_as_int: true, - body: query, - }) - ); - const data = await Promise.all(requests); - return response.ok({ body: data }); - } catch (err) { - if (isEsError(err)) { - return response.customError({ statusCode: err.statusCode, body: err }); - } - return response.internalError({ body: err }); - } - }; - - deps.router.post( - { - path: `${API_BASE_PATH}/search`, - validate: { - body: schema.arrayOf( - schema.object({ - index: schema.string(), - query: schema.any(), - }) - ), - }, - }, - licensePreRoutingFactory(legacy, handler) - ); -} diff --git a/x-pack/legacy/plugins/rollup/server/types.ts b/x-pack/legacy/plugins/rollup/server/types.ts deleted file mode 100644 index bcc6770e9b8ee..0000000000000 --- a/x-pack/legacy/plugins/rollup/server/types.ts +++ /dev/null @@ -1,21 +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 { IRouter, ElasticsearchServiceSetup, IClusterClient } from 'src/core/server'; -import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; - -export interface ServerShim { - plugins: { - xpack_main: XPackMainPlugin; - rollup: any; - }; -} - -export interface RouteDependencies { - router: IRouter; - elasticsearchService: ElasticsearchServiceSetup; - elasticsearch: IClusterClient; -} diff --git a/x-pack/legacy/plugins/rollup/tsconfig.json b/x-pack/legacy/plugins/rollup/tsconfig.json deleted file mode 100644 index 618c6c3e97b57..0000000000000 --- a/x-pack/legacy/plugins/rollup/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../tsconfig.json" -} diff --git a/x-pack/package.json b/x-pack/package.json index d0025ff436649..ca73884b8c7c7 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -121,7 +121,7 @@ "cheerio": "0.22.0", "commander": "3.0.2", "copy-webpack-plugin": "^5.0.4", - "cypress": "^4.2.0", + "cypress": "^4.4.1", "cypress-multi-reporters": "^1.2.3", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.2", diff --git a/x-pack/plugins/actions/server/action_type_registry.mock.ts b/x-pack/plugins/actions/server/action_type_registry.mock.ts index 6a806d1fa531c..d14d0ca2ddf84 100644 --- a/x-pack/plugins/actions/server/action_type_registry.mock.ts +++ b/x-pack/plugins/actions/server/action_type_registry.mock.ts @@ -14,6 +14,7 @@ const createActionTypeRegistryMock = () => { list: jest.fn(), ensureActionTypeEnabled: jest.fn(), isActionTypeEnabled: jest.fn(), + isActionExecutable: jest.fn(), }; return mocked; }; diff --git a/x-pack/plugins/actions/server/action_type_registry.test.ts b/x-pack/plugins/actions/server/action_type_registry.test.ts index 26bd68adfc4b6..3be2f26557079 100644 --- a/x-pack/plugins/actions/server/action_type_registry.test.ts +++ b/x-pack/plugins/actions/server/action_type_registry.test.ts @@ -28,6 +28,16 @@ beforeEach(() => { ), actionsConfigUtils: mockedActionsConfig, licenseState: mockedLicenseState, + preconfiguredActions: [ + { + actionTypeId: 'foo', + config: {}, + id: 'my-slack1', + name: 'Slack #xyz', + secrets: {}, + isPreconfigured: true, + }, + ], }; }); @@ -194,6 +204,19 @@ describe('isActionTypeEnabled', () => { expect(mockedActionsConfig.isActionTypeEnabled).toHaveBeenCalledWith('foo'); }); + test('should call isActionExecutable of the actions config', async () => { + mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true }); + actionTypeRegistry.isActionExecutable('my-slack1', 'foo'); + expect(mockedActionsConfig.isActionTypeEnabled).toHaveBeenCalledWith('foo'); + }); + + test('should return true when isActionTypeEnabled is false and isLicenseValidForActionType is true and it has preconfigured connectors', async () => { + mockedActionsConfig.isActionTypeEnabled.mockReturnValue(false); + mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true }); + + expect(actionTypeRegistry.isActionExecutable('my-slack1', 'foo')).toEqual(true); + }); + test('should call isLicenseValidForActionType of the license state', async () => { mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true }); actionTypeRegistry.isActionTypeEnabled('foo'); diff --git a/x-pack/plugins/actions/server/action_type_registry.ts b/x-pack/plugins/actions/server/action_type_registry.ts index 735ec349837a9..723982b11e1cc 100644 --- a/x-pack/plugins/actions/server/action_type_registry.ts +++ b/x-pack/plugins/actions/server/action_type_registry.ts @@ -8,7 +8,7 @@ import Boom from 'boom'; import { i18n } from '@kbn/i18n'; import { RunContext, TaskManagerSetupContract } from '../../task_manager/server'; import { ExecutorError, TaskRunnerFactory, ILicenseState } from './lib'; -import { ActionType } from './types'; +import { ActionType, PreConfiguredAction } from './types'; import { ActionType as CommonActionType } from '../common'; import { ActionsConfigurationUtilities } from './actions_config'; @@ -17,6 +17,7 @@ export interface ActionTypeRegistryOpts { taskRunnerFactory: TaskRunnerFactory; actionsConfigUtils: ActionsConfigurationUtilities; licenseState: ILicenseState; + preconfiguredActions: PreConfiguredAction[]; } export class ActionTypeRegistry { @@ -25,12 +26,14 @@ export class ActionTypeRegistry { private readonly taskRunnerFactory: TaskRunnerFactory; private readonly actionsConfigUtils: ActionsConfigurationUtilities; private readonly licenseState: ILicenseState; + private readonly preconfiguredActions: PreConfiguredAction[]; constructor(constructorParams: ActionTypeRegistryOpts) { this.taskManager = constructorParams.taskManager; this.taskRunnerFactory = constructorParams.taskRunnerFactory; this.actionsConfigUtils = constructorParams.actionsConfigUtils; this.licenseState = constructorParams.licenseState; + this.preconfiguredActions = constructorParams.preconfiguredActions; } /** @@ -58,6 +61,19 @@ export class ActionTypeRegistry { ); } + /** + * Returns true if action type is enabled or it is a preconfigured action type. + */ + public isActionExecutable(actionId: string, actionTypeId: string) { + return ( + this.isActionTypeEnabled(actionTypeId) || + (!this.isActionTypeEnabled(actionTypeId) && + this.preconfiguredActions.find( + preconfiguredAction => preconfiguredAction.id === actionId + ) !== undefined) + ); + } + /** * Registers an action type to the action type registry */ diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts index 14441bfd52dd7..c96c993fef606 100644 --- a/x-pack/plugins/actions/server/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client.test.ts @@ -44,6 +44,7 @@ beforeEach(() => { ), actionsConfigUtils: actionsConfigMock.create(), licenseState: mockedLicenseState, + preconfiguredActions: [], }; actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams); actionsClient = new ActionsClient({ @@ -221,6 +222,7 @@ describe('create()', () => { ), actionsConfigUtils: localConfigUtils, licenseState: licenseStateMock.create(), + preconfiguredActions: [], }; actionTypeRegistry = new ActionTypeRegistry(localActionTypeRegistryParams); diff --git a/x-pack/plugins/actions/server/builtin_action_types/index.test.ts b/x-pack/plugins/actions/server/builtin_action_types/index.test.ts index ac21905ede11c..9150633f06117 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/index.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/index.test.ts @@ -27,6 +27,7 @@ export function createActionTypeRegistry(): { ), actionsConfigUtils: actionsConfigMock.create(), licenseState: licenseStateMock.create(), + preconfiguredActions: [], }); registerBuiltInActionTypes({ logger, diff --git a/x-pack/plugins/actions/server/create_execute_function.test.ts b/x-pack/plugins/actions/server/create_execute_function.test.ts index 6bdd30848e4b7..1b7752588e3d3 100644 --- a/x-pack/plugins/actions/server/create_execute_function.test.ts +++ b/x-pack/plugins/actions/server/create_execute_function.test.ts @@ -282,4 +282,65 @@ describe('execute()', () => { }) ).rejects.toThrowErrorMatchingInlineSnapshot(`"Fail"`); }); + + test('should skip ensure action type if action type is preconfigured and license is valid', async () => { + const mockedActionTypeRegistry = actionTypeRegistryMock.create(); + const getScopedSavedObjectsClient = jest.fn().mockReturnValueOnce(savedObjectsClient); + const executeFn = createExecuteFunction({ + getBasePath, + taskManager: mockTaskManager, + getScopedSavedObjectsClient, + isESOUsingEphemeralEncryptionKey: false, + actionTypeRegistry: mockedActionTypeRegistry, + preconfiguredActions: [ + { + actionTypeId: 'mock-action', + config: {}, + id: 'my-slack1', + name: 'Slack #xyz', + secrets: {}, + isPreconfigured: true, + }, + ], + }); + mockedActionTypeRegistry.ensureActionTypeEnabled.mockImplementation(() => { + throw new Error('Fail'); + }); + mockedActionTypeRegistry.isActionExecutable.mockImplementation(() => true); + savedObjectsClient.get.mockResolvedValueOnce({ + id: '123', + type: 'action', + attributes: { + actionTypeId: 'mock-action', + }, + references: [], + }); + savedObjectsClient.create.mockResolvedValueOnce({ + id: '234', + type: 'action_task_params', + attributes: {}, + references: [], + }); + + await executeFn({ + id: '123', + params: { baz: false }, + spaceId: 'default', + apiKey: null, + }); + expect(getScopedSavedObjectsClient).toHaveBeenCalledWith({ + getBasePath: expect.anything(), + headers: {}, + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + }); + }); }); diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts index ac324587c5f4a..db38431b02cac 100644 --- a/x-pack/plugins/actions/server/create_execute_function.ts +++ b/x-pack/plugins/actions/server/create_execute_function.ts @@ -70,7 +70,9 @@ export function createExecuteFunction({ const savedObjectsClient = getScopedSavedObjectsClient(fakeRequest as KibanaRequest); const actionTypeId = await getActionTypeId(id); - actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); + if (!actionTypeRegistry.isActionExecutable(id, actionTypeId)) { + actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); + } const actionTaskParamsRecord = await savedObjectsClient.create('action_task_params', { actionId: id, diff --git a/x-pack/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts index 1b47bfeaf19af..4594fc1ddf6d9 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts @@ -218,6 +218,50 @@ test('throws an error if actionType is not enabled', async () => { expect(actionTypeRegistry.ensureActionTypeEnabled).toHaveBeenCalledWith('test'); }); +test('should not throws an error if actionType is preconfigured', async () => { + const actionType: jest.Mocked = { + id: 'test', + name: 'Test', + minimumLicenseRequired: 'basic', + executor: jest.fn(), + }; + const actionSavedObject = { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + config: { + bar: true, + }, + secrets: { + baz: true, + }, + }, + references: [], + }; + savedObjectsClient.get.mockResolvedValueOnce(actionSavedObject); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); + actionTypeRegistry.get.mockReturnValueOnce(actionType); + actionTypeRegistry.ensureActionTypeEnabled.mockImplementationOnce(() => { + throw new Error('not enabled for test'); + }); + actionTypeRegistry.isActionExecutable.mockImplementationOnce(() => true); + await actionExecutor.execute(executeParams); + + expect(actionTypeRegistry.ensureActionTypeEnabled).toHaveBeenCalledTimes(0); + expect(actionType.executor).toHaveBeenCalledWith({ + actionId: '1', + services: expect.anything(), + config: { + bar: true, + }, + secrets: { + baz: true, + }, + params: { foo: true }, + }); +}); + test('throws an error when passing isESOUsingEphemeralEncryptionKey with value of true', async () => { const customActionExecutor = new ActionExecutor({ isESOUsingEphemeralEncryptionKey: true }); customActionExecutor.initialize({ diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index ac574decdba71..59f8f3747273f 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -90,7 +90,9 @@ export class ActionExecutor { namespace.namespace ); - actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); + if (!actionTypeRegistry.isActionExecutable(actionId, actionTypeId)) { + actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); + } const actionType = actionTypeRegistry.get(actionTypeId); let validatedParams: Record; diff --git a/x-pack/plugins/actions/server/mocks.ts b/x-pack/plugins/actions/server/mocks.ts index 81be75d8f5daf..4160ace50f491 100644 --- a/x-pack/plugins/actions/server/mocks.ts +++ b/x-pack/plugins/actions/server/mocks.ts @@ -25,6 +25,7 @@ const createStartMock = () => { const mock: jest.Mocked = { execute: jest.fn(), isActionTypeEnabled: jest.fn(), + isActionExecutable: jest.fn(), getActionsClientWithRequest: jest.fn().mockResolvedValue(actionsClientMock.create()), preconfiguredActions: [], }; diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 5c50417172c0a..10e4d64584340 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -66,6 +66,7 @@ export interface PluginSetupContract { export interface PluginStartContract { isActionTypeEnabled(id: string): boolean; + isActionExecutable(actionId: string, actionTypeId: string): boolean; execute(options: ExecuteOptions): Promise; getActionsClientWithRequest(request: KibanaRequest): Promise>; preconfiguredActions: PreConfiguredAction[]; @@ -171,6 +172,7 @@ export class ActionsPlugin implements Plugin, Plugi taskManager: plugins.taskManager, actionsConfigUtils, licenseState: this.licenseState, + preconfiguredActions: this.preconfiguredActions, }); this.taskRunnerFactory = taskRunnerFactory; this.actionTypeRegistry = actionTypeRegistry; @@ -272,6 +274,9 @@ export class ActionsPlugin implements Plugin, Plugi isActionTypeEnabled: id => { return this.actionTypeRegistry!.isActionTypeEnabled(id); }, + isActionExecutable: (actionId: string, actionTypeId: string) => { + return this.actionTypeRegistry!.isActionExecutable(actionId, actionTypeId); + }, // Ability to get an actions client from legacy code async getActionsClientWithRequest(request: KibanaRequest) { if (isESOUsingEphemeralEncryptionKey === true) { diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts index 756080baba626..0e46ef4919626 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts @@ -51,6 +51,7 @@ const createExecutionHandlerParams = { beforeEach(() => { jest.resetAllMocks(); createExecutionHandlerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); + createExecutionHandlerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); }); test('calls actionsPlugin.execute per selected action', async () => { @@ -111,6 +112,7 @@ test('calls actionsPlugin.execute per selected action', async () => { test(`doesn't call actionsPlugin.execute for disabled actionTypes`, async () => { // Mock two calls, one for check against actions[0] and the second for actions[1] + createExecutionHandlerParams.actionsPlugin.isActionExecutable.mockReturnValueOnce(false); createExecutionHandlerParams.actionsPlugin.isActionTypeEnabled.mockReturnValueOnce(false); createExecutionHandlerParams.actionsPlugin.isActionTypeEnabled.mockReturnValueOnce(true); const executionHandler = createExecutionHandler({ @@ -148,6 +150,50 @@ test(`doesn't call actionsPlugin.execute for disabled actionTypes`, async () => }); }); +test('trow error error message when action type is disabled', async () => { + createExecutionHandlerParams.actionsPlugin.preconfiguredActions = []; + createExecutionHandlerParams.actionsPlugin.isActionExecutable.mockReturnValue(false); + createExecutionHandlerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(false); + const executionHandler = createExecutionHandler({ + ...createExecutionHandlerParams, + actions: [ + ...createExecutionHandlerParams.actions, + { + id: '2', + group: 'default', + actionTypeId: '.slack', + params: { + foo: true, + contextVal: 'My other {{context.value}} goes here', + stateVal: 'My other {{state.value}} goes here', + }, + }, + ], + }); + + await executionHandler({ + actionGroup: 'default', + state: {}, + context: {}, + alertInstanceId: '2', + }); + + expect(createExecutionHandlerParams.actionsPlugin.execute).toHaveBeenCalledTimes(0); + + createExecutionHandlerParams.actionsPlugin.isActionExecutable.mockImplementation(() => true); + const executionHandlerForPreconfiguredAction = createExecutionHandler({ + ...createExecutionHandlerParams, + actions: [...createExecutionHandlerParams.actions], + }); + await executionHandlerForPreconfiguredAction({ + actionGroup: 'default', + state: {}, + context: {}, + alertInstanceId: '2', + }); + expect(createExecutionHandlerParams.actionsPlugin.execute).toHaveBeenCalledTimes(1); +}); + test('limits actionsPlugin.execute per action group', async () => { const executionHandler = createExecutionHandler(createExecutionHandlerParams); await executionHandler({ diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts index 72f9e70905dc2..5c3e36b88879d 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts @@ -71,7 +71,7 @@ export function createExecutionHandler({ const alertLabel = `${alertType.id}:${alertId}: '${alertName}'`; for (const action of actions) { - if (!actionsPlugin.isActionTypeEnabled(action.actionTypeId)) { + if (!actionsPlugin.isActionExecutable(action.id, action.actionTypeId)) { logger.warn( `Alert "${alertId}" skipped scheduling action "${action.id}" because it is disabled` ); diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index 1d1f00bf32900..8b14199b7276a 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -182,6 +182,7 @@ describe('Task Runner', () => { test('actionsPlugin.execute is called per alert instance that is scheduled', async () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); + taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); alertType.executor.mockImplementation( ({ services: executorServices }: AlertExecutorOptions) => { executorServices.alertInstanceFactory('1').scheduleActions('default'); diff --git a/x-pack/plugins/endpoint/common/generate_data.ts b/x-pack/plugins/endpoint/common/generate_data.ts index 1978d780f54f5..4f5a6eaeb0a5e 100644 --- a/x-pack/plugins/endpoint/common/generate_data.ts +++ b/x-pack/plugins/endpoint/common/generate_data.ts @@ -6,7 +6,16 @@ import uuid from 'uuid'; import seedrandom from 'seedrandom'; -import { AlertEvent, EndpointEvent, HostMetadata, OSFields, HostFields, PolicyData } from './types'; +import { + AlertEvent, + EndpointEvent, + HostFields, + HostMetadata, + OSFields, + PolicyData, + HostPolicyResponse, + HostPolicyResponseActionStatus, +} from './types'; import { factory as policyFactory } from './models/policy_config'; export type Event = AlertEvent | EndpointEvent; @@ -486,6 +495,112 @@ export class EndpointDocGenerator { }; } + /** + * Generates a Host Policy response message + */ + generatePolicyResponse(): HostPolicyResponse { + return { + '@timestamp': new Date().toISOString(), + elastic: { + agent: { + id: 'c2a9093e-e289-4c0a-aa44-8c32a414fa7a', + }, + }, + ecs: { + version: '1.0.0', + }, + event: { + created: '2015-01-01T12:10:30Z', + kind: 'policy_response', + }, + agent: { + version: '6.0.0-rc2', + id: '8a4f500d', + }, + endpoint: { + artifacts: { + 'global-manifest': { + version: '1.2.3', + sha256: 'abcdef', + }, + 'endpointpe-v4-windows': { + version: '1.2.3', + sha256: 'abcdef', + }, + 'user-whitelist-windows': { + version: '1.2.3', + sha256: 'abcdef', + }, + 'global-whitelist-windows': { + version: '1.2.3', + sha256: 'abcdef', + }, + }, + policy: { + applied: { + version: '1.0.0', + id: '17d4b81d-9940-4b64-9de5-3e03ef1fb5cf', + status: HostPolicyResponseActionStatus.success, + response: { + configurations: { + malware: { + status: HostPolicyResponseActionStatus.success, + concerned_actions: ['download_model', 'workflow', 'a_custom_future_action'], + }, + events: { + status: HostPolicyResponseActionStatus.success, + concerned_actions: ['ingest_events_config', 'workflow'], + }, + logging: { + status: HostPolicyResponseActionStatus.success, + concerned_actions: ['configure_elasticsearch_connection'], + }, + streaming: { + status: HostPolicyResponseActionStatus.success, + concerned_actions: [ + 'detect_file_open_events', + 'download_global_artifacts', + 'a_custom_future_action', + ], + }, + }, + actions: { + download_model: { + status: HostPolicyResponseActionStatus.success, + message: 'model downloaded', + }, + ingest_events_config: { + status: HostPolicyResponseActionStatus.success, + message: 'no action taken', + }, + workflow: { + status: HostPolicyResponseActionStatus.success, + message: 'the flow worked well', + }, + a_custom_future_action: { + status: HostPolicyResponseActionStatus.success, + message: 'future message', + }, + configure_elasticsearch_connection: { + status: HostPolicyResponseActionStatus.success, + message: 'some message', + }, + detect_file_open_events: { + status: HostPolicyResponseActionStatus.success, + message: 'some message', + }, + download_global_artifacts: { + status: HostPolicyResponseActionStatus.success, + message: 'some message', + }, + }, + }, + }, + }, + }, + }; + } + private randomN(n: number): number { return Math.floor(this.random() * n); } diff --git a/x-pack/plugins/endpoint/common/types.ts b/x-pack/plugins/endpoint/common/types.ts index 49f8ebbd580d8..4da4f9bc797ca 100644 --- a/x-pack/plugins/endpoint/common/types.ts +++ b/x-pack/plugins/endpoint/common/types.ts @@ -573,3 +573,103 @@ export type NewPolicyData = NewDatasource & { } ]; }; + +/** + * the possible status for actions, configurations and overall Policy Response + */ +export enum HostPolicyResponseActionStatus { + success = 'success', + failure = 'failure', + warning = 'warning', +} + +/** + * The details of a given action + */ +interface HostPolicyResponseActionDetails { + status: HostPolicyResponseActionStatus; + message: string; +} + +/** + * A known list of possible Endpoint actions + */ +interface HostPolicyResponseActions { + download_model: HostPolicyResponseActionDetails; + ingest_events_config: HostPolicyResponseActionDetails; + workflow: HostPolicyResponseActionDetails; + configure_elasticsearch_connection: HostPolicyResponseActionDetails; + configure_kernel: HostPolicyResponseActionDetails; + configure_logging: HostPolicyResponseActionDetails; + configure_malware: HostPolicyResponseActionDetails; + connect_kernel: HostPolicyResponseActionDetails; + detect_file_open_events: HostPolicyResponseActionDetails; + detect_file_write_events: HostPolicyResponseActionDetails; + detect_image_load_events: HostPolicyResponseActionDetails; + detect_process_events: HostPolicyResponseActionDetails; + download_global_artifacts: HostPolicyResponseActionDetails; + load_config: HostPolicyResponseActionDetails; + load_malware_model: HostPolicyResponseActionDetails; + read_elasticsearch_config: HostPolicyResponseActionDetails; + read_events_config: HostPolicyResponseActionDetails; + read_kernel_config: HostPolicyResponseActionDetails; + read_logging_config: HostPolicyResponseActionDetails; + read_malware_config: HostPolicyResponseActionDetails; + // The list of possible Actions will change rapidly, so the below entry will allow + // them without us defining them here statically + [key: string]: HostPolicyResponseActionDetails; +} + +interface HostPolicyResponseConfigurationStatus { + status: HostPolicyResponseActionStatus; + concerned_actions: Array; +} + +/** + * Information about the applying of a policy to a given host + */ +export interface HostPolicyResponse { + '@timestamp': string; + elastic: { + agent: { + id: string; + }; + }; + ecs: { + version: string; + }; + event: { + created: string; + kind: string; + }; + agent: { + version: string; + id: string; + }; + endpoint: { + artifacts: {}; + policy: { + applied: { + version: string; + id: string; + status: HostPolicyResponseActionStatus; + response: { + configurations: { + malware: HostPolicyResponseConfigurationStatus; + events: HostPolicyResponseConfigurationStatus; + logging: HostPolicyResponseConfigurationStatus; + streaming: HostPolicyResponseConfigurationStatus; + }; + actions: Partial; + }; + }; + }; + }; +} + +/** + * REST API response for retrieving a host's Policy Response status + */ +export interface GetHostPolicyResponse { + policy_response: HostPolicyResponse; +} diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts index 56a49df3bdab4..16a1f96c926b8 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts @@ -5,7 +5,7 @@ */ import { ServerApiError } from '../../types'; -import { HostResultList, HostInfo } from '../../../../../common/types'; +import { HostResultList, HostInfo, GetHostPolicyResponse } from '../../../../../common/types'; interface ServerReturnedHostList { type: 'serverReturnedHostList'; @@ -27,8 +27,14 @@ interface ServerFailedToReturnHostDetails { payload: ServerApiError; } +interface ServerReturnedHostPolicyResponse { + type: 'serverReturnedHostPolicyResponse'; + payload: GetHostPolicyResponse; +} + export type HostAction = | ServerReturnedHostList | ServerFailedToReturnHostList | ServerReturnedHostDetails - | ServerFailedToReturnHostDetails; + | ServerFailedToReturnHostDetails + | ServerReturnedHostPolicyResponse; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts index bb1cfc4dd10af..d1b9a2cde4b31 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts @@ -8,6 +8,7 @@ import { HostResultList } from '../../../../../common/types'; import { isOnHostPage, hasSelectedHost, uiQueryParams, listData } from './selectors'; import { HostState } from '../../types'; import { ImmutableMiddlewareFactory } from '../../types'; +import { HostPolicyResponse } from '../../../../../common/types'; export const hostMiddlewareFactory: ImmutableMiddlewareFactory = coreStart => { return ({ getState, dispatch }) => next => async action => { @@ -69,6 +70,21 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory = core type: 'serverReturnedHostDetails', payload: response, }); + // FIXME: once we have the API implementation in place, we should call it parallel with the above api call and then dispatch this with the results of the second call + dispatch({ + type: 'serverReturnedHostPolicyResponse', + payload: { + policy_response: ({ + endpoint: { + policy: { + applied: { + status: 'success', + }, + }, + }, + } as unknown) as HostPolicyResponse, // Temporary until we get API + }, + }); } catch (error) { dispatch({ type: 'serverFailedToReturnHostDetails', diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts index adf18fa50c24f..eb74c40ff3687 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts @@ -20,6 +20,7 @@ const initialState = (): HostState => { details: undefined, detailsLoading: false, detailsError: undefined, + policyResponse: undefined, location: undefined, }; }; @@ -63,6 +64,11 @@ export const hostListReducer: ImmutableReducer = ( detailsError: action.payload, detailsLoading: false, }; + } else if (action.type === 'serverReturnedHostPolicyResponse') { + return { + ...state, + policyResponse: action.payload.policy_response, + }; } else if (action.type === 'userChangedUrl') { const newState: Immutable = { ...state, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts index b0f949ebbe757..b0711baf9cdff 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts @@ -86,3 +86,13 @@ export const showView: (state: HostState) => 'policy_response' | 'details' = cre return searchParams.show === 'policy_response' ? 'policy_response' : 'details'; } ); + +/** + * Returns the Policy Response overall status + */ +export const policyResponseStatus: (state: Immutable) => string = createSelector( + state => state.policyResponse, + policyResponse => { + return (policyResponse && policyResponse?.endpoint?.policy?.applied?.status) || ''; + } +); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index e5e600f6c6288..0598ce5f38efa 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -22,6 +22,7 @@ import { MalwareFields, UIPolicyConfig, PolicyData, + HostPolicyResponse, } from '../../../common/types'; import { EndpointPluginStartDependencies } from '../../plugin'; import { AppAction } from './store/action'; @@ -107,6 +108,8 @@ export interface HostState { detailsLoading: boolean; /** api error from retrieving host details */ detailsError?: ServerApiError; + /** Holds the Policy Response for the Host currently being displayed in the details */ + policyResponse?: HostPolicyResponse; /** current location info */ location?: Immutable; } diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx index e4da39d50304a..7d948f54bd0bc 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx @@ -16,12 +16,12 @@ import { import React, { memo, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { HostMetadata } from '../../../../../../common/types'; +import { HostMetadata, HostPolicyResponseActionStatus } from '../../../../../../common/types'; import { FormattedDateAndTime } from '../../formatted_date_time'; import { LinkToApp } from '../../components/link_to_app'; import { useHostSelector, useHostLogsUrl } from '../hooks'; import { urlFromQueryParams } from '../url_from_query_params'; -import { uiQueryParams } from '../../../store/hosts/selectors'; +import { policyResponseStatus, uiQueryParams } from '../../../store/hosts/selectors'; import { useNavigateByRouterEventHandler } from '../../hooks/use_navigate_by_router_event_handler'; const HostIds = styled(EuiListGroupItem)` @@ -31,9 +31,20 @@ const HostIds = styled(EuiListGroupItem)` } `; +const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze< + { [key in keyof typeof HostPolicyResponseActionStatus]: string } +>({ + success: 'success', + warning: 'warning', + failure: 'danger', +}); + export const HostDetails = memo(({ details }: { details: HostMetadata }) => { const { appId, appPath, url } = useHostLogsUrl(details.host.id); const queryParams = useHostSelector(uiQueryParams); + const policyStatus = useHostSelector( + policyResponseStatus + ) as keyof typeof POLICY_STATUS_TO_HEALTH_COLOR; const detailsResultsUpper = useMemo(() => { return [ { @@ -79,7 +90,10 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => { defaultMessage: 'Policy Status', }), description: ( - + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} { onClick={policyStatusClickHandler} > @@ -126,6 +141,7 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => { details.host.ip, policyResponseUri.search, policyStatusClickHandler, + policyStatus, ]); return ( diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx index 11dbed716c527..499efb4f4b8ed 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx @@ -14,9 +14,11 @@ import { mockHostResultList, } from '../../store/hosts/mock_host_result_list'; import { AppContextTestRender, createAppRootMockRenderer } from '../../mocks'; -import { HostInfo } from '../../../../../common/types'; +import { HostInfo, HostPolicyResponseActionStatus } from '../../../../../common/types'; +import { EndpointDocGenerator } from '../../../../../common/generate_data'; describe('when on the hosts page', () => { + const docGenerator = new EndpointDocGenerator(); let render: () => ReturnType; let history: AppContextTestRender['history']; let store: AppContextTestRender['store']; @@ -91,6 +93,19 @@ describe('when on the hosts page', () => { describe('when there is a selected host in the url', () => { let hostDetails: HostInfo; + const dispatchServerReturnedHostPolicyResponse = ( + overallStatus: HostPolicyResponseActionStatus = HostPolicyResponseActionStatus.success + ) => { + const policyResponse = docGenerator.generatePolicyResponse(); + policyResponse.endpoint.policy.applied.status = overallStatus; + store.dispatch({ + type: 'serverReturnedHostPolicyResponse', + payload: { + policy_response: policyResponse, + }, + }); + }; + beforeEach(() => { const { host_status, @@ -137,7 +152,6 @@ describe('when on the hosts page', () => { const renderResult = render(); const policyStatusLink = await renderResult.findByTestId('policyStatusValue'); expect(policyStatusLink).not.toBeNull(); - expect(policyStatusLink.textContent).toEqual('Successful'); expect(policyStatusLink.getAttribute('href')).toEqual( '?page_index=0&page_size=10&selected_host=1&show=policy_response' ); @@ -154,6 +168,58 @@ describe('when on the hosts page', () => { '?page_index=0&page_size=10&selected_host=1&show=policy_response' ); }); + it('should display Success overall policy status', async () => { + const renderResult = render(); + reactTestingLibrary.act(() => { + dispatchServerReturnedHostPolicyResponse(HostPolicyResponseActionStatus.success); + }); + const policyStatusLink = await renderResult.findByTestId('policyStatusValue'); + expect(policyStatusLink.textContent).toEqual('Success'); + + const policyStatusHealth = await renderResult.findByTestId('policyStatusHealth'); + expect( + policyStatusHealth.querySelector('[data-euiicon-type][color="success"]') + ).not.toBeNull(); + }); + it('should display Warning overall policy status', async () => { + const renderResult = render(); + reactTestingLibrary.act(() => { + dispatchServerReturnedHostPolicyResponse(HostPolicyResponseActionStatus.warning); + }); + const policyStatusLink = await renderResult.findByTestId('policyStatusValue'); + expect(policyStatusLink.textContent).toEqual('Warning'); + + const policyStatusHealth = await renderResult.findByTestId('policyStatusHealth'); + expect( + policyStatusHealth.querySelector('[data-euiicon-type][color="warning"]') + ).not.toBeNull(); + }); + it('should display Failed overall policy status', async () => { + const renderResult = render(); + reactTestingLibrary.act(() => { + dispatchServerReturnedHostPolicyResponse(HostPolicyResponseActionStatus.failure); + }); + const policyStatusLink = await renderResult.findByTestId('policyStatusValue'); + expect(policyStatusLink.textContent).toEqual('Failed'); + + const policyStatusHealth = await renderResult.findByTestId('policyStatusHealth'); + expect( + policyStatusHealth.querySelector('[data-euiicon-type][color="danger"]') + ).not.toBeNull(); + }); + it('should display Unknown overall policy status', async () => { + const renderResult = render(); + reactTestingLibrary.act(() => { + dispatchServerReturnedHostPolicyResponse('' as HostPolicyResponseActionStatus); + }); + const policyStatusLink = await renderResult.findByTestId('policyStatusValue'); + expect(policyStatusLink.textContent).toEqual('Unknown'); + + const policyStatusHealth = await renderResult.findByTestId('policyStatusHealth'); + expect( + policyStatusHealth.querySelector('[data-euiicon-type][color="subdued"]') + ).not.toBeNull(); + }); it('should include the link to logs', async () => { const renderResult = render(); const linkToLogs = await renderResult.findByTestId('hostDetailsLinkToLogs'); @@ -176,7 +242,7 @@ describe('when on the hosts page', () => { expect(coreStart.application.navigateToApp.mock.calls).toHaveLength(1); }); }); - describe('when showing host Policy Response', () => { + describe('when showing host Policy Response panel', () => { let renderResult: ReturnType; beforeEach(async () => { renderResult = render(); diff --git a/x-pack/plugins/infra/common/alerting/logs/types.ts b/x-pack/plugins/infra/common/alerting/logs/types.ts new file mode 100644 index 0000000000000..cbfffbfd8f940 --- /dev/null +++ b/x-pack/plugins/infra/common/alerting/logs/types.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; + +export const LOG_DOCUMENT_COUNT_ALERT_TYPE_ID = 'logs.alert.document.count'; + +export enum Comparator { + GT = 'more than', + GT_OR_EQ = 'more than or equals', + LT = 'less than', + LT_OR_EQ = 'less than or equals', + EQ = 'equals', + NOT_EQ = 'does not equal', + MATCH = 'matches', + NOT_MATCH = 'does not match', + MATCH_PHRASE = 'matches phrase', + NOT_MATCH_PHRASE = 'does not match phrase', +} + +// Maps our comparators to i18n strings, some comparators have more specific wording +// depending on the field type the comparator is being used with. +export const ComparatorToi18nMap = { + [Comparator.GT]: i18n.translate('xpack.infra.logs.alerting.comparator.gt', { + defaultMessage: 'more than', + }), + [Comparator.GT_OR_EQ]: i18n.translate('xpack.infra.logs.alerting.comparator.gtOrEq', { + defaultMessage: 'more than or equals', + }), + [Comparator.LT]: i18n.translate('xpack.infra.logs.alerting.comparator.lt', { + defaultMessage: 'less than', + }), + [Comparator.LT_OR_EQ]: i18n.translate('xpack.infra.logs.alerting.comparator.ltOrEq', { + defaultMessage: 'less than or equals', + }), + [Comparator.EQ]: i18n.translate('xpack.infra.logs.alerting.comparator.eq', { + defaultMessage: 'is', + }), + [Comparator.NOT_EQ]: i18n.translate('xpack.infra.logs.alerting.comparator.notEq', { + defaultMessage: 'is not', + }), + [`${Comparator.EQ}:number`]: i18n.translate('xpack.infra.logs.alerting.comparator.eqNumber', { + defaultMessage: 'equals', + }), + [`${Comparator.NOT_EQ}:number`]: i18n.translate( + 'xpack.infra.logs.alerting.comparator.notEqNumber', + { + defaultMessage: 'does not equal', + } + ), + [Comparator.MATCH]: i18n.translate('xpack.infra.logs.alerting.comparator.match', { + defaultMessage: 'matches', + }), + [Comparator.NOT_MATCH]: i18n.translate('xpack.infra.logs.alerting.comparator.notMatch', { + defaultMessage: 'does not match', + }), + [Comparator.MATCH_PHRASE]: i18n.translate('xpack.infra.logs.alerting.comparator.matchPhrase', { + defaultMessage: 'matches phrase', + }), + [Comparator.NOT_MATCH_PHRASE]: i18n.translate( + 'xpack.infra.logs.alerting.comparator.notMatchPhrase', + { + defaultMessage: 'does not match phrase', + } + ), +}; + +export enum AlertStates { + OK, + ALERT, + NO_DATA, + ERROR, +} + +export interface DocumentCount { + comparator: Comparator; + value: number; +} + +export interface Criterion { + field: string; + comparator: Comparator; + value: string | number; +} + +export interface LogDocumentCountAlertParams { + count: DocumentCount; + criteria: Criterion[]; + timeUnit: 's' | 'm' | 'h' | 'd'; + timeSize: number; +} + +export type TimeUnit = 's' | 'm' | 'h' | 'd'; diff --git a/x-pack/plugins/infra/common/http_api/log_entries/entries.ts b/x-pack/plugins/infra/common/http_api/log_entries/entries.ts index d532c079e3e9c..1c5a2f0fe1ad9 100644 --- a/x-pack/plugins/infra/common/http_api/log_entries/entries.ts +++ b/x-pack/plugins/infra/common/http_api/log_entries/entries.ts @@ -78,11 +78,11 @@ export const logEntryRT = rt.type({ id: rt.string, cursor: logEntriesCursorRT, columns: rt.array(logColumnRT), - context: rt.partial({ - 'log.file.path': rt.string, - 'host.name': rt.string, - 'container.id': rt.string, - }), + context: rt.union([ + rt.type({}), + rt.type({ 'container.id': rt.string }), + rt.type({ 'host.name': rt.string, 'log.file.path': rt.string }), + ]), }); export type LogMessageConstantPart = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx b/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx new file mode 100644 index 0000000000000..dd888639b6d07 --- /dev/null +++ b/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, useCallback, useMemo } from 'react'; +import { EuiPopover, EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { AlertFlyout } from './alert_flyout'; +import { useLinkProps } from '../../../hooks/use_link_props'; + +export const AlertDropdown = () => { + const [popoverOpen, setPopoverOpen] = useState(false); + const [flyoutVisible, setFlyoutVisible] = useState(false); + const manageAlertsLinkProps = useLinkProps( + { + app: 'kibana', + hash: 'management/kibana/triggersActions/alerts', + }, + { + hrefOnly: true, + } + ); + + const closePopover = useCallback(() => { + setPopoverOpen(false); + }, [setPopoverOpen]); + + const openPopover = useCallback(() => { + setPopoverOpen(true); + }, [setPopoverOpen]); + + const menuItems = useMemo(() => { + return [ + setFlyoutVisible(true)}> + + , + + + , + ]; + }, [manageAlertsLinkProps]); + + return ( + <> + + + + } + isOpen={popoverOpen} + closePopover={closePopover} + > + + + + + ); +}; diff --git a/x-pack/plugins/infra/public/components/alerting/logs/alert_flyout.tsx b/x-pack/plugins/infra/public/components/alerting/logs/alert_flyout.tsx new file mode 100644 index 0000000000000..b18c2e5b8d69c --- /dev/null +++ b/x-pack/plugins/infra/public/components/alerting/logs/alert_flyout.tsx @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext } from 'react'; +import { AlertsContextProvider, AlertAdd } from '../../../../../triggers_actions_ui/public'; +import { TriggerActionsContext } from '../../../utils/triggers_actions_context'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { LOG_DOCUMENT_COUNT_ALERT_TYPE_ID } from '../../../../common/alerting/logs/types'; + +interface Props { + visible?: boolean; + setVisible: React.Dispatch>; +} + +export const AlertFlyout = (props: Props) => { + const { triggersActionsUI } = useContext(TriggerActionsContext); + const { services } = useKibana(); + + return ( + <> + {triggersActionsUI && ( + + + + )} + + ); +}; diff --git a/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/criteria.tsx b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/criteria.tsx new file mode 100644 index 0000000000000..a9b45a117c281 --- /dev/null +++ b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/criteria.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; +import { IFieldType } from 'src/plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { IErrorObject } from '../../../../../../triggers_actions_ui/public/types'; +import { Criterion } from './criterion'; +import { + LogDocumentCountAlertParams, + Criterion as CriterionType, +} from '../../../../../common/alerting/logs/types'; + +interface Props { + fields: IFieldType[]; + criteria?: LogDocumentCountAlertParams['criteria']; + updateCriterion: (idx: number, params: Partial) => void; + removeCriterion: (idx: number) => void; + errors: IErrorObject; +} + +export const Criteria: React.FC = ({ + fields, + criteria, + updateCriterion, + removeCriterion, + errors, +}) => { + if (!criteria) return null; + return ( + + + {criteria.map((criterion, idx) => { + return ( + 1} + errors={errors[idx.toString()] as IErrorObject} + /> + ); + })} + + + ); +}; diff --git a/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/criterion.tsx b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/criterion.tsx new file mode 100644 index 0000000000000..e8cafecd94db1 --- /dev/null +++ b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/criterion.tsx @@ -0,0 +1,269 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, useMemo, useCallback } from 'react'; +import { + EuiPopoverTitle, + EuiFlexItem, + EuiFlexGroup, + EuiPopover, + EuiSelect, + EuiFieldNumber, + EuiExpression, + EuiFieldText, + EuiButtonIcon, + EuiFormRow, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { IFieldType } from 'src/plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { IErrorObject } from '../../../../../../triggers_actions_ui/public/types'; +import { + Comparator, + Criterion as CriterionType, + ComparatorToi18nMap, +} from '../../../../../common/alerting/logs/types'; + +const firstCriterionFieldPrefix = i18n.translate( + 'xpack.infra.logs.alertFlyout.firstCriterionFieldPrefix', + { + defaultMessage: 'with', + } +); + +const successiveCriterionFieldPrefix = i18n.translate( + 'xpack.infra.logs.alertFlyout.successiveCriterionFieldPrefix', + { + defaultMessage: 'and', + } +); + +const criterionFieldTitle = i18n.translate('xpack.infra.logs.alertFlyout.criterionFieldTitle', { + defaultMessage: 'Field', +}); + +const criterionComparatorValueTitle = i18n.translate( + 'xpack.infra.logs.alertFlyout.criterionComparatorValueTitle', + { + defaultMessage: 'Comparison : Value', + } +); + +const getCompatibleComparatorsForField = (fieldInfo: IFieldType | undefined) => { + if (fieldInfo?.type === 'number') { + return [ + { value: Comparator.GT, text: ComparatorToi18nMap[Comparator.GT] }, + { value: Comparator.GT_OR_EQ, text: ComparatorToi18nMap[Comparator.GT_OR_EQ] }, + { value: Comparator.LT, text: ComparatorToi18nMap[Comparator.LT] }, + { value: Comparator.LT_OR_EQ, text: ComparatorToi18nMap[Comparator.LT_OR_EQ] }, + { value: Comparator.EQ, text: ComparatorToi18nMap[`${Comparator.EQ}:number`] }, + { value: Comparator.NOT_EQ, text: ComparatorToi18nMap[`${Comparator.NOT_EQ}:number`] }, + ]; + } else if (fieldInfo?.aggregatable) { + return [ + { value: Comparator.EQ, text: ComparatorToi18nMap[Comparator.EQ] }, + { value: Comparator.NOT_EQ, text: ComparatorToi18nMap[Comparator.NOT_EQ] }, + ]; + } else { + return [ + { value: Comparator.MATCH, text: ComparatorToi18nMap[Comparator.MATCH] }, + { value: Comparator.NOT_MATCH, text: ComparatorToi18nMap[Comparator.NOT_MATCH] }, + { value: Comparator.MATCH_PHRASE, text: ComparatorToi18nMap[Comparator.MATCH_PHRASE] }, + { + value: Comparator.NOT_MATCH_PHRASE, + text: ComparatorToi18nMap[Comparator.NOT_MATCH_PHRASE], + }, + ]; + } +}; + +const getFieldInfo = (fields: IFieldType[], fieldName: string): IFieldType | undefined => { + return fields.find(field => { + return field.name === fieldName; + }); +}; + +interface Props { + idx: number; + fields: IFieldType[]; + criterion: CriterionType; + updateCriterion: (idx: number, params: Partial) => void; + removeCriterion: (idx: number) => void; + canDelete: boolean; + errors: IErrorObject; +} + +export const Criterion: React.FC = ({ + idx, + fields, + criterion, + updateCriterion, + removeCriterion, + canDelete, + errors, +}) => { + const [isFieldPopoverOpen, setIsFieldPopoverOpen] = useState(false); + const [isComparatorPopoverOpen, setIsComparatorPopoverOpen] = useState(false); + + const fieldOptions = useMemo(() => { + return fields.map(field => { + return { value: field.name, text: field.name }; + }); + }, [fields]); + + const fieldInfo: IFieldType | undefined = useMemo(() => { + return getFieldInfo(fields, criterion.field); + }, [fields, criterion]); + + const compatibleComparatorOptions = useMemo(() => { + return getCompatibleComparatorsForField(fieldInfo); + }, [fieldInfo]); + + const handleFieldChange = useCallback( + e => { + const fieldName = e.target.value; + const nextFieldInfo = getFieldInfo(fields, fieldName); + // If the field information we're dealing with has changed, reset the comparator and value. + if ( + fieldInfo && + nextFieldInfo && + (fieldInfo.type !== nextFieldInfo.type || + fieldInfo.aggregatable !== nextFieldInfo.aggregatable) + ) { + const compatibleComparators = getCompatibleComparatorsForField(nextFieldInfo); + updateCriterion(idx, { + field: fieldName, + comparator: compatibleComparators[0].value, + value: undefined, + }); + } else { + updateCriterion(idx, { field: fieldName }); + } + }, + [fieldInfo, fields, idx, updateCriterion] + ); + + return ( + + + + + setIsFieldPopoverOpen(true)} + /> + } + isOpen={isFieldPopoverOpen} + closePopover={() => setIsFieldPopoverOpen(false)} + ownFocus + panelPaddingSize="s" + anchorPosition="downLeft" + > +
+ {criterionFieldTitle} + 0} error={errors.field}> + + +
+
+
+ + setIsComparatorPopoverOpen(true)} + /> + } + isOpen={isComparatorPopoverOpen} + closePopover={() => setIsComparatorPopoverOpen(false)} + ownFocus + panelPaddingSize="s" + anchorPosition="downLeft" + > +
+ {criterionComparatorValueTitle} + + + 0} error={errors.comparator}> + + updateCriterion(idx, { comparator: e.target.value as Comparator }) + } + options={compatibleComparatorOptions} + /> + + + + 0} error={errors.value}> + {fieldInfo?.type === 'number' ? ( + { + const number = parseInt(e.target.value, 10); + updateCriterion(idx, { value: number ? number : undefined }); + }} + /> + ) : ( + updateCriterion(idx, { value: e.target.value })} + /> + )} + + + +
+
+
+
+
+ {canDelete && ( + + removeCriterion(idx)} + /> + + )} +
+ ); +}; diff --git a/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/document_count.tsx b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/document_count.tsx new file mode 100644 index 0000000000000..308165ce08a9b --- /dev/null +++ b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/document_count.tsx @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiPopoverTitle, + EuiFlexItem, + EuiFlexGroup, + EuiPopover, + EuiSelect, + EuiFieldNumber, + EuiExpression, + EuiFormRow, +} from '@elastic/eui'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { IErrorObject } from '../../../../../../triggers_actions_ui/public/types'; +import { + Comparator, + ComparatorToi18nMap, + LogDocumentCountAlertParams, +} from '../../../../../common/alerting/logs/types'; + +const documentCountPrefix = i18n.translate('xpack.infra.logs.alertFlyout.documentCountPrefix', { + defaultMessage: 'when', +}); + +const getComparatorOptions = (): Array<{ + value: Comparator; + text: string; +}> => { + return [ + { value: Comparator.LT, text: ComparatorToi18nMap[Comparator.LT] }, + { value: Comparator.LT_OR_EQ, text: ComparatorToi18nMap[Comparator.LT_OR_EQ] }, + { value: Comparator.GT, text: ComparatorToi18nMap[Comparator.GT] }, + { value: Comparator.GT_OR_EQ, text: ComparatorToi18nMap[Comparator.GT_OR_EQ] }, + ]; +}; + +interface Props { + comparator?: Comparator; + value?: number; + updateCount: (params: Partial) => void; + errors: IErrorObject; +} + +export const DocumentCount: React.FC = ({ comparator, value, updateCount, errors }) => { + const [isComparatorPopoverOpen, setComparatorPopoverOpenState] = useState(false); + const [isValuePopoverOpen, setIsValuePopoverOpen] = useState(false); + + const documentCountValue = i18n.translate('xpack.infra.logs.alertFlyout.documentCountValue', { + defaultMessage: '{value, plural, one {log entry} other {log entries}}', + values: { value }, + }); + + return ( + + + setComparatorPopoverOpenState(true)} + /> + } + isOpen={isComparatorPopoverOpen} + closePopover={() => setComparatorPopoverOpenState(false)} + ownFocus + panelPaddingSize="s" + anchorPosition="downLeft" + > +
+ {documentCountPrefix} + updateCount({ comparator: e.target.value as Comparator })} + options={getComparatorOptions()} + /> +
+
+
+ + + setIsValuePopoverOpen(true)} + color={errors.value.length === 0 ? 'secondary' : 'danger'} + /> + } + isOpen={isValuePopoverOpen} + closePopover={() => setIsValuePopoverOpen(false)} + ownFocus + panelPaddingSize="s" + anchorPosition="downLeft" + > +
+ {documentCountValue} + 0} error={errors.value}> + { + const number = parseInt(e.target.value, 10); + updateCount({ value: number ? number : undefined }); + }} + /> + +
+
+
+
+ ); +}; diff --git a/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/editor.tsx b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/editor.tsx new file mode 100644 index 0000000000000..3aed0db53bf2c --- /dev/null +++ b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/editor.tsx @@ -0,0 +1,175 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback, useMemo, useEffect, useState } from 'react'; +import { EuiButtonEmpty } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + ForLastExpression, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../triggers_actions_ui/public/common'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { IErrorObject } from '../../../../../../triggers_actions_ui/public/types'; +import { useSource } from '../../../../containers/source'; +import { + LogDocumentCountAlertParams, + Comparator, + TimeUnit, +} from '../../../../../common/alerting/logs/types'; +import { DocumentCount } from './document_count'; +import { Criteria } from './criteria'; + +export interface ExpressionCriteria { + field?: string; + comparator?: Comparator; + value?: string | number; +} + +interface Props { + errors: IErrorObject; + alertParams: Partial; + setAlertParams(key: string, value: any): void; + setAlertProperty(key: string, value: any): void; +} + +const DEFAULT_CRITERIA = { field: 'log.level', comparator: Comparator.EQ, value: 'error' }; + +const DEFAULT_EXPRESSION = { + count: { + value: 75, + comparator: Comparator.GT, + }, + criteria: [DEFAULT_CRITERIA], + timeSize: 5, + timeUnit: 'm', +}; + +export const ExpressionEditor: React.FC = props => { + const { setAlertParams, alertParams, errors } = props; + const { createDerivedIndexPattern } = useSource({ sourceId: 'default' }); + const [timeSize, setTimeSize] = useState(1); + const [timeUnit, setTimeUnit] = useState('m'); + const [hasSetDefaults, setHasSetDefaults] = useState(false); + const derivedIndexPattern = useMemo(() => createDerivedIndexPattern('logs'), [ + createDerivedIndexPattern, + ]); + + const supportedFields = useMemo(() => { + if (derivedIndexPattern?.fields) { + return derivedIndexPattern.fields.filter(field => { + return (field.type === 'string' || field.type === 'number') && field.searchable; + }); + } else { + return []; + } + }, [derivedIndexPattern]); + + // Set the default expression (disables exhaustive-deps as we only want to run this once on mount) + useEffect(() => { + for (const [key, value] of Object.entries(DEFAULT_EXPRESSION)) { + setAlertParams(key, value); + setHasSetDefaults(true); + } + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + const updateCount = useCallback( + countParams => { + const nextCountParams = { ...alertParams.count, ...countParams }; + setAlertParams('count', nextCountParams); + }, + [alertParams.count, setAlertParams] + ); + + const updateCriterion = useCallback( + (idx, criterionParams) => { + const nextCriteria = alertParams.criteria?.map((criterion, index) => { + return idx === index ? { ...criterion, ...criterionParams } : criterion; + }); + setAlertParams('criteria', nextCriteria ? nextCriteria : []); + }, + [alertParams, setAlertParams] + ); + + const updateTimeSize = useCallback( + (ts: number | undefined) => { + setTimeSize(ts || undefined); + setAlertParams('timeSize', ts); + }, + [setTimeSize, setAlertParams] + ); + + const updateTimeUnit = useCallback( + (tu: string) => { + setTimeUnit(tu as TimeUnit); + setAlertParams('timeUnit', tu); + }, + [setAlertParams] + ); + + const addCriterion = useCallback(() => { + const nextCriteria = alertParams?.criteria + ? [...alertParams.criteria, DEFAULT_CRITERIA] + : [DEFAULT_CRITERIA]; + setAlertParams('criteria', nextCriteria); + }, [alertParams, setAlertParams]); + + const removeCriterion = useCallback( + idx => { + const nextCriteria = alertParams?.criteria?.filter((criterion, index) => { + return index !== idx; + }); + setAlertParams('criteria', nextCriteria); + }, + [alertParams, setAlertParams] + ); + + // Wait until field info has loaded + if (supportedFields.length === 0) return null; + // Wait until the alert param defaults have been set + if (!hasSetDefaults) return null; + + return ( + <> + + + + + + +
+ + + +
+ + ); +}; diff --git a/x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/index.ts b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/index.tsx similarity index 77% rename from x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/index.ts rename to x-pack/plugins/infra/public/components/alerting/logs/expression_editor/index.tsx index 787814d87dff9..8b0fd5eb721b3 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/call_with_request_factory/index.ts +++ b/x-pack/plugins/infra/public/components/alerting/logs/expression_editor/index.tsx @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { callWithRequestFactory } from './call_with_request_factory'; +export * from './editor'; diff --git a/x-pack/plugins/infra/public/components/alerting/logs/log_threshold_alert_type.ts b/x-pack/plugins/infra/public/components/alerting/logs/log_threshold_alert_type.ts new file mode 100644 index 0000000000000..18126ec583696 --- /dev/null +++ b/x-pack/plugins/infra/public/components/alerting/logs/log_threshold_alert_type.ts @@ -0,0 +1,29 @@ +/* + * 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'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { AlertTypeModel } from '../../../../../triggers_actions_ui/public/types'; +import { LOG_DOCUMENT_COUNT_ALERT_TYPE_ID } from '../../../../common/alerting/logs/types'; +import { ExpressionEditor } from './expression_editor'; +import { validateExpression } from './validation'; + +export function getAlertType(): AlertTypeModel { + return { + id: LOG_DOCUMENT_COUNT_ALERT_TYPE_ID, + name: i18n.translate('xpack.infra.logs.alertFlyout.alertName', { + defaultMessage: 'Log threshold', + }), + iconClass: 'bell', + alertParamsExpression: ExpressionEditor, + validate: validateExpression, + defaultActionMessage: i18n.translate( + 'xpack.infra.logs.alerting.threshold.defaultActionMessage', + { + defaultMessage: `\\{\\{context.matchingDocuments\\}\\} log entries have matched the following conditions: \\{\\{context.conditions\\}\\}`, + } + ), + }; +} diff --git a/x-pack/plugins/infra/public/components/alerting/logs/validation.ts b/x-pack/plugins/infra/public/components/alerting/logs/validation.ts new file mode 100644 index 0000000000000..c8c513f57a9d7 --- /dev/null +++ b/x-pack/plugins/infra/public/components/alerting/logs/validation.ts @@ -0,0 +1,102 @@ +/* + * 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'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ValidationResult } from '../../../../../triggers_actions_ui/public/types'; +import { LogDocumentCountAlertParams } from '../../../../common/alerting/logs/types'; + +export function validateExpression({ + count, + criteria, + timeSize, + timeUnit, +}: Partial): ValidationResult { + const validationResult = { errors: {} }; + + // NOTE: In the case of components provided by the Alerting framework the error property names + // must match what they expect. + const errors: { + count: { + value: string[]; + }; + criteria: { + [id: string]: { + field: string[]; + comparator: string[]; + value: string[]; + }; + }; + timeWindowSize: string[]; + timeSizeUnit: string[]; + } = { + count: { + value: [], + }, + criteria: {}, + timeSizeUnit: [], + timeWindowSize: [], + }; + + validationResult.errors = errors; + + // Document count validation + if (typeof count?.value !== 'number') { + errors.count.value.push( + i18n.translate('xpack.infra.logs.alertFlyout.error.documentCountRequired', { + defaultMessage: 'Document count is Required.', + }) + ); + } + + // Time validation + if (!timeSize) { + errors.timeWindowSize.push( + i18n.translate('xpack.infra.logs.alertFlyout.error.timeSizeRequired', { + defaultMessage: 'Time size is Required.', + }) + ); + } + + if (criteria && criteria.length > 0) { + // Criteria validation + criteria.forEach((criterion, idx: number) => { + const id = idx.toString(); + + errors.criteria[id] = { + field: [], + comparator: [], + value: [], + }; + + if (!criterion.field) { + errors.criteria[id].field.push( + i18n.translate('xpack.infra.logs.alertFlyout.error.criterionFieldRequired', { + defaultMessage: 'Field is required.', + }) + ); + } + + if (!criterion.comparator) { + errors.criteria[id].comparator.push( + i18n.translate('xpack.infra.logs.alertFlyout.error.criterionComparatorRequired', { + defaultMessage: 'Comparator is required.', + }) + ); + } + + if (!criterion.value) { + errors.criteria[id].value.push( + i18n.translate('xpack.infra.logs.alertFlyout.error.criterionValueRequired', { + defaultMessage: 'Value is required.', + }) + ); + } + }); + } + + return validationResult; +} diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_actions_column.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_actions_column.tsx index e02346c4e758a..976e4165eb6d5 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_actions_column.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_actions_column.tsx @@ -24,29 +24,49 @@ interface LogEntryActionsColumnProps { isMenuOpen: boolean; onOpenMenu: () => void; onCloseMenu: () => void; - onViewDetails: () => void; + onViewDetails?: () => void; + onViewLogInContext?: () => void; } const MENU_LABEL = i18n.translate('xpack.infra.logEntryItemView.logEntryActionsMenuToolTip', { - defaultMessage: 'View Details', + defaultMessage: 'View actions for line', }); const LOG_DETAILS_LABEL = i18n.translate('xpack.infra.logs.logEntryActionsDetailsButton', { - defaultMessage: 'View actions for line', + defaultMessage: 'View details', }); +const LOG_VIEW_IN_CONTEXT_LABEL = i18n.translate( + 'xpack.infra.lobs.logEntryActionsViewInContextButton', + { + defaultMessage: 'View in context', + } +); + export const LogEntryActionsColumn: React.FC = ({ isHovered, isMenuOpen, onOpenMenu, onCloseMenu, onViewDetails, + onViewLogInContext, }) => { const handleClickViewDetails = useCallback(() => { onCloseMenu(); - onViewDetails(); + + // Function might be `undefined` and the linter doesn't like that. + // eslint-disable-next-line no-unused-expressions + onViewDetails?.(); }, [onCloseMenu, onViewDetails]); + const handleClickViewInContext = useCallback(() => { + onCloseMenu(); + + // Function might be `undefined` and the linter doesn't like that. + // eslint-disable-next-line no-unused-expressions + onViewLogInContext?.(); + }, [onCloseMenu, onViewLogInContext]); + const button = ( = ({ + {onViewLogInContext !== undefined ? ( + + ) : null} diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx index 7d7df796d13ad..5c20df000ae51 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx @@ -5,6 +5,7 @@ */ import React, { memo, useState, useCallback, useMemo } from 'react'; +import { isEmpty } from 'lodash'; import { euiStyled } from '../../../../../observability/public'; import { isTimestampColumn } from '../../../utils/log_entry'; @@ -32,6 +33,7 @@ interface LogEntryRowProps { isHighlighted: boolean; logEntry: LogEntry; openFlyoutWithItem?: (id: string) => void; + openViewLogInContext?: (entry: LogEntry) => void; scale: TextScale; wrap: boolean; } @@ -46,6 +48,7 @@ export const LogEntryRow = memo( isHighlighted, logEntry, openFlyoutWithItem, + openViewLogInContext, scale, wrap, }: LogEntryRowProps) => { @@ -63,6 +66,16 @@ export const LogEntryRow = memo( logEntry.id, ]); + const handleOpenViewLogInContext = useCallback(() => openViewLogInContext?.(logEntry), [ + openViewLogInContext, + logEntry, + ]); + + const hasContext = useMemo(() => !isEmpty(logEntry.context), [logEntry]); + const hasActionFlyoutWithItem = openFlyoutWithItem !== undefined; + const hasActionViewLogInContext = hasContext && openViewLogInContext !== undefined; + const hasActionsMenu = hasActionFlyoutWithItem || hasActionViewLogInContext; + const logEntryColumnsById = useMemo( () => logEntry.columns.reduce<{ @@ -165,18 +178,23 @@ export const LogEntryRow = memo( ); } })} - - - + {hasActionsMenu ? ( + + + + ) : null} ); } diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx index 2c389b47fa6cf..f89aaf12db1bc 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx @@ -26,6 +26,7 @@ import { MeasurableItemView } from './measurable_item_view'; import { VerticalScrollPanel } from './vertical_scroll_panel'; import { useColumnWidths, LogEntryColumnWidths } from './log_entry_column'; import { LogDateRow } from './log_date_row'; +import { LogEntry } from '../../../../common/http_api'; interface ScrollableLogTextStreamViewProps { columnConfigurations: LogColumnConfiguration[]; @@ -50,8 +51,9 @@ interface ScrollableLogTextStreamViewProps { }) => any; loadNewerItems: () => void; reloadItems: () => void; - setFlyoutItem: (id: string) => void; - setFlyoutVisibility: (visible: boolean) => void; + setFlyoutItem?: (id: string) => void; + setFlyoutVisibility?: (visible: boolean) => void; + setContextEntry?: (entry: LogEntry) => void; highlightedItem: string | null; currentHighlightKey: UniqueTimeKey | null; startDateExpression: string; @@ -140,9 +142,16 @@ export class ScrollableLogTextStreamView extends React.PureComponent< lastLoadedTime, updateDateRange, startLiveStreaming, + setFlyoutItem, + setFlyoutVisibility, + setContextEntry, } = this.props; + const { targetId, items, isScrollLocked } = this.state; const hasItems = items.length > 0; + const hasFlyoutAction = !!(setFlyoutItem && setFlyoutVisibility); + const hasContextAction = !!setContextEntry; + return ( {isReloading && (!isStreaming || !hasItems) ? ( @@ -227,7 +236,14 @@ export class ScrollableLogTextStreamView extends React.PureComponent< { - this.props.setFlyoutItem(id); - this.props.setFlyoutVisibility(true); + const { setFlyoutItem, setFlyoutVisibility } = this.props; + + if (setFlyoutItem && setFlyoutVisibility) { + setFlyoutItem(id); + setFlyoutVisibility(true); + } + }; + + private handleOpenViewLogInContext = (entry: LogEntry) => { + const { setContextEntry } = this.props; + if (setContextEntry) { + setContextEntry(entry); + } }; private handleReload = () => { diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx index 69a6abbca4b34..0eb6140c0de84 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/text_styles.tsx @@ -33,7 +33,7 @@ export const hoveredContentStyle = css` `; export const highlightedContentStyle = css` - background-color: ${props => props.theme.eui.euiFocusBackgroundColor}; + background-color: ${props => props.theme.eui.euiColorHighlight}; `; export const longWrappedContentStyle = css` diff --git a/x-pack/legacy/plugins/rollup/server/lib/is_es_error/index.ts b/x-pack/plugins/infra/public/containers/logs/view_log_in_context/index.ts similarity index 84% rename from x-pack/legacy/plugins/rollup/server/lib/is_es_error/index.ts rename to x-pack/plugins/infra/public/containers/logs/view_log_in_context/index.ts index a9a3c61472d8c..0110c55c7c556 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/is_es_error/index.ts +++ b/x-pack/plugins/infra/public/containers/logs/view_log_in_context/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { isEsError } from './is_es_error'; +export * from './view_log_in_context'; diff --git a/x-pack/plugins/infra/public/containers/logs/view_log_in_context/view_log_in_context.ts b/x-pack/plugins/infra/public/containers/logs/view_log_in_context/view_log_in_context.ts new file mode 100644 index 0000000000000..bc719cbd694e4 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/logs/view_log_in_context/view_log_in_context.ts @@ -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 { useState, useEffect, useCallback } from 'react'; +import createContainer from 'constate'; +import { LogEntry } from '../../../../common/http_api'; +import { fetchLogEntries } from '../log_entries/api/fetch_log_entries'; +import { esKuery } from '../../../../../../../src/plugins/data/public'; + +function getQueryFromLogEntry(entry: LogEntry) { + const expression = Object.entries(entry.context).reduce((kuery, [key, value]) => { + const currentExpression = `${key} : "${value}"`; + if (kuery.length > 0) { + return `${kuery} AND ${currentExpression}`; + } else { + return currentExpression; + } + }, ''); + + return JSON.stringify(esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(expression))); +} + +interface ViewLogInContextProps { + sourceId: string; + startTimestamp: number; + endTimestamp: number; +} + +export interface ViewLogInContextState { + entries: LogEntry[]; + isLoading: boolean; + contextEntry?: LogEntry; +} + +interface ViewLogInContextCallbacks { + setContextEntry: (entry?: LogEntry) => void; +} + +export const useViewLogInContext = ( + props: ViewLogInContextProps +): [ViewLogInContextState, ViewLogInContextCallbacks] => { + const [contextEntry, setContextEntry] = useState(); + const [entries, setEntries] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const { startTimestamp, endTimestamp, sourceId } = props; + + const maybeFetchLogs = useCallback(async () => { + if (contextEntry) { + setIsLoading(true); + const { data } = await fetchLogEntries({ + sourceId, + startTimestamp, + endTimestamp, + center: contextEntry.cursor, + query: getQueryFromLogEntry(contextEntry), + }); + setEntries(data.entries); + setIsLoading(false); + } else { + setEntries([]); + setIsLoading(false); + } + }, [contextEntry, startTimestamp, endTimestamp, sourceId]); + + useEffect(() => { + maybeFetchLogs(); + }, [maybeFetchLogs]); + + return [ + { + contextEntry, + entries, + isLoading, + }, + { + setContextEntry, + }, + ]; +}; + +export const ViewLogInContext = createContainer(useViewLogInContext); diff --git a/x-pack/plugins/infra/public/hooks/use_link_props.tsx b/x-pack/plugins/infra/public/hooks/use_link_props.tsx index 8c522bb7fa764..dec8eaae56f41 100644 --- a/x-pack/plugins/infra/public/hooks/use_link_props.tsx +++ b/x-pack/plugins/infra/public/hooks/use_link_props.tsx @@ -26,12 +26,20 @@ interface LinkProps { onClick?: (e: React.MouseEvent | React.MouseEvent) => void; } -export const useLinkProps = ({ app, pathname, hash, search }: LinkDescriptor): LinkProps => { +interface Options { + hrefOnly?: boolean; +} + +export const useLinkProps = ( + { app, pathname, hash, search }: LinkDescriptor, + options: Options = {} +): LinkProps => { validateParams({ app, pathname, hash, search }); const { prompt } = useNavigationWarningPrompt(); const prefixer = usePrefixPathWithBasepath(); const navigateToApp = useKibana().services.application?.navigateToApp; + const { hrefOnly } = options; const encodedSearch = useMemo(() => { return search ? encodeSearch(search) : undefined; @@ -86,7 +94,10 @@ export const useLinkProps = ({ app, pathname, hash, search }: LinkDescriptor): L return { href, - onClick, + // Sometimes it may not be desirable to have onClick call "navigateToApp". + // E.g. the management section of Kibana cannot be successfully deeplinked to via + // "navigateToApp". In those cases we can choose to defer to legacy behaviour. + onClick: hrefOnly ? undefined : onClick, }; }; diff --git a/x-pack/plugins/infra/public/pages/logs/page_content.tsx b/x-pack/plugins/infra/public/pages/logs/page_content.tsx index ed6f06deeef64..dc210406275d8 100644 --- a/x-pack/plugins/infra/public/pages/logs/page_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/page_content.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { Route, Switch } from 'react-router-dom'; @@ -21,6 +22,7 @@ import { LogEntryCategoriesPage } from './log_entry_categories'; import { LogEntryRatePage } from './log_entry_rate'; import { LogsSettingsPage } from './settings'; import { StreamPage } from './stream'; +import { AlertDropdown } from '../../components/alerting/logs/alert_dropdown'; export const LogsPageContent: React.FunctionComponent = () => { const uiCapabilities = useKibana().services.application?.capabilities; @@ -65,13 +67,20 @@ export const LogsPageContent: React.FunctionComponent = () => { readOnlyBadge={!uiCapabilities?.logs?.save} /> - + + + + + + + + diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page.tsx index 3e07dcebf112d..aff0ac27c36f8 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page.tsx @@ -10,6 +10,7 @@ import { ColumnarPage } from '../../../components/page'; import { StreamPageContent } from './page_content'; import { StreamPageHeader } from './page_header'; import { LogsPageProviders } from './page_providers'; +import { PageViewLogInContext } from './page_view_log_in_context'; import { useTrackPageview } from '../../../../../observability/public'; export const StreamPage = () => { @@ -21,6 +22,7 @@ export const StreamPage = () => { + ); }; diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_logs_content.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_logs_content.tsx index b6061203a1c72..3208ea2402950 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_logs_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_logs_content.tsx @@ -27,6 +27,7 @@ import { Source } from '../../../containers/source'; import { LogsToolbar } from './page_toolbar'; import { LogHighlightsState } from '../../../containers/logs/log_highlights'; +import { ViewLogInContext } from '../../../containers/logs/view_log_in_context'; export const LogsPageLogsContent: React.FunctionComponent = () => { const { source, sourceId, version } = useContext(Source.Context); @@ -55,6 +56,9 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { endDateExpression, updateDateRange, } = useContext(LogPositionState.Context); + + const [, { setContextEntry }] = useContext(ViewLogInContext.Context); + return ( <> @@ -104,6 +108,7 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { wrap={textWrap} setFlyoutItem={setFlyoutId} setFlyoutVisibility={setFlyoutVisibility} + setContextEntry={setContextEntry} highlightedItem={surroundingLogsId ? surroundingLogsId : null} currentHighlightKey={currentHighlightKey} startDateExpression={startDateExpression} diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_providers.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_providers.tsx index e4ccdaf7c5748..0341dc7c35b33 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_providers.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_providers.tsx @@ -14,6 +14,7 @@ import { LogFilterState, WithLogFilterUrlState } from '../../../containers/logs/ import { LogEntriesState } from '../../../containers/logs/log_entries'; import { Source } from '../../../containers/source'; +import { ViewLogInContext } from '../../../containers/logs/view_log_in_context'; const LogFilterStateProvider: React.FC = ({ children }) => { const { createDerivedIndexPattern } = useContext(Source.Context); @@ -26,6 +27,25 @@ const LogFilterStateProvider: React.FC = ({ children }) => { ); }; +const ViewLogInContextProvider: React.FC = ({ children }) => { + const { startTimestamp, endTimestamp } = useContext(LogPositionState.Context); + const { sourceId } = useContext(Source.Context); + + if (!startTimestamp || !endTimestamp) { + return null; + } + + return ( + + {children} + + ); +}; + const LogEntriesStateProvider: React.FC = ({ children }) => { const { sourceId } = useContext(Source.Context); const { @@ -91,11 +111,13 @@ export const LogsPageProviders: React.FunctionComponent = ({ children }) => { - - - {children} - - + + + + {children} + + + diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx new file mode 100644 index 0000000000000..fdfc16d6a9bef --- /dev/null +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext, useCallback, useMemo } from 'react'; +import { noop } from 'lodash'; +import { + EuiOverlayMask, + EuiModal, + EuiModalBody, + EuiText, + EuiTextColor, + EuiFlexGroup, + EuiFlexItem, + EuiToolTip, +} from '@elastic/eui'; +import { ViewLogInContext } from '../../../containers/logs/view_log_in_context'; +import { LogEntry } from '../../../../common/http_api'; +import { Source } from '../../../containers/source'; +import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration'; +import { ScrollableLogTextStreamView } from '../../../components/logging/log_text_stream'; +import { useViewportDimensions } from '../../../utils/use_viewport_dimensions'; + +const MODAL_MARGIN = 25; + +export const PageViewLogInContext: React.FC = () => { + const { source } = useContext(Source.Context); + const { textScale, textWrap } = useContext(LogViewConfiguration.Context); + const columnConfigurations = useMemo(() => (source && source.configuration.logColumns) || [], [ + source, + ]); + const [{ contextEntry, entries, isLoading }, { setContextEntry }] = useContext( + ViewLogInContext.Context + ); + const closeModal = useCallback(() => setContextEntry(undefined), [setContextEntry]); + const { width: vw, height: vh } = useViewportDimensions(); + + const streamItems = useMemo( + () => + entries.map(entry => ({ + kind: 'logEntry' as const, + logEntry: entry, + highlights: [], + })), + [entries] + ); + + if (!contextEntry) { + return null; + } + + return ( + + + + + + + + + + + + + ); +}; + +const LogEntryContext: React.FC<{ context: LogEntry['context'] }> = ({ context }) => { + if ('container.id' in context) { + return

Displayed logs are from container {context['container.id']}

; + } + + if ('host.name' in context) { + const shortenedFilePath = + context['log.file.path'].length > 45 + ? context['log.file.path'].slice(0, 20) + '...' + context['log.file.path'].slice(-25) + : context['log.file.path']; + + return ( + +

+ + Displayed logs are from file{' '} + + {shortenedFilePath} + {' '} + and host {context['host.name']} + +

+
+ ); + } + + return null; +}; diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index 3b6647b9bfbbe..40366b2a54f24 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -21,7 +21,8 @@ import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/p import { DataEnhancedSetup, DataEnhancedStart } from '../../data_enhanced/public'; import { TriggersAndActionsUIPublicPluginSetup } from '../../../plugins/triggers_actions_ui/public'; -import { getAlertType } from './components/alerting/metrics/metric_threshold_alert_type'; +import { getAlertType as getMetricsAlertType } from './components/alerting/metrics/metric_threshold_alert_type'; +import { getAlertType as getLogsAlertType } from './components/alerting/logs/log_threshold_alert_type'; export type ClientSetup = void; export type ClientStart = void; @@ -52,7 +53,8 @@ export class Plugin setup(core: CoreSetup, pluginsSetup: ClientPluginsSetup) { registerFeatures(pluginsSetup.home); - pluginsSetup.triggers_actions_ui.alertTypeRegistry.register(getAlertType()); + pluginsSetup.triggers_actions_ui.alertTypeRegistry.register(getMetricsAlertType()); + pluginsSetup.triggers_actions_ui.alertTypeRegistry.register(getLogsAlertType()); core.application.register({ id: 'logs', diff --git a/x-pack/plugins/infra/public/utils/datemath.test.ts b/x-pack/plugins/infra/public/utils/datemath.test.ts index 0f272733c5f97..c8fbe5583db2e 100644 --- a/x-pack/plugins/infra/public/utils/datemath.test.ts +++ b/x-pack/plugins/infra/public/utils/datemath.test.ts @@ -198,7 +198,7 @@ describe('extendDatemath()', () => { }); }); - describe('with a positive Operator', () => { + describe('with a positive operator', () => { it('Halves miliseconds', () => { expect(extendDatemath('now+250ms')).toEqual({ value: 'now+125ms', @@ -307,6 +307,274 @@ describe('extendDatemath()', () => { }); }); }); + + describe('moving after', () => { + describe('with a negative operator', () => { + it('Halves miliseconds', () => { + expect(extendDatemath('now-250ms', 'after')).toEqual({ + value: 'now-125ms', + diffAmount: 125, + diffUnit: 'ms', + }); + }); + + it('Halves seconds', () => { + expect(extendDatemath('now-10s', 'after')).toEqual({ + value: 'now-5s', + diffAmount: 5, + diffUnit: 's', + }); + }); + + it('Halves minutes when the amount is low', () => { + expect(extendDatemath('now-2m', 'after')).toEqual({ + value: 'now-1m', + diffAmount: 1, + diffUnit: 'm', + }); + expect(extendDatemath('now-4m', 'after')).toEqual({ + value: 'now-2m', + diffAmount: 2, + diffUnit: 'm', + }); + expect(extendDatemath('now-6m', 'after')).toEqual({ + value: 'now-3m', + diffAmount: 3, + diffUnit: 'm', + }); + }); + + it('advances minutes in half ammounts when the amount is high', () => { + expect(extendDatemath('now-30m', 'after')).toEqual({ + value: 'now-20m', + diffAmount: 10, + diffUnit: 'm', + }); + }); + + it('advances half an hour when the amount is one hour', () => { + expect(extendDatemath('now-1h', 'after')).toEqual({ + value: 'now-30m', + diffAmount: 30, + diffUnit: 'm', + }); + }); + + it('advances one hour when the amount is one day', () => { + expect(extendDatemath('now-1d', 'after')).toEqual({ + value: 'now-23h', + diffAmount: 1, + diffUnit: 'h', + }); + }); + + it('advances one day when the amount is more than one day', () => { + expect(extendDatemath('now-2d', 'after')).toEqual({ + value: 'now-1d', + diffAmount: 1, + diffUnit: 'd', + }); + expect(extendDatemath('now-3d', 'after')).toEqual({ + value: 'now-2d', + diffAmount: 1, + diffUnit: 'd', + }); + }); + + it('advances one day when the amount is one week', () => { + expect(extendDatemath('now-1w', 'after')).toEqual({ + value: 'now-6d', + diffAmount: 1, + diffUnit: 'd', + }); + }); + + it('advances one week when the amount is more than one week', () => { + expect(extendDatemath('now-2w', 'after')).toEqual({ + value: 'now-1w', + diffAmount: 1, + diffUnit: 'w', + }); + }); + + it('advances one week when the amount is one month', () => { + expect(extendDatemath('now-1M', 'after')).toEqual({ + value: 'now-3w', + diffAmount: 1, + diffUnit: 'w', + }); + }); + + it('advances one month when the amount is more than one month', () => { + expect(extendDatemath('now-2M', 'after')).toEqual({ + value: 'now-1M', + diffAmount: 1, + diffUnit: 'M', + }); + }); + + it('advances one month when the amount is one year', () => { + expect(extendDatemath('now-1y', 'after')).toEqual({ + value: 'now-11M', + diffAmount: 1, + diffUnit: 'M', + }); + }); + + it('advances one year when the amount is in years', () => { + expect(extendDatemath('now-2y', 'after')).toEqual({ + value: 'now-1y', + diffAmount: 1, + diffUnit: 'y', + }); + }); + }); + + describe('with a positive operator', () => { + it('doubles miliseconds', () => { + expect(extendDatemath('now+250ms', 'after')).toEqual({ + value: 'now+500ms', + diffAmount: 250, + diffUnit: 'ms', + }); + }); + + it('normalizes miliseconds', () => { + expect(extendDatemath('now+500ms', 'after')).toEqual({ + value: 'now+1s', + diffAmount: 500, + diffUnit: 'ms', + }); + }); + + it('doubles seconds', () => { + expect(extendDatemath('now+10s', 'after')).toEqual({ + value: 'now+20s', + diffAmount: 10, + diffUnit: 's', + }); + }); + + it('normalizes seconds', () => { + expect(extendDatemath('now+30s', 'after')).toEqual({ + value: 'now+1m', + diffAmount: 30, + diffUnit: 's', + }); + }); + + it('doubles minutes when amount is low', () => { + expect(extendDatemath('now+1m', 'after')).toEqual({ + value: 'now+2m', + diffAmount: 1, + diffUnit: 'm', + }); + expect(extendDatemath('now+2m', 'after')).toEqual({ + value: 'now+4m', + diffAmount: 2, + diffUnit: 'm', + }); + expect(extendDatemath('now+3m', 'after')).toEqual({ + value: 'now+6m', + diffAmount: 3, + diffUnit: 'm', + }); + }); + + it('adds half the minutes when the amount is high', () => { + expect(extendDatemath('now+20m', 'after')).toEqual({ + value: 'now+30m', + diffAmount: 10, + diffUnit: 'm', + }); + }); + + it('Adds half an hour when the amount is one hour', () => { + expect(extendDatemath('now+1h', 'after')).toEqual({ + value: 'now+90m', + diffAmount: 30, + diffUnit: 'm', + }); + }); + + it('Adds one hour when the amount more than one hour', () => { + expect(extendDatemath('now+2h', 'after')).toEqual({ + value: 'now+3h', + diffAmount: 1, + diffUnit: 'h', + }); + }); + + it('Adds one hour when the amount is one day', () => { + expect(extendDatemath('now+1d', 'after')).toEqual({ + value: 'now+25h', + diffAmount: 1, + diffUnit: 'h', + }); + }); + + it('Adds one day when the amount is more than one day', () => { + expect(extendDatemath('now+2d', 'after')).toEqual({ + value: 'now+3d', + diffAmount: 1, + diffUnit: 'd', + }); + expect(extendDatemath('now+3d', 'after')).toEqual({ + value: 'now+4d', + diffAmount: 1, + diffUnit: 'd', + }); + }); + + it('Adds one day when the amount is one week', () => { + expect(extendDatemath('now+1w', 'after')).toEqual({ + value: 'now+8d', + diffAmount: 1, + diffUnit: 'd', + }); + }); + + it('Adds one week when the amount is more than one week', () => { + expect(extendDatemath('now+2w', 'after')).toEqual({ + value: 'now+3w', + diffAmount: 1, + diffUnit: 'w', + }); + }); + + it('Adds one week when the amount is one month', () => { + expect(extendDatemath('now+1M', 'after')).toEqual({ + value: 'now+5w', + diffAmount: 1, + diffUnit: 'w', + }); + }); + + it('Adds one month when the amount is more than one month', () => { + expect(extendDatemath('now+2M', 'after')).toEqual({ + value: 'now+3M', + diffAmount: 1, + diffUnit: 'M', + }); + }); + + it('Adds one month when the amount is one year', () => { + expect(extendDatemath('now+1y', 'after')).toEqual({ + value: 'now+13M', + diffAmount: 1, + diffUnit: 'M', + }); + }); + + it('Adds one year when the amount is in years', () => { + expect(extendDatemath('now+2y', 'after')).toEqual({ + value: 'now+3y', + diffAmount: 1, + diffUnit: 'y', + }); + }); + }); + }); }); describe('convertDate()', () => { diff --git a/x-pack/plugins/infra/public/utils/datemath.ts b/x-pack/plugins/infra/public/utils/datemath.ts index 50a9b6e4f6945..7331a2450956f 100644 --- a/x-pack/plugins/infra/public/utils/datemath.ts +++ b/x-pack/plugins/infra/public/utils/datemath.ts @@ -68,7 +68,8 @@ function extendRelativeDatemath( return undefined; } - const mustIncreaseAmount = operator === '-' && direction === 'before'; + const mustIncreaseAmount = + (operator === '-' && direction === 'before') || (operator === '+' && direction === 'after'); const parsedAmount = parseInt(amount, 10); let newUnit: Unit = unit as Unit; let newAmount: number; diff --git a/x-pack/plugins/infra/public/utils/use_viewport_dimensions.ts b/x-pack/plugins/infra/public/utils/use_viewport_dimensions.ts new file mode 100644 index 0000000000000..ddaf8fcd31a39 --- /dev/null +++ b/x-pack/plugins/infra/public/utils/use_viewport_dimensions.ts @@ -0,0 +1,39 @@ +/* + * 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 { useState, useEffect } from 'react'; +import { throttle } from 'lodash'; + +interface ViewportDimensions { + width: number; + height: number; +} + +const getViewportWidth = () => + window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; +const getViewportHeight = () => + window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; + +export function useViewportDimensions(): ViewportDimensions { + const [dimensions, setDimensions] = useState({ + width: getViewportWidth(), + height: getViewportHeight(), + }); + + useEffect(() => { + const updateDimensions = throttle(() => { + setDimensions({ + width: getViewportWidth(), + height: getViewportHeight(), + }); + }, 250); + + window.addEventListener('resize', updateDimensions); + return () => window.removeEventListener('resize', updateDimensions); + }, []); + + return dimensions; +} diff --git a/x-pack/plugins/infra/server/graphql/sources/resolvers.ts b/x-pack/plugins/infra/server/graphql/sources/resolvers.ts index 1fe1431392a38..f880eca933241 100644 --- a/x-pack/plugins/infra/server/graphql/sources/resolvers.ts +++ b/x-pack/plugins/infra/server/graphql/sources/resolvers.ts @@ -93,7 +93,10 @@ export const createSourcesResolvers = ( } => ({ Query: { async source(root, args, { req }) { - const requestedSourceConfiguration = await libs.sources.getSourceConfiguration(req, args.id); + const requestedSourceConfiguration = await libs.sources.getSourceConfiguration( + req.core.savedObjects.client, + args.id + ); return requestedSourceConfiguration; }, diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts new file mode 100644 index 0000000000000..cdec04ab81a8e --- /dev/null +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts @@ -0,0 +1,250 @@ +/* + * 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 { AlertExecutorOptions, AlertServices } from '../../../../../alerting/server'; +import { + AlertStates, + Comparator, + LogDocumentCountAlertParams, + Criterion, +} from '../../../../common/alerting/logs/types'; +import { InfraBackendLibs } from '../../infra_types'; +import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds'; +import { InfraSource } from '../../../../common/http_api/source_api'; + +const checkValueAgainstComparatorMap: { + [key: string]: (a: number, b: number) => boolean; +} = { + [Comparator.GT]: (a: number, b: number) => a > b, + [Comparator.GT_OR_EQ]: (a: number, b: number) => a >= b, + [Comparator.LT]: (a: number, b: number) => a < b, + [Comparator.LT_OR_EQ]: (a: number, b: number) => a <= b, +}; + +export const createLogThresholdExecutor = (alertUUID: string, libs: InfraBackendLibs) => + async function({ services, params }: AlertExecutorOptions) { + const { count, criteria } = params as LogDocumentCountAlertParams; + const { alertInstanceFactory, savedObjectsClient, callCluster } = services; + const { sources } = libs; + + const sourceConfiguration = await sources.getSourceConfiguration(savedObjectsClient, 'default'); + const indexPattern = sourceConfiguration.configuration.logAlias; + + const alertInstance = alertInstanceFactory(alertUUID); + + try { + const query = getESQuery( + params as LogDocumentCountAlertParams, + sourceConfiguration.configuration + ); + const result = await getResults(query, indexPattern, callCluster); + + if (checkValueAgainstComparatorMap[count.comparator](result.count, count.value)) { + alertInstance.scheduleActions(FIRED_ACTIONS.id, { + matchingDocuments: result.count, + conditions: createConditionsMessage(criteria), + }); + + alertInstance.replaceState({ + alertState: AlertStates.ALERT, + }); + } else { + alertInstance.replaceState({ + alertState: AlertStates.OK, + }); + } + } catch (e) { + alertInstance.replaceState({ + alertState: AlertStates.ERROR, + }); + + throw new Error(e); + } + }; + +const getESQuery = ( + params: LogDocumentCountAlertParams, + sourceConfiguration: InfraSource['configuration'] +): object => { + const { timeSize, timeUnit, criteria } = params; + const interval = `${timeSize}${timeUnit}`; + const intervalAsSeconds = getIntervalInSeconds(interval); + const to = Date.now(); + const from = to - intervalAsSeconds * 1000; + + const rangeFilters = [ + { + range: { + [sourceConfiguration.fields.timestamp]: { + gte: from, + lte: to, + format: 'epoch_millis', + }, + }, + }, + ]; + + const positiveComparators = getPositiveComparators(); + const negativeComparators = getNegativeComparators(); + const positiveCriteria = criteria.filter(criterion => + positiveComparators.includes(criterion.comparator) + ); + const negativeCriteria = criteria.filter(criterion => + negativeComparators.includes(criterion.comparator) + ); + // Positive assertions (things that "must" match) + const mustFilters = buildFiltersForCriteria(positiveCriteria); + // Negative assertions (things that "must not" match) + const mustNotFilters = buildFiltersForCriteria(negativeCriteria); + + const query = { + query: { + bool: { + filter: [...rangeFilters], + ...(mustFilters.length > 0 && { must: mustFilters }), + ...(mustNotFilters.length > 0 && { must_not: mustNotFilters }), + }, + }, + }; + + return query; +}; + +type SupportedESQueryTypes = 'term' | 'match' | 'match_phrase' | 'range'; +type Filter = { + [key in SupportedESQueryTypes]?: object; +}; + +const buildFiltersForCriteria = (criteria: LogDocumentCountAlertParams['criteria']) => { + let filters: Filter[] = []; + + criteria.forEach(criterion => { + const criterionQuery = buildCriterionQuery(criterion); + if (criterionQuery) { + filters = [...filters, criterionQuery]; + } + }); + return filters; +}; + +const buildCriterionQuery = (criterion: Criterion): Filter | undefined => { + const { field, value, comparator } = criterion; + + const queryType = getQueryMappingForComparator(comparator); + + switch (queryType) { + case 'term': + return { + term: { + [field]: { + value, + }, + }, + }; + break; + case 'match': { + return { + match: { + [field]: value, + }, + }; + } + case 'match_phrase': { + return { + match_phrase: { + [field]: value, + }, + }; + } + case 'range': { + const comparatorToRangePropertyMapping: { + [key: string]: string; + } = { + [Comparator.LT]: 'lt', + [Comparator.LT_OR_EQ]: 'lte', + [Comparator.GT]: 'gt', + [Comparator.GT_OR_EQ]: 'gte', + }; + + const rangeProperty = comparatorToRangePropertyMapping[comparator]; + + return { + range: { + [field]: { + [rangeProperty]: value, + }, + }, + }; + } + default: { + return undefined; + } + } +}; + +const getPositiveComparators = () => { + return [ + Comparator.GT, + Comparator.GT_OR_EQ, + Comparator.LT, + Comparator.LT_OR_EQ, + Comparator.EQ, + Comparator.MATCH, + Comparator.MATCH_PHRASE, + ]; +}; + +const getNegativeComparators = () => { + return [Comparator.NOT_EQ, Comparator.NOT_MATCH, Comparator.NOT_MATCH_PHRASE]; +}; + +const queryMappings: { + [key: string]: string; +} = { + [Comparator.GT]: 'range', + [Comparator.GT_OR_EQ]: 'range', + [Comparator.LT]: 'range', + [Comparator.LT_OR_EQ]: 'range', + [Comparator.EQ]: 'term', + [Comparator.MATCH]: 'match', + [Comparator.MATCH_PHRASE]: 'match_phrase', + [Comparator.NOT_EQ]: 'term', + [Comparator.NOT_MATCH]: 'match', + [Comparator.NOT_MATCH_PHRASE]: 'match_phrase', +}; + +const getQueryMappingForComparator = (comparator: Comparator) => { + return queryMappings[comparator]; +}; + +const getResults = async ( + query: object, + index: string, + callCluster: AlertServices['callCluster'] +) => { + return await callCluster('count', { + body: query, + index, + }); +}; + +const createConditionsMessage = (criteria: LogDocumentCountAlertParams['criteria']) => { + const parts = criteria.map((criterion, index) => { + const { field, comparator, value } = criterion; + return `${index === 0 ? '' : 'and'} ${field} ${comparator} ${value}`; + }); + return parts.join(' '); +}; + +// When the Alerting plugin implements support for multiple action groups, add additional +// action groups here to send different messages, e.g. a recovery notification +export const FIRED_ACTIONS = { + id: 'logs.threshold.fired', + name: i18n.translate('xpack.infra.logs.alerting.threshold.fired', { + defaultMessage: 'Fired', + }), +}; diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts new file mode 100644 index 0000000000000..04207a4233dfd --- /dev/null +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts @@ -0,0 +1,90 @@ +/* + * 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 uuid from 'uuid'; +import { i18n } from '@kbn/i18n'; +import { schema } from '@kbn/config-schema'; +import { PluginSetupContract } from '../../../../../alerting/server'; +import { createLogThresholdExecutor, FIRED_ACTIONS } from './log_threshold_executor'; +import { + LOG_DOCUMENT_COUNT_ALERT_TYPE_ID, + Comparator, +} from '../../../../common/alerting/logs/types'; +import { InfraBackendLibs } from '../../infra_types'; + +const documentCountActionVariableDescription = i18n.translate( + 'xpack.infra.logs.alerting.threshold.documentCountActionVariableDescription', + { + defaultMessage: 'The number of log entries that matched the conditions provided', + } +); + +const conditionsActionVariableDescription = i18n.translate( + 'xpack.infra.logs.alerting.threshold.conditionsActionVariableDescription', + { + defaultMessage: 'The conditions that log entries needed to fulfill', + } +); + +const countSchema = schema.object({ + value: schema.number(), + comparator: schema.oneOf([ + schema.literal(Comparator.GT), + schema.literal(Comparator.LT), + schema.literal(Comparator.GT_OR_EQ), + schema.literal(Comparator.LT_OR_EQ), + schema.literal(Comparator.EQ), + ]), +}); + +const criteriaSchema = schema.object({ + field: schema.string(), + comparator: schema.oneOf([ + schema.literal(Comparator.GT), + schema.literal(Comparator.LT), + schema.literal(Comparator.GT_OR_EQ), + schema.literal(Comparator.LT_OR_EQ), + schema.literal(Comparator.EQ), + schema.literal(Comparator.NOT_EQ), + schema.literal(Comparator.MATCH), + schema.literal(Comparator.NOT_MATCH), + ]), + value: schema.oneOf([schema.number(), schema.string()]), +}); + +export async function registerLogThresholdAlertType( + alertingPlugin: PluginSetupContract, + libs: InfraBackendLibs +) { + if (!alertingPlugin) { + throw new Error( + 'Cannot register log threshold alert type. Both the actions and alerting plugins need to be enabled.' + ); + } + + const alertUUID = uuid.v4(); + + alertingPlugin.registerType({ + id: LOG_DOCUMENT_COUNT_ALERT_TYPE_ID, + name: 'Log threshold', + validate: { + params: schema.object({ + count: countSchema, + criteria: schema.arrayOf(criteriaSchema), + timeUnit: schema.string(), + timeSize: schema.number(), + }), + }, + defaultActionGroupId: FIRED_ACTIONS.id, + actionGroups: [FIRED_ACTIONS], + executor: createLogThresholdExecutor(alertUUID, libs), + actionVariables: { + context: [ + { name: 'matchingDocuments', description: documentCountActionVariableDescription }, + { name: 'conditions', description: conditionsActionVariableDescription }, + ], + }, + }); +} diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts index b697af4fa4c3b..3415ae9873bfb 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts @@ -9,6 +9,7 @@ import { schema } from '@kbn/config-schema'; import { PluginSetupContract } from '../../../../../alerting/server'; import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/http_api/metrics_explorer'; import { createMetricThresholdExecutor, FIRED_ACTIONS } from './metric_threshold_executor'; +import { InfraBackendLibs } from '../../infra_types'; import { METRIC_THRESHOLD_ALERT_TYPE_ID, Comparator } from './types'; const oneOfLiterals = (arrayOfLiterals: Readonly) => @@ -17,7 +18,10 @@ const oneOfLiterals = (arrayOfLiterals: Readonly) => arrayOfLiterals.includes(value) ? undefined : `must be one of ${arrayOfLiterals.join(' | ')}`, }); -export async function registerMetricThresholdAlertType(alertingPlugin: PluginSetupContract) { +export async function registerMetricThresholdAlertType( + alertingPlugin: PluginSetupContract, + libs: InfraBackendLibs +) { if (!alertingPlugin) { throw new Error( 'Cannot register metric threshold alert type. Both the actions and alerting plugins need to be enabled.' diff --git a/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts b/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts index 6ec6f31256b78..9760873ff7478 100644 --- a/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts +++ b/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts @@ -6,13 +6,15 @@ import { PluginSetupContract } from '../../../../alerting/server'; import { registerMetricThresholdAlertType } from './metric_threshold/register_metric_threshold_alert_type'; +import { registerLogThresholdAlertType } from './log_threshold/register_log_threshold_alert_type'; +import { InfraBackendLibs } from '../infra_types'; -const registerAlertTypes = (alertingPlugin: PluginSetupContract) => { +const registerAlertTypes = (alertingPlugin: PluginSetupContract, libs: InfraBackendLibs) => { if (alertingPlugin) { - const registerFns = [registerMetricThresholdAlertType]; + const registerFns = [registerMetricThresholdAlertType, registerLogThresholdAlertType]; registerFns.forEach(fn => { - fn(alertingPlugin); + fn(alertingPlugin, libs); }); } }; diff --git a/x-pack/plugins/infra/server/lib/domains/fields_domain.ts b/x-pack/plugins/infra/server/lib/domains/fields_domain.ts index d2e151ca2c3f5..b6837e5b769a6 100644 --- a/x-pack/plugins/infra/server/lib/domains/fields_domain.ts +++ b/x-pack/plugins/infra/server/lib/domains/fields_domain.ts @@ -21,7 +21,7 @@ export class InfraFieldsDomain { indexType: InfraIndexType ): Promise { const { configuration } = await this.libs.sources.getSourceConfiguration( - requestContext, + requestContext.core.savedObjects.client, sourceId ); const includeMetricIndices = [InfraIndexType.ANY, InfraIndexType.METRICS].includes(indexType); diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts index 528b9a69327fa..07bc965dda77a 100644 --- a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts +++ b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts @@ -113,7 +113,7 @@ export class InfraLogEntriesDomain { params: LogEntriesParams ): Promise { const { configuration } = await this.libs.sources.getSourceConfiguration( - requestContext, + requestContext.core.savedObjects.client, sourceId ); @@ -156,14 +156,7 @@ export class InfraLogEntriesDomain { } } ), - context: FIELDS_FROM_CONTEXT.reduce((ctx, field) => { - // Users might have different types here in their mappings. - const value = doc.fields[field]; - if (typeof value === 'string') { - ctx[field] = value; - } - return ctx; - }, {}), + context: getContextFromDoc(doc), }; }); @@ -179,7 +172,7 @@ export class InfraLogEntriesDomain { filterQuery?: LogEntryQuery ): Promise { const { configuration } = await this.libs.sources.getSourceConfiguration( - requestContext, + requestContext.core.savedObjects.client, sourceId ); const dateRangeBuckets = await this.adapter.getContainedLogSummaryBuckets( @@ -203,7 +196,7 @@ export class InfraLogEntriesDomain { filterQuery?: LogEntryQuery ): Promise { const { configuration } = await this.libs.sources.getSourceConfiguration( - requestContext, + requestContext.core.savedObjects.client, sourceId ); const messageFormattingRules = compileFormattingRules( @@ -352,3 +345,20 @@ const createHighlightQueryDsl = (phrase: string, fields: string[]) => ({ type: 'phrase', }, }); + +const getContextFromDoc = (doc: LogEntryDocument): LogEntry['context'] => { + // Get all context fields, then test for the presence and type of the ones that go together + const containerId = doc.fields['container.id']; + const hostName = doc.fields['host.name']; + const logFilePath = doc.fields['log.file.path']; + + if (typeof containerId === 'string') { + return { 'container.id': containerId }; + } + + if (typeof hostName === 'string' && typeof logFilePath === 'string') { + return { 'host.name': hostName, 'log.file.path': logFilePath }; + } + + return {}; +}; diff --git a/x-pack/plugins/infra/server/lib/source_status.ts b/x-pack/plugins/infra/server/lib/source_status.ts index 1f0845b6b223f..9bb953845e5a1 100644 --- a/x-pack/plugins/infra/server/lib/source_status.ts +++ b/x-pack/plugins/infra/server/lib/source_status.ts @@ -18,7 +18,7 @@ export class InfraSourceStatus { sourceId: string ): Promise { const sourceConfiguration = await this.libs.sources.getSourceConfiguration( - requestContext, + requestContext.core.savedObjects.client, sourceId ); const indexNames = await this.adapter.getIndexNames( @@ -32,7 +32,7 @@ export class InfraSourceStatus { sourceId: string ): Promise { const sourceConfiguration = await this.libs.sources.getSourceConfiguration( - requestContext, + requestContext.core.savedObjects.client, sourceId ); const indexNames = await this.adapter.getIndexNames( @@ -46,7 +46,7 @@ export class InfraSourceStatus { sourceId: string ): Promise { const sourceConfiguration = await this.libs.sources.getSourceConfiguration( - requestContext, + requestContext.core.savedObjects.client, sourceId ); const hasAlias = await this.adapter.hasAlias( @@ -60,7 +60,7 @@ export class InfraSourceStatus { sourceId: string ): Promise { const sourceConfiguration = await this.libs.sources.getSourceConfiguration( - requestContext, + requestContext.core.savedObjects.client, sourceId ); const hasAlias = await this.adapter.hasAlias( @@ -74,7 +74,7 @@ export class InfraSourceStatus { sourceId: string ): Promise { const sourceConfiguration = await this.libs.sources.getSourceConfiguration( - requestContext, + requestContext.core.savedObjects.client, sourceId ); const hasIndices = await this.adapter.hasIndices( @@ -88,7 +88,7 @@ export class InfraSourceStatus { sourceId: string ): Promise { const sourceConfiguration = await this.libs.sources.getSourceConfiguration( - requestContext, + requestContext.core.savedObjects.client, sourceId ); const hasIndices = await this.adapter.hasIndices( diff --git a/x-pack/plugins/infra/server/lib/sources/sources.test.ts b/x-pack/plugins/infra/server/lib/sources/sources.test.ts index 4a83ca730ff83..57efb0f676b2f 100644 --- a/x-pack/plugins/infra/server/lib/sources/sources.test.ts +++ b/x-pack/plugins/infra/server/lib/sources/sources.test.ts @@ -29,7 +29,9 @@ describe('the InfraSources lib', () => { }, }); - expect(await sourcesLib.getSourceConfiguration(request, 'TEST_ID')).toMatchObject({ + expect( + await sourcesLib.getSourceConfiguration(request.core.savedObjects.client, 'TEST_ID') + ).toMatchObject({ id: 'TEST_ID', version: 'foo', updatedAt: 946684800000, @@ -74,7 +76,9 @@ describe('the InfraSources lib', () => { }, }); - expect(await sourcesLib.getSourceConfiguration(request, 'TEST_ID')).toMatchObject({ + expect( + await sourcesLib.getSourceConfiguration(request.core.savedObjects.client, 'TEST_ID') + ).toMatchObject({ id: 'TEST_ID', version: 'foo', updatedAt: 946684800000, @@ -104,7 +108,9 @@ describe('the InfraSources lib', () => { attributes: {}, }); - expect(await sourcesLib.getSourceConfiguration(request, 'TEST_ID')).toMatchObject({ + expect( + await sourcesLib.getSourceConfiguration(request.core.savedObjects.client, 'TEST_ID') + ).toMatchObject({ id: 'TEST_ID', version: 'foo', updatedAt: 946684800000, diff --git a/x-pack/plugins/infra/server/lib/sources/sources.ts b/x-pack/plugins/infra/server/lib/sources/sources.ts index 99e062aa49ccf..0368c7bfd6db8 100644 --- a/x-pack/plugins/infra/server/lib/sources/sources.ts +++ b/x-pack/plugins/infra/server/lib/sources/sources.ts @@ -9,7 +9,7 @@ import { failure } from 'io-ts/lib/PathReporter'; import { identity, constant } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; import { map, fold } from 'fp-ts/lib/Either'; -import { RequestHandlerContext } from 'src/core/server'; +import { RequestHandlerContext, SavedObjectsClientContract } from 'src/core/server'; import { defaultSourceConfiguration } from './defaults'; import { NotFoundError } from './errors'; import { infraSourceConfigurationSavedObjectType } from './saved_object_mappings'; @@ -37,7 +37,7 @@ export class InfraSources { } public async getSourceConfiguration( - requestContext: RequestHandlerContext, + savedObjectsClient: SavedObjectsClientContract, sourceId: string ): Promise { const staticDefaultSourceConfiguration = await this.getStaticDefaultSourceConfiguration(); @@ -55,7 +55,7 @@ export class InfraSources { })) .catch(err => err instanceof NotFoundError - ? this.getSavedSourceConfiguration(requestContext, sourceId).then(result => ({ + ? this.getSavedSourceConfiguration(savedObjectsClient, sourceId).then(result => ({ ...result, configuration: mergeSourceConfiguration( staticDefaultSourceConfiguration, @@ -65,7 +65,7 @@ export class InfraSources { : Promise.reject(err) ) .catch(err => - requestContext.core.savedObjects.client.errors.isNotFoundError(err) + savedObjectsClient.errors.isNotFoundError(err) ? Promise.resolve({ id: sourceId, version: undefined, @@ -136,7 +136,10 @@ export class InfraSources { ) { const staticDefaultSourceConfiguration = await this.getStaticDefaultSourceConfiguration(); - const { configuration, version } = await this.getSourceConfiguration(requestContext, sourceId); + const { configuration, version } = await this.getSourceConfiguration( + requestContext.core.savedObjects.client, + sourceId + ); const updatedSourceConfigurationAttributes = mergeSourceConfiguration( configuration, @@ -199,10 +202,10 @@ export class InfraSources { } private async getSavedSourceConfiguration( - requestContext: RequestHandlerContext, + savedObjectsClient: SavedObjectsClientContract, sourceId: string ) { - const savedObject = await requestContext.core.savedObjects.client.get( + const savedObject = await savedObjectsClient.get( infraSourceConfigurationSavedObjectType, sourceId ); diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index e3804078604cc..d4dfa60ac67a0 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -147,7 +147,7 @@ export class InfraServerPlugin { ]); initInfraServer(this.libs); - registerAlertTypes(plugins.alerting); + registerAlertTypes(plugins.alerting, this.libs); // Telemetry UsageCollector.registerUsageCollector(plugins.usageCollection); diff --git a/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts b/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts index 7e9b7ada28c8e..687e368736a41 100644 --- a/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts +++ b/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts @@ -39,7 +39,7 @@ export const initInventoryMetaRoute = (libs: InfraBackendLibs) => { ); const { configuration } = await libs.sources.getSourceConfiguration( - requestContext, + requestContext.core.savedObjects.client, sourceId ); const awsMetadata = await getCloudMetadata( diff --git a/x-pack/plugins/infra/server/routes/log_entries/item.ts b/x-pack/plugins/infra/server/routes/log_entries/item.ts index 3a6bdaf3804e3..85dba8f598a89 100644 --- a/x-pack/plugins/infra/server/routes/log_entries/item.ts +++ b/x-pack/plugins/infra/server/routes/log_entries/item.ts @@ -37,8 +37,9 @@ export const initLogEntriesItemRoute = ({ framework, sources, logEntries }: Infr ); const { id, sourceId } = payload; - const sourceConfiguration = (await sources.getSourceConfiguration(requestContext, sourceId)) - .configuration; + const sourceConfiguration = ( + await sources.getSourceConfiguration(requestContext.core.savedObjects.client, sourceId) + ).configuration; const logEntry = await logEntries.getLogItem(requestContext, id, sourceConfiguration); diff --git a/x-pack/plugins/infra/server/routes/metadata/index.ts b/x-pack/plugins/infra/server/routes/metadata/index.ts index c45f191b1130d..fe142aa93dcda 100644 --- a/x-pack/plugins/infra/server/routes/metadata/index.ts +++ b/x-pack/plugins/infra/server/routes/metadata/index.ts @@ -44,7 +44,7 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => { ); const { configuration } = await libs.sources.getSourceConfiguration( - requestContext, + requestContext.core.savedObjects.client, sourceId ); const metricsMetadata = await getMetricMetadata( diff --git a/x-pack/plugins/infra/server/routes/node_details/index.ts b/x-pack/plugins/infra/server/routes/node_details/index.ts index 36906f6f4125b..a457ccac2416c 100644 --- a/x-pack/plugins/infra/server/routes/node_details/index.ts +++ b/x-pack/plugins/infra/server/routes/node_details/index.ts @@ -37,7 +37,10 @@ export const initNodeDetailsRoute = (libs: InfraBackendLibs) => { NodeDetailsRequestRT.decode(request.body), fold(throwErrors(Boom.badRequest), identity) ); - const source = await libs.sources.getSourceConfiguration(requestContext, sourceId); + const source = await libs.sources.getSourceConfiguration( + requestContext.core.savedObjects.client, + sourceId + ); UsageCollector.countNode(nodeType); diff --git a/x-pack/plugins/infra/server/routes/snapshot/index.ts b/x-pack/plugins/infra/server/routes/snapshot/index.ts index e45b9884967d0..d1dc03893a0d9 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/index.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/index.ts @@ -42,7 +42,10 @@ export const initSnapshotRoute = (libs: InfraBackendLibs) => { SnapshotRequestRT.decode(request.body), fold(throwErrors(Boom.badRequest), identity) ); - const source = await libs.sources.getSourceConfiguration(requestContext, sourceId); + const source = await libs.sources.getSourceConfiguration( + requestContext.core.savedObjects.client, + sourceId + ); UsageCollector.countNode(nodeType); const options = { filterQuery: parseFilterQuery(filterQuery), diff --git a/x-pack/plugins/infra/server/routes/source/index.ts b/x-pack/plugins/infra/server/routes/source/index.ts index 2f29320d7bb81..62b7fd7ba902f 100644 --- a/x-pack/plugins/infra/server/routes/source/index.ts +++ b/x-pack/plugins/infra/server/routes/source/index.ts @@ -37,7 +37,10 @@ export const initSourceRoute = (libs: InfraBackendLibs) => { try { const { type, sourceId } = request.params; - const source = await libs.sources.getSourceConfiguration(requestContext, sourceId); + const source = await libs.sources.getSourceConfiguration( + requestContext.core.savedObjects.client, + sourceId + ); if (!source) { return response.notFound(); } diff --git a/x-pack/plugins/ingest_manager/README.md b/x-pack/plugins/ingest_manager/README.md index 9cd4821c2a727..0e7abcc3d74a9 100644 --- a/x-pack/plugins/ingest_manager/README.md +++ b/x-pack/plugins/ingest_manager/README.md @@ -1,60 +1,70 @@ # Ingest Manager + ## Plugin - - The plugin is disabled by default. See the TypeScript type for the [the available plugin configuration options](https://github.com/elastic/kibana/blob/master/x-pack/plugins/ingest_manager/common/types/index.ts#L9-L27) - - Setting `xpack.ingestManager.enabled=true` enables the plugin including the EPM and Fleet features. It also adds the `DATASOURCE_API_ROUTES` and `AGENT_CONFIG_API_ROUTES` values in [`common/constants/routes.ts`](./common/constants/routes.ts) - - Adding `--xpack.ingestManager.epm.enabled=false` will disable the EPM API & UI - - Adding `--xpack.ingestManager.fleet.enabled=false` will disable the Fleet API & UI - - [code for adding the routes](https://github.com/elastic/kibana/blob/1f27d349533b1c2865c10c45b2cf705d7416fb36/x-pack/plugins/ingest_manager/server/plugin.ts#L115-L133) - - [Integration tests](server/integration_tests/router.test.ts) - - Both EPM and Fleet require `ingestManager` be enabled. They are not standalone features. + +- The plugin is disabled by default. See the TypeScript type for the [the available plugin configuration options](https://github.com/elastic/kibana/blob/master/x-pack/plugins/ingest_manager/common/types/index.ts#L9-L27) +- Setting `xpack.ingestManager.enabled=true` enables the plugin including the EPM and Fleet features. It also adds the `DATASOURCE_API_ROUTES` and `AGENT_CONFIG_API_ROUTES` values in [`common/constants/routes.ts`](./common/constants/routes.ts) +- Adding `--xpack.ingestManager.epm.enabled=false` will disable the EPM API & UI +- Adding `--xpack.ingestManager.fleet.enabled=false` will disable the Fleet API & UI + - [code for adding the routes](https://github.com/elastic/kibana/blob/1f27d349533b1c2865c10c45b2cf705d7416fb36/x-pack/plugins/ingest_manager/server/plugin.ts#L115-L133) + - [Integration tests](server/integration_tests/router.test.ts) +- Both EPM and Fleet require `ingestManager` be enabled. They are not standalone features. ## Development ### Getting started -See the Kibana docs for [how to set up your dev environment](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#setting-up-your-development-environment), [run Elasticsearch](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#running-elasticsearch), and [start Kibana](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#running-kibana) +See the Kibana docs for [how to set up your dev environment](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#setting-up-your-development-environment), [run Elasticsearch](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#running-elasticsearch), and [start Kibana](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#running-kibana) One common development workflow is: - - Bootstrap Kibana - ``` - yarn kbn bootstrap - ``` - - Start Elasticsearch in one shell - ``` - yarn es snapshot -E xpack.security.authc.api_key.enabled=true - ``` - - Start Kibana in another shell - ``` - yarn start --xpack.ingestManager.enabled=true --no-base-path --xpack.endpoint.enabled=true - ``` + +- Bootstrap Kibana + ``` + yarn kbn bootstrap + ``` +- Start Elasticsearch in one shell + ``` + yarn es snapshot -E xpack.security.authc.api_key.enabled=true + ``` +- Start Kibana in another shell + ``` + yarn start --xpack.ingestManager.enabled=true --no-base-path --xpack.endpoint.enabled=true + ``` This plugin follows the `common`, `server`, `public` structure from the [Architecture Style Guide ](https://github.com/elastic/kibana/blob/master/style_guides/architecture_style_guide.md#file-and-folder-structure). We also follow the pattern of developing feature branches under your personal fork of Kibana. ### API Tests + #### Ingest & Fleet - 1. In one terminal, change to the `x-pack` directory and start the test server with - ``` - node scripts/functional_tests_server.js --config test/api_integration/config.ts - ``` - - 1. in a second terminal, run the tests from the Kibana root directory with - ``` - node scripts/functional_test_runner.js --config x-pack/test/api_integration/config.ts - ``` + +1. In one terminal, change to the `x-pack` directory and start the test server with + + ``` + node scripts/functional_tests_server.js --config test/api_integration/config.js + ``` + +1. in a second terminal, run the tests from the Kibana root directory with + ``` + node scripts/functional_test_runner.js --config x-pack/test/api_integration/config.js + ``` + #### EPM - 1. In one terminal, change to the `x-pack` directory and start the test server with - ``` - node scripts/functional_tests_server.js --config test/epm_api_integration/config.ts - ``` - 1. in a second terminal, run the tests from the Kibana root directory with - ``` - node scripts/functional_test_runner.js --config x-pack/test/epm_api_integration/config.ts - ``` +1. In one terminal, change to the `x-pack` directory and start the test server with + + ``` + node scripts/functional_tests_server.js --config test/epm_api_integration/config.ts + ``` - ### Staying up-to-date with `master` - While we're developing in the `feature-ingest` feature branch, here's is more information on keeping up to date with upstream kibana. +1. in a second terminal, run the tests from the Kibana root directory with + ``` + node scripts/functional_test_runner.js --config x-pack/test/epm_api_integration/config.ts + ``` + +### Staying up-to-date with `master` + +While we're developing in the `feature-ingest` feature branch, here's is more information on keeping up to date with upstream kibana.
merge upstream master into feature-ingest @@ -80,6 +90,7 @@ git push origin ## push your changes to upstream feature branch from the terminal; not GitHub UI git push upstream ``` +
See https://github.com/elastic/kibana/pull/37950 for an example. diff --git a/x-pack/plugins/ingest_manager/common/constants/agent.ts b/x-pack/plugins/ingest_manager/common/constants/agent.ts index 0b462fb4c0319..f3990ba78c539 100644 --- a/x-pack/plugins/ingest_manager/common/constants/agent.ts +++ b/x-pack/plugins/ingest_manager/common/constants/agent.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export const AGENT_SAVED_OBJECT_TYPE = 'agents'; -export const AGENT_EVENT_SAVED_OBJECT_TYPE = 'agent_events'; -export const AGENT_ACTION_SAVED_OBJECT_TYPE = 'agent_actions'; +export const AGENT_SAVED_OBJECT_TYPE = 'fleet-agents'; +export const AGENT_EVENT_SAVED_OBJECT_TYPE = 'fleet-agent-events'; +export const AGENT_ACTION_SAVED_OBJECT_TYPE = 'fleet-agent-actions'; export const AGENT_TYPE_PERMANENT = 'PERMANENT'; export const AGENT_TYPE_EPHEMERAL = 'EPHEMERAL'; diff --git a/x-pack/plugins/ingest_manager/common/constants/agent_config.ts b/x-pack/plugins/ingest_manager/common/constants/agent_config.ts index 337022e552278..c5067480fb953 100644 --- a/x-pack/plugins/ingest_manager/common/constants/agent_config.ts +++ b/x-pack/plugins/ingest_manager/common/constants/agent_config.ts @@ -5,7 +5,7 @@ */ import { AgentConfigStatus, DefaultPackages } from '../types'; -export const AGENT_CONFIG_SAVED_OBJECT_TYPE = 'agent_configs'; +export const AGENT_CONFIG_SAVED_OBJECT_TYPE = 'ingest-agent-configs'; export const DEFAULT_AGENT_CONFIG = { name: 'Default config', diff --git a/x-pack/plugins/ingest_manager/common/constants/datasource.ts b/x-pack/plugins/ingest_manager/common/constants/datasource.ts index 0ff472b2afeb0..08113cff53bda 100644 --- a/x-pack/plugins/ingest_manager/common/constants/datasource.ts +++ b/x-pack/plugins/ingest_manager/common/constants/datasource.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const DATASOURCE_SAVED_OBJECT_TYPE = 'datasources'; +export const DATASOURCE_SAVED_OBJECT_TYPE = 'ingest-datasources'; diff --git a/x-pack/plugins/ingest_manager/common/constants/enrollment_api_key.ts b/x-pack/plugins/ingest_manager/common/constants/enrollment_api_key.ts index f4a4bcde2f393..fd28b6632b15c 100644 --- a/x-pack/plugins/ingest_manager/common/constants/enrollment_api_key.ts +++ b/x-pack/plugins/ingest_manager/common/constants/enrollment_api_key.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE = 'enrollment_api_keys'; +export const ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE = 'fleet-enrollment-api-keys'; diff --git a/x-pack/plugins/ingest_manager/common/constants/epm.ts b/x-pack/plugins/ingest_manager/common/constants/epm.ts index eb72c28e7bf39..4fb259609493d 100644 --- a/x-pack/plugins/ingest_manager/common/constants/epm.ts +++ b/x-pack/plugins/ingest_manager/common/constants/epm.ts @@ -4,5 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export const PACKAGES_SAVED_OBJECT_TYPE = 'epm-package'; +export const PACKAGES_SAVED_OBJECT_TYPE = 'epm-packages'; export const INDEX_PATTERN_SAVED_OBJECT_TYPE = 'index-pattern'; +export const DEFAULT_REGISTRY_URL = 'https://epr.elastic.co'; diff --git a/x-pack/plugins/ingest_manager/common/constants/index.ts b/x-pack/plugins/ingest_manager/common/constants/index.ts index 45d315e6d5664..6a2e559bbbe4f 100644 --- a/x-pack/plugins/ingest_manager/common/constants/index.ts +++ b/x-pack/plugins/ingest_manager/common/constants/index.ts @@ -12,3 +12,4 @@ export * from './datasource'; export * from './epm'; export * from './output'; export * from './enrollment_api_key'; +export * from './settings'; diff --git a/x-pack/plugins/ingest_manager/common/constants/output.ts b/x-pack/plugins/ingest_manager/common/constants/output.ts index 4c22d0e3fe7a3..ac2d6117be921 100644 --- a/x-pack/plugins/ingest_manager/common/constants/output.ts +++ b/x-pack/plugins/ingest_manager/common/constants/output.ts @@ -5,7 +5,7 @@ */ import { OutputType } from '../types'; -export const OUTPUT_SAVED_OBJECT_TYPE = 'outputs'; +export const OUTPUT_SAVED_OBJECT_TYPE = 'ingest-outputs'; export const DEFAULT_OUTPUT = { name: 'default', diff --git a/x-pack/plugins/ingest_manager/common/constants/routes.ts b/x-pack/plugins/ingest_manager/common/constants/routes.ts index 98ca52651a2ae..35e3be98e3982 100644 --- a/x-pack/plugins/ingest_manager/common/constants/routes.ts +++ b/x-pack/plugins/ingest_manager/common/constants/routes.ts @@ -48,6 +48,19 @@ export const AGENT_CONFIG_API_ROUTES = { FULL_INFO_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}/full`, }; +// Output API routes +export const OUTPUT_API_ROUTES = { + LIST_PATTERN: `${API_ROOT}/outputs`, + INFO_PATTERN: `${API_ROOT}/outputs/{outputId}`, + UPDATE_PATTERN: `${API_ROOT}/outputs/{outputId}`, +}; + +// Settings API routes +export const SETTINGS_API_ROUTES = { + INFO_PATTERN: `${API_ROOT}/settings`, + UPDATE_PATTERN: `${API_ROOT}/settings`, +}; + // Agent API routes export const AGENT_API_ROUTES = { LIST_PATTERN: `${FLEET_API_ROOT}/agents`, diff --git a/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/index.ts b/x-pack/plugins/ingest_manager/common/constants/settings.ts similarity index 76% rename from x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/index.ts rename to x-pack/plugins/ingest_manager/common/constants/settings.ts index 0743e443955f4..a9e7f1df4119c 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/license_pre_routing_factory/index.ts +++ b/x-pack/plugins/ingest_manager/common/constants/settings.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { licensePreRoutingFactory } from './license_pre_routing_factory'; +export const GLOBAL_SETTINGS_SAVED_OBJECT_TYPE = 'ingest_manager_settings'; diff --git a/x-pack/plugins/ingest_manager/common/services/agent_status.ts b/x-pack/plugins/ingest_manager/common/services/agent_status.ts index 7bbac55f11937..36e6e84b35bfe 100644 --- a/x-pack/plugins/ingest_manager/common/services/agent_status.ts +++ b/x-pack/plugins/ingest_manager/common/services/agent_status.ts @@ -8,20 +8,22 @@ import { AGENT_TYPE_TEMPORARY, AGENT_POLLING_THRESHOLD_MS, AGENT_TYPE_PERMANENT, + AGENT_SAVED_OBJECT_TYPE, } from '../constants'; export function buildKueryForOnlineAgents() { - return `agents.last_checkin >= now-${(3 * AGENT_POLLING_THRESHOLD_MS) / 1000}s`; + return `${AGENT_SAVED_OBJECT_TYPE}.last_checkin >= now-${(3 * AGENT_POLLING_THRESHOLD_MS) / + 1000}s`; } export function buildKueryForOfflineAgents() { - return `agents.type:${AGENT_TYPE_TEMPORARY} AND agents.last_checkin < now-${(3 * + return `${AGENT_SAVED_OBJECT_TYPE}.type:${AGENT_TYPE_TEMPORARY} AND ${AGENT_SAVED_OBJECT_TYPE}.last_checkin < now-${(3 * AGENT_POLLING_THRESHOLD_MS) / 1000}s`; } export function buildKueryForErrorAgents() { - return `agents.type:${AGENT_TYPE_PERMANENT} AND agents.last_checkin < now-${(4 * + return `${AGENT_SAVED_OBJECT_TYPE}.type:${AGENT_TYPE_PERMANENT} AND ${AGENT_SAVED_OBJECT_TYPE}.last_checkin < now-${(4 * AGENT_POLLING_THRESHOLD_MS) / 1000}s`; } diff --git a/x-pack/plugins/ingest_manager/common/services/routes.ts b/x-pack/plugins/ingest_manager/common/services/routes.ts index 46b76d886f3cd..1a1bd7c65aa25 100644 --- a/x-pack/plugins/ingest_manager/common/services/routes.ts +++ b/x-pack/plugins/ingest_manager/common/services/routes.ts @@ -13,6 +13,8 @@ import { AGENT_API_ROUTES, ENROLLMENT_API_KEY_ROUTES, SETUP_API_ROUTE, + OUTPUT_API_ROUTES, + SETTINGS_API_ROUTES, } from '../constants'; export const epmRouteService = { @@ -112,6 +114,18 @@ export const agentRouteService = { getStatusPath: () => AGENT_API_ROUTES.STATUS_PATTERN, }; +export const outputRoutesService = { + getInfoPath: (outputId: string) => OUTPUT_API_ROUTES.INFO_PATTERN.replace('{outputId}', outputId), + getUpdatePath: (outputId: string) => + OUTPUT_API_ROUTES.UPDATE_PATTERN.replace('{outputId}', outputId), + getListPath: () => OUTPUT_API_ROUTES.LIST_PATTERN, +}; + +export const settingsRoutesService = { + getInfoPath: () => SETTINGS_API_ROUTES.INFO_PATTERN, + getUpdatePath: () => SETTINGS_API_ROUTES.UPDATE_PATTERN, +}; + export const enrollmentAPIKeyRouteService = { getListPath: () => ENROLLMENT_API_KEY_ROUTES.LIST_PATTERN, getCreatePath: () => ENROLLMENT_API_KEY_ROUTES.CREATE_PATTERN, diff --git a/x-pack/plugins/ingest_manager/common/types/index.ts b/x-pack/plugins/ingest_manager/common/types/index.ts index 42f7a9333118e..748bb14d2d35d 100644 --- a/x-pack/plugins/ingest_manager/common/types/index.ts +++ b/x-pack/plugins/ingest_manager/common/types/index.ts @@ -10,7 +10,7 @@ export interface IngestManagerConfigType { enabled: boolean; epm: { enabled: boolean; - registryUrl: string; + registryUrl?: string; }; fleet: { enabled: boolean; diff --git a/x-pack/plugins/ingest_manager/common/types/models/index.ts b/x-pack/plugins/ingest_manager/common/types/models/index.ts index f73ab7af636a9..2310fdd54a719 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/index.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/index.ts @@ -11,3 +11,4 @@ export * from './data_stream'; export * from './output'; export * from './epm'; export * from './enrollment_api_key'; +export * from './settings'; diff --git a/x-pack/plugins/ingest_manager/common/types/models/settings.ts b/x-pack/plugins/ingest_manager/common/types/models/settings.ts new file mode 100644 index 0000000000000..2921808230b47 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/types/models/settings.ts @@ -0,0 +1,19 @@ +/* + * 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 { SavedObjectAttributes } from 'src/core/public'; + +interface BaseSettings { + agent_auto_upgrade?: boolean; + package_auto_upgrade?: boolean; + kibana_url?: string; + kibana_ca_sha256?: string; +} + +export interface Settings extends BaseSettings { + id: string; +} + +export interface SettingsSOAttributes extends BaseSettings, SavedObjectAttributes {} diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/index.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/index.ts index c1805023f497a..763fb7d820b2a 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/index.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/index.ts @@ -12,3 +12,5 @@ export * from './fleet_setup'; export * from './epm'; export * from './enrollment_api_key'; export * from './install_script'; +export * from './output'; +export * from './settings'; diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/output.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/output.ts new file mode 100644 index 0000000000000..4162060363381 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/output.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Output } from '../models'; + +export interface GetOneOutputResponse { + item: Output; + success: boolean; +} + +export interface GetOneOutputRequest { + params: { + outputId: string; + }; +} + +export interface PutOutputRequest { + params: { + outputId: string; + }; + body: { + hosts?: string[]; + ca_sha256?: string; + }; +} + +export interface PutOutputResponse { + item: Output; + success: boolean; +} + +export interface GetOutputsResponse { + items: Output[]; + total: number; + page: number; + perPage: number; + success: boolean; +} diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/settings.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/settings.ts new file mode 100644 index 0000000000000..c02a5e5878ee9 --- /dev/null +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/settings.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Settings } from '../models'; + +export interface GetSettingsResponse { + item: Settings; + success: boolean; +} + +export interface PutSettingsRequest { + body: Partial>; +} + +export interface PutSettingsResponse { + item: Settings; + success: boolean; +} diff --git a/x-pack/plugins/ingest_manager/dev_docs/api/agents_list.md b/x-pack/plugins/ingest_manager/dev_docs/api/agents_list.md index 38f80a8bdc022..fdb54411f8610 100644 --- a/x-pack/plugins/ingest_manager/dev_docs/api/agents_list.md +++ b/x-pack/plugins/ingest_manager/dev_docs/api/agents_list.md @@ -18,5 +18,5 @@ ## Example ```js -GET /api/ingest_manager/fleet/agents?kuery=agents.last_checkin:2019-10-01T13:42:54.323Z +GET /api/ingest_manager/fleet/agents?kuery=fleet-agents.last_checkin:2019-10-01T13:42:54.323Z ``` diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts index bdc8f350f7108..b0b4e79cece79 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/index.ts @@ -7,3 +7,4 @@ export { Loading } from './loading'; export { Error } from './error'; export { Header, HeaderProps } from './header'; export { AlphaMessaging } from './alpha_messaging'; +export * from './settings_flyout'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/search_bar.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/search_bar.tsx index 41c24dadba068..4429b9d8e6b82 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/search_bar.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/search_bar.tsx @@ -8,12 +8,11 @@ import React, { useState, useEffect } from 'react'; import { IFieldType } from 'src/plugins/data/public'; // @ts-ignore import { EuiSuggest, EuiSuggestItemProps } from '@elastic/eui'; -import { useDebounce } from '../hooks'; -import { useStartDeps } from '../hooks/use_deps'; -import { INDEX_NAME } from '../constants'; +import { useDebounce, useStartDeps } from '../hooks'; +import { INDEX_NAME, AGENT_SAVED_OBJECT_TYPE } from '../constants'; const DEBOUNCE_SEARCH_MS = 150; -const HIDDEN_FIELDS = ['agents.actions']; +const HIDDEN_FIELDS = [`${AGENT_SAVED_OBJECT_TYPE}.actions`]; interface Suggestion { label: string; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx new file mode 100644 index 0000000000000..92146e9ee5679 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx @@ -0,0 +1,240 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiTitle, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiSpacer, + EuiButton, + EuiFlyoutFooter, + EuiForm, + EuiFormRow, + EuiFieldText, + EuiRadioGroup, + EuiComboBox, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiText } from '@elastic/eui'; +import { useInput, useComboInput, useCore, useGetSettings, sendPutSettings } from '../hooks'; +import { useGetOutputs, sendPutOutput } from '../hooks/use_request/outputs'; + +interface Props { + onClose: () => void; +} + +function useSettingsForm(outputId: string | undefined) { + const { notifications } = useCore(); + const kibanaUrlInput = useInput(); + const elasticsearchUrlInput = useComboInput([]); + + return { + onSubmit: async () => { + try { + if (!outputId) { + throw new Error('Unable to load outputs'); + } + await sendPutOutput(outputId, { + hosts: elasticsearchUrlInput.value, + }); + await sendPutSettings({ + kibana_url: kibanaUrlInput.value, + }); + } catch (error) { + notifications.toasts.addError(error, { + title: 'Error', + }); + } + notifications.toasts.addSuccess( + i18n.translate('xpack.ingestManager.settings.success.message', { + defaultMessage: 'Settings saved', + }) + ); + }, + inputs: { + kibanaUrl: kibanaUrlInput, + elasticsearchUrl: elasticsearchUrlInput, + }, + }; +} + +export const SettingFlyout: React.FunctionComponent = ({ onClose }) => { + const core = useCore(); + const settingsRequest = useGetSettings(); + const settings = settingsRequest?.data?.item; + const outputsRequest = useGetOutputs(); + const output = outputsRequest.data?.items?.[0]; + const { inputs, onSubmit } = useSettingsForm(output?.id); + + useEffect(() => { + if (output) { + inputs.elasticsearchUrl.setValue(output.hosts || []); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [output]); + + useEffect(() => { + if (settings) { + inputs.kibanaUrl.setValue( + settings.kibana_url || `${window.location.origin}${core.http.basePath.get()}` + ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [settings]); + + const body = ( + + {}} + legend={{ + children: ( + +

+ +

+
+ ), + }} + /> + + {}} + legend={{ + children: ( + +

+ +

+
+ ), + }} + /> + + +

+ +

+
+ + + + + + + + + + + + + + + + +
+ ); + + return ( + + + +

+ +

+
+
+ {body} + + + + + + + + + + + + + + +
+ ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts index 619d03651dd96..e9b736e379b58 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/constants/index.ts @@ -3,7 +3,16 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -export { PLUGIN_ID, EPM_API_ROUTES, AGENT_CONFIG_SAVED_OBJECT_TYPE } from '../../../../common'; +export { + PLUGIN_ID, + EPM_API_ROUTES, + AGENT_API_ROUTES, + AGENT_CONFIG_SAVED_OBJECT_TYPE, + AGENT_EVENT_SAVED_OBJECT_TYPE, + AGENT_SAVED_OBJECT_TYPE, + ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, + DATASOURCE_SAVED_OBJECT_TYPE, +} from '../../../../common'; export const BASE_PATH = '/app/ingestManager'; export const EPM_PATH = '/epm'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_input.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_input.ts index 4aa0ad7155d2f..c535dc899638d 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_input.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_input.ts @@ -20,5 +20,27 @@ export function useInput(defaultValue = '') { clear: () => { setValue(''); }, + setValue, + }; +} + +export function useComboInput(defaultValue = []) { + const [value, setValue] = React.useState(defaultValue); + + return { + props: { + selectedOptions: value.map((val: string) => ({ label: val })), + onCreateOption: (newVal: any) => { + setValue([...value, newVal]); + }, + onChange: (newVals: any[]) => { + setValue(newVals.map(val => val.label)); + }, + }, + value, + clear: () => { + setValue([]); + }, + setValue, }; } diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/index.ts index 084aba9a34309..c39d2a5860bf0 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/index.ts @@ -10,3 +10,5 @@ export * from './data_stream'; export * from './agents'; export * from './enrollment_api_keys'; export * from './epm'; +export * from './outputs'; +export * from './settings'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/outputs.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/outputs.ts new file mode 100644 index 0000000000000..e57256d33ab2f --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/outputs.ts @@ -0,0 +1,24 @@ +/* + * 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 { sendRequest, useRequest } from './use_request'; +import { outputRoutesService } from '../../services'; +import { PutOutputRequest, GetOutputsResponse } from '../../types'; + +export function useGetOutputs() { + return useRequest({ + method: 'get', + path: outputRoutesService.getListPath(), + }); +} + +export function sendPutOutput(outputId: string, body: PutOutputRequest['body']) { + return sendRequest({ + method: 'put', + path: outputRoutesService.getUpdatePath(outputId), + body, + }); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/settings.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/settings.ts new file mode 100644 index 0000000000000..45e4eccf6625e --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/settings.ts @@ -0,0 +1,24 @@ +/* + * 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 { sendRequest, useRequest } from './use_request'; +import { settingsRoutesService } from '../../services'; +import { PutSettingsResponse, PutSettingsRequest, GetSettingsResponse } from '../../types'; + +export function useGetSettings() { + return useRequest({ + method: 'get', + path: settingsRoutesService.getInfoPath(), + }); +} + +export function sendPutSettings(body: PutSettingsRequest['body']) { + return sendRequest({ + method: 'put', + path: settingsRoutesService.getUpdatePath(), + body, + }); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx index f1f9063de72f0..10245e73520f7 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx @@ -5,10 +5,10 @@ */ import React from 'react'; import styled from 'styled-components'; -import { EuiTabs, EuiTab, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; +import { EuiTabs, EuiTab, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { Section } from '../sections'; -import { AlphaMessaging } from '../components'; +import { AlphaMessaging, SettingFlyout } from '../components'; import { useLink, useConfig } from '../hooks'; import { EPM_PATH, FLEET_PATH, AGENT_CONFIG_PATH, DATA_STREAM_PATH } from '../constants'; @@ -35,59 +35,79 @@ const Nav = styled.nav` export const DefaultLayout: React.FunctionComponent = ({ section, children }) => { const { epm, fleet } = useConfig(); + + const [isSettingsFlyoutOpen, setIsSettingsFlyoutOpen] = React.useState(false); + return ( - - + {children} + + + ); }; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_delete_provider.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_delete_provider.tsx index b18349e078766..9ae8369abbd52 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_delete_provider.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_delete_provider.tsx @@ -8,6 +8,7 @@ import React, { Fragment, useRef, useState } from 'react'; import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { AGENT_SAVED_OBJECT_TYPE } from '../../../constants'; import { sendDeleteAgentConfigs, useCore, sendRequest } from '../../../hooks'; interface Props { @@ -122,7 +123,7 @@ export const AgentConfigDeleteProvider: React.FunctionComponent = ({ chil path: `/api/ingest_manager/fleet/agents`, method: 'get', query: { - kuery: `agents.config_id : (${agentConfigsToCheck.join(' or ')})`, + kuery: `${AGENT_SAVED_OBJECT_TYPE}.config_id : (${agentConfigsToCheck.join(' or ')})`, }, }); setAgentsCount(data?.total || 0); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/datasource_delete_provider.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/datasource_delete_provider.tsx index 089b0631c2090..df679d33e0324 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/datasource_delete_provider.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/datasource_delete_provider.tsx @@ -9,8 +9,8 @@ import { EuiCallOut, EuiConfirmModal, EuiOverlayMask, EuiSpacer } from '@elastic import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { useCore, sendRequest, sendDeleteDatasource, useConfig } from '../../../hooks'; -import { AGENT_API_ROUTES } from '../../../../../../common/constants'; -import { AgentConfig } from '../../../../../../common/types/models'; +import { AGENT_API_ROUTES, AGENT_SAVED_OBJECT_TYPE } from '../../../constants'; +import { AgentConfig } from '../../../types'; interface Props { agentConfig: AgentConfig; @@ -51,7 +51,7 @@ export const DatasourceDeleteProvider: React.FunctionComponent = ({ query: { page: 1, perPage: 1, - kuery: `agents.config_id : ${agentConfig.id}`, + kuery: `${AGENT_SAVED_OBJECT_TYPE}.config_id : ${agentConfig.id}`, }, }); setAgentsCount(data?.total || 0); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/settings_panel.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/settings_panel.tsx index f947466caf4b0..3589a1a9444e1 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/settings_panel.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/settings_panel.tsx @@ -8,10 +8,11 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { EuiSpacer } from '@elastic/eui'; -import { useGetPackageInstallStatus } from '../../hooks'; import { InstallStatus, PackageInfo } from '../../../../types'; -import { InstallationButton } from './installation_button'; import { useGetDatasources } from '../../../../hooks'; +import { DATASOURCE_SAVED_OBJECT_TYPE } from '../../../../constants'; +import { useGetPackageInstallStatus } from '../../hooks'; +import { InstallationButton } from './installation_button'; const NoteLabel = () => ( = ({ ag - + diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx index c79255104a030..23fe18b82468c 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx @@ -40,7 +40,11 @@ import { SearchBar } from '../../../components/search_bar'; import { AgentHealth } from '../components/agent_health'; import { AgentUnenrollProvider } from '../components/agent_unenroll_provider'; import { AgentStatusKueryHelper } from '../../../services'; -import { FLEET_AGENT_DETAIL_PATH, AGENT_CONFIG_DETAILS_PATH } from '../../../constants'; +import { + FLEET_AGENT_DETAIL_PATH, + AGENT_CONFIG_DETAILS_PATH, + AGENT_SAVED_OBJECT_TYPE, +} from '../../../constants'; const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({ overflow: 'hidden', @@ -180,7 +184,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { if (kuery) { kuery = `(${kuery}) and`; } - kuery = `${kuery} agents.config_id : (${selectedConfigs + kuery = `${kuery} ${AGENT_SAVED_OBJECT_TYPE}.config_id : (${selectedConfigs .map(config => `"${config}"`) .join(' or ')})`; } @@ -394,7 +398,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { }); setSearch(newSearch); }} - fieldPrefix="agents" + fieldPrefix={AGENT_SAVED_OBJECT_TYPE} /> diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx index 7520f88215efe..c11e3a49c7693 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/enrollment_token_list_page/index.tsx @@ -18,6 +18,7 @@ import { EuiText, } from '@elastic/eui'; import { FormattedMessage, FormattedDate } from '@kbn/i18n/react'; +import { ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE } from '../../../constants'; import { usePagination, useGetEnrollmentAPIKeys, @@ -29,7 +30,6 @@ import { import { EnrollmentAPIKey } from '../../../types'; import { SearchBar } from '../../../components/search_bar'; import { NewEnrollmentTokenFlyout } from './components/new_enrollment_key_flyout'; -import {} from '@elastic/eui'; import { ConfirmEnrollmentTokenDelete } from './components/confirm_delete_modal'; const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({ @@ -251,7 +251,7 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { }); setSearch(newSearch); }} - fieldPrefix="enrollment_api_keys" + fieldPrefix={ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE} /> diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts index 53dbe295718c5..e4791cc816d04 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts @@ -15,6 +15,8 @@ export { enrollmentAPIKeyRouteService, epmRouteService, setupRouteService, + outputRoutesService, + settingsRoutesService, packageToConfigDatasourceInputs, storedDatasourceToAgentDatasource, AgentStatusKueryHelper, diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts index 8ca1495a94071..2f78ecd1b085e 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts @@ -17,6 +17,7 @@ export { DatasourceInput, DatasourceInputStream, DatasourceConfigRecordEntry, + Output, DataStream, // API schemas - Agent Config GetAgentConfigsResponse, @@ -48,6 +49,14 @@ export { GetEnrollmentAPIKeysResponse, GetEnrollmentAPIKeysRequest, GetOneEnrollmentAPIKeyResponse, + // API schemas - Outputs + GetOutputsResponse, + PutOutputRequest, + PutOutputResponse, + // API schemas - Settings + GetSettingsResponse, + PutSettingsRequest, + PutSettingsResponse, // EPM types AssetReference, AssetsGroupedByServiceByType, diff --git a/x-pack/plugins/ingest_manager/server/constants/index.ts b/x-pack/plugins/ingest_manager/server/constants/index.ts index b2e72fefe5997..75c14ffc8fa84 100644 --- a/x-pack/plugins/ingest_manager/server/constants/index.ts +++ b/x-pack/plugins/ingest_manager/server/constants/index.ts @@ -19,7 +19,9 @@ export { FLEET_SETUP_API_ROUTES, ENROLLMENT_API_KEY_ROUTES, INSTALL_SCRIPT_API_ROUTES, + OUTPUT_API_ROUTES, SETUP_API_ROUTE, + SETTINGS_API_ROUTES, // Saved object types AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE, @@ -30,7 +32,9 @@ export { PACKAGES_SAVED_OBJECT_TYPE, INDEX_PATTERN_SAVED_OBJECT_TYPE, ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, + GLOBAL_SETTINGS_SAVED_OBJECT_TYPE as GLOBAL_SETTINGS_SAVED_OBJET_TYPE, // Defaults DEFAULT_AGENT_CONFIG, DEFAULT_OUTPUT, + DEFAULT_REGISTRY_URL, } from '../../common'; diff --git a/x-pack/plugins/ingest_manager/server/index.ts b/x-pack/plugins/ingest_manager/server/index.ts index 851a58f5adac2..951ff2337d8c7 100644 --- a/x-pack/plugins/ingest_manager/server/index.ts +++ b/x-pack/plugins/ingest_manager/server/index.ts @@ -22,7 +22,7 @@ export const config = { enabled: schema.boolean({ defaultValue: false }), epm: schema.object({ enabled: schema.boolean({ defaultValue: true }), - registryUrl: schema.uri({ defaultValue: 'https://epr-staging.elastic.co' }), + registryUrl: schema.maybe(schema.uri()), }), fleet: schema.object({ enabled: schema.boolean({ defaultValue: true }), @@ -43,13 +43,3 @@ export type IngestManagerConfigType = TypeOf; export const plugin = (initializerContext: PluginInitializerContext) => { return new IngestManagerPlugin(initializerContext); }; - -// Saved object information bootstrapped by legacy `ingest_manager` plugin -// TODO: Remove once saved object mappings can be done from NP -export { savedObjectMappings } from './saved_objects'; -export { - OUTPUT_SAVED_OBJECT_TYPE, - AGENT_CONFIG_SAVED_OBJECT_TYPE, - DATASOURCE_SAVED_OBJECT_TYPE, - PACKAGES_SAVED_OBJECT_TYPE, -} from './constants'; diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/ingest_manager/server/plugin.ts index 55aea4b1a4cdd..097825e0b69e1 100644 --- a/x-pack/plugins/ingest_manager/server/plugin.ts +++ b/x-pack/plugins/ingest_manager/server/plugin.ts @@ -12,7 +12,7 @@ import { PluginInitializerContext, SavedObjectsServiceStart, } from 'kibana/server'; -import { LicensingPluginSetup } from '../../licensing/server'; +import { LicensingPluginSetup, ILicense } from '../../licensing/server'; import { EncryptedSavedObjectsPluginStart, EncryptedSavedObjectsPluginSetup, @@ -29,7 +29,7 @@ import { AGENT_EVENT_SAVED_OBJECT_TYPE, ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, } from './constants'; -import { registerEncryptedSavedObjects } from './saved_objects'; +import { registerSavedObjects, registerEncryptedSavedObjects } from './saved_objects'; import { registerEPMRoutes, registerDatasourceRoutes, @@ -39,11 +39,18 @@ import { registerAgentRoutes, registerEnrollmentApiKeyRoutes, registerInstallScriptRoutes, + registerOutputRoutes, + registerSettingsRoutes, } from './routes'; import { IngestManagerConfigType } from '../common'; -import { appContextService, ESIndexPatternSavedObjectService } from './services'; -import { ESIndexPatternService, AgentService } from './services'; +import { + appContextService, + licenseService, + ESIndexPatternSavedObjectService, + ESIndexPatternService, + AgentService, +} from './services'; import { getAgentStatusById } from './services/agents'; export interface IngestManagerSetupDeps { @@ -90,6 +97,7 @@ export class IngestManagerPlugin IngestManagerSetupDeps, IngestManagerStartDeps > { + private licensing$!: Observable; private config$: Observable; private security: SecurityPluginSetup | undefined; @@ -98,10 +106,12 @@ export class IngestManagerPlugin } public async setup(core: CoreSetup, deps: IngestManagerSetupDeps) { + this.licensing$ = deps.licensing.license$; if (deps.security) { this.security = deps.security; } + registerSavedObjects(core.savedObjects); registerEncryptedSavedObjects(deps.encryptedSavedObjects); // Register feature @@ -142,6 +152,8 @@ export class IngestManagerPlugin // Register routes registerAgentConfigRoutes(router); registerDatasourceRoutes(router); + registerOutputRoutes(router); + registerSettingsRoutes(router); registerDataStreamRoutes(router); // Conditional routes @@ -173,6 +185,7 @@ export class IngestManagerPlugin config$: this.config$, savedObjects: core.savedObjects, }); + licenseService.start(this.licensing$); return { esIndexPatternService: new ESIndexPatternSavedObjectService(), agentService: { @@ -183,5 +196,6 @@ export class IngestManagerPlugin public async stop() { appContextService.stop(); + licenseService.stop(); } } diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts index 67f758c2c1263..42298960cc615 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts @@ -8,6 +8,7 @@ import { RequestHandler } from 'src/core/server'; import bluebird from 'bluebird'; import { appContextService, agentConfigService, datasourceService } from '../../services'; import { listAgents } from '../../services/agents'; +import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; import { GetAgentConfigsRequestSchema, GetOneAgentConfigRequestSchema, @@ -50,7 +51,7 @@ export const getAgentConfigsHandler: RequestHandler< showInactive: true, perPage: 0, page: 1, - kuery: `agents.config_id:${agentConfig.id}`, + kuery: `${AGENT_SAVED_OBJECT_TYPE}.config_id:${agentConfig.id}`, }).then(({ total: agentTotal }) => (agentConfig.agents = agentTotal)), { concurrency: 10 } ); diff --git a/x-pack/plugins/ingest_manager/server/routes/index.ts b/x-pack/plugins/ingest_manager/server/routes/index.ts index 8a186c5485024..3ce34d15de46c 100644 --- a/x-pack/plugins/ingest_manager/server/routes/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/index.ts @@ -11,3 +11,5 @@ export { registerRoutes as registerSetupRoutes } from './setup'; export { registerRoutes as registerAgentRoutes } from './agent'; export { registerRoutes as registerEnrollmentApiKeyRoutes } from './enrollment_api_key'; export { registerRoutes as registerInstallScriptRoutes } from './install_script'; +export { registerRoutes as registerOutputRoutes } from './output'; +export { registerRoutes as registerSettingsRoutes } from './settings'; diff --git a/x-pack/plugins/ingest_manager/server/routes/output/handler.ts b/x-pack/plugins/ingest_manager/server/routes/output/handler.ts new file mode 100644 index 0000000000000..cd35b2a43426c --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/routes/output/handler.ts @@ -0,0 +1,90 @@ +/* + * 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 { RequestHandler } from 'src/core/server'; +import { TypeOf } from '@kbn/config-schema'; +import { GetOneOutputRequestSchema, PutOutputRequestSchema } from '../../types'; +import { GetOneOutputResponse, GetOutputsResponse } from '../../../common'; +import { outputService } from '../../services/output'; + +export const getOutputsHandler: RequestHandler = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + try { + const outputs = await outputService.list(soClient); + + const body: GetOutputsResponse = { + items: outputs.items, + page: outputs.page, + perPage: outputs.perPage, + total: outputs.total, + success: true, + }; + + return response.ok({ body }); + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; + +export const getOneOuputHandler: RequestHandler> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + try { + const output = await outputService.get(soClient, request.params.outputId); + + const body: GetOneOutputResponse = { + item: output, + success: true, + }; + + return response.ok({ body }); + } catch (e) { + if (e.isBoom && e.output.statusCode === 404) { + return response.notFound({ + body: { message: `Output ${request.params.outputId} not found` }, + }); + } + + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; + +export const putOuputHandler: RequestHandler< + TypeOf, + undefined, + TypeOf +> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + try { + await outputService.update(soClient, request.params.outputId, request.body); + const output = await outputService.get(soClient, request.params.outputId); + + const body: GetOneOutputResponse = { + item: output, + success: true, + }; + + return response.ok({ body }); + } catch (e) { + if (e.isBoom && e.output.statusCode === 404) { + return response.notFound({ + body: { message: `Output ${request.params.outputId} not found` }, + }); + } + + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; diff --git a/x-pack/plugins/ingest_manager/server/routes/output/index.ts b/x-pack/plugins/ingest_manager/server/routes/output/index.ts new file mode 100644 index 0000000000000..139d11dba951a --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/routes/output/index.ts @@ -0,0 +1,41 @@ +/* + * 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 { IRouter } from 'src/core/server'; +import { PLUGIN_ID, OUTPUT_API_ROUTES } from '../../constants'; +import { getOneOuputHandler, getOutputsHandler, putOuputHandler } from './handler'; +import { + GetOneOutputRequestSchema, + GetOutputsRequestSchema, + PutOutputRequestSchema, +} from '../../types'; + +export const registerRoutes = (router: IRouter) => { + router.get( + { + path: OUTPUT_API_ROUTES.LIST_PATTERN, + validate: GetOutputsRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-read`] }, + }, + getOutputsHandler + ); + router.get( + { + path: OUTPUT_API_ROUTES.INFO_PATTERN, + validate: GetOneOutputRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-read`] }, + }, + getOneOuputHandler + ); + router.put( + { + path: OUTPUT_API_ROUTES.UPDATE_PATTERN, + validate: PutOutputRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-read`] }, + }, + putOuputHandler + ); +}; diff --git a/x-pack/plugins/ingest_manager/server/routes/settings/index.ts b/x-pack/plugins/ingest_manager/server/routes/settings/index.ts new file mode 100644 index 0000000000000..56e666056e8d0 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/routes/settings/index.ts @@ -0,0 +1,81 @@ +/* + * 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 { IRouter, RequestHandler } from 'src/core/server'; +import { TypeOf } from '@kbn/config-schema'; +import { PLUGIN_ID, SETTINGS_API_ROUTES } from '../../constants'; +import { PutSettingsRequestSchema, GetSettingsRequestSchema } from '../../types'; + +import { settingsService } from '../../services'; + +export const getSettingsHandler: RequestHandler = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + + try { + const settings = await settingsService.getSettings(soClient); + const body = { + success: true, + item: settings, + }; + return response.ok({ body }); + } catch (e) { + if (e.isBoom && e.output.statusCode === 404) { + return response.notFound({ + body: { message: `Setings not found` }, + }); + } + + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; + +export const putSettingsHandler: RequestHandler< + undefined, + undefined, + TypeOf +> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + try { + const settings = await settingsService.saveSettings(soClient, request.body); + const body = { + success: true, + item: settings, + }; + return response.ok({ body }); + } catch (e) { + if (e.isBoom && e.output.statusCode === 404) { + return response.notFound({ + body: { message: `Setings not found` }, + }); + } + + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; + +export const registerRoutes = (router: IRouter) => { + router.get( + { + path: SETTINGS_API_ROUTES.INFO_PATTERN, + validate: GetSettingsRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-read`] }, + }, + getSettingsHandler + ); + router.put( + { + path: SETTINGS_API_ROUTES.UPDATE_PATTERN, + validate: PutSettingsRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-all`] }, + }, + putSettingsHandler + ); +}; diff --git a/x-pack/plugins/ingest_manager/server/saved_objects.ts b/x-pack/plugins/ingest_manager/server/saved_objects.ts index 37a00228443e1..0b130e7b70101 100644 --- a/x-pack/plugins/ingest_manager/server/saved_objects.ts +++ b/x-pack/plugins/ingest_manager/server/saved_objects.ts @@ -3,6 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + +import { SavedObjectsServiceSetup, SavedObjectsType } from 'kibana/server'; +import { EncryptedSavedObjectsPluginSetup } from '../../encrypted_saved_objects/server'; import { OUTPUT_SAVED_OBJECT_TYPE, AGENT_CONFIG_SAVED_OBJECT_TYPE, @@ -12,157 +15,244 @@ import { AGENT_EVENT_SAVED_OBJECT_TYPE, AGENT_ACTION_SAVED_OBJECT_TYPE, ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, + GLOBAL_SETTINGS_SAVED_OBJET_TYPE, } from './constants'; -import { EncryptedSavedObjectsPluginSetup } from '../../encrypted_saved_objects/server'; /* - * Saved object mappings + * Saved object types and mappings * * Please update typings in `/common/types` if mappings are updated. */ -export const savedObjectMappings = { + +const savedObjectTypes: { [key: string]: SavedObjectsType } = { + [GLOBAL_SETTINGS_SAVED_OBJET_TYPE]: { + name: GLOBAL_SETTINGS_SAVED_OBJET_TYPE, + hidden: false, + namespaceType: 'agnostic', + management: { + importableAndExportable: false, + }, + mappings: { + properties: { + agent_auto_upgrade: { type: 'keyword' }, + package_auto_upgrade: { type: 'keyword' }, + kibana_url: { type: 'keyword' }, + kibana_ca_sha256: { type: 'keyword' }, + }, + }, + }, [AGENT_SAVED_OBJECT_TYPE]: { - properties: { - shared_id: { type: 'keyword' }, - type: { type: 'keyword' }, - active: { type: 'boolean' }, - enrolled_at: { type: 'date' }, - access_api_key_id: { type: 'keyword' }, - version: { type: 'keyword' }, - user_provided_metadata: { type: 'text' }, - local_metadata: { type: 'text' }, - config_id: { type: 'keyword' }, - last_updated: { type: 'date' }, - last_checkin: { type: 'date' }, - config_revision: { type: 'integer' }, - config_newest_revision: { type: 'integer' }, - default_api_key_id: { type: 'keyword' }, - default_api_key: { type: 'keyword' }, - updated_at: { type: 'date' }, - current_error_events: { type: 'text' }, + name: AGENT_SAVED_OBJECT_TYPE, + hidden: false, + namespaceType: 'agnostic', + management: { + importableAndExportable: false, + }, + mappings: { + properties: { + shared_id: { type: 'keyword' }, + type: { type: 'keyword' }, + active: { type: 'boolean' }, + enrolled_at: { type: 'date' }, + access_api_key_id: { type: 'keyword' }, + version: { type: 'keyword' }, + user_provided_metadata: { type: 'text' }, + local_metadata: { type: 'text' }, + config_id: { type: 'keyword' }, + last_updated: { type: 'date' }, + last_checkin: { type: 'date' }, + config_revision: { type: 'integer' }, + config_newest_revision: { type: 'integer' }, + default_api_key_id: { type: 'keyword' }, + default_api_key: { type: 'keyword' }, + updated_at: { type: 'date' }, + current_error_events: { type: 'text' }, + }, }, }, [AGENT_ACTION_SAVED_OBJECT_TYPE]: { - properties: { - agent_id: { type: 'keyword' }, - type: { type: 'keyword' }, - data: { type: 'binary' }, - sent_at: { type: 'date' }, - created_at: { type: 'date' }, + name: AGENT_ACTION_SAVED_OBJECT_TYPE, + hidden: false, + namespaceType: 'agnostic', + management: { + importableAndExportable: false, + }, + mappings: { + properties: { + agent_id: { type: 'keyword' }, + type: { type: 'keyword' }, + data: { type: 'binary' }, + sent_at: { type: 'date' }, + created_at: { type: 'date' }, + }, }, }, [AGENT_EVENT_SAVED_OBJECT_TYPE]: { - properties: { - type: { type: 'keyword' }, - subtype: { type: 'keyword' }, - agent_id: { type: 'keyword' }, - action_id: { type: 'keyword' }, - config_id: { type: 'keyword' }, - stream_id: { type: 'keyword' }, - timestamp: { type: 'date' }, - message: { type: 'text' }, - payload: { type: 'text' }, - data: { type: 'text' }, + name: AGENT_EVENT_SAVED_OBJECT_TYPE, + hidden: false, + namespaceType: 'agnostic', + management: { + importableAndExportable: false, + }, + mappings: { + properties: { + type: { type: 'keyword' }, + subtype: { type: 'keyword' }, + agent_id: { type: 'keyword' }, + action_id: { type: 'keyword' }, + config_id: { type: 'keyword' }, + stream_id: { type: 'keyword' }, + timestamp: { type: 'date' }, + message: { type: 'text' }, + payload: { type: 'text' }, + data: { type: 'text' }, + }, }, }, [AGENT_CONFIG_SAVED_OBJECT_TYPE]: { - properties: { - id: { type: 'keyword' }, - name: { type: 'text' }, - is_default: { type: 'boolean' }, - namespace: { type: 'keyword' }, - description: { type: 'text' }, - status: { type: 'keyword' }, - datasources: { type: 'keyword' }, - updated_on: { type: 'keyword' }, - updated_by: { type: 'keyword' }, - revision: { type: 'integer' }, + name: AGENT_CONFIG_SAVED_OBJECT_TYPE, + hidden: false, + namespaceType: 'agnostic', + management: { + importableAndExportable: false, + }, + mappings: { + properties: { + id: { type: 'keyword' }, + name: { type: 'text' }, + is_default: { type: 'boolean' }, + namespace: { type: 'keyword' }, + description: { type: 'text' }, + status: { type: 'keyword' }, + datasources: { type: 'keyword' }, + updated_on: { type: 'keyword' }, + updated_by: { type: 'keyword' }, + revision: { type: 'integer' }, + }, }, }, [ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE]: { - properties: { - name: { type: 'keyword' }, - type: { type: 'keyword' }, - api_key: { type: 'binary' }, - api_key_id: { type: 'keyword' }, - config_id: { type: 'keyword' }, - created_at: { type: 'date' }, - updated_at: { type: 'date' }, - expire_at: { type: 'date' }, - active: { type: 'boolean' }, + name: ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, + hidden: false, + namespaceType: 'agnostic', + management: { + importableAndExportable: false, + }, + mappings: { + properties: { + name: { type: 'keyword' }, + type: { type: 'keyword' }, + api_key: { type: 'binary' }, + api_key_id: { type: 'keyword' }, + config_id: { type: 'keyword' }, + created_at: { type: 'date' }, + updated_at: { type: 'date' }, + expire_at: { type: 'date' }, + active: { type: 'boolean' }, + }, }, }, [OUTPUT_SAVED_OBJECT_TYPE]: { - properties: { - name: { type: 'keyword' }, - type: { type: 'keyword' }, - is_default: { type: 'boolean' }, - hosts: { type: 'keyword' }, - ca_sha256: { type: 'keyword' }, - fleet_enroll_username: { type: 'binary' }, - fleet_enroll_password: { type: 'binary' }, - config: { type: 'flattened' }, + name: OUTPUT_SAVED_OBJECT_TYPE, + hidden: false, + namespaceType: 'agnostic', + management: { + importableAndExportable: false, + }, + mappings: { + properties: { + name: { type: 'keyword' }, + type: { type: 'keyword' }, + is_default: { type: 'boolean' }, + hosts: { type: 'keyword' }, + ca_sha256: { type: 'keyword' }, + fleet_enroll_username: { type: 'binary' }, + fleet_enroll_password: { type: 'binary' }, + config: { type: 'flattened' }, + }, }, }, [DATASOURCE_SAVED_OBJECT_TYPE]: { - properties: { - name: { type: 'keyword' }, - description: { type: 'text' }, - namespace: { type: 'keyword' }, - config_id: { type: 'keyword' }, - enabled: { type: 'boolean' }, - package: { - properties: { - name: { type: 'keyword' }, - title: { type: 'keyword' }, - version: { type: 'keyword' }, + name: DATASOURCE_SAVED_OBJECT_TYPE, + hidden: false, + namespaceType: 'agnostic', + management: { + importableAndExportable: false, + }, + mappings: { + properties: { + name: { type: 'keyword' }, + description: { type: 'text' }, + namespace: { type: 'keyword' }, + config_id: { type: 'keyword' }, + enabled: { type: 'boolean' }, + package: { + properties: { + name: { type: 'keyword' }, + title: { type: 'keyword' }, + version: { type: 'keyword' }, + }, }, - }, - output_id: { type: 'keyword' }, - inputs: { - type: 'nested', - properties: { - type: { type: 'keyword' }, - enabled: { type: 'boolean' }, - processors: { type: 'keyword' }, - config: { type: 'flattened' }, - streams: { - type: 'nested', - properties: { - id: { type: 'keyword' }, - enabled: { type: 'boolean' }, - dataset: { type: 'keyword' }, - processors: { type: 'keyword' }, - config: { type: 'flattened' }, - pkg_stream: { type: 'flattened' }, + output_id: { type: 'keyword' }, + inputs: { + type: 'nested', + properties: { + type: { type: 'keyword' }, + enabled: { type: 'boolean' }, + processors: { type: 'keyword' }, + config: { type: 'flattened' }, + streams: { + type: 'nested', + properties: { + id: { type: 'keyword' }, + enabled: { type: 'boolean' }, + dataset: { type: 'keyword' }, + processors: { type: 'keyword' }, + config: { type: 'flattened' }, + pkg_stream: { type: 'flattened' }, + }, }, }, }, + revision: { type: 'integer' }, }, - revision: { type: 'integer' }, }, }, [PACKAGES_SAVED_OBJECT_TYPE]: { - properties: { - name: { type: 'keyword' }, - version: { type: 'keyword' }, - internal: { type: 'boolean' }, - removable: { type: 'boolean' }, - es_index_patterns: { - dynamic: false, - type: 'object', - }, - installed: { - type: 'nested', - properties: { - id: { type: 'keyword' }, - type: { type: 'keyword' }, + name: PACKAGES_SAVED_OBJECT_TYPE, + hidden: false, + namespaceType: 'agnostic', + management: { + importableAndExportable: false, + }, + mappings: { + properties: { + name: { type: 'keyword' }, + version: { type: 'keyword' }, + internal: { type: 'boolean' }, + removable: { type: 'boolean' }, + es_index_patterns: { + dynamic: 'false', + type: 'object', + }, + installed: { + type: 'nested', + properties: { + id: { type: 'keyword' }, + type: { type: 'keyword' }, + }, }, }, }, }, }; +export function registerSavedObjects(savedObjects: SavedObjectsServiceSetup) { + Object.values(savedObjectTypes).forEach(type => { + savedObjects.registerType(type); + }); +} + export function registerEncryptedSavedObjects( encryptedSavedObjects: EncryptedSavedObjectsPluginSetup ) { diff --git a/x-pack/plugins/ingest_manager/server/services/agent_config.ts b/x-pack/plugins/ingest_manager/server/services/agent_config.ts index 309ddca3784c2..75bbfc21293c2 100644 --- a/x-pack/plugins/ingest_manager/server/services/agent_config.ts +++ b/x-pack/plugins/ingest_manager/server/services/agent_config.ts @@ -67,7 +67,7 @@ class AgentConfigService { public async ensureDefaultAgentConfig(soClient: SavedObjectsClientContract) { const configs = await soClient.find({ type: AGENT_CONFIG_SAVED_OBJECT_TYPE, - filter: 'agent_configs.attributes.is_default:true', + filter: `${AGENT_CONFIG_SAVED_OBJECT_TYPE}.attributes.is_default:true`, }); if (configs.total === 0) { @@ -244,7 +244,7 @@ class AgentConfigService { public async getDefaultAgentConfigId(soClient: SavedObjectsClientContract) { const configs = await soClient.find({ type: AGENT_CONFIG_SAVED_OBJECT_TYPE, - filter: 'agent_configs.attributes.is_default:true', + filter: `${AGENT_CONFIG_SAVED_OBJECT_TYPE}.attributes.is_default:true`, }); if (configs.saved_objects.length === 0) { diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts index a8fada00e25da..ae0dedce178a8 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts @@ -14,7 +14,7 @@ import { AgentActionSOAttributes, AgentEvent, } from '../../../common/types/models'; -import { AGENT_TYPE_PERMANENT } from '../../../common/constants'; +import { AGENT_TYPE_PERMANENT, AGENT_ACTION_SAVED_OBJECT_TYPE } from '../../../common/constants'; import { acknowledgeAgentActions } from './acks'; import { appContextService } from '../app_context'; import { IngestManagerAppContext } from '../../plugin'; @@ -31,7 +31,7 @@ describe('test agent acks services', () => { Promise.resolve({ id: 'action1', references: [], - type: 'agent_actions', + type: AGENT_ACTION_SAVED_OBJECT_TYPE, attributes: { type: 'CONFIG_CHANGE', agent_id: 'id', @@ -48,7 +48,7 @@ describe('test agent acks services', () => { { id: 'action1', references: [], - type: 'agent_actions', + type: AGENT_ACTION_SAVED_OBJECT_TYPE, attributes: { type: 'CONFIG_CHANGE', agent_id: 'id', @@ -137,7 +137,7 @@ describe('test agent acks services', () => { { id: 'action1', references: [], - type: 'agent_actions', + type: AGENT_ACTION_SAVED_OBJECT_TYPE, attributes: { type: 'CONFIG_CHANGE', agent_id: 'id', diff --git a/x-pack/plugins/ingest_manager/server/services/agents/crud.ts b/x-pack/plugins/ingest_manager/server/services/agents/crud.ts index ec270884e62b4..175b92b75aca0 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/crud.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/crud.ts @@ -31,12 +31,17 @@ export async function listAgents( if (kuery && kuery !== '') { // To ensure users dont need to know about SO data structure... - filters.push(kuery.replace(/agents\./g, 'agents.attributes.')); + filters.push( + kuery.replace( + new RegExp(`${AGENT_SAVED_OBJECT_TYPE}\.`, 'g'), + `${AGENT_SAVED_OBJECT_TYPE}.attributes.` + ) + ); } if (showInactive === false) { - const agentActiveCondition = `agents.attributes.active:true AND not agents.attributes.type:${AGENT_TYPE_EPHEMERAL}`; - const recentlySeenEphemeralAgent = `agents.attributes.active:true AND agents.attributes.type:${AGENT_TYPE_EPHEMERAL} AND agents.attributes.last_checkin > ${Date.now() - + const agentActiveCondition = `${AGENT_SAVED_OBJECT_TYPE}.attributes.active:true AND not ${AGENT_SAVED_OBJECT_TYPE}.attributes.type:${AGENT_TYPE_EPHEMERAL}`; + const recentlySeenEphemeralAgent = `${AGENT_SAVED_OBJECT_TYPE}.attributes.active:true AND ${AGENT_SAVED_OBJECT_TYPE}.attributes.type:${AGENT_TYPE_EPHEMERAL} AND ${AGENT_SAVED_OBJECT_TYPE}.attributes.last_checkin > ${Date.now() - 3 * AGENT_POLLING_THRESHOLD_MS}`; filters.push(`(${agentActiveCondition}) OR (${recentlySeenEphemeralAgent})`); } diff --git a/x-pack/plugins/ingest_manager/server/services/agents/events.ts b/x-pack/plugins/ingest_manager/server/services/agents/events.ts index 707229845531c..2758374eba65f 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/events.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/events.ts @@ -23,7 +23,10 @@ export async function getAgentEvents( type: AGENT_EVENT_SAVED_OBJECT_TYPE, filter: kuery && kuery !== '' - ? kuery.replace(/agent_events\./g, 'agent_events.attributes.') + ? kuery.replace( + new RegExp(`${AGENT_EVENT_SAVED_OBJECT_TYPE}\.`, 'g'), + `${AGENT_EVENT_SAVED_OBJECT_TYPE}.attributes.` + ) : undefined, perPage, page, diff --git a/x-pack/plugins/ingest_manager/server/services/agents/status.ts b/x-pack/plugins/ingest_manager/server/services/agents/status.ts index fce989cea3248..c4d4a8436e147 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/status.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/status.ts @@ -6,7 +6,7 @@ import { SavedObjectsClientContract } from 'src/core/server'; import { getAgent, listAgents } from './crud'; -import { AGENT_EVENT_SAVED_OBJECT_TYPE } from '../../constants'; +import { AGENT_EVENT_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE } from '../../constants'; import { AgentStatus, Agent } from '../../types'; import { @@ -72,8 +72,8 @@ export async function getAgentStatusForConfig( page: 1, kuery: configId ? kuery - ? `(${kuery}) and (agents.config_id:"${configId}")` - : `agents.config_id:"${configId}"` + ? `(${kuery}) and (${AGENT_SAVED_OBJECT_TYPE}.config_id:"${configId}")` + : `${AGENT_SAVED_OBJECT_TYPE}.config_id:"${configId}"` : kuery, }) ) @@ -91,7 +91,9 @@ export async function getAgentStatusForConfig( async function getEventsCount(soClient: SavedObjectsClientContract, configId?: string) { const { total } = await soClient.find({ type: AGENT_EVENT_SAVED_OBJECT_TYPE, - filter: configId ? `agent_events.attributes.config_id:"${configId}"` : undefined, + filter: configId + ? `${AGENT_EVENT_SAVED_OBJECT_TYPE}.attributes.config_id:"${configId}"` + : undefined, perPage: 0, page: 1, sortField: 'timestamp', diff --git a/x-pack/plugins/ingest_manager/server/services/agents/update.ts b/x-pack/plugins/ingest_manager/server/services/agents/update.ts index 948e518dff5b4..fd57e83d7421e 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/update.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/update.ts @@ -22,7 +22,7 @@ export async function updateAgentsForConfigId( let page = 1; while (hasMore) { const { agents } = await listAgents(soClient, { - kuery: `agents.config_id:"${configId}"`, + kuery: `${AGENT_SAVED_OBJECT_TYPE}.config_id:"${configId}"`, page: page++, perPage: 1000, showInactive: true, @@ -46,7 +46,7 @@ export async function unenrollForConfigId(soClient: SavedObjectsClientContract, let page = 1; while (hasMore) { const { agents } = await listAgents(soClient, { - kuery: `agents.config_id:"${configId}"`, + kuery: `${AGENT_SAVED_OBJECT_TYPE}.config_id:"${configId}"`, page: page++, perPage: 1000, showInactive: true, diff --git a/x-pack/plugins/ingest_manager/server/services/api_keys/enrollment_api_key.ts b/x-pack/plugins/ingest_manager/server/services/api_keys/enrollment_api_key.ts index c9ead09b0908d..1ac812c3380cd 100644 --- a/x-pack/plugins/ingest_manager/server/services/api_keys/enrollment_api_key.ts +++ b/x-pack/plugins/ingest_manager/server/services/api_keys/enrollment_api_key.ts @@ -31,7 +31,10 @@ export async function listEnrollmentApiKeys( sortOrder: 'DESC', filter: kuery && kuery !== '' - ? kuery.replace(/enrollment_api_keys\./g, 'enrollment_api_keys.attributes.') + ? kuery.replace( + new RegExp(`${ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE}\.`, 'g'), + `${ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE}.attributes.` + ) : undefined, }); @@ -80,7 +83,7 @@ export async function deleteEnrollmentApiKeyForConfigId( const { items } = await listEnrollmentApiKeys(soClient, { page: page++, perPage: 100, - kuery: `enrollment_api_keys.config_id:${configId}`, + kuery: `${ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE}.config_id:${configId}`, }); if (items.length === 0) { diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts index a96afc5eb7fa5..8e9b920875617 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/index.ts @@ -16,11 +16,11 @@ import { RegistrySearchResults, RegistrySearchResult, } from '../../../types'; -import { appContextService } from '../../'; import { cacheGet, cacheSet } from './cache'; import { ArchiveEntry, untarBuffer } from './extract'; import { fetchUrl, getResponse, getResponseStream } from './requests'; import { streamToBuffer } from './streams'; +import { getRegistryUrl } from './registry_url'; export { ArchiveEntry } from './extract'; @@ -32,7 +32,7 @@ export const pkgToPkgKey = ({ name, version }: { name: string; version: string } `${name}-${version}`; export async function fetchList(params?: SearchParams): Promise { - const registryUrl = appContextService.getConfig()?.epm.registryUrl; + const registryUrl = getRegistryUrl(); const url = new URL(`${registryUrl}/search`); if (params && params.category) { url.searchParams.set('category', params.category); @@ -45,7 +45,7 @@ export async function fetchFindLatestPackage( packageName: string, internal: boolean = true ): Promise { - const registryUrl = appContextService.getConfig()?.epm.registryUrl; + const registryUrl = getRegistryUrl(); const url = new URL(`${registryUrl}/search?package=${packageName}&internal=${internal}`); const res = await fetchUrl(url.toString()); const searchResults = JSON.parse(res); @@ -57,17 +57,17 @@ export async function fetchFindLatestPackage( } export async function fetchInfo(pkgName: string, pkgVersion: string): Promise { - const registryUrl = appContextService.getConfig()?.epm.registryUrl; + const registryUrl = getRegistryUrl(); return fetchUrl(`${registryUrl}/package/${pkgName}/${pkgVersion}`).then(JSON.parse); } export async function fetchFile(filePath: string): Promise { - const registryUrl = appContextService.getConfig()?.epm.registryUrl; + const registryUrl = getRegistryUrl(); return getResponse(`${registryUrl}${filePath}`); } export async function fetchCategories(): Promise { - const registryUrl = appContextService.getConfig()?.epm.registryUrl; + const registryUrl = getRegistryUrl(); return fetchUrl(`${registryUrl}/categories`).then(JSON.parse); } @@ -151,7 +151,7 @@ async function getOrFetchArchiveBuffer(pkgName: string, pkgVersion: string): Pro async function fetchArchiveBuffer(pkgName: string, pkgVersion: string): Promise { const { download: archivePath } = await fetchInfo(pkgName, pkgVersion); - const registryUrl = appContextService.getConfig()?.epm.registryUrl; + const registryUrl = getRegistryUrl(); return getResponseStream(`${registryUrl}${archivePath}`).then(streamToBuffer); } diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/registry_url.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/registry_url.ts new file mode 100644 index 0000000000000..d92d6faf8472e --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/registry_url.ts @@ -0,0 +1,24 @@ +/* + * 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 { DEFAULT_REGISTRY_URL } from '../../../constants'; +import { appContextService, licenseService } from '../../'; + +export const getRegistryUrl = (): string => { + const license = licenseService.getLicenseInformation(); + const customUrl = appContextService.getConfig()?.epm.registryUrl; + + if ( + customUrl && + license && + license.isAvailable && + license.hasAtLeast('gold') && + license.isActive + ) { + return customUrl; + } + + return DEFAULT_REGISTRY_URL; +}; diff --git a/x-pack/plugins/ingest_manager/server/services/index.ts b/x-pack/plugins/ingest_manager/server/services/index.ts index 1b0f174cc1a8e..483661b9de915 100644 --- a/x-pack/plugins/ingest_manager/server/services/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/index.ts @@ -3,10 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { SavedObjectsClientContract } from 'kibana/server'; import { AgentStatus } from '../../common/types/models'; - -export { appContextService } from './app_context'; +import * as settingsService from './settings'; export { ESIndexPatternSavedObjectService } from './es_index_pattern'; /** @@ -36,3 +36,8 @@ export interface AgentService { export { datasourceService } from './datasource'; export { agentConfigService } from './agent_config'; export { outputService } from './output'; +export { settingsService }; + +// Plugin services +export { appContextService } from './app_context'; +export { licenseService } from './license'; diff --git a/x-pack/plugins/ingest_manager/server/services/output.ts b/x-pack/plugins/ingest_manager/server/services/output.ts index aebb8188db0cc..395c9af4a4ca2 100644 --- a/x-pack/plugins/ingest_manager/server/services/output.ts +++ b/x-pack/plugins/ingest_manager/server/services/output.ts @@ -14,7 +14,7 @@ class OutputService { public async ensureDefaultOutput(soClient: SavedObjectsClientContract) { const outputs = await soClient.find({ type: OUTPUT_SAVED_OBJECT_TYPE, - filter: 'outputs.attributes.is_default:true', + filter: `${OUTPUT_SAVED_OBJECT_TYPE}.attributes.is_default:true`, }); if (!outputs.saved_objects.length) { @@ -44,7 +44,7 @@ class OutputService { public async getDefaultOutputId(soClient: SavedObjectsClientContract) { const outputs = await soClient.find({ type: OUTPUT_SAVED_OBJECT_TYPE, - filter: 'outputs.attributes.is_default:true', + filter: `${OUTPUT_SAVED_OBJECT_TYPE}.attributes.is_default:true`, }); if (!outputs.saved_objects.length) { @@ -95,6 +95,34 @@ class OutputService { ...outputSO.attributes, }; } + + public async update(soClient: SavedObjectsClientContract, id: string, data: Partial) { + const outputSO = await soClient.update(SAVED_OBJECT_TYPE, id, data); + + if (outputSO.error) { + throw new Error(outputSO.error.message); + } + } + + public async list(soClient: SavedObjectsClientContract) { + const outputs = await soClient.find({ + type: SAVED_OBJECT_TYPE, + page: 1, + perPage: 1000, + }); + + return { + items: outputs.saved_objects.map(outputSO => { + return { + id: outputSO.id, + ...outputSO.attributes, + }; + }), + total: outputs.total, + page: 1, + perPage: 1000, + }; + } } export const outputService = new OutputService(); diff --git a/x-pack/plugins/ingest_manager/server/services/settings.ts b/x-pack/plugins/ingest_manager/server/services/settings.ts new file mode 100644 index 0000000000000..f1c09746d9abd --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/settings.ts @@ -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 Boom from 'boom'; +import { SavedObjectsClientContract } from 'kibana/server'; +import { GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, SettingsSOAttributes, Settings } from '../../common'; + +export async function getSettings(soClient: SavedObjectsClientContract): Promise { + const res = await soClient.find({ + type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + }); + + if (res.total === 0) { + throw Boom.notFound('Global settings not found'); + } + const settingsSo = res.saved_objects[0]; + return { + id: settingsSo.id, + ...settingsSo.attributes, + }; +} + +export async function saveSettings( + soClient: SavedObjectsClientContract, + newData: Partial> +): Promise { + try { + const settings = await getSettings(soClient); + + const res = await soClient.update( + GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + settings.id, + newData + ); + + return { + id: settings.id, + ...res.attributes, + }; + } catch (e) { + if (e.isBoom && e.output.statusCode === 404) { + const res = await soClient.create( + GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + newData + ); + + return { + id: res.id, + ...res.attributes, + }; + } + + throw e; + } +} diff --git a/x-pack/plugins/ingest_manager/server/services/setup.ts b/x-pack/plugins/ingest_manager/server/services/setup.ts index 167a24481aba5..206ad76703cf5 100644 --- a/x-pack/plugins/ingest_manager/server/services/setup.ts +++ b/x-pack/plugins/ingest_manager/server/services/setup.ts @@ -21,6 +21,8 @@ import { import { getPackageInfo } from './epm/packages'; import { datasourceService } from './datasource'; import { generateEnrollmentAPIKey } from './api_keys'; +import { settingsService } from '.'; +import { appContextService } from './app_context'; const FLEET_ENROLL_USERNAME = 'fleet_enroll'; const FLEET_ENROLL_ROLE = 'fleet_enroll'; @@ -34,6 +36,17 @@ export async function setupIngestManager( ensureInstalledDefaultPackages(soClient, callCluster), outputService.ensureDefaultOutput(soClient), agentConfigService.ensureDefaultAgentConfig(soClient), + settingsService.getSettings(soClient).catch((e: any) => { + if (e.isBoom && e.output.statusCode === 404) { + return settingsService.saveSettings(soClient, { + agent_auto_upgrade: true, + package_auto_upgrade: true, + kibana_url: appContextService.getConfig()?.fleet?.kibana?.host, + }); + } + + return Promise.reject(e); + }), ]); // ensure default packages are added to the default conifg diff --git a/x-pack/plugins/ingest_manager/server/types/index.tsx b/x-pack/plugins/ingest_manager/server/types/index.tsx index aa5496cc836b7..a7019ebc0a271 100644 --- a/x-pack/plugins/ingest_manager/server/types/index.tsx +++ b/x-pack/plugins/ingest_manager/server/types/index.tsx @@ -50,6 +50,8 @@ export { DefaultPackages, TemplateRef, IndexTemplateMappings, + Settings, + SettingsSOAttributes, } from '../../common'; export type CallESAsCurrentUser = ScopedClusterClient['callAsCurrentUser']; diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/index.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/index.ts index 42b607fa1c715..6976dae38d5f1 100644 --- a/x-pack/plugins/ingest_manager/server/types/rest_spec/index.ts +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/index.ts @@ -10,3 +10,5 @@ export * from './datasource'; export * from './epm'; export * from './enrollment_api_key'; export * from './install_script'; +export * from './output'; +export * from './settings'; diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/output.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/output.ts new file mode 100644 index 0000000000000..79a7c444dacdb --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/output.ts @@ -0,0 +1,24 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +export const GetOneOutputRequestSchema = { + params: schema.object({ + outputId: schema.string(), + }), +}; + +export const GetOutputsRequestSchema = {}; + +export const PutOutputRequestSchema = { + params: schema.object({ + outputId: schema.string(), + }), + body: schema.object({ + hosts: schema.maybe(schema.arrayOf(schema.string())), + ca_sha256: schema.maybe(schema.string()), + }), +}; diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts new file mode 100644 index 0000000000000..8b7500e4a9bd9 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts @@ -0,0 +1,17 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +export const GetSettingsRequestSchema = {}; + +export const PutSettingsRequestSchema = { + body: schema.object({ + agent_auto_upgrade: schema.maybe(schema.boolean()), + package_auto_upgrade: schema.maybe(schema.boolean()), + kibana_url: schema.maybe(schema.string()), + kibana_ca_sha256: schema.maybe(schema.string()), + }), +}; diff --git a/x-pack/plugins/maps/public/angular/get_initial_layers.js b/x-pack/plugins/maps/public/angular/get_initial_layers.js index 1eb5dac309f28..f02ded1704533 100644 --- a/x-pack/plugins/maps/public/angular/get_initial_layers.js +++ b/x-pack/plugins/maps/public/angular/get_initial_layers.js @@ -5,27 +5,18 @@ */ import _ from 'lodash'; // Import each layer type, even those not used, to init in registry - import '../layers/sources/wms_source'; - import '../layers/sources/ems_file_source'; - import '../layers/sources/es_search_source'; - -import '../layers/sources/es_pew_pew_source/es_pew_pew_source'; - +import '../layers/sources/es_pew_pew_source'; import '../layers/sources/kibana_regionmap_source'; - import '../layers/sources/es_geo_grid_source'; - import '../layers/sources/xyz_tms_source'; - import { KibanaTilemapSource } from '../layers/sources/kibana_tilemap_source'; - +import { TileLayer } from '../layers/tile_layer'; import { EMSTMSSource } from '../layers/sources/ems_tms_source'; - +import { VectorTileLayer } from '../layers/vector_tile_layer'; import { getInjectedVarFunc } from '../kibana_services'; - import { getKibanaTileMap } from '../meta'; export function getInitialLayers(layerListJSON, initialLayers = []) { @@ -35,18 +26,18 @@ export function getInitialLayers(layerListJSON, initialLayers = []) { const tilemapSourceFromKibana = getKibanaTileMap(); if (_.get(tilemapSourceFromKibana, 'url')) { - const sourceDescriptor = KibanaTilemapSource.createDescriptor(); - const source = new KibanaTilemapSource(sourceDescriptor); - const layer = source.createDefaultLayer(); - return [layer.toLayerDescriptor(), ...initialLayers]; + const layerDescriptor = TileLayer.createDescriptor({ + sourceDescriptor: KibanaTilemapSource.createDescriptor(), + }); + return [layerDescriptor, ...initialLayers]; } const isEmsEnabled = getInjectedVarFunc()('isEmsEnabled', true); if (isEmsEnabled) { - const descriptor = EMSTMSSource.createDescriptor({ isAutoSelect: true }); - const source = new EMSTMSSource(descriptor); - const layer = source.createDefaultLayer(); - return [layer.toLayerDescriptor(), ...initialLayers]; + const layerDescriptor = VectorTileLayer.createDescriptor({ + sourceDescriptor: EMSTMSSource.createDescriptor({ isAutoSelect: true }), + }); + return [layerDescriptor, ...initialLayers]; } return initialLayers; diff --git a/x-pack/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js b/x-pack/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js index a4fa0d492bf3f..8ebb17ac4fff5 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js +++ b/x-pack/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js @@ -4,51 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment } from 'react'; -import { EuiSpacer, EuiPanel, EuiButtonEmpty } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; +import { EuiPanel } from '@elastic/eui'; import { uploadLayerWizardConfig } from '../../../layers/sources/client_file_source'; -export const ImportEditor = ({ clearSource, isIndexingTriggered, ...props }) => { - const editorProperties = getEditorProperties({ isIndexingTriggered, ...props }); +export const ImportEditor = props => { + const editorProperties = getEditorProperties(props); return ( - - {isIndexingTriggered ? null : ( - - - - - - - )} - - {uploadLayerWizardConfig.renderWizard(editorProperties)} - - + + {uploadLayerWizardConfig.renderWizard(editorProperties)} + ); }; function getEditorProperties({ - inspectorAdapters, + previewLayer, + mapColors, onRemove, - viewLayer, isIndexingTriggered, onIndexReady, importSuccessHandler, importErrorHandler, }) { return { - onPreviewSource: viewLayer, - inspectorAdapters, + previewLayer, + mapColors, onRemove, importSuccessHandler, importErrorHandler, isIndexingTriggered, - addAndViewSource: viewLayer, onIndexReady, }; } diff --git a/x-pack/plugins/maps/public/connected_components/layer_addpanel/index.js b/x-pack/plugins/maps/public/connected_components/layer_addpanel/index.js index 24c1f5ced4fe6..a29898f8a2830 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_addpanel/index.js +++ b/x-pack/plugins/maps/public/connected_components/layer_addpanel/index.js @@ -34,12 +34,12 @@ function mapStateToProps(state = {}) { function mapDispatchToProps(dispatch) { return { - viewLayer: async layer => { + previewLayer: async layerDescriptor => { await dispatch(setSelectedLayer(null)); await dispatch(removeTransientLayer()); - dispatch(addLayer(layer.toLayerDescriptor())); - dispatch(setSelectedLayer(layer.getId())); - dispatch(setTransientLayer(layer.getId())); + dispatch(addLayer(layerDescriptor)); + dispatch(setSelectedLayer(layerDescriptor.id)); + dispatch(setTransientLayer(layerDescriptor.id)); }, removeTransientLayer: () => { dispatch(setSelectedLayer(null)); diff --git a/x-pack/plugins/maps/public/connected_components/layer_addpanel/source_editor/index.js b/x-pack/plugins/maps/public/connected_components/layer_addpanel/source_editor/index.js deleted file mode 100644 index 8937f32d3bf05..0000000000000 --- a/x-pack/plugins/maps/public/connected_components/layer_addpanel/source_editor/index.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { connect } from 'react-redux'; -import { SourceEditor } from './view'; - -import { getInspectorAdapters } from '../../../reducers/non_serializable_instances'; - -function mapStateToProps(state = {}) { - return { - inspectorAdapters: getInspectorAdapters(state), - }; -} - -const connectedFlyOut = connect(mapStateToProps)(SourceEditor); -export { connectedFlyOut as SourceEditor }; diff --git a/x-pack/plugins/maps/public/connected_components/layer_addpanel/source_editor/view.js b/x-pack/plugins/maps/public/connected_components/layer_addpanel/source_editor/view.js deleted file mode 100644 index 50312b68277fa..0000000000000 --- a/x-pack/plugins/maps/public/connected_components/layer_addpanel/source_editor/view.js +++ /dev/null @@ -1,40 +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 React, { Fragment } from 'react'; -import { EuiSpacer, EuiPanel, EuiButtonEmpty } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -export const SourceEditor = ({ - clearSource, - layerWizard, - isIndexingTriggered, - inspectorAdapters, - previewLayer, -}) => { - if (!layerWizard) { - return null; - } - - return ( - - {isIndexingTriggered ? null : ( - - - - - - - )} - - {layerWizard.renderWizard({ onPreviewSource: previewLayer, inspectorAdapters })} - - - ); -}; diff --git a/x-pack/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js b/x-pack/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js index 80b05a0fd015b..82df9237e6ed3 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js +++ b/x-pack/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js @@ -7,8 +7,7 @@ import React, { Fragment } from 'react'; import { getLayerWizards } from '../../../layers/layer_wizard_registry'; -import { EuiTitle, EuiSpacer, EuiCard, EuiIcon } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiSpacer, EuiCard, EuiIcon } from '@elastic/eui'; import _ from 'lodash'; export function SourceSelect({ updateSourceSelection }) { @@ -38,17 +37,5 @@ export function SourceSelect({ updateSourceSelection }) { ); }); - return ( - - -

- -

-
- {sourceCards} -
- ); + return {sourceCards}; } diff --git a/x-pack/plugins/maps/public/connected_components/layer_addpanel/view.js b/x-pack/plugins/maps/public/connected_components/layer_addpanel/view.js index 92fcf01f3901f..127b99d730db5 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_addpanel/view.js +++ b/x-pack/plugins/maps/public/connected_components/layer_addpanel/view.js @@ -4,18 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component } from 'react'; +import React, { Component, Fragment } from 'react'; import { SourceSelect } from './source_select/source_select'; import { FlyoutFooter } from './flyout_footer'; -import { SourceEditor } from './source_editor'; import { ImportEditor } from './import_editor'; -import { EuiFlexGroup, EuiTitle, EuiFlyoutHeader } from '@elastic/eui'; +import { EuiButtonEmpty, EuiPanel, EuiTitle, EuiFlyoutHeader, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; export class AddLayerPanel extends Component { state = { layerWizard: null, - layer: null, + layerDescriptor: null, // TODO get this from redux store instead of storing locally + isIndexingSource: false, importView: false, layerImportAddReady: false, }; @@ -35,13 +36,9 @@ export class AddLayerPanel extends Component { } _getPanelDescription() { - const { layerWizard, importView, layerImportAddReady } = this.state; + const { importView, layerImportAddReady } = this.state; let panelDescription; - if (!layerWizard) { - panelDescription = i18n.translate('xpack.maps.addLayerPanel.selectSource', { - defaultMessage: 'Select source', - }); - } else if (layerImportAddReady || !importView) { + if (layerImportAddReady || !importView) { panelDescription = i18n.translate('xpack.maps.addLayerPanel.addLayer', { defaultMessage: 'Add layer', }); @@ -53,29 +50,21 @@ export class AddLayerPanel extends Component { return panelDescription; } - _viewLayer = async (source, options = {}) => { + _previewLayer = async (layerDescriptor, isIndexingSource) => { if (!this._isMounted) { return; } - if (!source) { - this.setState({ layer: null }); + if (!layerDescriptor) { + this.setState({ + layerDescriptor: null, + isIndexingSource: false, + }); this.props.removeTransientLayer(); return; } - const styleDescriptor = - this.state.layer && this.state.layer.getCurrentStyle() - ? this.state.layer.getCurrentStyle().getDescriptor() - : null; - const layerInitProps = { - ...options, - style: styleDescriptor, - }; - const newLayer = source.createDefaultLayer(layerInitProps, this.props.mapColors); - if (!this._isMounted) { - return; - } - this.setState({ layer: newLayer }, () => this.props.viewLayer(this.state.layer)); + this.setState({ layerDescriptor, isIndexingSource }); + this.props.previewLayer(layerDescriptor); }; _clearLayerData = ({ keepSourceType = false }) => { @@ -84,7 +73,8 @@ export class AddLayerPanel extends Component { } this.setState({ - layer: null, + layerDescriptor: null, + isIndexingSource: false, ...(!keepSourceType ? { layerWizard: null, importView: false } : {}), }); this.props.removeTransientLayer(); @@ -95,72 +85,75 @@ export class AddLayerPanel extends Component { }; _layerAddHandler = () => { - const { - isIndexingTriggered, - setIndexingTriggered, - selectLayerAndAdd, - resetIndexing, - } = this.props; - const layerSource = this.state.layer.getSource(); - const boolIndexLayer = layerSource.shouldBeIndexed(); - this.setState({ layer: null }); - if (boolIndexLayer && !isIndexingTriggered) { - setIndexingTriggered(); + if (this.state.isIndexingSource && !this.props.isIndexingTriggered) { + this.props.setIndexingTriggered(); } else { - selectLayerAndAdd(); + this.props.selectLayerAndAdd(); if (this.state.importView) { this.setState({ layerImportAddReady: false, }); - resetIndexing(); + this.props.resetIndexing(); } } }; - _renderAddLayerPanel() { - const { layerWizard, importView } = this.state; - if (!layerWizard) { + _renderPanelBody() { + if (!this.state.layerWizard) { return ; } - if (importView) { + + const backButton = this.props.isIndexingTriggered ? null : ( + + + + + + + ); + + if (this.state.importView) { return ( - this._clearLayerData({ keepSourceType: true })} - /> + + {backButton} + this._clearLayerData({ keepSourceType: true })} + /> + ); } - return ( - - ); - } - - _renderFooter(buttonDescription) { - const { importView, layer } = this.state; - const { isIndexingReady, isIndexingSuccess } = this.props; - - const buttonEnabled = importView ? isIndexingReady || isIndexingSuccess : !!layer; return ( - + + {backButton} + + {this.state.layerWizard.renderWizard({ + previewLayer: this._previewLayer, + mapColors: this.props.mapColors, + })} + + ); } - _renderFlyout() { + render() { + if (!this.props.flyoutVisible) { + return null; + } + const panelDescription = this._getPanelDescription(); + const isNextBtnEnabled = this.state.importView + ? this.props.isIndexingReady || this.props.isIndexingSuccess + : !!this.state.layerDescriptor; return ( - +

{panelDescription}

@@ -168,14 +161,16 @@ export class AddLayerPanel extends Component {
-
{this._renderAddLayerPanel()}
+
{this._renderPanelBody()}
- {this._renderFooter(panelDescription)} -
- ); - } - render() { - return this.props.flyoutVisible ? this._renderFlyout() : null; + + + ); } } diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts b/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts index be6c3bf978d82..abfaba80c33d1 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts @@ -23,6 +23,7 @@ import { addLayerWithoutDataSync } from '../actions/map_actions'; import { getQueryableUniqueIndexPatternIds } from '../selectors/map_selectors'; import { getInitialLayers } from '../angular/get_initial_layers'; import { mergeInputWithSavedMap } from './merge_input_with_saved_map'; +import '../index.scss'; export class MapEmbeddableFactory implements EmbeddableFactoryDefinition { type = MAP_SAVED_OBJECT_TYPE; diff --git a/x-pack/plugins/maps/public/layers/blended_vector_layer.ts b/x-pack/plugins/maps/public/layers/blended_vector_layer.ts index 1fc3ad203706f..5c486200977d7 100644 --- a/x-pack/plugins/maps/public/layers/blended_vector_layer.ts +++ b/x-pack/plugins/maps/public/layers/blended_vector_layer.ts @@ -156,7 +156,7 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { static type = LAYER_TYPE.BLENDED_VECTOR; static createDescriptor( - options: VectorLayerDescriptor, + options: Partial, mapColors: string[] ): VectorLayerDescriptor { const layerDescriptor = VectorLayer.createDescriptor(options, mapColors); diff --git a/x-pack/plugins/maps/public/layers/layer.tsx b/x-pack/plugins/maps/public/layers/layer.tsx index ce48793e1481b..13fe447cec3da 100644 --- a/x-pack/plugins/maps/public/layers/layer.tsx +++ b/x-pack/plugins/maps/public/layers/layer.tsx @@ -62,7 +62,6 @@ export interface ILayer { isLayerLoading(): boolean; hasErrors(): boolean; getErrors(): string; - toLayerDescriptor(): LayerDescriptor; getMbLayerIds(): string[]; ownsMbLayerId(mbLayerId: string): boolean; ownsMbSourceId(mbSourceId: string): boolean; @@ -413,10 +412,6 @@ export class AbstractLayer implements ILayer { : ''; } - toLayerDescriptor(): LayerDescriptor { - return this._descriptor; - } - async syncData(syncContext: SyncContext) { // no-op by default } diff --git a/x-pack/plugins/maps/public/layers/layer_wizard_registry.ts b/x-pack/plugins/maps/public/layers/layer_wizard_registry.ts index cb87aeaa9da3f..633e8c86d8c94 100644 --- a/x-pack/plugins/maps/public/layers/layer_wizard_registry.ts +++ b/x-pack/plugins/maps/public/layers/layer_wizard_registry.ts @@ -6,13 +6,17 @@ /* eslint-disable @typescript-eslint/consistent-type-definitions */ import { ReactElement } from 'react'; -import { ISource } from './sources/source'; - -export type PreviewSourceHandler = (source: ISource | null) => void; +import { LayerDescriptor } from '../../common/descriptor_types'; export type RenderWizardArguments = { - onPreviewSource: PreviewSourceHandler; - inspectorAdapters: object; + previewLayer: (layerDescriptor: LayerDescriptor | null, isIndexingSource?: boolean) => void; + mapColors: string[]; + // upload arguments + isIndexingTriggered: boolean; + onRemove: () => void; + onIndexReady: () => void; + importSuccessHandler: (indexResponses: unknown) => void; + importErrorHandler: (indexResponses: unknown) => void; }; export type LayerWizard = { diff --git a/x-pack/plugins/maps/public/layers/load_layer_wizards.ts b/x-pack/plugins/maps/public/layers/load_layer_wizards.ts index 49d128257fe20..0b83f3bbdc613 100644 --- a/x-pack/plugins/maps/public/layers/load_layer_wizards.ts +++ b/x-pack/plugins/maps/public/layers/load_layer_wizards.ts @@ -12,7 +12,7 @@ import { esDocumentsLayerWizardConfig } from './sources/es_search_source'; // @ts-ignore import { clustersLayerWizardConfig, heatmapLayerWizardConfig } from './sources/es_geo_grid_source'; // @ts-ignore -import { point2PointLayerWizardConfig } from './sources/es_pew_pew_source/es_pew_pew_source'; +import { point2PointLayerWizardConfig } from './sources/es_pew_pew_source'; // @ts-ignore import { emsBoundariesLayerWizardConfig } from './sources/ems_file_source'; // @ts-ignore diff --git a/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js b/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js index 36f898f723757..3c9c71d2a1875 100644 --- a/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js +++ b/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js @@ -5,17 +5,7 @@ */ import { AbstractVectorSource } from '../vector_source'; -import React from 'react'; -import { - ES_GEO_FIELD_TYPE, - SOURCE_TYPES, - DEFAULT_MAX_RESULT_WINDOW, - SCALING_TYPES, -} from '../../../../common/constants'; -import { ClientFileCreateSourceEditor } from './create_client_file_source_editor'; -import { ESSearchSource } from '../es_search_source'; -import uuid from 'uuid/v4'; -import { i18n } from '@kbn/i18n'; +import { SOURCE_TYPES } from '../../../../common/constants'; import { registerSource } from '../source_registry'; export class GeojsonFileSource extends AbstractVectorSource { @@ -66,103 +56,9 @@ export class GeojsonFileSource extends AbstractVectorSource { canFormatFeatureProperties() { return true; } - - shouldBeIndexed() { - return true; - } } -const viewIndexedData = ( - addAndViewSource, - inspectorAdapters, - importSuccessHandler, - importErrorHandler -) => { - return (indexResponses = {}) => { - const { indexDataResp, indexPatternResp } = indexResponses; - - const indexCreationFailed = !(indexDataResp && indexDataResp.success); - const allDocsFailed = indexDataResp.failures.length === indexDataResp.docCount; - const indexPatternCreationFailed = !(indexPatternResp && indexPatternResp.success); - - if (indexCreationFailed || allDocsFailed || indexPatternCreationFailed) { - importErrorHandler(indexResponses); - return; - } - const { fields, id: indexPatternId } = indexPatternResp; - const geoField = fields.find(field => Object.values(ES_GEO_FIELD_TYPE).includes(field.type)); - if (!indexPatternId || !geoField) { - addAndViewSource(null); - } else { - const source = new ESSearchSource( - { - id: uuid(), - indexPatternId, - geoField: geoField.name, - // Only turn on bounds filter for large doc counts - filterByMapBounds: indexDataResp.docCount > DEFAULT_MAX_RESULT_WINDOW, - scalingType: - geoField.type === ES_GEO_FIELD_TYPE.GEO_POINT - ? SCALING_TYPES.CLUSTERS - : SCALING_TYPES.LIMIT, - }, - inspectorAdapters - ); - addAndViewSource(source); - importSuccessHandler(indexResponses); - } - }; -}; - -const previewGeojsonFile = (onPreviewSource, inspectorAdapters) => { - return (geojsonFile, name) => { - if (!geojsonFile) { - onPreviewSource(null); - return; - } - const sourceDescriptor = GeojsonFileSource.createDescriptor(geojsonFile, name); - const source = new GeojsonFileSource(sourceDescriptor, inspectorAdapters); - onPreviewSource(source); - }; -}; - registerSource({ ConstructorFunction: GeojsonFileSource, type: SOURCE_TYPES.GEOJSON_FILE, }); - -export const uploadLayerWizardConfig = { - description: i18n.translate('xpack.maps.source.geojsonFileDescription', { - defaultMessage: 'Index GeoJSON data in Elasticsearch', - }), - icon: 'importAction', - isIndexingSource: true, - renderWizard: ({ - onPreviewSource, - inspectorAdapters, - addAndViewSource, - isIndexingTriggered, - onRemove, - onIndexReady, - importSuccessHandler, - importErrorHandler, - }) => { - return ( - - ); - }, - title: i18n.translate('xpack.maps.source.geojsonFileTitle', { - defaultMessage: 'Upload GeoJSON', - }), -}; diff --git a/x-pack/plugins/maps/public/layers/sources/client_file_source/index.js b/x-pack/plugins/maps/public/layers/sources/client_file_source/index.js index a6a31def4b231..5c2a0afd31885 100644 --- a/x-pack/plugins/maps/public/layers/sources/client_file_source/index.js +++ b/x-pack/plugins/maps/public/layers/sources/client_file_source/index.js @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { GeojsonFileSource, uploadLayerWizardConfig } from './geojson_file_source'; +export { GeojsonFileSource } from './geojson_file_source'; +export { uploadLayerWizardConfig } from './upload_layer_wizard'; diff --git a/x-pack/plugins/maps/public/layers/sources/client_file_source/upload_layer_wizard.tsx b/x-pack/plugins/maps/public/layers/sources/client_file_source/upload_layer_wizard.tsx new file mode 100644 index 0000000000000..2f8aa67d74b52 --- /dev/null +++ b/x-pack/plugins/maps/public/layers/sources/client_file_source/upload_layer_wizard.tsx @@ -0,0 +1,106 @@ +/* + * 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 React from 'react'; +import { IFieldType } from 'src/plugins/data/public'; +import { + ES_GEO_FIELD_TYPE, + DEFAULT_MAX_RESULT_WINDOW, + SCALING_TYPES, +} from '../../../../common/constants'; +// @ts-ignore +import { ESSearchSource, createDefaultLayerDescriptor } from '../es_search_source'; +import { LayerWizard, RenderWizardArguments } from '../../layer_wizard_registry'; +// @ts-ignore +import { ClientFileCreateSourceEditor } from './create_client_file_source_editor'; +// @ts-ignore +import { GeojsonFileSource } from './geojson_file_source'; +import { VectorLayer } from '../../vector_layer'; + +export const uploadLayerWizardConfig: LayerWizard = { + description: i18n.translate('xpack.maps.source.geojsonFileDescription', { + defaultMessage: 'Index GeoJSON data in Elasticsearch', + }), + icon: 'importAction', + isIndexingSource: true, + renderWizard: ({ + previewLayer, + mapColors, + isIndexingTriggered, + onRemove, + onIndexReady, + importSuccessHandler, + importErrorHandler, + }: RenderWizardArguments) => { + function previewGeojsonFile(geojsonFile: unknown, name: string) { + if (!geojsonFile) { + previewLayer(null); + return; + } + const sourceDescriptor = GeojsonFileSource.createDescriptor(geojsonFile, name); + const layerDescriptor = VectorLayer.createDescriptor({ sourceDescriptor }, mapColors); + // TODO figure out a better way to handle passing this information back to layer_addpanel + previewLayer(layerDescriptor, true); + } + + function viewIndexedData(indexResponses: { + indexDataResp: unknown; + indexPatternResp: unknown; + }) { + const { indexDataResp, indexPatternResp } = indexResponses; + + // @ts-ignore + const indexCreationFailed = !(indexDataResp && indexDataResp.success); + // @ts-ignore + const allDocsFailed = indexDataResp.failures.length === indexDataResp.docCount; + // @ts-ignore + const indexPatternCreationFailed = !(indexPatternResp && indexPatternResp.success); + + if (indexCreationFailed || allDocsFailed || indexPatternCreationFailed) { + importErrorHandler(indexResponses); + return; + } + // @ts-ignore + const { fields, id: indexPatternId } = indexPatternResp; + const geoField = fields.find((field: IFieldType) => + [ES_GEO_FIELD_TYPE.GEO_POINT as string, ES_GEO_FIELD_TYPE.GEO_SHAPE as string].includes( + field.type + ) + ); + if (!indexPatternId || !geoField) { + previewLayer(null); + } else { + const esSearchSourceConfig = { + indexPatternId, + geoField: geoField.name, + // Only turn on bounds filter for large doc counts + // @ts-ignore + filterByMapBounds: indexDataResp.docCount > DEFAULT_MAX_RESULT_WINDOW, + scalingType: + geoField.type === ES_GEO_FIELD_TYPE.GEO_POINT + ? SCALING_TYPES.CLUSTERS + : SCALING_TYPES.LIMIT, + }; + previewLayer(createDefaultLayerDescriptor(esSearchSourceConfig, mapColors)); + importSuccessHandler(indexResponses); + } + } + + return ( + + ); + }, + title: i18n.translate('xpack.maps.source.geojsonFileTitle', { + defaultMessage: 'Upload GeoJSON', + }), +}; diff --git a/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_boundaries_layer_wizard.tsx b/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_boundaries_layer_wizard.tsx new file mode 100644 index 0000000000000..f31e770df2d95 --- /dev/null +++ b/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_boundaries_layer_wizard.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { VectorLayer } from '../../vector_layer'; +import { LayerWizard, RenderWizardArguments } from '../../layer_wizard_registry'; +// @ts-ignore +import { EMSFileCreateSourceEditor } from './create_source_editor'; +// @ts-ignore +import { EMSFileSource, sourceTitle } from './ems_file_source'; + +export const emsBoundariesLayerWizardConfig: LayerWizard = { + description: i18n.translate('xpack.maps.source.emsFileDescription', { + defaultMessage: 'Administrative boundaries from Elastic Maps Service', + }), + icon: 'emsApp', + renderWizard: ({ previewLayer, mapColors }: RenderWizardArguments) => { + const onSourceConfigChange = (sourceConfig: unknown) => { + // @ts-ignore + const sourceDescriptor = EMSFileSource.createDescriptor(sourceConfig); + const layerDescriptor = VectorLayer.createDescriptor({ sourceDescriptor }, mapColors); + previewLayer(layerDescriptor); + }; + return ; + }, + title: sourceTitle, +}; diff --git a/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js index e8af17b911939..5802a223e4846 100644 --- a/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js +++ b/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js @@ -9,14 +9,13 @@ import { VECTOR_SHAPE_TYPES } from '../vector_feature_types'; import React from 'react'; import { SOURCE_TYPES, FIELD_ORIGIN } from '../../../../common/constants'; import { getEMSClient } from '../../../meta'; -import { EMSFileCreateSourceEditor } from './create_source_editor'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { UpdateSourceEditor } from './update_source_editor'; import { EMSFileField } from '../../fields/ems_file_field'; import { registerSource } from '../source_registry'; -const sourceTitle = i18n.translate('xpack.maps.source.emsFileTitle', { +export const sourceTitle = i18n.translate('xpack.maps.source.emsFileTitle', { defaultMessage: 'EMS Boundaries', }); @@ -161,19 +160,3 @@ registerSource({ ConstructorFunction: EMSFileSource, type: SOURCE_TYPES.EMS_FILE, }); - -export const emsBoundariesLayerWizardConfig = { - description: i18n.translate('xpack.maps.source.emsFileDescription', { - defaultMessage: 'Administrative boundaries from Elastic Maps Service', - }), - icon: 'emsApp', - renderWizard: ({ onPreviewSource, inspectorAdapters }) => { - const onSourceConfigChange = sourceConfig => { - const sourceDescriptor = EMSFileSource.createDescriptor(sourceConfig); - const source = new EMSFileSource(sourceDescriptor, inspectorAdapters); - onPreviewSource(source); - }; - return ; - }, - title: sourceTitle, -}; diff --git a/x-pack/plugins/maps/public/layers/sources/ems_file_source/index.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/index.js index 28fbc04a1a032..e9bf592c6d2b7 100644 --- a/x-pack/plugins/maps/public/layers/sources/ems_file_source/index.js +++ b/x-pack/plugins/maps/public/layers/sources/ems_file_source/index.js @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { EMSFileSource, emsBoundariesLayerWizardConfig } from './ems_file_source'; +export { emsBoundariesLayerWizardConfig } from './ems_boundaries_layer_wizard'; +export { EMSFileSource } from './ems_file_source'; diff --git a/x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_base_map_layer_wizard.tsx b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_base_map_layer_wizard.tsx new file mode 100644 index 0000000000000..ced33a0bcf84a --- /dev/null +++ b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_base_map_layer_wizard.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { LayerWizard, RenderWizardArguments } from '../../layer_wizard_registry'; +// @ts-ignore +import { EMSTMSSource, sourceTitle } from './ems_tms_source'; +// @ts-ignore +import { VectorTileLayer } from '../../vector_tile_layer'; +// @ts-ignore +import { TileServiceSelect } from './tile_service_select'; + +export const emsBaseMapLayerWizardConfig: LayerWizard = { + description: i18n.translate('xpack.maps.source.emsTileDescription', { + defaultMessage: 'Tile map service from Elastic Maps Service', + }), + icon: 'emsApp', + renderWizard: ({ previewLayer }: RenderWizardArguments) => { + const onSourceConfigChange = (sourceConfig: unknown) => { + const layerDescriptor = VectorTileLayer.createDescriptor({ + sourceDescriptor: EMSTMSSource.createDescriptor(sourceConfig), + }); + previewLayer(layerDescriptor); + }; + + return ; + }, + title: sourceTitle, +}; diff --git a/x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js index 79121c4cdb31f..3bed9b2c09570 100644 --- a/x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js +++ b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js @@ -7,10 +7,8 @@ import _ from 'lodash'; import React from 'react'; import { AbstractTMSSource } from '../tms_source'; -import { VectorTileLayer } from '../../vector_tile_layer'; import { getEMSClient } from '../../../meta'; -import { TileServiceSelect } from './tile_service_select'; import { UpdateSourceEditor } from './update_source_editor'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; @@ -18,7 +16,7 @@ import { SOURCE_TYPES } from '../../../../common/constants'; import { getInjectedVarFunc, getUiSettings } from '../../../kibana_services'; import { registerSource } from '../source_registry'; -const sourceTitle = i18n.translate('xpack.maps.source.emsTileTitle', { +export const sourceTitle = i18n.translate('xpack.maps.source.emsTileTitle', { defaultMessage: 'EMS Basemaps', }); @@ -84,20 +82,6 @@ export class EMSTMSSource extends AbstractTMSSource { return tmsService; } - _createDefaultLayerDescriptor(options) { - return VectorTileLayer.createDescriptor({ - sourceDescriptor: this._descriptor, - ...options, - }); - } - - createDefaultLayer(options) { - return new VectorTileLayer({ - layerDescriptor: this._createDefaultLayerDescriptor(options), - source: this, - }); - } - async getDisplayName() { try { const emsTMSService = await this._getEMSTMSService(); @@ -150,20 +134,3 @@ registerSource({ ConstructorFunction: EMSTMSSource, type: SOURCE_TYPES.EMS_TMS, }); - -export const emsBaseMapLayerWizardConfig = { - description: i18n.translate('xpack.maps.source.emsTileDescription', { - defaultMessage: 'Tile map service from Elastic Maps Service', - }), - icon: 'emsApp', - renderWizard: ({ onPreviewSource, inspectorAdapters }) => { - const onSourceConfigChange = sourceConfig => { - const descriptor = EMSTMSSource.createDescriptor(sourceConfig); - const source = new EMSTMSSource(descriptor, inspectorAdapters); - onPreviewSource(source); - }; - - return ; - }, - title: sourceTitle, -}; diff --git a/x-pack/plugins/maps/public/layers/sources/ems_tms_source/index.js b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/index.js index 60a4c9b1de891..704bcfd370a85 100644 --- a/x-pack/plugins/maps/public/layers/sources/ems_tms_source/index.js +++ b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/index.js @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { EMSTMSSource, emsBaseMapLayerWizardConfig } from './ems_tms_source'; +export { emsBaseMapLayerWizardConfig } from './ems_base_map_layer_wizard'; +export { EMSTMSSource } from './ems_tms_source'; diff --git a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/clusters_layer_wizard.tsx b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/clusters_layer_wizard.tsx new file mode 100644 index 0000000000000..f9092e64833f1 --- /dev/null +++ b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/clusters_layer_wizard.tsx @@ -0,0 +1,108 @@ +/* + * 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 React from 'react'; +// @ts-ignore +import { CreateSourceEditor } from './create_source_editor'; +// @ts-ignore +import { ESGeoGridSource, clustersTitle } from './es_geo_grid_source'; +import { LayerWizard, RenderWizardArguments } from '../../layer_wizard_registry'; +import { VectorLayer } from '../../vector_layer'; +import { + ESGeoGridSourceDescriptor, + ColorDynamicOptions, + SizeDynamicOptions, +} from '../../../../common/descriptor_types'; +import { getDefaultDynamicProperties } from '../../styles/vector/vector_style_defaults'; +import { VectorStyle } from '../../styles/vector/vector_style'; +import { + COUNT_PROP_NAME, + COLOR_MAP_TYPE, + FIELD_ORIGIN, + RENDER_AS, + VECTOR_STYLES, + STYLE_TYPE, +} from '../../../../common/constants'; +// @ts-ignore +import { COLOR_GRADIENTS } from '../../styles/color_utils'; + +export const clustersLayerWizardConfig: LayerWizard = { + description: i18n.translate('xpack.maps.source.esGridClustersDescription', { + defaultMessage: 'Geospatial data grouped in grids with metrics for each gridded cell', + }), + icon: 'logoElasticsearch', + renderWizard: ({ previewLayer }: RenderWizardArguments) => { + const onSourceConfigChange = (sourceConfig: Partial) => { + if (!sourceConfig) { + previewLayer(null); + return; + } + + const defaultDynamicProperties = getDefaultDynamicProperties(); + const layerDescriptor = VectorLayer.createDescriptor({ + sourceDescriptor: ESGeoGridSource.createDescriptor(sourceConfig), + style: VectorStyle.createDescriptor({ + // @ts-ignore + [VECTOR_STYLES.FILL_COLOR]: { + type: STYLE_TYPE.DYNAMIC, + options: { + ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR]! + .options as ColorDynamicOptions), + field: { + name: COUNT_PROP_NAME, + origin: FIELD_ORIGIN.SOURCE, + }, + color: COLOR_GRADIENTS[0].value, + type: COLOR_MAP_TYPE.ORDINAL, + }, + }, + [VECTOR_STYLES.LINE_COLOR]: { + type: STYLE_TYPE.STATIC, + options: { + color: '#FFF', + }, + }, + [VECTOR_STYLES.LINE_WIDTH]: { + type: STYLE_TYPE.STATIC, + options: { + size: 0, + }, + }, + [VECTOR_STYLES.ICON_SIZE]: { + type: STYLE_TYPE.DYNAMIC, + options: { + ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE]!.options as SizeDynamicOptions), + field: { + name: COUNT_PROP_NAME, + origin: FIELD_ORIGIN.SOURCE, + }, + }, + }, + [VECTOR_STYLES.LABEL_TEXT]: { + type: STYLE_TYPE.DYNAMIC, + options: { + ...defaultDynamicProperties[VECTOR_STYLES.LABEL_TEXT]!.options, + field: { + name: COUNT_PROP_NAME, + origin: FIELD_ORIGIN.SOURCE, + }, + }, + }, + }), + }); + previewLayer(layerDescriptor); + }; + + return ( + + ); + }, + title: clustersTitle, +}; diff --git a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js index b9ef13e520bf8..17fad2f2e6453 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js +++ b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js @@ -8,39 +8,27 @@ import React from 'react'; import uuid from 'uuid/v4'; import { VECTOR_SHAPE_TYPES } from '../vector_feature_types'; -import { HeatmapLayer } from '../../heatmap_layer'; -import { VectorLayer } from '../../vector_layer'; import { convertCompositeRespToGeoJson, convertRegularRespToGeoJson } from './convert_to_geojson'; -import { VectorStyle } from '../../styles/vector/vector_style'; -import { getDefaultDynamicProperties } from '../../styles/vector/vector_style_defaults'; -import { COLOR_GRADIENTS } from '../../styles/color_utils'; -import { CreateSourceEditor } from './create_source_editor'; import { UpdateSourceEditor } from './update_source_editor'; import { SOURCE_TYPES, DEFAULT_MAX_BUCKETS_LIMIT, - COUNT_PROP_NAME, - COLOR_MAP_TYPE, RENDER_AS, GRID_RESOLUTION, - VECTOR_STYLES, - FIELD_ORIGIN, } from '../../../../common/constants'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { AbstractESAggSource } from '../es_agg_source'; -import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property'; -import { StaticStyleProperty } from '../../styles/vector/properties/static_style_property'; import { DataRequestAbortError } from '../../util/data_request'; import { registerSource } from '../source_registry'; export const MAX_GEOTILE_LEVEL = 29; -const clustersTitle = i18n.translate('xpack.maps.source.esGridClustersTitle', { +export const clustersTitle = i18n.translate('xpack.maps.source.esGridClustersTitle', { defaultMessage: 'Clusters and grids', }); -const heatmapTitle = i18n.translate('xpack.maps.source.esGridHeatmapTitle', { +export const heatmapTitle = i18n.translate('xpack.maps.source.esGridHeatmapTitle', { defaultMessage: 'Heat map', }); @@ -320,87 +308,6 @@ export class ESGeoGridSource extends AbstractESAggSource { return true; } - _createHeatmapLayerDescriptor(options) { - return HeatmapLayer.createDescriptor({ - sourceDescriptor: this._descriptor, - ...options, - }); - } - - _createVectorLayerDescriptor(options) { - const descriptor = VectorLayer.createDescriptor({ - sourceDescriptor: this._descriptor, - ...options, - }); - - const defaultDynamicProperties = getDefaultDynamicProperties(); - - descriptor.style = VectorStyle.createDescriptor({ - [VECTOR_STYLES.FILL_COLOR]: { - type: DynamicStyleProperty.type, - options: { - ...defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR].options, - field: { - name: COUNT_PROP_NAME, - origin: FIELD_ORIGIN.SOURCE, - }, - color: COLOR_GRADIENTS[0].value, - type: COLOR_MAP_TYPE.ORDINAL, - }, - }, - [VECTOR_STYLES.LINE_COLOR]: { - type: StaticStyleProperty.type, - options: { - color: '#FFF', - }, - }, - [VECTOR_STYLES.LINE_WIDTH]: { - type: StaticStyleProperty.type, - options: { - size: 0, - }, - }, - [VECTOR_STYLES.ICON_SIZE]: { - type: DynamicStyleProperty.type, - options: { - ...defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE].options, - field: { - name: COUNT_PROP_NAME, - origin: FIELD_ORIGIN.SOURCE, - }, - }, - }, - [VECTOR_STYLES.LABEL_TEXT]: { - type: DynamicStyleProperty.type, - options: { - ...defaultDynamicProperties[VECTOR_STYLES.LABEL_TEXT].options, - field: { - name: COUNT_PROP_NAME, - origin: FIELD_ORIGIN.SOURCE, - }, - }, - }, - }); - return descriptor; - } - - createDefaultLayer(options) { - if (this._descriptor.requestType === RENDER_AS.HEATMAP) { - return new HeatmapLayer({ - layerDescriptor: this._createHeatmapLayerDescriptor(options), - source: this, - }); - } - - const layerDescriptor = this._createVectorLayerDescriptor(options); - const style = new VectorStyle(layerDescriptor.style, this); - return new VectorLayer({ - layerDescriptor, - source: this, - style, - }); - } - canFormatFeatureProperties() { return true; } @@ -422,57 +329,3 @@ registerSource({ ConstructorFunction: ESGeoGridSource, type: SOURCE_TYPES.ES_GEO_GRID, }); - -export const clustersLayerWizardConfig = { - description: i18n.translate('xpack.maps.source.esGridClustersDescription', { - defaultMessage: 'Geospatial data grouped in grids with metrics for each gridded cell', - }), - icon: 'logoElasticsearch', - renderWizard: ({ onPreviewSource, inspectorAdapters }) => { - const onSourceConfigChange = sourceConfig => { - if (!sourceConfig) { - onPreviewSource(null); - return; - } - - const sourceDescriptor = ESGeoGridSource.createDescriptor(sourceConfig); - const source = new ESGeoGridSource(sourceDescriptor, inspectorAdapters); - onPreviewSource(source); - }; - - return ( - - ); - }, - title: clustersTitle, -}; - -export const heatmapLayerWizardConfig = { - description: i18n.translate('xpack.maps.source.esGridHeatmapDescription', { - defaultMessage: 'Geospatial data grouped in grids to show density', - }), - icon: 'logoElasticsearch', - renderWizard: ({ onPreviewSource, inspectorAdapters }) => { - const onSourceConfigChange = sourceConfig => { - if (!sourceConfig) { - onPreviewSource(null); - return; - } - - const sourceDescriptor = ESGeoGridSource.createDescriptor(sourceConfig); - const source = new ESGeoGridSource(sourceDescriptor, inspectorAdapters); - onPreviewSource(source); - }; - - return ( - - ); - }, - title: heatmapTitle, -}; diff --git a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/heatmap_layer_wizard.tsx b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/heatmap_layer_wizard.tsx new file mode 100644 index 0000000000000..fee1a81a5c63a --- /dev/null +++ b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/heatmap_layer_wizard.tsx @@ -0,0 +1,45 @@ +/* + * 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 React from 'react'; +// @ts-ignore +import { CreateSourceEditor } from './create_source_editor'; +// @ts-ignore +import { ESGeoGridSource, heatmapTitle } from './es_geo_grid_source'; +import { LayerWizard, RenderWizardArguments } from '../../layer_wizard_registry'; +// @ts-ignore +import { HeatmapLayer } from '../../heatmap_layer'; +import { ESGeoGridSourceDescriptor } from '../../../../common/descriptor_types'; +import { RENDER_AS } from '../../../../common/constants'; + +export const heatmapLayerWizardConfig: LayerWizard = { + description: i18n.translate('xpack.maps.source.esGridHeatmapDescription', { + defaultMessage: 'Geospatial data grouped in grids to show density', + }), + icon: 'logoElasticsearch', + renderWizard: ({ previewLayer }: RenderWizardArguments) => { + const onSourceConfigChange = (sourceConfig: Partial) => { + if (!sourceConfig) { + previewLayer(null); + return; + } + + const layerDescriptor = HeatmapLayer.createDescriptor({ + sourceDescriptor: ESGeoGridSource.createDescriptor(sourceConfig), + }); + previewLayer(layerDescriptor); + }; + + return ( + + ); + }, + title: heatmapTitle, +}; diff --git a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/index.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/index.js index c2fa2356b1a3e..2db66ff411627 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/index.js +++ b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/index.js @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { - ESGeoGridSource, - clustersLayerWizardConfig, - heatmapLayerWizardConfig, -} from './es_geo_grid_source'; +export { clustersLayerWizardConfig } from './clusters_layer_wizard'; +export { ESGeoGridSource } from './es_geo_grid_source'; +export { heatmapLayerWizardConfig } from './heatmap_layer_wizard'; diff --git a/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js index 57e5afb99404b..0d15cff032410 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js +++ b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js @@ -8,29 +8,18 @@ import React from 'react'; import uuid from 'uuid/v4'; import { VECTOR_SHAPE_TYPES } from '../vector_feature_types'; -import { VectorLayer } from '../../vector_layer'; -import { CreateSourceEditor } from './create_source_editor'; import { UpdateSourceEditor } from './update_source_editor'; -import { VectorStyle } from '../../styles/vector/vector_style'; -import { getDefaultDynamicProperties } from '../../styles/vector/vector_style_defaults'; import { i18n } from '@kbn/i18n'; -import { - FIELD_ORIGIN, - SOURCE_TYPES, - COUNT_PROP_NAME, - VECTOR_STYLES, -} from '../../../../common/constants'; +import { SOURCE_TYPES } from '../../../../common/constants'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { convertToLines } from './convert_to_lines'; import { AbstractESAggSource } from '../es_agg_source'; -import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property'; -import { COLOR_GRADIENTS } from '../../styles/color_utils'; import { indexPatterns } from '../../../../../../../src/plugins/data/public'; import { registerSource } from '../source_registry'; const MAX_GEOTILE_LEVEL = 29; -const sourceTitle = i18n.translate('xpack.maps.source.pewPewTitle', { +export const sourceTitle = i18n.translate('xpack.maps.source.pewPewTitle', { defaultMessage: 'Point to point', }); @@ -109,43 +98,6 @@ export class ESPewPewSource extends AbstractESAggSource { ]; } - createDefaultLayer(options) { - const defaultDynamicProperties = getDefaultDynamicProperties(); - const styleDescriptor = VectorStyle.createDescriptor({ - [VECTOR_STYLES.LINE_COLOR]: { - type: DynamicStyleProperty.type, - options: { - ...defaultDynamicProperties[VECTOR_STYLES.LINE_COLOR].options, - field: { - name: COUNT_PROP_NAME, - origin: FIELD_ORIGIN.SOURCE, - }, - color: COLOR_GRADIENTS[0].value, - }, - }, - [VECTOR_STYLES.LINE_WIDTH]: { - type: DynamicStyleProperty.type, - options: { - ...defaultDynamicProperties[VECTOR_STYLES.LINE_WIDTH].options, - field: { - name: COUNT_PROP_NAME, - origin: FIELD_ORIGIN.SOURCE, - }, - }, - }, - }); - - return new VectorLayer({ - layerDescriptor: VectorLayer.createDescriptor({ - ...options, - sourceDescriptor: this._descriptor, - style: styleDescriptor, - }), - source: this, - style: new VectorStyle(styleDescriptor, this), - }); - } - getGeoGridPrecision(zoom) { const targetGeotileLevel = Math.ceil(zoom) + 2; return Math.min(targetGeotileLevel, MAX_GEOTILE_LEVEL); @@ -234,25 +186,3 @@ registerSource({ ConstructorFunction: ESPewPewSource, type: SOURCE_TYPES.ES_PEW_PEW, }); - -export const point2PointLayerWizardConfig = { - description: i18n.translate('xpack.maps.source.pewPewDescription', { - defaultMessage: 'Aggregated data paths between the source and destination', - }), - icon: 'logoElasticsearch', - renderWizard: ({ onPreviewSource, inspectorAdapters }) => { - const onSourceConfigChange = sourceConfig => { - if (!sourceConfig) { - onPreviewSource(null); - return; - } - - const sourceDescriptor = ESPewPewSource.createDescriptor(sourceConfig); - const source = new ESPewPewSource(sourceDescriptor, inspectorAdapters); - onPreviewSource(source); - }; - - return ; - }, - title: sourceTitle, -}; diff --git a/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/index.js b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/index.js new file mode 100644 index 0000000000000..fabde578085ab --- /dev/null +++ b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/index.js @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ESPewPewSource } from './es_pew_pew_source'; +export { point2PointLayerWizardConfig } from './point_2_point_layer_wizard'; diff --git a/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/point_2_point_layer_wizard.tsx b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/point_2_point_layer_wizard.tsx new file mode 100644 index 0000000000000..3ad6d64903d4a --- /dev/null +++ b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/point_2_point_layer_wizard.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { getDefaultDynamicProperties } from '../../styles/vector/vector_style_defaults'; +import { VectorLayer } from '../../vector_layer'; +// @ts-ignore +import { ESPewPewSource, sourceTitle } from './es_pew_pew_source'; +import { VectorStyle } from '../../styles/vector/vector_style'; +import { + FIELD_ORIGIN, + COUNT_PROP_NAME, + VECTOR_STYLES, + STYLE_TYPE, +} from '../../../../common/constants'; +// @ts-ignore +import { COLOR_GRADIENTS } from '../../styles/color_utils'; +// @ts-ignore +import { CreateSourceEditor } from './create_source_editor'; +import { LayerWizard, RenderWizardArguments } from '../../layer_wizard_registry'; +import { ColorDynamicOptions, SizeDynamicOptions } from '../../../../common/descriptor_types'; + +export const point2PointLayerWizardConfig: LayerWizard = { + description: i18n.translate('xpack.maps.source.pewPewDescription', { + defaultMessage: 'Aggregated data paths between the source and destination', + }), + icon: 'logoElasticsearch', + renderWizard: ({ previewLayer }: RenderWizardArguments) => { + const onSourceConfigChange = (sourceConfig: unknown) => { + if (!sourceConfig) { + previewLayer(null); + return; + } + + const defaultDynamicProperties = getDefaultDynamicProperties(); + const layerDescriptor = VectorLayer.createDescriptor({ + sourceDescriptor: ESPewPewSource.createDescriptor(sourceConfig), + style: VectorStyle.createDescriptor({ + [VECTOR_STYLES.LINE_COLOR]: { + type: STYLE_TYPE.DYNAMIC, + options: { + ...(defaultDynamicProperties[VECTOR_STYLES.LINE_COLOR]! + .options as ColorDynamicOptions), + field: { + name: COUNT_PROP_NAME, + origin: FIELD_ORIGIN.SOURCE, + }, + color: COLOR_GRADIENTS[0].value, + }, + }, + [VECTOR_STYLES.LINE_WIDTH]: { + type: STYLE_TYPE.DYNAMIC, + options: { + ...(defaultDynamicProperties[VECTOR_STYLES.LINE_WIDTH]! + .options as SizeDynamicOptions), + field: { + name: COUNT_PROP_NAME, + origin: FIELD_ORIGIN.SOURCE, + }, + }, + }, + }), + }); + previewLayer(layerDescriptor); + }; + + return ; + }, + title: sourceTitle, +}; diff --git a/x-pack/plugins/maps/public/layers/sources/es_search_source/es_documents_layer_wizard.tsx b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_documents_layer_wizard.tsx new file mode 100644 index 0000000000000..4a775dd78f787 --- /dev/null +++ b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_documents_layer_wizard.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import React from 'react'; +// @ts-ignore +import { CreateSourceEditor } from './create_source_editor'; +import { LayerWizard, RenderWizardArguments } from '../../layer_wizard_registry'; +// @ts-ignore +import { ESSearchSource, sourceTitle } from './es_search_source'; +import { BlendedVectorLayer } from '../../blended_vector_layer'; +import { VectorLayer } from '../../vector_layer'; +import { SCALING_TYPES } from '../../../../common/constants'; + +export function createDefaultLayerDescriptor(sourceConfig: unknown, mapColors: string[]) { + const sourceDescriptor = ESSearchSource.createDescriptor(sourceConfig); + + return sourceDescriptor.scalingType === SCALING_TYPES.CLUSTERS + ? BlendedVectorLayer.createDescriptor({ sourceDescriptor }, mapColors) + : VectorLayer.createDescriptor({ sourceDescriptor }, mapColors); +} + +export const esDocumentsLayerWizardConfig: LayerWizard = { + description: i18n.translate('xpack.maps.source.esSearchDescription', { + defaultMessage: 'Vector data from a Kibana index pattern', + }), + icon: 'logoElasticsearch', + renderWizard: ({ previewLayer, mapColors }: RenderWizardArguments) => { + const onSourceConfigChange = (sourceConfig: unknown) => { + if (!sourceConfig) { + previewLayer(null); + return; + } + + previewLayer(createDefaultLayerDescriptor(sourceConfig, mapColors)); + }; + return ; + }, + title: sourceTitle, +}; diff --git a/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts index c904280a38c85..23e3c759d73c3 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts +++ b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts @@ -8,6 +8,8 @@ import { AbstractESSource } from '../es_source'; import { ESSearchSourceDescriptor } from '../../../../common/descriptor_types'; export class ESSearchSource extends AbstractESSource { + static createDescriptor(sourceConfig: unknown): ESSearchSourceDescriptor; + constructor(sourceDescriptor: Partial, inspectorAdapters: unknown); getFieldNames(): string[]; } diff --git a/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.js index bfbcca1eb3f61..a412c49faceac 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.js +++ b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.js @@ -6,15 +6,11 @@ import _ from 'lodash'; import React from 'react'; -import uuid from 'uuid/v4'; import { VECTOR_SHAPE_TYPES } from '../vector_feature_types'; import { AbstractESSource } from '../es_source'; import { getSearchService } from '../../../kibana_services'; -import { VectorStyle } from '../../styles/vector/vector_style'; -import { VectorLayer } from '../../vector_layer'; import { hitsToGeoJson } from '../../../elasticsearch_geo_utils'; -import { CreateSourceEditor } from './create_source_editor'; import { UpdateSourceEditor } from './update_source_editor'; import { SOURCE_TYPES, @@ -27,14 +23,14 @@ import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { getSourceFields } from '../../../index_pattern_util'; import { loadIndexSettings } from './load_index_settings'; -import { BlendedVectorLayer } from '../../blended_vector_layer'; +import uuid from 'uuid/v4'; import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants'; import { ESDocField } from '../../fields/es_doc_field'; import { getField, addFieldToDSL } from '../../util/es_agg_utils'; import { registerSource } from '../source_registry'; -const sourceTitle = i18n.translate('xpack.maps.source.esSearchTitle', { +export const sourceTitle = i18n.translate('xpack.maps.source.esSearchTitle', { defaultMessage: 'Documents', }); @@ -71,56 +67,31 @@ function getDocValueAndSourceFields(indexPattern, fieldNames) { export class ESSearchSource extends AbstractESSource { static type = SOURCE_TYPES.ES_SEARCH; + static createDescriptor(descriptor) { + return { + ...descriptor, + id: descriptor.id ? descriptor.id : uuid(), + type: ESSearchSource.type, + indexPatternId: descriptor.indexPatternId, + geoField: descriptor.geoField, + filterByMapBounds: _.get(descriptor, 'filterByMapBounds', DEFAULT_FILTER_BY_MAP_BOUNDS), + tooltipProperties: _.get(descriptor, 'tooltipProperties', []), + sortField: _.get(descriptor, 'sortField', ''), + sortOrder: _.get(descriptor, 'sortOrder', SORT_ORDER.DESC), + scalingType: _.get(descriptor, 'scalingType', SCALING_TYPES.LIMIT), + topHitsSplitField: descriptor.topHitsSplitField, + topHitsSize: _.get(descriptor, 'topHitsSize', 1), + }; + } + constructor(descriptor, inspectorAdapters) { - super( - { - ...descriptor, - id: descriptor.id, - type: ESSearchSource.type, - indexPatternId: descriptor.indexPatternId, - geoField: descriptor.geoField, - filterByMapBounds: _.get(descriptor, 'filterByMapBounds', DEFAULT_FILTER_BY_MAP_BOUNDS), - tooltipProperties: _.get(descriptor, 'tooltipProperties', []), - sortField: _.get(descriptor, 'sortField', ''), - sortOrder: _.get(descriptor, 'sortOrder', SORT_ORDER.DESC), - scalingType: _.get(descriptor, 'scalingType', SCALING_TYPES.LIMIT), - topHitsSplitField: descriptor.topHitsSplitField, - topHitsSize: _.get(descriptor, 'topHitsSize', 1), - }, - inspectorAdapters - ); + super(ESSearchSource.createDescriptor(descriptor), inspectorAdapters); this._tooltipFields = this._descriptor.tooltipProperties.map(property => this.createField({ fieldName: property }) ); } - createDefaultLayer(options, mapColors) { - if (this._descriptor.scalingType === SCALING_TYPES.CLUSTERS) { - const layerDescriptor = BlendedVectorLayer.createDescriptor( - { - sourceDescriptor: this._descriptor, - ...options, - }, - mapColors - ); - const style = new VectorStyle(layerDescriptor.style, this); - return new BlendedVectorLayer({ - layerDescriptor: layerDescriptor, - source: this, - style, - }); - } - - const layerDescriptor = this._createDefaultLayerDescriptor(options, mapColors); - const style = new VectorStyle(layerDescriptor.style, this); - return new VectorLayer({ - layerDescriptor: layerDescriptor, - source: this, - style, - }); - } - createField({ fieldName }) { return new ESDocField({ fieldName, @@ -586,29 +557,3 @@ registerSource({ ConstructorFunction: ESSearchSource, type: SOURCE_TYPES.ES_SEARCH, }); - -export const esDocumentsLayerWizardConfig = { - description: i18n.translate('xpack.maps.source.esSearchDescription', { - defaultMessage: 'Vector data from a Kibana index pattern', - }), - icon: 'logoElasticsearch', - renderWizard: ({ onPreviewSource, inspectorAdapters }) => { - const onSourceConfigChange = sourceConfig => { - if (!sourceConfig) { - onPreviewSource(null); - return; - } - - const source = new ESSearchSource( - { - id: uuid(), - ...sourceConfig, - }, - inspectorAdapters - ); - onPreviewSource(source); - }; - return ; - }, - title: sourceTitle, -}; diff --git a/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.test.ts b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.test.ts deleted file mode 100644 index 66cc2ddd85404..0000000000000 --- a/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.test.ts +++ /dev/null @@ -1,33 +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('ui/new_platform'); -jest.mock('../../../kibana_services'); - -import { ESSearchSource } from './es_search_source'; -import { VectorLayer } from '../../vector_layer'; -import { SCALING_TYPES, SOURCE_TYPES } from '../../../../common/constants'; -import { ESSearchSourceDescriptor } from '../../../../common/descriptor_types'; - -const descriptor: ESSearchSourceDescriptor = { - type: SOURCE_TYPES.ES_SEARCH, - id: '1234', - indexPatternId: 'myIndexPattern', - geoField: 'myLocation', - scalingType: SCALING_TYPES.LIMIT, -}; - -describe('ES Search Source', () => { - beforeEach(() => { - require('../../../kibana_services').getUiSettings = () => ({ - get: jest.fn(), - }); - }); - it('should create a vector layer', () => { - const source = new ESSearchSource(descriptor, null); - const layer = source.createDefaultLayer(); - expect(layer instanceof VectorLayer).toEqual(true); - }); -}); diff --git a/x-pack/plugins/maps/public/layers/sources/es_search_source/index.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/index.js index 2c401ac92567e..6ae327a18b7c2 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_search_source/index.js +++ b/x-pack/plugins/maps/public/layers/sources/es_search_source/index.js @@ -4,4 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export { ESSearchSource, esDocumentsLayerWizardConfig } from './es_search_source'; +export { ESSearchSource } from './es_search_source'; +export { + createDefaultLayerDescriptor, + esDocumentsLayerWizardConfig, +} from './es_documents_layer_wizard'; diff --git a/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js index 00c3bfc5f17c6..6e9d7ad1a613b 100644 --- a/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js +++ b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { KibanaRegionmapSource, kibanaRegionMapLayerWizardConfig } from './kibana_regionmap_source'; +export { kibanaRegionMapLayerWizardConfig } from './kibana_regionmap_layer_wizard'; +export { KibanaRegionmapSource } from './kibana_regionmap_source'; diff --git a/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx new file mode 100644 index 0000000000000..4321501760faf --- /dev/null +++ b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_layer_wizard.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { LayerWizard, RenderWizardArguments } from '../../layer_wizard_registry'; +// @ts-ignore +import { KibanaRegionmapSource, sourceTitle } from './kibana_regionmap_source'; +import { VectorLayer } from '../../vector_layer'; +// @ts-ignore +import { CreateSourceEditor } from './create_source_editor'; + +export const kibanaRegionMapLayerWizardConfig: LayerWizard = { + description: i18n.translate('xpack.maps.source.kbnRegionMapDescription', { + defaultMessage: 'Vector data from hosted GeoJSON configured in kibana.yml', + }), + icon: 'logoKibana', + renderWizard: ({ previewLayer, mapColors }: RenderWizardArguments) => { + const onSourceConfigChange = (sourceConfig: unknown) => { + const sourceDescriptor = KibanaRegionmapSource.createDescriptor(sourceConfig); + const layerDescriptor = VectorLayer.createDescriptor({ sourceDescriptor }, mapColors); + previewLayer(layerDescriptor); + }; + + return ; + }, + title: sourceTitle, +}; diff --git a/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js index be333f8ee85a4..fb5a2e4f42f1d 100644 --- a/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js +++ b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js @@ -5,8 +5,6 @@ */ import { AbstractVectorSource } from '../vector_source'; -import React from 'react'; -import { CreateSourceEditor } from './create_source_editor'; import { getKibanaRegionList } from '../../../meta'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; @@ -14,7 +12,7 @@ import { FIELD_ORIGIN, SOURCE_TYPES } from '../../../../common/constants'; import { KibanaRegionField } from '../../fields/kibana_region_field'; import { registerSource } from '../source_registry'; -const sourceTitle = i18n.translate('xpack.maps.source.kbnRegionMapTitle', { +export const sourceTitle = i18n.translate('xpack.maps.source.kbnRegionMapTitle', { defaultMessage: 'Configured GeoJSON', }); @@ -101,20 +99,3 @@ registerSource({ ConstructorFunction: KibanaRegionmapSource, type: SOURCE_TYPES.REGIONMAP_FILE, }); - -export const kibanaRegionMapLayerWizardConfig = { - description: i18n.translate('xpack.maps.source.kbnRegionMapDescription', { - defaultMessage: 'Vector data from hosted GeoJSON configured in kibana.yml', - }), - icon: 'logoKibana', - renderWizard: ({ onPreviewSource, inspectorAdapters }) => { - const onSourceConfigChange = sourceConfig => { - const sourceDescriptor = KibanaRegionmapSource.createDescriptor(sourceConfig); - const source = new KibanaRegionmapSource(sourceDescriptor, inspectorAdapters); - onPreviewSource(source); - }; - - return ; - }, - title: sourceTitle, -}; diff --git a/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js b/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js index 9fd7f088032ca..cc89091605456 100644 --- a/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js +++ b/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { KibanaTilemapSource, kibanaBasemapLayerWizardConfig } from './kibana_tilemap_source'; +export { kibanaBasemapLayerWizardConfig } from './kibana_base_map_layer_wizard'; +export { KibanaTilemapSource } from './kibana_tilemap_source'; diff --git a/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_base_map_layer_wizard.tsx b/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_base_map_layer_wizard.tsx new file mode 100644 index 0000000000000..aeea2d6084f84 --- /dev/null +++ b/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_base_map_layer_wizard.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { LayerWizard, RenderWizardArguments } from '../../layer_wizard_registry'; +// @ts-ignore +import { CreateSourceEditor } from './create_source_editor'; +// @ts-ignore +import { KibanaTilemapSource, sourceTitle } from './kibana_tilemap_source'; +import { TileLayer } from '../../tile_layer'; + +export const kibanaBasemapLayerWizardConfig: LayerWizard = { + description: i18n.translate('xpack.maps.source.kbnTMSDescription', { + defaultMessage: 'Tile map service configured in kibana.yml', + }), + icon: 'logoKibana', + renderWizard: ({ previewLayer }: RenderWizardArguments) => { + const onSourceConfigChange = () => { + const layerDescriptor = TileLayer.createDescriptor({ + sourceDescriptor: KibanaTilemapSource.createDescriptor(), + }); + previewLayer(layerDescriptor); + }; + return ; + }, + title: sourceTitle, +}; diff --git a/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js b/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js index bbb653eff32e2..7dc1d454a1c52 100644 --- a/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js +++ b/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js @@ -3,10 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; + import { AbstractTMSSource } from '../tms_source'; -import { TileLayer } from '../../tile_layer'; -import { CreateSourceEditor } from './create_source_editor'; import { getKibanaTileMap } from '../../../meta'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; @@ -14,7 +12,7 @@ import _ from 'lodash'; import { SOURCE_TYPES } from '../../../../common/constants'; import { registerSource } from '../source_registry'; -const sourceTitle = i18n.translate('xpack.maps.source.kbnTMSTitle', { +export const sourceTitle = i18n.translate('xpack.maps.source.kbnTMSTitle', { defaultMessage: 'Configured Tile Map Service', }); @@ -42,20 +40,6 @@ export class KibanaTilemapSource extends AbstractTMSSource { ]; } - _createDefaultLayerDescriptor(options) { - return TileLayer.createDescriptor({ - sourceDescriptor: this._descriptor, - ...options, - }); - } - - createDefaultLayer(options) { - return new TileLayer({ - layerDescriptor: this._createDefaultLayerDescriptor(options), - source: this, - }); - } - async getUrlTemplate() { const tilemap = getKibanaTileMap(); if (!tilemap.url) { @@ -88,19 +72,3 @@ registerSource({ ConstructorFunction: KibanaTilemapSource, type: SOURCE_TYPES.KIBANA_TILEMAP, }); - -export const kibanaBasemapLayerWizardConfig = { - description: i18n.translate('xpack.maps.source.kbnTMSDescription', { - defaultMessage: 'Tile map service configured in kibana.yml', - }), - icon: 'logoKibana', - renderWizard: ({ onPreviewSource, inspectorAdapters }) => { - const onSourceConfigChange = () => { - const sourceDescriptor = KibanaTilemapSource.createDescriptor(); - const source = new KibanaTilemapSource(sourceDescriptor, inspectorAdapters); - onPreviewSource(source); - }; - return ; - }, - title: sourceTitle, -}; diff --git a/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/layer_wizard.tsx b/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/layer_wizard.tsx index dfdea1489d50c..c94fec3deac67 100644 --- a/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/layer_wizard.tsx +++ b/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/layer_wizard.tsx @@ -12,30 +12,20 @@ import { } from './mvt_single_layer_vector_source_editor'; import { MVTSingleLayerVectorSource, sourceTitle } from './mvt_single_layer_vector_source'; import { LayerWizard, RenderWizardArguments } from '../../layer_wizard_registry'; -import { SOURCE_TYPES } from '../../../../common/constants'; +import { TiledVectorLayer } from '../../tiled_vector_layer'; export const mvtVectorSourceWizardConfig: LayerWizard = { description: i18n.translate('xpack.maps.source.mvtVectorSourceWizard', { defaultMessage: 'Vector source wizard', }), icon: 'grid', - renderWizard: ({ onPreviewSource, inspectorAdapters }: RenderWizardArguments) => { - const onSourceConfigChange = ({ - urlTemplate, - layerName, - minSourceZoom, - maxSourceZoom, - }: MVTSingleLayerVectorSourceConfig) => { - const sourceDescriptor = MVTSingleLayerVectorSource.createDescriptor({ - urlTemplate, - layerName, - minSourceZoom, - maxSourceZoom, - type: SOURCE_TYPES.MVT_SINGLE_LAYER, - }); - const source = new MVTSingleLayerVectorSource(sourceDescriptor, inspectorAdapters); - onPreviewSource(source); + renderWizard: ({ previewLayer, mapColors }: RenderWizardArguments) => { + const onSourceConfigChange = (sourceConfig: MVTSingleLayerVectorSourceConfig) => { + const sourceDescriptor = MVTSingleLayerVectorSource.createDescriptor(sourceConfig); + const layerDescriptor = TiledVectorLayer.createDescriptor({ sourceDescriptor }, mapColors); + previewLayer(layerDescriptor); }; + return ; }, title: sourceTitle, diff --git a/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts b/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts index a73cfbdc0d043..58e6e39aaa1f9 100644 --- a/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts +++ b/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; import uuid from 'uuid/v4'; import { AbstractSource, ImmutableSourceProperty } from '../source'; -import { TiledVectorLayer } from '../../tiled_vector_layer'; import { GeoJsonWithMeta, ITiledSingleLayerVectorSource } from '../vector_source'; import { MAX_ZOOM, MIN_ZOOM, SOURCE_TYPES } from '../../../../common/constants'; import { VECTOR_SHAPE_TYPES } from '../vector_feature_types'; @@ -17,11 +16,10 @@ import { getDataSourceLabel, getUrlLabel } from '../../../../common/i18n_getters import { MapExtent, TiledSingleLayerVectorSourceDescriptor, - VectorLayerDescriptor, VectorSourceRequestMeta, VectorSourceSyncMeta, } from '../../../../common/descriptor_types'; -import { VectorLayerArguments } from '../../vector_layer'; +import { MVTSingleLayerVectorSourceConfig } from './mvt_single_layer_vector_source_editor'; export const sourceTitle = i18n.translate( 'xpack.maps.source.MVTSingleLayerVectorSource.sourceTitle', @@ -37,7 +35,7 @@ export class MVTSingleLayerVectorSource extends AbstractSource layerName, minSourceZoom, maxSourceZoom, - }: TiledSingleLayerVectorSourceDescriptor) { + }: MVTSingleLayerVectorSourceConfig) { return { type: SOURCE_TYPES.MVT_SINGLE_LAYER, id: uuid(), @@ -66,22 +64,6 @@ export class MVTSingleLayerVectorSource extends AbstractSource return []; } - createDefaultLayer(options?: Partial): TiledVectorLayer { - const layerDescriptor: Partial = { - sourceDescriptor: this._descriptor, - ...options, - }; - const normalizedLayerDescriptor: VectorLayerDescriptor = TiledVectorLayer.createDescriptor( - layerDescriptor, - [] - ); - const vectorLayerArguments: VectorLayerArguments = { - layerDescriptor: normalizedLayerDescriptor, - source: this, - }; - return new TiledVectorLayer(vectorLayerArguments); - } - getGeoJsonWithMeta( layerName: 'string', searchFilters: unknown[], diff --git a/x-pack/plugins/maps/public/layers/sources/source.ts b/x-pack/plugins/maps/public/layers/sources/source.ts index 1cd84010159ab..af934d7464f61 100644 --- a/x-pack/plugins/maps/public/layers/sources/source.ts +++ b/x-pack/plugins/maps/public/layers/sources/source.ts @@ -13,8 +13,7 @@ import { Adapters } from 'src/plugins/inspector/public'; // @ts-ignore import { copyPersistentState } from '../../reducers/util'; -import { LayerDescriptor, SourceDescriptor } from '../../../common/descriptor_types'; -import { ILayer } from '../layer'; +import { SourceDescriptor } from '../../../common/descriptor_types'; import { IField } from '../fields/field'; import { MAX_ZOOM, MIN_ZOOM } from '../../../common/constants'; @@ -37,7 +36,6 @@ export type PreIndexedShape = { export type FieldFormatter = (value: string | number | null | undefined | boolean) => string; export interface ISource { - createDefaultLayer(options?: Partial): ILayer; destroy(): void; getDisplayName(): Promise; getInspectorAdapters(): Adapters | undefined; @@ -59,7 +57,6 @@ export interface ISource { getIndexPatternIds(): string[]; getQueryableIndexPatternIds(): string[]; getGeoGridPrecision(zoom: number): number; - shouldBeIndexed(): boolean; getPreIndexedShape(): Promise; createFieldFormatter(field: IField): Promise; getValueSuggestions(field: IField, query: string): Promise; @@ -99,10 +96,6 @@ export class AbstractSource implements ISource { return this._inspectorAdapters; } - createDefaultLayer(options?: Partial): ILayer { - throw new Error(`Source#createDefaultLayer not implemented`); - } - async getDisplayName(): Promise { return ''; } @@ -155,10 +148,6 @@ export class AbstractSource implements ISource { return false; } - shouldBeIndexed(): boolean { - return false; - } - isESSource(): boolean { return false; } diff --git a/x-pack/plugins/maps/public/layers/sources/vector_source/vector_source.js b/x-pack/plugins/maps/public/layers/sources/vector_source/vector_source.js index 509584cbc415a..12e1780d9cad5 100644 --- a/x-pack/plugins/maps/public/layers/sources/vector_source/vector_source.js +++ b/x-pack/plugins/maps/public/layers/sources/vector_source/vector_source.js @@ -4,9 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { VectorLayer } from '../../vector_layer'; import { TooltipProperty } from '../../tooltips/tooltip_property'; -import { VectorStyle } from '../../styles/vector/vector_style'; import { AbstractSource } from './../source'; import * as topojson from 'topojson-client'; import _ from 'lodash'; @@ -74,30 +72,10 @@ export class AbstractVectorSource extends AbstractSource { return this.createField({ fieldName: name }); } - _createDefaultLayerDescriptor(options, mapColors) { - return VectorLayer.createDescriptor( - { - sourceDescriptor: this._descriptor, - ...options, - }, - mapColors - ); - } - _getTooltipPropertyNames() { return this._tooltipFields.map(field => field.getName()); } - createDefaultLayer(options, mapColors) { - const layerDescriptor = this._createDefaultLayerDescriptor(options, mapColors); - const style = new VectorStyle(layerDescriptor.style, this); - return new VectorLayer({ - layerDescriptor: layerDescriptor, - source: this, - style, - }); - } - isFilterByMapBounds() { return false; } diff --git a/x-pack/plugins/maps/public/layers/sources/wms_source/index.js b/x-pack/plugins/maps/public/layers/sources/wms_source/index.js index daae552a6f772..792e7e862826c 100644 --- a/x-pack/plugins/maps/public/layers/sources/wms_source/index.js +++ b/x-pack/plugins/maps/public/layers/sources/wms_source/index.js @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { WMSSource, wmsLayerWizardConfig } from './wms_source'; +export { wmsLayerWizardConfig } from './wms_layer_wizard'; +export { WMSSource } from './wms_source'; diff --git a/x-pack/plugins/maps/public/layers/sources/wms_source/wms_layer_wizard.tsx b/x-pack/plugins/maps/public/layers/sources/wms_source/wms_layer_wizard.tsx new file mode 100644 index 0000000000000..fbf5e25c78b17 --- /dev/null +++ b/x-pack/plugins/maps/public/layers/sources/wms_source/wms_layer_wizard.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +// @ts-ignore +import { WMSCreateSourceEditor } from './wms_create_source_editor'; +// @ts-ignore +import { sourceTitle, WMSSource } from './wms_source'; +import { LayerWizard, RenderWizardArguments } from '../../layer_wizard_registry'; +import { TileLayer } from '../../tile_layer'; + +export const wmsLayerWizardConfig: LayerWizard = { + description: i18n.translate('xpack.maps.source.wmsDescription', { + defaultMessage: 'Maps from OGC Standard WMS', + }), + icon: 'grid', + renderWizard: ({ previewLayer }: RenderWizardArguments) => { + const onSourceConfigChange = (sourceConfig: unknown) => { + if (!sourceConfig) { + previewLayer(null); + return; + } + + const layerDescriptor = TileLayer.createDescriptor({ + sourceDescriptor: WMSSource.createDescriptor(sourceConfig), + }); + previewLayer(layerDescriptor); + }; + return ; + }, + title: sourceTitle, +}; diff --git a/x-pack/plugins/maps/public/layers/sources/wms_source/wms_source.js b/x-pack/plugins/maps/public/layers/sources/wms_source/wms_source.js index 33f764784124e..cb8f9c34e2b57 100644 --- a/x-pack/plugins/maps/public/layers/sources/wms_source/wms_source.js +++ b/x-pack/plugins/maps/public/layers/sources/wms_source/wms_source.js @@ -4,18 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; - import { AbstractTMSSource } from '../tms_source'; -import { TileLayer } from '../../tile_layer'; -import { WMSCreateSourceEditor } from './wms_create_source_editor'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel, getUrlLabel } from '../../../../common/i18n_getters'; import { WmsClient } from './wms_client'; import { SOURCE_TYPES } from '../../../../common/constants'; import { registerSource } from '../source_registry'; -const sourceTitle = i18n.translate('xpack.maps.source.wmsTitle', { +export const sourceTitle = i18n.translate('xpack.maps.source.wmsTitle', { defaultMessage: 'Web Map Service', }); @@ -52,20 +48,6 @@ export class WMSSource extends AbstractTMSSource { ]; } - _createDefaultLayerDescriptor(options) { - return TileLayer.createDescriptor({ - sourceDescriptor: this._descriptor, - ...options, - }); - } - - createDefaultLayer(options) { - return new TileLayer({ - layerDescriptor: this._createDefaultLayerDescriptor(options), - source: this, - }); - } - async getDisplayName() { return this._descriptor.serviceUrl; } @@ -94,24 +76,3 @@ registerSource({ ConstructorFunction: WMSSource, type: SOURCE_TYPES.WMS, }); - -export const wmsLayerWizardConfig = { - description: i18n.translate('xpack.maps.source.wmsDescription', { - defaultMessage: 'Maps from OGC Standard WMS', - }), - icon: 'grid', - renderWizard: ({ onPreviewSource, inspectorAdapters }) => { - const onSourceConfigChange = sourceConfig => { - if (!sourceConfig) { - onPreviewSource(null); - return; - } - - const sourceDescriptor = WMSSource.createDescriptor(sourceConfig); - const source = new WMSSource(sourceDescriptor, inspectorAdapters); - onPreviewSource(source); - }; - return ; - }, - title: sourceTitle, -}; diff --git a/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/layer_wizard.tsx b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/layer_wizard.tsx index 8b1ed588c8dd1..e970c75fa7adf 100644 --- a/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/layer_wizard.tsx +++ b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/layer_wizard.tsx @@ -9,17 +9,19 @@ import React from 'react'; import { XYZTMSEditor, XYZTMSSourceConfig } from './xyz_tms_editor'; import { XYZTMSSource, sourceTitle } from './xyz_tms_source'; import { LayerWizard, RenderWizardArguments } from '../../layer_wizard_registry'; +import { TileLayer } from '../../tile_layer'; export const tmsLayerWizardConfig: LayerWizard = { description: i18n.translate('xpack.maps.source.ems_xyzDescription', { defaultMessage: 'Tile map service configured in interface', }), icon: 'grid', - renderWizard: ({ onPreviewSource }: RenderWizardArguments) => { + renderWizard: ({ previewLayer }: RenderWizardArguments) => { const onSourceConfigChange = (sourceConfig: XYZTMSSourceConfig) => { - const sourceDescriptor = XYZTMSSource.createDescriptor(sourceConfig); - const source = new XYZTMSSource(sourceDescriptor); - onPreviewSource(source); + const layerDescriptor = TileLayer.createDescriptor({ + sourceDescriptor: XYZTMSSource.createDescriptor(sourceConfig), + }); + previewLayer(layerDescriptor); }; return ; }, diff --git a/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/xyz_tms_source.test.ts b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/xyz_tms_source.test.ts index 4031a18bff7cb..b1ba6fc6f6f8e 100644 --- a/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/xyz_tms_source.test.ts +++ b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/xyz_tms_source.test.ts @@ -5,8 +5,6 @@ */ import { XYZTMSSource } from './xyz_tms_source'; -import { ILayer } from '../../layer'; -import { TileLayer } from '../../tile_layer'; import { SOURCE_TYPES } from '../../../../common/constants'; import { XYZTMSSourceDescriptor } from '../../../../common/descriptor_types'; @@ -16,12 +14,6 @@ const descriptor: XYZTMSSourceDescriptor = { id: 'foobar', }; describe('xyz Tilemap Source', () => { - it('should create a tile-layer', () => { - const source = new XYZTMSSource(descriptor); - const layer: ILayer = source.createDefaultLayer(); - expect(layer instanceof TileLayer).toEqual(true); - }); - it('should echo url template for url template', async () => { const source = new XYZTMSSource(descriptor); const template = await source.getUrlTemplate(); diff --git a/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/xyz_tms_source.ts b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/xyz_tms_source.ts index 77f8d88a8c0ab..b1003d25fb759 100644 --- a/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/xyz_tms_source.ts +++ b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/xyz_tms_source.ts @@ -5,15 +5,13 @@ */ import { i18n } from '@kbn/i18n'; -import { TileLayer } from '../../tile_layer'; import { getDataSourceLabel, getUrlLabel } from '../../../../common/i18n_getters'; import { SOURCE_TYPES } from '../../../../common/constants'; import { registerSource } from '../source_registry'; import { AbstractTMSSource } from '../tms_source'; -import { LayerDescriptor, XYZTMSSourceDescriptor } from '../../../../common/descriptor_types'; +import { XYZTMSSourceDescriptor } from '../../../../common/descriptor_types'; import { Attribution, ImmutableSourceProperty } from '../source'; import { XYZTMSSourceConfig } from './xyz_tms_editor'; -import { ILayer } from '../../layer'; export const sourceTitle = i18n.translate('xpack.maps.source.ems_xyzTitle', { defaultMessage: 'Tile Map Service', @@ -49,17 +47,6 @@ export class XYZTMSSource extends AbstractTMSSource { ]; } - createDefaultLayer(options?: LayerDescriptor): ILayer { - const layerDescriptor: LayerDescriptor = TileLayer.createDescriptor({ - sourceDescriptor: this._descriptor, - ...options, - }); - return new TileLayer({ - layerDescriptor, - source: this, - }); - } - async getDisplayName(): Promise { return this._descriptor.urlTemplate; } diff --git a/x-pack/plugins/maps/public/layers/tile_layer.test.ts b/x-pack/plugins/maps/public/layers/tile_layer.test.ts index a7e8be9fc4b46..d536b18af4aad 100644 --- a/x-pack/plugins/maps/public/layers/tile_layer.test.ts +++ b/x-pack/plugins/maps/public/layers/tile_layer.test.ts @@ -23,9 +23,6 @@ class MockTileSource extends AbstractTMSSource implements ITMSSource { super(descriptor, {}); this._descriptor = descriptor; } - createDefaultLayer(): ILayer { - throw new Error('not implemented'); - } async getDisplayName(): Promise { return this._descriptor.urlTemplate; diff --git a/x-pack/legacy/plugins/rollup/README.md b/x-pack/plugins/rollup/README.md similarity index 76% rename from x-pack/legacy/plugins/rollup/README.md rename to x-pack/plugins/rollup/README.md index 3647be38b6a09..b43f4d5981409 100644 --- a/x-pack/legacy/plugins/rollup/README.md +++ b/x-pack/plugins/rollup/README.md @@ -14,7 +14,7 @@ The rest of this doc dives into the implementation details of each of the above ## Create and manage rollup jobs -The most straight forward part of this plugin! A new app called Rollup Jobs is registered in the Management section and follows a typical CRUD UI pattern. This app allows users to create, start, stop, clone, and delete rollup jobs. There is no way to edit an existing rollup job; instead, the UI offers a cloning ability. The client-side portion of this app lives [here](../../../plugins/rollup/public/crud_app) and uses endpoints registered [here](server/routes/api/jobs.js). +The most straight forward part of this plugin! A new app called Rollup Jobs is registered in the Management section and follows a typical CRUD UI pattern. This app allows users to create, start, stop, clone, and delete rollup jobs. There is no way to edit an existing rollup job; instead, the UI offers a cloning ability. The client-side portion of this app lives in [public/crud_app](public/crud_app) and uses endpoints registered in [(server/routes/api/jobs](server/routes/api/jobs). Refer to the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-getting-started.html) to understand rollup indices and how to create rollup jobs. @@ -22,22 +22,22 @@ Refer to the [Elasticsearch documentation](https://www.elastic.co/guide/en/elast Kibana uses index patterns to consume and visualize rollup indices. Typically, Kibana can inspect the indices captured by an index pattern, identify its aggregations and fields, and determine how to consume the data. Rollup indices don't contain this type of information, so we predefine how to consume a rollup index pattern with the type and typeMeta fields on the index pattern saved object. All rollup index patterns have `type` defined as "rollup" and `typeMeta` defined as an object of the index pattern's capabilities. -In the Index Pattern app, the "Create index pattern" button includes a context menu when a rollup index is detected. This menu offers items for creating a standard index pattern and a rollup index pattern. A [rollup config is registered to index pattern creation extension point](../../../plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js). The context menu behavior in particular uses the `getIndexPatternCreationOption()` method. When the user chooses to create a rollup index pattern, this config changes the behavior of the index pattern creation wizard: +In the Index Pattern app, the "Create index pattern" button includes a context menu when a rollup index is detected. This menu offers items for creating a standard index pattern and a rollup index pattern. A [rollup config is registered to index pattern creation extension point](public/index_pattern_creation/rollup_index_pattern_creation_config.js). The context menu behavior in particular uses the `getIndexPatternCreationOption()` method. When the user chooses to create a rollup index pattern, this config changes the behavior of the index pattern creation wizard: 1. Adds a `Rollup` badge to rollup indices using `getIndexTags()`. 2. Enforces index pattern rules using `checkIndicesForErrors()`. Rollup index patterns must match **one** rollup index, and optionally, any number of regular indices. A rollup index pattern configured with one or more regular indices is known as a "hybrid" index pattern. This allows the user to visualize historical (rollup) data and live (regular) data in the same visualization. -3. Routes to this plugin's [rollup `_fields_for_wildcard` endpoint](server/routes/api/index_patterns.js), instead of the standard one, using `getFetchForWildcardOptions()`, so that the internal rollup data field names are mapped to the original field names. +3. Routes to this plugin's [rollup `_fields_for_wildcard` endpoint](server/routes/api/index_patterns/register_fields_for_wildcard_route.ts), instead of the standard one, using `getFetchForWildcardOptions()`, so that the internal rollup data field names are mapped to the original field names. 4. Writes additional information about aggregations, fields, histogram interval, and date histogram interval and timezone to the rollup index pattern saved object using `getIndexPatternMappings()`. This collection of information is referred to as its "capabilities". -Once a rollup index pattern is created, it is tagged with `Rollup` in the list of index patterns, and its details page displays capabilities information. This is done by registering [yet another config for the index pattern list](../../../plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js) extension points. +Once a rollup index pattern is created, it is tagged with `Rollup` in the list of index patterns, and its details page displays capabilities information. This is done by registering [yet another config for the index pattern list](public/index_pattern_list/rollup_index_pattern_list_config.js) extension points. ## Create visualizations from rollup index patterns This plugin enables the user to create visualizations from rollup data using the Visualize app, excluding TSVB, Vega, and Timelion. When Visualize sends search requests, this plugin routes the requests to the [Elasticsearch rollup search endpoint](https://www.elastic.co/guide/en/elasticsearch/reference/current/rollup-search.html), which searches the special document structure within rollup indices. The visualization options available to users are based on the capabilities of the rollup index pattern they're visualizing. -Routing to the Elasticsearch rollup search endpoint is done by creating an extension point in Courier, effectively allowing multiple "search strategies" to be registered. A [rollup search strategy](../../../plugins/rollup/public/search/register.js) is registered by this plugin that queries [this plugin's rollup search endpoint](server/routes/api/search.js). +Routing to the Elasticsearch rollup search endpoint is done by creating an extension point in Courier, effectively allowing multiple "search strategies" to be registered. A [rollup search strategy](public/search/register.js) is registered by this plugin that queries [this plugin's rollup search endpoint](server/routes/api/search.js). -Limiting visualization editor options is done by [registering configs](../../../plugins/rollup/public/visualize/index.js) to various vis extension points. These configs use information stored on the rollup index pattern to limit: +Limiting visualization editor options is done by [registering configs](public/visualize/index.js) to various vis extension points. These configs use information stored on the rollup index pattern to limit: * Available aggregation types * Available fields for a particular aggregation * Default and base interval for histogram aggregation @@ -47,6 +47,6 @@ Limiting visualization editor options is done by [registering configs](../../../ In Index Management, similar to system indices, rollup indices are hidden by default. A toggle is provided to show rollup indices and add a badge to the table rows. This is done by using Index Management's extension points. -The toggle and badge are registered on client-side [here](../../../plugins/rollup/public/extend_index_management/index.js). +The toggle and badge are registered on the client-side in [public/extend_index_management](public/extend_index_management). -Additional data needed to filter rollup indices in Index Management is provided with a [data enricher](rollup_data_enricher.js). +Additional data needed to filter rollup indices in Index Management is provided with a [data enricher](rollup_data_enricher.ts). \ No newline at end of file diff --git a/x-pack/plugins/rollup/common/index.ts b/x-pack/plugins/rollup/common/index.ts index aeffa3dc3959f..e94726a6f3d95 100644 --- a/x-pack/plugins/rollup/common/index.ts +++ b/x-pack/plugins/rollup/common/index.ts @@ -4,6 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +import { LicenseType } from '../../licensing/common/types'; + +const basicLicense: LicenseType = 'basic'; + +export const PLUGIN = { + ID: 'rollup', + minimumLicenseType: basicLicense, +}; + export const CONFIG_ROLLUPS = 'rollups:enableIndexPatterns'; export const API_BASE_PATH = '/api/rollup'; diff --git a/x-pack/plugins/rollup/kibana.json b/x-pack/plugins/rollup/kibana.json index 8f832f6c6a345..4c7dcb48a4d3f 100644 --- a/x-pack/plugins/rollup/kibana.json +++ b/x-pack/plugins/rollup/kibana.json @@ -4,6 +4,17 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "optionalPlugins": ["home", "indexManagement", "indexPatternManagement", "usageCollection"], - "requiredPlugins": ["management", "data"] + "requiredPlugins": [ + "indexPatternManagement", + "management", + "licensing", + "data" + ], + "optionalPlugins": [ + "home", + "indexManagement", + "usageCollection", + "visTypeTimeseries" + ], + "configPath": ["xpack", "rollup"] } diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js index 4a55c4679c3d8..eca624e16cb86 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js @@ -42,7 +42,7 @@ export const stepIds = [ * 1. getDefaultFields: (overrides) => object * 2. fieldValidations * - * See x-pack/plugins/rollup/public/crud_app/services/jobs.js for more information on override's shape + * See rollup/public/crud_app/services/jobs.js for more information on override's shape */ export const stepIdToStepConfigMap = { [STEP_LOGISTICS]: { diff --git a/x-pack/plugins/rollup/public/crud_app/services/track_ui_metric.ts b/x-pack/plugins/rollup/public/crud_app/services/track_ui_metric.ts index aa1cc2dfea323..5d9340a140500 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/track_ui_metric.ts +++ b/x-pack/plugins/rollup/public/crud_app/services/track_ui_metric.ts @@ -16,6 +16,9 @@ export { METRIC_TYPE }; export function trackUserRequest(request: Promise, actionType: string) { // Only track successful actions. return request.then(response => { + // NOTE: METRIC_TYPE.LOADED is probably the wrong metric type here. The correct metric type + // is more likely METRIC_TYPE.APPLICATION_USAGE. This change was introduced in + // https://github.com/elastic/kibana/pull/41113/files#diff-58ac12bdd1a3a05a24e69ff20633c482R20 trackUiMetric(METRIC_TYPE.LOADED, actionType); // We return the response immediately without waiting for the tracking request to resolve, // to avoid adding additional latency. diff --git a/x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js b/x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js index 42c950f0b0d74..9d81abf70a55d 100644 --- a/x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js +++ b/x-pack/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js @@ -5,21 +5,34 @@ */ import React from 'react'; +import { i18n } from '@kbn/i18n'; import { EuiCallOut } from '@elastic/eui'; export const RollupPrompt = () => (

- Kibana's support for rollup index patterns is in beta. You might encounter issues using - these patterns in saved searches, visualizations, and dashboards. They are not supported in - some advanced features, such as Timelion, and Machine Learning. + {i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph1Text', + { + defaultMessage: + "Kibana's support for rollup index patterns is in beta. You might encounter issues using " + + 'these patterns in saved searches, visualizations, and dashboards. They are not supported in ' + + 'some advanced features, such as Timelion, and Machine Learning.', + } + )}

- You can match a rollup index pattern against one rollup index and zero or more regular - indices. A rollup index pattern has limited metrics, fields, intervals, and aggregations. A - rollup index is limited to indices that have one job configuration, or multiple jobs with - compatible configurations. + {i18n.translate( + 'xpack.rollupJobs.editRollupIndexPattern.rollupPrompt.betaCalloutParagraph2Text', + { + defaultMessage: + 'You can match a rollup index pattern against one rollup index and zero or more regular ' + + 'indices. A rollup index pattern has limited metrics, fields, intervals, and aggregations. A ' + + 'rollup index is limited to indices that have one job configuration, or multiple jobs with ' + + 'compatible configurations.', + } + )}

); diff --git a/x-pack/legacy/plugins/rollup/server/client/elasticsearch_rollup.ts b/x-pack/plugins/rollup/server/client/elasticsearch_rollup.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/client/elasticsearch_rollup.ts rename to x-pack/plugins/rollup/server/client/elasticsearch_rollup.ts diff --git a/x-pack/legacy/plugins/rollup/server/collectors/index.ts b/x-pack/plugins/rollup/server/collectors/index.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/collectors/index.ts rename to x-pack/plugins/rollup/server/collectors/index.ts diff --git a/x-pack/legacy/plugins/rollup/server/collectors/register.ts b/x-pack/plugins/rollup/server/collectors/register.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/collectors/register.ts rename to x-pack/plugins/rollup/server/collectors/register.ts diff --git a/x-pack/plugins/rollup/server/config.ts b/x-pack/plugins/rollup/server/config.ts new file mode 100644 index 0000000000000..6d02600521c3a --- /dev/null +++ b/x-pack/plugins/rollup/server/config.ts @@ -0,0 +1,13 @@ +/* + * 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 { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), +}); + +export type RollupConfig = TypeOf; diff --git a/x-pack/plugins/rollup/server/index.ts b/x-pack/plugins/rollup/server/index.ts index 4056842453776..78859a959a1e0 100644 --- a/x-pack/plugins/rollup/server/index.ts +++ b/x-pack/plugins/rollup/server/index.ts @@ -4,9 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from 'src/core/server'; +import { PluginInitializerContext, PluginConfigDescriptor } from 'src/core/server'; import { RollupPlugin } from './plugin'; +import { configSchema, RollupConfig } from './config'; -export const plugin = (initContext: PluginInitializerContext) => new RollupPlugin(initContext); +export const plugin = (pluginInitializerContext: PluginInitializerContext) => + new RollupPlugin(pluginInitializerContext); -export { RollupSetup } from './plugin'; +export const config: PluginConfigDescriptor = { + schema: configSchema, +}; diff --git a/x-pack/legacy/plugins/rollup/server/lib/__tests__/fixtures/index.js b/x-pack/plugins/rollup/server/lib/__tests__/fixtures/index.js similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/__tests__/fixtures/index.js rename to x-pack/plugins/rollup/server/lib/__tests__/fixtures/index.js diff --git a/x-pack/plugins/rollup/server/lib/__tests__/fixtures/jobs.js b/x-pack/plugins/rollup/server/lib/__tests__/fixtures/jobs.js new file mode 100644 index 0000000000000..c03b7c33abe0a --- /dev/null +++ b/x-pack/plugins/rollup/server/lib/__tests__/fixtures/jobs.js @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const jobs = [ + { + job_id: 'foo1', + rollup_index: 'foo_rollup', + index_pattern: 'foo-*', + fields: { + node: [ + { + agg: 'terms', + }, + ], + temperature: [ + { + agg: 'min', + }, + { + agg: 'max', + }, + { + agg: 'sum', + }, + ], + timestamp: [ + { + agg: 'date_histogram', + time_zone: 'UTC', + interval: '1h', + delay: '7d', + }, + ], + voltage: [ + { + agg: 'histogram', + interval: 5, + }, + { + agg: 'sum', + }, + ], + }, + }, + { + job_id: 'foo2', + rollup_index: 'foo_rollup', + index_pattern: 'foo-*', + fields: { + host: [ + { + agg: 'terms', + }, + ], + timestamp: [ + { + agg: 'date_histogram', + time_zone: 'UTC', + interval: '1h', + delay: '7d', + }, + ], + voltage: [ + { + agg: 'histogram', + interval: 20, + }, + ], + }, + }, + { + job_id: 'foo3', + rollup_index: 'foo_rollup', + index_pattern: 'foo-*', + fields: { + timestamp: [ + { + agg: 'date_histogram', + time_zone: 'PST', + interval: '1h', + delay: '7d', + }, + ], + voltage: [ + { + agg: 'histogram', + interval: 5, + }, + { + agg: 'sum', + }, + ], + }, + }, +]; diff --git a/x-pack/legacy/plugins/rollup/server/lib/__tests__/jobs_compatibility.js b/x-pack/plugins/rollup/server/lib/__tests__/jobs_compatibility.js similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/__tests__/jobs_compatibility.js rename to x-pack/plugins/rollup/server/lib/__tests__/jobs_compatibility.js diff --git a/x-pack/plugins/rollup/server/lib/format_es_error.ts b/x-pack/plugins/rollup/server/lib/format_es_error.ts new file mode 100644 index 0000000000000..9dde027cd6949 --- /dev/null +++ b/x-pack/plugins/rollup/server/lib/format_es_error.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +function extractCausedByChain( + causedBy: Record = {}, + accumulator: string[] = [] +): string[] { + const { reason, caused_by } = causedBy; // eslint-disable-line @typescript-eslint/camelcase + + if (reason) { + accumulator.push(reason); + } + + // eslint-disable-next-line @typescript-eslint/camelcase + if (caused_by) { + return extractCausedByChain(caused_by, accumulator); + } + + return accumulator; +} + +/** + * Wraps an error thrown by the ES JS client into a Boom error response and returns it + * + * @param err Object Error thrown by ES JS client + * @param statusCodeToMessageMap Object Optional map of HTTP status codes => error messages + */ +export function wrapEsError( + err: any, + statusCodeToMessageMap: Record = {} +): { message: string; body?: { cause?: string[] }; statusCode: number } { + const { statusCode, response } = err; + + const { + error: { + root_cause = [], // eslint-disable-line @typescript-eslint/camelcase + caused_by = undefined, // eslint-disable-line @typescript-eslint/camelcase + } = {}, + } = JSON.parse(response); + + // If no custom message if specified for the error's status code, just + // wrap the error as a Boom error response and return it + if (!statusCodeToMessageMap[statusCode]) { + // The caused_by chain has the most information so use that if it's available. If not then + // settle for the root_cause. + const causedByChain = extractCausedByChain(caused_by); + const defaultCause = root_cause.length ? extractCausedByChain(root_cause[0]) : undefined; + + return { + message: err.message, + statusCode, + body: { + cause: causedByChain.length ? causedByChain : defaultCause, + }, + }; + } + + // Otherwise, use the custom message to create a Boom error response and + // return it + const message = statusCodeToMessageMap[statusCode]; + return { message, statusCode }; +} + +export function formatEsError(err: any): any { + const { statusCode, message, body } = wrapEsError(err); + return { + statusCode, + body: { + message, + attributes: { + cause: body?.cause, + }, + }, + }; +} diff --git a/x-pack/legacy/plugins/rollup/server/lib/is_es_error/is_es_error.ts b/x-pack/plugins/rollup/server/lib/is_es_error.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/is_es_error/is_es_error.ts rename to x-pack/plugins/rollup/server/lib/is_es_error.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/jobs_compatibility.ts b/x-pack/plugins/rollup/server/lib/jobs_compatibility.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/jobs_compatibility.ts rename to x-pack/plugins/rollup/server/lib/jobs_compatibility.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/map_capabilities.ts b/x-pack/plugins/rollup/server/lib/map_capabilities.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/map_capabilities.ts rename to x-pack/plugins/rollup/server/lib/map_capabilities.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/merge_capabilities_with_fields.ts b/x-pack/plugins/rollup/server/lib/merge_capabilities_with_fields.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/merge_capabilities_with_fields.ts rename to x-pack/plugins/rollup/server/lib/merge_capabilities_with_fields.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/index.ts b/x-pack/plugins/rollup/server/lib/search_strategies/index.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/index.ts rename to x-pack/plugins/rollup/server/lib/search_strategies/index.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js rename to x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts b/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts rename to x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js rename to x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts b/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts similarity index 76% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts rename to x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts index 93c4c1b52140b..333863979ba95 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts +++ b/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts @@ -3,18 +3,19 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { getRollupSearchStrategy } from './rollup_search_strategy'; -import { getRollupSearchRequest } from './rollup_search_request'; -import { getRollupSearchCapabilities } from './rollup_search_capabilities'; + import { AbstractSearchRequest, DefaultSearchCapabilities, AbstractSearchStrategy, -} from '../../../../../../../src/plugins/vis_type_timeseries/server'; -import { RouteDependencies } from '../../types'; +} from '../../../../../../src/plugins/vis_type_timeseries/server'; +import { CallWithRequestFactoryShim } from '../../types'; +import { getRollupSearchStrategy } from './rollup_search_strategy'; +import { getRollupSearchRequest } from './rollup_search_request'; +import { getRollupSearchCapabilities } from './rollup_search_capabilities'; export const registerRollupSearchStrategy = ( - { elasticsearchService }: RouteDependencies, + callWithRequestFactory: CallWithRequestFactoryShim, addSearchStrategy: (searchStrategy: any) => void ) => { const RollupSearchRequest = getRollupSearchRequest(AbstractSearchRequest); @@ -22,8 +23,9 @@ export const registerRollupSearchStrategy = ( const RollupSearchStrategy = getRollupSearchStrategy( AbstractSearchStrategy, RollupSearchRequest, - RollupSearchCapabilities + RollupSearchCapabilities, + callWithRequestFactory ); - addSearchStrategy(new RollupSearchStrategy(elasticsearchService)); + addSearchStrategy(new RollupSearchStrategy()); }; diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js rename to x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts similarity index 98% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts rename to x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts index 5a57129aa6039..151afe660847f 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts +++ b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { get, has } from 'lodash'; -import { KibanaRequest } from 'kibana/server'; +import { KibanaRequest } from 'src/core/server'; import { leastCommonInterval, isCalendarInterval } from './lib/interval_helper'; export const getRollupSearchCapabilities = (DefaultSearchCapabilities: any) => diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_request.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.test.js similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_request.test.js rename to x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.test.js diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts rename to x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_request.ts diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js similarity index 100% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js rename to x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js diff --git a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts similarity index 84% rename from x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts rename to x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts index 9d5aad2c2d3bc..815fe163411b3 100644 --- a/x-pack/legacy/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts +++ b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { indexBy, isString } from 'lodash'; -import { ElasticsearchServiceSetup, KibanaRequest } from 'kibana/server'; -import { callWithRequestFactory } from '../call_with_request_factory'; +import { KibanaRequest } from 'src/core/server'; + +import { CallWithRequestFactoryShim } from '../../types'; import { mergeCapabilitiesWithFields } from '../merge_capabilities_with_fields'; import { getCapabilitiesForRollupIndices } from '../map_capabilities'; @@ -20,13 +21,16 @@ const isIndexPatternValid = (indexPattern: string) => export const getRollupSearchStrategy = ( AbstractSearchStrategy: any, RollupSearchRequest: any, - RollupSearchCapabilities: any + RollupSearchCapabilities: any, + callWithRequestFactory: CallWithRequestFactoryShim ) => class RollupSearchStrategy extends AbstractSearchStrategy { name = 'rollup'; - constructor(elasticsearchService: ElasticsearchServiceSetup) { - super(elasticsearchService, callWithRequestFactory, RollupSearchRequest); + constructor() { + // TODO: When vis_type_timeseries and AbstractSearchStrategy are migrated to the NP, it + // shouldn't require elasticsearchService to be injected, and we can remove this null argument. + super(null, callWithRequestFactory, RollupSearchRequest); } getRollupData(req: KibanaRequest, indexPattern: string) { diff --git a/x-pack/plugins/rollup/server/plugin.ts b/x-pack/plugins/rollup/server/plugin.ts index ea6d197e22029..ee9a1844c7468 100644 --- a/x-pack/plugins/rollup/server/plugin.ts +++ b/x-pack/plugins/rollup/server/plugin.ts @@ -4,20 +4,98 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup, Plugin, PluginInitializerContext } from 'src/core/server'; +declare module 'src/core/server' { + interface RequestHandlerContext { + rollup?: RollupContext; + } +} + +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; +import { + CoreSetup, + Plugin, + Logger, + KibanaRequest, + PluginInitializerContext, + IScopedClusterClient, + APICaller, + SharedGlobalConfig, +} from 'src/core/server'; import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; -import { CONFIG_ROLLUPS } from '../common'; -export class RollupPlugin implements Plugin { - private readonly initContext: PluginInitializerContext; +import { PLUGIN, CONFIG_ROLLUPS } from '../common'; +import { Dependencies, CallWithRequestFactoryShim } from './types'; +import { registerApiRoutes } from './routes'; +import { License } from './services'; +import { registerRollupUsageCollector } from './collectors'; +import { rollupDataEnricher } from './rollup_data_enricher'; +import { IndexPatternsFetcher } from './shared_imports'; +import { registerRollupSearchStrategy } from './lib/search_strategies'; +import { elasticsearchJsPlugin } from './client/elasticsearch_rollup'; +import { isEsError } from './lib/is_es_error'; +import { formatEsError } from './lib/format_es_error'; +import { getCapabilitiesForRollupIndices } from './lib/map_capabilities'; +import { mergeCapabilitiesWithFields } from './lib/merge_capabilities_with_fields'; + +interface RollupContext { + client: IScopedClusterClient; +} + +export class RollupPlugin implements Plugin { + private readonly logger: Logger; + private readonly globalConfig$: Observable; + private readonly license: License; - constructor(initContext: PluginInitializerContext) { - this.initContext = initContext; + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + this.globalConfig$ = initializerContext.config.legacy.globalConfig$; + this.license = new License(); } - public setup(core: CoreSetup) { - core.uiSettings.register({ + public setup( + { http, uiSettings, elasticsearch }: CoreSetup, + { licensing, indexManagement, visTypeTimeseries, usageCollection }: Dependencies + ) { + this.license.setup( + { + pluginId: PLUGIN.ID, + minimumLicenseType: PLUGIN.minimumLicenseType, + defaultErrorMessage: i18n.translate('xpack.rollupJobs.licenseCheckErrorMessage', { + defaultMessage: 'License check failed', + }), + }, + { + licensing, + logger: this.logger, + } + ); + + // Extend the elasticsearchJs client with additional endpoints. + const esClientConfig = { plugins: [elasticsearchJsPlugin] }; + const rollupEsClient = elasticsearch.createClient('rollup', esClientConfig); + http.registerRouteHandlerContext('rollup', (context, request) => { + return { + client: rollupEsClient.asScoped(request), + }; + }); + + registerApiRoutes({ + router: http.createRouter(), + license: this.license, + lib: { + isEsError, + formatEsError, + getCapabilitiesForRollupIndices, + mergeCapabilitiesWithFields, + }, + sharedImports: { + IndexPatternsFetcher, + }, + }); + + uiSettings.register({ [CONFIG_ROLLUPS]: { name: i18n.translate('xpack.rollupJobs.rollupIndexPatternsTitle', { defaultMessage: 'Enable rollup index patterns', @@ -33,22 +111,34 @@ export class RollupPlugin implements Plugin { }, }); - return { - __legacy: { - config: this.initContext.config, - logger: this.initContext.logger, - }, - }; - } + if (visTypeTimeseries) { + // TODO: When vis_type_timeseries is fully migrated to the NP, it shouldn't require this shim. + const callWithRequestFactoryShim = ( + elasticsearchServiceShim: CallWithRequestFactoryShim, + request: KibanaRequest + ): APICaller => rollupEsClient.asScoped(request).callAsCurrentUser; - public start() {} - public stop() {} -} + const { addSearchStrategy } = visTypeTimeseries; + registerRollupSearchStrategy(callWithRequestFactoryShim, addSearchStrategy); + } + + if (usageCollection) { + this.globalConfig$ + .pipe(first()) + .toPromise() + .then(globalConfig => { + registerRollupUsageCollector(usageCollection, globalConfig.kibana.index); + }) + .catch((e: any) => { + this.logger.warn(`Registering Rollup collector failed: ${e}`); + }); + } + + if (indexManagement && indexManagement.indexDataEnricher) { + indexManagement.indexDataEnricher.add(rollupDataEnricher); + } + } -export interface RollupSetup { - /** @deprecated */ - __legacy: { - config: PluginInitializerContext['config']; - logger: PluginInitializerContext['logger']; - }; + start() {} + stop() {} } diff --git a/x-pack/legacy/plugins/rollup/server/rollup_data_enricher.ts b/x-pack/plugins/rollup/server/rollup_data_enricher.ts similarity index 92% rename from x-pack/legacy/plugins/rollup/server/rollup_data_enricher.ts rename to x-pack/plugins/rollup/server/rollup_data_enricher.ts index ad621f2d9ba80..b06cf971a6460 100644 --- a/x-pack/legacy/plugins/rollup/server/rollup_data_enricher.ts +++ b/x-pack/plugins/rollup/server/rollup_data_enricher.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Index } from '../../../../plugins/index_management/server'; +import { Index } from '../../../plugins/index_management/server'; export const rollupDataEnricher = async (indicesList: Index[], callWithRequest: any) => { if (!indicesList || !indicesList.length) { diff --git a/x-pack/plugins/rollup/server/routes/api/index_patterns/index.ts b/x-pack/plugins/rollup/server/routes/api/index_patterns/index.ts new file mode 100644 index 0000000000000..7bf525ca4aa98 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/index_patterns/index.ts @@ -0,0 +1,12 @@ +/* + * 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 { RouteDependencies } from '../../../types'; +import { registerFieldsForWildcardRoute } from './register_fields_for_wildcard_route'; + +export function registerIndexPatternsRoutes(dependencies: RouteDependencies) { + registerFieldsForWildcardRoute(dependencies); +} diff --git a/x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts b/x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts new file mode 100644 index 0000000000000..32f23314c5259 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts @@ -0,0 +1,141 @@ +/* + * 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 { indexBy } from 'lodash'; +import { schema } from '@kbn/config-schema'; +import { Field } from '../../../lib/merge_capabilities_with_fields'; +import { RouteDependencies } from '../../../types'; + +const parseMetaFields = (metaFields: string | string[]) => { + let parsedFields: string[] = []; + if (typeof metaFields === 'string') { + parsedFields = JSON.parse(metaFields); + } else { + parsedFields = metaFields; + } + return parsedFields; +}; + +const getFieldsForWildcardRequest = async ( + context: any, + request: any, + response: any, + IndexPatternsFetcher: any +) => { + const { callAsCurrentUser } = context.core.elasticsearch.dataClient; + const indexPatterns = new IndexPatternsFetcher(callAsCurrentUser); + const { pattern, meta_fields: metaFields } = request.query; + + let parsedFields: string[] = []; + try { + parsedFields = parseMetaFields(metaFields); + } catch (error) { + return response.badRequest({ + body: error, + }); + } + + try { + const fields = await indexPatterns.getFieldsForWildcard({ + pattern, + metaFields: parsedFields, + }); + + return response.ok({ + body: { fields }, + headers: { + 'content-type': 'application/json', + }, + }); + } catch (error) { + return response.notFound(); + } +}; + +/** + * Get list of fields for rollup index pattern, in the format of regular index pattern fields + */ +export const registerFieldsForWildcardRoute = ({ + router, + license, + lib: { isEsError, formatEsError, getCapabilitiesForRollupIndices, mergeCapabilitiesWithFields }, + sharedImports: { IndexPatternsFetcher }, +}: RouteDependencies) => { + const querySchema = schema.object({ + pattern: schema.string(), + meta_fields: schema.arrayOf(schema.string(), { + defaultValue: [], + }), + params: schema.string({ + validate(value) { + try { + const params = JSON.parse(value); + const keys = Object.keys(params); + const { rollup_index: rollupIndex } = params; + + if (!rollupIndex) { + return '[request query.params]: "rollup_index" is required'; + } else if (keys.length > 1) { + const invalidParams = keys.filter(key => key !== 'rollup_index'); + return `[request query.params]: ${invalidParams.join(', ')} is not allowed`; + } + } catch (err) { + return '[request query.params]: expected JSON string'; + } + }, + }), + }); + + router.get( + { + path: '/api/index_patterns/rollup/_fields_for_wildcard', + validate: { + query: querySchema, + }, + }, + license.guardApiRoute(async (context, request, response) => { + const { params, meta_fields: metaFields } = request.query; + + try { + // Make call and use field information from response + const { payload } = await getFieldsForWildcardRequest( + context, + request, + response, + IndexPatternsFetcher + ); + const fields = payload.fields; + const parsedParams = JSON.parse(params); + const rollupIndex = parsedParams.rollup_index; + const rollupFields: Field[] = []; + const fieldsFromFieldCapsApi: { [key: string]: any } = indexBy(fields, 'name'); + const rollupIndexCapabilities = getCapabilitiesForRollupIndices( + await context.rollup!.client.callAsCurrentUser('rollup.rollupIndexCapabilities', { + indexPattern: rollupIndex, + }) + )[rollupIndex].aggs; + + // Keep meta fields + metaFields.forEach( + (field: string) => + fieldsFromFieldCapsApi[field] && rollupFields.push(fieldsFromFieldCapsApi[field]) + ); + + const mergedRollupFields = mergeCapabilitiesWithFields( + rollupIndexCapabilities, + fieldsFromFieldCapsApi, + rollupFields + ); + return response.ok({ body: { fields: mergedRollupFields } }); + } catch (err) { + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/api/indices/index.ts b/x-pack/plugins/rollup/server/routes/api/indices/index.ts new file mode 100644 index 0000000000000..0aa5772b56991 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/indices/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RouteDependencies } from '../../../types'; +import { registerGetRoute } from './register_get_route'; +import { registerValidateIndexPatternRoute } from './register_validate_index_pattern_route'; + +export function registerIndicesRoutes(dependencies: RouteDependencies) { + registerGetRoute(dependencies); + registerValidateIndexPatternRoute(dependencies); +} diff --git a/x-pack/plugins/rollup/server/routes/api/indices/register_get_route.ts b/x-pack/plugins/rollup/server/routes/api/indices/register_get_route.ts new file mode 100644 index 0000000000000..3521650c1dc3e --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/indices/register_get_route.ts @@ -0,0 +1,39 @@ +/* + * 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 { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +/** + * Returns a list of all rollup index names + */ +export const registerGetRoute = ({ + router, + license, + lib: { isEsError, formatEsError, getCapabilitiesForRollupIndices }, +}: RouteDependencies) => { + router.get( + { + path: addBasePath('/indices'), + validate: false, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const data = await context.rollup!.client.callAsCurrentUser( + 'rollup.rollupIndexCapabilities', + { + indexPattern: '_all', + } + ); + return response.ok({ body: getCapabilitiesForRollupIndices(data) }); + } catch (err) { + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts b/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts new file mode 100644 index 0000000000000..9e22060b9beb7 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/indices/register_validate_index_pattern_route.ts @@ -0,0 +1,142 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +type NumericField = + | 'long' + | 'integer' + | 'short' + | 'byte' + | 'scaled_float' + | 'double' + | 'float' + | 'half_float'; + +interface FieldCapability { + date?: any; + keyword?: any; + long?: any; + integer?: any; + short?: any; + byte?: any; + double?: any; + float?: any; + half_float?: any; + scaled_float?: any; +} + +interface FieldCapabilities { + fields: FieldCapability[]; +} + +function isNumericField(fieldCapability: FieldCapability) { + const numericTypes = [ + 'long', + 'integer', + 'short', + 'byte', + 'double', + 'float', + 'half_float', + 'scaled_float', + ]; + return numericTypes.some(numericType => fieldCapability[numericType as NumericField] != null); +} + +/** + * Returns information on validity of an index pattern for creating a rollup job: + * - Does the index pattern match any indices? + * - Does the index pattern match rollup indices? + * - Which date fields, numeric fields, and keyword fields are available in the matching indices? + */ +export const registerValidateIndexPatternRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.get( + { + path: addBasePath('/index_pattern_validity/{indexPattern}'), + validate: { + params: schema.object({ + indexPattern: schema.string(), + }), + }, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const { indexPattern } = request.params; + const [fieldCapabilities, rollupIndexCapabilities]: [ + FieldCapabilities, + { [key: string]: any } + ] = await Promise.all([ + context.rollup!.client.callAsCurrentUser('rollup.fieldCapabilities', { indexPattern }), + context.rollup!.client.callAsCurrentUser('rollup.rollupIndexCapabilities', { + indexPattern, + }), + ]); + + const doesMatchIndices = Object.entries(fieldCapabilities.fields).length !== 0; + const doesMatchRollupIndices = Object.entries(rollupIndexCapabilities).length !== 0; + + const dateFields: string[] = []; + const numericFields: string[] = []; + const keywordFields: string[] = []; + + const fieldCapabilitiesEntries = Object.entries(fieldCapabilities.fields); + + fieldCapabilitiesEntries.forEach( + ([fieldName, fieldCapability]: [string, FieldCapability]) => { + if (fieldCapability.date) { + dateFields.push(fieldName); + return; + } + + if (isNumericField(fieldCapability)) { + numericFields.push(fieldName); + return; + } + + if (fieldCapability.keyword) { + keywordFields.push(fieldName); + } + } + ); + + const body = { + doesMatchIndices, + doesMatchRollupIndices, + dateFields, + numericFields, + keywordFields, + }; + + return response.ok({ body }); + } catch (err) { + // 404s are still valid results. + if (err.statusCode === 404) { + const notFoundBody = { + doesMatchIndices: false, + doesMatchRollupIndices: false, + dateFields: [], + numericFields: [], + keywordFields: [], + }; + return response.ok({ body: notFoundBody }); + } + + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/index.ts b/x-pack/plugins/rollup/server/routes/api/jobs/index.ts new file mode 100644 index 0000000000000..fe1d1c6109a88 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/jobs/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RouteDependencies } from '../../../types'; +import { registerCreateRoute } from './register_create_route'; +import { registerDeleteRoute } from './register_delete_route'; +import { registerGetRoute } from './register_get_route'; +import { registerStartRoute } from './register_start_route'; +import { registerStopRoute } from './register_stop_route'; + +export function registerJobsRoutes(dependencies: RouteDependencies) { + registerCreateRoute(dependencies); + registerDeleteRoute(dependencies); + registerGetRoute(dependencies); + registerStartRoute(dependencies); + registerStopRoute(dependencies); +} diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_create_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_create_route.ts new file mode 100644 index 0000000000000..adf8c1da0af0e --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_create_route.ts @@ -0,0 +1,49 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +export const registerCreateRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.put( + { + path: addBasePath('/create'), + validate: { + body: schema.object({ + job: schema.object( + { + id: schema.string(), + }, + { unknowns: 'allow' } + ), + }), + }, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const { id, ...rest } = request.body.job; + // Create job. + await context.rollup!.client.callAsCurrentUser('rollup.createJob', { + id, + body: rest, + }); + // Then request the newly created job. + const results = await context.rollup!.client.callAsCurrentUser('rollup.job', { id }); + return response.ok({ body: results.jobs[0] }); + } catch (err) { + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts new file mode 100644 index 0000000000000..32f7b3f35e163 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +export const registerDeleteRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.post( + { + path: addBasePath('/delete'), + validate: { + body: schema.object({ + jobIds: schema.arrayOf(schema.string()), + }), + }, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const { jobIds } = request.body; + const data = await Promise.all( + jobIds.map((id: string) => + context.rollup!.client.callAsCurrentUser('rollup.deleteJob', { id }) + ) + ).then(() => ({ success: true })); + return response.ok({ body: data }); + } catch (err) { + // There is an issue opened on ES to handle the following error correctly + // https://github.com/elastic/elasticsearch/issues/42908 + // Until then we'll modify the response here. + if (err.response && err.response.includes('Job must be [STOPPED] before deletion')) { + err.status = 400; + err.statusCode = 400; + err.displayName = 'Bad request'; + err.message = JSON.parse(err.response).task_failures[0].reason.reason; + } + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_get_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_get_route.ts new file mode 100644 index 0000000000000..a8d51f4639fc6 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_get_route.ts @@ -0,0 +1,32 @@ +/* + * 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 { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +export const registerGetRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.get( + { + path: addBasePath('/jobs'), + validate: false, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const data = await context.rollup!.client.callAsCurrentUser('rollup.jobs'); + return response.ok({ body: data }); + } catch (err) { + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_start_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_start_route.ts new file mode 100644 index 0000000000000..fb6f2b12ba52e --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_start_route.ts @@ -0,0 +1,48 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +export const registerStartRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.post( + { + path: addBasePath('/start'), + validate: { + body: schema.object({ + jobIds: schema.arrayOf(schema.string()), + }), + query: schema.maybe( + schema.object({ + waitForCompletion: schema.maybe(schema.string()), + }) + ), + }, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const { jobIds } = request.body; + + const data = await Promise.all( + jobIds.map((id: string) => + context.rollup!.client.callAsCurrentUser('rollup.startJob', { id }) + ) + ).then(() => ({ success: true })); + return response.ok({ body: data }); + } catch (err) { + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_stop_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_stop_route.ts new file mode 100644 index 0000000000000..118d98e36e03c --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_stop_route.ts @@ -0,0 +1,49 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +export const registerStopRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.post( + { + path: addBasePath('/stop'), + validate: { + body: schema.object({ + jobIds: schema.arrayOf(schema.string()), + }), + query: schema.object({ + waitForCompletion: schema.maybe(schema.string()), + }), + }, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const { jobIds } = request.body; + // For our API integration tests we need to wait for the jobs to be stopped + // in order to be able to delete them sequentially. + const { waitForCompletion } = request.query; + const stopRollupJob = (id: string) => + context.rollup!.client.callAsCurrentUser('rollup.stopJob', { + id, + waitForCompletion: waitForCompletion === 'true', + }); + const data = await Promise.all(jobIds.map(stopRollupJob)).then(() => ({ success: true })); + return response.ok({ body: data }); + } catch (err) { + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/api/search/index.ts b/x-pack/plugins/rollup/server/routes/api/search/index.ts new file mode 100644 index 0000000000000..2a2d823e79bc6 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/search/index.ts @@ -0,0 +1,12 @@ +/* + * 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 { RouteDependencies } from '../../../types'; +import { registerSearchRoute } from './register_search_route'; + +export function registerSearchRoutes(dependencies: RouteDependencies) { + registerSearchRoute(dependencies); +} diff --git a/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts b/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts new file mode 100644 index 0000000000000..c5c56336def1a --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/api/search/register_search_route.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +export const registerSearchRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.post( + { + path: addBasePath('/search'), + validate: { + body: schema.arrayOf( + schema.object({ + index: schema.string(), + query: schema.any(), + }) + ), + }, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const requests = request.body.map(({ index, query }: { index: string; query?: any }) => + context.rollup!.client.callAsCurrentUser('rollup.search', { + index, + rest_total_hits_as_int: true, + body: query, + }) + ); + const data = await Promise.all(requests); + return response.ok({ body: data }); + } catch (err) { + if (isEsError(err)) { + return response.customError({ statusCode: err.statusCode, body: err }); + } + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/rollup/server/routes/index.ts b/x-pack/plugins/rollup/server/routes/index.ts new file mode 100644 index 0000000000000..b25480855b4a2 --- /dev/null +++ b/x-pack/plugins/rollup/server/routes/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { RouteDependencies } from '../types'; + +import { registerIndexPatternsRoutes } from './api/index_patterns'; +import { registerIndicesRoutes } from './api/indices'; +import { registerJobsRoutes } from './api/jobs'; +import { registerSearchRoutes } from './api/search'; + +export function registerApiRoutes(dependencies: RouteDependencies) { + registerIndexPatternsRoutes(dependencies); + registerIndicesRoutes(dependencies); + registerJobsRoutes(dependencies); + registerSearchRoutes(dependencies); +} diff --git a/x-pack/plugins/rollup/server/services/add_base_path.ts b/x-pack/plugins/rollup/server/services/add_base_path.ts new file mode 100644 index 0000000000000..7d7cce3aab334 --- /dev/null +++ b/x-pack/plugins/rollup/server/services/add_base_path.ts @@ -0,0 +1,9 @@ +/* + * 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 { API_BASE_PATH } from '../../common'; + +export const addBasePath = (uri: string): string => `${API_BASE_PATH}${uri}`; diff --git a/x-pack/legacy/plugins/rollup/server/index.ts b/x-pack/plugins/rollup/server/services/index.ts similarity index 55% rename from x-pack/legacy/plugins/rollup/server/index.ts rename to x-pack/plugins/rollup/server/services/index.ts index 6bbd00ac6576e..7f79c4f446546 100644 --- a/x-pack/legacy/plugins/rollup/server/index.ts +++ b/x-pack/plugins/rollup/server/services/index.ts @@ -3,7 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from 'src/core/server'; -import { RollupsServerPlugin } from './plugin'; -export const plugin = (ctx: PluginInitializerContext) => new RollupsServerPlugin(ctx); +export { addBasePath } from './add_base_path'; +export { License } from './license'; diff --git a/x-pack/plugins/rollup/server/services/license.ts b/x-pack/plugins/rollup/server/services/license.ts new file mode 100644 index 0000000000000..bfd357867c3e2 --- /dev/null +++ b/x-pack/plugins/rollup/server/services/license.ts @@ -0,0 +1,93 @@ +/* + * 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 { Logger } from 'src/core/server'; +import { + KibanaRequest, + KibanaResponseFactory, + RequestHandler, + RequestHandlerContext, +} from 'src/core/server'; + +import { LicensingPluginSetup } from '../../../licensing/server'; +import { LicenseType } from '../../../licensing/common/types'; + +export interface LicenseStatus { + isValid: boolean; + message?: string; +} + +interface SetupSettings { + pluginId: string; + minimumLicenseType: LicenseType; + defaultErrorMessage: string; +} + +export class License { + private licenseStatus: LicenseStatus = { + isValid: false, + message: 'Invalid License', + }; + + private _isEsSecurityEnabled: boolean = false; + + setup( + { pluginId, minimumLicenseType, defaultErrorMessage }: SetupSettings, + { licensing, logger }: { licensing: LicensingPluginSetup; logger: Logger } + ) { + licensing.license$.subscribe(license => { + const { state, message } = license.check(pluginId, minimumLicenseType); + const hasRequiredLicense = state === 'valid'; + + // Retrieving security checks the results of GET /_xpack as well as license state, + // so we're also checking whether the security is disabled in elasticsearch.yml. + this._isEsSecurityEnabled = license.getFeature('security').isEnabled; + + if (hasRequiredLicense) { + this.licenseStatus = { isValid: true }; + } else { + this.licenseStatus = { + isValid: false, + message: message || defaultErrorMessage, + }; + if (message) { + logger.info(message); + } + } + }); + } + + guardApiRoute(handler: RequestHandler) { + const license = this; + + return function licenseCheck( + ctx: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory + ) { + const licenseStatus = license.getStatus(); + + if (!licenseStatus.isValid) { + return response.customError({ + body: { + message: licenseStatus.message || '', + }, + statusCode: 403, + }); + } + + return handler(ctx, request, response); + }; + } + + getStatus() { + return this.licenseStatus; + } + + // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility + get isEsSecurityEnabled() { + return this._isEsSecurityEnabled; + } +} diff --git a/x-pack/legacy/plugins/rollup/server/shared_imports.ts b/x-pack/plugins/rollup/server/shared_imports.ts similarity index 75% rename from x-pack/legacy/plugins/rollup/server/shared_imports.ts rename to x-pack/plugins/rollup/server/shared_imports.ts index 941610b97707f..09842f529abed 100644 --- a/x-pack/legacy/plugins/rollup/server/shared_imports.ts +++ b/x-pack/plugins/rollup/server/shared_imports.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { IndexPatternsFetcher } from '../../../../../src/plugins/data/server'; +export { IndexPatternsFetcher } from '../../../../src/plugins/data/server'; diff --git a/x-pack/plugins/rollup/server/types.ts b/x-pack/plugins/rollup/server/types.ts new file mode 100644 index 0000000000000..c21d76400164e --- /dev/null +++ b/x-pack/plugins/rollup/server/types.ts @@ -0,0 +1,45 @@ +/* + * 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 { IRouter, APICaller, KibanaRequest } from 'src/core/server'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { VisTypeTimeseriesSetup } from 'src/plugins/vis_type_timeseries/server'; + +import { IndexManagementPluginSetup } from '../../index_management/server'; +import { LicensingPluginSetup } from '../../licensing/server'; +import { License } from './services'; +import { IndexPatternsFetcher } from './shared_imports'; +import { isEsError } from './lib/is_es_error'; +import { formatEsError } from './lib/format_es_error'; +import { getCapabilitiesForRollupIndices } from './lib/map_capabilities'; +import { mergeCapabilitiesWithFields } from './lib/merge_capabilities_with_fields'; + +export interface Dependencies { + indexManagement?: IndexManagementPluginSetup; + visTypeTimeseries?: VisTypeTimeseriesSetup; + usageCollection?: UsageCollectionSetup; + licensing: LicensingPluginSetup; +} + +export interface RouteDependencies { + router: IRouter; + license: License; + lib: { + isEsError: typeof isEsError; + formatEsError: typeof formatEsError; + getCapabilitiesForRollupIndices: typeof getCapabilitiesForRollupIndices; + mergeCapabilitiesWithFields: typeof mergeCapabilitiesWithFields; + }; + sharedImports: { + IndexPatternsFetcher: typeof IndexPatternsFetcher; + }; +} + +// TODO: When vis_type_timeseries is fully migrated to the NP, it shouldn't require this shim. +export type CallWithRequestFactoryShim = ( + elasticsearchServiceShim: CallWithRequestFactoryShim, + request: KibanaRequest +) => APICaller; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5b55c7a15f1b3..591de8ace1869 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -138,17 +138,6 @@ "charts.controls.rangeErrorMessage": "値は {min} と {max} の間でなければなりません", "charts.controls.vislibBasicOptions.legendPositionLabel": "凡例位置", "charts.controls.vislibBasicOptions.showTooltipLabel": "ツールヒントを表示", - "common.ui.errorAutoCreateIndex.breadcrumbs.errorText": "エラー", - "common.ui.errorAutoCreateIndex.errorDescription": "Elasticsearch クラスターの {autoCreateIndexActionConfig} 設定が原因で、Kibana が保存されたオブジェクトを格納するインデックスを自動的に作成できないようです。Kibana は、保存されたオブジェクトインデックスが適切なマッピング/スキーマを使用し Kibana から Elasticsearch へのポーリングの回数を減らすための最適な手段であるため、この Elasticsearch の機能を使用します。", - "common.ui.errorAutoCreateIndex.errorDisclaimer": "申し訳ございませんが、この問題が解決されるまで Kibana で何も保存することができません。", - "common.ui.errorAutoCreateIndex.errorTitle": "おっと!", - "common.ui.errorAutoCreateIndex.howToFixError.goBackText": "ブラウザの戻るボタンで前の画面に戻ります。", - "common.ui.errorAutoCreateIndex.howToFixError.removeConfigText": "Elasticsearch 構成ファイルから {autoCreateIndexActionConfig} を削除します。", - "common.ui.errorAutoCreateIndex.howToFixError.restartText": "Elasticsearch を再起動します。", - "common.ui.errorAutoCreateIndex.howToFixErrorTitle": "どうすれば良いのでしょう?", - "common.ui.errorAutoCreateIndex.noteImageAriaLabel": "情報", - "common.ui.errorAutoCreateIndex.noteMessage": "{autoCreateIndexActionConfig} は、機能を有効にするパターンのホワイトリストを定義することもできます。Kibana と同じ理由でこの機能を使用する他のプラグイン/操作をすべて把握する必要があるため、この設定のこのような使い方はここでは説明しません。", - "common.ui.errorAutoCreateIndex.noteTitle": "注:", "common.ui.errorUrlOverflow.breadcrumbs.errorText": "エラー", "common.ui.errorUrlOverflow.errorDescription": "とても長い URL ですね。残念なお知らせがあります。ご使用のブラウザは Kibana の超巨大 URL に対応していません。問題を避けるため、Kibana はご使用のブラウザでの URL を {urlCharacterLimit} 文字に制限します。", "common.ui.errorUrlOverflow.errorTitle": "おっと!", @@ -8830,10 +8819,8 @@ "xpack.logstash.workersTooltip": "パイプラインのフィルターとアウトプットステージを同時に実行するワーカーの数です。イベントが詰まってしまう場合や CPU が飽和状態ではない場合は、マシンの処理能力をより有効に活用するため、この数字を上げてみてください。\n\nデフォルト値:ホストの CPU コア数です", "xpack.maps.addLayerPanel.addLayer": "レイヤーを追加", "xpack.maps.addLayerPanel.changeDataSourceButtonLabel": "データソースを変更", - "xpack.maps.addLayerPanel.chooseDataSourceTitle": "データソースの選択", "xpack.maps.addLayerPanel.footer.cancelButtonLabel": "キャンセル", "xpack.maps.addLayerPanel.importFile": "ファイルのインポート", - "xpack.maps.addLayerPanel.selectSource": "ソースを選択", "xpack.maps.aggs.defaultCountLabel": "カウント", "xpack.maps.appDescription": "マップアプリケーション", "xpack.maps.appTitle": "マップ", @@ -12366,7 +12353,6 @@ "xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV レポート", "xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF レポート", "xpack.reporting.shareContextMenu.pngReportsButtonLabel": "PNG レポート", - "xpack.rollupJobs.appName": "ロールアップジョブ", "xpack.rollupJobs.appTitle": "ロールアップジョブ", "xpack.rollupJobs.breadcrumbsTitle": "ロールアップジョブ", "xpack.rollupJobs.create.backButton.label": "戻る", @@ -15944,7 +15930,6 @@ "xpack.triggersActionsUI.sections.alertForm.loadingActionTypesDescription": "アクションタイプを読み込み中...", "xpack.triggersActionsUI.sections.alertForm.renotifyFieldLabel": "通知間隔", "xpack.triggersActionsUI.sections.alertForm.renotifyWithTooltip": "アラートがアクティブな間にアクションを繰り返す頻度を定義します。", - "xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeEditTitle": "{actionConnectorName}", "xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeTitle": "アクション:アクションタイプを選択してください", "xpack.triggersActionsUI.sections.alertForm.selectAlertTypeTitle": "トリガータイプを選択してください", "xpack.triggersActionsUI.sections.alertForm.selectedAlertTypeTitle": "{alertType}", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 1758588d01ba8..c46f395a8c64e 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -138,17 +138,6 @@ "charts.controls.rangeErrorMessage": "值必须是在 {min} 到 {max} 的范围内", "charts.controls.vislibBasicOptions.legendPositionLabel": "图例位置", "charts.controls.vislibBasicOptions.showTooltipLabel": "显示工具提示", - "common.ui.errorAutoCreateIndex.breadcrumbs.errorText": "错误", - "common.ui.errorAutoCreateIndex.errorDescription": "似乎 Elasticsearch 集群的 {autoCreateIndexActionConfig} 设置使 Kibana 无法自动创建用于存储已保存对象的索引。Kibana 将使用此 Elasticsearch 功能,因为这是确保已保存对象索引使用正确映射/架构的最好方式,而且其允许 Kibana 较少地轮询 Elasticsearch。", - "common.ui.errorAutoCreateIndex.errorDisclaimer": "但是,只有解决了此问题后,您才能在 Kibana 保存内容。", - "common.ui.errorAutoCreateIndex.errorTitle": "糟糕!", - "common.ui.errorAutoCreateIndex.howToFixError.goBackText": "使用浏览器的后退按钮返回您之前正做的工作。", - "common.ui.errorAutoCreateIndex.howToFixError.removeConfigText": "从 Elasticsearch 配置文件中删除 {autoCreateIndexActionConfig}", - "common.ui.errorAutoCreateIndex.howToFixError.restartText": "重新启动 Elasticsearch。", - "common.ui.errorAutoCreateIndex.howToFixErrorTitle": "那么,我如何解决此问题?", - "common.ui.errorAutoCreateIndex.noteImageAriaLabel": "信息", - "common.ui.errorAutoCreateIndex.noteMessage": "{autoCreateIndexActionConfig} 还可以定义应启用此功能的模式白名单。我们在这里不讨论如何以那种方式使用该设置,因为这和 Kibana 一样需要您了解依赖该功能的所有其他插件/交互。", - "common.ui.errorAutoCreateIndex.noteTitle": "注意:", "common.ui.errorUrlOverflow.breadcrumbs.errorText": "错误", "common.ui.errorUrlOverflow.errorDescription": "您的 URL 真不小。我有一些不幸的消息:您的浏览器与 Kibana 的超长 URL 不太兼容。为了避免您遇到问题,Kibana 在您的浏览器中将 URL 长度限制在 {urlCharacterLimit} 个字符。", "common.ui.errorUrlOverflow.errorTitle": "喔哦!", @@ -8833,10 +8822,8 @@ "xpack.logstash.workersTooltip": "并行执行管道的筛选和输出阶段的工作线程数目。如果您发现事件出现积压或 CPU 未饱和,请考虑增大此数值,以更好地利用机器处理能力。\n\n默认值:主机的 CPU 核心数", "xpack.maps.addLayerPanel.addLayer": "添加图层", "xpack.maps.addLayerPanel.changeDataSourceButtonLabel": "更改数据源", - "xpack.maps.addLayerPanel.chooseDataSourceTitle": "选择数据源", "xpack.maps.addLayerPanel.footer.cancelButtonLabel": "鍙栨秷", "xpack.maps.addLayerPanel.importFile": "导入文件", - "xpack.maps.addLayerPanel.selectSource": "选择源", "xpack.maps.aggs.defaultCountLabel": "计数", "xpack.maps.appDescription": "地图应用程序", "xpack.maps.appTitle": "Maps", @@ -12370,7 +12357,6 @@ "xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV 报告", "xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF 报告", "xpack.reporting.shareContextMenu.pngReportsButtonLabel": "PNG 报告", - "xpack.rollupJobs.appName": "汇总/打包作业", "xpack.rollupJobs.appTitle": "汇总/打包作业", "xpack.rollupJobs.breadcrumbsTitle": "汇总/打包作业", "xpack.rollupJobs.create.backButton.label": "上一步", @@ -15949,7 +15935,6 @@ "xpack.triggersActionsUI.sections.alertForm.loadingActionTypesDescription": "正在加载操作类型……", "xpack.triggersActionsUI.sections.alertForm.renotifyFieldLabel": "通知频率", "xpack.triggersActionsUI.sections.alertForm.renotifyWithTooltip": "定义告警处于活动状态时重复操作的频率。", - "xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeEditTitle": "{actionConnectorName}", "xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeTitle": "操作:选择操作类型", "xpack.triggersActionsUI.sections.alertForm.selectAlertTypeTitle": "选择触发器类型", "xpack.triggersActionsUI.sections.alertForm.selectedAlertTypeTitle": "{alertType}", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_type_compare.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_type_compare.test.ts index 9ce50cf47560a..0a2ec3f203a9a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_type_compare.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_type_compare.test.ts @@ -33,11 +33,20 @@ test('should sort enabled action types first', async () => { enabledInConfig: true, enabledInLicense: true, }, + { + id: '4', + minimumLicenseRequired: 'basic', + name: 'x-fourth', + enabled: true, + enabledInConfig: false, + enabledInLicense: true, + }, ]; const result = [...actionTypes].sort(actionTypeCompare); expect(result[0]).toEqual(actionTypes[0]); expect(result[1]).toEqual(actionTypes[2]); - expect(result[2]).toEqual(actionTypes[1]); + expect(result[2]).toEqual(actionTypes[3]); + expect(result[3]).toEqual(actionTypes[1]); }); test('should sort by name when all enabled', async () => { @@ -66,9 +75,18 @@ test('should sort by name when all enabled', async () => { enabledInConfig: true, enabledInLicense: true, }, + { + id: '4', + minimumLicenseRequired: 'basic', + name: 'x-fourth', + enabled: true, + enabledInConfig: false, + enabledInLicense: true, + }, ]; const result = [...actionTypes].sort(actionTypeCompare); expect(result[0]).toEqual(actionTypes[1]); expect(result[1]).toEqual(actionTypes[2]); expect(result[2]).toEqual(actionTypes[0]); + expect(result[3]).toEqual(actionTypes[3]); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_type_compare.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_type_compare.ts index d18cb21b3a0fe..8078ef4938e50 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_type_compare.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_type_compare.ts @@ -4,14 +4,35 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ActionType } from '../../types'; +import { ActionType, ActionConnector } from '../../types'; -export function actionTypeCompare(a: ActionType, b: ActionType) { - if (a.enabled === true && b.enabled === false) { +export function actionTypeCompare( + a: ActionType, + b: ActionType, + preconfiguredConnectors?: ActionConnector[] +) { + const aEnabled = getIsEnabledValue(a, preconfiguredConnectors); + const bEnabled = getIsEnabledValue(b, preconfiguredConnectors); + + if (aEnabled === true && bEnabled === false) { return -1; } - if (a.enabled === false && b.enabled === true) { + if (aEnabled === false && bEnabled === true) { return 1; } return a.name.localeCompare(b.name); } + +const getIsEnabledValue = (actionType: ActionType, preconfiguredConnectors?: ActionConnector[]) => { + let isEnabled = actionType.enabled; + if ( + !actionType.enabledInConfig && + preconfiguredConnectors && + preconfiguredConnectors.length > 0 + ) { + isEnabled = + preconfiguredConnectors.find(connector => connector.actionTypeId === actionType.id) !== + undefined; + } + return isEnabled; +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.test.tsx index 566ed7935e013..9c017aa6fd31f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.test.tsx @@ -4,43 +4,47 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ActionType } from '../../types'; -import { checkActionTypeEnabled } from './check_action_type_enabled'; +import { ActionType, ActionConnector } from '../../types'; +import { + checkActionTypeEnabled, + checkActionFormActionTypeEnabled, +} from './check_action_type_enabled'; -test(`returns isEnabled:true when action type isn't provided`, async () => { - expect(checkActionTypeEnabled()).toMatchInlineSnapshot(` +describe('checkActionTypeEnabled', () => { + test(`returns isEnabled:true when action type isn't provided`, async () => { + expect(checkActionTypeEnabled()).toMatchInlineSnapshot(` Object { "isEnabled": true, } `); -}); + }); -test('returns isEnabled:true when action type is enabled', async () => { - const actionType: ActionType = { - id: '1', - minimumLicenseRequired: 'basic', - name: 'my action', - enabled: true, - enabledInConfig: true, - enabledInLicense: true, - }; - expect(checkActionTypeEnabled(actionType)).toMatchInlineSnapshot(` + test('returns isEnabled:true when action type is enabled', async () => { + const actionType: ActionType = { + id: '1', + minimumLicenseRequired: 'basic', + name: 'my action', + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + }; + expect(checkActionTypeEnabled(actionType)).toMatchInlineSnapshot(` Object { "isEnabled": true, } `); -}); + }); -test('returns isEnabled:false when action type is disabled by license', async () => { - const actionType: ActionType = { - id: '1', - minimumLicenseRequired: 'basic', - name: 'my action', - enabled: false, - enabledInConfig: true, - enabledInLicense: false, - }; - expect(checkActionTypeEnabled(actionType)).toMatchInlineSnapshot(` + test('returns isEnabled:false when action type is disabled by license', async () => { + const actionType: ActionType = { + id: '1', + minimumLicenseRequired: 'basic', + name: 'my action', + enabled: false, + enabledInConfig: true, + enabledInLicense: false, + }; + expect(checkActionTypeEnabled(actionType)).toMatchInlineSnapshot(` Object { "isEnabled": false, "message": "This connector requires a Basic license.", @@ -63,18 +67,82 @@ test('returns isEnabled:false when action type is disabled by license', async () , } `); + }); + + test('returns isEnabled:false when action type is disabled by config', async () => { + const actionType: ActionType = { + id: '1', + minimumLicenseRequired: 'basic', + name: 'my action', + enabled: false, + enabledInConfig: false, + enabledInLicense: true, + }; + expect(checkActionTypeEnabled(actionType)).toMatchInlineSnapshot(` + Object { + "isEnabled": false, + "message": "This connector is disabled by the Kibana configuration.", + "messageCard": , + } + `); + }); }); -test('returns isEnabled:false when action type is disabled by config', async () => { - const actionType: ActionType = { - id: '1', - minimumLicenseRequired: 'basic', - name: 'my action', - enabled: false, - enabledInConfig: false, - enabledInLicense: true, - }; - expect(checkActionTypeEnabled(actionType)).toMatchInlineSnapshot(` +describe('checkActionFormActionTypeEnabled', () => { + const preconfiguredConnectors: ActionConnector[] = [ + { + actionTypeId: '1', + config: {}, + id: 'test1', + isPreconfigured: true, + name: 'test', + secrets: {}, + referencedByCount: 0, + }, + { + actionTypeId: '2', + config: {}, + id: 'test2', + isPreconfigured: true, + name: 'test', + secrets: {}, + referencedByCount: 0, + }, + ]; + + test('returns isEnabled:true when action type is preconfigured', async () => { + const actionType: ActionType = { + id: '1', + minimumLicenseRequired: 'basic', + name: 'my action', + enabled: true, + enabledInConfig: false, + enabledInLicense: true, + }; + + expect(checkActionFormActionTypeEnabled(actionType, preconfiguredConnectors)) + .toMatchInlineSnapshot(` + Object { + "isEnabled": true, + } + `); + }); + + test('returns isEnabled:false when action type is disabled by config and not preconfigured', async () => { + const actionType: ActionType = { + id: 'disabled-by-config', + minimumLicenseRequired: 'basic', + name: 'my action', + enabled: true, + enabledInConfig: false, + enabledInLicense: true, + }; + expect(checkActionFormActionTypeEnabled(actionType, preconfiguredConnectors)) + .toMatchInlineSnapshot(` Object { "isEnabled": false, "message": "This connector is disabled by the Kibana configuration.", @@ -85,4 +153,5 @@ test('returns isEnabled:false when action type is disabled by config', async () />, } `); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.tsx b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.tsx index 263502a82ec79..971d6dbbb57bf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/check_action_type_enabled.tsx @@ -9,7 +9,7 @@ import { capitalize } from 'lodash'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiCard, EuiLink } from '@elastic/eui'; -import { ActionType } from '../../types'; +import { ActionType, ActionConnector } from '../../types'; import { VIEW_LICENSE_OPTIONS_LINK } from '../../common/constants'; import './check_action_type_enabled.scss'; @@ -22,71 +22,98 @@ export interface IsDisabledResult { messageCard: JSX.Element; } +const getLicenseCheckResult = (actionType: ActionType) => { + return { + isEnabled: false, + message: i18n.translate( + 'xpack.triggersActionsUI.checkActionTypeEnabled.actionTypeDisabledByLicenseMessage', + { + defaultMessage: 'This connector requires a {minimumLicenseRequired} license.', + values: { + minimumLicenseRequired: capitalize(actionType.minimumLicenseRequired), + }, + } + ), + messageCard: ( + + + + } + /> + ), + }; +}; + +const configurationCheckResult = { + isEnabled: false, + message: i18n.translate( + 'xpack.triggersActionsUI.checkActionTypeEnabled.actionTypeDisabledByConfigMessage', + { defaultMessage: 'This connector is disabled by the Kibana configuration.' } + ), + messageCard: ( + + ), +}; + export function checkActionTypeEnabled( actionType?: ActionType ): IsEnabledResult | IsDisabledResult { if (actionType?.enabledInLicense === false) { - return { - isEnabled: false, - message: i18n.translate( - 'xpack.triggersActionsUI.checkActionTypeEnabled.actionTypeDisabledByLicenseMessage', - { - defaultMessage: 'This connector requires a {minimumLicenseRequired} license.', - values: { - minimumLicenseRequired: capitalize(actionType.minimumLicenseRequired), - }, - } - ), - messageCard: ( - - - - } - /> - ), - }; + return getLicenseCheckResult(actionType); } if (actionType?.enabledInConfig === false) { - return { - isEnabled: false, - message: i18n.translate( - 'xpack.triggersActionsUI.checkActionTypeEnabled.actionTypeDisabledByConfigMessage', - { defaultMessage: 'This connector is disabled by the Kibana configuration.' } - ), - messageCard: ( - - ), - }; + return configurationCheckResult; + } + + return { isEnabled: true }; +} + +export function checkActionFormActionTypeEnabled( + actionType: ActionType, + preconfiguredConnectors: ActionConnector[] +): IsEnabledResult | IsDisabledResult { + if (actionType?.enabledInLicense === false) { + return getLicenseCheckResult(actionType); + } + + if ( + actionType?.enabledInConfig === false && + // do not disable action type if it contains preconfigured connectors (is preconfigured) + !preconfiguredConnectors.find( + preconfiguredConnector => preconfiguredConnector.actionTypeId === actionType.id + ) + ) { + return configurationCheckResult; } return { isEnabled: true }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index d4def86b07b1f..aed7d18bd9f3d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -73,6 +73,21 @@ describe('action_form', () => { actionParamsFields: null, }; + const preconfiguredOnly = { + id: 'preconfigured', + iconClass: 'test', + selectMessage: 'test', + validateConnector: (): ValidationResult => { + return { errors: {} }; + }, + validateParams: (): ValidationResult => { + const validationResult = { errors: {} }; + return validationResult; + }, + actionConnectorFields: null, + actionParamsFields: null, + }; + describe('action_form in alert', () => { let wrapper: ReactWrapper; @@ -95,6 +110,22 @@ describe('action_form', () => { config: {}, isPreconfigured: true, }, + { + secrets: {}, + id: 'test3', + actionTypeId: preconfiguredOnly.id, + name: 'Preconfigured Only', + config: {}, + isPreconfigured: true, + }, + { + secrets: {}, + id: 'test4', + actionTypeId: preconfiguredOnly.id, + name: 'Regular connector', + config: {}, + isPreconfigured: false, + }, ]); const mockes = coreMock.createSetup(); deps = { @@ -106,6 +137,7 @@ describe('action_form', () => { actionType, disabledByConfigActionType, disabledByLicenseActionType, + preconfiguredOnly, ]); actionTypeRegistry.has.mockReturnValue(true); actionTypeRegistry.get.mockReturnValue(actionType); @@ -166,6 +198,14 @@ describe('action_form', () => { enabledInLicense: true, minimumLicenseRequired: 'basic', }, + { + id: 'preconfigured', + name: 'Preconfigured only', + enabled: true, + enabledInConfig: false, + enabledInLicense: true, + minimumLicenseRequired: 'basic', + }, { id: 'disabled-by-config', name: 'Disabled by config', @@ -207,21 +247,27 @@ describe('action_form', () => { ).toBeFalsy(); }); - it(`doesn't render action types disabled by config`, async () => { + it('does not render action types disabled by config', async () => { await setup(); const actionOption = wrapper.find( - `[data-test-subj="disabled-by-config-ActionTypeSelectOption"]` + '[data-test-subj="disabled-by-config-ActionTypeSelectOption"]' ); expect(actionOption.exists()).toBeFalsy(); }); - it(`renders available connectors for the selected action type`, async () => { + it('render action types which is preconfigured only (disabled by config and with preconfigured connectors)', async () => { + await setup(); + const actionOption = wrapper.find('[data-test-subj="preconfigured-ActionTypeSelectOption"]'); + expect(actionOption.exists()).toBeTruthy(); + }); + + it('renders available connectors for the selected action type', async () => { await setup(); const actionOption = wrapper.find( `[data-test-subj="${actionType.id}-ActionTypeSelectOption"]` ); actionOption.first().simulate('click'); - const combobox = wrapper.find(`[data-test-subj="selectActionConnector"]`); + const combobox = wrapper.find(`[data-test-subj="selectActionConnector-${actionType.id}"]`); expect((combobox.first().props() as any).options).toMatchInlineSnapshot(` Array [ Object { @@ -238,10 +284,37 @@ describe('action_form', () => { `); }); + it('renders only preconfigured connectors for the selected preconfigured action type', async () => { + await setup(); + const actionOption = wrapper.find('[data-test-subj="preconfigured-ActionTypeSelectOption"]'); + actionOption.first().simulate('click'); + const combobox = wrapper.find('[data-test-subj="selectActionConnector-preconfigured"]'); + expect((combobox.first().props() as any).options).toMatchInlineSnapshot(` + Array [ + Object { + "id": "test3", + "key": "test3", + "label": "Preconfigured Only (preconfigured)", + }, + ] + `); + }); + + it('does not render "Add new" button for preconfigured only action type', async () => { + await setup(); + const actionOption = wrapper.find('[data-test-subj="preconfigured-ActionTypeSelectOption"]'); + actionOption.first().simulate('click'); + const preconfigPannel = wrapper.find('[data-test-subj="alertActionAccordion-default"]'); + const addNewConnectorButton = preconfigPannel.find( + '[data-test-subj="addNewActionConnectorButton-preconfigured"]' + ); + expect(addNewConnectorButton.exists()).toBeFalsy(); + }); + it('renders action types disabled by license', async () => { await setup(); const actionOption = wrapper.find( - `[data-test-subj="disabled-by-license-ActionTypeSelectOption"]` + '[data-test-subj="disabled-by-license-ActionTypeSelectOption"]' ); expect(actionOption.exists()).toBeTruthy(); expect( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 4199cfb7b4b7f..0027837c913d1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -29,7 +29,7 @@ import { EuiText, } from '@elastic/eui'; import { HttpSetup, ToastsApi } from 'kibana/public'; -import { loadActionTypes, loadAllActions } from '../../lib/action_connector_api'; +import { loadActionTypes, loadAllActions as loadConnectors } from '../../lib/action_connector_api'; import { IErrorObject, ActionTypeModel, @@ -42,7 +42,7 @@ import { SectionLoading } from '../../components/section_loading'; import { ConnectorAddModal } from './connector_add_modal'; import { TypeRegistry } from '../../type_registry'; import { actionTypeCompare } from '../../lib/action_type_compare'; -import { checkActionTypeEnabled } from '../../lib/check_action_type_enabled'; +import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_enabled'; import { VIEW_LICENSE_OPTIONS_LINK } from '../../../common/constants'; interface ActionAccordionFormProps { @@ -111,14 +111,12 @@ export const ActionForm = ({ setHasActionsDisabled(hasActionsDisabled); } } catch (e) { - if (toastNotifications) { - toastNotifications.addDanger({ - title: i18n.translate( - 'xpack.triggersActionsUI.sections.alertForm.unableToLoadActionTypesMessage', - { defaultMessage: 'Unable to load action types' } - ), - }); - } + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertForm.unableToLoadActionTypesMessage', + { defaultMessage: 'Unable to load action types' } + ), + }); } finally { setIsLoadingActionTypes(false); } @@ -126,41 +124,50 @@ export const ActionForm = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // load connectors useEffect(() => { - loadConnectors(); + (async () => { + try { + setIsLoadingConnectors(true); + setConnectors(await loadConnectors({ http })); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertForm.unableToLoadActionsMessage', + { + defaultMessage: 'Unable to load connectors', + } + ), + }); + } finally { + setIsLoadingConnectors(false); + } + })(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - async function loadConnectors() { - try { - setIsLoadingConnectors(true); - const actionsResponse = await loadAllActions({ http }); - setConnectors(actionsResponse); - } catch (e) { - toastNotifications.addDanger({ - title: i18n.translate( - 'xpack.triggersActionsUI.sections.alertForm.unableToLoadActionsMessage', - { - defaultMessage: 'Unable to load connectors', - } - ), - }); - } finally { - setIsLoadingConnectors(false); - } - } const preconfiguredMessage = i18n.translate( 'xpack.triggersActionsUI.sections.actionForm.preconfiguredTitleMessage', { defaultMessage: '(preconfigured)', } ); + const getSelectedOptions = (actionItemId: string) => { - const val = connectors.find(connector => connector.id === actionItemId); - if (!val) { + const selectedConnector = connectors.find(connector => connector.id === actionItemId); + if ( + !selectedConnector || + // if selected connector is not preconfigured and action type is for preconfiguration only, + // do not show regular connectors of this type + (actionTypesIndex && + !actionTypesIndex[selectedConnector.actionTypeId].enabledInConfig && + !selectedConnector.isPreconfigured) + ) { return []; } - const optionTitle = `${val.name} ${val.isPreconfigured ? preconfiguredMessage : ''}`; + const optionTitle = `${selectedConnector.name} ${ + selectedConnector.isPreconfigured ? preconfiguredMessage : '' + }`; return [ { label: optionTitle, @@ -179,8 +186,15 @@ export const ActionForm = ({ }, index: number ) => { + const actionType = actionTypesIndex![actionItem.actionTypeId]; + const optionsList = connectors - .filter(connectorItem => connectorItem.actionTypeId === actionItem.actionTypeId) + .filter( + connectorItem => + connectorItem.actionTypeId === actionItem.actionTypeId && + // include only enabled by config connectors or preconfigured + (actionType.enabledInConfig || connectorItem.isPreconfigured) + ) .map(({ name, id, isPreconfigured }) => ({ label: `${name} ${isPreconfigured ? preconfiguredMessage : ''}`, key: id, @@ -189,8 +203,9 @@ export const ActionForm = ({ const actionTypeRegistered = actionTypeRegistry.get(actionConnector.actionTypeId); if (!actionTypeRegistered || actionItem.group !== defaultActionGroupId) return null; const ParamsFieldsComponent = actionTypeRegistered.actionParamsFields; - const checkEnabledResult = checkActionTypeEnabled( - actionTypesIndex && actionTypesIndex[actionConnector.actionTypeId] + const checkEnabledResult = checkActionFormActionTypeEnabled( + actionTypesIndex![actionConnector.actionTypeId], + connectors.filter(connector => connector.isPreconfigured) ); const accordionContent = checkEnabledResult.isEnabled ? ( @@ -211,19 +226,21 @@ export const ActionForm = ({ /> } labelAppend={ - { - setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index }); - setAddModalVisibility(true); - }} - > - - + actionTypesIndex![actionConnector.actionTypeId].enabledInConfig ? ( + { + setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index }); + setAddModalVisibility(true); + }} + > + + + ) : null } > { setActionIdByIndex(selectedOptions[0].id ?? '', index); @@ -258,10 +275,9 @@ export const ActionForm = ({ ); return ( - + -

+

-

+
@@ -349,10 +365,9 @@ export const ActionForm = ({ const actionTypeRegistered = actionTypeRegistry.get(actionItem.actionTypeId); if (!actionTypeRegistered || actionItem.group !== defaultActionGroupId) return null; return ( - + -

+

-

+
@@ -486,18 +501,26 @@ export const ActionForm = ({ } } - let actionTypeNodes: JSX.Element[] | null = null; + let actionTypeNodes: Array | null = null; let hasDisabledByLicenseActionTypes = false; if (actionTypesIndex) { + const preconfiguredConnectors = connectors.filter(connector => connector.isPreconfigured); actionTypeNodes = actionTypeRegistry .list() - .filter( - item => actionTypesIndex[item.id] && actionTypesIndex[item.id].enabledInConfig === true + .filter(item => actionTypesIndex[item.id]) + .sort((a, b) => + actionTypeCompare(actionTypesIndex[a.id], actionTypesIndex[b.id], preconfiguredConnectors) ) - .sort((a, b) => actionTypeCompare(actionTypesIndex[a.id], actionTypesIndex[b.id])) .map(function(item, index) { const actionType = actionTypesIndex[item.id]; - const checkEnabledResult = checkActionTypeEnabled(actionTypesIndex[item.id]); + const checkEnabledResult = checkActionFormActionTypeEnabled( + actionTypesIndex[item.id], + preconfiguredConnectors + ); + // if action type is not enabled in config and not preconfigured, it shouldn't be displayed + if (!actionType.enabledInConfig && !checkEnabledResult.isEnabled) { + return null; + } if (!actionType.enabledInLicense) { hasDisabledByLicenseActionTypes = true; } diff --git a/x-pack/test/api_integration/apis/fleet/agents/acks.ts b/x-pack/test/api_integration/apis/fleet/agents/acks.ts index f08ce33d8b60f..adde6dd184b81 100644 --- a/x-pack/test/api_integration/apis/fleet/agents/acks.ts +++ b/x-pack/test/api_integration/apis/fleet/agents/acks.ts @@ -32,12 +32,12 @@ export default function(providerContext: FtrProviderContext) { body: { _source: agentDoc }, } = await esClient.get({ index: '.kibana', - id: 'agents:agent1', + id: 'fleet-agents:agent1', }); - agentDoc.agents.access_api_key_id = apiKey.id; + agentDoc['fleet-agents'].access_api_key_id = apiKey.id; await esClient.update({ index: '.kibana', - id: 'agents:agent1', + id: 'fleet-agents:agent1', refresh: 'true', body: { doc: agentDoc, diff --git a/x-pack/test/api_integration/apis/fleet/agents/actions.ts b/x-pack/test/api_integration/apis/fleet/agents/actions.ts index cf0641acf9e1c..577299e652610 100644 --- a/x-pack/test/api_integration/apis/fleet/agents/actions.ts +++ b/x-pack/test/api_integration/apis/fleet/agents/actions.ts @@ -67,7 +67,7 @@ export default function(providerContext: FtrProviderContext) { }, }) .expect(404); - expect(apiResponse.message).to.eql('Saved object [agents/agent100] not found'); + expect(apiResponse.message).to.eql('Saved object [fleet-agents/agent100] not found'); }); }); } diff --git a/x-pack/test/api_integration/apis/fleet/agents/checkin.ts b/x-pack/test/api_integration/apis/fleet/agents/checkin.ts index ca51676126e73..b405b5065bc0e 100644 --- a/x-pack/test/api_integration/apis/fleet/agents/checkin.ts +++ b/x-pack/test/api_integration/apis/fleet/agents/checkin.ts @@ -32,12 +32,12 @@ export default function(providerContext: FtrProviderContext) { body: { _source: agentDoc }, } = await esClient.get({ index: '.kibana', - id: 'agents:agent1', + id: 'fleet-agents:agent1', }); - agentDoc.agents.access_api_key_id = apiKey.id; + agentDoc['fleet-agents'].access_api_key_id = apiKey.id; await esClient.update({ index: '.kibana', - id: 'agents:agent1', + id: 'fleet-agents:agent1', refresh: 'true', body: { doc: agentDoc, diff --git a/x-pack/test/api_integration/apis/fleet/agents/enroll.ts b/x-pack/test/api_integration/apis/fleet/agents/enroll.ts index d8e9749744ea4..c934ddf8a406b 100644 --- a/x-pack/test/api_integration/apis/fleet/agents/enroll.ts +++ b/x-pack/test/api_integration/apis/fleet/agents/enroll.ts @@ -33,13 +33,13 @@ export default function(providerContext: FtrProviderContext) { body: { _source: enrollmentApiKeyDoc }, } = await esClient.get({ index: '.kibana', - id: 'enrollment_api_keys:ed22ca17-e178-4cfe-8b02-54ea29fbd6d0', + id: 'fleet-enrollment-api-keys:ed22ca17-e178-4cfe-8b02-54ea29fbd6d0', }); // @ts-ignore - enrollmentApiKeyDoc.enrollment_api_keys.api_key_id = apiKey.id; + enrollmentApiKeyDoc['fleet-enrollment-api-keys'].api_key_id = apiKey.id; await esClient.update({ index: '.kibana', - id: 'enrollment_api_keys:ed22ca17-e178-4cfe-8b02-54ea29fbd6d0', + id: 'fleet-enrollment-api-keys:ed22ca17-e178-4cfe-8b02-54ea29fbd6d0', refresh: 'true', body: { doc: enrollmentApiKeyDoc, diff --git a/x-pack/test/api_integration/apis/fleet/unenroll_agent.ts b/x-pack/test/api_integration/apis/fleet/unenroll_agent.ts index d33b92acf95a5..5b8e03269ceef 100644 --- a/x-pack/test/api_integration/apis/fleet/unenroll_agent.ts +++ b/x-pack/test/api_integration/apis/fleet/unenroll_agent.ts @@ -40,18 +40,18 @@ export default function(providerContext: FtrProviderContext) { body: { _source: agentDoc }, } = await esClient.get({ index: '.kibana', - id: 'agents:agent1', + id: 'fleet-agents:agent1', }); // @ts-ignore - agentDoc.agents.access_api_key_id = accessAPIKeyId; - agentDoc.agents.default_api_key_id = outputAPIKeyBody.id; - agentDoc.agents.default_api_key = Buffer.from( + agentDoc['fleet-agents'].access_api_key_id = accessAPIKeyId; + agentDoc['fleet-agents'].default_api_key_id = outputAPIKeyBody.id; + agentDoc['fleet-agents'].default_api_key = Buffer.from( `${outputAPIKeyBody.id}:${outputAPIKeyBody.api_key}` ).toString('base64'); await esClient.update({ index: '.kibana', - id: 'agents:agent1', + id: 'fleet-agents:agent1', refresh: 'true', body: { doc: agentDoc, diff --git a/x-pack/test/api_integration/apis/infra/log_entries.ts b/x-pack/test/api_integration/apis/infra/log_entries.ts index 3c12f5e4dc789..991dc4a7f96cf 100644 --- a/x-pack/test/api_integration/apis/infra/log_entries.ts +++ b/x-pack/test/api_integration/apis/infra/log_entries.ts @@ -126,7 +126,7 @@ export default function({ getService }: FtrProviderContext) { expect(messageColumn.message.length).to.be.greaterThan(0); }); - it('Returns the context fields', async () => { + it('Does not build context if entry does not have all fields', async () => { const { body } = await supertest .post(LOG_ENTRIES_PATH) .set(COMMON_HEADERS) @@ -147,9 +147,7 @@ export default function({ getService }: FtrProviderContext) { const entries = logEntriesResponse.data.entries; const entry = entries[0]; - - expect(entry.context).to.have.property('host.name'); - expect(entry.context['host.name']).to.be('demo-stack-nginx-01'); + expect(entry.context).to.eql({}); }); it('Paginates correctly with `after`', async () => { diff --git a/x-pack/test/functional/apps/api_keys/home_page.ts b/x-pack/test/functional/apps/api_keys/home_page.ts index 1c83a17e78ca7..abfd90bd6ad8e 100644 --- a/x-pack/test/functional/apps/api_keys/home_page.ts +++ b/x-pack/test/functional/apps/api_keys/home_page.ts @@ -13,7 +13,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const security = getService('security'); describe('Home page', function() { - this.tags('smoke'); before(async () => { await security.testUser.setRoles(['kibana_admin']); await pageObjects.common.navigateToApp('apiKeys'); diff --git a/x-pack/test/functional/apps/canvas/smoke_test.js b/x-pack/test/functional/apps/canvas/smoke_test.js index a240a55b9765c..df41b725f6daf 100644 --- a/x-pack/test/functional/apps/canvas/smoke_test.js +++ b/x-pack/test/functional/apps/canvas/smoke_test.js @@ -15,7 +15,7 @@ export default function canvasSmokeTest({ getService, getPageObjects }) { const PageObjects = getPageObjects(['common']); describe('smoke test', function() { - this.tags('smoke'); + this.tags('includeFirefox'); const workpadListSelector = 'canvasWorkpadLoaderTable > canvasWorkpadLoaderWorkpad'; const testWorkpadId = 'workpad-1705f884-6224-47de-ba49-ca224fe6ec31'; diff --git a/x-pack/test/functional/apps/cross_cluster_replication/home_page.ts b/x-pack/test/functional/apps/cross_cluster_replication/home_page.ts index eef47ae0341c8..8d3b98004245e 100644 --- a/x-pack/test/functional/apps/cross_cluster_replication/home_page.ts +++ b/x-pack/test/functional/apps/cross_cluster_replication/home_page.ts @@ -12,7 +12,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const log = getService('log'); describe('Home page', function() { - this.tags('smoke'); before(async () => { await pageObjects.common.navigateToApp('crossClusterReplication'); }); diff --git a/x-pack/test/functional/apps/grok_debugger/grok_debugger.js b/x-pack/test/functional/apps/grok_debugger/grok_debugger.js index 994f65a79011e..09d246a3a6e1b 100644 --- a/x-pack/test/functional/apps/grok_debugger/grok_debugger.js +++ b/x-pack/test/functional/apps/grok_debugger/grok_debugger.js @@ -12,7 +12,7 @@ export default function({ getService, getPageObjects }) { const PageObjects = getPageObjects(['grokDebugger']); describe('grok debugger app', function() { - this.tags('smoke'); + this.tags('includeFirefox'); before(async () => { await esArchiver.load('empty_kibana'); // Increase window height to ensure "Simulate" button is shown above the diff --git a/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts b/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts index c7ad3f933ea64..b5d43be21f0c5 100644 --- a/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts +++ b/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts @@ -12,7 +12,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const log = getService('log'); describe('Home page', function() { - this.tags('smoke'); before(async () => { await pageObjects.common.navigateToApp('indexLifecycleManagement'); }); diff --git a/x-pack/test/functional/apps/index_management/home_page.ts b/x-pack/test/functional/apps/index_management/home_page.ts index c6b7517fc1858..046b8ec44b9fa 100644 --- a/x-pack/test/functional/apps/index_management/home_page.ts +++ b/x-pack/test/functional/apps/index_management/home_page.ts @@ -14,7 +14,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const browser = getService('browser'); describe('Home page', function() { - this.tags('smoke'); before(async () => { await pageObjects.common.navigateToApp('indexManagement'); }); diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index 0c1d57202b8eb..ed8bec570ab60 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -15,7 +15,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'infraHome']); describe('Home page', function() { - this.tags('smoke'); + this.tags('includeFirefox'); before(async () => { await esArchiver.load('empty_kibana'); }); diff --git a/x-pack/test/functional/apps/infra/link_to.ts b/x-pack/test/functional/apps/infra/link_to.ts index a287d53d5df0b..4e5ebab90880e 100644 --- a/x-pack/test/functional/apps/infra/link_to.ts +++ b/x-pack/test/functional/apps/infra/link_to.ts @@ -21,7 +21,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const traceId = '433b4651687e18be2c6c8e3b11f53d09'; describe('Infra link-to', function() { - this.tags('smoke'); it('redirects to the logs app and parses URL search params correctly', async () => { const location = { hash: '', diff --git a/x-pack/test/functional/apps/infra/log_entry_categories_tab.ts b/x-pack/test/functional/apps/infra/log_entry_categories_tab.ts index c703738e37228..3dd1592286a3a 100644 --- a/x-pack/test/functional/apps/infra/log_entry_categories_tab.ts +++ b/x-pack/test/functional/apps/infra/log_entry_categories_tab.ts @@ -13,7 +13,7 @@ export default ({ getService }: FtrProviderContext) => { const retry = getService('retry'); describe('Log Entry Categories Tab', function() { - this.tags('smoke'); + this.tags('includeFirefox'); describe('with a trial license', () => { it('is visible', async () => { diff --git a/x-pack/test/functional/apps/infra/log_entry_rate_tab.ts b/x-pack/test/functional/apps/infra/log_entry_rate_tab.ts index 95228a520aaa2..ee4b59d04899f 100644 --- a/x-pack/test/functional/apps/infra/log_entry_rate_tab.ts +++ b/x-pack/test/functional/apps/infra/log_entry_rate_tab.ts @@ -13,7 +13,7 @@ export default ({ getService }: FtrProviderContext) => { const retry = getService('retry'); describe('Log Entry Rate Tab', function() { - this.tags('smoke'); + this.tags('includeFirefox'); describe('with a trial license', () => { it('is visible', async () => { diff --git a/x-pack/test/functional/apps/infra/logs_source_configuration.ts b/x-pack/test/functional/apps/infra/logs_source_configuration.ts index f40c908f23c80..7293405ce80ff 100644 --- a/x-pack/test/functional/apps/infra/logs_source_configuration.ts +++ b/x-pack/test/functional/apps/infra/logs_source_configuration.ts @@ -17,8 +17,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const retry = getService('retry'); describe('Logs Source Configuration', function() { - this.tags('smoke'); - before(async () => { await esArchiver.load('empty_kibana'); }); diff --git a/x-pack/test/functional/apps/infra/metrics_source_configuration.ts b/x-pack/test/functional/apps/infra/metrics_source_configuration.ts index d334fa7956be4..1c03b3ebf6681 100644 --- a/x-pack/test/functional/apps/infra/metrics_source_configuration.ts +++ b/x-pack/test/functional/apps/infra/metrics_source_configuration.ts @@ -15,7 +15,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'infraHome']); describe('Infrastructure Source Configuration', function() { - this.tags('smoke'); before(async () => { await esArchiver.load('empty_kibana'); }); diff --git a/x-pack/test/functional/apps/logstash/index.js b/x-pack/test/functional/apps/logstash/index.js index ee710b00b0be8..0ca09aa317bc0 100644 --- a/x-pack/test/functional/apps/logstash/index.js +++ b/x-pack/test/functional/apps/logstash/index.js @@ -6,7 +6,7 @@ export default function({ loadTestFile }) { describe('logstash', function() { - this.tags(['ciGroup2', 'smoke']); + this.tags(['ciGroup2']); loadTestFile(require.resolve('./pipeline_list')); loadTestFile(require.resolve('./pipeline_create')); diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts index a5faf325aa6cb..a9133bb380179 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/advanced_job.ts @@ -195,7 +195,6 @@ export default function({ getService }: FtrProviderContext) { modelSizeStats: { result_type: 'model_size_stats', model_bytes_exceeded: '0.0 B', - model_bytes_memory_limit: '10.0 MB', total_by_field_count: '37', total_over_field_count: '92', total_partition_field_count: '8', @@ -262,7 +261,6 @@ export default function({ getService }: FtrProviderContext) { modelSizeStats: { result_type: 'model_size_stats', model_bytes_exceeded: '0.0 B', - model_bytes_memory_limit: '100.0 MB', total_by_field_count: '994', total_over_field_count: '0', total_partition_field_count: '2', @@ -277,7 +275,7 @@ export default function({ getService }: FtrProviderContext) { const calendarId = `wizard-test-calendar_${Date.now()}`; describe('advanced job', function() { - this.tags(['smoke', 'mlqa']); + this.tags(['mlqa']); before(async () => { await esArchiver.loadIfNeeded('ml/ecommerce'); await ml.testResources.createIndexPatternIfNeeded('ft_ecommerce', 'order_date'); @@ -549,7 +547,6 @@ export default function({ getService }: FtrProviderContext) { job_id: testData.jobId, result_type: testData.expected.modelSizeStats.result_type, model_bytes_exceeded: testData.expected.modelSizeStats.model_bytes_exceeded, - model_bytes_memory_limit: testData.expected.modelSizeStats.model_bytes_memory_limit, total_by_field_count: testData.expected.modelSizeStats.total_by_field_count, total_over_field_count: testData.expected.modelSizeStats.total_over_field_count, total_partition_field_count: @@ -813,7 +810,6 @@ export default function({ getService }: FtrProviderContext) { job_id: testData.jobIdClone, result_type: testData.expected.modelSizeStats.result_type, model_bytes_exceeded: testData.expected.modelSizeStats.model_bytes_exceeded, - model_bytes_memory_limit: testData.expected.modelSizeStats.model_bytes_memory_limit, total_by_field_count: testData.expected.modelSizeStats.total_by_field_count, total_over_field_count: testData.expected.modelSizeStats.total_over_field_count, total_partition_field_count: diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/anomaly_explorer.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/anomaly_explorer.ts index 8827559a5f470..ae88208fa9a11 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/anomaly_explorer.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/anomaly_explorer.ts @@ -57,7 +57,7 @@ export default function({ getService }: FtrProviderContext) { const ml = getService('ml'); describe('anomaly explorer', function() { - this.tags(['smoke', 'mlqa']); + this.tags(['mlqa']); before(async () => { await esArchiver.loadIfNeeded('ml/farequote'); await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/categorization_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/categorization_job.ts index 9b5ae171d4115..b8ac646c35875 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/categorization_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/categorization_job.ts @@ -64,7 +64,6 @@ export default function({ getService }: FtrProviderContext) { job_id: expectedJobId, result_type: 'model_size_stats', model_bytes_exceeded: '0.0 B', - model_bytes_memory_limit: '15.0 MB', total_by_field_count: '30', total_over_field_count: '0', total_partition_field_count: '2', @@ -77,7 +76,7 @@ export default function({ getService }: FtrProviderContext) { const calendarId = `wizard-test-calendar_${Date.now()}`; describe('categorization', function() { - this.tags(['smoke', 'mlqa']); + this.tags(['mlqa']); before(async () => { await esArchiver.loadIfNeeded('ml/categorization'); await ml.testResources.createIndexPatternIfNeeded('ft_categorization', '@timestamp'); diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/date_nanos_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/date_nanos_job.ts index 570deee01c684..d3934d674124e 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/date_nanos_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/date_nanos_job.ts @@ -153,7 +153,6 @@ export default function({ getService }: FtrProviderContext) { modelSizeStats: { result_type: 'model_size_stats', model_bytes_exceeded: '0.0 B', - model_bytes_memory_limit: '10.0 MB', total_by_field_count: '3', total_over_field_count: '0', total_partition_field_count: '2', @@ -166,7 +165,7 @@ export default function({ getService }: FtrProviderContext) { ]; describe('job on data set with date_nanos time field', function() { - this.tags(['smoke', 'mlqa']); + this.tags(['mlqa']); before(async () => { await esArchiver.loadIfNeeded('ml/event_rate_nanos'); await ml.testResources.createIndexPatternIfNeeded( @@ -422,7 +421,6 @@ export default function({ getService }: FtrProviderContext) { job_id: testData.jobId, result_type: testData.expected.modelSizeStats.result_type, model_bytes_exceeded: testData.expected.modelSizeStats.model_bytes_exceeded, - model_bytes_memory_limit: testData.expected.modelSizeStats.model_bytes_memory_limit, total_by_field_count: testData.expected.modelSizeStats.total_by_field_count, total_over_field_count: testData.expected.modelSizeStats.total_over_field_count, total_partition_field_count: diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts index 4739f987541d6..f6a9c96492f39 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts @@ -61,7 +61,6 @@ export default function({ getService }: FtrProviderContext) { job_id: expectedJobId, result_type: 'model_size_stats', model_bytes_exceeded: '0.0 B', - model_bytes_memory_limit: '20.0 MB', total_by_field_count: '59', total_over_field_count: '0', total_partition_field_count: '58', @@ -74,7 +73,7 @@ export default function({ getService }: FtrProviderContext) { const calendarId = `wizard-test-calendar_${Date.now()}`; describe('multi metric', function() { - this.tags(['smoke', 'mlqa']); + this.tags(['mlqa']); before(async () => { await esArchiver.loadIfNeeded('ml/farequote'); await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts index 0279c70bb73a9..547c489411b5f 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts @@ -75,7 +75,6 @@ export default function({ getService }: FtrProviderContext) { job_id: expectedJobId, result_type: 'model_size_stats', model_bytes_exceeded: '0.0 B', - model_bytes_memory_limit: '8.0 MB', total_by_field_count: '25', total_over_field_count: '92', total_partition_field_count: '3', @@ -88,7 +87,7 @@ export default function({ getService }: FtrProviderContext) { const calendarId = `wizard-test-calendar_${Date.now()}`; describe('population', function() { - this.tags(['smoke', 'mlqa']); + this.tags(['mlqa']); before(async () => { await esArchiver.loadIfNeeded('ml/ecommerce'); await ml.testResources.createIndexPatternIfNeeded('ft_ecommerce', 'order_date'); diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts index a5652d76358eb..b1fee1633641a 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts @@ -54,7 +54,6 @@ export default function({ getService }: FtrProviderContext) { modelSizeStats: { result_type: 'model_size_stats', model_bytes_exceeded: '0.0 B', - model_bytes_memory_limit: '20.0 MB', total_by_field_count: '3', total_over_field_count: '0', total_partition_field_count: '2', @@ -105,7 +104,6 @@ export default function({ getService }: FtrProviderContext) { modelSizeStats: { result_type: 'model_size_stats', model_bytes_exceeded: '0.0 B', - model_bytes_memory_limit: '20.0 MB', total_by_field_count: '7', total_over_field_count: '0', total_partition_field_count: '6', @@ -156,7 +154,6 @@ export default function({ getService }: FtrProviderContext) { modelSizeStats: { result_type: 'model_size_stats', model_bytes_exceeded: '0.0 B', - model_bytes_memory_limit: '20.0 MB', total_by_field_count: '7', total_over_field_count: '0', total_partition_field_count: '6', @@ -208,7 +205,6 @@ export default function({ getService }: FtrProviderContext) { modelSizeStats: { result_type: 'model_size_stats', model_bytes_exceeded: '0.0 B', - model_bytes_memory_limit: '20.0 MB', total_by_field_count: '3', total_over_field_count: '0', total_partition_field_count: '2', @@ -259,7 +255,6 @@ export default function({ getService }: FtrProviderContext) { modelSizeStats: { result_type: 'model_size_stats', model_bytes_exceeded: '0.0 B', - model_bytes_memory_limit: '20.0 MB', total_by_field_count: '3', total_over_field_count: '0', total_partition_field_count: '2', @@ -272,7 +267,7 @@ export default function({ getService }: FtrProviderContext) { ]; describe('saved search', function() { - this.tags(['smoke', 'mlqa']); + this.tags(['mlqa']); before(async () => { await esArchiver.loadIfNeeded('ml/farequote'); await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); @@ -464,7 +459,6 @@ export default function({ getService }: FtrProviderContext) { job_id: testData.jobId, result_type: testData.expected.modelSizeStats.result_type, model_bytes_exceeded: testData.expected.modelSizeStats.model_bytes_exceeded, - model_bytes_memory_limit: testData.expected.modelSizeStats.model_bytes_memory_limit, total_by_field_count: testData.expected.modelSizeStats.total_by_field_count, total_over_field_count: testData.expected.modelSizeStats.total_over_field_count, total_partition_field_count: diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts index 43053decb3924..0f8655e3c6bbc 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts @@ -60,7 +60,6 @@ export default function({ getService }: FtrProviderContext) { job_id: expectedJobId, result_type: 'model_size_stats', model_bytes_exceeded: '0.0 B', - model_bytes_memory_limit: '15.0 MB', total_by_field_count: '3', total_over_field_count: '0', total_partition_field_count: '2', @@ -73,7 +72,7 @@ export default function({ getService }: FtrProviderContext) { const calendarId = `wizard-test-calendar_${Date.now()}`; describe('single metric', function() { - this.tags(['smoke', 'mlqa']); + this.tags(['mlqa']); before(async () => { await esArchiver.loadIfNeeded('ml/farequote'); await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_viewer.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_viewer.ts index cc7c9828ce87d..9e1829998bec4 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_viewer.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_viewer.ts @@ -40,7 +40,7 @@ export default function({ getService }: FtrProviderContext) { const ml = getService('ml'); describe('single metric viewer', function() { - this.tags(['smoke', 'mlqa']); + this.tags(['mlqa']); before(async () => { await esArchiver.loadIfNeeded('ml/farequote'); await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); diff --git a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/classification_creation.ts b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/classification_creation.ts index 8a6741bd88daa..eb57a743f14cb 100644 --- a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/classification_creation.ts +++ b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/classification_creation.ts @@ -12,7 +12,6 @@ export default function({ getService }: FtrProviderContext) { const ml = getService('ml'); describe('classification creation', function() { - this.tags(['smoke']); before(async () => { await esArchiver.loadIfNeeded('ml/bm_classification'); await ml.testResources.createIndexPatternIfNeeded('ft_bank_marketing', '@timestamp'); diff --git a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/cloning.ts b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/cloning.ts index d98d8feaaf4fe..93f225989592e 100644 --- a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/cloning.ts +++ b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/cloning.ts @@ -14,8 +14,6 @@ export default function({ getService }: FtrProviderContext) { const ml = getService('ml'); describe('jobs cloning supported by UI form', function() { - this.tags(['smoke']); - const testDataList: Array<{ suiteTitle: string; archive: string; diff --git a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/outlier_detection_creation.ts b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/outlier_detection_creation.ts index 8dfe058cf6885..4e8a9000598b3 100644 --- a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/outlier_detection_creation.ts +++ b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/outlier_detection_creation.ts @@ -12,7 +12,6 @@ export default function({ getService }: FtrProviderContext) { const ml = getService('ml'); describe('outlier detection creation', function() { - this.tags(['smoke']); before(async () => { await esArchiver.loadIfNeeded('ml/ihp_outlier'); await ml.testResources.createIndexPatternIfNeeded('ft_ihp_outlier', '@timestamp'); diff --git a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/regression_creation.ts b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/regression_creation.ts index 271f3e2018dad..c3718e421d451 100644 --- a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/regression_creation.ts +++ b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/regression_creation.ts @@ -12,7 +12,6 @@ export default function({ getService }: FtrProviderContext) { const ml = getService('ml'); describe('regression creation', function() { - this.tags(['smoke']); before(async () => { await esArchiver.loadIfNeeded('ml/egs_regression'); await ml.testResources.createIndexPatternIfNeeded('ft_egs_regression', '@timestamp'); diff --git a/x-pack/test/functional/apps/machine_learning/data_visualizer/file_data_visualizer.ts b/x-pack/test/functional/apps/machine_learning/data_visualizer/file_data_visualizer.ts index ae958ad7f570f..868e75228f1cc 100644 --- a/x-pack/test/functional/apps/machine_learning/data_visualizer/file_data_visualizer.ts +++ b/x-pack/test/functional/apps/machine_learning/data_visualizer/file_data_visualizer.ts @@ -34,7 +34,7 @@ export default function({ getService }: FtrProviderContext) { ]; describe('file based', function() { - this.tags(['smoke', 'mlqa']); + this.tags(['mlqa']); before(async () => { await ml.testResources.setKibanaTimeZoneToUTC(); diff --git a/x-pack/test/functional/apps/machine_learning/data_visualizer/index_data_visualizer.ts b/x-pack/test/functional/apps/machine_learning/data_visualizer/index_data_visualizer.ts index e71b57a4562e7..e4e0c1c92d73a 100644 --- a/x-pack/test/functional/apps/machine_learning/data_visualizer/index_data_visualizer.ts +++ b/x-pack/test/functional/apps/machine_learning/data_visualizer/index_data_visualizer.ts @@ -376,7 +376,7 @@ export default function({ getService }: FtrProviderContext) { } describe('index based', function() { - this.tags(['smoke', 'mlqa']); + this.tags(['mlqa']); before(async () => { await esArchiver.loadIfNeeded('ml/farequote'); await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); diff --git a/x-pack/test/functional/apps/machine_learning/pages.ts b/x-pack/test/functional/apps/machine_learning/pages.ts index 95930f18061fa..35536b4eeda12 100644 --- a/x-pack/test/functional/apps/machine_learning/pages.ts +++ b/x-pack/test/functional/apps/machine_learning/pages.ts @@ -10,7 +10,7 @@ export default function({ getService }: FtrProviderContext) { const ml = getService('ml'); describe('page navigation', function() { - this.tags(['smoke', 'mlqa']); + this.tags(['skipFirefox', 'mlqa']); before(async () => { await ml.api.cleanMlIndices(); await ml.securityUI.loginAsMlPowerUser(); diff --git a/x-pack/test/functional/apps/remote_clusters/home_page.ts b/x-pack/test/functional/apps/remote_clusters/home_page.ts index 879a0a11ede78..394e48404b927 100644 --- a/x-pack/test/functional/apps/remote_clusters/home_page.ts +++ b/x-pack/test/functional/apps/remote_clusters/home_page.ts @@ -11,7 +11,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'remoteClusters']); describe('Home page', function() { - this.tags('smoke'); before(async () => { await pageObjects.common.navigateToApp('remoteClusters'); }); diff --git a/x-pack/test/functional/apps/security/security.ts b/x-pack/test/functional/apps/security/security.ts index 2096a7755e01d..37516acec7c4b 100644 --- a/x-pack/test/functional/apps/security/security.ts +++ b/x-pack/test/functional/apps/security/security.ts @@ -16,7 +16,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { const spaces = getService('spaces'); describe('Security', function() { - this.tags('smoke'); + this.tags('includeFirefox'); describe('Login Page', () => { before(async () => { await esArchiver.load('empty_kibana'); diff --git a/x-pack/test/functional/apps/security/user_email.js b/x-pack/test/functional/apps/security/user_email.js index a007c40a06b62..b4adfe1ce2b66 100644 --- a/x-pack/test/functional/apps/security/user_email.js +++ b/x-pack/test/functional/apps/security/user_email.js @@ -12,7 +12,6 @@ export default function({ getService, getPageObjects }) { const esArchiver = getService('esArchiver'); describe('useremail', function() { - this.tags('smoke'); before(async () => { await esArchiver.load('security/discover'); await PageObjects.settings.navigateTo(); diff --git a/x-pack/test/functional/apps/security/users.js b/x-pack/test/functional/apps/security/users.js index f49a74a661a63..04d59334a01c4 100644 --- a/x-pack/test/functional/apps/security/users.js +++ b/x-pack/test/functional/apps/security/users.js @@ -12,7 +12,6 @@ export default function({ getService, getPageObjects }) { const log = getService('log'); describe('users', function() { - this.tags('smoke'); before(async () => { log.debug('users'); await PageObjects.settings.navigateTo(); diff --git a/x-pack/test/functional/apps/snapshot_restore/home_page.ts b/x-pack/test/functional/apps/snapshot_restore/home_page.ts index 608c7f321a08f..56ebee79d06ff 100644 --- a/x-pack/test/functional/apps/snapshot_restore/home_page.ts +++ b/x-pack/test/functional/apps/snapshot_restore/home_page.ts @@ -13,7 +13,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const es = getService('legacyEs'); describe('Home page', function() { - this.tags('smoke'); before(async () => { await pageObjects.common.navigateToApp('snapshotRestore'); }); diff --git a/x-pack/test/functional/apps/spaces/enter_space.ts b/x-pack/test/functional/apps/spaces/enter_space.ts index 38220c15cb266..d45b8a1ea4cdb 100644 --- a/x-pack/test/functional/apps/spaces/enter_space.ts +++ b/x-pack/test/functional/apps/spaces/enter_space.ts @@ -13,7 +13,7 @@ export default function enterSpaceFunctonalTests({ const PageObjects = getPageObjects(['security', 'spaceSelector']); describe('Enter Space', function() { - this.tags('smoke'); + this.tags('includeFirefox'); before(async () => { await esArchiver.load('spaces/enter_space'); await PageObjects.security.forceLogout(); diff --git a/x-pack/test/functional/apps/spaces/spaces_selection.ts b/x-pack/test/functional/apps/spaces/spaces_selection.ts index 7b4a1e6e2b8a0..b88311597d765 100644 --- a/x-pack/test/functional/apps/spaces/spaces_selection.ts +++ b/x-pack/test/functional/apps/spaces/spaces_selection.ts @@ -21,7 +21,7 @@ export default function spaceSelectorFunctonalTests({ ]); describe('Spaces', function() { - this.tags('smoke'); + this.tags('includeFirefox'); describe('Space Selector', () => { before(async () => { await esArchiver.load('spaces/selector'); diff --git a/x-pack/test/functional/apps/status_page/status_page.ts b/x-pack/test/functional/apps/status_page/status_page.ts index 58551aaaf4112..fa5a0c0aa0402 100644 --- a/x-pack/test/functional/apps/status_page/status_page.ts +++ b/x-pack/test/functional/apps/status_page/status_page.ts @@ -13,7 +13,7 @@ export default function statusPageFunctonalTests({ const PageObjects = getPageObjects(['security', 'statusPage', 'home']); describe('Status Page', function() { - this.tags('smoke'); + this.tags('includeFirefox'); before(async () => await esArchiver.load('empty_kibana')); after(async () => await esArchiver.unload('empty_kibana')); diff --git a/x-pack/test/functional/apps/transform/cloning.ts b/x-pack/test/functional/apps/transform/cloning.ts index e6e12f60f0bcc..4f71d81eacdf8 100644 --- a/x-pack/test/functional/apps/transform/cloning.ts +++ b/x-pack/test/functional/apps/transform/cloning.ts @@ -27,7 +27,6 @@ export default function({ getService }: FtrProviderContext) { const transform = getService('transform'); describe('cloning', function() { - this.tags(['smoke']); const transformConfig = getTransformConfig(); before(async () => { diff --git a/x-pack/test/functional/apps/transform/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation_index_pattern.ts index dbdbca0368146..0e61635fb70e4 100644 --- a/x-pack/test/functional/apps/transform/creation_index_pattern.ts +++ b/x-pack/test/functional/apps/transform/creation_index_pattern.ts @@ -18,7 +18,6 @@ export default function({ getService }: FtrProviderContext) { const transform = getService('transform'); describe('creation_index_pattern', function() { - this.tags(['smoke']); before(async () => { await esArchiver.loadIfNeeded('ml/ecommerce'); await transform.testResources.createIndexPatternIfNeeded('ft_ecommerce', 'order_date'); diff --git a/x-pack/test/functional/apps/transform/creation_saved_search.ts b/x-pack/test/functional/apps/transform/creation_saved_search.ts index fd5673de0d7a7..26aec913e4756 100644 --- a/x-pack/test/functional/apps/transform/creation_saved_search.ts +++ b/x-pack/test/functional/apps/transform/creation_saved_search.ts @@ -18,7 +18,6 @@ export default function({ getService }: FtrProviderContext) { const transform = getService('transform'); describe('creation_saved_search', function() { - this.tags(['smoke']); before(async () => { await esArchiver.loadIfNeeded('ml/farequote'); await transform.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); diff --git a/x-pack/test/functional/apps/upgrade_assistant/upgrade_assistant.ts b/x-pack/test/functional/apps/upgrade_assistant/upgrade_assistant.ts index 42118f4c63b1f..d2210bee0063d 100644 --- a/x-pack/test/functional/apps/upgrade_assistant/upgrade_assistant.ts +++ b/x-pack/test/functional/apps/upgrade_assistant/upgrade_assistant.ts @@ -14,7 +14,7 @@ export default function upgradeAssistantFunctionalTests({ const PageObjects = getPageObjects(['upgradeAssistant']); describe('Upgrade Checkup', function() { - this.tags('smoke'); + this.tags('includeFirefox'); before(async () => await esArchiver.load('empty_kibana')); after(async () => { await PageObjects.upgradeAssistant.expectTelemetryHasFinish(); diff --git a/x-pack/test/functional/apps/watcher/index.js b/x-pack/test/functional/apps/watcher/index.js index 9f0d5a5de405a..e894a890fdb50 100644 --- a/x-pack/test/functional/apps/watcher/index.js +++ b/x-pack/test/functional/apps/watcher/index.js @@ -6,7 +6,7 @@ export default function({ loadTestFile }) { describe('watcher app', function() { - this.tags(['ciGroup1', 'smoke']); + this.tags(['ciGroup1', 'includeFirefox']); //loadTestFile(require.resolve('./management')); loadTestFile(require.resolve('./watcher_test')); diff --git a/x-pack/test/functional/es_archives/fleet/agents/data.json b/x-pack/test/functional/es_archives/fleet/agents/data.json index 1ffb119ca1023..3fe4f828ba128 100644 --- a/x-pack/test/functional/es_archives/fleet/agents/data.json +++ b/x-pack/test/functional/es_archives/fleet/agents/data.json @@ -1,11 +1,11 @@ { "type": "doc", "value": { - "id": "agents:agent1", + "id": "fleet-agents:agent1", "index": ".kibana", "source": { - "type": "agents", - "agents": { + "type": "fleet-agents", + "fleet-agents": { "access_api_key_id": "api-key-2", "active": true, "shared_id": "agent1_filebeat", @@ -21,11 +21,11 @@ { "type": "doc", "value": { - "id": "agents:agent2", + "id": "fleet-agents:agent2", "index": ".kibana", "source": { - "type": "agents", - "agents": { + "type": "fleet-agents", + "fleet-agents": { "access_api_key_id": "api-key-2", "active": true, "shared_id": "agent2_filebeat", @@ -40,11 +40,11 @@ { "type": "doc", "value": { - "id": "agents:agent3", + "id": "fleet-agents:agent3", "index": ".kibana", "source": { - "type": "agents", - "agents": { + "type": "fleet-agents", + "fleet-agents": { "access_api_key_id": "api-key-3", "active": true, "shared_id": "agent3_metricbeat", @@ -59,11 +59,11 @@ { "type": "doc", "value": { - "id": "agents:agent4", + "id": "fleet-agents:agent4", "index": ".kibana", "source": { - "type": "agents", - "agents": { + "type": "fleet-agents", + "fleet-agents": { "access_api_key_id": "api-key-4", "active": true, "shared_id": "agent4_metricbeat", @@ -78,17 +78,17 @@ { "type": "doc", "value": { - "id": "enrollment_api_keys:ed22ca17-e178-4cfe-8b02-54ea29fbd6d0", + "id": "fleet-enrollment-api-keys:ed22ca17-e178-4cfe-8b02-54ea29fbd6d0", "index": ".kibana", "source": { - "enrollment_api_keys" : { + "fleet-enrollment-api-keys" : { "created_at" : "2019-10-10T16:31:12.518Z", "name": "FleetEnrollmentKey:1", "api_key_id" : "key", "config_id" : "policy:1", "active" : true }, - "type" : "enrollment_api_keys", + "type" : "fleet-enrollment-api-keys", "references": [] } } @@ -97,11 +97,11 @@ { "type": "doc", "value": { - "id": "events:event1", + "id": "fleet-agent-events:event1", "index": ".kibana", "source": { - "type": "agent_events", - "agent_events": { + "type": "fleet-agent-events", + "fleet-agent-events": { "agent_id": "agent1", "type": "STATE", "subtype": "STARTED", @@ -116,11 +116,11 @@ { "type": "doc", "value": { - "id": "events:event2", + "id": "fleet-agent-events:event2", "index": ".kibana", "source": { - "type": "agent_events", - "agent_events": { + "type": "fleet-agent-events", + "fleet-agent-events": { "agent_id": "agent1", "type": "STATE", "subtype": "STOPPED", @@ -135,11 +135,11 @@ { "type": "doc", "value": { - "id": "agent_actions:37ed51ff-e80f-4f2a-a62d-f4fa975e7d85", + "id": "fleet-agent-actions:37ed51ff-e80f-4f2a-a62d-f4fa975e7d85", "index": ".kibana", "source": { - "type": "agent_actions", - "agent_actions": { + "type": "fleet-agent-actions", + "fleet-agent-actions": { "agent_id": "agent1", "created_at": "2019-09-04T15:04:07+0000", "type": "RESUME", @@ -152,11 +152,11 @@ { "type": "doc", "value": { - "id": "agent_actions:b400439c-bbbf-43d5-83cb-cf8b7e32506f", + "id": "fleet-agent-actions:b400439c-bbbf-43d5-83cb-cf8b7e32506f", "index": ".kibana", "source": { - "type": "agent_actions", - "agent_actions": { + "type": "fleet-agent-actions", + "fleet-agent-actions": { "agent_id": "agent1", "type": "PAUSE", "created_at": "2019-09-04T15:01:07+0000", @@ -169,11 +169,11 @@ { "type": "doc", "value": { - "id": "agent_actions:48cebde1-c906-4893-b89f-595d943b72a1", + "id": "fleet-agent-actions:48cebde1-c906-4893-b89f-595d943b72a1", "index": ".kibana", "source": { - "type": "agent_actions", - "agent_actions": { + "type": "fleet-agent-actions", + "fleet-agent-actions": { "agent_id": "agent1", "type": "CONFIG_CHANGE", "created_at": "2020-03-15T03:47:15.129Z", @@ -186,11 +186,11 @@ { "type": "doc", "value": { - "id": "agent_actions:48cebde1-c906-4893-b89f-595d943b72a2", + "id": "fleet-agent-actions:48cebde1-c906-4893-b89f-595d943b72a2", "index": ".kibana", "source": { - "type": "agent_actions", - "agent_actions": { + "type": "fleet-agent-actions", + "fleet-agent-actions": { "agent_id": "agent1", "type": "CONFIG_CHANGE", "created_at": "2020-03-15T03:47:15.129Z", diff --git a/x-pack/test/functional/es_archives/fleet/agents/mappings.json b/x-pack/test/functional/es_archives/fleet/agents/mappings.json index 31ae161049303..5d5d373797d4c 100644 --- a/x-pack/test/functional/es_archives/fleet/agents/mappings.json +++ b/x-pack/test/functional/es_archives/fleet/agents/mappings.json @@ -9,7 +9,7 @@ "dynamic": "strict", "_meta": { "migrationMappingPropertyHashes": { - "outputs": "aee9782e0d500b867859650a36280165", + "ingest-outputs": "aee9782e0d500b867859650a36280165", "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", "visualization": "52d7a13ad68a150c4525b292d23e12cc", "references": "7997cf5a56cc02bdc9c93361bde732b0", @@ -23,14 +23,14 @@ "dashboard": "d00f614b29a80360e1190193fd333bab", "metrics-explorer-view": "53c5365793677328df0ccb6138bf3cdd", "siem-detection-engine-rule-actions": "90eee2e4635260f4be0a1da8f5bc0aa0", - "agent_events": "3231653fafe4ef3196fe3b32ab774bf2", + "fleet-agent-events": "3231653fafe4ef3196fe3b32ab774bf2", "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", "inventory-view": "9ecce5b58867403613d82fe496470b34", - "enrollment_api_keys": "28b91e20b105b6f928e2012600085d8f", + "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f", "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", "cases-comments": "c2061fb929f585df57425102fa928b4b", "canvas-element": "7390014e1091044523666d97247392fc", @@ -50,20 +50,20 @@ "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", "map": "23d7aa4a720d4938ccde3983f87bd58d", "uptime-dynamic-settings": "b6289473c8985c79b6c47eebc19a0ca5", - "epm-package": "75d12cd13c867fd713d7dfb27366bc20", + "epm-packages": "75d12cd13c867fd713d7dfb27366bc20", "apm-telemetry": "3525d7c22c42bc80f5e6e9cb3f2b26a2", "cases": "08b8b110dbca273d37e8aef131ecab61", "siem-ui-timeline": "ac8020190f5950dd3250b6499144e7fb", "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", "url": "c7f66a0df8b1b52f17c28c4adb111105", - "agents": "c3eeb7b9d97176f15f6d126370ab23c7", + "fleet-agents": "c3eeb7b9d97176f15f6d126370ab23c7", "migrationVersion": "4a1746014a75ade3a714e1db5763276f", "index-pattern": "66eccb05066c5a89924f48a9e9736499", "maps-telemetry": "268da3a48066123fc5baf35abaa55014", "namespace": "2f4316de49999235636386fe51dc06c1", "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", - "agent_actions": "ed270b46812f0fa1439366c428a2cf17", + "fleet-agent-actions": "ed270b46812f0fa1439366c428a2cf17", "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", "config": "ae24d22d5986d04124cc6568f771066f", @@ -107,7 +107,7 @@ } } }, - "agent_actions": { + "fleet-agent-actions": { "properties": { "agent_id": { "type": "keyword" @@ -160,7 +160,7 @@ } } }, - "agent_events": { + "fleet-agent-events": { "properties": { "action_id": { "type": "keyword" @@ -194,7 +194,7 @@ } } }, - "agents": { + "fleet-agents": { "properties": { "access_api_key_id": { "type": "keyword" @@ -1705,7 +1705,7 @@ } } }, - "enrollment_api_keys": { + "fleet-enrollment-api-keys": { "properties": { "active": { "type": "boolean" @@ -1736,7 +1736,7 @@ } } }, - "epm-package": { + "epm-packages": { "properties": { "installed": { "type": "nested", @@ -2211,7 +2211,7 @@ "namespace": { "type": "keyword" }, - "outputs": { + "ingest-outputs": { "properties": { "api_key": { "type": "keyword" diff --git a/x-pack/test/functional/services/machine_learning/job_table.ts b/x-pack/test/functional/services/machine_learning/job_table.ts index 0e638963f2367..e2451328ea941 100644 --- a/x-pack/test/functional/services/machine_learning/job_table.ts +++ b/x-pack/test/functional/services/machine_learning/job_table.ts @@ -187,44 +187,24 @@ export function MachineLearningJobTableProvider({ getService }: FtrProviderConte expectedCounts: object, expectedModelSizeStats: object ) { - const countDetails = await this.parseJobCounts(jobId); - const counts = countDetails.counts; - - // fields that have changing values are only validated - // to be present and then removed so they don't make - // the object validation fail - expect(counts).to.have.property('last_data_time'); - delete counts.last_data_time; - - expect(counts).to.eql(expectedCounts); - - const modelSizeStats = countDetails.modelSizeStats; - - // fields that have changing values are only validated - // to be present and then removed so they don't make - // the object validation fail - expect(modelSizeStats).to.have.property('log_time'); - delete modelSizeStats.log_time; - expect(modelSizeStats).to.have.property('model_bytes'); - delete modelSizeStats.model_bytes; - - // remove categorization fields from validation until - // the ES version is updated - delete modelSizeStats.categorization_status; - delete modelSizeStats.categorized_doc_count; - delete modelSizeStats.dead_category_count; - delete modelSizeStats.frequent_category_count; - delete modelSizeStats.rare_category_count; - delete modelSizeStats.total_category_count; - - // MML during clone has changed in #61589 - // TODO: adjust test code to reflect the new behavior - expect(modelSizeStats).to.have.property('model_bytes_memory_limit'); - delete modelSizeStats.model_bytes_memory_limit; - // @ts-ignore - delete expectedModelSizeStats.model_bytes_memory_limit; - - expect(modelSizeStats).to.eql(expectedModelSizeStats); + const { counts, modelSizeStats } = await this.parseJobCounts(jobId); + + // Only check for expected keys / values, ignore additional properties + // This way the tests stay stable when new properties are added on the ES side + for (const [key, value] of Object.entries(expectedCounts)) { + expect(counts) + .to.have.property(key) + .eql(value, `Expected counts property '${key}' to exist with value '${value}'`); + } + + for (const [key, value] of Object.entries(expectedModelSizeStats)) { + expect(modelSizeStats) + .to.have.property(key) + .eql( + value, + `Expected model size stats property '${key}' to exist with value '${value}')` + ); + } } public async clickActionsMenu(jobId: string) { diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index bbf8881f0c62a..597f1ad9119b0 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -68,7 +68,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await nameInput.click(); await testSubjects.click('.slack-ActionTypeSelectOption'); - await testSubjects.click('createActionConnectorButton'); + await testSubjects.click('addNewActionConnectorButton-.slack'); const slackConnectorName = generateUniqueKey(); await testSubjects.setValue('nameInput', slackConnectorName); await testSubjects.setValue('slackWebhookUrlInput', 'https://test'); diff --git a/yarn.lock b/yarn.lock index b9f80cd5923b7..f348aa99e6f20 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1192,6 +1192,32 @@ date-fns "^1.27.2" figures "^1.7.0" +"@cypress/request@2.88.5": + version "2.88.5" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.5.tgz#8d7ecd17b53a849cfd5ab06d5abe7d84976375d7" + integrity sha512-TzEC1XMi1hJkywWpRfD2clreTa/Z+lOrXDCxxBTBPEcY5azdPi56A6Xw+O4tWJnaJH3iIE7G5aDXZC6JgRZLcA== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + "@cypress/webpack-preprocessor@^4.1.0": version "4.1.0" resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-4.1.0.tgz#8c4debc0b1abf045b62524d1996dd9aeaf7e86a8" @@ -1236,10 +1262,10 @@ dependencies: "@elastic/apm-rum-core" "^5.2.0" -"@elastic/charts@18.4.1": - version "18.4.1" - resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-18.4.1.tgz#19d82c39aef347fd00b33e33b68b683ac4d745b0" - integrity sha512-vV5AAKIKbwgY923OD2Rsr77XFHmsUsWWg/aeCZvG5/b6Yb+fNgM0RF94GADiDMvRvQANhTn2CPPVvNfL18MegQ== +"@elastic/charts@18.4.2": + version "18.4.2" + resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-18.4.2.tgz#7d3c40dca8a7a701fb7227382191b84d36d8b32a" + integrity sha512-fmEDRUeFEtVWGurafhp/5bHBypOjdXiRXY074tCqnez43hA2iA4v1KrZL8tPFlMePgc/kpZf9wb8aEwxlfWumw== dependencies: classnames "^2.2.6" d3-array "^1.2.4" @@ -1538,11 +1564,11 @@ through2 "^2.0.3" "@hapi/boom@7.x.x": - version "7.4.2" - resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-7.4.2.tgz#c16957cd09796f6c1bfb4031bdc39d66d6d750c3" - integrity sha512-T2CYcTI0AqSvC6YC7keu/fh9LVSMzfoMLharBnPbOwmc+Cexj9joIc5yNDKunaxYq9LPuOwMS0f2B3S1tFQUNw== + version "7.4.11" + resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-7.4.11.tgz#37af8417eb9416aef3367aa60fa04a1a9f1fc262" + integrity sha512-VSU/Cnj1DXouukYxxkes4nNJonCnlogHvIff1v1RVoN4xzkKhMXX+GRmb3NyH1iar10I9WFPDv2JPwfH3GaV0A== dependencies: - "@hapi/hoek" "6.x.x" + "@hapi/hoek" "8.x.x" "@hapi/bourne@1.x.x": version "1.3.2" @@ -1557,11 +1583,6 @@ "@hapi/hoek" "8.x.x" fast-safe-stringify "2.x.x" -"@hapi/hoek@6.x.x": - version "6.2.1" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-6.2.1.tgz#d3a66329159af879bfdf0b0cff2229c43c5a3451" - integrity sha512-+ryw4GU9pjr1uT6lBuErHJg3NYqzwJTvZ75nKuJijEzpd00Uqi6oiawTGDDf5Hl0zWmI7qHfOtaqB0kpQZJQzA== - "@hapi/hoek@8.x.x": version "8.5.1" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.5.1.tgz#fde96064ca446dec8c55a8c2f130957b070c6e06" @@ -3704,15 +3725,20 @@ resolved "https://registry.yarnpkg.com/@types/base64-js/-/base64-js-1.2.5.tgz#582b2476169a6cba460a214d476c744441d873d5" integrity sha1-WCskdhaabLpGCiFNR2x0REHYc9U= -"@types/bluebird@*": - version "3.5.28" - resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.28.tgz#04c1a520ff076649236bc8ca21198542ce2bdb09" - integrity sha512-0Vk/kqkukxPKSzP9c8WJgisgGDx5oZDbsLLWIP5t70yThO/YleE+GEm2S1GlRALTaack3O7U5OS5qEm7q2kciA== +"@types/blob-util@1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@types/blob-util/-/blob-util-1.3.3.tgz#adba644ae34f88e1dd9a5864c66ad651caaf628a" + integrity sha512-4ahcL/QDnpjWA2Qs16ZMQif7HjGP2cw3AGjHabybjw7Vm1EKu+cfQN1D78BaZbS1WJNa1opSMF5HNMztx7lR0w== + +"@types/bluebird@*", "@types/bluebird@^3.1.1": + version "3.5.30" + resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.30.tgz#ee034a0eeea8b84ed868b1aa60d690b08a6cfbc5" + integrity sha512-8LhzvcjIoqoi1TghEkRMkbbmM+jhHnBokPGkJWjclMK+Ks0MxEBow3/p2/iFTZ+OIbJHQDSfpgdZEb+af3gfVw== -"@types/bluebird@^3.1.1": - version "3.5.20" - resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.20.tgz#f6363172add6f4eabb8cada53ca9af2781e8d6a1" - integrity sha512-Wk41MVdF+cHBfVXj/ufUHJeO3BlIQr1McbHZANErMykaCWeDSZbH5erGjNBw2/3UlRdSxZbLfSuQTzFmPOYFsA== +"@types/bluebird@3.5.29": + version "3.5.29" + resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.29.tgz#7cd933c902c4fc83046517a1bef973886d00bdb6" + integrity sha512-kmVtnxTuUuhCET669irqQmPAez4KFnFVKvpleVRyfC3g+SHD1hIkFZcWLim9BVcwUBLO59o8VZE4yGCmTif8Yw== "@types/boom@*", "@types/boom@^7.2.0": version "7.2.0" @@ -3744,6 +3770,19 @@ resolved "https://registry.yarnpkg.com/@types/catbox/-/catbox-10.0.1.tgz#266679017749041fe9873fee1131dd2aaa04a07e" integrity sha512-ECuJ+f5gGHiLeiE4RlE/xdqv/0JVDToegPV1aTb10tQStYa0Ycq2OJfQukDv3IFaw3B+CMV46jHc5bXe6QXEQg== +"@types/chai-jquery@1.1.40": + version "1.1.40" + resolved "https://registry.yarnpkg.com/@types/chai-jquery/-/chai-jquery-1.1.40.tgz#445bedcbbb2ae4e3027f46fa2c1733c43481ffa1" + integrity sha512-mCNEZ3GKP7T7kftKeIs7QmfZZQM7hslGSpYzKbOlR2a2HCFf9ph4nlMRA9UnuOETeOQYJVhJQK7MwGqNZVyUtQ== + dependencies: + "@types/chai" "*" + "@types/jquery" "*" + +"@types/chai@*", "@types/chai@4.2.7", "@types/chai@^4.2.11": + version "4.2.11" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.11.tgz#d3614d6c5f500142358e6ed24e1bf16657536c50" + integrity sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw== + "@types/chance@^1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/chance/-/chance-1.0.1.tgz#c10703020369602c40dd9428cc6e1437027116df" @@ -4181,7 +4220,7 @@ resolved "https://registry.yarnpkg.com/@types/joi/-/joi-13.6.1.tgz#325486a397504f8e22c8c551dc8b0e1d41d5d5ae" integrity sha512-JxZ0NP8NuB0BJOXi1KvAA6rySLTPmhOy4n2gzSFq/IFM3LNFm0h+2Vn/bPPgEYlWqzS2NPeLgKqfm75baX+Hog== -"@types/jquery@*", "@types/jquery@^3.3.31": +"@types/jquery@*", "@types/jquery@3.3.31", "@types/jquery@^3.3.31": version "3.3.31" resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.31.tgz#27c706e4bf488474e1cb54a71d8303f37c93451b" integrity sha512-Lz4BAJihoFw5nRzKvg4nawXPzutkv7wmfQ5121avptaSIXlDNJCUuxZxX/G+9EVidZGuO0UBlk+YjKbwRKJigg== @@ -4287,9 +4326,14 @@ "@types/lodash" "*" "@types/lodash@*", "@types/lodash@^4.14.110", "@types/lodash@^4.14.116": - version "4.14.137" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.137.tgz#8a4804937dc6462274ffcc088df8f14fc1b368e2" - integrity sha512-g4rNK5SRKloO+sUGbuO7aPtwbwzMgjK+bm9BBhLD7jGUiGR7zhwYEhSln/ihgYQBeIJ5j7xjyaYzrWTcu3UotQ== + version "4.14.150" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.150.tgz#649fe44684c3f1fcb6164d943c5a61977e8cf0bd" + integrity sha512-kMNLM5JBcasgYscD9x/Gvr6lTAv2NVgsKtet/hm93qMyf/D1pt+7jeEZklKJKxMVmXjxbRVQQGfqDSfipYCO6w== + +"@types/lodash@4.14.149": + version "4.14.149" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440" + integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ== "@types/lodash@^3.10.1": version "3.10.2" @@ -4347,7 +4391,7 @@ dependencies: "@types/mime-db" "*" -"@types/minimatch@*", "@types/minimatch@^3.0.3": +"@types/minimatch@*", "@types/minimatch@3.0.3", "@types/minimatch@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== @@ -4364,6 +4408,11 @@ dependencies: "@types/node" "*" +"@types/mocha@5.2.7": + version "5.2.7" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea" + integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ== + "@types/mocha@^7.0.2": version "7.0.2" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce" @@ -4756,11 +4805,36 @@ dependencies: "@types/node" "*" +"@types/sinon-chai@3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@types/sinon-chai/-/sinon-chai-3.2.3.tgz#afe392303dda95cc8069685d1e537ff434fa506e" + integrity sha512-TOUFS6vqS0PVL1I8NGVSNcFaNJtFoyZPXZ5zur+qlhDfOmQECZZM4H4kKgca6O8L+QceX/ymODZASfUfn+y4yQ== + dependencies: + "@types/chai" "*" + "@types/sinon" "*" + +"@types/sinon@*": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.0.tgz#5b70a360f55645dd64f205defd2a31b749a59799" + integrity sha512-v2TkYHkts4VXshMkcmot/H+ERZ2SevKa10saGaJPGCJ8vh3lKrC4u663zYEeRZxep+VbG6YRDtQ6gVqw9dYzPA== + dependencies: + "@types/sinonjs__fake-timers" "*" + +"@types/sinon@7.5.1": + version "7.5.1" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.5.1.tgz#d27b81af0d1cfe1f9b24eebe7a24f74ae40f5b7c" + integrity sha512-EZQUP3hSZQyTQRfiLqelC9NMWd1kqLcmQE0dMiklxBkgi84T+cHOhnKpgk4NnOWpGX863yE6+IaGnOXUNFqDnQ== + "@types/sinon@^7.0.13": version "7.0.13" resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.0.13.tgz#ca039c23a9e27ebea53e0901ef928ea2a1a6d313" integrity sha512-d7c/C/+H/knZ3L8/cxhicHUiTDxdgap0b/aNJfsmLwFu/iOP17mdgbQsbHA3SJmrzsjD0l3UEE5SN4xxuz5ung== +"@types/sinonjs__fake-timers@*": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz#681df970358c82836b42f989188d133e218c458e" + integrity sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA== + "@types/sizzle@*", "@types/sizzle@2.3.2": version "2.3.2" resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47" @@ -5506,11 +5580,6 @@ adm-zip@0.4.11: resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.11.tgz#2aa54c84c4b01a9d0fb89bb11982a51f13e3d62a" integrity sha512-L8vcjDTCOIJk7wFvmlEUN7AsSb8T+2JrdP7KINBjzr24TJ5Mwj590sLu3BC7zNZowvJWa/JtPmD8eJCzdtDWjA== -adm-zip@^0.4.13: - version "0.4.13" - resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.13.tgz#597e2f8cc3672151e1307d3e95cddbc75672314a" - integrity sha512-fERNJX8sOXfel6qCBCMPvZLzENBEhZTzKqg6vrOW5pvoEaQuJhRU4ndTAh6lHOxn1I6jnz2NHra56ZODM751uw== - affine-hull@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/affine-hull/-/affine-hull-1.0.0.tgz#763ff1d38d063ceb7e272f17ee4d7bbcaf905c5d" @@ -6417,11 +6486,6 @@ array-reduce@~0.0.0: resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" integrity sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys= -array-slice@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" - integrity sha1-3Tz7gO15c6dRF82sabC5nshhhvU= - array-slice@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" @@ -6453,11 +6517,6 @@ array-uniq@^1.0.0, array-uniq@^1.0.1: resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= -array-unique@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" - integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM= - array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" @@ -6684,7 +6743,7 @@ async@^2.6.0, async@^2.6.1: dependencies: lodash "^4.17.10" -async@^2.6.3: +async@^2.6.2, async@^2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== @@ -7578,7 +7637,7 @@ bluebird@3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -bluebird@^3.3.0, bluebird@^3.3.1: +bluebird@^3.3.1: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA== @@ -7773,13 +7832,6 @@ brace@0.11.1, brace@^0.11.0, brace@^0.11.1: resolved "https://registry.yarnpkg.com/brace/-/brace-0.11.1.tgz#4896fcc9d544eef45f4bb7660db320d3b379fe58" integrity sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg= -braces@^0.1.2: - version "0.1.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-0.1.5.tgz#c085711085291d8b75fdd74eab0f8597280711e6" - integrity sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY= - dependencies: - expand-range "^0.1.0" - braces@^2.3.0, braces@^2.3.1, braces@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" @@ -7796,7 +7848,7 @@ braces@^2.3.0, braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1, braces@~3.0.2: +braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -8701,7 +8753,7 @@ cheerio@^1.0.0-rc.3: lodash "^4.15.0" parse5 "^3.0.1" -chokidar@2.1.2, chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.0.4: +chokidar@2.1.2, chokidar@^2.0.2, chokidar@^2.0.4: version "2.1.2" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.2.tgz#9c23ea40b01638439e0513864d362aeacc5ad058" integrity sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg== @@ -8769,7 +8821,7 @@ chokidar@^2.0.0, chokidar@^2.1.2, chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chokidar@^3.2.2: +chokidar@^3.0.0, chokidar@^3.2.2: version "3.3.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== @@ -8848,11 +8900,6 @@ circular-json@^0.3.1: resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== -circular-json@^0.5.5: - version "0.5.9" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.9.tgz#932763ae88f4f7dead7a0d09c8a51a4743a53b1d" - integrity sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ== - class-extend@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/class-extend/-/class-extend-0.1.2.tgz#8057a82b00f53f82a5d62c50ef8cffdec6fabc34" @@ -9317,13 +9364,6 @@ colorspace@1.1.x: color "3.0.x" text-hex "1.0.x" -combine-lists@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/combine-lists/-/combine-lists-1.0.1.tgz#458c07e09e0d900fc28b70a3fec2dacd1d2cb7f6" - integrity sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y= - dependencies: - lodash "^4.5.0" - combined-stream@^1.0.5: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -9479,15 +9519,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@1.6.0, concat-stream@^1.4.7, concat-stream@~1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" - integrity sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc= - dependencies: - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - concat-stream@1.6.2, concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^1.6.0, concat-stream@^1.6.1, concat-stream@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" @@ -9498,6 +9529,15 @@ concat-stream@1.6.2, concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^ readable-stream "^2.2.2" typedarray "^0.0.6" +concat-stream@^1.4.7, concat-stream@~1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + integrity sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc= + dependencies: + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + concat-stream@~1.5.0: version "1.5.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266" @@ -9873,7 +9913,7 @@ core-js@^1.0.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= -core-js@^2.2.0, core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.1, core-js@^2.5.3, core-js@^2.6.5, core-js@^2.6.9: +core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.1, core-js@^2.5.3, core-js@^2.6.5, core-js@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== @@ -10409,13 +10449,24 @@ cypress-multi-reporters@^1.2.3: debug "^4.1.1" lodash "^4.17.11" -cypress@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.2.0.tgz#45673fb648b1a77b9a78d73e58b89ed05212d243" - integrity sha512-8LdreL91S/QiTCLYLNbIjLL8Ht4fJmu/4HGLxUI20Tc7JSfqEfCmXELrRfuPT0kjosJwJJZacdSji9XSRkPKUw== +cypress@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.4.1.tgz#f5aa1aa5f328f1299bff328103f7cbad89e80f29" + integrity sha512-LcskZ/PXRG9XTlEeeenKqz/KddT1x+7O7dqXsdKWPII01LxLNmNHIvHnlUqApchVbinJ5vir6J255CkELSeL0A== dependencies: "@cypress/listr-verbose-renderer" "0.4.1" + "@cypress/request" "2.88.5" "@cypress/xvfb" "1.2.4" + "@types/blob-util" "1.3.3" + "@types/bluebird" "3.5.29" + "@types/chai" "4.2.7" + "@types/chai-jquery" "1.1.40" + "@types/jquery" "3.3.31" + "@types/lodash" "4.14.149" + "@types/minimatch" "3.0.3" + "@types/mocha" "5.2.7" + "@types/sinon" "7.5.1" + "@types/sinon-chai" "3.2.3" "@types/sizzle" "2.3.2" arch "2.1.1" bluebird "3.7.2" @@ -10429,7 +10480,7 @@ cypress@^4.2.0: eventemitter2 "4.1.2" execa "1.0.0" executable "4.1.1" - extract-zip "1.6.7" + extract-zip "1.7.0" fs-extra "8.1.0" getos "3.1.4" is-ci "2.0.0" @@ -10438,12 +10489,11 @@ cypress@^4.2.0: listr "0.14.3" lodash "4.17.15" log-symbols "3.0.0" - minimist "1.2.2" + minimist "1.2.5" moment "2.24.0" ospath "1.2.2" pretty-bytes "5.3.0" ramda "0.26.1" - request cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16 request-progress "3.0.0" supports-color "7.1.0" tmp "0.1.0" @@ -10752,10 +10802,10 @@ date-fns@^1.27.2: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" integrity sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw== -date-format@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-1.2.0.tgz#615e828e233dd1ab9bb9ae0950e0ceccfa6ecad8" - integrity sha1-YV6CjiM90aubua4JUODOzPpuytg= +date-format@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf" + integrity sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA== date-now@^0.1.4: version "0.1.4" @@ -13000,15 +13050,6 @@ exit@^0.1.2, exit@~0.1.1: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= -expand-braces@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/expand-braces/-/expand-braces-0.1.2.tgz#488b1d1d2451cb3d3a6b192cfc030f44c5855fea" - integrity sha1-SIsdHSRRyz06axks/AMPRMWFX+o= - dependencies: - array-slice "^0.2.3" - array-unique "^0.2.1" - braces "^0.1.2" - expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" @@ -13022,14 +13063,6 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expand-range@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-0.1.1.tgz#4cb8eda0993ca56fa4f41fc42f3cbb4ccadff044" - integrity sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ= - dependencies: - is-number "^0.1.1" - repeat-string "^0.2.2" - expand-tilde@^2.0.0, expand-tilde@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" @@ -13243,27 +13276,7 @@ extract-stack@^1.0.0: resolved "https://registry.yarnpkg.com/extract-stack/-/extract-stack-1.0.0.tgz#b97acaf9441eea2332529624b732fc5a1c8165fa" integrity sha1-uXrK+UQe6iMyUpYktzL8WhyBZfo= -extract-zip@1.6.6: - version "1.6.6" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.6.tgz#1290ede8d20d0872b429fd3f351ca128ec5ef85c" - integrity sha1-EpDt6NINCHK0Kf0/NRyhKOxe+Fw= - dependencies: - concat-stream "1.6.0" - debug "2.6.9" - mkdirp "0.5.0" - yauzl "2.4.1" - -extract-zip@1.6.7: - version "1.6.7" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" - integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k= - dependencies: - concat-stream "1.6.2" - debug "2.6.9" - mkdirp "0.5.1" - yauzl "2.4.1" - -extract-zip@^1.6.6, extract-zip@^1.7.0: +extract-zip@1.7.0, extract-zip@^1.6.6, extract-zip@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA== @@ -14947,11 +14960,11 @@ gonzales-pe-sl@^4.2.3: minimist "1.1.x" gonzales-pe@^4.2.3: - version "4.2.4" - resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.2.4.tgz#356ae36a312c46fe0f1026dd6cb539039f8500d2" - integrity sha512-v0Ts/8IsSbh9n1OJRnSfa7Nlxi4AkXIsWB6vPept8FDbL4bXn3FNuxjYtO/nmBGu7GDkL9MFeGebeSu6l55EPQ== + version "4.3.0" + resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" + integrity sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ== dependencies: - minimist "1.1.x" + minimist "^1.2.5" good-listener@^1.2.2: version "1.2.2" @@ -16377,16 +16390,14 @@ idx@^2.5.6: resolved "https://registry.yarnpkg.com/idx/-/idx-2.5.6.tgz#1f824595070100ae9ad585c86db08dc74f83a59d" integrity sha512-WFXLF7JgPytbMgelpRY46nHz5tyDcedJ76pLV+RJWdb8h33bxFq4bdZau38DhNSzk5eVniBf1K3jwfK+Lb5nYA== -iedriver@^3.14.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/iedriver/-/iedriver-3.14.1.tgz#447c49be83c62d3f2f158283d58ccf7b35002be8" - integrity sha512-YyCi703BGK7R37A8QlSe2B87xgwDGGoPqBrlXe4Q68o/MNLJrR53/IpTs6J1+KKk51MLiTbWa57N7P3KZ11tow== +iedriver@^3.14.2: + version "3.14.2" + resolved "https://registry.yarnpkg.com/iedriver/-/iedriver-3.14.2.tgz#a19391ff123e21823ce0afe300e38b58a7dc79c4" + integrity sha512-vvFwfpOOZXmpXT/3Oa9SOFrr4uZNNUtBKPLRz7z8oZigvvIOokDiBlbImrd80q+rgjkmqUGi6a2NnpyCOAXnOw== dependencies: - adm-zip "^0.4.13" - extract-zip "1.6.6" + extract-zip "1.7.0" kew "~0.1.7" - md5-file "^1.1.4" - mkdirp "0.3.5" + mkdirp "0.5.4" npmconf "^2.1.3" request "^2.88.0" rimraf "~2.0.2" @@ -17319,11 +17330,6 @@ is-number-object@^1.0.4: resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== -is-number@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-0.1.1.tgz#69a7af116963d47206ec9bd9b48a14216f1e3806" - integrity sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY= - is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -17649,10 +17655,10 @@ isbinaryfile@4.0.2: resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.2.tgz#bfc45642da645681c610cca831022e30af426488" integrity sha512-C3FSxJdNrEr2F4z6uFtNzECDM5hXk+46fxaa+cwBe5/XrWSmzdG8DDgyjfX6/NRdBB21q2JXuRAzPCUs+fclnQ== -isbinaryfile@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.2.tgz#4a3e974ec0cba9004d3fc6cde7209ea69368a621" - integrity sha1-Sj6XTsDLqQBNP8bN5yCeppNopiE= +isbinaryfile@^4.0.2: + version "4.0.6" + resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b" + integrity sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg== isemail@3.x.x: version "3.1.4" @@ -18850,51 +18856,47 @@ karma-junit-reporter@1.2.0: path-is-absolute "^1.0.0" xmlbuilder "8.2.2" -karma-mocha@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/karma-mocha/-/karma-mocha-1.3.0.tgz#eeaac7ffc0e201eb63c467440d2b69c7cf3778bf" - integrity sha1-7qrH/8DiAetjxGdEDStpx883eL8= +karma-mocha@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/karma-mocha/-/karma-mocha-2.0.0.tgz#ad6b56b6a72e9b191e4c432dd30f4a44fc2435bc" + integrity sha512-qiZkZDJnn2kb9t2m4LoM4cYJHJVPoxvAYYe0B+go5s+A/3vc/3psUT05zW4yFz4vT0xHf+XzTTery8zdr8GWgA== dependencies: - minimist "1.2.0" + minimist "^1.2.3" karma-safari-launcher@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/karma-safari-launcher/-/karma-safari-launcher-1.0.0.tgz#96982a2cc47d066aae71c553babb28319115a2ce" integrity sha1-lpgqLMR9BmquccVTursoMZEVos4= -karma@3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/karma/-/karma-3.1.4.tgz#3890ca9722b10d1d14b726e1335931455788499e" - integrity sha512-31Vo8Qr5glN+dZEVIpnPCxEGleqE0EY6CtC2X9TagRV3rRQ3SNrvfhddICkJgUK3AgqpeKSZau03QumTGhGoSw== +karma@5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/karma/-/karma-5.0.2.tgz#e404373dac6e3fa08409ae4d9eda7d83adb43ee5" + integrity sha512-RpUuCuGJfN3WnjYPGIH+VBF8023Lfm3TQH6D1kcNL+FxtEPc2UUz/nVjbVAGXH4Pm+Q7FVOAQjdAeFUpXpQ3IA== dependencies: - bluebird "^3.3.0" body-parser "^1.16.1" - chokidar "^2.0.3" + braces "^3.0.2" + chokidar "^3.0.0" colors "^1.1.0" - combine-lists "^1.0.0" connect "^3.6.0" - core-js "^2.2.0" di "^0.0.1" dom-serialize "^2.2.0" - expand-braces "^0.1.1" flatted "^2.0.0" glob "^7.1.1" graceful-fs "^4.1.2" http-proxy "^1.13.0" - isbinaryfile "^3.0.0" - lodash "^4.17.5" - log4js "^3.0.0" + isbinaryfile "^4.0.2" + lodash "^4.17.14" + log4js "^4.0.0" mime "^2.3.1" minimatch "^3.0.2" - optimist "^0.6.1" qjobs "^1.1.4" range-parser "^1.2.0" rimraf "^2.6.0" - safe-buffer "^5.0.1" socket.io "2.1.1" source-map "^0.6.1" tmp "0.0.33" - useragent "2.3.0" + ua-parser-js "0.7.21" + yargs "^15.3.1" kdbush@^3.0.0: version "3.0.0" @@ -19759,7 +19761,7 @@ lodash.uniqby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" integrity sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI= -lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.6.1, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: +lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -19820,16 +19822,16 @@ log-update@^1.0.2: ansi-escapes "^1.0.0" cli-cursor "^1.0.2" -log4js@^3.0.0: - version "3.0.6" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-3.0.6.tgz#e6caced94967eeeb9ce399f9f8682a4b2b28c8ff" - integrity sha512-ezXZk6oPJCWL483zj64pNkMuY/NcRX5MPiB0zE6tjZM137aeusrOnW1ecxgF9cmwMWkBMhjteQxBPoZBh9FDxQ== +log4js@^4.0.0: + version "4.5.1" + resolved "https://registry.yarnpkg.com/log4js/-/log4js-4.5.1.tgz#e543625e97d9e6f3e6e7c9fc196dd6ab2cae30b5" + integrity sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw== dependencies: - circular-json "^0.5.5" - date-format "^1.2.0" - debug "^3.1.0" - rfdc "^1.1.2" - streamroller "0.7.0" + date-format "^2.0.0" + debug "^4.1.1" + flatted "^2.0.0" + rfdc "^1.1.4" + streamroller "^1.0.6" logform@^2.1.1: version "2.1.2" @@ -20186,11 +20188,6 @@ material-colors@^1.2.1: resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.5.tgz#5292593e6754cb1bcc2b98030e4e0d6a3afc9ea1" integrity sha1-UpJZPmdUyxvMK5gDDk4Najr8nqE= -md5-file@^1.1.4: - version "1.1.10" - resolved "https://registry.yarnpkg.com/md5-file/-/md5-file-1.1.10.tgz#d8f4fce76c92cb20b7d143a59f58ca49b4cf3174" - integrity sha1-2PT852ySyyC30UOln1jKSbTPMXQ= - md5.js@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" @@ -20661,21 +20658,11 @@ minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2 resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -minimist@1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.2.tgz#b00a00230a1108c48c169e69a291aafda3aacd63" - integrity sha512-rIqbOrKb8GJmx/5bc2M0QchhUouMXSpd1RTclXsB41JdL+VtnojfaJR+h7F9k18/4kHUsBFgk80Uk+q569vjPA== - -minimist@^1.2.5: +minimist@1.2.5, minimist@^1.2.3, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= - minimost@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/minimost/-/minimost-1.0.0.tgz#1d07954aa0268873408b95552fbffc5977dfc78b" @@ -20775,18 +20762,6 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@0.3.5, mkdirp@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" - integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc= - -mkdirp@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12" - integrity sha1-HXMHam35hs2TROFecfzAWkyavxI= - dependencies: - minimist "0.0.8" - mkdirp@0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -20801,13 +20776,18 @@ mkdirp@0.5.3: dependencies: minimist "^1.2.5" -mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@0.5.4, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== dependencies: minimist "^1.2.5" +mkdirp@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" + integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc= + mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" @@ -22140,14 +22120,6 @@ optimism@^0.9.0: dependencies: "@wry/context" "^0.4.0" -optimist@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" - integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= - dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" - optional-js@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/optional-js/-/optional-js-2.1.1.tgz#c2dc519ad119648510b4d241dbb60b1167c36a46" @@ -25139,7 +25111,7 @@ readable-stream@1.0, "readable-stream@>=1.0.33-1 <1.1.0-0": string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.3, readable-stream@~2.3.6: +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.3, readable-stream@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== @@ -25693,11 +25665,6 @@ repeat-element@^1.1.2: resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" integrity sha1-7wiaF40Ug7quTZPrmLT55OEdmQo= -repeat-string@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-0.2.2.tgz#c7a8d3236068362059a7e4651fc6884e8b1fb4ae" - integrity sha1-x6jTI2BoNiBZp+RlH8aITosftK4= - repeat-string@^1.5.0, repeat-string@^1.5.2, repeat-string@^1.5.4, repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" @@ -25900,31 +25867,6 @@ request@^2.87.0: tunnel-agent "^0.6.0" uuid "^3.1.0" -request@cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16: - version "2.88.1" - resolved "https://codeload.github.com/cypress-io/request/tar.gz/b5af0d1fa47eec97ba980cde90a13e69a2afcd16" - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - require-ancestors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/require-ancestors/-/require-ancestors-1.0.0.tgz#807831f8f8081fb12863da81ddb15c8f2a73a004" @@ -26258,10 +26200,10 @@ rework@1.0.1: convert-source-map "^0.3.3" css "^2.0.0" -rfdc@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.2.tgz#e6e72d74f5dc39de8f538f65e00c36c18018e349" - integrity sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA== +rfdc@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2" + integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug== right-align@^0.1.1: version "0.1.3" @@ -27928,15 +27870,16 @@ stream-spigot@~2.1.2: dependencies: readable-stream "~1.1.0" -streamroller@0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-0.7.0.tgz#a1d1b7cf83d39afb0d63049a5acbf93493bdf64b" - integrity sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ== +streamroller@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-1.0.6.tgz#8167d8496ed9f19f05ee4b158d9611321b8cacd9" + integrity sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg== dependencies: - date-format "^1.2.0" - debug "^3.1.0" - mkdirp "^0.5.1" - readable-stream "^2.3.0" + async "^2.6.2" + date-format "^2.0.0" + debug "^3.2.6" + fs-extra "^7.0.1" + lodash "^4.17.14" strict-uri-encode@^1.0.0: version "1.1.0" @@ -30093,10 +30036,10 @@ typings-tester@^0.3.2: dependencies: commander "^2.12.2" -ua-parser-js@^0.7.18, ua-parser-js@^0.7.9: - version "0.7.19" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b" - integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ== +ua-parser-js@0.7.21, ua-parser-js@^0.7.18, ua-parser-js@^0.7.9: + version "0.7.21" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777" + integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.5" @@ -30665,7 +30608,7 @@ user-home@^2.0.0: dependencies: os-homedir "^1.0.0" -useragent@2.3.0, useragent@^2.3.0: +useragent@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.3.0.tgz#217f943ad540cb2128658ab23fc960f6a88c9972" integrity sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw== @@ -31943,11 +31886,6 @@ wordwrap@^1.0.0, wordwrap@~1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= - worker-farm@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" @@ -32567,13 +32505,6 @@ yauzl@2.10.0, yauzl@^2.10.0: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" -yauzl@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" - integrity sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU= - dependencies: - fd-slicer "~1.0.1" - yauzl@^2.4.2: version "2.9.1" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.9.1.tgz#a81981ea70a57946133883f029c5821a89359a7f"